feat: move to openauth

pull/715/head
Travis Fischer 2025-05-24 22:02:24 +07:00
rodzic 5b23dd92ed
commit ecd8f803bc
45 zmienionych plików z 1206 dodań i 905 usunięć

2
.gitignore vendored
Wyświetl plik

@ -42,3 +42,5 @@ next-env.d.ts
old/
out/
apps/api/auth-db-temp.json

Wyświetl plik

@ -33,14 +33,15 @@
"@hono/node-server": "^1.14.1",
"@hono/sentry": "^1.2.1",
"@hono/zod-openapi": "^0.19.6",
"@openauthjs/openauth": "^0.4.3",
"@paralleldrive/cuid2": "^2.2.2",
"@redocly/openapi-core": "^1.34.3",
"@sentry/node": "^9.19.0",
"better-auth": "^1.2.8",
"eventid": "^2.0.1",
"exit-hook": "catalog:",
"hono": "^4.7.9",
"jsonwebtoken": "^9.0.2",
"octokit": "^5.0.2",
"p-all": "^5.0.0",
"parse-json": "^8.3.0",
"postgres": "^3.4.5",

Wyświetl plik

@ -1,8 +1,7 @@
import { OpenAPIHono } from '@hono/zod-openapi'
import { fromError } from 'zod-validation-error'
import type { AuthenticatedEnv, DefaultContext } from '@/lib/types'
import { auth } from '@/lib/auth'
import type { AuthenticatedEnv } from '@/lib/types'
import * as middleware from '@/lib/middleware'
import { registerOpenAPIErrorResponses } from '@/lib/openapi-utils'
@ -105,16 +104,6 @@ registerV1AdminConsumersGetConsumerByToken(privateRouter)
// Webhook event handlers
registerV1StripeWebhook(publicRouter)
// Better-Auth Handler for all auth-related routes
apiV1.on(['POST', 'GET'], 'auth/**', async (c: DefaultContext) => {
const logger = c.get('logger')
logger.info(c.req.method, c.req.url, c.req.header())
const res = await auth.handler(c.req.raw)
logger.info('auth result', res)
return res
})
// Setup routes and middleware
apiV1.route('/', publicRouter)
apiV1.use(middleware.authenticate)

Wyświetl plik

@ -4,7 +4,7 @@ import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
import type { AuthenticatedEnv } from '@/lib/types'
import { db, schema } from '@/db'
import { ensureAuthUser } from '@/lib/ensure-auth-user'
import { ensureUniqueTeamSlug } from '@/lib/ensure-unique-team-slug'
import { ensureUniqueNamespace } from '@/lib/ensure-unique-namespace'
import {
openapiAuthenticatedSecuritySchemas,
openapiErrorResponses
@ -45,7 +45,7 @@ export function registerV1TeamsCreateTeam(app: OpenAPIHono<AuthenticatedEnv>) {
const user = await ensureAuthUser(c)
const body = c.req.valid('json')
await ensureUniqueTeamSlug(body.slug)
await ensureUniqueNamespace(body.slug, { label: 'Team slug' })
return db.transaction(async (tx) => {
const [team] = await tx

Wyświetl plik

@ -5,7 +5,7 @@ import { assert, HttpError } from '@agentic/platform-core'
import { and, db, eq, schema } from '@/db'
import { setConsumerStripeSubscriptionStatus } from '@/lib/consumers/utils'
import { env, isStripeLive } from '@/lib/env'
import { stripe } from '@/lib/stripe'
import { stripe } from '@/lib/external/stripe'
const relevantStripeEvents = new Set<Stripe.Event.Type>([
'customer.subscription.updated'

Wyświetl plik

@ -0,0 +1,118 @@
import { assert, pick } from '@agentic/platform-core'
import { issuer } from '@openauthjs/openauth'
import { GithubProvider } from '@openauthjs/openauth/provider/github'
import { PasswordProvider } from '@openauthjs/openauth/provider/password'
import { MemoryStorage } from '@openauthjs/openauth/storage/memory'
import { PasswordUI } from '@openauthjs/openauth/ui/password'
import { type RawUser } from '@/db'
import { subjects } from '@/lib/auth/subjects'
import { upsertOrLinkUserAccount } from '@/lib/auth/upsert-or-link-user-account'
import { env } from '@/lib/env'
import { getGitHubClient } from '@/lib/external/github'
export const authRouter = issuer({
subjects,
storage: MemoryStorage({
persist: './auth-db-temp.json'
}),
providers: {
github: GithubProvider({
clientID: env.GITHUB_CLIENT_ID,
clientSecret: env.GITHUB_CLIENT_SECRET,
scopes: ['user:email']
}),
password: PasswordProvider(
PasswordUI({
sendCode: async (email, code) => {
// eslint-disable-next-line no-console
console.log({ email, code })
}
})
)
},
success: async (ctx, value) => {
const { provider } = value
let user: RawUser | undefined
// eslint-disable-next-line no-console
console.log('Auth success', provider, ctx, JSON.stringify(value, null, 2))
function getPartialOAuthAccount() {
assert(provider === 'github', `Unsupported provider "${provider}"`)
return {
provider,
accessToken: value.tokenset.access,
refreshToken: value.tokenset.refresh,
// `expires_in` and `refresh_token_expires_in` are given in seconds
accessTokenExpiresAt: new Date(
Date.now() + value.tokenset.raw.expires_in * 1000
),
refreshTokenExpiresAt: new Date(
Date.now() + value.tokenset.raw.refresh_token_expires_in * 1000
),
scope: (value.tokenset.raw.scope as string) || undefined
}
}
if (provider === 'github') {
const client = getGitHubClient({
accessToken: value.tokenset.access
})
const { data: ghUser } = await client.rest.users.getAuthenticated()
if (!ghUser.email) {
const { data: emails } = await client.request('GET /user/emails')
const primary = emails.find((e) => e.primary)
const verified = emails.find((e) => e.verified)
const fallback = emails.find((e) => e.email)
const email = primary?.email || verified?.email || fallback?.email
ghUser.email = email!
}
assert(
ghUser.email,
'Error authenticating with GitHub: user email is required.'
)
user = await upsertOrLinkUserAccount({
partialAccount: {
accountId: `${ghUser.id}`,
accountUsername: ghUser.login.toLowerCase(),
...getPartialOAuthAccount()
},
partialUser: {
email: ghUser.email,
emailVerified: true,
name: ghUser.name || undefined,
username: ghUser.login.toLowerCase(),
image: ghUser.avatar_url
}
})
} else if (provider === 'password') {
user = await upsertOrLinkUserAccount({
partialAccount: {
provider,
accountId: value.email
},
partialUser: {
email: value.email
}
})
} else {
assert(
user,
404,
`Authentication error: unsupported provider "${provider}"`
)
}
assert(
user,
404,
`Authentication error for provider "${provider}": User not found`
)
return ctx.subject('user', pick(user, 'id'))
}
})

Wyświetl plik

@ -0,0 +1,72 @@
import { relations } from '@fisch0920/drizzle-orm'
import { index, pgTable, text, timestamp } from '@fisch0920/drizzle-orm/pg-core'
import { userIdSchema } from '../schemas'
import {
accountPrimaryId,
authProviderTypeEnum,
createSelectSchema,
timestamps,
userId
} from './common'
import { users } from './user'
export const accounts = pgTable(
'accounts',
{
...accountPrimaryId,
...timestamps,
userId: userId()
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
provider: authProviderTypeEnum().notNull(),
/** Provider-specific account ID (or email in the case of `password` provider) */
accountId: text().notNull(),
/** Provider-specific username */
accountUsername: text(),
/** Standard OAuth2 access token */
accessToken: text(),
/** Standard OAuth2 refresh token */
refreshToken: text(),
/** Standard OAuth2 access token expires at */
accessTokenExpiresAt: timestamp(),
/** Standard OAuth2 refresh token expires at */
refreshTokenExpiresAt: timestamp(),
/** OAuth scope(s) */
scope: text()
},
(table) => [
index('account_provider_idx').on(table.provider),
index('account_userId_idx').on(table.userId),
index('account_createdAt_idx').on(table.createdAt),
index('account_updatedAt_idx').on(table.updatedAt),
index('account_deletedAt_idx').on(table.deletedAt)
]
)
export const accountsRelations = relations(accounts, ({ one }) => ({
user: one(users, {
fields: [accounts.userId],
references: [users.id]
})
}))
export const accountSelectSchema = createSelectSchema(accounts, {
userId: userIdSchema
})
.omit({
accessToken: true,
refreshToken: true,
accessTokenExpiresAt: true,
refreshTokenExpiresAt: true
})
.strip()
.openapi('Account')

Wyświetl plik

@ -1,53 +0,0 @@
import { pgTable, text, timestamp } from '@fisch0920/drizzle-orm/pg-core'
import {
accountPrimaryId,
authTimestamps,
sessionPrimaryId,
userId,
verificationPrimaryId
} from './common'
import { users } from './user'
// These tables are all managed by better-auth.
export const sessions = pgTable('sessions', {
...sessionPrimaryId,
...authTimestamps,
token: text().notNull().unique(),
expiresAt: timestamp({ mode: 'date' }).notNull(),
ipAddress: text(),
userAgent: text(),
userId: userId()
.notNull()
.references(() => users.id, { onDelete: 'cascade' })
})
export const accounts = pgTable('accounts', {
...accountPrimaryId,
...authTimestamps,
accountId: text().notNull(),
providerId: text().notNull(),
userId: userId()
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
accessToken: text(),
refreshToken: text(),
accessTokenExpiresAt: timestamp({ mode: 'date' }),
refreshTokenExpiresAt: timestamp({ mode: 'date' }),
scope: text(),
idToken: text(),
password: text()
})
export const verifications = pgTable('verifications', {
...verificationPrimaryId,
...authTimestamps,
identifier: text().notNull(),
value: text().notNull(),
expiresAt: timestamp({ mode: 'date' }).notNull()
})

Wyświetl plik

@ -14,7 +14,7 @@ import { createSchemaFactory } from '@fisch0920/drizzle-zod'
import { z } from '@hono/zod-openapi'
import { createId as createCuid2 } from '@paralleldrive/cuid2'
const usernameAndTeamSlugLength = 64 as const
export const namespaceMaxLength = 64 as const
// prefix is max 5 characters
// separator is 1 character
@ -29,18 +29,10 @@ export const idPrefixMap = {
consumer: 'csmr',
logEntry: 'log',
// better-auth
// auth
user: 'user',
account: 'acct',
session: 'sess',
verification: 'veri',
'rate-limit': 'ratel',
organization: 'org',
member: 'mem',
invitation: 'inv',
jwks: 'jwks',
passkey: 'passk',
'two-factor': '2fa'
session: 'sess'
} as const
export type ModelType = keyof typeof idPrefixMap
@ -69,9 +61,7 @@ export const consumerPrimaryId = getPrimaryId('consumer')
export const logEntryPrimaryId = getPrimaryId('logEntry')
export const teamPrimaryId = getPrimaryId('team')
export const userPrimaryId = getPrimaryId('user')
export const sessionPrimaryId = getPrimaryId('session')
export const accountPrimaryId = getPrimaryId('account')
export const verificationPrimaryId = getPrimaryId('verification')
/**
* All of our model primary ids have the following format:
@ -123,14 +113,14 @@ export function deploymentIdentifier<
export function username<U extends string, T extends Readonly<[U, ...U[]]>>(
config?: PgVarcharConfig<T | Writable<T>, never>
): PgVarcharBuilderInitial<'', Writable<T>, typeof usernameAndTeamSlugLength> {
return varchar({ length: usernameAndTeamSlugLength, ...config })
): PgVarcharBuilderInitial<'', Writable<T>, typeof namespaceMaxLength> {
return varchar({ length: namespaceMaxLength, ...config })
}
export function teamSlug<U extends string, T extends Readonly<[U, ...U[]]>>(
config?: PgVarcharConfig<T | Writable<T>, never>
): PgVarcharBuilderInitial<'', Writable<T>, typeof usernameAndTeamSlugLength> {
return varchar({ length: usernameAndTeamSlugLength, ...config })
): PgVarcharBuilderInitial<'', Writable<T>, typeof namespaceMaxLength> {
return varchar({ length: namespaceMaxLength, ...config })
}
/**
@ -157,15 +147,6 @@ export const timestamps = {
deletedAt: timestamp()
}
export const authTimestamps = {
createdAt: timestamp({ mode: 'date' })
.notNull()
.$defaultFn(() => /* @__PURE__ */ new Date()),
updatedAt: timestamp({ mode: 'date' })
.notNull()
.$defaultFn(() => /* @__PURE__ */ new Date())
}
export const userRoleEnum = pgEnum('UserRole', ['user', 'admin'])
export const teamMemberRoleEnum = pgEnum('TeamMemberRole', ['user', 'admin'])
export const logEntryTypeEnum = pgEnum('LogEntryType', ['log'])
@ -183,6 +164,10 @@ export const pricingIntervalEnum = pgEnum('PricingInterval', [
'year'
])
export const pricingCurrencyEnum = pgEnum('PricingCurrency', ['usd'])
export const authProviderTypeEnum = pgEnum('AuthProviderType', [
'github',
'password'
])
export const { createInsertSchema, createSelectSchema, createUpdateSchema } =
createSchemaFactory({

Wyświetl plik

@ -1,4 +1,4 @@
export * from './auth'
export * from './account'
export * from './common'
export * from './consumer'
export * from './deployment'

Wyświetl plik

@ -1,226 +0,0 @@
// TODO: Currently unused after forking @fisch0920/drizzle-zod.
export function makeNullablePropsOptional<Schema extends z.AnyZodObject>(
schema: Schema
): z.ZodObject<{
[key in keyof Schema['shape']]: Schema['shape'][key] extends z.ZodNullable<
infer T
>
? z.ZodOptional<T>
: Schema['shape'][key]
}> {
const entries = Object.entries(schema.shape)
const newProps: any = {}
for (const [key, value] of entries) {
newProps[key] =
value instanceof z.ZodNullable ? value.unwrap().optional() : value
return newProps
}
return z.object(newProps) as any
}
export type ColumnType =
// string
| 'text'
| 'varchar'
| 'timestamp'
| 'stripeId'
| 'projectId'
| 'deploymentId'
| 'cuid'
// boolean
| 'boolean'
// number
| 'integer'
| 'smallint'
| 'bigint'
// json
| 'json'
| 'jsonb'
export type ColumnTypeToTSType<T extends ColumnType> = T extends
| 'text'
| 'varchar'
| 'timestamp'
| 'cuid'
| 'stripeId'
| 'projectId'
| 'deploymentId'
? string
: T extends 'boolean'
? boolean
: T extends 'integer' | 'smallint' | 'bigint'
? number
: never
/**
* @see https://github.com/drizzle-team/@fisch0920/drizzle-orm/issues/2745
*/
function optional<
T extends ColumnType,
InferredType extends
| string
| boolean
| number
| object = ColumnTypeToTSType<T>
>(dataType: T) {
return customType<{
data: InferredType | undefined
driverData: InferredType | null
config: T extends 'stripeId'
? {
length: number
}
: never
}>({
dataType() {
if (dataType === 'stripeId') {
return 'varchar({ length: 255 })'
}
if (dataType === 'cuid') {
return 'varchar({ length: 24 })'
}
if (dataType === 'projectId') {
return 'varchar({ length: 130 })'
}
if (dataType === 'deploymentId') {
return 'varchar({ length: 160 })'
}
if (dataType === 'timestamp') {
return 'timestamp({ mode: "string" })'
}
return dataType
},
fromDriver: (v) => v ?? undefined,
toDriver: (v) => v ?? null
})
}
export const optionalText = optional('text')
export const optionalTimestamp = optional('timestamp')
export const optionalBoolean = optional('boolean')
export const optionalVarchar = optional('varchar')
export const optionalCuid = optional('cuid')
export const optionalStripeId = optional('stripeId')
export const optionalProjectId = optional('projectId')
export const optionalDeploymentId = optional('deploymentId')
// ---
type MaybePromise<T> = Promise<T> | T;
type RequestTypes = {
body?: ZodRequestBody;
params?: ZodType;
query?: ZodType;
cookies?: ZodType;
headers?: ZodType | ZodType[];
};
type InputTypeBase<R extends RouteConfig, Part extends string, Type extends keyof ValidationTargets> = R['request'] extends RequestTypes ? RequestPart<R, Part> extends ZodType ? {
in: {
[K in Type]: HasUndefined<ValidationTargets[K]> extends true ? {
[K2 in keyof z.input<RequestPart<R, Part>>]?: z.input<RequestPart<R, Part>>[K2];
} : {
[K2 in keyof z.input<RequestPart<R, Part>>]: z.input<RequestPart<R, Part>>[K2];
};
};
out: {
[K in Type]: z.output<RequestPart<R, Part>>;
};
} : {} : {};
type InputTypeParam<R extends RouteConfig> = InputTypeBase<R, 'params', 'param'>;
type InputTypeQuery<R extends RouteConfig> = InputTypeBase<R, 'query', 'query'>;
type InputTypeHeader<R extends RouteConfig> = InputTypeBase<R, 'headers', 'header'>;
type InputTypeCookie<R extends RouteConfig> = InputTypeBase<R, 'cookies', 'cookie'>;
type o: <R extends RouteConfig, I extends Input = InputTypeParam<R> & InputTypeQuery<R> & InputTypeHeader<R> & InputTypeCookie<R> & InputTypeForm<R> & InputTypeJson<R>, P extends string = ConvertPathType<R["path"]>>({ middleware: routeMiddleware, hide, ...route }: R, handler: Handler<R["middleware"] extends MiddlewareHandler[] | MiddlewareHandler ? RouteMiddlewareParam<R>["env"] & E : E, P, I, R extends {
responses: {
[statusCode: number]: {
content: {
[mediaType: string]: ZodMediaTypeObject;
};
};
};
} ? MaybePromise<RouteConfigToTypedResponse<R>> : MaybePromise<RouteConfigToTypedResponse<R>> | MaybePromise<Response>>) => OpenAPIHono<E, S & ToSchema<R["method"], MergePath<BasePath, P>, I, RouteConfigToTypedResponse<R>>, BasePath>;
// ---
import type { SetOptional, SetRequired, Simplify } from 'type-fest'
import type { AnyZodObject } from 'zod'
import { createRoute, type RouteConfig } from '@hono/zod-openapi'
export type CreateOpenAPIHonoRouteOpts<
TAuthenticated extends boolean = true,
TMethod extends RouteConfig['method'] = RouteConfig['method']
> = Simplify<
{
authenticated: TAuthenticated
paramsSchema?: AnyZodObject
bodySchema?: AnyZodObject
responseSchema: AnyZodObject
method: TMethod
} & SetRequired<
Omit<
SetOptional<Parameters<typeof createRoute>[0], 'responses'>,
'request' | 'responses' | 'security'
>,
'path' | 'operationId' | 'tags' | 'description'
>
>
export function createOpenAPIHonoRoute<TAuthenticated extends boolean>(
opts: CreateOpenAPIHonoRouteOpts<TAuthenticated>
) {
const {
authenticated,
paramsSchema,
bodySchema,
responseSchema,
...createRouteOpts
} = opts
return createRoute({
...createRouteOpts,
security: authenticated
? [
{
bearerAuth: []
}
]
: [],
request: {
params: paramsSchema!,
body: bodySchema
? {
required: true,
content: {
'application/json': {
schema: bodySchema
}
}
}
: undefined
},
responses: {
200: {
description: responseSchema.shape.description,
content: {
'application/json': {
schema: responseSchema
}
}
}
// TODO
// ...openppiErrorResponses
}
})
}

Wyświetl plik

@ -1,3 +1,4 @@
import { relations } from '@fisch0920/drizzle-orm'
import {
boolean,
index,
@ -6,11 +7,12 @@ import {
uniqueIndex
} from '@fisch0920/drizzle-orm/pg-core'
import { accounts } from './account'
import {
authTimestamps,
createSelectSchema,
createUpdateSchema,
stripeId,
timestamps,
username,
// username,
userPrimaryId,
@ -23,17 +25,16 @@ export const users = pgTable(
'users',
{
...userPrimaryId,
...authTimestamps,
...timestamps,
name: text().notNull(),
username: username().unique().notNull(),
role: userRoleEnum().default('user').notNull(),
name: text(),
email: text().notNull().unique(),
emailVerified: boolean().default(false).notNull(),
image: text(),
// TODO: re-add username
username: username().unique(),
role: userRoleEnum().default('user').notNull(),
isStripeConnectEnabledByDefault: boolean().default(true).notNull(),
stripeCustomerId: stripeId()
@ -47,6 +48,10 @@ export const users = pgTable(
]
)
export const usersRelations = relations(users, ({ many }) => ({
accounts: many(accounts)
}))
export const userSelectSchema = createSelectSchema(users)
.strip()
.openapi('User')

Wyświetl plik

@ -78,4 +78,5 @@ export type RawConsumerUpdate = Partial<
export type LogEntry = z.infer<typeof schema.logEntrySelectSchema>
export type RawLogEntry = InferSelectModel<typeof schema.logEntries>
export type RawSession = InferSelectModel<typeof schema.sessions>
export type Account = z.infer<typeof schema.accountSelectSchema>
export type RawAccount = InferSelectModel<typeof schema.accounts>

Wyświetl plik

@ -1,75 +0,0 @@
import { validators } from '@agentic/platform-validators'
import { betterAuth } from 'better-auth'
import { drizzleAdapter } from 'better-auth/adapters/drizzle'
import { username } from 'better-auth/plugins'
import { createIdForModel, db, type ModelType } from '@/db'
import { env } from './env'
export const auth = betterAuth({
appName: 'Agentic',
basePath: '/v1/auth',
database: drizzleAdapter(db, {
provider: 'pg'
}),
trustedOrigins: [
// Used for an oauth redirect when authenticating via the CLI
'http://localhost:6013'
],
emailAndPassword: {
enabled: true
},
socialProviders: {
github: {
clientId: env.GITHUB_CLIENT_ID,
clientSecret: env.GITHUB_CLIENT_SECRET
}
},
user: {
modelName: 'users',
additionalFields: {
role: {
type: 'string',
required: false,
defaultValue: 'user',
input: false // don't allow user to set role
},
// username: {
// type: 'string',
// required: false
// },
stripeCustomerId: {
type: 'string',
required: false
}
}
},
session: {
modelName: 'sessions',
cookieCache: {
enabled: true,
maxAge: 10 * 60 // 10 minutes in seconds
}
},
account: {
modelName: 'accounts',
accountLinking: {
enabled: true,
trustedProviders: ['github']
}
},
verification: {
modelName: 'verifications'
},
advanced: {
database: {
generateId: ({ model }) => createIdForModel(model as ModelType)
}
},
plugins: [
username({
usernameValidator: validators.username
})
]
})

Wyświetl plik

@ -0,0 +1,6 @@
import { createClient as createAuthClient } from '@openauthjs/openauth/client'
export const authClient = createAuthClient({
issuer: 'http://localhost:3000',
clientID: 'agentic-internal-api-client'
})

Wyświetl plik

@ -0,0 +1,10 @@
import { createSubjects } from '@openauthjs/openauth/subject'
import { z } from 'zod'
import { userIdSchema } from '@/db'
export const subjects = createSubjects({
user: z.object({
id: userIdSchema
})
})

Wyświetl plik

@ -0,0 +1,144 @@
import type { SetRequired, Simplify } from 'type-fest'
import { assert } from '@agentic/platform-core'
import { and, db, eq, type RawAccount, type RawUser, schema } from '@/db'
import { getUniqueNamespace } from '../ensure-unique-namespace'
/**
* After a user completes an authentication flow, we'll have partial account info
* and partial suer info. This function takes these partial values and maps them
* to a valid database Account and User.
*
* This will result in the Account being upserted, and may result in a new User
* being created.
*/
export async function upsertOrLinkUserAccount({
partialAccount,
partialUser
}: {
partialAccount: Simplify<
SetRequired<
Partial<
Pick<
RawAccount,
| 'provider'
| 'accountId'
| 'accountUsername'
| 'accessToken'
| 'refreshToken'
| 'accessTokenExpiresAt'
| 'refreshTokenExpiresAt'
| 'scope'
>
>,
'provider' | 'accountId'
>
>
partialUser: Simplify<
SetRequired<
Partial<
Pick<RawUser, 'email' | 'name' | 'username' | 'image' | 'emailVerified'>
>,
'email'
>
>
}): Promise<RawUser> {
const { provider, accountId } = partialAccount
const [existingAccount, existingUser] = await Promise.all([
db.query.accounts.findFirst({
where: and(
eq(schema.accounts.provider, provider),
eq(schema.accounts.accountId, accountId)
),
with: {
user: true
}
}),
db.query.users.findFirst({
where: eq(schema.users.email, partialUser.email)
})
])
if (existingAccount && existingUser) {
// Happy path case: the user is just logging in with an existing account
// that's already linked to a user.
assert(
existingAccount.userId === existingUser.id,
`Error authenticating with ${provider}: Account id "${existingAccount.id}" user id "${existingAccount.userId}" does not match expected user id "${existingUser.id}"`
)
// Udate the account with the up-to-date provider data, including any OAuth
// tokens.
await db
.update(schema.accounts)
.set(partialAccount)
.where(eq(schema.accounts.id, existingAccount.id))
return existingUser
} else if (existingUser && !existingAccount) {
// Linking a new account to an existing user
await db.insert(schema.accounts).values({
...partialAccount,
userId: existingUser.id
})
// TODO: Same caveat as below: if the existing user has a different email than
// the one in the account we're linking, we should throw an error unless it's
// a "trusted" provider.
if (provider === 'password' && existingUser.email !== partialUser.email) {
await db
.update(schema.users)
.set(partialUser)
.where(eq(schema.users.id, existingUser.id))
}
return existingUser
} else if (existingAccount && !existingUser) {
assert(
existingAccount.user,
404,
`Error authenticating with ${provider}: Account id "${existingAccount.id}" is linked to a user with a different email address than their ${provider} account, but the linked account user id "${existingAccount.userId}" is not found.`
)
// Existing account is linked to a user with a different email address than
// this provider account. This should be fine since it's pretty common for
// users to have multiple email addresses, but we may want to limit the
// ability to automatically link accounts like this in the future to only
// certain, trusted providers like `better-auth` does.
return existingAccount.user
} else {
const username = await getUniqueNamespace(
partialUser.username || partialUser.email.split('@')[0]!.toLowerCase(),
{ label: 'Username' }
)
// This is a user's first time signing up with the platform, so create both
// a new user and linked account.
return db.transaction(async (tx) => {
// Create a new user
const [user] = await tx
.insert(schema.users)
.values({
...partialUser,
username
})
.returning()
assert(
user,
500,
`Error creating new user during ${provider} authentication`
)
// Create a new account linked to the new user
await tx.insert(schema.accounts).values({
...partialAccount,
userId: user.id
})
return user
})
}
}

Wyświetl plik

@ -2,7 +2,7 @@ import type Stripe from 'stripe'
import { assert } from '@agentic/platform-core'
import { db, eq, type RawConsumer, type RawProject, schema } from '@/db'
import { stripe } from '@/lib/stripe'
import { stripe } from '@/lib/external/stripe'
// TODO: Update this for the new / updated Stripe Connect API

Wyświetl plik

@ -4,7 +4,7 @@ import { assert } from '@agentic/platform-core'
import type { AuthenticatedContext } from '@/lib/types'
import { db, eq, type RawUser, schema } from '@/db'
import { ensureAuthUser } from '@/lib/ensure-auth-user'
import { stripe } from '@/lib/stripe'
import { stripe } from '@/lib/external/stripe'
export async function upsertStripeCustomer(ctx: AuthenticatedContext): Promise<{
user: RawUser

Wyświetl plik

@ -14,7 +14,7 @@ import {
type RawProject,
schema
} from '@/db'
import { stripe } from '@/lib/stripe'
import { stripe } from '@/lib/external/stripe'
/**
* Upserts all the Stripe resources corresponding to a Deployment's pricing

Wyświetl plik

@ -13,7 +13,7 @@ import {
type RawUser,
schema
} from '@/db'
import { stripe } from '@/lib/stripe'
import { stripe } from '@/lib/external/stripe'
import { setConsumerStripeSubscriptionStatus } from '../consumers/utils'

Wyświetl plik

@ -0,0 +1,55 @@
import { assert, sha256 } from '@agentic/platform-core'
import { db, eq, schema } from '@/db'
export async function ensureUniqueNamespace(
namespace: string,
{ label = 'Namespace' }: { label?: string } = {}
) {
namespace = namespace.toLocaleLowerCase()
const [existingTeam, existingUser] = await Promise.all([
db.query.teams.findFirst({
where: eq(schema.teams.slug, namespace)
}),
db.query.users.findFirst({
where: eq(schema.users.username, namespace)
})
])
assert(
!existingUser && !existingTeam,
409,
`${label} "${namespace}" is not available`
)
}
export async function getUniqueNamespace(
namespace?: string,
{ label = 'Namespace' }: { label?: string } = {}
) {
namespace ??= `${label}_${sha256().slice(0, 24)}`
namespace = namespace
.replaceAll(/[^a-zA-Z0-9_-]/g, '')
.toLowerCase()
.slice(0, schema.namespaceMaxLength - 1)
let currentNamespace = namespace
let attempts = 0
do {
try {
await ensureUniqueNamespace(namespace, { label })
return currentNamespace
} catch (err) {
if (++attempts > 10) {
throw err
}
const suffix = sha256().slice(0, 8)
currentNamespace = `${namespace.slice(0, schema.namespaceMaxLength - 1 - suffix.length)}${suffix}`
}
} while (true)
}

Wyświetl plik

@ -1,23 +0,0 @@
import { assert } from '@agentic/platform-core'
import { db, eq, schema } from '@/db'
export async function ensureUniqueTeamSlug(slug: string) {
slug = slug.toLocaleLowerCase()
const [existingTeam, existingUser] = await Promise.all([
db.query.teams.findFirst({
where: eq(schema.teams.slug, slug)
}),
db.query.users.findFirst({
where: eq(schema.users.username, slug)
})
])
assert(
!existingUser && !existingTeam,
409,
`Team slug "${slug}" is not available`
)
}

Wyświetl plik

@ -0,0 +1,9 @@
import { Octokit } from 'octokit'
export function getGitHubClient({
accessToken
}: {
accessToken: string
}): Octokit {
return new Octokit({ auth: accessToken })
}

Wyświetl plik

@ -1,6 +1,6 @@
import Stripe from 'stripe'
import { env } from './env'
import { env } from '@/lib/env'
export const stripe = new Stripe(env.STRIPE_SECRET_KEY, {
apiVersion: '2025-04-30.basil'

Wyświetl plik

@ -1,38 +1,48 @@
import { assert } from '@agentic/platform-core'
// import { auth } from '@/lib/auth'
import { createMiddleware } from 'hono/factory'
// import * as jwt from 'hono/jwt'
import type { AuthenticatedEnv } from '@/lib/types'
import { auth } from '@/lib/auth'
import { authClient } from '@/lib/auth/client'
import { subjects } from '@/lib/auth/subjects'
export const authenticate = createMiddleware<AuthenticatedEnv>(
async function authenticateMiddleware(ctx, next) {
const session = await auth.api.getSession({
// TODO: investigate this type issue
headers: ctx.req.raw.headers as any
})
assert(session, 401, 'Unauthorized')
assert(session.user?.id, 401, 'Unauthorized')
assert(session.session, 401, 'Unauthorized')
// TODO
// const session = await auth.api.getSession({
// // TODO: investigate this type issue
// headers: ctx.req.raw.headers as any
// })
// assert(session, 401, 'Unauthorized')
// assert(session.user?.id, 401, 'Unauthorized')
// assert(session.session, 401, 'Unauthorized')
ctx.set('userId', session.user.id)
ctx.set('user', session.user as any) // TODO: resolve AuthUser and RawUser types
ctx.set('session', session.session)
// ctx.set('userId', session.user.id)
// ctx.set('user', session.user as any) // TODO: resolve AuthUser and RawUser types
// ctx.set('session', session.session)
await next()
// await next()
// const credentials = ctx.req.raw.headers.get('Authorization')
// assert(credentials, 401, 'Unauthorized')
const credentials = ctx.req.raw.headers.get('Authorization')
assert(credentials, 401, 'Unauthorized')
// const parts = credentials.split(/\s+/)
// assert(
// parts.length === 1 ||
// (parts.length === 2 && parts[0]?.toLowerCase() === 'bearer'),
// 401,
// 'Unauthorized'
// )
// const token = parts.at(-1)
// assert(token, 401, 'Unauthorized')
const parts = credentials.split(/\s+/)
assert(
parts.length === 1 ||
(parts.length === 2 && parts[0]?.toLowerCase() === 'bearer'),
401,
'Unauthorized'
)
const token = parts.at(-1)
assert(token, 401, 'Unauthorized')
const verified = await authClient.verify(subjects, token)
assert(!verified.err, 401, 'Unauthorized')
const userId = verified.subject.properties.id
assert(userId, 401, 'Unauthorized')
ctx.set('userId', userId)
// const payload = await jwt.verify(token, env.JWT_SECRET)
// assert(payload, 401, 'Unauthorized')
@ -50,6 +60,6 @@ export const authenticate = createMiddleware<AuthenticatedEnv>(
// assert(user, 401, 'Unauthorized')
// ctx.set('user', user as any)
// await next()
await next()
}
)

Wyświetl plik

@ -2,7 +2,6 @@ import type { Context } from 'hono'
import type { RawTeamMember, RawUser } from '@/db'
import type { auth } from './auth'
import type { Env } from './env'
import type { Logger } from './logger'
@ -11,9 +10,6 @@ export type { OpenAPI3 as LooseOpenAPI3Spec } from 'openapi-typescript'
export type Environment = Env['NODE_ENV']
export type Service = 'api'
// export type AuthUser = typeof auth.$Infer.Session.user
export type AuthSession = typeof auth.$Infer.Session.session
export type DefaultEnvVariables = {
requestId: string
logger: Logger
@ -22,7 +18,6 @@ export type DefaultEnvVariables = {
export type AuthenticatedEnvVariables = DefaultEnvVariables & {
userId: string
user?: RawUser
session?: AuthSession
teamMember?: RawTeamMember
}

Wyświetl plik

@ -1,4 +1,4 @@
import '@/lib/sentry'
import '@/lib/external/sentry'
import { serve } from '@hono/node-server'
import { sentry } from '@hono/sentry'
@ -10,6 +10,7 @@ import { apiV1 } from '@/api-v1'
import { env } from '@/lib/env'
import * as middleware from '@/lib/middleware'
import { authRouter } from './auth'
import { initExitHooks } from './lib/exit-hooks'
export const app = new OpenAPIHono()
@ -31,6 +32,8 @@ app.use(middleware.accessLogger)
app.use(middleware.responseTime)
app.use(middleware.errorHandler)
app.route('', authRouter)
app.route('/v1', apiV1)
app.doc31('/docs', {

Wyświetl plik

@ -24,9 +24,10 @@
},
"dependencies": {
"@agentic/platform-core": "workspace:*",
"better-auth": "^1.2.8",
"@openauthjs/openauth": "^0.4.3",
"ky": "catalog:",
"type-fest": "catalog:"
"type-fest": "catalog:",
"zod": "catalog:"
},
"devDependencies": {
"openapi-typescript": "^7.8.0"

Wyświetl plik

@ -1,61 +1,153 @@
import type { Simplify } from 'type-fest'
import { assert, getEnv, sanitizeSearchParams } from '@agentic/platform-core'
import { createAuthClient } from 'better-auth/client'
import { username } from 'better-auth/plugins'
import { assert, sanitizeSearchParams } from '@agentic/platform-core'
import {
type Client as AuthClient,
createClient as createAuthClient
} from '@openauthjs/openauth/client'
import defaultKy, { type KyInstance } from 'ky'
import type { operations } from './openapi'
import type { AuthSession } from './types'
import type {
AuthorizeResult,
AuthTokens,
AuthUser,
OnUpdateAuthSessionFunction
} from './types'
import { subjects } from './subjects'
export class AgenticApiClient {
static readonly DEFAULT_API_BASE_URL = 'https://api.agentic.so'
public readonly apiBaseUrl: string
public readonly authClient: ReturnType<typeof createAuthClient>
public ky: KyInstance
public readonly ky: KyInstance
public readonly onUpdateAuth?: OnUpdateAuthSessionFunction
protected _authTokens?: Readonly<AuthTokens>
protected _authClient: AuthClient
constructor({
apiCookie = getEnv('AGENTIC_API_COOKIE'),
apiBaseUrl = AgenticApiClient.DEFAULT_API_BASE_URL,
ky = defaultKy
ky = defaultKy,
onUpdateAuth
}: {
apiCookie?: string
apiBaseUrl?: string
ky?: KyInstance
onUpdateAuth?: OnUpdateAuthSessionFunction
}) {
assert(apiBaseUrl, 'AgenticApiClient missing required "apiBaseUrl"')
this.apiBaseUrl = apiBaseUrl
this.onUpdateAuth = onUpdateAuth
this._authClient = createAuthClient({
issuer: apiBaseUrl,
clientID: 'agentic-api-client'
})
this.ky = ky.extend({
prefixUrl: apiBaseUrl,
// headers: { Authorization: `Bearer ${apiKey}` }
headers: { cookie: apiCookie }
})
this.authClient = createAuthClient({
baseURL: `${apiBaseUrl}/v1/auth`,
plugins: [username()]
hooks: {
beforeRequest: [
async (request) => {
// Always verify freshness of auth tokens before making a request
await this.verifyAuthAndRefreshIfNecessary()
assert(this._authTokens, 'Not authenticated')
request.headers.set(
'Authorization',
`Bearer ${this._authTokens.access}`
)
}
]
}
})
}
async getAuthSession(cookie?: string): Promise<AuthSession> {
return this.ky
.get('v1/auth/get-session', cookie ? { headers: { cookie } } : {})
.json<AuthSession>()
get isAuthenticated(): boolean {
return !!this._authTokens
}
async setAuthSession(cookie: string): Promise<AuthSession> {
this.ky = this.ky.extend({
headers: { cookie }
})
return this.getAuthSession()
get authTokens(): Readonly<AuthTokens> | undefined {
return this._authTokens
}
async clearAuthSession(): Promise<void> {
this.ky = this.ky.extend({
headers: {}
async setRefreshAuthToken(refreshToken: string): Promise<void> {
const result = await this._authClient.refresh(refreshToken)
if (result.err) {
throw result.err
}
this._authTokens = result.tokens
}
async verifyAuthAndRefreshIfNecessary(): Promise<AuthUser> {
if (!this._authTokens) {
throw new Error('This method requires authentication.')
}
const verified = await this._authClient.verify(
subjects,
this._authTokens.access,
{
refresh: this._authTokens.refresh
}
)
if (verified.err) {
throw verified.err
}
if (verified.tokens) {
this._authTokens = verified.tokens
}
this.onUpdateAuth?.({
session: this._authTokens,
user: verified.subject.properties
})
return verified.subject.properties
}
async exchangeAuthCode({
code,
redirectUri,
verifier
}: {
code: string
redirectUri: string
verifier?: string
}): Promise<AuthUser> {
const result = await this._authClient.exchange(code, redirectUri, verifier)
if (result.err) {
throw result.err
}
this._authTokens = result.tokens
return this.verifyAuthAndRefreshIfNecessary()
}
async initAuthFlow({
redirectUri,
provider
}: {
redirectUri: string
provider: 'github' | 'password'
}): Promise<AuthorizeResult> {
return this._authClient.authorize(redirectUri, 'code', {
provider
})
}
async logout(): Promise<void> {
this._authTokens = undefined
this.onUpdateAuth?.()
}
async getMe(): Promise<OperationResponse<'getUser'>> {
const user = await this.verifyAuthAndRefreshIfNecessary()
return this.ky.get(`v1/users/${user.id}`).json()
}
async getUser({

Wyświetl plik

@ -0,0 +1,10 @@
import { createSubjects } from '@openauthjs/openauth/subject'
import { z } from 'zod'
// TODO: share this with the API server
export const subjects = createSubjects({
user: z.object({
id: z.string()
})
})
export type AuthUser = z.infer<typeof subjects.user>

Wyświetl plik

@ -1,4 +1,7 @@
import type { Tokens as AuthTokens } from '@openauthjs/openauth/client'
import type { components } from './openapi'
import type { AuthUser } from './subjects'
export type Consumer = components['schemas']['Consumer']
export type Project = components['schemas']['Project']
@ -23,30 +26,13 @@ export type PricingPlanName = components['schemas']['name']
export type PricingPlanSlug = components['schemas']['slug']
export type PricingPlanLabel = components['schemas']['label']
export type AuthSession = {
session: Session
export type { AuthUser } from './subjects'
export type {
AuthorizeResult,
Tokens as AuthTokens
} from '@openauthjs/openauth/client'
export type OnUpdateAuthSessionFunction = (update?: {
session: AuthTokens
user: AuthUser
}
export interface Session {
id: string
token: string
userId: string
ipAddress?: string | null
userAgent?: string | null
expiresAt: string
createdAt: string
updatedAt: string
}
export interface AuthUser {
id: string
name: string
role: string
username?: string
email: string
emailVerified: boolean
image?: string
createdAt: string
updatedAt: string
}
}) => unknown

Wyświetl plik

@ -1,35 +0,0 @@
import type {
AgenticApiClient,
AuthSession
} from '@agentic/platform-api-client'
import { assert } from '@agentic/platform-core'
import { AuthStore } from './store'
export async function authWithEmailPassword({
client,
email,
password
}: {
client: AgenticApiClient
email: string
password: string
}): Promise<AuthSession> {
let cookie: string | undefined
await client.authClient.signIn.email({
email,
password,
fetchOptions: {
onSuccess: ({ response }) => {
cookie = response.headers.get('set-cookie')!
}
}
})
assert(cookie, 'Failed to get auth cookie')
const session = await client.setAuthSession(cookie)
assert(session, 'Failed to get auth session')
AuthStore.setAuth({ cookie, session })
return session
}

Wyświetl plik

@ -1,95 +0,0 @@
import type {
AgenticApiClient,
AuthSession
} from '@agentic/platform-api-client'
import { assert } from '@agentic/platform-core'
import { serve } from '@hono/node-server'
import getPort from 'get-port'
import { Hono } from 'hono'
import open from 'open'
import { oraPromise } from 'ora'
import { client } from './client'
import { AuthStore } from './store'
export async function authWithGitHub({
preferredPort = 6013
}: {
client: AgenticApiClient
preferredPort?: number
}): Promise<AuthSession> {
const port = await getPort({ port: preferredPort })
const app = new Hono()
if (port !== preferredPort) {
throw new Error(
`Port ${preferredPort} is required to authenticate with GitHub, but it is already in use.`
)
}
let _resolveAuth: any
let _rejectAuth: any
const authP = new Promise<AuthSession>((resolve, reject) => {
_resolveAuth = resolve
_rejectAuth = reject
})
app.get('/callback/github/success', async (c) => {
const cookie = c.req.header().cookie
assert(cookie, 'Missing required auth cookie header')
const session = await client.setAuthSession(cookie)
assert(session, 'Failed to get auth session')
AuthStore.setAuth({ cookie, session })
_resolveAuth(session)
return c.text(
'Huzzah! You are now signed in to the Agentic CLI with GitHub.\n\nYou may close this browser tab. 😄'
)
})
const server = serve({
fetch: app.fetch,
port
})
// TODO: clean these up
process.on('SIGINT', () => {
server.close()
process.exit(0)
})
process.on('SIGTERM', () => {
server.close((err) => {
if (err) {
console.error(err)
process.exit(1)
}
process.exit(0)
})
})
const res = await client.authClient.signIn.social({
provider: 'github',
// TODO: add error url as well
callbackURL: `http://localhost:${port}/callback/github/success`
})
assert(
!res.error,
['Error signing in with GitHub', res.error?.code, res.error?.message]
.filter(Boolean)
.join(', ')
)
assert(res.data?.url, 'No URL returned from authClient.signIn.social')
await open(res.data.url)
const session = await oraPromise(authP, {
text: 'Authenticating with GitHub',
successText: 'You are now signed in with GitHub.',
failText: 'Failed to authenticate with GitHub.'
})
server.close()
return session
}

Wyświetl plik

@ -0,0 +1,108 @@
import type { AgenticApiClient } from '@agentic/platform-api-client'
import { assert } from '@agentic/platform-core'
import { serve } from '@hono/node-server'
import getPort from 'get-port'
import { Hono } from 'hono'
import open from 'open'
import { oraPromise } from 'ora'
import type { AuthSession } from './types'
import { AuthStore } from './store'
const providerToLabel = {
github: 'GitHub',
password: 'email and password'
}
export async function auth({
client,
provider,
preferredPort = 6013
}: {
client: AgenticApiClient
provider: 'github' | 'password'
preferredPort?: number
}): Promise<AuthSession> {
const providerLabel = providerToLabel[provider]!
const port = await getPort({ port: preferredPort })
const app = new Hono()
if (port !== preferredPort) {
throw new Error(
`Port ${preferredPort} is required to sign in with ${providerLabel}, but it is already in use.`
)
}
const redirectUri = `http://localhost:${port}/callback/${provider}/success`
let _resolveAuth: any
let _rejectAuth: any
const authP = new Promise<AuthSession>((resolve, reject) => {
_resolveAuth = resolve
_rejectAuth = reject
})
app.get(`/callback/${provider}/success`, async (c) => {
// console.log(`/callback/${provider}/success`, c.req.method, c.req.url, {
// headers: c.req.header(),
// query: c.req.query()
// })
const code = c.req.query('code')
assert(code, 'Missing required code query parameter')
await client.exchangeAuthCode({
code,
redirectUri,
verifier: authorizeResult.challenge?.verifier
})
assert(
client.authTokens,
`Error ${providerLabel} auth: failed to exchange auth code for token`
)
// AuthStore should be updated via the onUpdateAuth callback
const session = AuthStore.tryGetAuth()
assert(session && session.refreshToken === client.authTokens.refresh)
_resolveAuth(session)
return c.text(
`Huzzah! You are now signed in to the Agentic CLI.\n\nYou may close this browser tab. 😄`
)
})
const server = serve({
fetch: app.fetch,
port
})
// TODO: clean these up
process.on('SIGINT', () => {
server.close()
process.exit(0)
})
process.on('SIGTERM', () => {
server.close((err) => {
if (err) {
console.error(err)
process.exit(1)
}
process.exit(0)
})
})
const authorizeResult = await client.initAuthFlow({
provider,
redirectUri
})
assert(authorizeResult.url, `Error signing in with ${providerLabel}`)
await open(authorizeResult.url)
const authSession = await oraPromise(authP, {
text: `Signing in with ${providerLabel}`,
successText: 'You are now signed in.',
failText: 'Failed to sign in.'
})
server.close()
return authSession
}

Wyświetl plik

@ -1,9 +0,0 @@
import { AgenticApiClient } from '@agentic/platform-api-client'
import { AuthStore } from './store'
// Create a singleton instance of the API client
export const client = new AgenticApiClient({
apiCookie: AuthStore.tryGetAuth()?.cookie,
apiBaseUrl: 'http://localhost:3000'
})

Wyświetl plik

@ -1,9 +1,7 @@
import type { AuthSession } from '@agentic/platform-api-client'
import { Command, InvalidArgumentError } from 'commander'
import { Command } from 'commander'
import type { Context } from '../types'
import { authWithEmailPassword } from '../auth-with-email-password'
import { authWithGitHub } from '../auth-with-github'
import { auth } from '../auth'
export function registerSigninCommand({ client, program, logger }: Context) {
const command = new Command('login')
@ -11,29 +9,21 @@ export function registerSigninCommand({ client, program, logger }: Context) {
.description(
'Signs in to Agentic. If no credentials are provided, uses GitHub auth.'
)
// TODO
// .option('-u, --username <username>', 'account username')
.option('-e, --email <email>', 'account email')
.option('-p, --password <password>', 'account password')
.option('-e, --email', 'Log in using email and password')
.action(async (opts) => {
let session: AuthSession | undefined
if (opts.email) {
if (!opts.password) {
throw new InvalidArgumentError(
'Password is required when using email'
)
}
session = await authWithEmailPassword({
await auth({
client,
email: opts.email,
password: opts.password
provider: 'password'
// email: opts.email,
// password: opts.password
})
} else {
session = await authWithGitHub({ client })
await auth({ client, provider: 'github' })
}
logger.log(session)
const user = await client.getMe()
logger.log(user)
})
program.addCommand(command)

Wyświetl plik

@ -8,11 +8,11 @@ export function registerSignoutCommand({ client, program, logger }: Context) {
.alias('signout')
.description('Signs the current user out')
.action(async () => {
if (!AuthStore.isAuthenticated()) {
if (!client.isAuthenticated) {
return
}
await client.clearAuthSession()
await client.logout()
AuthStore.clearAuth()
logger.log('Signed out')

Wyświetl plik

@ -1,18 +1,17 @@
import { Command } from 'commander'
import type { Context } from '../types'
import { AuthStore } from '../store'
export function registerWhoAmICommand({ client, program, logger }: Context) {
const command = new Command('whoami')
.description('Displays info about the current user')
.action(async () => {
if (!AuthStore.isAuthenticated()) {
if (!client.isAuthenticated) {
logger.log('Not signed in')
return
}
const res = await client.getAuthSession()
const res = await client.getMe()
logger.log(res)
})

Wyświetl plik

@ -13,10 +13,33 @@ async function main() {
restoreCursor()
const client = new AgenticApiClient({
apiCookie: AuthStore.tryGetAuth()?.cookie,
apiBaseUrl: process.env.AGENTIC_API_BASE_URL
apiBaseUrl: process.env.AGENTIC_API_BASE_URL,
onUpdateAuth: (update) => {
if (update) {
AuthStore.setAuth({
refreshToken: update.session.refresh,
user: update.user
})
} else {
AuthStore.clearAuth()
}
}
})
// Try to initialize the existing auth session if one exists
const authSession = AuthStore.tryGetAuth()
if (authSession) {
try {
await client.setRefreshAuthToken(authSession.refreshToken)
} catch (err: any) {
console.warn(
'Existing auth session is invalid; logging out...',
err.message
)
AuthStore.clearAuth()
}
}
const program = new Command('agentic')
.option('-j, --json', 'Print output in JSON format')
.showHelpAfterError()

Wyświetl plik

@ -1,24 +1,17 @@
import type { AuthSession } from '@agentic/platform-api-client'
import { assert } from '@agentic/platform-core'
import Conf from 'conf'
export type AuthState = {
cookie: string
session: AuthSession
teamId?: string
teamSlug?: string
}
import type { AuthSession } from './types'
const keyTeamId = 'teamId'
const keyTeamSlug = 'teamSlug'
const keyCookie = 'cookie'
const keySession = 'session'
const keyAuthSession = 'authSession'
export const AuthStore = {
store: new Conf({ projectName: 'agentic' }),
store: new Conf<{
authSession: AuthSession
}>({ projectName: 'agentic' }),
isAuthenticated() {
return this.store.has(keyCookie) && this.store.has(keySession)
return this.store.has(keyAuthSession)
},
requireAuth() {
@ -28,43 +21,34 @@ export const AuthStore = {
)
},
tryGetAuth(): AuthState | undefined {
tryGetAuth(): AuthSession | undefined {
if (!this.isAuthenticated()) {
return undefined
}
return {
cookie: this.store.get(keyCookie),
session: this.store.get(keySession),
teamId: this.store.get(keyTeamId),
teamSlug: this.store.get(keyTeamSlug)
} as AuthState
return this.store.get(keyAuthSession)
},
getAuth(): AuthState {
getAuth(): AuthSession {
this.requireAuth()
return this.tryGetAuth()!
},
setAuth({ cookie, session }: { cookie: string; session: AuthSession }) {
this.store.set(keyCookie, cookie)
this.store.set(keySession, session)
setAuth(authSession: AuthSession) {
this.store.set(keyAuthSession, authSession)
},
clearAuth() {
this.store.delete(keyCookie)
this.store.delete(keySession)
this.store.delete(keyTeamId)
this.store.delete(keyTeamSlug)
},
switchTeam(team?: { id: string; slug: string }) {
if (team?.id) {
this.store.set(keyTeamId, team.id)
this.store.set(keyTeamSlug, team.slug)
} else {
this.store.delete(keyTeamId)
this.store.delete(keyTeamSlug)
}
this.store.delete(keyAuthSession)
}
// switchTeam(team?: { id: string; slug: string }) {
// if (team?.id) {
// this.store.set(keyTeamId, team.id)
// this.store.set(keyTeamSlug, team.slug)
// } else {
// this.store.delete(keyTeamId)
// this.store.delete(keyTeamSlug)
// }
// }
}

Wyświetl plik

@ -1,4 +1,4 @@
import type { AgenticApiClient } from '@agentic/platform-api-client'
import type { AgenticApiClient, AuthUser } from '@agentic/platform-api-client'
import type { Command } from 'commander'
export type Context = {
@ -8,3 +8,8 @@ export type Context = {
log: (...args: any[]) => void
}
}
export type AuthSession = {
refreshToken: string
user: AuthUser
}

Wyświetl plik

@ -149,6 +149,9 @@ importers:
'@hono/zod-openapi':
specifier: ^0.19.6
version: 0.19.6(hono@4.7.9)(zod@3.24.4)
'@openauthjs/openauth':
specifier: ^0.4.3
version: 0.4.3(arctic@2.3.4)(hono@4.7.9)
'@paralleldrive/cuid2':
specifier: ^2.2.2
version: 2.2.2
@ -158,9 +161,6 @@ importers:
'@sentry/node':
specifier: ^9.19.0
version: 9.19.0
better-auth:
specifier: ^1.2.8
version: 1.2.8
eventid:
specifier: ^2.0.1
version: 2.0.1
@ -173,6 +173,9 @@ importers:
jsonwebtoken:
specifier: ^9.0.2
version: 9.0.2
octokit:
specifier: ^5.0.2
version: 5.0.2
p-all:
specifier: ^5.0.0
version: 5.0.0
@ -222,15 +225,18 @@ importers:
'@agentic/platform-core':
specifier: workspace:*
version: link:../core
better-auth:
specifier: ^1.2.8
version: 1.2.8
'@openauthjs/openauth':
specifier: ^0.4.3
version: 0.4.3(arctic@2.3.4)(hono@4.7.9)
ky:
specifier: 'catalog:'
version: 1.8.1
type-fest:
specifier: 'catalog:'
version: 4.41.0
zod:
specifier: 'catalog:'
version: 3.24.4
devDependencies:
openapi-typescript:
specifier: ^7.8.0
@ -332,12 +338,6 @@ packages:
resolution: {integrity: sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==}
engines: {node: '>=6.9.0'}
'@better-auth/utils@0.2.5':
resolution: {integrity: sha512-uI2+/8h/zVsH8RrYdG8eUErbuGBk16rZKQfz8CjxQOyCE6v7BqFYEbFwvOkvl1KbUdxhqOnXp78+uE5h8qVEgQ==}
'@better-fetch/fetch@1.1.18':
resolution: {integrity: sha512-rEFOE1MYIsBmoMJtQbl32PGHHXuG2hDxvEd7rUHE0vCBoFQVSDqaVs9hkZEtHCxRoY+CljXKFCOuJ8uxqw1LcA==}
'@commander-js/extra-typings@14.0.0':
resolution: {integrity: sha512-hIn0ncNaJRLkZrxBIp5AsW/eXEHNKYQBh0aPdoUqNgD+Io3NIykQqpKFyKcuasZhicGaEZJX/JBSIkZ4e5x8Dg==}
peerDependencies:
@ -783,9 +783,6 @@ packages:
'@fisch0920/drizzle-orm': '>=0.36.0'
zod: '>=3.0.0'
'@hexagon/base64@1.1.28':
resolution: {integrity: sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==}
'@hono/node-server@1.14.1':
resolution: {integrity: sha512-vmbuM+HPinjWzPe7FFPWMMQMsbKE9gDPhaH0FFdqbGpkT5lp++tcWDTxwBl5EgS5y6JVgIaCdjeHRfQ4XRBRjQ==}
engines: {node: '>=18.14.1'}
@ -856,16 +853,10 @@ packages:
'@jridgewell/trace-mapping@0.3.25':
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
'@levischuck/tiny-cbor@0.2.11':
resolution: {integrity: sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow==}
'@modelcontextprotocol/sdk@1.11.2':
resolution: {integrity: sha512-H9vwztj5OAqHg9GockCQC06k1natgcxWQSRpQcPJf6i5+MWBzfKkRtxGbjQf0X2ihii0ffLZCRGbYV2f2bjNCQ==}
engines: {node: '>=18'}
'@noble/ciphers@0.6.0':
resolution: {integrity: sha512-mIbq/R9QXk5/cTfESb1OKtyFnk7oc1Om/8onA1158K9/OZUQFDEVy55jVTato+xmp3XX6F6Qh0zz0Nc1AxAlRQ==}
'@noble/hashes@1.8.0':
resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==}
engines: {node: ^14.21.3 || >=16}
@ -882,6 +873,119 @@ packages:
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
engines: {node: '>= 8'}
'@octokit/app@16.0.1':
resolution: {integrity: sha512-kgTeTsWmpUX+s3Fs4EK4w1K+jWCDB6ClxLSWUWTyhlw7+L3jHtuXDR4QtABu2GsmCMdk67xRhruiXotS3ay3Yw==}
engines: {node: '>= 20'}
'@octokit/auth-app@8.0.1':
resolution: {integrity: sha512-P2J5pB3pjiGwtJX4WqJVYCtNkcZ+j5T2Wm14aJAEIC3WJOrv12jvBley3G1U/XI8q9o1A7QMG54LiFED2BiFlg==}
engines: {node: '>= 20'}
'@octokit/auth-oauth-app@9.0.1':
resolution: {integrity: sha512-TthWzYxuHKLAbmxdFZwFlmwVyvynpyPmjwc+2/cI3cvbT7mHtsAW9b1LvQaNnAuWL+pFnqtxdmrU8QpF633i1g==}
engines: {node: '>= 20'}
'@octokit/auth-oauth-device@8.0.1':
resolution: {integrity: sha512-TOqId/+am5yk9zor0RGibmlqn4V0h8vzjxlw/wYr3qzkQxl8aBPur384D1EyHtqvfz0syeXji4OUvKkHvxk/Gw==}
engines: {node: '>= 20'}
'@octokit/auth-oauth-user@6.0.0':
resolution: {integrity: sha512-GV9IW134PHsLhtUad21WIeP9mlJ+QNpFd6V9vuPWmaiN25HEJeEQUcS4y5oRuqCm9iWDLtfIs+9K8uczBXKr6A==}
engines: {node: '>= 20'}
'@octokit/auth-token@6.0.0':
resolution: {integrity: sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==}
engines: {node: '>= 20'}
'@octokit/auth-unauthenticated@7.0.1':
resolution: {integrity: sha512-qVq1vdjLLZdE8kH2vDycNNjuJRCD1q2oet1nA/GXWaYlpDxlR7rdVhX/K/oszXslXiQIiqrQf+rdhDlA99JdTQ==}
engines: {node: '>= 20'}
'@octokit/core@7.0.2':
resolution: {integrity: sha512-ODsoD39Lq6vR6aBgvjTnA3nZGliknKboc9Gtxr7E4WDNqY24MxANKcuDQSF0jzapvGb3KWOEDrKfve4HoWGK+g==}
engines: {node: '>= 20'}
'@octokit/endpoint@11.0.0':
resolution: {integrity: sha512-hoYicJZaqISMAI3JfaDr1qMNi48OctWuOih1m80bkYow/ayPw6Jj52tqWJ6GEoFTk1gBqfanSoI1iY99Z5+ekQ==}
engines: {node: '>= 20'}
'@octokit/graphql@9.0.1':
resolution: {integrity: sha512-j1nQNU1ZxNFx2ZtKmL4sMrs4egy5h65OMDmSbVyuCzjOcwsHq6EaYjOTGXPQxgfiN8dJ4CriYHk6zF050WEULg==}
engines: {node: '>= 20'}
'@octokit/oauth-app@8.0.1':
resolution: {integrity: sha512-QnhMYEQpnYbEPn9cae+wXL2LuPMFglmfeuDJXXsyxIXdoORwkLK8y0cHhd/5du9MbO/zdG/BXixzB7EEwU63eQ==}
engines: {node: '>= 20'}
'@octokit/oauth-authorization-url@8.0.0':
resolution: {integrity: sha512-7QoLPRh/ssEA/HuHBHdVdSgF8xNLz/Bc5m9fZkArJE5bb6NmVkDm3anKxXPmN1zh6b5WKZPRr3697xKT/yM3qQ==}
engines: {node: '>= 20'}
'@octokit/oauth-methods@6.0.0':
resolution: {integrity: sha512-Q8nFIagNLIZgM2odAraelMcDssapc+lF+y3OlcIPxyAU+knefO8KmozGqfnma1xegRDP4z5M73ABsamn72bOcA==}
engines: {node: '>= 20'}
'@octokit/openapi-types@25.0.0':
resolution: {integrity: sha512-FZvktFu7HfOIJf2BScLKIEYjDsw6RKc7rBJCdvCTfKsVnx2GEB/Nbzjr29DUdb7vQhlzS/j8qDzdditP0OC6aw==}
'@octokit/openapi-webhooks-types@11.0.0':
resolution: {integrity: sha512-ZBzCFj98v3SuRM7oBas6BHZMJRadlnDoeFfvm1olVxZnYeU6Vh97FhPxyS5aLh5pN51GYv2I51l/hVUAVkGBlA==}
'@octokit/plugin-paginate-graphql@6.0.0':
resolution: {integrity: sha512-crfpnIoFiBtRkvPqOyLOsw12XsveYuY2ieP6uYDosoUegBJpSVxGwut9sxUgFFcll3VTOTqpUf8yGd8x1OmAkQ==}
engines: {node: '>= 20'}
peerDependencies:
'@octokit/core': '>=6'
'@octokit/plugin-paginate-rest@13.0.0':
resolution: {integrity: sha512-nPXM3wgil9ONnAINcm8cN+nwso4QhNB13PtnlRFkYFHCUIogcH9DHak/StQYcwkkjuc7pUluLG1AWZNscgvH7Q==}
engines: {node: '>= 20'}
peerDependencies:
'@octokit/core': '>=6'
'@octokit/plugin-rest-endpoint-methods@15.0.0':
resolution: {integrity: sha512-db6UdWvpX7O6tNsdkPk1BttVwTeVdA4n8RDFeXOyjBCPjE2YPufIAlzWh8CyeH8hl/3dSuQXDa+qLmsBlkTY+Q==}
engines: {node: '>= 20'}
peerDependencies:
'@octokit/core': '>=6'
'@octokit/plugin-retry@8.0.1':
resolution: {integrity: sha512-KUoYR77BjF5O3zcwDQHRRZsUvJwepobeqiSSdCJ8lWt27FZExzb0GgVxrhhfuyF6z2B2zpO0hN5pteni1sqWiw==}
engines: {node: '>= 20'}
peerDependencies:
'@octokit/core': '>=7'
'@octokit/plugin-throttling@11.0.1':
resolution: {integrity: sha512-S+EVhy52D/272L7up58dr3FNSMXWuNZolkL4zMJBNIfIxyZuUcczsQAU4b5w6dewJXnKYVgSHSV5wxitMSW1kw==}
engines: {node: '>= 20'}
peerDependencies:
'@octokit/core': ^7.0.0
'@octokit/request-error@7.0.0':
resolution: {integrity: sha512-KRA7VTGdVyJlh0cP5Tf94hTiYVVqmt2f3I6mnimmaVz4UG3gQV/k4mDJlJv3X67iX6rmN7gSHCF8ssqeMnmhZg==}
engines: {node: '>= 20'}
'@octokit/request@10.0.2':
resolution: {integrity: sha512-iYj4SJG/2bbhh+iIpFmG5u49DtJ4lipQ+aPakjL9OKpsGY93wM8w06gvFbEQxcMsZcCvk5th5KkIm2m8o14aWA==}
engines: {node: '>= 20'}
'@octokit/types@14.0.0':
resolution: {integrity: sha512-VVmZP0lEhbo2O1pdq63gZFiGCKkm8PPp8AUOijlwPO6hojEVjspA0MWKP7E4hbvGxzFKNqKr6p0IYtOH/Wf/zA==}
'@octokit/webhooks-methods@6.0.0':
resolution: {integrity: sha512-MFlzzoDJVw/GcbfzVC1RLR36QqkTLUf79vLVO3D+xn7r0QgxnFoLZgtrzxiQErAjFUOdH6fas2KeQJ1yr/qaXQ==}
engines: {node: '>= 20'}
'@octokit/webhooks@14.0.0':
resolution: {integrity: sha512-IZV4vg/s1pqIpCs86a0tp5FQ/O94DUaqksMdNrXFSaE037TXsB+fIhr8OVig09oEx3WazVgE6B2U+u7/Fvdlsw==}
engines: {node: '>= 20'}
'@openauthjs/openauth@0.4.3':
resolution: {integrity: sha512-RlnjqvHzqcbFVymEwhlUEuac4utA5h4nhSK/i2szZuQmxTIqbGUxZ+nM+avM+VV4Ing+/ZaNLKILoXS3yrkOOw==}
peerDependencies:
arctic: ^2.2.2
hono: ^4.0.0
'@opentelemetry/api-logs@0.57.2':
resolution: {integrity: sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A==}
engines: {node: '>=14'}
@ -1070,24 +1174,27 @@ packages:
peerDependencies:
'@opentelemetry/api': ^1.1.0
'@oslojs/asn1@1.0.0':
resolution: {integrity: sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA==}
'@oslojs/binary@1.0.0':
resolution: {integrity: sha512-9RCU6OwXU6p67H4NODbuxv2S3eenuQ4/WFLrsq+K/k682xrznH5EVWA7N4VFk9VYVcbFtKqur5YQQZc0ySGhsQ==}
'@oslojs/crypto@1.0.1':
resolution: {integrity: sha512-7n08G8nWjAr/Yu3vu9zzrd0L9XnrJfpMioQcvCMxBIiF5orECHe5/3J0jmXRVvgfqMm/+4oxlQ+Sq39COYLcNQ==}
'@oslojs/encoding@0.4.1':
resolution: {integrity: sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q==}
'@oslojs/encoding@1.1.0':
resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==}
'@oslojs/jwt@0.2.0':
resolution: {integrity: sha512-bLE7BtHrURedCn4Mco3ma9L4Y1GR2SMBuIvjWr7rmQ4/W/4Jy70TIAgZ+0nIlk0xHz1vNP8x8DCns45Sb2XRbg==}
'@paralleldrive/cuid2@2.2.2':
resolution: {integrity: sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==}
'@peculiar/asn1-android@2.3.16':
resolution: {integrity: sha512-a1viIv3bIahXNssrOIkXZIlI2ePpZaNmR30d4aBL99mu2rO+mT9D6zBsp7H6eROWGtmwv0Ionp5olJurIo09dw==}
'@peculiar/asn1-ecc@2.3.15':
resolution: {integrity: sha512-/HtR91dvgog7z/WhCVdxZJ/jitJuIu8iTqiyWVgRE9Ac5imt2sT/E4obqIVGKQw7PIy+X6i8lVBoT6wC73XUgA==}
'@peculiar/asn1-rsa@2.3.15':
resolution: {integrity: sha512-p6hsanvPhexRtYSOHihLvUUgrJ8y0FtOM97N5UEpC+VifFYyZa0iZ5cXjTkZoDwxJ/TTJ1IJo3HVTB2JJTpXvg==}
'@peculiar/asn1-schema@2.3.15':
resolution: {integrity: sha512-QPeD8UA8axQREpgR5UTAfu2mqQmm97oUqahDtNdBcfj3qAnoXzFdQW+aNf/tD2WVXF8Fhmftxoj0eMIT++gX2w==}
'@peculiar/asn1-x509@2.3.15':
resolution: {integrity: sha512-0dK5xqTqSLaxv1FHXIcd4Q/BZNuopg+u1l23hT9rOmQ1g4dNtw0g/RnEi+TboB0gOwGtrWn269v27cMgchFIIg==}
'@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
@ -1241,20 +1348,19 @@ packages:
resolution: {integrity: sha512-A4srR9mEBFdVXwSEKjQ94msUbVkMr8JeFiEj9ouOFORw/Y/ux/WV2bWVD/ZI9wq0TcTNK8L1wBgU8UMS5lIq3A==}
engines: {node: '>=14.18'}
'@simplewebauthn/browser@13.1.0':
resolution: {integrity: sha512-WuHZ/PYvyPJ9nxSzgHtOEjogBhwJfC8xzYkPC+rR/+8chl/ft4ngjiK8kSU5HtRJfczupyOh33b25TjYbvwAcg==}
'@simplewebauthn/server@13.1.1':
resolution: {integrity: sha512-1hsLpRHfSuMB9ee2aAdh0Htza/X3f4djhYISrggqGe3xopNjOcePiSDkDDoPzDYaaMCrbqGP1H2TYU7bgL9PmA==}
engines: {node: '>=20.0.0'}
'@sindresorhus/merge-streams@2.3.0':
resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==}
engines: {node: '>=18'}
'@standard-schema/spec@1.0.0-beta.3':
resolution: {integrity: sha512-0ifF3BjA1E8SY9C+nUew8RefNOIq0cDlYALPty4rhUm8Rrl6tCM8hBT4bhGhx7I7iXD0uAgt50lgo8dD73ACMw==}
'@total-typescript/ts-reset@0.6.1':
resolution: {integrity: sha512-cka47fVSo6lfQDIATYqb/vO1nvFfbPw7uWLayIXIhGETj0wcOOlrlkobOMDNQOFr9QOafegUPq13V2+6vtD7yg==}
'@types/aws-lambda@8.10.149':
resolution: {integrity: sha512-NXSZIhfJjnXqJgtS7IwutqIF/SOy1Wz5Px4gUY1RWITp3AYTyuJS4xaXr/bIJY1v15XMzrJ5soGnPM+7uigZjA==}
'@types/connect@3.4.38':
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
@ -1460,6 +1566,9 @@ packages:
any-promise@1.3.0:
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
arctic@2.3.4:
resolution: {integrity: sha512-+p30BOWsctZp+CVYCt7oAean/hWGW42sH5LAcRQX56ttEkFJWbzXBhmSpibbzwSJkRrotmsA+oAoJoVsU0f5xA==}
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
@ -1499,10 +1608,6 @@ packages:
resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==}
engines: {node: '>= 0.4'}
asn1js@3.0.6:
resolution: {integrity: sha512-UOCGPYbl0tv8+006qks/dTgV9ajs97X2p0FAbyS2iyCRrmLSRolDaHdp+v/CLgnzHc3fVB+CwYiUmei7ndFcgA==}
engines: {node: '>=12.0.0'}
assertion-error@2.0.1:
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
engines: {node: '>=12'}
@ -1521,6 +1626,9 @@ packages:
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
engines: {node: '>= 0.4'}
aws4fetch@1.0.20:
resolution: {integrity: sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g==}
axe-core@4.10.3:
resolution: {integrity: sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==}
engines: {node: '>=4'}
@ -1535,11 +1643,8 @@ packages:
base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
better-auth@1.2.8:
resolution: {integrity: sha512-y8ry7ZW3/3ZIr82Eo1zUDtMzdoQlFnwNuZ0+b0RxoNZgqmvgTIc/0tCDC7NDJerqSu4UCzer0dvYxBsv3WMIGg==}
better-call@1.0.9:
resolution: {integrity: sha512-Qfm0gjk0XQz0oI7qvTK1hbqTsBY4xV2hsHAxF8LZfUYl3RaECCIifXuVqtPpZJWvlCCMlQSvkvhhyuApGUba6g==}
before-after-hook@4.0.0:
resolution: {integrity: sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==}
bl@4.1.0:
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
@ -1548,6 +1653,9 @@ packages:
resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==}
engines: {node: '>=18'}
bottleneck@2.19.5:
resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==}
brace-expansion@1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
@ -1816,9 +1924,6 @@ packages:
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
engines: {node: '>= 0.4'}
defu@6.1.4:
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
del-cli@6.0.0:
resolution: {integrity: sha512-9nitGV2W6KLFyya4qYt4+9AKQFL+c0Ehj5K7V7IwlxTc6RMCfQUGY9E9pLG6e8TQjtwXpuiWIGGZb3mfVxyZkw==}
engines: {node: '>=18'}
@ -2223,6 +2328,9 @@ packages:
resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==}
engines: {node: '>=4'}
fast-content-type-parse@3.0.0:
resolution: {integrity: sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==}
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
@ -2662,8 +2770,8 @@ packages:
jackspeak@3.4.3:
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
jose@5.10.0:
resolution: {integrity: sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==}
jose@5.9.6:
resolution: {integrity: sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==}
joycon@3.1.1:
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
@ -2917,10 +3025,6 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
nanostores@0.11.4:
resolution: {integrity: sha512-k1oiVNN4hDK8NcNERSZLQiMfRzEGtfnvZvdBvey3SQbgn8Dcrk0h1I6vpxApjb10PFUflZrgJ2WEZyJQ+5v7YQ==}
engines: {node: ^18.0.0 || >=20.0.0}
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
@ -2976,6 +3080,10 @@ packages:
resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
engines: {node: '>= 0.4'}
octokit@5.0.2:
resolution: {integrity: sha512-WCO9Oip2F+qsrIcNMfLwm1+dL2g70oO++pkmiluisJDMXXwdO4susVaVg1iQZgZNiDtA1qcLXs5662Mdj/vqdw==}
engines: {node: '>= 20'}
on-finished@2.4.1:
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
engines: {node: '>= 0.8'}
@ -3198,13 +3306,6 @@ packages:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
pvtsutils@1.3.6:
resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==}
pvutils@1.1.3:
resolution: {integrity: sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==}
engines: {node: '>=6.0.0'}
qs@6.14.0:
resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
engines: {node: '>=0.6'}
@ -3314,9 +3415,6 @@ packages:
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
rou3@0.5.1:
resolution: {integrity: sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ==}
router@2.2.0:
resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==}
engines: {node: '>= 18'}
@ -3373,9 +3471,6 @@ packages:
resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==}
engines: {node: '>= 18'}
set-cookie-parser@2.7.1:
resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==}
set-function-length@1.2.2:
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
engines: {node: '>= 0.4'}
@ -3621,6 +3716,10 @@ packages:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
toad-cache@3.7.0:
resolution: {integrity: sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==}
engines: {node: '>=12'}
toidentifier@1.0.1:
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
engines: {node: '>=0.6'}
@ -3770,9 +3869,6 @@ packages:
resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
engines: {node: '>= 0.4'}
uncrypto@0.1.3:
resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==}
undici-types@6.21.0:
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
@ -3784,6 +3880,12 @@ packages:
resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==}
engines: {node: '>=18'}
universal-github-app-jwt@2.2.2:
resolution: {integrity: sha512-dcmbeSrOdTnsjGjUfAlqNDJrhxXizjAz94ija9Qw8YkZ1uu0d+GoZzyH+Jb9tIIqvGsadUfwg+22k5aDqqwzbw==}
universal-user-agent@7.0.3:
resolution: {integrity: sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==}
unpipe@1.0.0:
resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
engines: {node: '>= 0.8'}
@ -4022,13 +4124,6 @@ snapshots:
dependencies:
regenerator-runtime: 0.14.1
'@better-auth/utils@0.2.5':
dependencies:
typescript: 5.8.3
uncrypto: 0.1.3
'@better-fetch/fetch@1.1.18': {}
'@commander-js/extra-typings@14.0.0(commander@14.0.0)':
dependencies:
commander: 14.0.0
@ -4281,8 +4376,6 @@ snapshots:
'@fisch0920/drizzle-orm': 0.43.7(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(kysely@0.28.2)(postgres@3.4.5)
zod: 3.24.4
'@hexagon/base64@1.1.28': {}
'@hono/node-server@1.14.1(hono@4.7.9)':
dependencies:
hono: 4.7.9
@ -4345,8 +4438,6 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.0
'@levischuck/tiny-cbor@0.2.11': {}
'@modelcontextprotocol/sdk@1.11.2':
dependencies:
content-type: 1.0.5
@ -4362,8 +4453,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@noble/ciphers@0.6.0': {}
'@noble/hashes@1.8.0': {}
'@nodelib/fs.scandir@2.1.5':
@ -4378,6 +4467,161 @@ snapshots:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.19.1
'@octokit/app@16.0.1':
dependencies:
'@octokit/auth-app': 8.0.1
'@octokit/auth-unauthenticated': 7.0.1
'@octokit/core': 7.0.2
'@octokit/oauth-app': 8.0.1
'@octokit/plugin-paginate-rest': 13.0.0(@octokit/core@7.0.2)
'@octokit/types': 14.0.0
'@octokit/webhooks': 14.0.0
'@octokit/auth-app@8.0.1':
dependencies:
'@octokit/auth-oauth-app': 9.0.1
'@octokit/auth-oauth-user': 6.0.0
'@octokit/request': 10.0.2
'@octokit/request-error': 7.0.0
'@octokit/types': 14.0.0
toad-cache: 3.7.0
universal-github-app-jwt: 2.2.2
universal-user-agent: 7.0.3
'@octokit/auth-oauth-app@9.0.1':
dependencies:
'@octokit/auth-oauth-device': 8.0.1
'@octokit/auth-oauth-user': 6.0.0
'@octokit/request': 10.0.2
'@octokit/types': 14.0.0
universal-user-agent: 7.0.3
'@octokit/auth-oauth-device@8.0.1':
dependencies:
'@octokit/oauth-methods': 6.0.0
'@octokit/request': 10.0.2
'@octokit/types': 14.0.0
universal-user-agent: 7.0.3
'@octokit/auth-oauth-user@6.0.0':
dependencies:
'@octokit/auth-oauth-device': 8.0.1
'@octokit/oauth-methods': 6.0.0
'@octokit/request': 10.0.2
'@octokit/types': 14.0.0
universal-user-agent: 7.0.3
'@octokit/auth-token@6.0.0': {}
'@octokit/auth-unauthenticated@7.0.1':
dependencies:
'@octokit/request-error': 7.0.0
'@octokit/types': 14.0.0
'@octokit/core@7.0.2':
dependencies:
'@octokit/auth-token': 6.0.0
'@octokit/graphql': 9.0.1
'@octokit/request': 10.0.2
'@octokit/request-error': 7.0.0
'@octokit/types': 14.0.0
before-after-hook: 4.0.0
universal-user-agent: 7.0.3
'@octokit/endpoint@11.0.0':
dependencies:
'@octokit/types': 14.0.0
universal-user-agent: 7.0.3
'@octokit/graphql@9.0.1':
dependencies:
'@octokit/request': 10.0.2
'@octokit/types': 14.0.0
universal-user-agent: 7.0.3
'@octokit/oauth-app@8.0.1':
dependencies:
'@octokit/auth-oauth-app': 9.0.1
'@octokit/auth-oauth-user': 6.0.0
'@octokit/auth-unauthenticated': 7.0.1
'@octokit/core': 7.0.2
'@octokit/oauth-authorization-url': 8.0.0
'@octokit/oauth-methods': 6.0.0
'@types/aws-lambda': 8.10.149
universal-user-agent: 7.0.3
'@octokit/oauth-authorization-url@8.0.0': {}
'@octokit/oauth-methods@6.0.0':
dependencies:
'@octokit/oauth-authorization-url': 8.0.0
'@octokit/request': 10.0.2
'@octokit/request-error': 7.0.0
'@octokit/types': 14.0.0
'@octokit/openapi-types@25.0.0': {}
'@octokit/openapi-webhooks-types@11.0.0': {}
'@octokit/plugin-paginate-graphql@6.0.0(@octokit/core@7.0.2)':
dependencies:
'@octokit/core': 7.0.2
'@octokit/plugin-paginate-rest@13.0.0(@octokit/core@7.0.2)':
dependencies:
'@octokit/core': 7.0.2
'@octokit/types': 14.0.0
'@octokit/plugin-rest-endpoint-methods@15.0.0(@octokit/core@7.0.2)':
dependencies:
'@octokit/core': 7.0.2
'@octokit/types': 14.0.0
'@octokit/plugin-retry@8.0.1(@octokit/core@7.0.2)':
dependencies:
'@octokit/core': 7.0.2
'@octokit/request-error': 7.0.0
'@octokit/types': 14.0.0
bottleneck: 2.19.5
'@octokit/plugin-throttling@11.0.1(@octokit/core@7.0.2)':
dependencies:
'@octokit/core': 7.0.2
'@octokit/types': 14.0.0
bottleneck: 2.19.5
'@octokit/request-error@7.0.0':
dependencies:
'@octokit/types': 14.0.0
'@octokit/request@10.0.2':
dependencies:
'@octokit/endpoint': 11.0.0
'@octokit/request-error': 7.0.0
'@octokit/types': 14.0.0
fast-content-type-parse: 3.0.0
universal-user-agent: 7.0.3
'@octokit/types@14.0.0':
dependencies:
'@octokit/openapi-types': 25.0.0
'@octokit/webhooks-methods@6.0.0': {}
'@octokit/webhooks@14.0.0':
dependencies:
'@octokit/openapi-webhooks-types': 11.0.0
'@octokit/request-error': 7.0.0
'@octokit/webhooks-methods': 6.0.0
'@openauthjs/openauth@0.4.3(arctic@2.3.4)(hono@4.7.9)':
dependencies:
'@standard-schema/spec': 1.0.0-beta.3
arctic: 2.3.4
aws4fetch: 1.0.20
hono: 4.7.9
jose: 5.9.6
'@opentelemetry/api-logs@0.57.2':
dependencies:
'@opentelemetry/api': 1.9.0
@ -4620,43 +4864,29 @@ snapshots:
'@opentelemetry/api': 1.9.0
'@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0)
'@oslojs/asn1@1.0.0':
dependencies:
'@oslojs/binary': 1.0.0
'@oslojs/binary@1.0.0': {}
'@oslojs/crypto@1.0.1':
dependencies:
'@oslojs/asn1': 1.0.0
'@oslojs/binary': 1.0.0
'@oslojs/encoding@0.4.1': {}
'@oslojs/encoding@1.1.0': {}
'@oslojs/jwt@0.2.0':
dependencies:
'@oslojs/encoding': 0.4.1
'@paralleldrive/cuid2@2.2.2':
dependencies:
'@noble/hashes': 1.8.0
'@peculiar/asn1-android@2.3.16':
dependencies:
'@peculiar/asn1-schema': 2.3.15
asn1js: 3.0.6
tslib: 2.8.1
'@peculiar/asn1-ecc@2.3.15':
dependencies:
'@peculiar/asn1-schema': 2.3.15
'@peculiar/asn1-x509': 2.3.15
asn1js: 3.0.6
tslib: 2.8.1
'@peculiar/asn1-rsa@2.3.15':
dependencies:
'@peculiar/asn1-schema': 2.3.15
'@peculiar/asn1-x509': 2.3.15
asn1js: 3.0.6
tslib: 2.8.1
'@peculiar/asn1-schema@2.3.15':
dependencies:
asn1js: 3.0.6
pvtsutils: 1.3.6
tslib: 2.8.1
'@peculiar/asn1-x509@2.3.15':
dependencies:
'@peculiar/asn1-schema': 2.3.15
asn1js: 3.0.6
pvtsutils: 1.3.6
tslib: 2.8.1
'@pkgjs/parseargs@0.11.0':
optional: true
@ -4814,22 +5044,14 @@ snapshots:
dependencies:
'@sentry/types': 8.9.2
'@simplewebauthn/browser@13.1.0': {}
'@simplewebauthn/server@13.1.1':
dependencies:
'@hexagon/base64': 1.1.28
'@levischuck/tiny-cbor': 0.2.11
'@peculiar/asn1-android': 2.3.16
'@peculiar/asn1-ecc': 2.3.15
'@peculiar/asn1-rsa': 2.3.15
'@peculiar/asn1-schema': 2.3.15
'@peculiar/asn1-x509': 2.3.15
'@sindresorhus/merge-streams@2.3.0': {}
'@standard-schema/spec@1.0.0-beta.3': {}
'@total-typescript/ts-reset@0.6.1': {}
'@types/aws-lambda@8.10.149': {}
'@types/connect@3.4.38':
dependencies:
'@types/node': 22.15.18
@ -5066,6 +5288,12 @@ snapshots:
any-promise@1.3.0: {}
arctic@2.3.4:
dependencies:
'@oslojs/crypto': 1.0.1
'@oslojs/encoding': 1.1.0
'@oslojs/jwt': 0.2.0
argparse@2.0.1: {}
aria-query@5.3.2: {}
@ -5135,12 +5363,6 @@ snapshots:
get-intrinsic: 1.3.0
is-array-buffer: 3.0.5
asn1js@3.0.6:
dependencies:
pvtsutils: 1.3.6
pvutils: 1.1.3
tslib: 2.8.1
assertion-error@2.0.1: {}
ast-types-flow@0.0.8: {}
@ -5156,6 +5378,8 @@ snapshots:
dependencies:
possible-typed-array-names: 1.1.0
aws4fetch@1.0.20: {}
axe-core@4.10.3: {}
axobject-query@4.1.0: {}
@ -5164,27 +5388,7 @@ snapshots:
base64-js@1.5.1: {}
better-auth@1.2.8:
dependencies:
'@better-auth/utils': 0.2.5
'@better-fetch/fetch': 1.1.18
'@noble/ciphers': 0.6.0
'@noble/hashes': 1.8.0
'@simplewebauthn/browser': 13.1.0
'@simplewebauthn/server': 13.1.1
better-call: 1.0.9
defu: 6.1.4
jose: 5.10.0
kysely: 0.28.2
nanostores: 0.11.4
zod: 3.24.4
better-call@1.0.9:
dependencies:
'@better-fetch/fetch': 1.1.18
rou3: 0.5.1
set-cookie-parser: 2.7.1
uncrypto: 0.1.3
before-after-hook@4.0.0: {}
bl@4.1.0:
dependencies:
@ -5206,6 +5410,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
bottleneck@2.19.5: {}
brace-expansion@1.1.11:
dependencies:
balanced-match: 1.0.2
@ -5449,8 +5655,6 @@ snapshots:
has-property-descriptors: 1.0.2
object-keys: 1.1.1
defu@6.1.4: {}
del-cli@6.0.0:
dependencies:
del: 8.0.0
@ -5968,6 +6172,8 @@ snapshots:
iconv-lite: 0.4.24
tmp: 0.0.33
fast-content-type-parse@3.0.0: {}
fast-deep-equal@3.1.3: {}
fast-glob@3.3.3:
@ -6410,7 +6616,7 @@ snapshots:
optionalDependencies:
'@pkgjs/parseargs': 0.11.0
jose@5.10.0: {}
jose@5.9.6: {}
joycon@3.1.1: {}
@ -6479,7 +6685,8 @@ snapshots:
ky@1.8.1: {}
kysely@0.28.2: {}
kysely@0.28.2:
optional: true
language-subtag-registry@0.3.23: {}
@ -6639,8 +6846,6 @@ snapshots:
nanoid@3.3.11: {}
nanostores@0.11.4: {}
natural-compare@1.4.0: {}
negotiator@1.0.0: {}
@ -6708,6 +6913,20 @@ snapshots:
define-properties: 1.2.1
es-object-atoms: 1.1.1
octokit@5.0.2:
dependencies:
'@octokit/app': 16.0.1
'@octokit/core': 7.0.2
'@octokit/oauth-app': 8.0.1
'@octokit/plugin-paginate-graphql': 6.0.0(@octokit/core@7.0.2)
'@octokit/plugin-paginate-rest': 13.0.0(@octokit/core@7.0.2)
'@octokit/plugin-rest-endpoint-methods': 15.0.0(@octokit/core@7.0.2)
'@octokit/plugin-retry': 8.0.1(@octokit/core@7.0.2)
'@octokit/plugin-throttling': 11.0.1(@octokit/core@7.0.2)
'@octokit/request-error': 7.0.0
'@octokit/types': 14.0.0
'@octokit/webhooks': 14.0.0
on-finished@2.4.1:
dependencies:
ee-first: 1.1.1
@ -6910,12 +7129,6 @@ snapshots:
punycode@2.3.1: {}
pvtsutils@1.3.6:
dependencies:
tslib: 2.8.1
pvutils@1.1.3: {}
qs@6.14.0:
dependencies:
side-channel: 1.1.0
@ -7058,8 +7271,6 @@ snapshots:
'@rollup/rollup-win32-x64-msvc': 4.40.0
fsevents: 2.3.3
rou3@0.5.1: {}
router@2.2.0:
dependencies:
debug: 4.4.1(supports-color@10.0.0)
@ -7138,8 +7349,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
set-cookie-parser@2.7.1: {}
set-function-length@1.2.2:
dependencies:
define-data-property: 1.1.4
@ -7408,6 +7617,8 @@ snapshots:
dependencies:
is-number: 7.0.0
toad-cache@3.7.0: {}
toidentifier@1.0.1: {}
toucan-js@4.1.1:
@ -7570,14 +7781,16 @@ snapshots:
has-symbols: 1.1.0
which-boxed-primitive: 1.1.1
uncrypto@0.1.3: {}
undici-types@6.21.0: {}
unicorn-magic@0.1.0: {}
unicorn-magic@0.3.0: {}
universal-github-app-jwt@2.2.2: {}
universal-user-agent@7.0.3: {}
unpipe@1.0.0: {}
update-browserslist-db@1.1.3(browserslist@4.24.4):

Wyświetl plik

@ -30,6 +30,7 @@
- https://github.com/transitive-bullshit?submit=Search&q=oauth&tab=stars&type=&sort=&direction=&submit=Search
- clerk / workos / auth0
- consider switching to [consola](https://github.com/unjs/consola) for logging?
- consider switching to `bun` (for `--hot` reloading!!)
## License