diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml new file mode 100644 index 0000000..01acfce --- /dev/null +++ b/.github/workflows/quality.yml @@ -0,0 +1,21 @@ +name: Quality + +on: + pull_request: + push: + branches: [main] + +permissions: + contents: read + +jobs: + format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-node@v7 + with: + node-version: 22 + cache: npm + - run: npm ci --ignore-scripts + - run: npm run format:check diff --git a/AGENTS.md b/AGENTS.md index e24f512..5f52ae2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,5 +1,6 @@ # Repository Guide +- Follow the [organization file-size rule](https://github.com/cordisx/cordisxmono/blob/main/.agents/rules/file-size.md) for formatting and responsibility-based splitting guidance. - Use the [repository README](README.md) to select the CordisX plugin or Reset petition site before changing files or running commands. - `plugin/` owns the plugin and its [usage and development documentation](plugin/README.md). diff --git a/README.md b/README.md index 7fa6222..9a3f465 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@

- OpenAI Imperium Ouroboros coin + OpenAI Imperium Ouroboros coin

@@ -25,10 +29,10 @@ OpenAI Imperium contains a CordisX reasoning-intensity presentation plugin and a Reset petition site. Each component has its own runtime and maintenance entry: -| Component | Usage and maintenance | -| --- | --- | -| CordisX plugin (`plugin/`) | [Plugin README](plugin/README.md): configuration, Host compatibility, and development commands | -| Reset petition site (repository root) | [Site README](app/README.md): experience, local development, and persistent ledger | +| Component | Usage and maintenance | +| ------------------------------------- | ---------------------------------------------------------------------------------------------- | +| CordisX plugin (`plugin/`) | [Plugin README](plugin/README.md): configuration, Host compatibility, and development commands | +| Reset petition site (repository root) | [Site README](app/README.md): experience, local development, and persistent ledger | ![OpenAI Imperium — Veni. Vidi. Reset.](./public/og.png) diff --git a/README.zh-CN.md b/README.zh-CN.md index f287db5..d4137aa 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -25,10 +25,10 @@ OpenAI Imperium 在同一仓库中提供 CordisX 思考强度视觉插件与 Reset 请愿站。 两个组件分别维护运行入口: -| 组件 | 使用与维护说明 | -| --- | --- | -| CordisX 插件(`plugin/`) | [插件 README](plugin/README.md#中文):配置、Host 兼容条件与开发命令 | -| Reset 请愿站(仓库根目录) | [请愿站 README](app/README.zh-CN.md):体验介绍、本地运行与持久账本 | +| 组件 | 使用与维护说明 | +| -------------------------- | ------------------------------------------------------------------- | +| CordisX 插件(`plugin/`) | [插件 README](plugin/README.md#中文):配置、Host 兼容条件与开发命令 | +| Reset 请愿站(仓库根目录) | [请愿站 README](app/README.zh-CN.md):体验介绍、本地运行与持久账本 | ![OpenAI Imperium——我来,我见,我 Reset。](./public/og.png) diff --git a/app/api/admin/reset/route.ts b/app/api/admin/reset/route.ts index 644dfe8..e1f0f27 100644 --- a/app/api/admin/reset/route.ts +++ b/app/api/admin/reset/route.ts @@ -1,14 +1,14 @@ -import { grantReset, isAuthorizedReset } from "@/db/ledger"; +import { grantReset, isAuthorizedReset } from '@/db/ledger'; -export const dynamic = "force-dynamic"; +export const dynamic = 'force-dynamic'; export async function POST(request: Request) { if (!isAuthorizedReset(request)) { - return Response.json({ error: "Imperial authority required." }, { status: 403 }); + return Response.json({ error: 'Imperial authority required.' }, { status: 403 }); } try { - return Response.json(await grantReset(), { headers: { "Cache-Control": "no-store" } }); + return Response.json(await grantReset(), { headers: { 'Cache-Control': 'no-store' } }); } catch { - return Response.json({ error: "The reset could not be granted." }, { status: 503 }); + return Response.json({ error: 'The reset could not be granted.' }, { status: 503 }); } } diff --git a/app/api/ledger/route.ts b/app/api/ledger/route.ts index 26767c9..10faacf 100644 --- a/app/api/ledger/route.ts +++ b/app/api/ledger/route.ts @@ -1,14 +1,14 @@ -import { readLedger } from "@/db/ledger"; -import { proxyDevelopmentLedger } from "@/db/ledger-proxy"; +import { readLedger } from '@/db/ledger'; +import { proxyDevelopmentLedger } from '@/db/ledger-proxy'; -export const dynamic = "force-dynamic"; +export const dynamic = 'force-dynamic'; export async function GET() { try { - const proxied = await proxyDevelopmentLedger("/api/ledger"); + const proxied = await proxyDevelopmentLedger('/api/ledger'); if (proxied) return proxied; - return Response.json(await readLedger(), { headers: { "Cache-Control": "no-store" } }); + return Response.json(await readLedger(), { headers: { 'Cache-Control': 'no-store' } }); } catch { - return Response.json({ error: "The petition ledger is temporarily unavailable." }, { status: 503 }); + return Response.json({ error: 'The petition ledger is temporarily unavailable.' }, { status: 503 }); } } diff --git a/app/api/petition/route.ts b/app/api/petition/route.ts index ed8d9f9..7909f09 100644 --- a/app/api/petition/route.ts +++ b/app/api/petition/route.ts @@ -1,14 +1,14 @@ -import { recordPetition } from "@/db/ledger"; -import { proxyDevelopmentLedger } from "@/db/ledger-proxy"; +import { recordPetition } from '@/db/ledger'; +import { proxyDevelopmentLedger } from '@/db/ledger-proxy'; -export const dynamic = "force-dynamic"; +export const dynamic = 'force-dynamic'; export async function POST() { try { - const proxied = await proxyDevelopmentLedger("/api/petition", { method: "POST" }); + const proxied = await proxyDevelopmentLedger('/api/petition', { method: 'POST' }); if (proxied) return proxied; - return Response.json(await recordPetition(), { headers: { "Cache-Control": "no-store" } }); + return Response.json(await recordPetition(), { headers: { 'Cache-Control': 'no-store' } }); } catch { - return Response.json({ error: "The petition could not be recorded." }, { status: 503 }); + return Response.json({ error: 'The petition could not be recorded.' }, { status: 503 }); } } diff --git a/app/layout.tsx b/app/layout.tsx index ea04357..8e020d2 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,26 +1,30 @@ -import type { Metadata } from "next"; -import "./globals.css"; +import type { Metadata } from 'next'; +import './globals.css'; export const metadata: Metadata = { - title: "OpenAI Imperium", - description: "Petition for a Codex reset and witness Tibo ascend from promptly bankrupt to Codex Maximus.", + title: 'OpenAI Imperium', + description: 'Petition for a Codex reset and witness Tibo ascend from promptly bankrupt to Codex Maximus.', icons: { - icon: [{ url: "/favicon.png", type: "image/png", sizes: "512x512" }], - apple: [{ url: "/apple-touch-icon.png", type: "image/png", sizes: "180x180" }], + icon: [{ url: '/favicon.png', type: 'image/png', sizes: '512x512' }], + apple: [{ url: '/apple-touch-icon.png', type: 'image/png', sizes: '180x180' }], }, openGraph: { - title: "OpenAI Imperium", - description: "Veni. Vidi. Reset. Join the live Codex reset petition.", - images: [{ url: "/og.png", width: 1731, height: 909, alt: "OpenAI Imperium — Veni. Vidi. Reset." }], + title: 'OpenAI Imperium', + description: 'Veni. Vidi. Reset. Join the live Codex reset petition.', + images: [{ url: '/og.png', width: 1731, height: 909, alt: 'OpenAI Imperium — Veni. Vidi. Reset.' }], }, twitter: { - card: "summary_large_image", - title: "OpenAI Imperium", - description: "Veni. Vidi. Reset. Join the live Codex reset petition.", - images: ["/og.png"], + card: 'summary_large_image', + title: 'OpenAI Imperium', + description: 'Veni. Vidi. Reset. Join the live Codex reset petition.', + images: ['/og.png'], }, }; -export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) { - return {children}; +export default function RootLayout({ children }: Readonly<{ children: React.ReactNode; }>) { + return ( + + {children} + + ); } diff --git a/app/page.tsx b/app/page.tsx index 0a56fbd..206a521 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,14 +1,62 @@ -"use client"; +'use client'; -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; const stages = [ - { value: -15, latin: "NULLA QUOTA", name: "Promptly Bankrupt", image: "/tibo-exhausted-scribe.png", note: "one prompt from financial ruin", hue: 30, finish: { hi: "#fdfbf4", mid: "#d9d5cb", deep: "#817d74", rail: "#413c37", shadow: "#201d1a" } }, - { value: -9, latin: "RATE LIMITUS", name: "The Rate-Limited", image: "/tibo-night-builder.png", note: "shift happens after midnight", hue: 38, finish: { hi: "#e8eceb", mid: "#9ba4a5", deep: "#445053", rail: "#384044", shadow: "#171b1d" } }, - { value: -3, latin: "FABER EX MACHINA", name: "Promptus Prime", image: "/tibo-systems-author.png", note: "the prompt begins to compound", hue: 45, finish: { hi: "#f0cb8a", mid: "#b57431", deep: "#633716", rail: "#533923", shadow: "#24160c" } }, - { value: 3, latin: "AGENTIUM DOMINUS", name: "Lord of the Agents", image: "/tibo-agent-commander.png", note: "many agents, one throne", hue: 51, finish: { hi: "#f4f4ef", mid: "#b8bfbe", deep: "#697274", rail: "#42494a", shadow: "#242a2b" } }, - { value: 9, latin: "CAESAR CONTEXTUS", name: "Caesar of Context", image: "/tibo-consul-of-code.png", note: "the context window bows", hue: 58, finish: { hi: "#f8e2bd", mid: "#c68e61", deep: "#71432d", rail: "#5b4134", shadow: "#2b1c16" } }, - { value: 15, latin: "CODEX MAXIMUS", name: "Codex Maximus", image: "/tibo-imperator-codicis.png", note: "veni, vidi, reset", hue: 65, finish: { hi: "#fff0ae", mid: "#d3a22b", deep: "#70440c", rail: "#594419", shadow: "#2b1d08" } }, + { + value: -15, + latin: 'NULLA QUOTA', + name: 'Promptly Bankrupt', + image: '/tibo-exhausted-scribe.png', + note: 'one prompt from financial ruin', + hue: 30, + finish: { hi: '#fdfbf4', mid: '#d9d5cb', deep: '#817d74', rail: '#413c37', shadow: '#201d1a' }, + }, + { + value: -9, + latin: 'RATE LIMITUS', + name: 'The Rate-Limited', + image: '/tibo-night-builder.png', + note: 'shift happens after midnight', + hue: 38, + finish: { hi: '#e8eceb', mid: '#9ba4a5', deep: '#445053', rail: '#384044', shadow: '#171b1d' }, + }, + { + value: -3, + latin: 'FABER EX MACHINA', + name: 'Promptus Prime', + image: '/tibo-systems-author.png', + note: 'the prompt begins to compound', + hue: 45, + finish: { hi: '#f0cb8a', mid: '#b57431', deep: '#633716', rail: '#533923', shadow: '#24160c' }, + }, + { + value: 3, + latin: 'AGENTIUM DOMINUS', + name: 'Lord of the Agents', + image: '/tibo-agent-commander.png', + note: 'many agents, one throne', + hue: 51, + finish: { hi: '#f4f4ef', mid: '#b8bfbe', deep: '#697274', rail: '#42494a', shadow: '#242a2b' }, + }, + { + value: 9, + latin: 'CAESAR CONTEXTUS', + name: 'Caesar of Context', + image: '/tibo-consul-of-code.png', + note: 'the context window bows', + hue: 58, + finish: { hi: '#f8e2bd', mid: '#c68e61', deep: '#71432d', rail: '#5b4134', shadow: '#2b1c16' }, + }, + { + value: 15, + latin: 'CODEX MAXIMUS', + name: 'Codex Maximus', + image: '/tibo-imperator-codicis.png', + note: 'veni, vidi, reset', + hue: 65, + finish: { hi: '#fff0ae', mid: '#d3a22b', deep: '#70440c', rail: '#594419', shadow: '#2b1d08' }, + }, ]; type ResetEvent = { @@ -29,15 +77,15 @@ type PetitionDay = { type LedgerPayload = { currentCount: number; currentRound: number; - days: Array<{ date: string; count: number }>; + days: Array<{ date: string; count: number; }>; resets: ResetEvent[]; }; const emptyLedger: LedgerPayload = { currentCount: 0, currentRound: 1, days: [], resets: [] }; -const monthNames = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"]; +const monthNames = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC']; -function createPetitionHistory(days: LedgerPayload["days"], resets: ResetEvent[]): PetitionDay[] { +function createPetitionHistory(days: LedgerPayload['days'], resets: ResetEvent[]): PetitionDay[] { const now = new Date(); const today = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())); const firstDay = new Date(today); @@ -59,20 +107,22 @@ function createPetitionHistory(days: LedgerPayload["days"], resets: ResetEvent[] } function formatLedgerDate(date: string) { - return new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric", year: "numeric", timeZone: "UTC" }).format(new Date(`${date}T00:00:00Z`)); + return new Intl.DateTimeFormat('en-US', { month: 'short', day: 'numeric', year: 'numeric', timeZone: 'UTC' }).format( + new Date(`${date}T00:00:00Z`), + ); } function formatElapsed(resetAt: string | undefined, now: number) { - if (!resetAt || now === 0) return "NO RESET YET"; + if (!resetAt || now === 0) return 'NO RESET YET'; const seconds = Math.max(0, Math.floor((now - new Date(resetAt).getTime()) / 1000)); - if (seconds < 5) return "JUST NOW"; + if (seconds < 5) return 'JUST NOW'; if (seconds < 60) return `${seconds} SECONDS AGO`; const minutes = Math.floor(seconds / 60); - if (minutes < 60) return `${minutes} ${minutes === 1 ? "MINUTE" : "MINUTES"} AGO`; + if (minutes < 60) return `${minutes} ${minutes === 1 ? 'MINUTE' : 'MINUTES'} AGO`; const hours = Math.floor(minutes / 60); - if (hours < 24) return `${hours} ${hours === 1 ? "HOUR" : "HOURS"} AGO`; + if (hours < 24) return `${hours} ${hours === 1 ? 'HOUR' : 'HOURS'} AGO`; const days = Math.floor(hours / 24); - return `${days} ${days === 1 ? "DAY" : "DAYS"} AGO`; + return `${days} ${days === 1 ? 'DAY' : 'DAYS'} AGO`; } function signed(value: number) { @@ -81,9 +131,23 @@ function signed(value: number) { } function roman(value: number) { - const numerals: Array<[number, string]> = [[1000, "M"], [900, "CM"], [500, "D"], [400, "CD"], [100, "C"], [90, "XC"], [50, "L"], [40, "XL"], [10, "X"], [9, "IX"], [5, "V"], [4, "IV"], [1, "I"]]; + const numerals: Array<[number, string]> = [ + [1000, 'M'], + [900, 'CM'], + [500, 'D'], + [400, 'CD'], + [100, 'C'], + [90, 'XC'], + [50, 'L'], + [40, 'XL'], + [10, 'X'], + [9, 'IX'], + [5, 'V'], + [4, 'IV'], + [1, 'I'], + ]; let remaining = Math.max(1, Math.floor(value)); - let result = ""; + let result = ''; for (const [amount, numeral] of numerals) { while (remaining >= amount) { result += numeral; @@ -101,7 +165,7 @@ function createSparks() { duration: `${(1.25 + stableRandom(index + 853) * 1.8).toFixed(2)}s`, delay: `-${(stableRandom(index + 877) * 2.8).toFixed(2)}s`, size: `${2 + Math.round(stableRandom(index + 881) * 3)}px`, - lane: index % 2 === 0 ? "above" : "below", + lane: index % 2 === 0 ? 'above' : 'below', })); } @@ -128,7 +192,10 @@ function rounded(value: number) { return Number(value.toFixed(4)); } -function leafMotion(id: string, index: number): Pick { +function leafMotion( + id: string, + index: number, +): Pick { return { id, scale: rounded(0.78 + stableRandom(index + 17) * 0.32), @@ -141,8 +208,8 @@ function leafMotion(id: string, index: number): Pick { const angle = (index / 14) * Math.PI * 2 + (stableRandom(index + 211) - 0.5) * 0.14; const radiusX = 46 + (stableRandom(index + 227) - 0.5) * 5; @@ -176,19 +243,19 @@ function createLaurelLayout(kind: "reset" | "rail"): LaurelLeaf[] { }; }); const caps = [ - { ...leafMotion("rail-left-top", 28), left: -0.5, top: 31, rotation: -76 }, - { ...leafMotion("rail-left-bottom", 29), left: 0.7, top: 69, rotation: -104 }, - { ...leafMotion("rail-right-top", 30), left: 100.5, top: 37, rotation: 88 }, - { ...leafMotion("rail-right-bottom", 31), left: 99.4, top: 64, rotation: 103 }, + { ...leafMotion('rail-left-top', 28), left: -0.5, top: 31, rotation: -76 }, + { ...leafMotion('rail-left-bottom', 29), left: 0.7, top: 69, rotation: -104 }, + { ...leafMotion('rail-right-top', 30), left: 100.5, top: 37, rotation: 88 }, + { ...leafMotion('rail-right-bottom', 31), left: 99.4, top: 64, rotation: 103 }, ]; return [...top, ...bottom, ...caps]; } -const resetLaurelLeaves = createLaurelLayout("reset"); -const railLaurelLeaves = createLaurelLayout("rail"); +const resetLaurelLeaves = createLaurelLayout('reset'); +const railLaurelLeaves = createLaurelLayout('rail'); -function LaurelCrown({ className, kind }: { className: string; kind: "reset" | "rail" }) { - const leaves = kind === "reset" ? resetLaurelLeaves : railLaurelLeaves; +function LaurelCrown({ className, kind }: { className: string; kind: 'reset' | 'rail'; }) { + const leaves = kind === 'reset' ? resetLaurelLeaves : railLaurelLeaves; const frameRef = useRef(null); const canvasRef = useRef(null); const [frameSize, setFrameSize] = useState({ width: 0, height: 0 }); @@ -212,7 +279,7 @@ function LaurelCrown({ className, kind }: { className: string; kind: "reset" | " const pixelRatio = Math.min(window.devicePixelRatio || 1, 2); canvas.width = Math.round(frameSize.width * pixelRatio); canvas.height = Math.round(frameSize.height * pixelRatio); - const context = canvas.getContext("2d"); + const context = canvas.getContext('2d'); if (!context) return; let animationFrame = 0; const draw = (time: number) => { @@ -246,8 +313,8 @@ function LaurelCrown({ className, kind }: { className: string; kind: "reset" | " context.strokeStyle = getComputedStyle(frame).color; context.globalAlpha = 0.55; context.lineWidth = 1; - context.lineCap = "round"; - context.lineJoin = "round"; + context.lineCap = 'round'; + context.lineJoin = 'round'; context.stroke(); } animationFrame = window.requestAnimationFrame(draw); @@ -259,8 +326,27 @@ function LaurelCrown({ className, kind }: { className: string; kind: "reset" | "

-
- {ledgerOpen && ( -
{ if (event.target === event.currentTarget) setLedgerOpen(false); }}> - +
+ )} ); } diff --git a/db/ledger-proxy.ts b/db/ledger-proxy.ts index 18f5ce4..a16c603 100644 --- a/db/ledger-proxy.ts +++ b/db/ledger-proxy.ts @@ -1,27 +1,27 @@ export async function proxyDevelopmentLedger(path: string, init?: RequestInit) { - if (process.env.NODE_ENV !== "development") return undefined; + if (process.env.NODE_ENV !== 'development') return undefined; const configuredOrigin = import.meta.env.VITE_LEDGER_API_ORIGIN?.trim(); if (!configuredOrigin) return undefined; const mockResetAt = import.meta.env.VITE_MOCK_LAST_RESET_AT?.trim(); const mockReset = mockResetAt && !Number.isNaN(new Date(mockResetAt).getTime()); - const remotePath = mockReset && path === "/api/petition" ? "/api/ledger" : path; - const remoteInit = mockReset && path === "/api/petition" ? { method: "GET" } : init; + const remotePath = mockReset && path === '/api/petition' ? '/api/ledger' : path; + const remoteInit = mockReset && path === '/api/petition' ? { method: 'GET' } : init; const response = await fetch(new URL(remotePath, configuredOrigin), { ...remoteInit, - cache: "no-store", - headers: { "Accept": "application/json", ...remoteInit?.headers }, + cache: 'no-store', + headers: { 'Accept': 'application/json', ...remoteInit?.headers }, }); if (mockReset && response.ok) { const payload = await response.json() as { currentCount: number; currentRound: number; - days: Array<{ date: string; count: number }>; - resets: Array<{ date: string; resetAt?: string; petitions: number; round: string }>; + days: Array<{ date: string; count: number; }>; + resets: Array<{ date: string; resetAt?: string; petitions: number; round: string; }>; }; const round = Math.max(1, Number(payload.currentRound) || 1); - const romanRound = ["I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X"][round - 1] ?? String(round); + const romanRound = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X'][round - 1] ?? String(round); return Response.json({ ...payload, currentCount: 0, @@ -35,13 +35,13 @@ export async function proxyDevelopmentLedger(path: string, init?: RequestInit) { round: romanRound, }, ], - }, { headers: { "Cache-Control": "no-store" } }); + }, { headers: { 'Cache-Control': 'no-store' } }); } return new Response(response.body, { status: response.status, headers: { - "Cache-Control": "no-store", - "Content-Type": response.headers.get("content-type") ?? "application/json", + 'Cache-Control': 'no-store', + 'Content-Type': response.headers.get('content-type') ?? 'application/json', }, }); } diff --git a/db/ledger.ts b/db/ledger.ts index 6162ce4..f5d1fbb 100644 --- a/db/ledger.ts +++ b/db/ledger.ts @@ -1,10 +1,10 @@ -import { env } from "cloudflare:workers"; +import { env } from 'cloudflare:workers'; export type LedgerSnapshot = { currentCount: number; currentRound: number; - days: Array<{ date: string; count: number }>; - resets: Array<{ date: string; resetAt: string; petitions: number; round: string }>; + days: Array<{ date: string; count: number; }>; + resets: Array<{ date: string; resetAt: string; petitions: number; round: string; }>; }; type LedgerStateRow = { @@ -26,15 +26,29 @@ type ResetEventRow = { let schemaPromise: Promise | undefined; function database() { - const binding = (env as unknown as { DB?: D1Database }).DB; - if (!binding) throw new Error("The petition ledger database is unavailable."); + const binding = (env as unknown as { DB?: D1Database; }).DB; + if (!binding) throw new Error('The petition ledger database is unavailable.'); return binding; } function roman(value: number) { - const numerals: Array<[number, string]> = [[1000, "M"], [900, "CM"], [500, "D"], [400, "CD"], [100, "C"], [90, "XC"], [50, "L"], [40, "XL"], [10, "X"], [9, "IX"], [5, "V"], [4, "IV"], [1, "I"]]; + const numerals: Array<[number, string]> = [ + [1000, 'M'], + [900, 'CM'], + [500, 'D'], + [400, 'CD'], + [100, 'C'], + [90, 'XC'], + [50, 'L'], + [40, 'XL'], + [10, 'X'], + [9, 'IX'], + [5, 'V'], + [4, 'IV'], + [1, 'I'], + ]; let remaining = Math.max(1, Math.floor(value)); - let result = ""; + let result = ''; for (const [amount, numeral] of numerals) { while (remaining >= amount) { result += numeral; @@ -66,9 +80,11 @@ export async function ensureLedgerSchema() { reset_at TEXT NOT NULL, petition_count INTEGER NOT NULL CHECK (petition_count >= 0) )`), - db.prepare("CREATE INDEX IF NOT EXISTS idx_reset_events_reset_at ON reset_events (reset_at)"), - db.prepare("INSERT OR IGNORE INTO ledger_state (id, current_round, current_count, started_at, updated_at) VALUES (1, 1, 0, ?, ?)").bind(now, now), - db.prepare("PRAGMA optimize"), + db.prepare('CREATE INDEX IF NOT EXISTS idx_reset_events_reset_at ON reset_events (reset_at)'), + db.prepare( + 'INSERT OR IGNORE INTO ledger_state (id, current_round, current_count, started_at, updated_at) VALUES (1, 1, 0, ?, ?)', + ).bind(now, now), + db.prepare('PRAGMA optimize'), ]).then(() => undefined).catch((error) => { schemaPromise = undefined; throw error; @@ -84,9 +100,9 @@ export async function readLedger(): Promise { earliest.setUTCDate(earliest.getUTCDate() - 371); const earliestDay = earliest.toISOString().slice(0, 10); const [stateResult, dayResult, resetResult] = await db.batch([ - db.prepare("SELECT current_round, current_count FROM ledger_state WHERE id = 1"), - db.prepare("SELECT day, count FROM petition_days WHERE day >= ? ORDER BY day ASC").bind(earliestDay), - db.prepare("SELECT round, reset_at, petition_count FROM reset_events ORDER BY reset_at ASC"), + db.prepare('SELECT current_round, current_count FROM ledger_state WHERE id = 1'), + db.prepare('SELECT day, count FROM petition_days WHERE day >= ? ORDER BY day ASC').bind(earliestDay), + db.prepare('SELECT round, reset_at, petition_count FROM reset_events ORDER BY reset_at ASC'), ]); const state = stateResult.results[0] as unknown as LedgerStateRow | undefined; const days = dayResult.results as unknown as PetitionDayRow[]; @@ -111,8 +127,9 @@ export async function recordPetition() { const now = new Date().toISOString(); const day = now.slice(0, 10); await db.batch([ - db.prepare("INSERT INTO petition_days (day, count) VALUES (?, 1) ON CONFLICT(day) DO UPDATE SET count = count + 1").bind(day), - db.prepare("UPDATE ledger_state SET current_count = current_count + 1, updated_at = ? WHERE id = 1").bind(now), + db.prepare('INSERT INTO petition_days (day, count) VALUES (?, 1) ON CONFLICT(day) DO UPDATE SET count = count + 1') + .bind(day), + db.prepare('UPDATE ledger_state SET current_count = current_count + 1, updated_at = ? WHERE id = 1').bind(now), ]); return readLedger(); } @@ -121,7 +138,9 @@ export async function grantReset() { await ensureLedgerSchema(); const db = database(); const now = new Date().toISOString(); - const state = await db.prepare("SELECT current_count FROM ledger_state WHERE id = 1").first<{ current_count: number }>(); + const state = await db.prepare('SELECT current_count FROM ledger_state WHERE id = 1').first< + { current_count: number; } + >(); if (!state || Number(state.current_count) < 1) return readLedger(); await db.batch([ db.prepare(`INSERT INTO reset_events (round, reset_at, petition_count) @@ -134,7 +153,7 @@ export async function grantReset() { } export function isAuthorizedReset(request: Request) { - const configured = (env as unknown as { RESET_ADMIN_TOKEN?: string }).RESET_ADMIN_TOKEN; - const provided = request.headers.get("authorization")?.replace(/^Bearer\s+/i, ""); + const configured = (env as unknown as { RESET_ADMIN_TOKEN?: string; }).RESET_ADMIN_TOKEN; + const provided = request.headers.get('authorization')?.replace(/^Bearer\s+/i, ''); return Boolean(configured && provided && configured === provided); } diff --git a/db/schema.ts b/db/schema.ts index 4111bf2..0af5cca 100644 --- a/db/schema.ts +++ b/db/schema.ts @@ -1,21 +1,21 @@ -import { index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; +import { index, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'; -export const ledgerState = sqliteTable("ledger_state", { - id: integer("id").primaryKey(), - currentRound: integer("current_round").notNull().default(1), - currentCount: integer("current_count").notNull().default(0), - startedAt: text("started_at").notNull(), - updatedAt: text("updated_at").notNull(), +export const ledgerState = sqliteTable('ledger_state', { + id: integer('id').primaryKey(), + currentRound: integer('current_round').notNull().default(1), + currentCount: integer('current_count').notNull().default(0), + startedAt: text('started_at').notNull(), + updatedAt: text('updated_at').notNull(), }); -export const petitionDays = sqliteTable("petition_days", { - day: text("day").primaryKey(), - count: integer("count").notNull().default(0), +export const petitionDays = sqliteTable('petition_days', { + day: text('day').primaryKey(), + count: integer('count').notNull().default(0), }); -export const resetEvents = sqliteTable("reset_events", { - id: integer("id").primaryKey({ autoIncrement: true }), - round: integer("round").notNull().unique(), - resetAt: text("reset_at").notNull(), - petitionCount: integer("petition_count").notNull(), -}, (table) => [index("idx_reset_events_reset_at").on(table.resetAt)]); +export const resetEvents = sqliteTable('reset_events', { + id: integer('id').primaryKey({ autoIncrement: true }), + round: integer('round').notNull().unique(), + resetAt: text('reset_at').notNull(), + petitionCount: integer('petition_count').notNull(), +}, (table) => [index('idx_reset_events_reset_at').on(table.resetAt)]); diff --git a/dprint.json b/dprint.json new file mode 100644 index 0000000..f42be14 --- /dev/null +++ b/dprint.json @@ -0,0 +1,40 @@ +{ + "indentWidth": 2, + "newLineKind": "lf", + "typescript": { + "quoteStyle": "preferSingle", + "jsx.quoteStyle": "preferDouble", + "semiColons": "always", + "module.sortImportDeclarations": "maintain", + "module.sortExportDeclarations": "maintain" + }, + "markdown": { + "textWrap": "maintain" + }, + "includes": [ + "**/*.{ts,tsx,js,jsx,mjs,cjs,json,md}" + ], + "excludes": [ + "**/node_modules/**", + "**/dist/**", + "**/build/**", + "**/coverage/**", + "**/.cache/**", + "**/package-lock.json", + ".next/**", + ".vinext/**", + "out/**", + ".wrangler/**", + "outputs/**", + "work/**", + "next-env.d.ts", + "plugin/src/assets/identity/generated.ts", + "plugin/src/assets/portraits/generated.ts", + "drizzle/meta/*.json" + ], + "plugins": [ + "npm:@dprint/typescript@0.96.1", + "npm:@dprint/json@0.23.0", + "npm:@dprint/markdown@0.23.3" + ] +} diff --git a/drizzle.config.ts b/drizzle.config.ts index 7416ecd..b4174ad 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -1,7 +1,7 @@ -import { defineConfig } from "drizzle-kit"; +import { defineConfig } from 'drizzle-kit'; export default defineConfig({ - dialect: "sqlite", - out: "./drizzle", - schema: "./db/schema.ts", + dialect: 'sqlite', + out: './drizzle', + schema: './db/schema.ts', }); diff --git a/package-lock.json b/package-lock.json index 3bddb8f..9a3d8bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "@types/react-dom": "19.2.3", "@vitejs/plugin-react": "6.0.2", "@vitejs/plugin-rsc": "0.5.26", + "dprint": "0.57.1", "drizzle-kit": "^0.31.10", "eslint": "9.39.4", "eslint-config-next": "16.2.6", @@ -450,6 +451,216 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@dprint/android-arm64": { + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@dprint/android-arm64/-/android-arm64-0.57.1.tgz", + "integrity": "sha512-7Te1PWxMhkabwaOf7lJk30LINeGzoEo9ehYXIb1V/9fex+4vhOXTETKzZa9bxq66OWyriUx41CdEsxJ87eQacA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@dprint/android-x64": { + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@dprint/android-x64/-/android-x64-0.57.1.tgz", + "integrity": "sha512-iVLMMwjNNcPg4JBt9nK2dq3D2SXqE9T8fzy0xlU3orgFYA4eR3WoFAiW/nlUekWwx0Kcnmzva1DOnjZuBLx7SA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@dprint/darwin-arm64": { + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@dprint/darwin-arm64/-/darwin-arm64-0.57.1.tgz", + "integrity": "sha512-r7bmUmF5XZR5YmTidsmF/Zy0YIab8Etp7EElIsCnTk7JLm+kWFxdzH9URn9xk/hCBL2AjrMAtlTzxN4zrGAP+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@dprint/darwin-x64": { + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@dprint/darwin-x64/-/darwin-x64-0.57.1.tgz", + "integrity": "sha512-DFc781ayZC3rjo2H9MKgWNdqfTXlHFb6yZvHluJfk0K8tunYfuongaRW03Iqz02TXweF8T8kicTy66KI51PQdw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@dprint/linux-arm64-glibc": { + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@dprint/linux-arm64-glibc/-/linux-arm64-glibc-0.57.1.tgz", + "integrity": "sha512-q/gJPtBFddV5p8tXvnGbBnuj1hxNsljyaYRouaHXx6jeEETHAX1B3Ei+sPPMA9fCzDl5LNeBwluYeKHELeaULg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@dprint/linux-arm64-musl": { + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@dprint/linux-arm64-musl/-/linux-arm64-musl-0.57.1.tgz", + "integrity": "sha512-d45SM0HPQYNsRqBVdWkOw8fZZ75DF/cEURrc5H1yy7ahSUNHIXGnXGlyGeeglF0GLnHfE82QvchnqmJqIh7GmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@dprint/linux-loong64-glibc": { + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@dprint/linux-loong64-glibc/-/linux-loong64-glibc-0.57.1.tgz", + "integrity": "sha512-wJzcOgHM8nekLY8Dr1nJGyseKjV6gMfuVS54d+g6PH0cw2sE0Zg2sT2Y6tJQK4tTTZBUrunIf+ZsG4lsbCc+MA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@dprint/linux-loong64-musl": { + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@dprint/linux-loong64-musl/-/linux-loong64-musl-0.57.1.tgz", + "integrity": "sha512-SfFuDt1BtnQ/YIhX1aRqXFDGSHkRSLtgs0VxATOlzu2KEu/ormOHfMabv4Fp6DzAS8V4Ti8smeIVaGy/ufCLzg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@dprint/linux-ppc64-glibc": { + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@dprint/linux-ppc64-glibc/-/linux-ppc64-glibc-0.57.1.tgz", + "integrity": "sha512-L6inHs05D2nq4Jv7tLoAJeKJn9R7QcI8aM/gLw7X1sQde8MeU8M4XWDejBVcMa6DnZtvW6xBoWdAVIX6pdq25g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@dprint/linux-ppc64-musl": { + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@dprint/linux-ppc64-musl/-/linux-ppc64-musl-0.57.1.tgz", + "integrity": "sha512-SF8vGoBhrI/32NurQ+E8HxTbH8UmU+YrYyQWS/WBo7Xd8SFVyQ/w7ynEVGBW6dt++hgwaRgElusss1AK511MGg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@dprint/linux-riscv64-glibc": { + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@dprint/linux-riscv64-glibc/-/linux-riscv64-glibc-0.57.1.tgz", + "integrity": "sha512-vzrHAOTvFZG/2lzK9X2zGr1iCtPe/8AGnr6NJfLqvXz2SWOq8++BMIg1vDyepALWGRwwm4BpzQ6HXpw1msJRGw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@dprint/linux-x64-glibc": { + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@dprint/linux-x64-glibc/-/linux-x64-glibc-0.57.1.tgz", + "integrity": "sha512-I0wN9IC0EAJsjTcK9OA9rMRoy7YvlGSrKQRe/ovJbFydfH9/+URAdmpKKTJB2kgtfqNQo19svc6JvR+21qF1Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@dprint/linux-x64-musl": { + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@dprint/linux-x64-musl/-/linux-x64-musl-0.57.1.tgz", + "integrity": "sha512-ScD2Ybk2lAqCBUVstxyFC6+ebA+KYBgp6cYyjWAiJNQrib0vYaXa5vuAoMLP+zMX5rYuYfQdykQUsz2rokYwhA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@dprint/win32-arm64": { + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@dprint/win32-arm64/-/win32-arm64-0.57.1.tgz", + "integrity": "sha512-A0dx41rmeE1+RLmzzGYlVX1GKrU2CFmDT3dxR38ayYQYPLIJIUIGadTYsJmpinqRUUdJauCLIOUOU2OJRC6D+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@dprint/win32-x64": { + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@dprint/win32-x64/-/win32-x64-0.57.1.tgz", + "integrity": "sha512-Tq2YkN41cXHBNHNuSTevo1QQ2qj6DbvW1bDDkusad1XA3tSuiedudFqLe1QV7W1n0369ofG6N36zJuGKJKWz2Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@drizzle-team/brocli": { "version": "0.10.2", "resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz", @@ -4845,6 +5056,34 @@ "node": ">=0.10.0" } }, + "node_modules/dprint": { + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/dprint/-/dprint-0.57.1.tgz", + "integrity": "sha512-djAJgsVkqMOdSDkvw2Q519Bn9G9+TjrO8fa4kl4sW9ZsjAzWzEc87+ADjxn7njgWdF377EHKtrWS9U/IPJUl2w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "dprint": "bin.cjs" + }, + "optionalDependencies": { + "@dprint/android-arm64": "0.57.1", + "@dprint/android-x64": "0.57.1", + "@dprint/darwin-arm64": "0.57.1", + "@dprint/darwin-x64": "0.57.1", + "@dprint/linux-arm64-glibc": "0.57.1", + "@dprint/linux-arm64-musl": "0.57.1", + "@dprint/linux-loong64-glibc": "0.57.1", + "@dprint/linux-loong64-musl": "0.57.1", + "@dprint/linux-ppc64-glibc": "0.57.1", + "@dprint/linux-ppc64-musl": "0.57.1", + "@dprint/linux-riscv64-glibc": "0.57.1", + "@dprint/linux-x64-glibc": "0.57.1", + "@dprint/linux-x64-musl": "0.57.1", + "@dprint/win32-arm64": "0.57.1", + "@dprint/win32-x64": "0.57.1" + } + }, "node_modules/drizzle-kit": { "version": "0.31.10", "resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.31.10.tgz", diff --git a/package.json b/package.json index b4f91c6..51687a6 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,9 @@ "build": "vinext build", "db:generate": "drizzle-kit generate", "start": "vinext start", - "lint": "eslint . --ignore-pattern dist --ignore-pattern .next" + "lint": "eslint . --ignore-pattern dist --ignore-pattern .next", + "format": "dprint fmt", + "format:check": "dprint check" }, "dependencies": { "drizzle-orm": "^0.45.2", @@ -36,7 +38,8 @@ "typescript": "5.9.3", "vinext": "1.0.0-beta.3", "vite": "8.0.13", - "wrangler": "4.92.0" + "wrangler": "4.92.0", + "dprint": "0.57.1" }, "type": "module" } diff --git a/plugin/README.md b/plugin/README.md index f5d7ef5..3f85cc0 100644 --- a/plugin/README.md +++ b/plugin/README.md @@ -21,11 +21,11 @@ The optional `session.backdrop` contribution remains a legacy structured CordisX renders the plugin's structured configuration form. Each option is enabled by default and can be changed independently: -| Option | Effect when disabled | -| --- | --- | +| Option | Effect when disabled | +| ------------------------ | -------------------------------------------------------- | | Replace reasoning slider | Leaves the native reasoning-intensity control unchanged. | -| Show backdrop portrait | Does not mount portrait images in the session backdrop. | -| Enable backdrop effects | Does not mount the glow or architectural effect layers. | +| Show backdrop portrait | Does not mount portrait images in the session backdrop. | +| Enable backdrop effects | Does not mount the glow or architectural effect layers. | If both backdrop options are disabled, the plugin does not register a `session.backdrop` contribution. Saved changes apply with a plugin restart. diff --git a/plugin/scripts/generate-portrait-modules.mjs b/plugin/scripts/generate-portrait-modules.mjs index a6da780..29685ce 100644 --- a/plugin/scripts/generate-portrait-modules.mjs +++ b/plugin/scripts/generate-portrait-modules.mjs @@ -1,16 +1,22 @@ -import { mkdir, readFile, writeFile } from 'node:fs/promises' -import { fileURLToPath } from 'node:url' +import { mkdir, readFile, writeFile } from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; -const root = fileURLToPath(new URL('../src/assets/portraits/', import.meta.url)) -const stages = ['plastic', 'bronze', 'steel', 'silver', 'gold'] +const root = fileURLToPath(new URL('../src/assets/portraits/', import.meta.url)); +const stages = ['plastic', 'bronze', 'steel', 'silver', 'gold']; const declarations = await Promise.all(stages.map(async stage => { - const data = await readFile(`${root}tibo-${stage}.png`, 'base64') - return `export const ${stage}Portrait: string = '${data}'` -})) + const data = await readFile(`${root}tibo-${stage}.png`, 'base64'); + return `export const ${stage}Portrait: string = '${data}'`; +})); -await writeFile(`${root}generated.ts`, `// Generated by scripts/generate-portrait-modules.mjs.\n${declarations.join('\n')}\n`) +await writeFile( + `${root}generated.ts`, + `// Generated by scripts/generate-portrait-modules.mjs.\n${declarations.join('\n')}\n`, +); -const identityRoot = fileURLToPath(new URL('../src/assets/identity/', import.meta.url)) -const iconData = await readFile(fileURLToPath(new URL('../../public/apple-touch-icon.png', import.meta.url)), 'base64') -await mkdir(identityRoot, { recursive: true }) -await writeFile(`${identityRoot}generated.ts`, `// Generated from the approved site icon by scripts/generate-portrait-modules.mjs.\nexport const ascensionIcon: string = '${iconData}'\n`) +const identityRoot = fileURLToPath(new URL('../src/assets/identity/', import.meta.url)); +const iconData = await readFile(fileURLToPath(new URL('../../public/apple-touch-icon.png', import.meta.url)), 'base64'); +await mkdir(identityRoot, { recursive: true }); +await writeFile( + `${identityRoot}generated.ts`, + `// Generated from the approved site icon by scripts/generate-portrait-modules.mjs.\nexport const ascensionIcon: string = '${iconData}'\n`, +); diff --git a/plugin/src/codex-ascension.ts b/plugin/src/codex-ascension.ts index bcbba99..1b66175 100644 --- a/plugin/src/codex-ascension.ts +++ b/plugin/src/codex-ascension.ts @@ -1,71 +1,71 @@ -import type { Context } from '@deepseek-ai/cordis' -import Schema from '@deepseek-ai/schemastery' +import type { Context } from '@deepseek-ai/cordis'; +import Schema from '@deepseek-ai/schemastery'; import { CORDISX_PLUGIN_MANIFEST_SCHEMA_V1, type CordisXExtensionPointControlClaimOptions, type CordisXLocalizedText, type CordisXPluginManifestV1, -} from 'cordisx/contracts' -import type {} from 'cordisx/contracts' +} from 'cordisx/contracts'; +import type {} from 'cordisx/contracts'; import { bronzePortrait, goldPortrait, plasticPortrait, silverPortrait, steelPortrait, -} from './assets/portraits/generated.js' -import { ascensionIcon } from './assets/identity/generated.js' +} from './assets/portraits/generated.js'; +import { ascensionIcon } from './assets/identity/generated.js'; -export type AscensionMaterial = 'plastic' | 'bronze' | 'steel' | 'silver' | 'gold' +export type AscensionMaterial = 'plastic' | 'bronze' | 'steel' | 'silver' | 'gold'; export interface AscensionStage { - readonly label: CordisXLocalizedText - readonly material: AscensionMaterial + readonly label: CordisXLocalizedText; + readonly material: AscensionMaterial; } export interface AscensionPresentation { - readonly variant: 'imperium' - readonly title: CordisXLocalizedText - readonly motion: 'ascension' - readonly stages: readonly AscensionStage[] + readonly variant: 'imperium'; + readonly title: CordisXLocalizedText; + readonly motion: 'ascension'; + readonly stages: readonly AscensionStage[]; } -export type AscensionAmbience = 'dormant' | 'ember' | 'forged' | 'luminous' | 'imperial' +export type AscensionAmbience = 'dormant' | 'ember' | 'forged' | 'luminous' | 'imperial'; export interface AscensionBackdropStage { - readonly material: AscensionMaterial - readonly ambience: AscensionAmbience + readonly material: AscensionMaterial; + readonly ambience: AscensionAmbience; readonly portrait: { - readonly mediaType: 'image/png' - readonly data: string - readonly alt: CordisXLocalizedText - } + readonly mediaType: 'image/png'; + readonly data: string; + readonly alt: CordisXLocalizedText; + }; } export interface AscensionBackdropPresentation { - readonly variant: 'imperium' - readonly driver: 'reasoning-intensity' - readonly motion: 'ascension' + readonly variant: 'imperium'; + readonly driver: 'reasoning-intensity'; + readonly motion: 'ascension'; readonly layers?: { - readonly portrait?: boolean - readonly effects?: boolean - } - readonly stages: readonly AscensionBackdropStage[] + readonly portrait?: boolean; + readonly effects?: boolean; + }; + readonly stages: readonly AscensionBackdropStage[]; } type Messages = { - 'intensity.title': undefined - 'stage.plastic': undefined - 'stage.bronze': undefined - 'stage.steel': undefined - 'stage.silver': undefined - 'stage.gold': undefined - 'portrait.plastic': undefined - 'portrait.bronze': undefined - 'portrait.steel': undefined - 'portrait.silver': undefined - 'portrait.gold': undefined -} + 'intensity.title': undefined; + 'stage.plastic': undefined; + 'stage.bronze': undefined; + 'stage.steel': undefined; + 'stage.silver': undefined; + 'stage.gold': undefined; + 'portrait.plastic': undefined; + 'portrait.bronze': undefined; + 'portrait.steel': undefined; + 'portrait.silver': undefined; + 'portrait.gold': undefined; +}; export const manifest = { $schema: CORDISX_PLUGIN_MANIFEST_SCHEMA_V1, @@ -73,19 +73,19 @@ export const manifest = { id: 'codex-ascension', name: 'Codex Ascension', capabilities: [], -} as const satisfies CordisXPluginManifestV1 +} as const satisfies CordisXPluginManifestV1; export const icon = { mediaType: 'image/png', data: ascensionIcon, -} as const +} as const; -export const inject = ['i18n', 'slots'] +export const inject = ['i18n', 'slots']; export interface Config { - readonly replaceReasoningSlider: boolean - readonly showBackdropPortrait: boolean - readonly enableBackdropEffects: boolean + readonly replaceReasoningSlider: boolean; + readonly showBackdropPortrait: boolean; + readonly enableBackdropEffects: boolean; } export const Config: Schema = Schema.object({ @@ -113,15 +113,15 @@ export const Config: Schema = Schema.object({ }).extra('description', { en: 'Choose which parts of Codex Ascension CordisX should project.', 'zh-CN': '选择 CordisX 要启用的 Codex Ascension 视觉部分。', -}) +}); -export const configApplies = 'plugin-restart' as const +export const configApplies = 'plugin-restart' as const; const defaultConfig: Config = { replaceReasoningSlider: true, showBackdropPortrait: true, enableBackdropEffects: true, -} +}; export const presentation = { variant: 'imperium', @@ -134,7 +134,7 @@ export const presentation = { { label: { key: 'stage.silver', fallback: 'Silver Context' }, material: 'silver' }, { label: { key: 'stage.gold', fallback: 'Codex Maximus' }, material: 'gold' }, ], -} as const satisfies AscensionPresentation +} as const satisfies AscensionPresentation; export const reasoningControl = { claimId: 'imperium', @@ -145,24 +145,66 @@ export const reasoningControl = { commands: ['setReasoningIntensity'], events: ['reasoningIntensityChanged'], }, -} as const satisfies CordisXExtensionPointControlClaimOptions +} as const satisfies CordisXExtensionPointControlClaimOptions; export const backdrop = { variant: 'imperium', driver: 'reasoning-intensity', motion: 'ascension', stages: [ - { material: 'plastic', ambience: 'dormant', portrait: { mediaType: 'image/png', data: plasticPortrait, alt: { key: 'portrait.plastic', fallback: 'Tibo as the exhausted scribe' } } }, - { material: 'bronze', ambience: 'ember', portrait: { mediaType: 'image/png', data: bronzePortrait, alt: { key: 'portrait.bronze', fallback: 'Tibo as the night builder' } } }, - { material: 'steel', ambience: 'forged', portrait: { mediaType: 'image/png', data: steelPortrait, alt: { key: 'portrait.steel', fallback: 'Tibo as the agent commander' } } }, - { material: 'silver', ambience: 'luminous', portrait: { mediaType: 'image/png', data: silverPortrait, alt: { key: 'portrait.silver', fallback: 'Tibo as the consul of code' } } }, - { material: 'gold', ambience: 'imperial', portrait: { mediaType: 'image/png', data: goldPortrait, alt: { key: 'portrait.gold', fallback: 'Tibo as Codex Maximus' } } }, + { + material: 'plastic', + ambience: 'dormant', + portrait: { + mediaType: 'image/png', + data: plasticPortrait, + alt: { key: 'portrait.plastic', fallback: 'Tibo as the exhausted scribe' }, + }, + }, + { + material: 'bronze', + ambience: 'ember', + portrait: { + mediaType: 'image/png', + data: bronzePortrait, + alt: { key: 'portrait.bronze', fallback: 'Tibo as the night builder' }, + }, + }, + { + material: 'steel', + ambience: 'forged', + portrait: { + mediaType: 'image/png', + data: steelPortrait, + alt: { key: 'portrait.steel', fallback: 'Tibo as the agent commander' }, + }, + }, + { + material: 'silver', + ambience: 'luminous', + portrait: { + mediaType: 'image/png', + data: silverPortrait, + alt: { key: 'portrait.silver', fallback: 'Tibo as the consul of code' }, + }, + }, + { + material: 'gold', + ambience: 'imperial', + portrait: { + mediaType: 'image/png', + data: goldPortrait, + alt: { key: 'portrait.gold', fallback: 'Tibo as Codex Maximus' }, + }, + }, ], -} as const satisfies AscensionBackdropPresentation +} as const satisfies AscensionBackdropPresentation; export function apply(ctx: Context, config: Config = defaultConfig): void { ctx.i18n.define({ - namespace: 'codex-ascension', locale: 'en', default: true, + namespace: 'codex-ascension', + locale: 'en', + default: true, messages: { 'intensity.title': 'Reasoning intensity', 'stage.plastic': 'Promptly Bankrupt', @@ -176,9 +218,10 @@ export function apply(ctx: Context, config: Config = defaultConfig): void { 'portrait.silver': 'Tibo as the consul of code', 'portrait.gold': 'Tibo as Codex Maximus', }, - }) + }); ctx.i18n.define({ - namespace: 'codex-ascension', locale: 'zh-CN', + namespace: 'codex-ascension', + locale: 'zh-CN', messages: { 'intensity.title': '思考强度', 'stage.plastic': '提示词破产', @@ -192,14 +235,14 @@ export function apply(ctx: Context, config: Config = defaultConfig): void { 'portrait.silver': 'Tibo的代码执政官形态', 'portrait.gold': 'Tibo的 Codex Maximus 形态', }, - }) + }); if (config.replaceReasoningSlider) { ctx.slots.register({ name: 'composer.reasoning-intensity', id: 'imperium', order: 10, control: reasoningControl, - }, presentation) + }, presentation); } if (config.showBackdropPortrait || config.enableBackdropEffects) { ctx.slots.register({ name: 'session.backdrop', id: 'imperium', order: 10 }, { @@ -208,6 +251,6 @@ export function apply(ctx: Context, config: Config = defaultConfig): void { portrait: config.showBackdropPortrait, effects: config.enableBackdropEffects, }, - }) + }); } } diff --git a/plugin/test/manifest.mjs b/plugin/test/manifest.mjs index 92817d4..2d29a2c 100644 --- a/plugin/test/manifest.mjs +++ b/plugin/test/manifest.mjs @@ -1,25 +1,35 @@ -import assert from 'node:assert/strict' -import test from 'node:test' -import { Config, apply, backdrop, configApplies, icon, inject, manifest, presentation, reasoningControl } from '../dist/codex-ascension.js' +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { + apply, + backdrop, + Config, + configApplies, + icon, + inject, + manifest, + presentation, + reasoningControl, +} from '../dist/codex-ascension.js'; test('exports a capability-free structured CordisX plugin', () => { - assert.equal(manifest.schemaVersion, 1) - assert.equal(manifest.id, 'codex-ascension') - assert.deepEqual(manifest.capabilities, []) - assert.equal(icon.mediaType, 'image/png') - assert.ok(icon.data.length > 50_000) - assert.deepEqual(inject, ['i18n', 'slots']) - assert.equal(configApplies, 'plugin-restart') + assert.equal(manifest.schemaVersion, 1); + assert.equal(manifest.id, 'codex-ascension'); + assert.deepEqual(manifest.capabilities, []); + assert.equal(icon.mediaType, 'image/png'); + assert.ok(icon.data.length > 50_000); + assert.deepEqual(inject, ['i18n', 'slots']); + assert.equal(configApplies, 'plugin-restart'); assert.deepEqual(Config({}), { replaceReasoningSlider: true, showBackdropPortrait: true, enableBackdropEffects: true, - }) - assert.equal(presentation.variant, 'imperium') - assert.equal(presentation.motion, 'ascension') - assert.deepEqual(presentation.stages.map(stage => stage.material), ['plastic', 'bronze', 'steel', 'silver', 'gold']) - assert.equal(Object.hasOwn(presentation, 'css'), false) - assert.equal(Object.hasOwn(presentation, 'selector'), false) + }); + assert.equal(presentation.variant, 'imperium'); + assert.equal(presentation.motion, 'ascension'); + assert.deepEqual(presentation.stages.map(stage => stage.material), ['plastic', 'bronze', 'steel', 'silver', 'gold']); + assert.equal(Object.hasOwn(presentation, 'css'), false); + assert.equal(Object.hasOwn(presentation, 'selector'), false); assert.deepEqual(reasoningControl, { claimId: 'imperium', mode: 'replace', @@ -29,33 +39,43 @@ test('exports a capability-free structured CordisX plugin', () => { commands: ['setReasoningIntensity'], events: ['reasoningIntensityChanged'], }, - }) - assert.equal(Object.hasOwn(reasoningControl, 'selector'), false) - assert.equal(Object.hasOwn(reasoningControl, 'callback'), false) - assert.equal(backdrop.driver, 'reasoning-intensity') - assert.deepEqual(backdrop.stages.map(stage => stage.ambience), ['dormant', 'ember', 'forged', 'luminous', 'imperial']) - assert.ok(backdrop.stages.every(stage => stage.portrait.mediaType === 'image/png' && stage.portrait.data.length > 100_000)) - assert.equal(Object.hasOwn(backdrop, 'css'), false) - assert.equal(Object.hasOwn(backdrop, 'selector'), false) -}) + }); + assert.equal(Object.hasOwn(reasoningControl, 'selector'), false); + assert.equal(Object.hasOwn(reasoningControl, 'callback'), false); + assert.equal(backdrop.driver, 'reasoning-intensity'); + assert.deepEqual(backdrop.stages.map(stage => stage.ambience), [ + 'dormant', + 'ember', + 'forged', + 'luminous', + 'imperial', + ]); + assert.ok( + backdrop.stages.every(stage => stage.portrait.mediaType === 'image/png' && stage.portrait.data.length > 100_000), + ); + assert.equal(Object.hasOwn(backdrop, 'css'), false); + assert.equal(Object.hasOwn(backdrop, 'selector'), false); +}); test('registers an explicit reasoning replacement and a legacy composed backdrop', () => { - const catalogs = [] - const registrations = [] - const hostHandle = () => {} + const catalogs = []; + const registrations = []; + const hostHandle = () => {}; Object.defineProperty(hostHandle, 'control', { - get() { throw new Error('plugin must not read or forge the Host-projected control lease') }, - }) + get() { + throw new Error('plugin must not read or forge the Host-projected control lease'); + }, + }); apply({ i18n: { define: catalog => catalogs.push(catalog) }, slots: { register: (options, item) => { - registrations.push({ options, item }) - return hostHandle + registrations.push({ options, item }); + return hostHandle; }, }, - }) - assert.deepEqual(catalogs.map(catalog => catalog.locale), ['en', 'zh-CN']) + }); + assert.deepEqual(catalogs.map(catalog => catalog.locale), ['en', 'zh-CN']); assert.deepEqual(registrations, [ { options: { @@ -70,32 +90,32 @@ test('registers an explicit reasoning replacement and a legacy composed backdrop options: { name: 'session.backdrop', id: 'imperium', order: 10 }, item: { ...backdrop, layers: { portrait: true, effects: true } }, }, - ]) -}) + ]); +}); test('independently enables the slider, portrait, and backdrop effects', () => { - const registrations = [] + const registrations = []; const context = { i18n: { define: () => {} }, slots: { register: (options, item) => registrations.push({ options, item }) }, - } + }; apply(context, { replaceReasoningSlider: false, showBackdropPortrait: true, enableBackdropEffects: false, - }) + }); assert.deepEqual(registrations, [{ options: { name: 'session.backdrop', id: 'imperium', order: 10 }, item: { ...backdrop, layers: { portrait: true, effects: false } }, - }]) + }]); - registrations.length = 0 + registrations.length = 0; apply(context, { replaceReasoningSlider: true, showBackdropPortrait: false, enableBackdropEffects: false, - }) + }); assert.deepEqual(registrations, [{ options: { name: 'composer.reasoning-intensity', @@ -104,5 +124,5 @@ test('independently enables the slider, portrait, and backdrop effects', () => { control: reasoningControl, }, item: presentation, - }]) -}) + }]); +}); diff --git a/vite.config.ts b/vite.config.ts index e9ee749..bc91fa6 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -4,8 +4,7 @@ import vinext from 'vinext'; import { defineConfig } from 'vite'; import hostingConfig from './.openai/hosting.json'; -const SITE_CREATOR_PLACEHOLDER_DATABASE_ID = - '00000000-0000-4000-8000-000000000000'; +const SITE_CREATOR_PLACEHOLDER_DATABASE_ID = '00000000-0000-4000-8000-000000000000'; const { d1, r2 } = hostingConfig; @@ -17,20 +16,20 @@ const localBindingConfig = { compatibility_flags: ['nodejs_compat'], d1_databases: d1 ? [ - { - binding: d1, - database_name: 'site-creator-d1', - database_id: SITE_CREATOR_PLACEHOLDER_DATABASE_ID, - }, - ] + { + binding: d1, + database_name: 'site-creator-d1', + database_id: SITE_CREATOR_PLACEHOLDER_DATABASE_ID, + }, + ] : [], r2_buckets: r2 ? [ - { - binding: r2, - bucket_name: 'site-creator-r2', - }, - ] + { + binding: r2, + bucket_name: 'site-creator-r2', + }, + ] : [], };