diff --git a/cli/src/cli.ts b/cli/src/cli.ts
index 040b210955..d589c77f0a 100644
--- a/cli/src/cli.ts
+++ b/cli/src/cli.ts
@@ -11,7 +11,7 @@ import { initLogger, showToken, logInfo, logWarning, logError } from './logger.t
const DEFAULT_PORT = 31415
const DEFAULT_FRONTEND_URL = 'https://npmx.dev/'
-const DEV_FRONTEND_URL = 'http://localhost:3000/'
+const DEV_FRONTEND_URL = 'http://127.0.0.1:3000/'
async function runNpmLogin(): Promise {
return new Promise(resolve => {
diff --git a/cli/src/server.ts b/cli/src/server.ts
index c4dc1d42f4..fc609e06f8 100644
--- a/cli/src/server.ts
+++ b/cli/src/server.ts
@@ -53,7 +53,7 @@ function generateOperationId(): string {
}
const corsOptions: CorsOptions = {
- origin: ['https://npmx.dev', /^http:\/\/localhost:\d+$/],
+ origin: ['https://npmx.dev', /^http:\/\/localhost:\d+$/, /^http:\/\/127.0.0.1:\d+$/],
methods: ['GET', 'POST', 'DELETE', 'OPTIONS'],
allowHeaders: ['Content-Type', 'Authorization'],
}
diff --git a/i18n/locales/en.json b/i18n/locales/en.json
index 546e4c6212..21ab459025 100644
--- a/i18n/locales/en.json
+++ b/i18n/locales/en.json
@@ -347,7 +347,7 @@
"connector": {
"status": {
"connecting": "connecting...",
- "connected_as": "connected as {'@'}{user}",
+ "connected_as": "connected as ~{user}",
"connected": "connected",
"connect_cli": "connect local CLI",
"aria_connecting": "Connecting to local connector",
@@ -359,7 +359,7 @@
"title": "Local Connector",
"close_modal": "Close modal",
"connected": "Connected",
- "logged_in_as": "Logged in as {'@'}{user}",
+ "connected_as_user": "Connected as ~{user}",
"connected_hint": "You can now manage packages and organizations from the web UI.",
"disconnect": "Disconnect",
"run_hint": "Run the connector on your machine to enable admin features.",
@@ -729,6 +729,35 @@
}
}
},
+ "account_menu": {
+ "connect": "connect",
+ "account": "Account",
+ "npm_cli": "npm CLI",
+ "atmosphere": "Atmosphere",
+ "npm_cli_desc": "Manage packages & orgs",
+ "atmosphere_desc": "Social features & identity",
+ "connect_npm_cli": "Connect to npm CLI",
+ "connect_atmosphere": "Connect to Atmosphere",
+ "connecting": "Connecting...",
+ "ops": "ops",
+ "disconnect": "Disconnect"
+ },
+ "auth": {
+ "modal": {
+ "title": "Atmosphere",
+ "close": "Close",
+ "connected_as": "Connected as {'@'}{handle}",
+ "disconnect": "Disconnect",
+ "connect_prompt": "Connect with your Atmosphere account",
+ "handle_label": "Handle",
+ "handle_placeholder": "alice.bsky.social",
+ "connect": "Connect",
+ "create_account": "Create a new account",
+ "connect_bluesky": "Connect with Bluesky",
+ "what_is_atmosphere": "What is an Atmosphere account?",
+ "atmosphere_explanation": "{npmx} uses the {atproto} to power many of its social features, allowing users to own their data and use one account for all compatible applications. Once you create an account, you can use other apps like {bluesky} and {tangled} with the same account."
+ }
+ },
"header": {
"home": "npmx home",
"github": "GitHub",
diff --git a/knip.json b/knip.json
index f9a71bd3d1..2e385cc4db 100644
--- a/knip.json
+++ b/knip.json
@@ -40,6 +40,7 @@
"@iconify-json/solar",
"@iconify-json/svg-spinners",
"@iconify-json/vscode-icons",
+ "@vercel/kv",
"@voidzero-dev/vite-plus-core",
"h3",
"ohash",
diff --git a/lunaria/files/en-US.json b/lunaria/files/en-US.json
index 546e4c6212..21ab459025 100644
--- a/lunaria/files/en-US.json
+++ b/lunaria/files/en-US.json
@@ -347,7 +347,7 @@
"connector": {
"status": {
"connecting": "connecting...",
- "connected_as": "connected as {'@'}{user}",
+ "connected_as": "connected as ~{user}",
"connected": "connected",
"connect_cli": "connect local CLI",
"aria_connecting": "Connecting to local connector",
@@ -359,7 +359,7 @@
"title": "Local Connector",
"close_modal": "Close modal",
"connected": "Connected",
- "logged_in_as": "Logged in as {'@'}{user}",
+ "connected_as_user": "Connected as ~{user}",
"connected_hint": "You can now manage packages and organizations from the web UI.",
"disconnect": "Disconnect",
"run_hint": "Run the connector on your machine to enable admin features.",
@@ -729,6 +729,35 @@
}
}
},
+ "account_menu": {
+ "connect": "connect",
+ "account": "Account",
+ "npm_cli": "npm CLI",
+ "atmosphere": "Atmosphere",
+ "npm_cli_desc": "Manage packages & orgs",
+ "atmosphere_desc": "Social features & identity",
+ "connect_npm_cli": "Connect to npm CLI",
+ "connect_atmosphere": "Connect to Atmosphere",
+ "connecting": "Connecting...",
+ "ops": "ops",
+ "disconnect": "Disconnect"
+ },
+ "auth": {
+ "modal": {
+ "title": "Atmosphere",
+ "close": "Close",
+ "connected_as": "Connected as {'@'}{handle}",
+ "disconnect": "Disconnect",
+ "connect_prompt": "Connect with your Atmosphere account",
+ "handle_label": "Handle",
+ "handle_placeholder": "alice.bsky.social",
+ "connect": "Connect",
+ "create_account": "Create a new account",
+ "connect_bluesky": "Connect with Bluesky",
+ "what_is_atmosphere": "What is an Atmosphere account?",
+ "atmosphere_explanation": "{npmx} uses the {atproto} to power many of its social features, allowing users to own their data and use one account for all compatible applications. Once you create an account, you can use other apps like {bluesky} and {tangled} with the same account."
+ }
+ },
"header": {
"home": "npmx home",
"github": "GitHub",
diff --git a/modules/cache.ts b/modules/cache.ts
index 3964ce91d7..ba58e59aa6 100644
--- a/modules/cache.ts
+++ b/modules/cache.ts
@@ -27,6 +27,16 @@ export default defineNuxtModule({
...nitroConfig.storage[FETCH_CACHE_STORAGE_BASE],
driver: 'vercel-runtime-cache',
}
+
+ const env = process.env.VERCEL_ENV
+
+ nitroConfig.storage['oauth-atproto-state'] = {
+ driver: env === 'production' ? 'vercel-kv' : 'vercel-runtime-cache',
+ }
+
+ nitroConfig.storage['oauth-atproto-session'] = {
+ driver: env === 'production' ? 'vercel-kv' : 'vercel-runtime-cache',
+ }
})
},
})
diff --git a/modules/dev.ts b/modules/dev.ts
new file mode 100644
index 0000000000..dcd82344c0
--- /dev/null
+++ b/modules/dev.ts
@@ -0,0 +1,28 @@
+import { defineNuxtModule, useNuxt } from 'nuxt/kit'
+import { join } from 'node:path'
+import { appendFileSync, existsSync, readFileSync } from 'node:fs'
+import { randomUUID } from 'node:crypto'
+
+export default defineNuxtModule({
+ meta: {
+ name: 'dev',
+ },
+ setup() {
+ const nuxt = useNuxt()
+ if (nuxt.options._prepare || process.env.NUXT_SESSION_PASSWORD) {
+ return
+ }
+
+ const envPath = join(nuxt.options.rootDir, '.env')
+ const hasPassword =
+ existsSync(envPath) && /^NUXT_SESSION_PASSWORD=/m.test(readFileSync(envPath, 'utf-8'))
+
+ if (!hasPassword) {
+ console.info('Generating NUXT_SESSION_PASSWORD for development environment.')
+ const password = randomUUID().replace(/-/g, '')
+
+ nuxt.options.runtimeConfig.sessionPassword = password
+ appendFileSync(envPath, `# generated by dev module\nNUXT_SESSION_PASSWORD=${password}\n`)
+ }
+ },
+})
diff --git a/nuxt.config.ts b/nuxt.config.ts
index 9e1bda7f1b..6fcbf3a653 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -1,3 +1,4 @@
+import process from 'node:process'
import { currentLocales } from './config/i18n'
export default defineNuxtConfig({
@@ -45,8 +46,23 @@ export default defineNuxtConfig({
css: ['~/assets/main.css', 'vue-data-ui/style.css'],
+ runtimeConfig: {
+ sessionPassword: '',
+ // Upstash Redis for distributed OAuth token refresh locking in production
+ upstash: {
+ redisRestUrl: process.env.KV_REST_API_URL || '',
+ redisRestToken: process.env.KV_REST_API_TOKEN || '',
+ },
+ },
+
devtools: { enabled: true },
+ devServer: {
+ // Used with atproto oauth
+ // https://atproto.com/specs/oauth#localhost-client-development
+ host: '127.0.0.1',
+ },
+
app: {
head: {
htmlAttrs: { lang: 'en-US' },
@@ -132,6 +148,14 @@ export default defineNuxtConfig({
driver: 'fsLite',
base: './.cache/fetch',
},
+ 'oauth-atproto-state': {
+ driver: 'fsLite',
+ base: './.cache/atproto-oauth/state',
+ },
+ 'oauth-atproto-session': {
+ driver: 'fsLite',
+ base: './.cache/atproto-oauth/session',
+ },
},
},
diff --git a/package.json b/package.json
index fd67e00652..efd3ef173f 100644
--- a/package.json
+++ b/package.json
@@ -33,7 +33,9 @@
"start:playwright:webserver": "NODE_ENV=test pnpm build && pnpm preview --port 5678"
},
"dependencies": {
+ "@atproto/api": "^0.18.17",
"@atproto/lex": "0.0.13",
+ "@atproto/oauth-client-node": "^0.3.15",
"@deno/doc": "jsr:^0.189.1",
"@iconify-json/simple-icons": "1.2.68",
"@iconify-json/vscode-icons": "1.2.40",
@@ -48,6 +50,8 @@
"@nuxtjs/i18n": "10.2.1",
"@shikijs/langs": "3.21.0",
"@shikijs/themes": "3.21.0",
+ "@upstash/redis": "1.36.1",
+ "@vercel/kv": "3.0.0",
"@vueuse/core": "14.1.0",
"@vueuse/nuxt": "14.1.0",
"module-replacements": "2.11.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 5a2afb351a..aaaec080f2 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -20,9 +20,15 @@ importers:
.:
dependencies:
+ '@atproto/api':
+ specifier: ^0.18.17
+ version: 0.18.20
'@atproto/lex':
specifier: 0.0.13
version: 0.0.13
+ '@atproto/oauth-client-node':
+ specifier: ^0.3.15
+ version: 0.3.16
'@deno/doc':
specifier: jsr:^0.189.1
version: '@jsr/deno__doc@0.189.1(patch_hash=24f326e123c822a07976329a5afe91a8713e82d53134b5586625b72431c87832)'
@@ -46,10 +52,10 @@ importers:
version: 1.0.0-alpha.1(magicast@0.5.1)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))
'@nuxt/fonts':
specifier: 0.13.0
- version: 0.13.0(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(magicast@0.5.1)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))
+ version: 0.13.0(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(magicast@0.5.1)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))
'@nuxt/scripts':
specifier: 0.13.2
- version: 0.13.2(@unhead/vue@2.1.2(vue@3.5.27(typescript@5.9.3)))(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(magicast@0.5.1)(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3))
+ version: 0.13.2(@unhead/vue@2.1.2(vue@3.5.27(typescript@5.9.3)))(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(magicast@0.5.1)(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3))
'@nuxtjs/color-mode':
specifier: 4.0.0
version: 4.0.0(magicast@0.5.1)
@@ -58,28 +64,34 @@ importers:
version: 2.1.0(@voidzero-dev/vite-plus-test@0.0.0-833c515fa25cef20905a7f9affb156dfa6f151ab(@types/node@24.10.9)(esbuild@0.27.2)(happy-dom@20.4.0)(jiti@2.6.1)(terser@5.46.0)(typescript@5.9.3)(yaml@2.8.2))(magicast@0.5.1)
'@nuxtjs/i18n':
specifier: 10.2.1
- version: 10.2.1(@vue/compiler-dom@3.5.27)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(rollup@4.57.0)(vue@3.5.27(typescript@5.9.3))
+ version: 10.2.1(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(@vue/compiler-dom@3.5.27)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(rollup@4.57.0)(vue@3.5.27(typescript@5.9.3))
'@shikijs/langs':
specifier: 3.21.0
version: 3.21.0
'@shikijs/themes':
specifier: 3.21.0
version: 3.21.0
+ '@upstash/redis':
+ specifier: 1.36.1
+ version: 1.36.1
+ '@vercel/kv':
+ specifier: 3.0.0
+ version: 3.0.0
'@vueuse/core':
specifier: 14.1.0
version: 14.1.0(vue@3.5.27(typescript@5.9.3))
'@vueuse/nuxt':
specifier: 14.1.0
- version: 14.1.0(magicast@0.5.1)(nuxt@4.3.0(@parcel/watcher@2.5.6)(@types/node@24.10.9)(@vue/compiler-sfc@3.5.27)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.42.0(oxlint-tsgolint@0.11.3))(rolldown@1.0.0-rc.1)(rollup@4.57.0)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@3.2.4(typescript@5.9.3))(yaml@2.8.2))(vue@3.5.27(typescript@5.9.3))
+ version: 14.1.0(magicast@0.5.1)(nuxt@4.3.0(@parcel/watcher@2.5.6)(@types/node@24.10.9)(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(@vue/compiler-sfc@3.5.27)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.42.0(oxlint-tsgolint@0.11.3))(rolldown@1.0.0-rc.1)(rollup@4.57.0)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@3.2.4(typescript@5.9.3))(yaml@2.8.2))(vue@3.5.27(typescript@5.9.3))
module-replacements:
specifier: 2.11.0
version: 2.11.0
nuxt:
specifier: 4.3.0
- version: 4.3.0(@parcel/watcher@2.5.6)(@types/node@24.10.9)(@vue/compiler-sfc@3.5.27)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.42.0(oxlint-tsgolint@0.11.3))(rolldown@1.0.0-rc.1)(rollup@4.57.0)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@3.2.4(typescript@5.9.3))(yaml@2.8.2)
+ version: 4.3.0(@parcel/watcher@2.5.6)(@types/node@24.10.9)(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(@vue/compiler-sfc@3.5.27)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.42.0(oxlint-tsgolint@0.11.3))(rolldown@1.0.0-rc.1)(rollup@4.57.0)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@3.2.4(typescript@5.9.3))(yaml@2.8.2)
nuxt-og-image:
specifier: 5.1.13
- version: 5.1.13(@unhead/vue@2.1.2(vue@3.5.27(typescript@5.9.3)))(magicast@0.5.1)(unstorage@1.17.4(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2))(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.27(typescript@5.9.3))
+ version: 5.1.13(@unhead/vue@2.1.2(vue@3.5.27(typescript@5.9.3)))(magicast@0.5.1)(unstorage@1.17.4(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2))(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.27(typescript@5.9.3))
ohash:
specifier: 2.0.11
version: 2.0.11
@@ -261,10 +273,10 @@ importers:
version: 12.6.2
docus:
specifier: 5.4.4
- version: 5.4.4(29b67e4ea82958d3bb639aa676be121d)
+ version: 5.4.4(f5d56481b2ddeca600f9d39a31d83017)
nuxt:
specifier: 4.3.0
- version: 4.3.0(@parcel/watcher@2.5.6)(@types/node@24.10.9)(@vue/compiler-sfc@3.5.27)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.42.0(oxlint-tsgolint@0.11.3))(rolldown@1.0.0-rc.1)(rollup@4.57.0)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@3.2.4(typescript@5.9.3))(yaml@2.8.2)
+ version: 4.3.0(@parcel/watcher@2.5.6)(@types/node@24.10.9)(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(@vue/compiler-sfc@3.5.27)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.42.0(oxlint-tsgolint@0.11.3))(rolldown@1.0.0-rc.1)(rollup@4.57.0)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@3.2.4(typescript@5.9.3))(yaml@2.8.2)
packages:
@@ -288,9 +300,23 @@ packages:
'@atproto-labs/did-resolver@0.2.6':
resolution: {integrity: sha512-2K1bC04nI2fmgNcvof+yA28IhGlpWn2JKYlPa7To9JTKI45FINCGkQSGiL2nyXlyzDJJ34fZ1aq6/IRFIOIiqg==}
+ '@atproto-labs/fetch-node@0.2.0':
+ resolution: {integrity: sha512-Krq09nH/aeoiU2s9xdHA0FjTEFWG9B5FFenipv1iRixCcPc7V3DhTNDawxG9gI8Ny0k4dBVS9WTRN/IDzBx86Q==}
+ engines: {node: '>=18.7.0'}
+
'@atproto-labs/fetch@0.2.3':
resolution: {integrity: sha512-NZtbJOCbxKUFRFKMpamT38PUQMY0hX0p7TG5AEYOPhZKZEP7dHZ1K2s1aB8MdVH0qxmqX7nQleNrrvLf09Zfdw==}
+ '@atproto-labs/handle-resolver-node@0.1.25':
+ resolution: {integrity: sha512-NY9WYM2VLd3IuMGRkkmvGBg8xqVEaK/fitv1vD8SMXqFTekdpjOLCCyv7EFtqVHouzmDcL83VOvWRfHVa8V9Yw==}
+ engines: {node: '>=18.7.0'}
+
+ '@atproto-labs/handle-resolver@0.3.6':
+ resolution: {integrity: sha512-qnSTXvOBNj1EHhp2qTWSX8MS5q3AwYU5LKlt5fBvSbCjgmTr2j0URHCv+ydrwO55KvsojIkTMgeMOh4YuY4fCA==}
+
+ '@atproto-labs/identity-resolver@0.3.6':
+ resolution: {integrity: sha512-qoWqBDRobln0NR8L8dQjSp79E0chGkBhibEgxQa2f9WD+JbJdjQ0YvwwO5yeQn05pJoJmAwmI2wyJ45zjU7aWg==}
+
'@atproto-labs/pipe@0.1.1':
resolution: {integrity: sha512-hdNw2oUs2B6BN1lp+32pF7cp8EMKuIN5Qok2Vvv/aOpG/3tNSJ9YkvfI0k6Zd188LeDDYRUpYpxcoFIcGH/FNg==}
@@ -300,9 +326,15 @@ packages:
'@atproto-labs/simple-store@0.3.0':
resolution: {integrity: sha512-nOb6ONKBRJHRlukW1sVawUkBqReLlLx6hT35VS3imaNPwiXDxLnTK7lxw3Lrl9k5yugSBDQAkZAq3MPTEFSUBQ==}
+ '@atproto/api@0.18.20':
+ resolution: {integrity: sha512-BZYZkh2VJIFCXEnc/vzKwAwWjAQQTgbNJ8FBxpBK+z+KYh99O0uPCsRYKoCQsRrnkgrhzdU9+g2G+7zanTIGbw==}
+
'@atproto/common-web@0.4.14':
resolution: {integrity: sha512-rMU8Q+kpyPpirUS9OqT7aOD1hxKa+diem3vc7BA0lOkj0tU6wcAxegxmbPZ8JaOsR7SSYhP/jCt/5wbT4qqkuQ==}
+ '@atproto/common-web@0.4.15':
+ resolution: {integrity: sha512-A4l9gyqUNez8CjZp/Trypz/D3WIQsNj8dN05WR6+RoBbvwc9JhWjKPrm+WoVYc/F16RPdXHLkE3BEJlGIyYIiA==}
+
'@atproto/common@0.5.9':
resolution: {integrity: sha512-rzl8dB7ErpA/VUgCidahUtbxEph50J4g7j68bZmlwwrHlrtvTe8DjrwH5EUFEcegl9dadIhcVJ3qi0kPKEUr+g==}
engines: {node: '>=18.7.0'}
@@ -314,6 +346,15 @@ packages:
'@atproto/did@0.3.0':
resolution: {integrity: sha512-raUPzUGegtW/6OxwCmM8bhZvuIMzxG5t9oWsth6Tp91Kb5fTnHV2h/KKNF1C82doeA4BdXCErTyg7ISwLbQkzA==}
+ '@atproto/jwk-jose@0.1.11':
+ resolution: {integrity: sha512-i4Fnr2sTBYmMmHXl7NJh8GrCH+tDQEVWrcDMDnV5DjJfkgT17wIqvojIw9SNbSL4Uf0OtfEv6AgG0A+mgh8b5Q==}
+
+ '@atproto/jwk-webcrypto@0.2.0':
+ resolution: {integrity: sha512-UmgRrrEAkWvxwhlwe30UmDOdTEFidlIzBC7C3cCbeJMcBN1x8B3KH+crXrsTqfWQBG58mXgt8wgSK3Kxs2LhFg==}
+
+ '@atproto/jwk@0.6.0':
+ resolution: {integrity: sha512-bDoJPvt7TrQVi/rBfBrSSpGykhtIriKxeYCYQTiPRKFfyRhbgpElF0wPXADjIswnbzZdOwbY63az4E/CFVT3Tw==}
+
'@atproto/lex-builder@0.0.12':
resolution: {integrity: sha512-ObWnmsbkPwjKKIX/L0JyMptmggr3gvbZKPDcpr1eSBUWyWeqqX8OfIlHYLgm5veNuO776RV05CE7BdQFQUA+9Q==}
@@ -323,6 +364,9 @@ packages:
'@atproto/lex-client@0.0.10':
resolution: {integrity: sha512-n3g9KoY5hw7W29mcR4TrjN5qOi6JiWty7r1heqLLfYiq5TxaRx9/QBa2hbN4h1p4xxICPZoDlNtuGq8YV4U8mg==}
+ '@atproto/lex-data@0.0.10':
+ resolution: {integrity: sha512-FDbcy8VIUVzS9Mi1F8SMxbkL/jOUmRRpqbeM1xB4A0fMxeZJTxf6naAbFt4gYF3quu/+TPJGmio6/7cav05FqQ==}
+
'@atproto/lex-data@0.0.9':
resolution: {integrity: sha512-1slwe4sG0cyWtsq16+rBoWIxNDqGPkkvN+PV6JuzA7dgUK9bjUmXBGQU4eZlUPSS43X1Nhmr/9VjgKmEzU9vDw==}
@@ -332,6 +376,9 @@ packages:
'@atproto/lex-installer@0.0.13':
resolution: {integrity: sha512-Uu9JsZBBTVel8qz+wgf/M46uimPMn4Ub3hToscngELa+C9+6amHAtcArVdJgv4UsDu13TneOj3I6bdkU0luLTw==}
+ '@atproto/lex-json@0.0.10':
+ resolution: {integrity: sha512-L6MyXU17C5ODMeob8myQ2F3xvgCTvJUtM0ew8qSApnN//iDasB/FDGgd7ty4UVNmx4NQ/rtvz8xV94YpG6kneQ==}
+
'@atproto/lex-json@0.0.9':
resolution: {integrity: sha512-Q2v1EVZcnd+ndyZj1r2UlGikA7q6It24CFPLbxokcf5Ba4RBupH8IkkQX7mqUDSRWPgQdmZYIdW9wUln+MKDqw==}
@@ -348,6 +395,16 @@ packages:
'@atproto/lexicon@0.6.1':
resolution: {integrity: sha512-/vI1kVlY50Si+5MXpvOucelnYwb0UJ6Qto5mCp+7Q5C+Jtp+SoSykAPVvjVtTnQUH2vrKOFOwpb3C375vSKzXw==}
+ '@atproto/oauth-client-node@0.3.16':
+ resolution: {integrity: sha512-2dooMzxAkiQ4MkOAZlEQ3iwbB9SEovrbIKMNuBbVCLQYORVNxe20tMdjs3lvhrzdpzvaHLlQnJJhw5dA9VELFw==}
+ engines: {node: '>=18.7.0'}
+
+ '@atproto/oauth-client@0.5.14':
+ resolution: {integrity: sha512-sPH+vcdq9maTEAhJI0HzmFcFAMrkCS19np+RUssNkX6kS8Xr3OYr57tvYRCbkcnIyYTfYcxKQgpwHKx3RVEaYw==}
+
+ '@atproto/oauth-types@0.6.2':
+ resolution: {integrity: sha512-2cuboM4RQBCYR8NQC5uGRkW6KgCgKyq/B5/+tnMmWZYtZGVUQvsUWQHK/ZiMCnVXbcDNtc/RIEJQJDZ8FXMoxg==}
+
'@atproto/repo@0.8.12':
resolution: {integrity: sha512-QpVTVulgfz5PUiCTELlDBiRvnsnwrFWi+6CfY88VwXzrRHd9NE8GItK7sfxQ6U65vD/idH8ddCgFrlrsn1REPQ==}
engines: {node: '>=18.7.0'}
@@ -355,6 +412,9 @@ packages:
'@atproto/syntax@0.4.3':
resolution: {integrity: sha512-YoZUz40YAJr5nPwvCDWgodEOlt5IftZqPJvA0JDWjuZKD8yXddTwSzXSaKQAzGOpuM+/A3uXRtPzJJqlScc+iA==}
+ '@atproto/xrpc@0.7.7':
+ resolution: {integrity: sha512-K1ZyO/BU8JNtXX5dmPp7b5UrkLMMqpsIa/Lrj5D3Su+j1Xwq1m6QJ2XJ1AgjEjkI1v4Muzm7klianLE6XGxtmA==}
+
'@babel/code-frame@7.28.6':
resolution: {integrity: sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==}
engines: {node: '>=6.9.0'}
@@ -3995,6 +4055,14 @@ packages:
peerDependencies:
webpack: ^4 || ^5
+ '@upstash/redis@1.36.1':
+ resolution: {integrity: sha512-N6SjDcgXdOcTAF+7uNoY69o7hCspe9BcA7YjQdxVu5d25avljTwyLaHBW3krWjrP0FfocgMk94qyVtQbeDp39A==}
+
+ '@vercel/kv@3.0.0':
+ resolution: {integrity: sha512-pKT8fRnfyYk2MgvyB6fn6ipJPCdfZwiKDdw7vB+HL50rjboEBHDVBEcnwfkEpVSp2AjNtoaOUH7zG+bVC/rvSg==}
+ engines: {node: '>=14.6'}
+ deprecated: 'Vercel KV is deprecated. If you had an existing KV store, it should have moved to Upstash Redis which you will see under Vercel Integrations. For new projects, install a Redis integration from Vercel Marketplace: https://vercel.com/marketplace?category=storage&search=redis'
+
'@vercel/nft@1.3.0':
resolution: {integrity: sha512-i4EYGkCsIjzu4vorDUbqglZc5eFtQI2syHb++9ZUDm6TU4edVywGpVnYDein35x9sevONOn9/UabfQXuNXtuzQ==}
engines: {node: '>=20'}
@@ -4573,6 +4641,9 @@ packages:
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
engines: {node: '>= 0.4'}
+ await-lock@2.2.2:
+ resolution: {integrity: sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==}
+
axe-core@4.11.1:
resolution: {integrity: sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A==}
engines: {node: '>=4'}
@@ -6053,6 +6124,10 @@ packages:
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
engines: {node: '>= 0.10'}
+ ipaddr.js@2.3.0:
+ resolution: {integrity: sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==}
+ engines: {node: '>= 10'}
+
ipx@3.1.1:
resolution: {integrity: sha512-7Xnt54Dco7uYkfdAw0r2vCly3z0rSaVhEXMzPvl3FndsTVm5p26j+PO+gyinkYmcsEUvX2Rh7OGK7KzYWRu6BA==}
hasBin: true
@@ -6327,6 +6402,9 @@ packages:
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
hasBin: true
+ jose@5.10.0:
+ resolution: {integrity: sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==}
+
jose@6.1.3:
resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==}
@@ -8454,6 +8532,10 @@ packages:
resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==}
engines: {node: '>=14.0.0'}
+ tlds@1.261.0:
+ resolution: {integrity: sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA==}
+ hasBin: true
+
to-buffer@1.2.2:
resolution: {integrity: sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==}
engines: {node: '>= 0.4'}
@@ -8610,6 +8692,10 @@ packages:
undici-types@7.16.0:
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
+ undici@6.23.0:
+ resolution: {integrity: sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==}
+ engines: {node: '>=18.17'}
+
unenv@2.0.0-rc.24:
resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==}
@@ -9404,10 +9490,35 @@ snapshots:
'@atproto/did': 0.3.0
zod: 3.25.76
+ '@atproto-labs/fetch-node@0.2.0':
+ dependencies:
+ '@atproto-labs/fetch': 0.2.3
+ '@atproto-labs/pipe': 0.1.1
+ ipaddr.js: 2.3.0
+ undici: 6.23.0
+
'@atproto-labs/fetch@0.2.3':
dependencies:
'@atproto-labs/pipe': 0.1.1
+ '@atproto-labs/handle-resolver-node@0.1.25':
+ dependencies:
+ '@atproto-labs/fetch-node': 0.2.0
+ '@atproto-labs/handle-resolver': 0.3.6
+ '@atproto/did': 0.3.0
+
+ '@atproto-labs/handle-resolver@0.3.6':
+ dependencies:
+ '@atproto-labs/simple-store': 0.3.0
+ '@atproto-labs/simple-store-memory': 0.1.4
+ '@atproto/did': 0.3.0
+ zod: 3.25.76
+
+ '@atproto-labs/identity-resolver@0.3.6':
+ dependencies:
+ '@atproto-labs/did-resolver': 0.2.6
+ '@atproto-labs/handle-resolver': 0.3.6
+
'@atproto-labs/pipe@0.1.1': {}
'@atproto-labs/simple-store-memory@0.1.4':
@@ -9417,6 +9528,17 @@ snapshots:
'@atproto-labs/simple-store@0.3.0': {}
+ '@atproto/api@0.18.20':
+ dependencies:
+ '@atproto/common-web': 0.4.15
+ '@atproto/lexicon': 0.6.1
+ '@atproto/syntax': 0.4.3
+ '@atproto/xrpc': 0.7.7
+ await-lock: 2.2.2
+ multiformats: 9.9.0
+ tlds: 1.261.0
+ zod: 3.25.76
+
'@atproto/common-web@0.4.14':
dependencies:
'@atproto/lex-data': 0.0.9
@@ -9424,6 +9546,13 @@ snapshots:
'@atproto/syntax': 0.4.3
zod: 3.25.76
+ '@atproto/common-web@0.4.15':
+ dependencies:
+ '@atproto/lex-data': 0.0.10
+ '@atproto/lex-json': 0.0.10
+ '@atproto/syntax': 0.4.3
+ zod: 3.25.76
+
'@atproto/common@0.5.9':
dependencies:
'@atproto/common-web': 0.4.14
@@ -9443,6 +9572,22 @@ snapshots:
dependencies:
zod: 3.25.76
+ '@atproto/jwk-jose@0.1.11':
+ dependencies:
+ '@atproto/jwk': 0.6.0
+ jose: 5.10.0
+
+ '@atproto/jwk-webcrypto@0.2.0':
+ dependencies:
+ '@atproto/jwk': 0.6.0
+ '@atproto/jwk-jose': 0.1.11
+ zod: 3.25.76
+
+ '@atproto/jwk@0.6.0':
+ dependencies:
+ multiformats: 9.9.0
+ zod: 3.25.76
+
'@atproto/lex-builder@0.0.12':
dependencies:
'@atproto/lex-document': 0.0.11
@@ -9463,6 +9608,13 @@ snapshots:
'@atproto/lex-schema': 0.0.10
tslib: 2.8.1
+ '@atproto/lex-data@0.0.10':
+ dependencies:
+ multiformats: 9.9.0
+ tslib: 2.8.1
+ uint8arrays: 3.0.0
+ unicode-segmenter: 0.14.5
+
'@atproto/lex-data@0.0.9':
dependencies:
multiformats: 9.9.0
@@ -9487,6 +9639,11 @@ snapshots:
'@atproto/syntax': 0.4.3
tslib: 2.8.1
+ '@atproto/lex-json@0.0.10':
+ dependencies:
+ '@atproto/lex-data': 0.0.10
+ tslib: 2.8.1
+
'@atproto/lex-json@0.0.9':
dependencies:
'@atproto/lex-data': 0.0.9
@@ -9529,6 +9686,40 @@ snapshots:
multiformats: 9.9.0
zod: 3.25.76
+ '@atproto/oauth-client-node@0.3.16':
+ dependencies:
+ '@atproto-labs/did-resolver': 0.2.6
+ '@atproto-labs/handle-resolver-node': 0.1.25
+ '@atproto-labs/simple-store': 0.3.0
+ '@atproto/did': 0.3.0
+ '@atproto/jwk': 0.6.0
+ '@atproto/jwk-jose': 0.1.11
+ '@atproto/jwk-webcrypto': 0.2.0
+ '@atproto/oauth-client': 0.5.14
+ '@atproto/oauth-types': 0.6.2
+
+ '@atproto/oauth-client@0.5.14':
+ dependencies:
+ '@atproto-labs/did-resolver': 0.2.6
+ '@atproto-labs/fetch': 0.2.3
+ '@atproto-labs/handle-resolver': 0.3.6
+ '@atproto-labs/identity-resolver': 0.3.6
+ '@atproto-labs/simple-store': 0.3.0
+ '@atproto-labs/simple-store-memory': 0.1.4
+ '@atproto/did': 0.3.0
+ '@atproto/jwk': 0.6.0
+ '@atproto/oauth-types': 0.6.2
+ '@atproto/xrpc': 0.7.7
+ core-js: 3.48.0
+ multiformats: 9.9.0
+ zod: 3.25.76
+
+ '@atproto/oauth-types@0.6.2':
+ dependencies:
+ '@atproto/did': 0.3.0
+ '@atproto/jwk': 0.6.0
+ zod: 3.25.76
+
'@atproto/repo@0.8.12':
dependencies:
'@atproto/common': 0.5.9
@@ -9545,6 +9736,11 @@ snapshots:
dependencies:
tslib: 2.8.1
+ '@atproto/xrpc@0.7.7':
+ dependencies:
+ '@atproto/lexicon': 0.6.1
+ zod: 3.25.76
+
'@babel/code-frame@7.28.6':
dependencies:
'@babel/helper-validator-identifier': 7.28.5
@@ -11148,7 +11344,7 @@ snapshots:
- utf-8-validate
- vue
- '@nuxt/fonts@0.12.1(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(magicast@0.5.1)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))':
+ '@nuxt/fonts@0.12.1(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(magicast@0.5.1)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))':
dependencies:
'@nuxt/devtools-kit': 3.1.1(magicast@0.5.1)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))
'@nuxt/kit': 4.3.0(magicast@0.5.1)
@@ -11157,7 +11353,7 @@ snapshots:
defu: 6.1.4
esbuild: 0.25.12
fontaine: 0.7.0
- fontless: 0.1.0(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))
+ fontless: 0.1.0(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))
h3: 1.15.5
jiti: 2.6.1
magic-regexp: 0.10.0
@@ -11170,7 +11366,7 @@ snapshots:
ufo: 1.6.3
unifont: 0.6.0
unplugin: 2.3.11
- unstorage: 1.17.4(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)
+ unstorage: 1.17.4(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)
transitivePeerDependencies:
- '@azure/app-configuration'
- '@azure/cosmos'
@@ -11194,7 +11390,7 @@ snapshots:
- uploadthing
- vite
- '@nuxt/fonts@0.13.0(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(magicast@0.5.1)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))':
+ '@nuxt/fonts@0.13.0(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(magicast@0.5.1)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))':
dependencies:
'@nuxt/devtools-kit': 3.1.1(magicast@0.5.1)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))
'@nuxt/kit': 4.3.0(magicast@0.5.1)
@@ -11203,7 +11399,7 @@ snapshots:
defu: 6.1.4
esbuild: 0.27.2
fontaine: 0.8.0
- fontless: 0.1.0(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))
+ fontless: 0.1.0(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))
h3: 1.15.5
jiti: 2.6.1
magic-regexp: 0.10.0
@@ -11216,7 +11412,7 @@ snapshots:
ufo: 1.6.3
unifont: 0.6.0
unplugin: 2.3.11
- unstorage: 1.17.4(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)
+ unstorage: 1.17.4(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)
transitivePeerDependencies:
- '@azure/app-configuration'
- '@azure/cosmos'
@@ -11261,7 +11457,7 @@ snapshots:
- vite
- vue
- '@nuxt/image@2.0.0(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(magicast@0.5.1)':
+ '@nuxt/image@2.0.0(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(magicast@0.5.1)':
dependencies:
'@nuxt/kit': 4.3.0(magicast@0.5.1)
consola: 3.4.2
@@ -11274,7 +11470,7 @@ snapshots:
std-env: 3.10.0
ufo: 1.6.3
optionalDependencies:
- ipx: 3.1.1(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)
+ ipx: 3.1.1(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)
transitivePeerDependencies:
- '@azure/app-configuration'
- '@azure/cosmos'
@@ -11348,7 +11544,7 @@ snapshots:
transitivePeerDependencies:
- magicast
- '@nuxt/nitro-server@4.3.0(better-sqlite3@12.6.2)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(magicast@0.5.1)(nuxt@4.3.0(@parcel/watcher@2.5.6)(@types/node@24.10.9)(@vue/compiler-sfc@3.5.27)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.42.0(oxlint-tsgolint@0.11.3))(rolldown@1.0.0-rc.1)(rollup@4.57.0)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@3.2.4(typescript@5.9.3))(yaml@2.8.2))(rolldown@1.0.0-rc.1)(typescript@5.9.3)':
+ '@nuxt/nitro-server@4.3.0(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(better-sqlite3@12.6.2)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(magicast@0.5.1)(nuxt@4.3.0(@parcel/watcher@2.5.6)(@types/node@24.10.9)(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(@vue/compiler-sfc@3.5.27)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.42.0(oxlint-tsgolint@0.11.3))(rolldown@1.0.0-rc.1)(rollup@4.57.0)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@3.2.4(typescript@5.9.3))(yaml@2.8.2))(rolldown@1.0.0-rc.1)(typescript@5.9.3)':
dependencies:
'@nuxt/devalue': 2.0.2
'@nuxt/kit': 4.3.0(magicast@0.5.1)
@@ -11365,8 +11561,8 @@ snapshots:
impound: 1.0.0
klona: 2.0.6
mocked-exports: 0.1.1
- nitropack: 2.13.1(better-sqlite3@12.6.2)(rolldown@1.0.0-rc.1)
- nuxt: 4.3.0(@parcel/watcher@2.5.6)(@types/node@24.10.9)(@vue/compiler-sfc@3.5.27)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.42.0(oxlint-tsgolint@0.11.3))(rolldown@1.0.0-rc.1)(rollup@4.57.0)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@3.2.4(typescript@5.9.3))(yaml@2.8.2)
+ nitropack: 2.13.1(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(better-sqlite3@12.6.2)(rolldown@1.0.0-rc.1)
+ nuxt: 4.3.0(@parcel/watcher@2.5.6)(@types/node@24.10.9)(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(@vue/compiler-sfc@3.5.27)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.42.0(oxlint-tsgolint@0.11.3))(rolldown@1.0.0-rc.1)(rollup@4.57.0)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@3.2.4(typescript@5.9.3))(yaml@2.8.2)
ohash: 2.0.11
pathe: 2.0.3
pkg-types: 2.3.0
@@ -11374,7 +11570,7 @@ snapshots:
std-env: 3.10.0
ufo: 1.6.3
unctx: 2.5.0
- unstorage: 1.17.4(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)
+ unstorage: 1.17.4(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)
vue: 3.5.27(typescript@5.9.3)
vue-bundle-renderer: 2.2.0
vue-devtools-stub: 0.1.0
@@ -11421,7 +11617,7 @@ snapshots:
pkg-types: 2.3.0
std-env: 3.10.0
- '@nuxt/scripts@0.13.2(@unhead/vue@2.1.2(vue@3.5.27(typescript@5.9.3)))(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(magicast@0.5.1)(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3))':
+ '@nuxt/scripts@0.13.2(@unhead/vue@2.1.2(vue@3.5.27(typescript@5.9.3)))(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(magicast@0.5.1)(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3))':
dependencies:
'@nuxt/kit': 4.3.0(magicast@0.5.1)
'@unhead/vue': 2.1.2(vue@3.5.27(typescript@5.9.3))
@@ -11439,7 +11635,7 @@ snapshots:
std-env: 3.10.0
ufo: 1.6.3
unplugin: 2.3.11
- unstorage: 1.17.4(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)
+ unstorage: 1.17.4(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)
valibot: 1.2.0(typescript@5.9.3)
transitivePeerDependencies:
- '@azure/app-configuration'
@@ -11564,13 +11760,13 @@ snapshots:
- magicast
- typescript
- '@nuxt/ui@4.4.0(@nuxt/content@3.11.0(better-sqlite3@12.6.2)(magicast@0.5.1)(valibot@1.2.0(typescript@5.9.3)))(@tiptap/extensions@3.17.1(@tiptap/core@3.17.1(@tiptap/pm@3.17.1))(@tiptap/pm@3.17.1))(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.5)(y-protocols@1.0.7(yjs@13.6.29))(yjs@13.6.29))(db0@0.3.4(better-sqlite3@12.6.2))(embla-carousel@8.6.0)(ioredis@5.9.2)(magicast@0.5.1)(tailwindcss@4.1.18)(typescript@5.9.3)(valibot@1.2.0(typescript@5.9.3))(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue-router@4.6.4(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))(yjs@13.6.29)(zod@4.3.6)':
+ '@nuxt/ui@4.4.0(@nuxt/content@3.11.0(better-sqlite3@12.6.2)(magicast@0.5.1)(valibot@1.2.0(typescript@5.9.3)))(@tiptap/extensions@3.17.1(@tiptap/core@3.17.1(@tiptap/pm@3.17.1))(@tiptap/pm@3.17.1))(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.5)(y-protocols@1.0.7(yjs@13.6.29))(yjs@13.6.29))(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(db0@0.3.4(better-sqlite3@12.6.2))(embla-carousel@8.6.0)(ioredis@5.9.2)(magicast@0.5.1)(tailwindcss@4.1.18)(typescript@5.9.3)(valibot@1.2.0(typescript@5.9.3))(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue-router@4.6.4(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))(yjs@13.6.29)(zod@4.3.6)':
dependencies:
'@floating-ui/dom': 1.7.5
'@iconify/vue': 5.0.0(vue@3.5.27(typescript@5.9.3))
'@internationalized/date': 3.10.1
'@internationalized/number': 3.6.5
- '@nuxt/fonts': 0.12.1(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(magicast@0.5.1)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))
+ '@nuxt/fonts': 0.12.1(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(magicast@0.5.1)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))
'@nuxt/icon': 2.2.1(magicast@0.5.1)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.27(typescript@5.9.3))
'@nuxt/kit': 4.3.0(magicast@0.5.1)
'@nuxt/schema': 4.3.0
@@ -11679,7 +11875,7 @@ snapshots:
- vue
- yjs
- '@nuxt/vite-builder@4.3.0(@types/node@24.10.9)(eslint@9.39.2(jiti@2.6.1))(magicast@0.5.1)(nuxt@4.3.0(@parcel/watcher@2.5.6)(@types/node@24.10.9)(@vue/compiler-sfc@3.5.27)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.42.0(oxlint-tsgolint@0.11.3))(rolldown@1.0.0-rc.1)(rollup@4.57.0)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@3.2.4(typescript@5.9.3))(yaml@2.8.2))(optionator@0.9.4)(oxlint@1.42.0(oxlint-tsgolint@0.11.3))(rolldown@1.0.0-rc.1)(rollup@4.57.0)(terser@5.46.0)(typescript@5.9.3)(vue-tsc@3.2.4(typescript@5.9.3))(vue@3.5.27(typescript@5.9.3))(yaml@2.8.2)':
+ '@nuxt/vite-builder@4.3.0(@types/node@24.10.9)(eslint@9.39.2(jiti@2.6.1))(magicast@0.5.1)(nuxt@4.3.0(@parcel/watcher@2.5.6)(@types/node@24.10.9)(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(@vue/compiler-sfc@3.5.27)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.42.0(oxlint-tsgolint@0.11.3))(rolldown@1.0.0-rc.1)(rollup@4.57.0)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@3.2.4(typescript@5.9.3))(yaml@2.8.2))(optionator@0.9.4)(oxlint@1.42.0(oxlint-tsgolint@0.11.3))(rolldown@1.0.0-rc.1)(rollup@4.57.0)(terser@5.46.0)(typescript@5.9.3)(vue-tsc@3.2.4(typescript@5.9.3))(vue@3.5.27(typescript@5.9.3))(yaml@2.8.2)':
dependencies:
'@nuxt/kit': 4.3.0(magicast@0.5.1)
'@rollup/plugin-replace': 6.0.3(rollup@4.57.0)
@@ -11698,7 +11894,7 @@ snapshots:
magic-string: 0.30.21
mlly: 1.8.0
mocked-exports: 0.1.1
- nuxt: 4.3.0(@parcel/watcher@2.5.6)(@types/node@24.10.9)(@vue/compiler-sfc@3.5.27)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.42.0(oxlint-tsgolint@0.11.3))(rolldown@1.0.0-rc.1)(rollup@4.57.0)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@3.2.4(typescript@5.9.3))(yaml@2.8.2)
+ nuxt: 4.3.0(@parcel/watcher@2.5.6)(@types/node@24.10.9)(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(@vue/compiler-sfc@3.5.27)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.42.0(oxlint-tsgolint@0.11.3))(rolldown@1.0.0-rc.1)(rollup@4.57.0)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@3.2.4(typescript@5.9.3))(yaml@2.8.2)
pathe: 2.0.3
pkg-types: 2.3.0
postcss: 8.5.6
@@ -11778,7 +11974,7 @@ snapshots:
- magicast
- vitest
- '@nuxtjs/i18n@10.2.1(@vue/compiler-dom@3.5.27)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(rollup@4.57.0)(vue@3.5.27(typescript@5.9.3))':
+ '@nuxtjs/i18n@10.2.1(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(@vue/compiler-dom@3.5.27)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(rollup@4.57.0)(vue@3.5.27(typescript@5.9.3))':
dependencies:
'@intlify/core': 11.2.8
'@intlify/h3': 0.7.4
@@ -11805,7 +12001,7 @@ snapshots:
ufo: 1.6.3
unplugin: 2.3.11
unplugin-vue-router: 0.16.2(@vue/compiler-sfc@3.5.27)(vue-router@4.6.4(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))
- unstorage: 1.17.4(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)
+ unstorage: 1.17.4(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)
vue-i18n: 11.2.8(vue@3.5.27(typescript@5.9.3))
vue-router: 4.6.4(vue@3.5.27(typescript@5.9.3))
transitivePeerDependencies:
@@ -13451,6 +13647,14 @@ snapshots:
webpack: 5.104.1(esbuild@0.27.2)
webpack-sources: 3.3.3
+ '@upstash/redis@1.36.1':
+ dependencies:
+ uncrypto: 0.1.3
+
+ '@vercel/kv@3.0.0':
+ dependencies:
+ '@upstash/redis': 1.36.1
+
'@vercel/nft@1.3.0(rollup@4.57.0)':
dependencies:
'@mapbox/node-pre-gyp': 2.0.3
@@ -13833,13 +14037,13 @@ snapshots:
'@vueuse/metadata@14.1.0': {}
- '@vueuse/nuxt@14.1.0(magicast@0.5.1)(nuxt@4.3.0(@parcel/watcher@2.5.6)(@types/node@24.10.9)(@vue/compiler-sfc@3.5.27)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.42.0(oxlint-tsgolint@0.11.3))(rolldown@1.0.0-rc.1)(rollup@4.57.0)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@3.2.4(typescript@5.9.3))(yaml@2.8.2))(vue@3.5.27(typescript@5.9.3))':
+ '@vueuse/nuxt@14.1.0(magicast@0.5.1)(nuxt@4.3.0(@parcel/watcher@2.5.6)(@types/node@24.10.9)(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(@vue/compiler-sfc@3.5.27)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.42.0(oxlint-tsgolint@0.11.3))(rolldown@1.0.0-rc.1)(rollup@4.57.0)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@3.2.4(typescript@5.9.3))(yaml@2.8.2))(vue@3.5.27(typescript@5.9.3))':
dependencies:
'@nuxt/kit': 4.3.0(magicast@0.5.1)
'@vueuse/core': 14.1.0(vue@3.5.27(typescript@5.9.3))
'@vueuse/metadata': 14.1.0
local-pkg: 1.1.2
- nuxt: 4.3.0(@parcel/watcher@2.5.6)(@types/node@24.10.9)(@vue/compiler-sfc@3.5.27)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.42.0(oxlint-tsgolint@0.11.3))(rolldown@1.0.0-rc.1)(rollup@4.57.0)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@3.2.4(typescript@5.9.3))(yaml@2.8.2)
+ nuxt: 4.3.0(@parcel/watcher@2.5.6)(@types/node@24.10.9)(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(@vue/compiler-sfc@3.5.27)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.42.0(oxlint-tsgolint@0.11.3))(rolldown@1.0.0-rc.1)(rollup@4.57.0)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@3.2.4(typescript@5.9.3))(yaml@2.8.2)
vue: 3.5.27(typescript@5.9.3)
transitivePeerDependencies:
- magicast
@@ -14137,6 +14341,8 @@ snapshots:
dependencies:
possible-typed-array-names: 1.1.0
+ await-lock@2.2.2: {}
+
axe-core@4.11.1: {}
b4a@1.7.3: {}
@@ -14689,16 +14895,16 @@ snapshots:
diff@8.0.3: {}
- docus@5.4.4(29b67e4ea82958d3bb639aa676be121d):
+ docus@5.4.4(f5d56481b2ddeca600f9d39a31d83017):
dependencies:
'@iconify-json/lucide': 1.2.87
'@iconify-json/simple-icons': 1.2.68
'@iconify-json/vscode-icons': 1.2.40
'@nuxt/content': 3.11.0(better-sqlite3@12.6.2)(magicast@0.5.1)(valibot@1.2.0(typescript@5.9.3))
- '@nuxt/image': 2.0.0(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(magicast@0.5.1)
+ '@nuxt/image': 2.0.0(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(magicast@0.5.1)
'@nuxt/kit': 4.3.0(magicast@0.5.1)
- '@nuxt/ui': 4.4.0(@nuxt/content@3.11.0(better-sqlite3@12.6.2)(magicast@0.5.1)(valibot@1.2.0(typescript@5.9.3)))(@tiptap/extensions@3.17.1(@tiptap/core@3.17.1(@tiptap/pm@3.17.1))(@tiptap/pm@3.17.1))(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.5)(y-protocols@1.0.7(yjs@13.6.29))(yjs@13.6.29))(db0@0.3.4(better-sqlite3@12.6.2))(embla-carousel@8.6.0)(ioredis@5.9.2)(magicast@0.5.1)(tailwindcss@4.1.18)(typescript@5.9.3)(valibot@1.2.0(typescript@5.9.3))(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue-router@4.6.4(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))(yjs@13.6.29)(zod@4.3.6)
- '@nuxtjs/i18n': 10.2.1(@vue/compiler-dom@3.5.27)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(rollup@4.57.0)(vue@3.5.27(typescript@5.9.3))
+ '@nuxt/ui': 4.4.0(@nuxt/content@3.11.0(better-sqlite3@12.6.2)(magicast@0.5.1)(valibot@1.2.0(typescript@5.9.3)))(@tiptap/extensions@3.17.1(@tiptap/core@3.17.1(@tiptap/pm@3.17.1))(@tiptap/pm@3.17.1))(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.5)(y-protocols@1.0.7(yjs@13.6.29))(yjs@13.6.29))(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(db0@0.3.4(better-sqlite3@12.6.2))(embla-carousel@8.6.0)(ioredis@5.9.2)(magicast@0.5.1)(tailwindcss@4.1.18)(typescript@5.9.3)(valibot@1.2.0(typescript@5.9.3))(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue-router@4.6.4(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))(yjs@13.6.29)(zod@4.3.6)
+ '@nuxtjs/i18n': 10.2.1(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(@vue/compiler-dom@3.5.27)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(rollup@4.57.0)(vue@3.5.27(typescript@5.9.3))
'@nuxtjs/mcp-toolkit': 0.6.2(hono@4.11.7)(magicast@0.5.1)(zod@4.3.6)
'@nuxtjs/mdc': 0.20.0(magicast@0.5.1)
'@nuxtjs/robots': 5.6.7(magicast@0.5.1)(vue@3.5.27(typescript@5.9.3))(zod@4.3.6)
@@ -14709,9 +14915,9 @@ snapshots:
git-url-parse: 16.1.0
minimark: 0.2.0
motion-v: 1.10.2(@vueuse/core@14.1.0(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))
- nuxt: 4.3.0(@parcel/watcher@2.5.6)(@types/node@24.10.9)(@vue/compiler-sfc@3.5.27)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.42.0(oxlint-tsgolint@0.11.3))(rolldown@1.0.0-rc.1)(rollup@4.57.0)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@3.2.4(typescript@5.9.3))(yaml@2.8.2)
+ nuxt: 4.3.0(@parcel/watcher@2.5.6)(@types/node@24.10.9)(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(@vue/compiler-sfc@3.5.27)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.42.0(oxlint-tsgolint@0.11.3))(rolldown@1.0.0-rc.1)(rollup@4.57.0)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@3.2.4(typescript@5.9.3))(yaml@2.8.2)
nuxt-llms: 0.2.0(magicast@0.5.1)
- nuxt-og-image: 5.1.13(@unhead/vue@2.1.2(vue@3.5.27(typescript@5.9.3)))(magicast@0.5.1)(unstorage@1.17.4(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2))(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.27(typescript@5.9.3))
+ nuxt-og-image: 5.1.13(@unhead/vue@2.1.2(vue@3.5.27(typescript@5.9.3)))(magicast@0.5.1)(unstorage@1.17.4(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2))(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.27(typescript@5.9.3))
pkg-types: 2.3.0
scule: 1.3.0
tailwindcss: 4.1.18
@@ -15384,7 +15590,7 @@ snapshots:
dependencies:
tiny-inflate: 1.0.3
- fontless@0.1.0(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)):
+ fontless@0.1.0(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)):
dependencies:
consola: 3.4.2
css-tree: 3.1.0
@@ -15398,7 +15604,7 @@ snapshots:
pathe: 2.0.3
ufo: 1.6.3
unifont: 0.6.0
- unstorage: 1.17.4(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)
+ unstorage: 1.17.4(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)
optionalDependencies:
vite: 7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)
transitivePeerDependencies:
@@ -15934,7 +16140,9 @@ snapshots:
ipaddr.js@1.9.1: {}
- ipx@3.1.1(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2):
+ ipaddr.js@2.3.0: {}
+
+ ipx@3.1.1(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2):
dependencies:
'@fastify/accept-negotiator': 2.0.1
citty: 0.1.6
@@ -15950,7 +16158,7 @@ snapshots:
sharp: 0.34.5
svgo: 4.0.0
ufo: 1.6.3
- unstorage: 1.17.4(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)
+ unstorage: 1.17.4(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)
xss: 1.0.15
transitivePeerDependencies:
- '@azure/app-configuration'
@@ -16225,6 +16433,8 @@ snapshots:
jiti@2.6.1: {}
+ jose@5.10.0: {}
+
jose@6.1.3: {}
js-beautify@1.15.4:
@@ -17039,7 +17249,7 @@ snapshots:
neotraverse@0.6.18: {}
- nitropack@2.13.1(better-sqlite3@12.6.2)(rolldown@1.0.0-rc.1):
+ nitropack@2.13.1(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(better-sqlite3@12.6.2)(rolldown@1.0.0-rc.1):
dependencies:
'@cloudflare/kv-asset-handler': 0.4.2
'@rollup/plugin-alias': 6.0.0(rollup@4.57.0)
@@ -17106,7 +17316,7 @@ snapshots:
unenv: 2.0.0-rc.24
unimport: 5.6.0
unplugin-utils: 0.3.1
- unstorage: 1.17.4(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)
+ unstorage: 1.17.4(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)
untyped: 2.0.0
unwasm: 0.5.3
youch: 4.1.0-beta.13
@@ -17212,7 +17422,7 @@ snapshots:
transitivePeerDependencies:
- magicast
- nuxt-og-image@5.1.13(@unhead/vue@2.1.2(vue@3.5.27(typescript@5.9.3)))(magicast@0.5.1)(unstorage@1.17.4(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2))(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.27(typescript@5.9.3)):
+ nuxt-og-image@5.1.13(@unhead/vue@2.1.2(vue@3.5.27(typescript@5.9.3)))(magicast@0.5.1)(unstorage@1.17.4(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2))(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.27(typescript@5.9.3)):
dependencies:
'@nuxt/devtools-kit': 3.1.1(magicast@0.5.1)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))
'@nuxt/kit': 4.3.0(magicast@0.5.1)
@@ -17243,7 +17453,7 @@ snapshots:
strip-literal: 3.1.0
ufo: 1.6.3
unplugin: 2.3.11
- unstorage: 1.17.4(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)
+ unstorage: 1.17.4(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)
unwasm: 0.5.3
yoga-wasm-web: 0.3.3
transitivePeerDependencies:
@@ -17277,16 +17487,16 @@ snapshots:
- magicast
- vue
- nuxt@4.3.0(@parcel/watcher@2.5.6)(@types/node@24.10.9)(@vue/compiler-sfc@3.5.27)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.42.0(oxlint-tsgolint@0.11.3))(rolldown@1.0.0-rc.1)(rollup@4.57.0)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@3.2.4(typescript@5.9.3))(yaml@2.8.2):
+ nuxt@4.3.0(@parcel/watcher@2.5.6)(@types/node@24.10.9)(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(@vue/compiler-sfc@3.5.27)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.42.0(oxlint-tsgolint@0.11.3))(rolldown@1.0.0-rc.1)(rollup@4.57.0)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@3.2.4(typescript@5.9.3))(yaml@2.8.2):
dependencies:
'@dxup/nuxt': 0.3.2(magicast@0.5.1)
'@nuxt/cli': 3.32.0(cac@6.7.14)(magicast@0.5.1)
'@nuxt/devtools': 3.1.1(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.27(typescript@5.9.3))
'@nuxt/kit': 4.3.0(magicast@0.5.1)
- '@nuxt/nitro-server': 4.3.0(better-sqlite3@12.6.2)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(magicast@0.5.1)(nuxt@4.3.0(@parcel/watcher@2.5.6)(@types/node@24.10.9)(@vue/compiler-sfc@3.5.27)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.42.0(oxlint-tsgolint@0.11.3))(rolldown@1.0.0-rc.1)(rollup@4.57.0)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@3.2.4(typescript@5.9.3))(yaml@2.8.2))(rolldown@1.0.0-rc.1)(typescript@5.9.3)
+ '@nuxt/nitro-server': 4.3.0(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(better-sqlite3@12.6.2)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(magicast@0.5.1)(nuxt@4.3.0(@parcel/watcher@2.5.6)(@types/node@24.10.9)(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(@vue/compiler-sfc@3.5.27)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.42.0(oxlint-tsgolint@0.11.3))(rolldown@1.0.0-rc.1)(rollup@4.57.0)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@3.2.4(typescript@5.9.3))(yaml@2.8.2))(rolldown@1.0.0-rc.1)(typescript@5.9.3)
'@nuxt/schema': 4.3.0
'@nuxt/telemetry': 2.6.6(magicast@0.5.1)
- '@nuxt/vite-builder': 4.3.0(@types/node@24.10.9)(eslint@9.39.2(jiti@2.6.1))(magicast@0.5.1)(nuxt@4.3.0(@parcel/watcher@2.5.6)(@types/node@24.10.9)(@vue/compiler-sfc@3.5.27)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.42.0(oxlint-tsgolint@0.11.3))(rolldown@1.0.0-rc.1)(rollup@4.57.0)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@3.2.4(typescript@5.9.3))(yaml@2.8.2))(optionator@0.9.4)(oxlint@1.42.0(oxlint-tsgolint@0.11.3))(rolldown@1.0.0-rc.1)(rollup@4.57.0)(terser@5.46.0)(typescript@5.9.3)(vue-tsc@3.2.4(typescript@5.9.3))(vue@3.5.27(typescript@5.9.3))(yaml@2.8.2)
+ '@nuxt/vite-builder': 4.3.0(@types/node@24.10.9)(eslint@9.39.2(jiti@2.6.1))(magicast@0.5.1)(nuxt@4.3.0(@parcel/watcher@2.5.6)(@types/node@24.10.9)(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(@vue/compiler-sfc@3.5.27)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.42.0(oxlint-tsgolint@0.11.3))(rolldown@1.0.0-rc.1)(rollup@4.57.0)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@3.2.4(typescript@5.9.3))(yaml@2.8.2))(optionator@0.9.4)(oxlint@1.42.0(oxlint-tsgolint@0.11.3))(rolldown@1.0.0-rc.1)(rollup@4.57.0)(terser@5.46.0)(typescript@5.9.3)(vue-tsc@3.2.4(typescript@5.9.3))(vue@3.5.27(typescript@5.9.3))(yaml@2.8.2)
'@unhead/vue': 2.1.2(vue@3.5.27(typescript@5.9.3))
'@vue/shared': 3.5.27
c12: 3.3.3(magicast@0.5.1)
@@ -19119,6 +19329,8 @@ snapshots:
tinyrainbow@3.0.3: {}
+ tlds@1.261.0: {}
+
to-buffer@1.2.2:
dependencies:
isarray: 2.0.5
@@ -19289,6 +19501,8 @@ snapshots:
undici-types@7.16.0: {}
+ undici@6.23.0: {}
+
unenv@2.0.0-rc.24:
dependencies:
pathe: 2.0.3
@@ -19525,7 +19739,7 @@ snapshots:
dependencies:
rolldown: 1.0.0-rc.1
- unstorage@1.17.4(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2):
+ unstorage@1.17.4(@upstash/redis@1.36.1)(@vercel/kv@3.0.0)(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2):
dependencies:
anymatch: 3.1.3
chokidar: 5.0.0
@@ -19536,6 +19750,8 @@ snapshots:
ofetch: 1.5.1
ufo: 1.6.3
optionalDependencies:
+ '@upstash/redis': 1.36.1
+ '@vercel/kv': 3.0.0
db0: 0.3.4(better-sqlite3@12.6.2)
ioredis: 5.9.2
diff --git a/server/api/auth/atproto.get.ts b/server/api/auth/atproto.get.ts
new file mode 100644
index 0000000000..d6dd07e0e4
--- /dev/null
+++ b/server/api/auth/atproto.get.ts
@@ -0,0 +1,64 @@
+import { Agent } from '@atproto/api'
+import { NodeOAuthClient } from '@atproto/oauth-client-node'
+import { createError, getQuery, sendRedirect } from 'h3'
+import { useOAuthStorage } from '#server/utils/atproto/storage'
+import { SLINGSHOT_ENDPOINT } from '#shared/utils/constants'
+import type { UserSession } from '#shared/schemas/userSession'
+
+export default defineEventHandler(async event => {
+ const config = useRuntimeConfig(event)
+ if (!config.sessionPassword) {
+ throw createError({
+ status: 500,
+ message: 'NUXT_SESSION_PASSWORD not set',
+ })
+ }
+
+ const query = getQuery(event)
+ const clientMetadata = getOauthClientMetadata()
+ const { stateStore, sessionStore } = useOAuthStorage(event)
+
+ const atclient = new NodeOAuthClient({
+ stateStore,
+ sessionStore,
+ clientMetadata,
+ })
+
+ if (!query.code) {
+ const handle = query.handle?.toString()
+ const create = query.create?.toString()
+
+ if (!handle) {
+ throw createError({
+ status: 400,
+ message: 'Handle not provided in query',
+ })
+ }
+
+ const redirectUrl = await atclient.authorize(handle, {
+ scope,
+ prompt: create ? 'create' : undefined,
+ })
+ return sendRedirect(event, redirectUrl.toString())
+ }
+
+ const { session: authSession } = await atclient.callback(
+ new URLSearchParams(query as Record),
+ )
+ const agent = new Agent(authSession)
+ event.context.agent = agent
+
+ const session = await useSession(event, {
+ password: config.sessionPassword,
+ })
+
+ const response = await fetch(
+ `${SLINGSHOT_ENDPOINT}/xrpc/com.bad-example.identity.resolveMiniDoc?identifier=${agent.did}`,
+ { headers: { 'User-Agent': 'npmx' } },
+ )
+ const miniDoc = (await response.json()) as UserSession
+
+ await session.update(miniDoc)
+
+ return sendRedirect(event, '/')
+})
diff --git a/server/api/auth/session.delete.ts b/server/api/auth/session.delete.ts
new file mode 100644
index 0000000000..1b3ff676b2
--- /dev/null
+++ b/server/api/auth/session.delete.ts
@@ -0,0 +1,6 @@
+export default eventHandlerWithOAuthSession(async (event, oAuthSession, serverSession) => {
+ await oAuthSession?.signOut()
+ await serverSession.clear()
+
+ return 'Session cleared'
+})
diff --git a/server/api/auth/session.get.ts b/server/api/auth/session.get.ts
new file mode 100644
index 0000000000..64903ca701
--- /dev/null
+++ b/server/api/auth/session.get.ts
@@ -0,0 +1,11 @@
+import { UserSessionSchema } from '#shared/schemas/userSession'
+import { safeParse } from 'valibot'
+
+export default eventHandlerWithOAuthSession(async (event, oAuthSession, serverSession) => {
+ const result = safeParse(UserSessionSchema, serverSession.data)
+ if (!result.success) {
+ return null
+ }
+
+ return result.output
+})
diff --git a/server/routes/oauth-client-metadata.json.get.ts b/server/routes/oauth-client-metadata.json.get.ts
new file mode 100644
index 0000000000..0ea235ed64
--- /dev/null
+++ b/server/routes/oauth-client-metadata.json.get.ts
@@ -0,0 +1,3 @@
+export default defineEventHandler(() => {
+ return getOauthClientMetadata()
+})
diff --git a/server/utils/atproto/lock.ts b/server/utils/atproto/lock.ts
new file mode 100644
index 0000000000..21f00dc2b2
--- /dev/null
+++ b/server/utils/atproto/lock.ts
@@ -0,0 +1,68 @@
+import type { RuntimeLock } from '@atproto/oauth-client-node'
+import { requestLocalLock } from '@atproto/oauth-client-node'
+import { Redis } from '@upstash/redis'
+
+type Awaitable = T | PromiseLike
+
+/**
+ * Creates a distributed lock using Upstash Redis.
+ * Falls back gracefully if the lock cannot be acquired.
+ */
+function createUpstashLock(redis: Redis): RuntimeLock {
+ return async (key: string, fn: () => Awaitable): Promise => {
+ const lockKey = `oauth:lock:${key}`
+ const lockValue = crypto.randomUUID()
+ const lockTTL = 30 // seconds
+
+ // Try to acquire lock with NX (only set if not exists) and EX (expire)
+ const acquired = await redis.set(lockKey, lockValue, {
+ nx: true,
+ ex: lockTTL,
+ })
+
+ if (!acquired) {
+ // Another instance holds the lock, wait briefly and retry once
+ await new Promise(resolve => setTimeout(resolve, 100))
+ const retryAcquired = await redis.set(lockKey, lockValue, {
+ nx: true,
+ ex: lockTTL,
+ })
+ if (!retryAcquired) {
+ // Still can't acquire, proceed without lock (better than failing)
+ // The worst case is a token refresh race, which will just require re-auth
+ return await fn()
+ }
+ }
+
+ try {
+ return await fn()
+ } finally {
+ // Release lock only if we still own it (compare-and-delete)
+ const currentValue = await redis.get(lockKey)
+ if (currentValue === lockValue) {
+ await redis.del(lockKey)
+ }
+ }
+ }
+}
+
+/**
+ * Returns the appropriate lock mechanism based on environment:
+ * - Production with Upstash config: distributed Redis lock
+ * - Otherwise: in-memory lock (sufficient for single instance)
+ */
+export function getOAuthLock(): RuntimeLock {
+ const config = useRuntimeConfig()
+
+ // Use distributed lock in production if Upstash is configured
+ if (!import.meta.dev && config.upstash?.redisRestUrl && config.upstash?.redisRestToken) {
+ const redis = new Redis({
+ url: config.upstash.redisRestUrl,
+ token: config.upstash.redisRestToken,
+ })
+ return createUpstashLock(redis)
+ }
+
+ // Fall back to in-memory lock for dev/preview or when Redis isn't configured
+ return requestLocalLock
+}
diff --git a/server/utils/atproto/oauth.ts b/server/utils/atproto/oauth.ts
new file mode 100644
index 0000000000..a1bc55ba51
--- /dev/null
+++ b/server/utils/atproto/oauth.ts
@@ -0,0 +1,82 @@
+import type { OAuthClientMetadataInput, OAuthSession } from '@atproto/oauth-client-node'
+import type { EventHandlerRequest, H3Event, SessionManager } from 'h3'
+import { NodeOAuthClient } from '@atproto/oauth-client-node'
+import { parse } from 'valibot'
+import { getOAuthLock } from '#server/utils/atproto/lock'
+import { useOAuthStorage } from '#server/utils/atproto/storage'
+import { UNSET_NUXT_SESSION_PASSWORD } from '#shared/utils/constants'
+import { OAuthMetadataSchema } from '#shared/schemas/oauth'
+// TODO: limit scope as features gets added. atproto just allows login so no scary login screen till we have scopes
+export const scope = 'atproto'
+
+export function getOauthClientMetadata() {
+ const dev = import.meta.dev
+
+ // on dev, match in nuxt.config.ts devServer: { host: "127.0.0.1" }
+ const client_uri = dev ? `http://127.0.0.1:3000` : 'https://npmx.dev'
+ const redirect_uri = `${client_uri}/api/auth/atproto`
+
+ const client_id = dev
+ ? `http://localhost?redirect_uri=${encodeURIComponent(redirect_uri)}&scope=${encodeURIComponent(scope)}`
+ : `${client_uri}/oauth-client-metadata.json`
+
+ // If anything changes here, please make sure to also update /shared/schemas/oauth.ts to match
+ return parse(OAuthMetadataSchema, {
+ client_name: 'npmx.dev',
+ client_id,
+ client_uri,
+ scope,
+ redirect_uris: [redirect_uri] as [string, ...string[]],
+ grant_types: ['authorization_code', 'refresh_token'],
+ application_type: 'web',
+ token_endpoint_auth_method: 'none',
+ dpop_bound_access_tokens: true,
+ }) as OAuthClientMetadataInput
+}
+
+type EventHandlerWithOAuthSession = (
+ event: H3Event,
+ session: OAuthSession | undefined,
+ serverSession: SessionManager,
+) => Promise
+
+async function getOAuthSession(event: H3Event): Promise {
+ const clientMetadata = getOauthClientMetadata()
+ const { stateStore, sessionStore } = useOAuthStorage(event)
+
+ const client = new NodeOAuthClient({
+ stateStore,
+ sessionStore,
+ clientMetadata,
+ requestLock: getOAuthLock(),
+ })
+
+ const currentSession = await sessionStore.get()
+ if (!currentSession) return undefined
+
+ // restore using the subject
+ return await client.restore(currentSession.tokenSet.sub)
+}
+
+/** @public */
+export function eventHandlerWithOAuthSession(
+ handler: EventHandlerWithOAuthSession,
+) {
+ return defineEventHandler(async event => {
+ const config = useRuntimeConfig(event)
+
+ if (!config.sessionPassword) {
+ throw createError({
+ status: 500,
+ message: UNSET_NUXT_SESSION_PASSWORD,
+ })
+ }
+
+ const serverSession = await useSession(event, {
+ password: config.sessionPassword,
+ })
+
+ const oAuthSession = await getOAuthSession(event)
+ return await handler(event, oAuthSession, serverSession)
+ })
+}
diff --git a/server/utils/atproto/storage.ts b/server/utils/atproto/storage.ts
new file mode 100644
index 0000000000..3e5c6b6644
--- /dev/null
+++ b/server/utils/atproto/storage.ts
@@ -0,0 +1,89 @@
+import type {
+ NodeSavedSession,
+ NodeSavedSessionStore,
+ NodeSavedState,
+ NodeSavedStateStore,
+} from '@atproto/oauth-client-node'
+import type { H3Event } from 'h3'
+
+/**
+ * Storage key prefix for oauth state storage.
+ */
+export const OAUTH_STATE_CACHE_STORAGE_BASE = 'oauth-atproto-state'
+
+export class OAuthStateStore implements NodeSavedStateStore {
+ private readonly cookieKey = 'oauth:atproto:state'
+ private readonly storage = useStorage(OAUTH_STATE_CACHE_STORAGE_BASE)
+
+ constructor(private event: H3Event) {}
+
+ async get(): Promise {
+ const stateKey = getCookie(this.event, this.cookieKey)
+ if (!stateKey) return
+ const result = await this.storage.getItem(stateKey)
+ if (!result) return
+ return result
+ }
+
+ async set(key: string, val: NodeSavedState) {
+ setCookie(this.event, this.cookieKey, key, {
+ httpOnly: true,
+ secure: !import.meta.dev,
+ sameSite: 'lax',
+ })
+ await this.storage.setItem(key, val)
+ }
+
+ async del() {
+ const stateKey = getCookie(this.event, this.cookieKey)
+ deleteCookie(this.event, this.cookieKey)
+ if (stateKey) {
+ await this.storage.del(stateKey)
+ }
+ }
+}
+
+/**
+ * Storage key prefix for oauth session storage.
+ */
+export const OAUTH_SESSION_CACHE_STORAGE_BASE = 'oauth-atproto-session'
+
+export class OAuthSessionStore implements NodeSavedSessionStore {
+ // TODO: not sure if we will support multi accounts, but if we do in the future will need to change this around
+ private readonly cookieKey = 'oauth:atproto:session'
+ private readonly storage = useStorage(OAUTH_SESSION_CACHE_STORAGE_BASE)
+
+ constructor(private event: H3Event) {}
+
+ async get(): Promise {
+ const sessionKey = getCookie(this.event, this.cookieKey)
+ if (!sessionKey) return
+ const result = await this.storage.getItem(sessionKey)
+ if (!result) return
+ return result
+ }
+
+ async set(key: string, val: NodeSavedSession) {
+ setCookie(this.event, this.cookieKey, key, {
+ httpOnly: true,
+ secure: !import.meta.dev,
+ sameSite: 'lax',
+ })
+ await this.storage.setItem(key, val)
+ }
+
+ async del() {
+ const sessionKey = getCookie(this.event, this.cookieKey)
+ if (sessionKey) {
+ await this.storage.del(sessionKey)
+ }
+ deleteCookie(this.event, this.cookieKey)
+ }
+}
+
+export const useOAuthStorage = (event: H3Event) => {
+ return {
+ stateStore: new OAuthStateStore(event),
+ sessionStore: new OAuthSessionStore(event),
+ }
+}
diff --git a/shared/schemas/oauth.ts b/shared/schemas/oauth.ts
new file mode 100644
index 0000000000..ca4980de17
--- /dev/null
+++ b/shared/schemas/oauth.ts
@@ -0,0 +1,13 @@
+import { object, string, pipe, url, array, minLength, boolean } from 'valibot'
+
+export const OAuthMetadataSchema = object({
+ client_id: pipe(string(), url()),
+ client_name: string(),
+ client_uri: pipe(string(), url()),
+ redirect_uris: pipe(array(string()), minLength(1)),
+ scope: string(),
+ grant_types: array(string()),
+ application_type: string(),
+ token_endpoint_auth_method: string(),
+ dpop_bound_access_tokens: boolean(),
+})
diff --git a/shared/schemas/userSession.ts b/shared/schemas/userSession.ts
new file mode 100644
index 0000000000..ccbdc934f3
--- /dev/null
+++ b/shared/schemas/userSession.ts
@@ -0,0 +1,10 @@
+import { object, string, pipe, url } from 'valibot'
+import type { InferOutput } from 'valibot'
+
+export const UserSessionSchema = object({
+ did: string(),
+ handle: string(),
+ pds: pipe(string(), url()),
+})
+
+export type UserSession = InferOutput
diff --git a/shared/utils/constants.ts b/shared/utils/constants.ts
index e6dbb68df4..15dc7639fc 100644
--- a/shared/utils/constants.ts
+++ b/shared/utils/constants.ts
@@ -16,9 +16,14 @@ export const ERROR_CALC_INSTALL_SIZE_FAILED = 'Failed to calculate install size.
export const NPM_MISSING_README_SENTINEL = 'ERROR: No README data found!'
export const ERROR_JSR_FETCH_FAILED = 'Failed to fetch package from JSR registry.'
export const ERROR_NPM_FETCH_FAILED = 'Failed to fetch package from npm registry.'
+export const UNSET_NUXT_SESSION_PASSWORD = 'NUXT_SESSION_PASSWORD not set'
/** @public */
export const ERROR_SUGGESTIONS_FETCH_FAILED = 'Failed to fetch suggestions.'
+// microcosm services
+export const CONSTELLATION_ENDPOINT = 'https://constellation.microcosm.blue'
+export const SLINGSHOT_ENDPOINT = 'https://slingshot.microcosm.blue'
+
// Theming
export const ACCENT_COLORS = {
rose: 'oklch(0.797 0.084 11.056)',
diff --git a/shared/utils/fetch-cache-config.ts b/shared/utils/fetch-cache-config.ts
index eb8ed1459d..4b6465cfe3 100644
--- a/shared/utils/fetch-cache-config.ts
+++ b/shared/utils/fetch-cache-config.ts
@@ -5,6 +5,8 @@
* using Nitro's storage layer (backed by Vercel's runtime cache in production).
*/
+import { CONSTELLATION_ENDPOINT, SLINGSHOT_ENDPOINT } from './constants'
+
/**
* Domains that should have their fetch responses cached.
* Only requests to these domains will be intercepted and cached.
@@ -24,6 +26,9 @@ export const FETCH_CACHE_ALLOWED_DOMAINS = [
'api.bitbucket.org', // Bitbucket API
'codeberg.org', // Codeberg (Gitea-based)
'gitee.com', // Gitee API
+ // microcosm endpoints for atproto data
+ CONSTELLATION_ENDPOINT,
+ SLINGSHOT_ENDPOINT,
] as const
/**