kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
feat: add initial impl of better-auth
rodzic
cfff2d37fc
commit
8a5a9b421e
|
@ -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/**"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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/**']
|
||||
}
|
||||
]
|
|
@ -1,7 +1,7 @@
|
|||
import { OpenAPIHono } from '@hono/zod-openapi'
|
||||
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 * as middleware from '@/lib/middleware'
|
||||
import { registerOpenAPIErrorResponses } from '@/lib/openapi-utils'
|
||||
|
@ -18,7 +18,6 @@ import { registerV1DeploymentsListDeployments } from './deployments/list-deploym
|
|||
import { registerV1DeploymentsPublishDeployment } from './deployments/publish-deployment'
|
||||
import { registerV1DeploymentsUpdateDeployment } from './deployments/update-deployment'
|
||||
import { registerHealthCheck } from './health-check'
|
||||
// import { registerV1OAuthRedirect } from './oauth-redirect'
|
||||
import { registerV1ProjectsCreateProject } from './projects/create-project'
|
||||
import { registerV1ProjectsGetProject } from './projects/get-project'
|
||||
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 { registerV1TeamsMembersUpdateTeamMember } from './teams/members/update-team-member'
|
||||
import { registerV1TeamsUpdateTeam } from './teams/update-team'
|
||||
// import { registerV1UsersGetUser } from './users/get-user'
|
||||
// import { registerV1UsersUpdateUser } from './users/update-user'
|
||||
import { registerV1UsersGetUser } from './users/get-user'
|
||||
import { registerV1UsersUpdateUser } from './users/update-user'
|
||||
import { registerV1StripeWebhook } from './webhooks/stripe-webhook'
|
||||
|
||||
export const apiV1 = new OpenAPIHono({
|
||||
|
@ -65,8 +64,8 @@ const privateRouter = new OpenAPIHono<AuthenticatedEnv>()
|
|||
registerHealthCheck(publicRouter)
|
||||
|
||||
// Users
|
||||
// registerV1UsersGetUser(privateRouter)
|
||||
// registerV1UsersUpdateUser(privateRouter)
|
||||
registerV1UsersGetUser(privateRouter)
|
||||
registerV1UsersUpdateUser(privateRouter)
|
||||
|
||||
// Teams
|
||||
registerV1TeamsCreateTeam(privateRouter)
|
||||
|
@ -106,10 +105,15 @@ registerV1AdminConsumersGetConsumerByToken(privateRouter)
|
|||
// Webhook event handlers
|
||||
registerV1StripeWebhook(publicRouter)
|
||||
|
||||
// OAuth redirect
|
||||
// registerV1OAuthRedirect(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())
|
||||
|
||||
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
|
||||
apiV1.route('/', publicRouter)
|
||||
|
|
|
@ -1,51 +1,51 @@
|
|||
// import { assert, parseZodSchema } from '@agentic/platform-core'
|
||||
// import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
|
||||
import { assert, parseZodSchema } from '@agentic/platform-core'
|
||||
import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
|
||||
|
||||
// import type { AuthenticatedEnv } from '@/lib/types'
|
||||
// import { db, eq, schema } from '@/db'
|
||||
// import { acl } from '@/lib/acl'
|
||||
// import {
|
||||
// openapiAuthenticatedSecuritySchemas,
|
||||
// openapiErrorResponse404,
|
||||
// openapiErrorResponses
|
||||
// } from '@/lib/openapi-utils'
|
||||
import type { AuthenticatedEnv } from '@/lib/types'
|
||||
import { db, eq, schema } from '@/db'
|
||||
import { acl } from '@/lib/acl'
|
||||
import {
|
||||
openapiAuthenticatedSecuritySchemas,
|
||||
openapiErrorResponse404,
|
||||
openapiErrorResponses
|
||||
} from '@/lib/openapi-utils'
|
||||
|
||||
// import { userIdParamsSchema } from './schemas'
|
||||
import { userIdParamsSchema } from './schemas'
|
||||
|
||||
// const route = createRoute({
|
||||
// description: 'Gets a user',
|
||||
// tags: ['users'],
|
||||
// operationId: 'getUser',
|
||||
// method: 'get',
|
||||
// path: 'users/{userId}',
|
||||
// security: openapiAuthenticatedSecuritySchemas,
|
||||
// request: {
|
||||
// params: userIdParamsSchema
|
||||
// },
|
||||
// responses: {
|
||||
// 200: {
|
||||
// description: 'A user object',
|
||||
// content: {
|
||||
// 'application/json': {
|
||||
// schema: schema.userSelectSchema
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// ...openapiErrorResponses,
|
||||
// ...openapiErrorResponse404
|
||||
// }
|
||||
// })
|
||||
const route = createRoute({
|
||||
description: 'Gets a user',
|
||||
tags: ['users'],
|
||||
operationId: 'getUser',
|
||||
method: 'get',
|
||||
path: 'users/{userId}',
|
||||
security: openapiAuthenticatedSecuritySchemas,
|
||||
request: {
|
||||
params: userIdParamsSchema
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: 'A user object',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: schema.userSelectSchema
|
||||
}
|
||||
}
|
||||
},
|
||||
...openapiErrorResponses,
|
||||
...openapiErrorResponse404
|
||||
}
|
||||
})
|
||||
|
||||
// export function registerV1UsersGetUser(app: OpenAPIHono<AuthenticatedEnv>) {
|
||||
// return app.openapi(route, async (c) => {
|
||||
// const { userId } = c.req.valid('param')
|
||||
// await acl(c, { userId }, { label: 'User' })
|
||||
export function registerV1UsersGetUser(app: OpenAPIHono<AuthenticatedEnv>) {
|
||||
return app.openapi(route, async (c) => {
|
||||
const { userId } = c.req.valid('param')
|
||||
await acl(c, { userId }, { label: 'User' })
|
||||
|
||||
// const user = await db.query.users.findFirst({
|
||||
// where: eq(schema.users.id, userId)
|
||||
// })
|
||||
// assert(user, 404, `User not found "${userId}"`)
|
||||
const user = await db.query.users.findFirst({
|
||||
where: eq(schema.users.id, userId)
|
||||
})
|
||||
assert(user, 404, `User not found "${userId}"`)
|
||||
|
||||
// return c.json(parseZodSchema(schema.userSelectSchema, user))
|
||||
// })
|
||||
// }
|
||||
return c.json(parseZodSchema(schema.userSelectSchema, user))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,62 +1,62 @@
|
|||
// import { assert, parseZodSchema } from '@agentic/platform-core'
|
||||
// import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
|
||||
import { assert, parseZodSchema } from '@agentic/platform-core'
|
||||
import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
|
||||
|
||||
// import type { AuthenticatedEnv } from '@/lib/types'
|
||||
// import { db, eq, schema } from '@/db'
|
||||
// import { acl } from '@/lib/acl'
|
||||
// import {
|
||||
// openapiAuthenticatedSecuritySchemas,
|
||||
// openapiErrorResponse404,
|
||||
// openapiErrorResponses
|
||||
// } from '@/lib/openapi-utils'
|
||||
import type { AuthenticatedEnv } from '@/lib/types'
|
||||
import { db, eq, schema } from '@/db'
|
||||
import { acl } from '@/lib/acl'
|
||||
import {
|
||||
openapiAuthenticatedSecuritySchemas,
|
||||
openapiErrorResponse404,
|
||||
openapiErrorResponses
|
||||
} from '@/lib/openapi-utils'
|
||||
|
||||
// import { userIdParamsSchema } from './schemas'
|
||||
import { userIdParamsSchema } from './schemas'
|
||||
|
||||
// const route = createRoute({
|
||||
// description: 'Updates a user',
|
||||
// tags: ['users'],
|
||||
// operationId: 'updateUser',
|
||||
// method: 'post',
|
||||
// path: 'users/{userId}',
|
||||
// security: openapiAuthenticatedSecuritySchemas,
|
||||
// request: {
|
||||
// params: userIdParamsSchema,
|
||||
// body: {
|
||||
// required: true,
|
||||
// content: {
|
||||
// 'application/json': {
|
||||
// schema: schema.userUpdateSchema
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// responses: {
|
||||
// 200: {
|
||||
// description: 'A user object',
|
||||
// content: {
|
||||
// 'application/json': {
|
||||
// schema: schema.userSelectSchema
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// ...openapiErrorResponses,
|
||||
// ...openapiErrorResponse404
|
||||
// }
|
||||
// })
|
||||
const route = createRoute({
|
||||
description: 'Updates a user',
|
||||
tags: ['users'],
|
||||
operationId: 'updateUser',
|
||||
method: 'post',
|
||||
path: 'users/{userId}',
|
||||
security: openapiAuthenticatedSecuritySchemas,
|
||||
request: {
|
||||
params: userIdParamsSchema,
|
||||
body: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: schema.userUpdateSchema
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: 'A user object',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: schema.userSelectSchema
|
||||
}
|
||||
}
|
||||
},
|
||||
...openapiErrorResponses,
|
||||
...openapiErrorResponse404
|
||||
}
|
||||
})
|
||||
|
||||
// export function registerV1UsersUpdateUser(app: OpenAPIHono<AuthenticatedEnv>) {
|
||||
// return app.openapi(route, async (c) => {
|
||||
// const { userId } = c.req.valid('param')
|
||||
// await acl(c, { userId }, { label: 'User' })
|
||||
// const body = c.req.valid('json')
|
||||
export function registerV1UsersUpdateUser(app: OpenAPIHono<AuthenticatedEnv>) {
|
||||
return app.openapi(route, async (c) => {
|
||||
const { userId } = c.req.valid('param')
|
||||
await acl(c, { userId }, { label: 'User' })
|
||||
const body = c.req.valid('json')
|
||||
|
||||
// const [user] = await db
|
||||
// .update(schema.users)
|
||||
// .set(body)
|
||||
// .where(eq(schema.users.id, userId))
|
||||
// .returning()
|
||||
// assert(user, 404, `User not found "${userId}"`)
|
||||
const [user] = await db
|
||||
.update(schema.users)
|
||||
.set(body)
|
||||
.where(eq(schema.users.id, userId))
|
||||
.returning()
|
||||
assert(user, 404, `User not found "${userId}"`)
|
||||
|
||||
// return c.json(parseZodSchema(schema.userSelectSchema, user))
|
||||
// })
|
||||
// }
|
||||
return c.json(parseZodSchema(schema.userSelectSchema, user))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@ import { pgTable, text, timestamp } from '@fisch0920/drizzle-orm/pg-core'
|
|||
|
||||
import {
|
||||
accountPrimaryId,
|
||||
authTimestamps,
|
||||
sessionPrimaryId,
|
||||
timestamps,
|
||||
userId,
|
||||
verificationPrimaryId
|
||||
} from './common'
|
||||
|
@ -13,38 +13,41 @@ import { users } from './user'
|
|||
|
||||
export const sessions = pgTable('sessions', {
|
||||
...sessionPrimaryId,
|
||||
...timestamps,
|
||||
...authTimestamps,
|
||||
|
||||
expiresAt: timestamp('expiresAt').notNull(),
|
||||
ipAddress: text('ipAddress'),
|
||||
userAgent: text('userAgent'),
|
||||
token: text().notNull().unique(),
|
||||
expiresAt: timestamp({ mode: 'date' }).notNull(),
|
||||
ipAddress: text(),
|
||||
userAgent: text(),
|
||||
userId: userId()
|
||||
.notNull()
|
||||
.references(() => users.id)
|
||||
.references(() => users.id, { onDelete: 'cascade' })
|
||||
})
|
||||
|
||||
export const accounts = pgTable('accounts', {
|
||||
...accountPrimaryId,
|
||||
...timestamps,
|
||||
...authTimestamps,
|
||||
|
||||
accountId: text('accountId').notNull(),
|
||||
providerId: text('providerId').notNull(),
|
||||
accountId: text().notNull(),
|
||||
providerId: text().notNull(),
|
||||
userId: userId()
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
accessToken: text('accessToken'),
|
||||
refreshToken: text('refreshToken'),
|
||||
idToken: text('idToken'),
|
||||
expiresAt: timestamp('expiresAt').notNull(),
|
||||
password: text('password')
|
||||
.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,
|
||||
...timestamps,
|
||||
...authTimestamps,
|
||||
|
||||
identifier: text('identifier').notNull(),
|
||||
value: text('value').notNull(),
|
||||
identifier: text().notNull(),
|
||||
value: text().notNull(),
|
||||
|
||||
expiresAt: timestamp('expiresAt').notNull()
|
||||
expiresAt: timestamp({ mode: 'date' }).notNull()
|
||||
})
|
||||
|
|
|
@ -157,6 +157,15 @@ 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'])
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { relations } from '@fisch0920/drizzle-orm'
|
||||
import {
|
||||
boolean,
|
||||
index,
|
||||
|
@ -8,15 +7,15 @@ import {
|
|||
} from '@fisch0920/drizzle-orm/pg-core'
|
||||
|
||||
import {
|
||||
authTimestamps,
|
||||
createSelectSchema,
|
||||
createUpdateSchema,
|
||||
stripeId,
|
||||
timestamps,
|
||||
username,
|
||||
// username,
|
||||
userPrimaryId,
|
||||
userRoleEnum
|
||||
} from './common'
|
||||
import { teams } from './team'
|
||||
|
||||
// This table is mostly managed by better-auth.
|
||||
|
||||
|
@ -24,15 +23,15 @@ export const users = pgTable(
|
|||
'users',
|
||||
{
|
||||
...userPrimaryId,
|
||||
...timestamps,
|
||||
...authTimestamps,
|
||||
|
||||
name: text('name').notNull(),
|
||||
email: text('email').notNull().unique(),
|
||||
emailVerified: boolean('emailVerified').default(false).notNull(),
|
||||
image: text('image'),
|
||||
name: text().notNull(),
|
||||
email: text().notNull().unique(),
|
||||
emailVerified: boolean().default(false).notNull(),
|
||||
image: text(),
|
||||
|
||||
// TODO: re-add username
|
||||
username: username(),
|
||||
username: username().unique(),
|
||||
role: userRoleEnum().default('user').notNull(),
|
||||
|
||||
isStripeConnectEnabledByDefault: boolean().default(true).notNull(),
|
||||
|
@ -41,61 +40,21 @@ export const users = pgTable(
|
|||
},
|
||||
(table) => [
|
||||
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_updatedAt_idx').on(table.updatedAt),
|
||||
index('user_deletedAt_idx').on(table.deletedAt)
|
||||
index('user_updatedAt_idx').on(table.updatedAt)
|
||||
// index('user_deletedAt_idx').on(table.deletedAt)
|
||||
]
|
||||
)
|
||||
|
||||
export const usersRelations = relations(users, ({ many }) => ({
|
||||
teamsOwned: many(teams)
|
||||
}))
|
||||
|
||||
export const userSelectSchema = createSelectSchema(users, {
|
||||
// authProviders: publicAuthProvidersSchema
|
||||
})
|
||||
// .omit({ password: true, emailConfirmToken: true, passwordResetToken: true })
|
||||
export const userSelectSchema = createSelectSchema(users)
|
||||
.strip()
|
||||
.openapi('User')
|
||||
|
||||
// export const userInsertSchema = createInsertSchema(users, {
|
||||
// username: (schema) =>
|
||||
// schema.refine((username) => validators.username(username), {
|
||||
// message: 'Invalid username'
|
||||
// }),
|
||||
|
||||
// email: (schema) => schema.email().optional()
|
||||
// })
|
||||
// .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
|
||||
// }
|
||||
// })
|
||||
export const userUpdateSchema = createUpdateSchema(users)
|
||||
.pick({
|
||||
name: true,
|
||||
image: true,
|
||||
isStripeConnectEnabledByDefault: true
|
||||
})
|
||||
.strict()
|
||||
|
|
|
@ -8,9 +8,12 @@ import { createIdForModel, db } from '@/db'
|
|||
import { env } from './env'
|
||||
|
||||
export const auth = betterAuth({
|
||||
adapter: drizzleAdapter(db, {
|
||||
appName: 'Agentic',
|
||||
basePath: '/v1/auth',
|
||||
database: drizzleAdapter(db, {
|
||||
provider: 'pg'
|
||||
}),
|
||||
trustedOrigins: ['http://localhost:6013'],
|
||||
emailAndPassword: {
|
||||
enabled: true
|
||||
},
|
||||
|
@ -40,10 +43,18 @@ export const auth = betterAuth({
|
|||
}
|
||||
},
|
||||
session: {
|
||||
modelName: 'sessions'
|
||||
modelName: 'sessions',
|
||||
cookieCache: {
|
||||
enabled: true,
|
||||
maxAge: 10 * 60 // 10 minutes in seconds
|
||||
}
|
||||
},
|
||||
account: {
|
||||
modelName: 'accounts'
|
||||
modelName: 'accounts',
|
||||
accountLinking: {
|
||||
enabled: true,
|
||||
trustedProviders: ['github']
|
||||
}
|
||||
},
|
||||
verification: {
|
||||
modelName: 'verifications'
|
||||
|
|
|
@ -65,7 +65,11 @@ export class ConsoleLogger implements Logger {
|
|||
return
|
||||
}
|
||||
|
||||
this.console.trace(this._marshal('trace', message, ...detail))
|
||||
if (this.environment === 'development') {
|
||||
this.console.trace(message, ...detail)
|
||||
} else {
|
||||
this.console.trace(this._marshal('trace', message, ...detail))
|
||||
}
|
||||
}
|
||||
|
||||
debug(message?: any, ...detail: any[]) {
|
||||
|
@ -73,7 +77,11 @@ export class ConsoleLogger implements Logger {
|
|||
return
|
||||
}
|
||||
|
||||
this.console.debug(this._marshal('debug', message, ...detail))
|
||||
if (this.environment === 'development') {
|
||||
this.console.debug(message, ...detail)
|
||||
} else {
|
||||
this.console.debug(this._marshal('debug', message, ...detail))
|
||||
}
|
||||
}
|
||||
|
||||
info(message?: any, ...detail: any[]) {
|
||||
|
@ -81,7 +89,11 @@ export class ConsoleLogger implements Logger {
|
|||
return
|
||||
}
|
||||
|
||||
this.console.info(this._marshal('info', message, ...detail))
|
||||
if (this.environment === 'development') {
|
||||
this.console.info(message, ...detail)
|
||||
} else {
|
||||
this.console.info(this._marshal('info', message, ...detail))
|
||||
}
|
||||
}
|
||||
|
||||
warn(message?: any, ...detail: any[]) {
|
||||
|
@ -89,7 +101,11 @@ export class ConsoleLogger implements Logger {
|
|||
return
|
||||
}
|
||||
|
||||
this.console.warn(this._marshal('warn', message, ...detail))
|
||||
if (this.environment === 'development') {
|
||||
this.console.warn(message, ...detail)
|
||||
} else {
|
||||
this.console.warn(this._marshal('warn', message, ...detail))
|
||||
}
|
||||
}
|
||||
|
||||
error(message?: any, ...detail: any[]) {
|
||||
|
@ -97,7 +113,11 @@ export class ConsoleLogger implements Logger {
|
|||
return
|
||||
}
|
||||
|
||||
this.console.error(this._marshal('error', message, ...detail))
|
||||
if (this.environment === 'development') {
|
||||
this.console.error(message, ...detail)
|
||||
} else {
|
||||
this.console.error(this._marshal('error', message, ...detail))
|
||||
}
|
||||
}
|
||||
|
||||
protected _marshal(level: LogLevel, message?: any, ...detail: any[]): string {
|
||||
|
|
|
@ -16,7 +16,16 @@ export const app = new OpenAPIHono()
|
|||
|
||||
app.use(sentry())
|
||||
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.accessLogger)
|
||||
app.use(middleware.responseTime)
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import { config } from '@fisch0920/config/eslint'
|
||||
import drizzle from 'eslint-plugin-drizzle'
|
||||
|
||||
export default [
|
||||
...config,
|
||||
{
|
||||
ignores: ['**/out/**', 'packages/api-client/src/openapi.d.ts']
|
||||
},
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
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
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@agentic/platform-core": "workspace:*",
|
||||
"better-auth": "^1.2.8",
|
||||
"ky": "catalog:",
|
||||
"type-fest": "catalog:"
|
||||
},
|
||||
|
|
|
@ -1,30 +1,55 @@
|
|||
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 type { operations } from './openapi'
|
||||
import type { AuthSession } from './types'
|
||||
|
||||
export class AgenticApiClient {
|
||||
static readonly DEFAULT_API_BASE_URL = 'https://api.agentic.so'
|
||||
|
||||
public readonly ky: KyInstance
|
||||
public readonly apiBaseUrl: string
|
||||
public readonly authClient: ReturnType<typeof createAuthClient>
|
||||
public ky: KyInstance
|
||||
|
||||
constructor({
|
||||
apiKey = getEnv('AGENTIC_API_KEY'),
|
||||
apiCookie = getEnv('AGENTIC_API_COOKIE'),
|
||||
apiBaseUrl = AgenticApiClient.DEFAULT_API_BASE_URL,
|
||||
ky = defaultKy
|
||||
}: {
|
||||
apiKey?: string
|
||||
apiCookie?: string
|
||||
apiBaseUrl?: string
|
||||
ky?: KyInstance
|
||||
}) {
|
||||
assert(apiBaseUrl, 'AgenticApiClient missing required "apiBaseUrl"')
|
||||
this.apiBaseUrl = apiBaseUrl
|
||||
|
||||
this.ky = ky.extend({
|
||||
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({
|
||||
|
|
|
@ -26,3 +26,31 @@ export type PricingPlan = components['schemas']['PricingPlan']
|
|||
export type PricingPlanName = components['schemas']['name']
|
||||
export type PricingPlanSlug = components['schemas']['slug']
|
||||
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
|
||||
}
|
||||
|
|
|
@ -29,10 +29,14 @@
|
|||
"dependencies": {
|
||||
"@agentic/platform-api-client": "workspace:*",
|
||||
"@agentic/platform-core": "workspace:*",
|
||||
"better-auth": "^1.2.8",
|
||||
"@hono/node-server": "^1.14.1",
|
||||
"commander": "^14.0.0",
|
||||
"conf": "^13.1.0",
|
||||
"dotenv": "catalog:",
|
||||
"get-port": "^7.1.0",
|
||||
"hono": "^4.7.9",
|
||||
"inquirer": "^9.2.15",
|
||||
"open": "^10.1.2",
|
||||
"ora": "^8.2.0",
|
||||
"restore-cursor": "catalog:",
|
||||
"zod": "catalog:"
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
import { AgenticApiClient } from '@agentic/platform-api-client'
|
||||
|
||||
import { AuthStore } from './store'
|
||||
|
||||
// Create a singleton instance of the API client
|
||||
export const client = new AgenticApiClient({
|
||||
apiKey: process.env.AGENTIC_API_KEY
|
||||
apiCookie: AuthStore.tryGetAuth()?.cookie,
|
||||
apiBaseUrl: 'http://localhost:3000'
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
|
@ -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)
|
||||
}
|
||||
})
|
|
@ -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)
|
||||
}
|
||||
})
|
|
@ -1,23 +1,21 @@
|
|||
import { Command } from 'commander'
|
||||
import ora from 'ora'
|
||||
|
||||
import { client } from '../client'
|
||||
// import ora from 'ora'
|
||||
|
||||
export const publish = new Command('publish')
|
||||
.description('Publishes a deployment')
|
||||
.argument('[deploymentId]', 'deployment ID')
|
||||
.action(async (deploymentId: string) => {
|
||||
const spinner = ora('Publishing deployment').start()
|
||||
try {
|
||||
const deployment = await client.publishDeployment(
|
||||
{ version: '1.0.0' },
|
||||
{ deploymentId }
|
||||
)
|
||||
spinner.succeed('Deployment published successfully')
|
||||
console.log(JSON.stringify(deployment, null, 2))
|
||||
} catch (err) {
|
||||
spinner.fail('Failed to publish deployment')
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
}
|
||||
.action(async (_opts) => {
|
||||
// const spinner = ora('Publishing deployment').start()
|
||||
// try {
|
||||
// const deployment = await client.publishDeployment(
|
||||
// { version: '1.0.0' },
|
||||
// { deploymentId }
|
||||
// )
|
||||
// spinner.succeed('Deployment published successfully')
|
||||
// console.log(JSON.stringify(deployment, null, 2))
|
||||
// } catch (err) {
|
||||
// spinner.fail('Failed to publish deployment')
|
||||
// console.error(err)
|
||||
// process.exit(1)
|
||||
// }
|
||||
})
|
||||
|
|
|
@ -2,8 +2,6 @@ import { Command } from 'commander'
|
|||
import inquirer from 'inquirer'
|
||||
import ora from 'ora'
|
||||
|
||||
import { client } from '../client'
|
||||
|
||||
export const rm = new Command('rm')
|
||||
.description('Removes deployments')
|
||||
.argument('[deploymentIds...]', 'deployment IDs to remove')
|
||||
|
|
|
@ -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')
|
||||
.alias('signin')
|
||||
.description(
|
||||
'Signs in to Agentic. If no credentials are provided, uses GitHub auth.'
|
||||
)
|
||||
.option('-u, --username <username>', 'account username')
|
||||
.option('-e, --email <email>', 'account email')
|
||||
.option('-p, --password <password>', 'account password')
|
||||
.action(async (_opts) => {
|
||||
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')
|
||||
.description(
|
||||
'Signs in to Agentic. If no credentials are provided, uses GitHub auth.'
|
||||
)
|
||||
// TODO
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('TODO: signin')
|
||||
})
|
||||
// .option('-u, --username <username>', 'account username')
|
||||
.option('-e, --email <email>', 'account email')
|
||||
.option('-p, --password <password>', 'account 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({
|
||||
client,
|
||||
email: opts.email,
|
||||
password: opts.password
|
||||
})
|
||||
} else {
|
||||
session = await authWithGitHub({ client })
|
||||
}
|
||||
|
||||
logger.log(session)
|
||||
})
|
||||
|
||||
program.addCommand(command)
|
||||
}
|
||||
|
|
|
@ -1,15 +1,20 @@
|
|||
import { Command } from 'commander'
|
||||
|
||||
import { getAuth } from '../store'
|
||||
import type { Context } from '../types'
|
||||
import { AuthStore } from '../store'
|
||||
|
||||
export const whoami = new Command('whoami')
|
||||
.description('Displays info about the current user')
|
||||
.action(async () => {
|
||||
const auth = getAuth()
|
||||
export function registerWhoAmICommand({ client, program }: Context) {
|
||||
const command = new Command('whoami')
|
||||
.description('Displays info about the current user')
|
||||
.action(async () => {
|
||||
if (!AuthStore.isAuthenticated()) {
|
||||
console.log('Not signed in')
|
||||
return
|
||||
}
|
||||
|
||||
// TODO
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
JSON.stringify({ user: auth.user, team: auth.teamSlug }, null, 2)
|
||||
)
|
||||
})
|
||||
const res = await client.getAuthSession()
|
||||
console.log(res)
|
||||
})
|
||||
|
||||
program.addCommand(command)
|
||||
}
|
||||
|
|
|
@ -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 restoreCursor from 'restore-cursor'
|
||||
|
||||
import { deploy } from './commands/deploy'
|
||||
import { get } from './commands/get'
|
||||
import { ls } from './commands/ls'
|
||||
import { publish } from './commands/publish'
|
||||
import { rm } from './commands/rm'
|
||||
import { signin } from './commands/signin'
|
||||
|
||||
const authClient = createAuthClient({
|
||||
baseURL: 'http://localhost:3000/v1/auth'
|
||||
})
|
||||
import { registerSigninCommand } from './commands/signin'
|
||||
import { registerWhoAmICommand } from './commands/whoami'
|
||||
import { AuthStore } from './store'
|
||||
|
||||
async function main() {
|
||||
restoreCursor()
|
||||
|
||||
const res = await authClient.signIn.social({
|
||||
provider: 'github'
|
||||
const client = new AgenticApiClient({
|
||||
apiCookie: AuthStore.tryGetAuth()?.cookie,
|
||||
apiBaseUrl: process.env.AGENTIC_API_BASE_URL
|
||||
})
|
||||
console.log(res)
|
||||
return
|
||||
|
||||
const program = new Command()
|
||||
program.addCommand(signin)
|
||||
program.addCommand(get)
|
||||
program.addCommand(ls)
|
||||
program.addCommand(publish)
|
||||
program.addCommand(rm)
|
||||
program.addCommand(deploy)
|
||||
const program = new Command('agentic')
|
||||
.option('-j, --json', 'Print output in JSON format')
|
||||
.showHelpAfterError()
|
||||
|
||||
const logger = {
|
||||
log: (...args: any[]) => {
|
||||
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()
|
||||
}
|
||||
|
|
|
@ -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 Conf from 'conf'
|
||||
|
||||
export const store = new Conf({ projectName: 'agentic' })
|
||||
|
||||
export type Auth = {
|
||||
token: string
|
||||
user: User
|
||||
export type AuthState = {
|
||||
cookie: string
|
||||
session: AuthSession
|
||||
teamId?: string
|
||||
teamSlug?: string
|
||||
}
|
||||
|
||||
const keyTeamId = 'teamId'
|
||||
const keyTeamSlug = 'teamSlug'
|
||||
const keyToken = 'token'
|
||||
const keyUser = 'user'
|
||||
const keyCookie = 'cookie'
|
||||
const keySession = 'session'
|
||||
|
||||
export function isAuthenticated() {
|
||||
return store.has(keyToken) && store.has(keyUser)
|
||||
}
|
||||
export const AuthStore = {
|
||||
store: new Conf({ projectName: 'agentic' }),
|
||||
|
||||
export function requireAuth() {
|
||||
assert(
|
||||
isAuthenticated(),
|
||||
'Command requires authentication. Please login first.'
|
||||
)
|
||||
}
|
||||
isAuthenticated() {
|
||||
return this.store.has(keyCookie) && this.store.has(keySession)
|
||||
},
|
||||
|
||||
export function getAuth(): Auth {
|
||||
requireAuth()
|
||||
requireAuth() {
|
||||
assert(
|
||||
this.isAuthenticated(),
|
||||
'Command requires authentication. Please login first.'
|
||||
)
|
||||
},
|
||||
|
||||
return {
|
||||
token: store.get(keyToken),
|
||||
user: store.get(keyUser),
|
||||
teamId: store.get(keyTeamId),
|
||||
teamSlug: store.get(keyTeamSlug)
|
||||
} as Auth
|
||||
}
|
||||
tryGetAuth(): AuthState | undefined {
|
||||
if (!this.isAuthenticated()) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
export function signinUser({ token, user }: { token: string; user: string }) {
|
||||
store.set(keyToken, token)
|
||||
store.set(keyUser, user)
|
||||
store.delete(keyTeamId)
|
||||
store.delete(keyTeamSlug)
|
||||
}
|
||||
return {
|
||||
cookie: this.store.get(keyCookie),
|
||||
session: this.store.get(keySession),
|
||||
teamId: this.store.get(keyTeamId),
|
||||
teamSlug: this.store.get(keyTeamSlug)
|
||||
} as AuthState
|
||||
},
|
||||
|
||||
export function signout() {
|
||||
store.delete(keyToken)
|
||||
store.delete(keyUser)
|
||||
store.delete(keyTeamId)
|
||||
store.delete(keyTeamSlug)
|
||||
}
|
||||
getAuth(): AuthState {
|
||||
this.requireAuth()
|
||||
return this.tryGetAuth()!
|
||||
},
|
||||
|
||||
export function switchTeam(team?: { id: string; slug: string }) {
|
||||
if (team?.id) {
|
||||
store.set(keyTeamId, team.id)
|
||||
store.set(keyTeamSlug, team.slug)
|
||||
} else {
|
||||
store.delete(keyTeamId)
|
||||
store.delete(keyTeamSlug)
|
||||
setAuth({ cookie, session }: { cookie: string; session: AuthSession }) {
|
||||
this.store.set(keyCookie, cookie)
|
||||
this.store.set(keySession, session)
|
||||
},
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -219,6 +219,9 @@ importers:
|
|||
'@agentic/platform-core':
|
||||
specifier: workspace:*
|
||||
version: link:../core
|
||||
better-auth:
|
||||
specifier: ^1.2.8
|
||||
version: 1.2.8
|
||||
ky:
|
||||
specifier: 'catalog:'
|
||||
version: 1.8.1
|
||||
|
@ -238,18 +241,30 @@ importers:
|
|||
'@agentic/platform-core':
|
||||
specifier: workspace:*
|
||||
version: link:../core
|
||||
better-auth:
|
||||
specifier: ^1.2.8
|
||||
version: 1.2.8
|
||||
'@hono/node-server':
|
||||
specifier: ^1.14.1
|
||||
version: 1.14.1(hono@4.7.9)
|
||||
commander:
|
||||
specifier: ^14.0.0
|
||||
version: 14.0.0
|
||||
conf:
|
||||
specifier: ^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:
|
||||
specifier: ^9.2.15
|
||||
version: 9.3.7
|
||||
open:
|
||||
specifier: ^10.1.2
|
||||
version: 10.1.2
|
||||
ora:
|
||||
specifier: ^8.2.0
|
||||
version: 8.2.0
|
||||
|
@ -1558,6 +1573,10 @@ packages:
|
|||
resolution: {integrity: sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg==}
|
||||
engines: {node: '>=18.20'}
|
||||
|
||||
bundle-name@4.1.0:
|
||||
resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
bundle-require@5.1.0:
|
||||
resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
@ -1771,6 +1790,14 @@ packages:
|
|||
deep-is@0.1.4:
|
||||
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:
|
||||
resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==}
|
||||
|
||||
|
@ -1778,6 +1805,10 @@ packages:
|
|||
resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
define-lazy-prop@3.0.0:
|
||||
resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
define-properties@1.2.1:
|
||||
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
@ -2285,6 +2316,10 @@ packages:
|
|||
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
get-port@7.1.0:
|
||||
resolution: {integrity: sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
get-proto@1.0.1:
|
||||
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
@ -2474,6 +2509,11 @@ packages:
|
|||
resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==}
|
||||
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:
|
||||
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
@ -2502,6 +2542,11 @@ packages:
|
|||
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
||||
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:
|
||||
resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==}
|
||||
engines: {node: '>=8'}
|
||||
|
@ -2593,6 +2638,10 @@ packages:
|
|||
resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
is-wsl@3.1.0:
|
||||
resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
isarray@2.0.5:
|
||||
resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
|
||||
|
||||
|
@ -2943,6 +2992,10 @@ packages:
|
|||
resolution: {integrity: sha512-M7CJbmv7UCopc0neRKdzfoGWaVZC+xC1925GitKH9EAqYFzX9//25Q7oX4+jw0tiCCj+t5l6VZh8UPH23NZkMA==}
|
||||
hasBin: true
|
||||
|
||||
open@10.1.2:
|
||||
resolution: {integrity: sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
openapi-typescript@7.8.0:
|
||||
resolution: {integrity: sha512-1EeVWmDzi16A+siQlo/SwSGIT7HwaFAVjvMA7/jG5HMLSnrUOzPL7uSTRZZa4v/LCRxHTApHKtNY6glApEoiUQ==}
|
||||
hasBin: true
|
||||
|
@ -3265,6 +3318,10 @@ packages:
|
|||
resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==}
|
||||
engines: {node: '>= 18'}
|
||||
|
||||
run-applescript@7.0.0:
|
||||
resolution: {integrity: sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
run-async@3.0.0:
|
||||
resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==}
|
||||
engines: {node: '>=0.12.0'}
|
||||
|
@ -5177,6 +5234,10 @@ snapshots:
|
|||
|
||||
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):
|
||||
dependencies:
|
||||
esbuild: 0.25.4
|
||||
|
@ -5360,6 +5421,13 @@ snapshots:
|
|||
|
||||
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:
|
||||
dependencies:
|
||||
clone: 1.0.4
|
||||
|
@ -5370,6 +5438,8 @@ snapshots:
|
|||
es-errors: 1.3.0
|
||||
gopd: 1.2.0
|
||||
|
||||
define-lazy-prop@3.0.0: {}
|
||||
|
||||
define-properties@1.2.1:
|
||||
dependencies:
|
||||
define-data-property: 1.1.4
|
||||
|
@ -5998,6 +6068,8 @@ snapshots:
|
|||
hasown: 2.0.2
|
||||
math-intrinsics: 1.1.0
|
||||
|
||||
get-port@7.1.0: {}
|
||||
|
||||
get-proto@1.0.1:
|
||||
dependencies:
|
||||
dunder-proto: 1.0.1
|
||||
|
@ -6207,6 +6279,8 @@ snapshots:
|
|||
call-bound: 1.0.4
|
||||
has-tostringtag: 1.0.2
|
||||
|
||||
is-docker@3.0.0: {}
|
||||
|
||||
is-extglob@2.1.1: {}
|
||||
|
||||
is-finalizationregistry@1.1.1:
|
||||
|
@ -6232,6 +6306,10 @@ snapshots:
|
|||
dependencies:
|
||||
is-extglob: 2.1.1
|
||||
|
||||
is-inside-container@1.0.0:
|
||||
dependencies:
|
||||
is-docker: 3.0.0
|
||||
|
||||
is-interactive@1.0.0: {}
|
||||
|
||||
is-interactive@2.0.0: {}
|
||||
|
@ -6304,6 +6382,10 @@ snapshots:
|
|||
call-bound: 1.0.4
|
||||
get-intrinsic: 1.3.0
|
||||
|
||||
is-wsl@3.1.0:
|
||||
dependencies:
|
||||
is-inside-container: 1.0.0
|
||||
|
||||
isarray@2.0.5: {}
|
||||
|
||||
isexe@2.0.0: {}
|
||||
|
@ -6643,6 +6725,13 @@ snapshots:
|
|||
dependencies:
|
||||
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):
|
||||
dependencies:
|
||||
'@redocly/openapi-core': 1.34.3(supports-color@10.0.0)
|
||||
|
@ -6978,6 +7067,8 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
run-applescript@7.0.0: {}
|
||||
|
||||
run-async@3.0.0: {}
|
||||
|
||||
run-parallel@1.2.0:
|
||||
|
|
Ładowanie…
Reference in New Issue