feat: add initial impl of better-auth

pull/715/head
Travis Fischer 2025-05-23 00:19:04 +07:00
rodzic cfff2d37fc
commit 8a5a9b421e
30 zmienionych plików z 719 dodań i 417 usunięć

37
.vscode/launch.json vendored 100644
Wyświetl plik

@ -0,0 +1,37 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug API",
"type": "node",
"request": "launch",
// Debug server in VSCode
"cwd": "${workspaceFolder}/apps/api",
"program": "src/server.ts",
// "program": "${file}",
/*
* Path to tsx binary
* Assuming locally installed
*/
"runtimeExecutable": "tsx",
/*
* Open terminal when debugging starts (Optional)
* Useful to see console.logs
*/
"console": "integratedTerminal",
"internalConsoleOptions": "openOnFirstSessionStart",
// Files to exclude from debugger (e.g. call stack)
"skipFiles": [
// Node.js internal core modules
"<node_internals>/**"
// Ignore all dependencies (optional)
// "${workspaceFolder}/node_modules/**"
]
}
]
}

Wyświetl plik

@ -1,20 +0,0 @@
import { config } from '@fisch0920/config/eslint'
import drizzle from 'eslint-plugin-drizzle'
export default [
...config,
{
files: ['**/*.ts', '**/*.tsx'],
plugins: {
drizzle
},
rules: {
...drizzle.configs.recommended.rules,
'no-console': 'error',
'unicorn/no-array-reduce': 'off'
}
},
{
ignores: ['**/out/**']
}
]

Wyświetl plik

@ -1,7 +1,7 @@
import { OpenAPIHono } from '@hono/zod-openapi' import { OpenAPIHono } from '@hono/zod-openapi'
import { fromError } from 'zod-validation-error' import { fromError } from 'zod-validation-error'
import type { AuthenticatedEnv } from '@/lib/types' import type { AuthenticatedEnv, DefaultContext } from '@/lib/types'
import { auth } from '@/lib/auth' import { auth } from '@/lib/auth'
import * as middleware from '@/lib/middleware' import * as middleware from '@/lib/middleware'
import { registerOpenAPIErrorResponses } from '@/lib/openapi-utils' import { registerOpenAPIErrorResponses } from '@/lib/openapi-utils'
@ -18,7 +18,6 @@ import { registerV1DeploymentsListDeployments } from './deployments/list-deploym
import { registerV1DeploymentsPublishDeployment } from './deployments/publish-deployment' import { registerV1DeploymentsPublishDeployment } from './deployments/publish-deployment'
import { registerV1DeploymentsUpdateDeployment } from './deployments/update-deployment' import { registerV1DeploymentsUpdateDeployment } from './deployments/update-deployment'
import { registerHealthCheck } from './health-check' import { registerHealthCheck } from './health-check'
// import { registerV1OAuthRedirect } from './oauth-redirect'
import { registerV1ProjectsCreateProject } from './projects/create-project' import { registerV1ProjectsCreateProject } from './projects/create-project'
import { registerV1ProjectsGetProject } from './projects/get-project' import { registerV1ProjectsGetProject } from './projects/get-project'
import { registerV1ProjectsListProjects } from './projects/list-projects' import { registerV1ProjectsListProjects } from './projects/list-projects'
@ -31,8 +30,8 @@ import { registerV1TeamsMembersCreateTeamMember } from './teams/members/create-t
import { registerV1TeamsMembersDeleteTeamMember } from './teams/members/delete-team-member' import { registerV1TeamsMembersDeleteTeamMember } from './teams/members/delete-team-member'
import { registerV1TeamsMembersUpdateTeamMember } from './teams/members/update-team-member' import { registerV1TeamsMembersUpdateTeamMember } from './teams/members/update-team-member'
import { registerV1TeamsUpdateTeam } from './teams/update-team' import { registerV1TeamsUpdateTeam } from './teams/update-team'
// import { registerV1UsersGetUser } from './users/get-user' import { registerV1UsersGetUser } from './users/get-user'
// import { registerV1UsersUpdateUser } from './users/update-user' import { registerV1UsersUpdateUser } from './users/update-user'
import { registerV1StripeWebhook } from './webhooks/stripe-webhook' import { registerV1StripeWebhook } from './webhooks/stripe-webhook'
export const apiV1 = new OpenAPIHono({ export const apiV1 = new OpenAPIHono({
@ -65,8 +64,8 @@ const privateRouter = new OpenAPIHono<AuthenticatedEnv>()
registerHealthCheck(publicRouter) registerHealthCheck(publicRouter)
// Users // Users
// registerV1UsersGetUser(privateRouter) registerV1UsersGetUser(privateRouter)
// registerV1UsersUpdateUser(privateRouter) registerV1UsersUpdateUser(privateRouter)
// Teams // Teams
registerV1TeamsCreateTeam(privateRouter) registerV1TeamsCreateTeam(privateRouter)
@ -106,10 +105,15 @@ registerV1AdminConsumersGetConsumerByToken(privateRouter)
// Webhook event handlers // Webhook event handlers
registerV1StripeWebhook(publicRouter) registerV1StripeWebhook(publicRouter)
// OAuth redirect // Better-Auth Handler for all auth-related routes
// registerV1OAuthRedirect(publicRouter) apiV1.on(['POST', 'GET'], 'auth/**', async (c: DefaultContext) => {
const logger = c.get('logger')
logger.info(c.req.method, c.req.url, c.req.header())
publicRouter.on(['POST', 'GET'], 'auth/**', (c) => auth.handler(c.req.raw)) const res = await auth.handler(c.req.raw)
logger.info('auth result', res)
return res
})
// Setup routes and middleware // Setup routes and middleware
apiV1.route('/', publicRouter) apiV1.route('/', publicRouter)

Wyświetl plik

@ -1,51 +1,51 @@
// import { assert, parseZodSchema } from '@agentic/platform-core' import { assert, parseZodSchema } from '@agentic/platform-core'
// import { createRoute, type OpenAPIHono } from '@hono/zod-openapi' import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
// import type { AuthenticatedEnv } from '@/lib/types' import type { AuthenticatedEnv } from '@/lib/types'
// import { db, eq, schema } from '@/db' import { db, eq, schema } from '@/db'
// import { acl } from '@/lib/acl' import { acl } from '@/lib/acl'
// import { import {
// openapiAuthenticatedSecuritySchemas, openapiAuthenticatedSecuritySchemas,
// openapiErrorResponse404, openapiErrorResponse404,
// openapiErrorResponses openapiErrorResponses
// } from '@/lib/openapi-utils' } from '@/lib/openapi-utils'
// import { userIdParamsSchema } from './schemas' import { userIdParamsSchema } from './schemas'
// const route = createRoute({ const route = createRoute({
// description: 'Gets a user', description: 'Gets a user',
// tags: ['users'], tags: ['users'],
// operationId: 'getUser', operationId: 'getUser',
// method: 'get', method: 'get',
// path: 'users/{userId}', path: 'users/{userId}',
// security: openapiAuthenticatedSecuritySchemas, security: openapiAuthenticatedSecuritySchemas,
// request: { request: {
// params: userIdParamsSchema params: userIdParamsSchema
// }, },
// responses: { responses: {
// 200: { 200: {
// description: 'A user object', description: 'A user object',
// content: { content: {
// 'application/json': { 'application/json': {
// schema: schema.userSelectSchema schema: schema.userSelectSchema
// } }
// } }
// }, },
// ...openapiErrorResponses, ...openapiErrorResponses,
// ...openapiErrorResponse404 ...openapiErrorResponse404
// } }
// }) })
// export function registerV1UsersGetUser(app: OpenAPIHono<AuthenticatedEnv>) { export function registerV1UsersGetUser(app: OpenAPIHono<AuthenticatedEnv>) {
// return app.openapi(route, async (c) => { return app.openapi(route, async (c) => {
// const { userId } = c.req.valid('param') const { userId } = c.req.valid('param')
// await acl(c, { userId }, { label: 'User' }) await acl(c, { userId }, { label: 'User' })
// const user = await db.query.users.findFirst({ const user = await db.query.users.findFirst({
// where: eq(schema.users.id, userId) where: eq(schema.users.id, userId)
// }) })
// assert(user, 404, `User not found "${userId}"`) assert(user, 404, `User not found "${userId}"`)
// return c.json(parseZodSchema(schema.userSelectSchema, user)) return c.json(parseZodSchema(schema.userSelectSchema, user))
// }) })
// } }

Wyświetl plik

@ -1,62 +1,62 @@
// import { assert, parseZodSchema } from '@agentic/platform-core' import { assert, parseZodSchema } from '@agentic/platform-core'
// import { createRoute, type OpenAPIHono } from '@hono/zod-openapi' import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
// import type { AuthenticatedEnv } from '@/lib/types' import type { AuthenticatedEnv } from '@/lib/types'
// import { db, eq, schema } from '@/db' import { db, eq, schema } from '@/db'
// import { acl } from '@/lib/acl' import { acl } from '@/lib/acl'
// import { import {
// openapiAuthenticatedSecuritySchemas, openapiAuthenticatedSecuritySchemas,
// openapiErrorResponse404, openapiErrorResponse404,
// openapiErrorResponses openapiErrorResponses
// } from '@/lib/openapi-utils' } from '@/lib/openapi-utils'
// import { userIdParamsSchema } from './schemas' import { userIdParamsSchema } from './schemas'
// const route = createRoute({ const route = createRoute({
// description: 'Updates a user', description: 'Updates a user',
// tags: ['users'], tags: ['users'],
// operationId: 'updateUser', operationId: 'updateUser',
// method: 'post', method: 'post',
// path: 'users/{userId}', path: 'users/{userId}',
// security: openapiAuthenticatedSecuritySchemas, security: openapiAuthenticatedSecuritySchemas,
// request: { request: {
// params: userIdParamsSchema, params: userIdParamsSchema,
// body: { body: {
// required: true, required: true,
// content: { content: {
// 'application/json': { 'application/json': {
// schema: schema.userUpdateSchema schema: schema.userUpdateSchema
// } }
// } }
// } }
// }, },
// responses: { responses: {
// 200: { 200: {
// description: 'A user object', description: 'A user object',
// content: { content: {
// 'application/json': { 'application/json': {
// schema: schema.userSelectSchema schema: schema.userSelectSchema
// } }
// } }
// }, },
// ...openapiErrorResponses, ...openapiErrorResponses,
// ...openapiErrorResponse404 ...openapiErrorResponse404
// } }
// }) })
// export function registerV1UsersUpdateUser(app: OpenAPIHono<AuthenticatedEnv>) { export function registerV1UsersUpdateUser(app: OpenAPIHono<AuthenticatedEnv>) {
// return app.openapi(route, async (c) => { return app.openapi(route, async (c) => {
// const { userId } = c.req.valid('param') const { userId } = c.req.valid('param')
// await acl(c, { userId }, { label: 'User' }) await acl(c, { userId }, { label: 'User' })
// const body = c.req.valid('json') const body = c.req.valid('json')
// const [user] = await db const [user] = await db
// .update(schema.users) .update(schema.users)
// .set(body) .set(body)
// .where(eq(schema.users.id, userId)) .where(eq(schema.users.id, userId))
// .returning() .returning()
// assert(user, 404, `User not found "${userId}"`) assert(user, 404, `User not found "${userId}"`)
// return c.json(parseZodSchema(schema.userSelectSchema, user)) return c.json(parseZodSchema(schema.userSelectSchema, user))
// }) })
// } }

Wyświetl plik

@ -2,8 +2,8 @@ import { pgTable, text, timestamp } from '@fisch0920/drizzle-orm/pg-core'
import { import {
accountPrimaryId, accountPrimaryId,
authTimestamps,
sessionPrimaryId, sessionPrimaryId,
timestamps,
userId, userId,
verificationPrimaryId verificationPrimaryId
} from './common' } from './common'
@ -13,38 +13,41 @@ import { users } from './user'
export const sessions = pgTable('sessions', { export const sessions = pgTable('sessions', {
...sessionPrimaryId, ...sessionPrimaryId,
...timestamps, ...authTimestamps,
expiresAt: timestamp('expiresAt').notNull(), token: text().notNull().unique(),
ipAddress: text('ipAddress'), expiresAt: timestamp({ mode: 'date' }).notNull(),
userAgent: text('userAgent'), ipAddress: text(),
userAgent: text(),
userId: userId() userId: userId()
.notNull() .notNull()
.references(() => users.id) .references(() => users.id, { onDelete: 'cascade' })
}) })
export const accounts = pgTable('accounts', { export const accounts = pgTable('accounts', {
...accountPrimaryId, ...accountPrimaryId,
...timestamps, ...authTimestamps,
accountId: text('accountId').notNull(), accountId: text().notNull(),
providerId: text('providerId').notNull(), providerId: text().notNull(),
userId: userId() userId: userId()
.notNull() .notNull()
.references(() => users.id), .references(() => users.id, { onDelete: 'cascade' }),
accessToken: text('accessToken'), accessToken: text(),
refreshToken: text('refreshToken'), refreshToken: text(),
idToken: text('idToken'), accessTokenExpiresAt: timestamp({ mode: 'date' }),
expiresAt: timestamp('expiresAt').notNull(), refreshTokenExpiresAt: timestamp({ mode: 'date' }),
password: text('password') scope: text(),
idToken: text(),
password: text()
}) })
export const verifications = pgTable('verifications', { export const verifications = pgTable('verifications', {
...verificationPrimaryId, ...verificationPrimaryId,
...timestamps, ...authTimestamps,
identifier: text('identifier').notNull(), identifier: text().notNull(),
value: text('value').notNull(), value: text().notNull(),
expiresAt: timestamp('expiresAt').notNull() expiresAt: timestamp({ mode: 'date' }).notNull()
}) })

Wyświetl plik

@ -157,6 +157,15 @@ export const timestamps = {
deletedAt: timestamp() 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 userRoleEnum = pgEnum('UserRole', ['user', 'admin'])
export const teamMemberRoleEnum = pgEnum('TeamMemberRole', ['user', 'admin']) export const teamMemberRoleEnum = pgEnum('TeamMemberRole', ['user', 'admin'])
export const logEntryTypeEnum = pgEnum('LogEntryType', ['log']) export const logEntryTypeEnum = pgEnum('LogEntryType', ['log'])

Wyświetl plik

@ -1,4 +1,3 @@
import { relations } from '@fisch0920/drizzle-orm'
import { import {
boolean, boolean,
index, index,
@ -8,15 +7,15 @@ import {
} from '@fisch0920/drizzle-orm/pg-core' } from '@fisch0920/drizzle-orm/pg-core'
import { import {
authTimestamps,
createSelectSchema, createSelectSchema,
createUpdateSchema,
stripeId, stripeId,
timestamps,
username, username,
// username, // username,
userPrimaryId, userPrimaryId,
userRoleEnum userRoleEnum
} from './common' } from './common'
import { teams } from './team'
// This table is mostly managed by better-auth. // This table is mostly managed by better-auth.
@ -24,15 +23,15 @@ export const users = pgTable(
'users', 'users',
{ {
...userPrimaryId, ...userPrimaryId,
...timestamps, ...authTimestamps,
name: text('name').notNull(), name: text().notNull(),
email: text('email').notNull().unique(), email: text().notNull().unique(),
emailVerified: boolean('emailVerified').default(false).notNull(), emailVerified: boolean().default(false).notNull(),
image: text('image'), image: text(),
// TODO: re-add username // TODO: re-add username
username: username(), username: username().unique(),
role: userRoleEnum().default('user').notNull(), role: userRoleEnum().default('user').notNull(),
isStripeConnectEnabledByDefault: boolean().default(true).notNull(), isStripeConnectEnabledByDefault: boolean().default(true).notNull(),
@ -41,61 +40,21 @@ export const users = pgTable(
}, },
(table) => [ (table) => [
uniqueIndex('user_email_idx').on(table.email), uniqueIndex('user_email_idx').on(table.email),
// uniqueIndex('user_username_idx').on(table.username), uniqueIndex('user_username_idx').on(table.username),
index('user_createdAt_idx').on(table.createdAt), index('user_createdAt_idx').on(table.createdAt),
index('user_updatedAt_idx').on(table.updatedAt), index('user_updatedAt_idx').on(table.updatedAt)
index('user_deletedAt_idx').on(table.deletedAt) // index('user_deletedAt_idx').on(table.deletedAt)
] ]
) )
export const usersRelations = relations(users, ({ many }) => ({ export const userSelectSchema = createSelectSchema(users)
teamsOwned: many(teams)
}))
export const userSelectSchema = createSelectSchema(users, {
// authProviders: publicAuthProvidersSchema
})
// .omit({ password: true, emailConfirmToken: true, passwordResetToken: true })
.strip() .strip()
.openapi('User') .openapi('User')
// export const userInsertSchema = createInsertSchema(users, { export const userUpdateSchema = createUpdateSchema(users)
// username: (schema) => .pick({
// schema.refine((username) => validators.username(username), { name: true,
// message: 'Invalid username' image: true,
// }), isStripeConnectEnabledByDefault: true
})
// email: (schema) => schema.email().optional() .strict()
// })
// .pick({
// username: true,
// email: true,
// password: true,
// firstName: true,
// lastName: true,
// image: true
// })
// .strict()
// .transform((user) => {
// return {
// ...user,
// emailConfirmToken: sha256(),
// password: user.password ? hashSync(user.password) : undefined
// }
// })
// export const userUpdateSchema = createUpdateSchema(users)
// .pick({
// firstName: true,
// lastName: true,
// image: true,
// password: true,
// isStripeConnectEnabledByDefault: true
// })
// .strict()
// .transform((user) => {
// return {
// ...user,
// password: user.password ? hashSync(user.password) : undefined
// }
// })

Wyświetl plik

@ -8,9 +8,12 @@ import { createIdForModel, db } from '@/db'
import { env } from './env' import { env } from './env'
export const auth = betterAuth({ export const auth = betterAuth({
adapter: drizzleAdapter(db, { appName: 'Agentic',
basePath: '/v1/auth',
database: drizzleAdapter(db, {
provider: 'pg' provider: 'pg'
}), }),
trustedOrigins: ['http://localhost:6013'],
emailAndPassword: { emailAndPassword: {
enabled: true enabled: true
}, },
@ -40,10 +43,18 @@ export const auth = betterAuth({
} }
}, },
session: { session: {
modelName: 'sessions' modelName: 'sessions',
cookieCache: {
enabled: true,
maxAge: 10 * 60 // 10 minutes in seconds
}
}, },
account: { account: {
modelName: 'accounts' modelName: 'accounts',
accountLinking: {
enabled: true,
trustedProviders: ['github']
}
}, },
verification: { verification: {
modelName: 'verifications' modelName: 'verifications'

Wyświetl plik

@ -65,40 +65,60 @@ export class ConsoleLogger implements Logger {
return return
} }
if (this.environment === 'development') {
this.console.trace(message, ...detail)
} else {
this.console.trace(this._marshal('trace', message, ...detail)) this.console.trace(this._marshal('trace', message, ...detail))
} }
}
debug(message?: any, ...detail: any[]) { debug(message?: any, ...detail: any[]) {
if (logLevelsMap[this.logLevel] > logLevelsMap.debug) { if (logLevelsMap[this.logLevel] > logLevelsMap.debug) {
return return
} }
if (this.environment === 'development') {
this.console.debug(message, ...detail)
} else {
this.console.debug(this._marshal('debug', message, ...detail)) this.console.debug(this._marshal('debug', message, ...detail))
} }
}
info(message?: any, ...detail: any[]) { info(message?: any, ...detail: any[]) {
if (logLevelsMap[this.logLevel] > logLevelsMap.info) { if (logLevelsMap[this.logLevel] > logLevelsMap.info) {
return return
} }
if (this.environment === 'development') {
this.console.info(message, ...detail)
} else {
this.console.info(this._marshal('info', message, ...detail)) this.console.info(this._marshal('info', message, ...detail))
} }
}
warn(message?: any, ...detail: any[]) { warn(message?: any, ...detail: any[]) {
if (logLevelsMap[this.logLevel] > logLevelsMap.warn) { if (logLevelsMap[this.logLevel] > logLevelsMap.warn) {
return return
} }
if (this.environment === 'development') {
this.console.warn(message, ...detail)
} else {
this.console.warn(this._marshal('warn', message, ...detail)) this.console.warn(this._marshal('warn', message, ...detail))
} }
}
error(message?: any, ...detail: any[]) { error(message?: any, ...detail: any[]) {
if (logLevelsMap[this.logLevel] > logLevelsMap.error) { if (logLevelsMap[this.logLevel] > logLevelsMap.error) {
return return
} }
if (this.environment === 'development') {
this.console.error(message, ...detail)
} else {
this.console.error(this._marshal('error', message, ...detail)) this.console.error(this._marshal('error', message, ...detail))
} }
}
protected _marshal(level: LogLevel, message?: any, ...detail: any[]): string { protected _marshal(level: LogLevel, message?: any, ...detail: any[]): string {
const log = { const log = {

Wyświetl plik

@ -16,7 +16,16 @@ export const app = new OpenAPIHono()
app.use(sentry()) app.use(sentry())
app.use(compress()) app.use(compress())
app.use(cors()) app.use(
cors({
origin: '*',
allowHeaders: ['Content-Type', 'Authorization'],
allowMethods: ['POST', 'GET', 'OPTIONS'],
exposeHeaders: ['Content-Length'],
maxAge: 600,
credentials: true
})
)
app.use(middleware.init) app.use(middleware.init)
app.use(middleware.accessLogger) app.use(middleware.accessLogger)
app.use(middleware.responseTime) app.use(middleware.responseTime)

Wyświetl plik

@ -1,7 +1,11 @@
import { config } from '@fisch0920/config/eslint' import { config } from '@fisch0920/config/eslint'
import drizzle from 'eslint-plugin-drizzle'
export default [ export default [
...config, ...config,
{
ignores: ['**/out/**', 'packages/api-client/src/openapi.d.ts']
},
{ {
files: ['**/*.ts', '**/*.tsx'], files: ['**/*.ts', '**/*.tsx'],
rules: { rules: {
@ -10,6 +14,20 @@ export default [
} }
}, },
{ {
ignores: ['**/out/**', 'packages/api-client/src/openapi.d.ts'] files: ['packages/cli/src/**/*.ts'],
rules: {
'no-console': 'off',
'no-process-env': 'off',
'unicorn/no-process-exit': 'off'
}
},
{
files: ['apps/api/src/**/*.ts'],
plugins: {
drizzle
},
rules: {
...drizzle.configs.recommended.rules
}
} }
] ]

Wyświetl plik

@ -24,6 +24,7 @@
}, },
"dependencies": { "dependencies": {
"@agentic/platform-core": "workspace:*", "@agentic/platform-core": "workspace:*",
"better-auth": "^1.2.8",
"ky": "catalog:", "ky": "catalog:",
"type-fest": "catalog:" "type-fest": "catalog:"
}, },

Wyświetl plik

@ -1,30 +1,55 @@
import type { Simplify } from 'type-fest' import type { Simplify } from 'type-fest'
import { getEnv, sanitizeSearchParams } from '@agentic/platform-core' import { assert, getEnv, sanitizeSearchParams } from '@agentic/platform-core'
import { createAuthClient } from 'better-auth/client'
import { username } from 'better-auth/plugins'
import defaultKy, { type KyInstance } from 'ky' import defaultKy, { type KyInstance } from 'ky'
import type { operations } from './openapi' import type { operations } from './openapi'
import type { AuthSession } from './types'
export class AgenticApiClient { export class AgenticApiClient {
static readonly DEFAULT_API_BASE_URL = 'https://api.agentic.so' static readonly DEFAULT_API_BASE_URL = 'https://api.agentic.so'
public readonly ky: KyInstance
public readonly apiBaseUrl: string public readonly apiBaseUrl: string
public readonly authClient: ReturnType<typeof createAuthClient>
public ky: KyInstance
constructor({ constructor({
apiKey = getEnv('AGENTIC_API_KEY'), apiCookie = getEnv('AGENTIC_API_COOKIE'),
apiBaseUrl = AgenticApiClient.DEFAULT_API_BASE_URL, apiBaseUrl = AgenticApiClient.DEFAULT_API_BASE_URL,
ky = defaultKy ky = defaultKy
}: { }: {
apiKey?: string apiCookie?: string
apiBaseUrl?: string apiBaseUrl?: string
ky?: KyInstance ky?: KyInstance
}) { }) {
assert(apiBaseUrl, 'AgenticApiClient missing required "apiBaseUrl"')
this.apiBaseUrl = apiBaseUrl this.apiBaseUrl = apiBaseUrl
this.ky = ky.extend({ this.ky = ky.extend({
prefixUrl: apiBaseUrl, prefixUrl: apiBaseUrl,
headers: { Authorization: `Bearer ${apiKey}` } // headers: { Authorization: `Bearer ${apiKey}` }
headers: { cookie: apiCookie }
}) })
this.authClient = createAuthClient({
baseURL: `${apiBaseUrl}/v1/auth`,
plugins: [username()]
})
}
async getAuthSession(cookie?: string): Promise<AuthSession> {
return this.ky
.get('v1/auth/get-session', cookie ? { headers: { cookie } } : {})
.json<AuthSession>()
}
async setAuthSession(cookie: string): Promise<AuthSession> {
this.ky = this.ky.extend({
headers: { cookie }
})
return this.getAuthSession()
} }
async getUser({ async getUser({

Wyświetl plik

@ -26,3 +26,31 @@ export type PricingPlan = components['schemas']['PricingPlan']
export type PricingPlanName = components['schemas']['name'] export type PricingPlanName = components['schemas']['name']
export type PricingPlanSlug = components['schemas']['slug'] export type PricingPlanSlug = components['schemas']['slug']
export type PricingPlanLabel = components['schemas']['label'] export type PricingPlanLabel = components['schemas']['label']
export type AuthSession = {
session: Session
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
}

Wyświetl plik

@ -29,10 +29,14 @@
"dependencies": { "dependencies": {
"@agentic/platform-api-client": "workspace:*", "@agentic/platform-api-client": "workspace:*",
"@agentic/platform-core": "workspace:*", "@agentic/platform-core": "workspace:*",
"better-auth": "^1.2.8", "@hono/node-server": "^1.14.1",
"commander": "^14.0.0", "commander": "^14.0.0",
"conf": "^13.1.0", "conf": "^13.1.0",
"dotenv": "catalog:",
"get-port": "^7.1.0",
"hono": "^4.7.9",
"inquirer": "^9.2.15", "inquirer": "^9.2.15",
"open": "^10.1.2",
"ora": "^8.2.0", "ora": "^8.2.0",
"restore-cursor": "catalog:", "restore-cursor": "catalog:",
"zod": "catalog:" "zod": "catalog:"

Wyświetl plik

@ -0,0 +1,35 @@
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

@ -0,0 +1,88 @@
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()
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',
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

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

Wyświetl plik

@ -1,37 +0,0 @@
import fs from 'node:fs/promises'
import path from 'node:path'
import { Command } from 'commander'
import ora from 'ora'
import { client } from '../client'
export const deploy = new Command('deploy')
.description('Creates a new deployment')
.argument('[path]', 'path to project directory', process.cwd())
.action(async (projectPath: string) => {
const spinner = ora('Creating deployment').start()
try {
// Read agentic.json from the project path
const configPath = path.join(projectPath, 'agentic.json')
const configContent = await fs.readFile(configPath, 'utf8')
const config = JSON.parse(configContent)
const deployment = await client.createDeployment(
{
identifier: config.name,
projectId: config.projectId,
version: config.version,
originUrl: config.originUrl || '',
pricingPlans: config.pricingPlans || []
},
{}
)
spinner.succeed('Deployment created successfully')
console.log(JSON.stringify(deployment, null, 2))
} catch (err) {
spinner.fail('Failed to create deployment')
console.error(err)
process.exit(1)
}
})

Wyświetl plik

@ -1,20 +0,0 @@
import { Command } from 'commander'
import ora from 'ora'
import { client } from '../client'
export const get = new Command('get')
.description('Gets details for a specific deployment')
.argument('<id>', 'deployment ID')
.action(async (id: string) => {
const spinner = ora('Fetching deployment details').start()
try {
const deployment = await client.getDeployment({ deploymentId: id })
spinner.succeed('Deployment details retrieved')
console.log(JSON.stringify(deployment, null, 2))
} catch (err) {
spinner.fail('Failed to fetch deployment details')
console.error(err)
process.exit(1)
}
})

Wyświetl plik

@ -1,21 +0,0 @@
import { Command } from 'commander'
import ora from 'ora'
import { client } from '../client'
export const ls = new Command('ls')
.alias('list')
.description('Lists deployments by project')
.argument('[project]', 'project ID')
.action(async (projectId?: string) => {
const spinner = ora('Fetching deployments').start()
try {
const deployments = await client.listDeployments({ projectId })
spinner.succeed('Deployments retrieved')
console.log(JSON.stringify(deployments, null, 2))
} catch (err) {
spinner.fail('Failed to fetch deployments')
console.error(err)
process.exit(1)
}
})

Wyświetl plik

@ -1,23 +1,21 @@
import { Command } from 'commander' import { Command } from 'commander'
import ora from 'ora' // import ora from 'ora'
import { client } from '../client'
export const publish = new Command('publish') export const publish = new Command('publish')
.description('Publishes a deployment') .description('Publishes a deployment')
.argument('[deploymentId]', 'deployment ID') .argument('[deploymentId]', 'deployment ID')
.action(async (deploymentId: string) => { .action(async (_opts) => {
const spinner = ora('Publishing deployment').start() // const spinner = ora('Publishing deployment').start()
try { // try {
const deployment = await client.publishDeployment( // const deployment = await client.publishDeployment(
{ version: '1.0.0' }, // { version: '1.0.0' },
{ deploymentId } // { deploymentId }
) // )
spinner.succeed('Deployment published successfully') // spinner.succeed('Deployment published successfully')
console.log(JSON.stringify(deployment, null, 2)) // console.log(JSON.stringify(deployment, null, 2))
} catch (err) { // } catch (err) {
spinner.fail('Failed to publish deployment') // spinner.fail('Failed to publish deployment')
console.error(err) // console.error(err)
process.exit(1) // process.exit(1)
} // }
}) })

Wyświetl plik

@ -2,8 +2,6 @@ import { Command } from 'commander'
import inquirer from 'inquirer' import inquirer from 'inquirer'
import ora from 'ora' import ora from 'ora'
import { client } from '../client'
export const rm = new Command('rm') export const rm = new Command('rm')
.description('Removes deployments') .description('Removes deployments')
.argument('[deploymentIds...]', 'deployment IDs to remove') .argument('[deploymentIds...]', 'deployment IDs to remove')

Wyświetl plik

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

Wyświetl plik

@ -1,15 +1,20 @@
import { Command } from 'commander' import { Command } from 'commander'
import { getAuth } from '../store' import type { Context } from '../types'
import { AuthStore } from '../store'
export const whoami = new Command('whoami') export function registerWhoAmICommand({ client, program }: Context) {
const command = new Command('whoami')
.description('Displays info about the current user') .description('Displays info about the current user')
.action(async () => { .action(async () => {
const auth = getAuth() if (!AuthStore.isAuthenticated()) {
console.log('Not signed in')
return
}
// TODO const res = await client.getAuthSession()
// eslint-disable-next-line no-console console.log(res)
console.log(
JSON.stringify({ user: auth.user, team: auth.teamSlug }, null, 2)
)
}) })
program.addCommand(command)
}

Wyświetl plik

@ -1,34 +1,46 @@
import { createAuthClient } from 'better-auth/client' import 'dotenv/config'
import { AgenticApiClient } from '@agentic/platform-api-client'
import { Command } from 'commander' import { Command } from 'commander'
import restoreCursor from 'restore-cursor' import restoreCursor from 'restore-cursor'
import { deploy } from './commands/deploy' import { registerSigninCommand } from './commands/signin'
import { get } from './commands/get' import { registerWhoAmICommand } from './commands/whoami'
import { ls } from './commands/ls' import { AuthStore } from './store'
import { publish } from './commands/publish'
import { rm } from './commands/rm'
import { signin } from './commands/signin'
const authClient = createAuthClient({
baseURL: 'http://localhost:3000/v1/auth'
})
async function main() { async function main() {
restoreCursor() restoreCursor()
const res = await authClient.signIn.social({ const client = new AgenticApiClient({
provider: 'github' apiCookie: AuthStore.tryGetAuth()?.cookie,
apiBaseUrl: process.env.AGENTIC_API_BASE_URL
}) })
console.log(res)
return
const program = new Command() const program = new Command('agentic')
program.addCommand(signin) .option('-j, --json', 'Print output in JSON format')
program.addCommand(get) .showHelpAfterError()
program.addCommand(ls)
program.addCommand(publish) const logger = {
program.addCommand(rm) log: (...args: any[]) => {
program.addCommand(deploy) if (program.opts().json) {
console.log(
args.length === 1 ? JSON.stringify(args[0]) : JSON.stringify(args)
)
} else {
console.log(...args)
}
}
}
const ctx = {
client,
program,
logger
}
// Register all commands
registerSigninCommand(ctx)
registerWhoAmICommand(ctx)
program.parse() program.parse()
} }

Wyświetl plik

@ -1,63 +1,70 @@
import type { User } from '@agentic/platform-api-client' import type { AuthSession } from '@agentic/platform-api-client'
import { assert } from '@agentic/platform-core' import { assert } from '@agentic/platform-core'
import Conf from 'conf' import Conf from 'conf'
export const store = new Conf({ projectName: 'agentic' }) export type AuthState = {
cookie: string
export type Auth = { session: AuthSession
token: string
user: User
teamId?: string teamId?: string
teamSlug?: string teamSlug?: string
} }
const keyTeamId = 'teamId' const keyTeamId = 'teamId'
const keyTeamSlug = 'teamSlug' const keyTeamSlug = 'teamSlug'
const keyToken = 'token' const keyCookie = 'cookie'
const keyUser = 'user' const keySession = 'session'
export function isAuthenticated() { export const AuthStore = {
return store.has(keyToken) && store.has(keyUser) store: new Conf({ projectName: 'agentic' }),
}
export function requireAuth() { isAuthenticated() {
return this.store.has(keyCookie) && this.store.has(keySession)
},
requireAuth() {
assert( assert(
isAuthenticated(), this.isAuthenticated(),
'Command requires authentication. Please login first.' 'Command requires authentication. Please login first.'
) )
} },
export function getAuth(): Auth { tryGetAuth(): AuthState | undefined {
requireAuth() if (!this.isAuthenticated()) {
return undefined
}
return { return {
token: store.get(keyToken), cookie: this.store.get(keyCookie),
user: store.get(keyUser), session: this.store.get(keySession),
teamId: store.get(keyTeamId), teamId: this.store.get(keyTeamId),
teamSlug: store.get(keyTeamSlug) teamSlug: this.store.get(keyTeamSlug)
} as Auth } as AuthState
} },
export function signinUser({ token, user }: { token: string; user: string }) { getAuth(): AuthState {
store.set(keyToken, token) this.requireAuth()
store.set(keyUser, user) return this.tryGetAuth()!
store.delete(keyTeamId) },
store.delete(keyTeamSlug)
}
export function signout() { setAuth({ cookie, session }: { cookie: string; session: AuthSession }) {
store.delete(keyToken) this.store.set(keyCookie, cookie)
store.delete(keyUser) this.store.set(keySession, session)
store.delete(keyTeamId) },
store.delete(keyTeamSlug)
}
export function switchTeam(team?: { id: string; slug: string }) { 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) { if (team?.id) {
store.set(keyTeamId, team.id) this.store.set(keyTeamId, team.id)
store.set(keyTeamSlug, team.slug) this.store.set(keyTeamSlug, team.slug)
} else { } else {
store.delete(keyTeamId) this.store.delete(keyTeamId)
store.delete(keyTeamSlug) this.store.delete(keyTeamSlug)
}
} }
} }

Wyświetl plik

@ -0,0 +1,10 @@
import type { AgenticApiClient } from '@agentic/platform-api-client'
import type { Command } from 'commander'
export type Context = {
client: AgenticApiClient
program: Command
logger: {
log: (...args: any[]) => void
}
}

Wyświetl plik

@ -219,6 +219,9 @@ importers:
'@agentic/platform-core': '@agentic/platform-core':
specifier: workspace:* specifier: workspace:*
version: link:../core version: link:../core
better-auth:
specifier: ^1.2.8
version: 1.2.8
ky: ky:
specifier: 'catalog:' specifier: 'catalog:'
version: 1.8.1 version: 1.8.1
@ -238,18 +241,30 @@ importers:
'@agentic/platform-core': '@agentic/platform-core':
specifier: workspace:* specifier: workspace:*
version: link:../core version: link:../core
better-auth: '@hono/node-server':
specifier: ^1.2.8 specifier: ^1.14.1
version: 1.2.8 version: 1.14.1(hono@4.7.9)
commander: commander:
specifier: ^14.0.0 specifier: ^14.0.0
version: 14.0.0 version: 14.0.0
conf: conf:
specifier: ^13.1.0 specifier: ^13.1.0
version: 13.1.0 version: 13.1.0
dotenv:
specifier: 'catalog:'
version: 16.5.0
get-port:
specifier: ^7.1.0
version: 7.1.0
hono:
specifier: ^4.7.9
version: 4.7.9
inquirer: inquirer:
specifier: ^9.2.15 specifier: ^9.2.15
version: 9.3.7 version: 9.3.7
open:
specifier: ^10.1.2
version: 10.1.2
ora: ora:
specifier: ^8.2.0 specifier: ^8.2.0
version: 8.2.0 version: 8.2.0
@ -1558,6 +1573,10 @@ packages:
resolution: {integrity: sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg==} resolution: {integrity: sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg==}
engines: {node: '>=18.20'} engines: {node: '>=18.20'}
bundle-name@4.1.0:
resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
engines: {node: '>=18'}
bundle-require@5.1.0: bundle-require@5.1.0:
resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@ -1771,6 +1790,14 @@ packages:
deep-is@0.1.4: deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
default-browser-id@5.0.0:
resolution: {integrity: sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==}
engines: {node: '>=18'}
default-browser@5.2.1:
resolution: {integrity: sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==}
engines: {node: '>=18'}
defaults@1.0.4: defaults@1.0.4:
resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==}
@ -1778,6 +1805,10 @@ packages:
resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
define-lazy-prop@3.0.0:
resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==}
engines: {node: '>=12'}
define-properties@1.2.1: define-properties@1.2.1:
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@ -2285,6 +2316,10 @@ packages:
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
get-port@7.1.0:
resolution: {integrity: sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==}
engines: {node: '>=16'}
get-proto@1.0.1: get-proto@1.0.1:
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@ -2474,6 +2509,11 @@ packages:
resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
is-docker@3.0.0:
resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
hasBin: true
is-extglob@2.1.1: is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -2502,6 +2542,11 @@ packages:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
is-inside-container@1.0.0:
resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==}
engines: {node: '>=14.16'}
hasBin: true
is-interactive@1.0.0: is-interactive@1.0.0:
resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -2593,6 +2638,10 @@ packages:
resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
is-wsl@3.1.0:
resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==}
engines: {node: '>=16'}
isarray@2.0.5: isarray@2.0.5:
resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
@ -2943,6 +2992,10 @@ packages:
resolution: {integrity: sha512-M7CJbmv7UCopc0neRKdzfoGWaVZC+xC1925GitKH9EAqYFzX9//25Q7oX4+jw0tiCCj+t5l6VZh8UPH23NZkMA==} resolution: {integrity: sha512-M7CJbmv7UCopc0neRKdzfoGWaVZC+xC1925GitKH9EAqYFzX9//25Q7oX4+jw0tiCCj+t5l6VZh8UPH23NZkMA==}
hasBin: true hasBin: true
open@10.1.2:
resolution: {integrity: sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw==}
engines: {node: '>=18'}
openapi-typescript@7.8.0: openapi-typescript@7.8.0:
resolution: {integrity: sha512-1EeVWmDzi16A+siQlo/SwSGIT7HwaFAVjvMA7/jG5HMLSnrUOzPL7uSTRZZa4v/LCRxHTApHKtNY6glApEoiUQ==} resolution: {integrity: sha512-1EeVWmDzi16A+siQlo/SwSGIT7HwaFAVjvMA7/jG5HMLSnrUOzPL7uSTRZZa4v/LCRxHTApHKtNY6glApEoiUQ==}
hasBin: true hasBin: true
@ -3265,6 +3318,10 @@ packages:
resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==}
engines: {node: '>= 18'} engines: {node: '>= 18'}
run-applescript@7.0.0:
resolution: {integrity: sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==}
engines: {node: '>=18'}
run-async@3.0.0: run-async@3.0.0:
resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==} resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==}
engines: {node: '>=0.12.0'} engines: {node: '>=0.12.0'}
@ -5177,6 +5234,10 @@ snapshots:
builtin-modules@5.0.0: {} builtin-modules@5.0.0: {}
bundle-name@4.1.0:
dependencies:
run-applescript: 7.0.0
bundle-require@5.1.0(esbuild@0.25.4): bundle-require@5.1.0(esbuild@0.25.4):
dependencies: dependencies:
esbuild: 0.25.4 esbuild: 0.25.4
@ -5360,6 +5421,13 @@ snapshots:
deep-is@0.1.4: {} deep-is@0.1.4: {}
default-browser-id@5.0.0: {}
default-browser@5.2.1:
dependencies:
bundle-name: 4.1.0
default-browser-id: 5.0.0
defaults@1.0.4: defaults@1.0.4:
dependencies: dependencies:
clone: 1.0.4 clone: 1.0.4
@ -5370,6 +5438,8 @@ snapshots:
es-errors: 1.3.0 es-errors: 1.3.0
gopd: 1.2.0 gopd: 1.2.0
define-lazy-prop@3.0.0: {}
define-properties@1.2.1: define-properties@1.2.1:
dependencies: dependencies:
define-data-property: 1.1.4 define-data-property: 1.1.4
@ -5998,6 +6068,8 @@ snapshots:
hasown: 2.0.2 hasown: 2.0.2
math-intrinsics: 1.1.0 math-intrinsics: 1.1.0
get-port@7.1.0: {}
get-proto@1.0.1: get-proto@1.0.1:
dependencies: dependencies:
dunder-proto: 1.0.1 dunder-proto: 1.0.1
@ -6207,6 +6279,8 @@ snapshots:
call-bound: 1.0.4 call-bound: 1.0.4
has-tostringtag: 1.0.2 has-tostringtag: 1.0.2
is-docker@3.0.0: {}
is-extglob@2.1.1: {} is-extglob@2.1.1: {}
is-finalizationregistry@1.1.1: is-finalizationregistry@1.1.1:
@ -6232,6 +6306,10 @@ snapshots:
dependencies: dependencies:
is-extglob: 2.1.1 is-extglob: 2.1.1
is-inside-container@1.0.0:
dependencies:
is-docker: 3.0.0
is-interactive@1.0.0: {} is-interactive@1.0.0: {}
is-interactive@2.0.0: {} is-interactive@2.0.0: {}
@ -6304,6 +6382,10 @@ snapshots:
call-bound: 1.0.4 call-bound: 1.0.4
get-intrinsic: 1.3.0 get-intrinsic: 1.3.0
is-wsl@3.1.0:
dependencies:
is-inside-container: 1.0.0
isarray@2.0.5: {} isarray@2.0.5: {}
isexe@2.0.0: {} isexe@2.0.0: {}
@ -6643,6 +6725,13 @@ snapshots:
dependencies: dependencies:
which-pm-runs: 1.1.0 which-pm-runs: 1.1.0
open@10.1.2:
dependencies:
default-browser: 5.2.1
define-lazy-prop: 3.0.0
is-inside-container: 1.0.0
is-wsl: 3.1.0
openapi-typescript@7.8.0(typescript@5.8.3): openapi-typescript@7.8.0(typescript@5.8.3):
dependencies: dependencies:
'@redocly/openapi-core': 1.34.3(supports-color@10.0.0) '@redocly/openapi-core': 1.34.3(supports-color@10.0.0)
@ -6978,6 +7067,8 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
run-applescript@7.0.0: {}
run-async@3.0.0: {} run-async@3.0.0: {}
run-parallel@1.2.0: run-parallel@1.2.0: