kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
pull/715/head
rodzic
9d0a388ead
commit
74c84d9c07
|
@ -13,3 +13,9 @@ SENTRY_DSN=
|
||||||
GCP_PROJECT_ID=
|
GCP_PROJECT_ID=
|
||||||
GCP_LOG_NAME='local-dev'
|
GCP_LOG_NAME='local-dev'
|
||||||
METADATA_SERVER_DETECTION='none'
|
METADATA_SERVER_DETECTION='none'
|
||||||
|
|
||||||
|
STRIPE_SECRET_KEY=
|
||||||
|
|
||||||
|
WORKOS_CLIENT_ID=
|
||||||
|
WORKOS_API_KEY=
|
||||||
|
WORKOS_SESSION_SECRET=
|
||||||
|
|
|
@ -56,6 +56,7 @@
|
||||||
"pino-abstract-transport": "^2.0.0",
|
"pino-abstract-transport": "^2.0.0",
|
||||||
"postgres": "^3.4.5",
|
"postgres": "^3.4.5",
|
||||||
"restore-cursor": "catalog:",
|
"restore-cursor": "catalog:",
|
||||||
|
"stripe": "^18.1.0",
|
||||||
"type-fest": "catalog:",
|
"type-fest": "catalog:",
|
||||||
"zod": "catalog:",
|
"zod": "catalog:",
|
||||||
"zod-validation-error": "^3.4.0"
|
"zod-validation-error": "^3.4.0"
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
import { createRoute, type OpenAPIHono, z } from '@hono/zod-openapi'
|
||||||
|
import { setCookie } from 'hono/cookie'
|
||||||
|
|
||||||
|
import { env } from '@/lib/env'
|
||||||
|
import { assert } from '@/lib/utils'
|
||||||
|
import { workos } from '@/lib/workos'
|
||||||
|
|
||||||
|
const route = createRoute({
|
||||||
|
method: 'get',
|
||||||
|
path: 'auth/callback',
|
||||||
|
hide: true,
|
||||||
|
request: {
|
||||||
|
query: z.object({
|
||||||
|
code: z.string()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
responses: {
|
||||||
|
302: {
|
||||||
|
description: 'Redirect'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export function registerAuthCallback(app: OpenAPIHono) {
|
||||||
|
return app.openapi(route, async (c) => {
|
||||||
|
const { code } = c.req.valid('query')
|
||||||
|
assert(code, 400, '"code" is required')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const authenticateResponse =
|
||||||
|
await workos.userManagement.authenticateWithCode({
|
||||||
|
clientId: env.WORKOS_CLIENT_ID,
|
||||||
|
code,
|
||||||
|
session: {
|
||||||
|
sealSession: true,
|
||||||
|
cookiePassword: env.WORKOS_SESSION_SECRET
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const { user: _user, sealedSession } = authenticateResponse
|
||||||
|
assert(sealedSession, 500, 'Sealed session is required')
|
||||||
|
|
||||||
|
// Store session in a cookie
|
||||||
|
setCookie(c, 'wos-session', sealedSession!, {
|
||||||
|
path: '/',
|
||||||
|
httpOnly: true,
|
||||||
|
secure: true,
|
||||||
|
sameSite: 'lax',
|
||||||
|
maxAge: 60 * 60 * 24 * 30 // 30 days
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO: `user`
|
||||||
|
|
||||||
|
// Redirect the user to the homepage
|
||||||
|
return c.redirect('/')
|
||||||
|
} catch {
|
||||||
|
return c.redirect('/auth/login')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { createRoute, type OpenAPIHono, z } from '@hono/zod-openapi'
|
||||||
|
|
||||||
|
import { env } from '@/lib/env'
|
||||||
|
import { workos } from '@/lib/workos'
|
||||||
|
|
||||||
|
const route = createRoute({
|
||||||
|
method: 'get',
|
||||||
|
path: 'auth/login',
|
||||||
|
hide: true,
|
||||||
|
request: {
|
||||||
|
query: z.object({
|
||||||
|
redirectUri: z.string().url().optional()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
responses: {
|
||||||
|
302: {
|
||||||
|
description: 'Redirect to WorkOS login page'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export function registerAuthLogin(app: OpenAPIHono) {
|
||||||
|
return app.openapi(route, async (c) => {
|
||||||
|
const { redirectUri = 'http://localhost:3000/auth/callback' } =
|
||||||
|
c.req.valid('query')
|
||||||
|
|
||||||
|
const authorizationUrl = workos.userManagement.getAuthorizationUrl({
|
||||||
|
clientId: env.WORKOS_CLIENT_ID,
|
||||||
|
|
||||||
|
// Specify that we'd like AuthKit to handle the authentication flow
|
||||||
|
provider: 'authkit',
|
||||||
|
|
||||||
|
// The callback endpoint that WorkOS will redirect to after a user authenticates
|
||||||
|
redirectUri
|
||||||
|
})
|
||||||
|
|
||||||
|
return c.redirect(authorizationUrl)
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
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 { assert, parseZodSchema } from '@/lib/utils'
|
||||||
|
|
||||||
|
import { consumerIdParamsSchema } from './schemas'
|
||||||
|
|
||||||
|
const route = createRoute({
|
||||||
|
description: 'Gets a consumer',
|
||||||
|
tags: ['consumers'],
|
||||||
|
operationId: 'getConsumer',
|
||||||
|
method: 'get',
|
||||||
|
path: 'consumers/{consumersId}',
|
||||||
|
security: [{ bearerAuth: [] }],
|
||||||
|
request: {
|
||||||
|
params: consumerIdParamsSchema
|
||||||
|
},
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: 'A consumer object',
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
schema: schema.consumerSelectSchema
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO
|
||||||
|
// ...openApiErrorResponses
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export function registerV1ConsumersGetConsumer(
|
||||||
|
app: OpenAPIHono<AuthenticatedEnv>
|
||||||
|
) {
|
||||||
|
return app.openapi(route, async (c) => {
|
||||||
|
const { consumerId } = c.req.valid('param')
|
||||||
|
|
||||||
|
const consumer = await db.query.consumers.findFirst({
|
||||||
|
where: eq(schema.consumers.id, consumerId)
|
||||||
|
})
|
||||||
|
assert(consumer, 404, `Consumer not found "${consumerId}"`)
|
||||||
|
await acl(c, consumer, { label: 'Consumer' })
|
||||||
|
|
||||||
|
return c.json(parseZodSchema(schema.consumerSelectSchema, consumer))
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { z } from '@hono/zod-openapi'
|
||||||
|
|
||||||
|
import { consumerIdSchema } from '@/db'
|
||||||
|
|
||||||
|
export const consumerIdParamsSchema = z.object({
|
||||||
|
consumerId: consumerIdSchema.openapi({
|
||||||
|
param: {
|
||||||
|
description: 'Consumer ID',
|
||||||
|
name: 'consumerId',
|
||||||
|
in: 'path'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
|
@ -3,6 +3,7 @@ import { OpenAPIHono } from '@hono/zod-openapi'
|
||||||
import type { AuthenticatedEnv } from '@/lib/types'
|
import type { AuthenticatedEnv } from '@/lib/types'
|
||||||
import * as middleware from '@/lib/middleware'
|
import * as middleware from '@/lib/middleware'
|
||||||
|
|
||||||
|
import { registerV1ConsumersGetConsumer } from './consumers/get-consumer'
|
||||||
import { registerHealthCheck } from './health-check'
|
import { registerHealthCheck } from './health-check'
|
||||||
import { registerV1ProjectsCreateProject } from './projects/create-project'
|
import { registerV1ProjectsCreateProject } from './projects/create-project'
|
||||||
import { registerV1ProjectsGetProject } from './projects/get-project'
|
import { registerV1ProjectsGetProject } from './projects/get-project'
|
||||||
|
@ -18,6 +19,7 @@ import { registerV1TeamsMembersUpdateTeamMember } from './teams/members/update-t
|
||||||
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'
|
||||||
|
|
||||||
export const apiV1 = new OpenAPIHono()
|
export const apiV1 = new OpenAPIHono()
|
||||||
|
|
||||||
|
@ -62,6 +64,12 @@ registerV1ProjectsUpdateProject(pri)
|
||||||
// require('./projects').read
|
// require('./projects').read
|
||||||
// )
|
// )
|
||||||
|
|
||||||
|
// Consumers crud
|
||||||
|
registerV1ConsumersGetConsumer(pri)
|
||||||
|
|
||||||
|
// webhook events
|
||||||
|
registerV1StripeWebhook(pub)
|
||||||
|
|
||||||
// Setup routes and middleware
|
// Setup routes and middleware
|
||||||
apiV1.route('/', pub)
|
apiV1.route('/', pub)
|
||||||
apiV1.use(middleware.authenticate)
|
apiV1.use(middleware.authenticate)
|
||||||
|
|
|
@ -0,0 +1,117 @@
|
||||||
|
import type { OpenAPIHono } from '@hono/zod-openapi'
|
||||||
|
import type Stripe from 'stripe'
|
||||||
|
|
||||||
|
import { and, db, eq, schema } from '@/db'
|
||||||
|
import { env, isStripeLive } from '@/lib/env'
|
||||||
|
import { HttpError } from '@/lib/errors'
|
||||||
|
import { stripe } from '@/lib/stripe'
|
||||||
|
import { assert } from '@/lib/utils'
|
||||||
|
|
||||||
|
const relevantStripeEvents = new Set<Stripe.Event.Type>([
|
||||||
|
'customer.subscription.updated'
|
||||||
|
])
|
||||||
|
|
||||||
|
const stripeValidSubscriptionStatuses = new Set([
|
||||||
|
'active',
|
||||||
|
'trialing',
|
||||||
|
'incomplete',
|
||||||
|
'past_due'
|
||||||
|
])
|
||||||
|
|
||||||
|
export function registerV1StripeWebhook(app: OpenAPIHono) {
|
||||||
|
return app.get('/webhooks/stripe', async (ctx) => {
|
||||||
|
const body = await ctx.req.text()
|
||||||
|
const signature = ctx.req.header('Stripe-Signature')
|
||||||
|
assert(signature, 400, 'missing signature')
|
||||||
|
|
||||||
|
let event: Stripe.Event
|
||||||
|
|
||||||
|
try {
|
||||||
|
event = stripe.webhooks.constructEvent(
|
||||||
|
body,
|
||||||
|
signature,
|
||||||
|
env.STRIPE_WEBHOOK_SECRET
|
||||||
|
)
|
||||||
|
} catch (err) {
|
||||||
|
throw new HttpError({
|
||||||
|
message: 'invalid stripe event',
|
||||||
|
cause: err,
|
||||||
|
statusCode: 400
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shouldn't ever happen because the signatures should be different, but it's
|
||||||
|
// a useful sanity check just in case.
|
||||||
|
assert(
|
||||||
|
event.livemode === isStripeLive,
|
||||||
|
400,
|
||||||
|
'invalid stripe event: livemode mismatch'
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!relevantStripeEvents.has(event.type)) {
|
||||||
|
// TODO
|
||||||
|
return ctx.json({ status: 'ok' })
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch (event.type) {
|
||||||
|
case 'customer.subscription.updated': {
|
||||||
|
// https://docs.stripe.com/billing/subscriptions/overview#subscription-statuses
|
||||||
|
const subscription = event.data.object
|
||||||
|
const { userId, projectId } = subscription.metadata
|
||||||
|
assert(userId, 400, 'missing metadata userId')
|
||||||
|
assert(projectId, 400, 'missing metadata projectId')
|
||||||
|
|
||||||
|
// logger.info(event.type, {
|
||||||
|
// userId,
|
||||||
|
// projectId,
|
||||||
|
// status: subscription.status
|
||||||
|
// })
|
||||||
|
|
||||||
|
const consumer = await db.query.consumers.findFirst({
|
||||||
|
where: and(
|
||||||
|
eq(schema.consumers.userId, userId),
|
||||||
|
eq(schema.consumers.projectId, projectId)
|
||||||
|
),
|
||||||
|
with: {
|
||||||
|
user: true,
|
||||||
|
project: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
assert(consumer, 404, 'consumer not found')
|
||||||
|
|
||||||
|
if (consumer.stripeStatus !== subscription.status) {
|
||||||
|
consumer.stripeStatus = subscription.status
|
||||||
|
consumer.enabled =
|
||||||
|
consumer.plan === 'free' ||
|
||||||
|
stripeValidSubscriptionStatuses.has(consumer.stripeStatus)
|
||||||
|
|
||||||
|
await db
|
||||||
|
.update(schema.consumers)
|
||||||
|
.set({
|
||||||
|
stripeStatus: consumer.stripeStatus
|
||||||
|
})
|
||||||
|
.where(eq(schema.consumers.id, consumer.id))
|
||||||
|
|
||||||
|
// TODO: invoke provider webhooks
|
||||||
|
// event.data.customer = consumer.getPublicDocument()
|
||||||
|
// await invokeWebhooks(consumer.project, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`unexpected unhandled event "${event.type}"`)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
throw new HttpError({
|
||||||
|
message: `error processing stripe webhook type "${event.type}"`,
|
||||||
|
cause: err,
|
||||||
|
statusCode: 500
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.json({ status: 'ok' })
|
||||||
|
})
|
||||||
|
}
|
|
@ -14,7 +14,6 @@ import { users, userSelectSchema } from './user'
|
||||||
import {
|
import {
|
||||||
createInsertSchema,
|
createInsertSchema,
|
||||||
createSelectSchema,
|
createSelectSchema,
|
||||||
createUpdateSchema,
|
|
||||||
cuid,
|
cuid,
|
||||||
deploymentId,
|
deploymentId,
|
||||||
id,
|
id,
|
||||||
|
@ -33,6 +32,7 @@ export const consumers = pgTable(
|
||||||
id,
|
id,
|
||||||
...timestamps,
|
...timestamps,
|
||||||
|
|
||||||
|
// API token for this consumer
|
||||||
token: text().notNull(),
|
token: text().notNull(),
|
||||||
plan: text(),
|
plan: text(),
|
||||||
|
|
||||||
|
@ -42,9 +42,6 @@ export const consumers = pgTable(
|
||||||
env: text().default('dev').notNull(),
|
env: text().default('dev').notNull(),
|
||||||
coupon: text(),
|
coupon: text(),
|
||||||
|
|
||||||
// stripe subscription status (synced via webhooks)
|
|
||||||
status: text(),
|
|
||||||
|
|
||||||
// only used during initial creation
|
// only used during initial creation
|
||||||
source: text(),
|
source: text(),
|
||||||
|
|
||||||
|
@ -67,6 +64,9 @@ export const consumers = pgTable(
|
||||||
onDelete: 'cascade'
|
onDelete: 'cascade'
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
// stripe subscription status (synced via webhooks)
|
||||||
|
stripeStatus: text(),
|
||||||
|
|
||||||
stripeSubscriptionId: stripeId().notNull(),
|
stripeSubscriptionId: stripeId().notNull(),
|
||||||
stripeSubscriptionBaseItemId: stripeId(),
|
stripeSubscriptionBaseItemId: stripeId(),
|
||||||
stripeSubscriptionRequestItemId: stripeId(),
|
stripeSubscriptionRequestItemId: stripeId(),
|
||||||
|
@ -107,13 +107,6 @@ export const consumersRelations = relations(consumers, ({ one }) => ({
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const stripeValidSubscriptionStatuses = new Set([
|
|
||||||
'active',
|
|
||||||
'trialing',
|
|
||||||
'incomplete',
|
|
||||||
'past_due'
|
|
||||||
])
|
|
||||||
|
|
||||||
export const consumerSelectSchema = createSelectSchema(consumers)
|
export const consumerSelectSchema = createSelectSchema(consumers)
|
||||||
.omit({
|
.omit({
|
||||||
_stripeCustomerId: true
|
_stripeCustomerId: true
|
||||||
|
@ -148,15 +141,3 @@ export const consumerInsertSchema = createInsertSchema(consumers)
|
||||||
deploymentId: true
|
deploymentId: true
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
|
|
||||||
export const consumerUpdateSchema = createUpdateSchema(consumers)
|
|
||||||
.strict()
|
|
||||||
.refine((consumer) => {
|
|
||||||
return {
|
|
||||||
...consumer,
|
|
||||||
enabled:
|
|
||||||
consumer.plan === 'free' ||
|
|
||||||
(consumer.status &&
|
|
||||||
stripeValidSubscriptionStatuses.has(consumer.status))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ function getCuidSchema(idLabel: string) {
|
||||||
|
|
||||||
export const cuidSchema = getCuidSchema('id')
|
export const cuidSchema = getCuidSchema('id')
|
||||||
export const userIdSchema = getCuidSchema('user id')
|
export const userIdSchema = getCuidSchema('user id')
|
||||||
|
export const consumerIdSchema = getCuidSchema('consumer id')
|
||||||
|
|
||||||
export const projectIdSchema = z
|
export const projectIdSchema = z
|
||||||
.string()
|
.string()
|
||||||
|
|
|
@ -11,7 +11,15 @@ export const envSchema = z.object({
|
||||||
DATABASE_URL: z.string().url(),
|
DATABASE_URL: z.string().url(),
|
||||||
JWT_SECRET: z.string(),
|
JWT_SECRET: z.string(),
|
||||||
PORT: z.number().default(3000),
|
PORT: z.number().default(3000),
|
||||||
SENTRY_DSN: z.string().url()
|
SENTRY_DSN: z.string().url(),
|
||||||
|
|
||||||
|
WORKOS_CLIENT_ID: z.string(),
|
||||||
|
WORKOS_API_KEY: z.string(),
|
||||||
|
WORKOS_SESSION_SECRET: z.string(),
|
||||||
|
|
||||||
|
STRIPE_SECRET_KEY: z.string(),
|
||||||
|
STRIPE_PUBLISHABLE_KEY: z.string(),
|
||||||
|
STRIPE_WEBHOOK_SECRET: z.string()
|
||||||
})
|
})
|
||||||
export type Env = z.infer<typeof envSchema>
|
export type Env = z.infer<typeof envSchema>
|
||||||
|
|
||||||
|
@ -23,3 +31,5 @@ export const env = parseZodSchema(envSchema, process.env, {
|
||||||
export const isDev = env.NODE_ENV === 'development'
|
export const isDev = env.NODE_ENV === 'development'
|
||||||
export const isProd = env.NODE_ENV === 'production'
|
export const isProd = env.NODE_ENV === 'production'
|
||||||
export const isBrowser = (globalThis as any).window !== undefined
|
export const isBrowser = (globalThis as any).window !== undefined
|
||||||
|
|
||||||
|
export const isStripeLive = env.STRIPE_PUBLISHABLE_KEY.startsWith('pk_live_')
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
import Stripe from 'stripe'
|
||||||
|
|
||||||
|
import { env } from './env'
|
||||||
|
|
||||||
|
export const stripe = new Stripe(env.STRIPE_SECRET_KEY, {
|
||||||
|
apiVersion: '2025-02-24.acacia'
|
||||||
|
})
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { WorkOS } from '@workos-inc/node'
|
||||||
|
|
||||||
|
import { env } from './env'
|
||||||
|
|
||||||
|
export const workos = new WorkOS(env.WORKOS_API_KEY, {
|
||||||
|
clientId: env.WORKOS_CLIENT_ID
|
||||||
|
})
|
|
@ -185,6 +185,9 @@ importers:
|
||||||
restore-cursor:
|
restore-cursor:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 5.1.0
|
version: 5.1.0
|
||||||
|
stripe:
|
||||||
|
specifier: ^18.1.0
|
||||||
|
version: 18.1.0(@types/node@22.15.2)
|
||||||
type-fest:
|
type-fest:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 4.40.1
|
version: 4.40.1
|
||||||
|
@ -3036,6 +3039,10 @@ packages:
|
||||||
resolution: {integrity: sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==}
|
resolution: {integrity: sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==}
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
|
|
||||||
|
qs@6.14.0:
|
||||||
|
resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
|
||||||
|
engines: {node: '>=0.6'}
|
||||||
|
|
||||||
queue-microtask@1.2.3:
|
queue-microtask@1.2.3:
|
||||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||||
|
|
||||||
|
@ -3352,6 +3359,15 @@ packages:
|
||||||
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
stripe@18.1.0:
|
||||||
|
resolution: {integrity: sha512-MLDiniPTHqcfIT3anyBPmOEcaiDhYa7/jRaNypQ3Rt2SJnayQZBvVbFghIziUCZdltGAndm/ZxVOSw6uuSCDig==}
|
||||||
|
engines: {node: '>=12.*'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/node': '>=12.x.x'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/node':
|
||||||
|
optional: true
|
||||||
|
|
||||||
stubs@3.0.0:
|
stubs@3.0.0:
|
||||||
resolution: {integrity: sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==}
|
resolution: {integrity: sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==}
|
||||||
|
|
||||||
|
@ -6631,6 +6647,10 @@ snapshots:
|
||||||
|
|
||||||
pvutils@1.1.3: {}
|
pvutils@1.1.3: {}
|
||||||
|
|
||||||
|
qs@6.14.0:
|
||||||
|
dependencies:
|
||||||
|
side-channel: 1.1.0
|
||||||
|
|
||||||
queue-microtask@1.2.3: {}
|
queue-microtask@1.2.3: {}
|
||||||
|
|
||||||
quick-format-unescaped@4.0.4: {}
|
quick-format-unescaped@4.0.4: {}
|
||||||
|
@ -7016,6 +7036,12 @@ snapshots:
|
||||||
|
|
||||||
strip-json-comments@3.1.1: {}
|
strip-json-comments@3.1.1: {}
|
||||||
|
|
||||||
|
stripe@18.1.0(@types/node@22.15.2):
|
||||||
|
dependencies:
|
||||||
|
qs: 6.14.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/node': 22.15.2
|
||||||
|
|
||||||
stubs@3.0.0: {}
|
stubs@3.0.0: {}
|
||||||
|
|
||||||
sucrase@3.35.0:
|
sucrase@3.35.0:
|
||||||
|
|
|
@ -5,7 +5,9 @@
|
||||||
|
|
||||||
# Agentic <!-- omit from toc -->
|
# Agentic <!-- omit from toc -->
|
||||||
|
|
||||||
**TODO**
|
## TODO
|
||||||
|
|
||||||
|
- simplify logger
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|
Ładowanie…
Reference in New Issue