diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index fd8afca..67f111b 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -16,5 +16,5 @@ jobs: cache: npm - run: npm ci - run: npm run verify - - run: git diff --exit-code -- packages/orbit-tags/runtime/dist/injected.js + - run: git diff --exit-code -- packages/orbit-tags/runtime/dist/injected.js packages/orbit-blur/runtime/dist/injected.js - run: npm run test:package diff --git a/docs/source-layout.md b/docs/source-layout.md index d1ed2d5..5724780 100644 --- a/docs/source-layout.md +++ b/docs/source-layout.md @@ -1,6 +1,6 @@ # Source structure and dependency rules -OrbitAI uses two independently versioned packages. Directory boundaries describe execution environments and ownership; they are not additional npm packages. `npm run check` validates imports using a TypeScript-capable AST parser and rejects runtime import cycles. +OrbitAI uses independently versioned packages. Directory boundaries describe execution environments and ownership; they are not additional npm packages. `npm run check` validates imports using a TypeScript-capable AST parser and rejects runtime import cycles. ## Orbit @@ -63,9 +63,21 @@ packages/orbit-tags/ Orbit snapshots the business package without flattening its compiled hierarchy. The plugin manifest loads `dist/runtime/src/service/activate.js`; the diagnostic controller does not write platform configuration. Tags uses module ID `orbit-tags`; its package snapshot records the assigned data directory. See [installation ownership](orbit-platform-design.md). +## Orbit Blur + +```text +packages/orbit-blur/ +├── src/ # Renderer TypeScript (style injection) +├── scripts/build.mjs # esbuild IIFE → runtime/dist/injected.js +├── runtime/dist/injected.js # Tracked browser bundle +└── orbit-plugin.json # Manifest (renderer-only, no service) +``` + +Renderer-only plugin: transparent backgrounds plus `backdrop-filter` blur. It imports type-only `@c0sc0s/orbit/sdk`. No private Codex selectors and no Orbit implementation imports. + ## Verification and change discipline -- `npm run build`: clean only each package's generated Node directory, build Orbit first, then Tags and its browser bundle. +- `npm run build`: clean only each package's generated Node directory, build Orbit first, then Tags, then Blur and its browser bundle. - `npm run check`: syntax, package boundaries, source-layer dependencies, runtime cycles and documentation links. - `npm run typecheck`: strict implementation checks and public SDK contract tests. - Browser typechecks explicitly exclude Node ambient types, preventing accidental use of Node globals in renderer code. diff --git a/package-lock.json b/package-lock.json index cd5c681..e2db43c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -508,6 +508,10 @@ "resolved": "packages/orbit", "link": true }, + "node_modules/@c0sc0s/orbit-blur": { + "resolved": "packages/orbit-blur", + "link": true + }, "node_modules/@c0sc0s/orbit-tags": { "resolved": "packages/orbit-tags", "link": true @@ -11305,6 +11309,19 @@ "node": ">=22" } }, + "packages/orbit-blur": { + "name": "@c0sc0s/orbit-blur", + "version": "0.1.0", + "license": "UNLICENSED", + "devDependencies": { + "@c0sc0s/orbit": "0.5.2", + "esbuild": "0.28.2", + "typescript": "7.0.2" + }, + "engines": { + "node": ">=22" + } + }, "packages/orbit-tags": { "name": "@c0sc0s/orbit-tags", "version": "0.9.0", diff --git a/package.json b/package.json index b1883f7..bdc0933 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "node": ">=22.13.0" }, "scripts": { - "build": "npm run build -w @c0sc0s/orbit && npm run build -w @c0sc0s/orbit-tags", + "build": "npm run build -w @c0sc0s/orbit && npm run build -w @c0sc0s/orbit-tags && npm run build -w @c0sc0s/orbit-blur", "check": "node scripts/check.mjs && node scripts/check-architecture.mjs", "typecheck": "npm run typecheck --workspaces --if-present", "test": "node --test scripts/test/*.test.mjs && npm test --workspaces --if-present", diff --git a/packages/orbit-blur/README.md b/packages/orbit-blur/README.md new file mode 100644 index 0000000..f14746a --- /dev/null +++ b/packages/orbit-blur/README.md @@ -0,0 +1,11 @@ +# @c0sc0s/orbit-blur + +Renderer-only Orbit plugin. Injects transparent page backgrounds and `backdrop-filter` Gaussian blur on Codex chrome. + +```sh +npm run build -w @c0sc0s/orbit-blur +orbit plugin add ./packages/orbit-blur +orbit plugin enable orbit-blur +``` + +Bump `runtimeVersion` in `orbit-plugin.json` (and `RUNTIME_VERSION` in source) after injected changes, then rebuild and re-add/apply. The tracked bundle is `runtime/dist/injected.js`. diff --git a/packages/orbit-blur/orbit-plugin.json b/packages/orbit-blur/orbit-plugin.json new file mode 100644 index 0000000..9b04fe5 --- /dev/null +++ b/packages/orbit-blur/orbit-plugin.json @@ -0,0 +1,8 @@ +{ + "schemaVersion": 1, + "id": "orbit-blur", + "version": "0.1.0", + "runtimeVersion": "1.0.0", + "orbit": { "min": "0.5.0", "maxExclusive": "0.6.0" }, + "renderer": "runtime/dist/injected.js" +} diff --git a/packages/orbit-blur/package.json b/packages/orbit-blur/package.json new file mode 100644 index 0000000..0787bc9 --- /dev/null +++ b/packages/orbit-blur/package.json @@ -0,0 +1,34 @@ +{ + "name": "@c0sc0s/orbit-blur", + "version": "0.1.0", + "description": "Orbit plugin: transparent Codex chrome with Gaussian blur.", + "type": "module", + "files": [ + "orbit-plugin.json", + "runtime/dist/", + "README.md" + ], + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org" + }, + "license": "UNLICENSED", + "repository": { + "type": "git", + "url": "git+https://github.com/c0sc0s/codex-tags.git" + }, + "scripts": { + "build": "node scripts/build.mjs", + "prepack": "npm run build", + "test": "npm run build && node --test test/*.test.mjs", + "typecheck": "tsc --noEmit -p tsconfig.json" + }, + "engines": { + "node": ">=22" + }, + "devDependencies": { + "@c0sc0s/orbit": "0.5.2", + "esbuild": "0.28.2", + "typescript": "7.0.2" + } +} diff --git a/packages/orbit-blur/runtime/dist/injected.js b/packages/orbit-blur/runtime/dist/injected.js new file mode 100644 index 0000000..afe5405 --- /dev/null +++ b/packages/orbit-blur/runtime/dist/injected.js @@ -0,0 +1,125 @@ +"use strict"; +var CodexPlugin = (() => { + var __defProp = Object.defineProperty; + var __getOwnPropDesc = Object.getOwnPropertyDescriptor; + var __getOwnPropNames = Object.getOwnPropertyNames; + var __hasOwnProp = Object.prototype.hasOwnProperty; + var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); + }; + var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; + }; + var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + + // src/entry.ts + var entry_exports = {}; + __export(entry_exports, { + activate: () => activate + }); + + // src/constants.ts + var RUNTIME_VERSION = "1.0.0"; + var PLUGIN_ID = "orbit-blur"; + var STYLE_ID = "orbit-blur-style"; + var DEFAULT_BLUR_PX = 18; + var DEFAULT_TINT_OPACITY = 0.42; + + // src/styles.ts + function clamp(value, min, max) { + return Math.min(max, Math.max(min, value)); + } + function numberOption(value, fallback) { + return typeof value === "number" && Number.isFinite(value) ? value : fallback; + } + function resolveBlurSettings(config = {}) { + return { + blurPx: clamp(numberOption(config.blurPx, DEFAULT_BLUR_PX), 0, 64), + tintOpacity: clamp(numberOption(config.tintOpacity, DEFAULT_TINT_OPACITY), 0, 1) + }; + } + function buildBlurStyles({ blurPx, tintOpacity }) { + const tintPercent = Math.round(tintOpacity * 100); + return ` +:root { + --orbit-blur-px: ${blurPx}px; + --orbit-blur-tint: color-mix(in srgb, Canvas ${tintPercent}%, transparent); + --orbit-blur-shell: color-mix(in srgb, Canvas ${Math.max(12, Math.round(tintPercent * 0.55))}%, transparent); +} + +html, body { + background: transparent !important; + background-color: transparent !important; +} + +:root { + --color-background: transparent; + --color-background-primary: transparent; + --color-bg: transparent; + --color-bg-primary: transparent; + --background: transparent; + --color-token-background: transparent; +} + +aside, +nav, +header, +[role="navigation"], +[role="banner"], +[role="complementary"] { + background-color: var(--orbit-blur-tint) !important; + backdrop-filter: blur(var(--orbit-blur-px)) saturate(1.15); + -webkit-backdrop-filter: blur(var(--orbit-blur-px)) saturate(1.15); +} + +#root, +#app, +body > div:first-of-type { + background-color: var(--orbit-blur-shell) !important; + backdrop-filter: blur(var(--orbit-blur-px)); + -webkit-backdrop-filter: blur(var(--orbit-blur-px)); +} + +[role="dialog"], +[aria-modal="true"] { + backdrop-filter: blur(calc(var(--orbit-blur-px) * 0.65)) saturate(1.1); + -webkit-backdrop-filter: blur(calc(var(--orbit-blur-px) * 0.65)) saturate(1.1); +} +`.trim(); + } + + // src/entry.ts + function ensureStyleElement() { + const existing = document.getElementById(STYLE_ID); + if (existing instanceof HTMLStyleElement) return existing; + const style = document.createElement("style"); + style.id = STYLE_ID; + (document.head ?? document.documentElement).appendChild(style); + return style; + } + async function activate(context) { + const settings = resolveBlurSettings(context.config); + const style = ensureStyleElement(); + style.textContent = buildBlurStyles(settings); + context.onDispose(() => { + document.getElementById(STYLE_ID)?.remove(); + }); + return { + isActive: () => Boolean(document.getElementById(STYLE_ID)), + status: () => ({ + plugin: PLUGIN_ID, + runtimeVersion: RUNTIME_VERSION, + active: Boolean(document.getElementById(STYLE_ID)), + blurPx: settings.blurPx, + tintOpacity: settings.tintOpacity + }) + }; + } + return __toCommonJS(entry_exports); +})(); diff --git a/packages/orbit-blur/scripts/build.mjs b/packages/orbit-blur/scripts/build.mjs new file mode 100644 index 0000000..67d9b50 --- /dev/null +++ b/packages/orbit-blur/scripts/build.mjs @@ -0,0 +1,36 @@ +import { build } from "esbuild"; +import { mkdir } from "node:fs/promises"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const projectRoot = dirname(dirname(fileURLToPath(import.meta.url))); +const outputDirectory = join(projectRoot, "runtime", "dist"); +const testOutputDirectory = join(projectRoot, "dist"); +await mkdir(outputDirectory, { recursive: true }); +await mkdir(testOutputDirectory, { recursive: true }); + +await build({ + entryPoints: [join(projectRoot, "src", "entry.ts")], + outfile: join(outputDirectory, "injected.js"), + bundle: true, + format: "iife", + globalName: "CodexPlugin", + platform: "browser", + target: "chrome120", + minify: false, + sourcemap: false, + legalComments: "none", +}); + +// ESM helpers for Node tests (gitignored under packages/*/dist/). +await build({ + entryPoints: [join(projectRoot, "src", "styles.ts")], + outfile: join(testOutputDirectory, "styles.mjs"), + bundle: true, + format: "esm", + platform: "neutral", + target: "es2022", + minify: false, + sourcemap: false, + legalComments: "none", +}); diff --git a/packages/orbit-blur/src/constants.ts b/packages/orbit-blur/src/constants.ts new file mode 100644 index 0000000..da9f68c --- /dev/null +++ b/packages/orbit-blur/src/constants.ts @@ -0,0 +1,9 @@ +/** Injected bundle cachebuster; keep in sync with orbit-plugin.json runtimeVersion. */ +export const RUNTIME_VERSION = "1.0.0"; +export const PLUGIN_ID = "orbit-blur"; +export const STYLE_ID = "orbit-blur-style"; + +/** Soft glass: enough blur to read as frosted without smearing text. */ +export const DEFAULT_BLUR_PX = 18; +/** Canvas tint opacity for frosted chrome (0–1). */ +export const DEFAULT_TINT_OPACITY = 0.42; diff --git a/packages/orbit-blur/src/entry.ts b/packages/orbit-blur/src/entry.ts new file mode 100644 index 0000000..0666b66 --- /dev/null +++ b/packages/orbit-blur/src/entry.ts @@ -0,0 +1,31 @@ +import type { RendererContext, RendererPlugin } from "@c0sc0s/orbit/sdk"; +import { PLUGIN_ID, RUNTIME_VERSION, STYLE_ID } from "./constants.js"; +import { buildBlurStyles, resolveBlurSettings } from "./styles.js"; + +function ensureStyleElement(): HTMLStyleElement { + const existing = document.getElementById(STYLE_ID); + if (existing instanceof HTMLStyleElement) return existing; + const style = document.createElement("style"); + style.id = STYLE_ID; + (document.head ?? document.documentElement).appendChild(style); + return style; +} + +export async function activate(context: RendererContext): Promise { + const settings = resolveBlurSettings(context.config); + const style = ensureStyleElement(); + style.textContent = buildBlurStyles(settings); + context.onDispose(() => { + document.getElementById(STYLE_ID)?.remove(); + }); + return { + isActive: () => Boolean(document.getElementById(STYLE_ID)), + status: () => ({ + plugin: PLUGIN_ID, + runtimeVersion: RUNTIME_VERSION, + active: Boolean(document.getElementById(STYLE_ID)), + blurPx: settings.blurPx, + tintOpacity: settings.tintOpacity, + }), + }; +} diff --git a/packages/orbit-blur/src/styles.ts b/packages/orbit-blur/src/styles.ts new file mode 100644 index 0000000..8e7c6cf --- /dev/null +++ b/packages/orbit-blur/src/styles.ts @@ -0,0 +1,80 @@ +import { + DEFAULT_BLUR_PX, + DEFAULT_TINT_OPACITY, +} from "./constants.js"; + +export { DEFAULT_BLUR_PX, DEFAULT_TINT_OPACITY, RUNTIME_VERSION, PLUGIN_ID, STYLE_ID } from "./constants.js"; + +export interface BlurSettings { + blurPx: number; + tintOpacity: number; +} + +function clamp(value: number, min: number, max: number): number { + return Math.min(max, Math.max(min, value)); +} + +function numberOption(value: unknown, fallback: number): number { + return typeof value === "number" && Number.isFinite(value) ? value : fallback; +} + +/** Resolve plugin config from Orbit loader entry; unknown keys are ignored. */ +export function resolveBlurSettings(config: Record = {}): BlurSettings { + return { + blurPx: clamp(numberOption(config.blurPx, DEFAULT_BLUR_PX), 0, 64), + tintOpacity: clamp(numberOption(config.tintOpacity, DEFAULT_TINT_OPACITY), 0, 1), + }; +} + +export function buildBlurStyles({ blurPx, tintOpacity }: BlurSettings): string { + const tintPercent = Math.round(tintOpacity * 100); + // Landmark/role selectors only — no private Codex DOM attributes (those stay in orbit-tags). + // root transparency helps when the Electron host is already translucent; frosted chrome + // still blurs in-app content underneath via backdrop-filter either way. + return ` +:root { + --orbit-blur-px: ${blurPx}px; + --orbit-blur-tint: color-mix(in srgb, Canvas ${tintPercent}%, transparent); + --orbit-blur-shell: color-mix(in srgb, Canvas ${Math.max(12, Math.round(tintPercent * 0.55))}%, transparent); +} + +html, body { + background: transparent !important; + background-color: transparent !important; +} + +:root { + --color-background: transparent; + --color-background-primary: transparent; + --color-bg: transparent; + --color-bg-primary: transparent; + --background: transparent; + --color-token-background: transparent; +} + +aside, +nav, +header, +[role="navigation"], +[role="banner"], +[role="complementary"] { + background-color: var(--orbit-blur-tint) !important; + backdrop-filter: blur(var(--orbit-blur-px)) saturate(1.15); + -webkit-backdrop-filter: blur(var(--orbit-blur-px)) saturate(1.15); +} + +#root, +#app, +body > div:first-of-type { + background-color: var(--orbit-blur-shell) !important; + backdrop-filter: blur(var(--orbit-blur-px)); + -webkit-backdrop-filter: blur(var(--orbit-blur-px)); +} + +[role="dialog"], +[aria-modal="true"] { + backdrop-filter: blur(calc(var(--orbit-blur-px) * 0.65)) saturate(1.1); + -webkit-backdrop-filter: blur(calc(var(--orbit-blur-px) * 0.65)) saturate(1.1); +} +`.trim(); +} diff --git a/packages/orbit-blur/test/register.test.mjs b/packages/orbit-blur/test/register.test.mjs new file mode 100644 index 0000000..05210bf --- /dev/null +++ b/packages/orbit-blur/test/register.test.mjs @@ -0,0 +1,33 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { mkdtemp, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { createInstallationManager } from "@c0sc0s/orbit/installation"; + +const packageRoot = dirname(dirname(fileURLToPath(import.meta.url))); + +test("Orbit registers orbit-blur as a renderer-only plugin", async (t) => { + const home = await mkdtemp(join(tmpdir(), "orbit-blur-register-")); + t.after(() => rm(home, { recursive: true, force: true })); + const manager = createInstallationManager({ + home, + root: join(home, "orbit"), + platform: "darwin", + run: async (file, args) => { + if (args[1] !== "--help") throw new Error("Fixture must not start real Codex"); + return { stdout: "Orbit", stderr: "" }; + }, + }); + await manager.install(); + const result = await manager.addPlugin(packageRoot); + assert.equal(result.status, "registered"); + assert.equal(result.id, "orbit-blur"); + const config = await manager.read(); + const entry = config.plugins.find((plugin) => plugin.id === "orbit-blur"); + assert.ok(entry); + assert.equal(entry.enabled, true); + assert.match(entry.entry, /runtime\/dist\/injected\.js$/); + assert.equal(entry.service, undefined); +}); diff --git a/packages/orbit-blur/test/styles.test.mjs b/packages/orbit-blur/test/styles.test.mjs new file mode 100644 index 0000000..d716167 --- /dev/null +++ b/packages/orbit-blur/test/styles.test.mjs @@ -0,0 +1,43 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { readFile } from "node:fs/promises"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { buildBlurStyles, resolveBlurSettings } from "../dist/styles.mjs"; + +const root = dirname(dirname(fileURLToPath(import.meta.url))); + +test("resolveBlurSettings applies defaults and clamps", () => { + assert.deepEqual(resolveBlurSettings({}), { + blurPx: 18, + tintOpacity: 0.42, + }); + assert.deepEqual(resolveBlurSettings({ blurPx: 99, tintOpacity: -1 }), { + blurPx: 64, + tintOpacity: 0, + }); + assert.deepEqual(resolveBlurSettings({ blurPx: 12, tintOpacity: 0.5 }), { + blurPx: 12, + tintOpacity: 0.5, + }); +}); + +test("buildBlurStyles includes blur tokens and landmark chrome", () => { + const css = buildBlurStyles({ blurPx: 20, tintOpacity: 0.4 }); + assert.match(css, /--orbit-blur-px:\s*20px/); + assert.match(css, /backdrop-filter:\s*blur/); + assert.match(css, /\[role="navigation"\]/); + assert.match(css, /background:\s*transparent/); +}); + +test("orbit-plugin.json runtimeVersion matches injected bundle", async () => { + const manifest = JSON.parse(await readFile(join(root, "orbit-plugin.json"), "utf8")); + const injected = await readFile(join(root, "runtime", "dist", "injected.js"), "utf8"); + assert.equal(manifest.id, "orbit-blur"); + assert.equal(manifest.renderer, "runtime/dist/injected.js"); + assert.equal(manifest.service, undefined); + assert.match(injected, /CodexPlugin/); + assert.match(injected, new RegExp(String.raw`RUNTIME_VERSION\s*=\s*"${manifest.runtimeVersion}"`)); + assert.match(injected, /runtimeVersion:\s*RUNTIME_VERSION/); + assert.match(injected, /backdrop-filter/); +}); diff --git a/packages/orbit-blur/tsconfig.json b/packages/orbit-blur/tsconfig.json new file mode 100644 index 0000000..ef1029e --- /dev/null +++ b/packages/orbit-blur/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "types": [], + "noEmit": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "verbatimModuleSyntax": true + }, + "include": ["src/**/*.ts"] +} diff --git a/scripts/check.mjs b/scripts/check.mjs index d4ca0cb..b23bf81 100644 --- a/scripts/check.mjs +++ b/scripts/check.mjs @@ -7,13 +7,13 @@ import { fileURLToPath } from "node:url"; process.chdir(dirname(dirname(fileURLToPath(import.meta.url)))); const run = promisify(execFile); -for (const directory of ["packages/orbit/src", "packages/orbit-tags/runtime/src", "packages/orbit-tags/hooks", "scripts", "packages/orbit-tags/scripts", "packages/orbit-tags/bin"]) { +for (const directory of ["packages/orbit/src", "packages/orbit-tags/runtime/src", "packages/orbit-tags/hooks", "scripts", "packages/orbit-tags/scripts", "packages/orbit-tags/bin", "packages/orbit-blur/scripts"]) { for (const file of await readdir(directory, { recursive: true })) { if (file.endsWith(".mjs")) await run(process.execPath, ["--check", join(directory, file)]); } } -for (const workspace of ["orbit", "orbit-tags"]) { +for (const workspace of ["orbit", "orbit-tags", "orbit-blur"]) { const root = resolve("packages", workspace); const manifest = JSON.parse(await readFile(join(root, "package.json"), "utf8")); if (workspace === "orbit" && Object.keys(manifest.dependencies ?? {}).some(name => !["figlet", "ora", "picocolors"].includes(name))) { @@ -31,11 +31,20 @@ for (const workspace of ["orbit", "orbit-tags"]) { throw new Error(`Cross-workspace source import in ${workspace}/${file}: ${specifier}`); } if (workspace === "orbit" && specifier.startsWith("@c0sc0s/orbit-tags")) throw new Error("Orbit must not import Tags"); + if (workspace === "orbit" && specifier.startsWith("@c0sc0s/orbit-blur")) throw new Error("Orbit must not import business plugins"); + if (workspace === "orbit-blur" && specifier.startsWith("@c0sc0s/orbit") && specifier !== "@c0sc0s/orbit/sdk") { + throw new Error(`Blur plugin must use the SDK contract: ${file}`); + } + if (workspace === "orbit-blur" && specifier === "@c0sc0s/orbit/sdk") { + const typeOnly = /import\s+type\s+[^;]*from\s+["']@c0sc0s\/orbit\/sdk["']/u.test(source) + && !/import\s+(?!type\b)[^;]*from\s+["']@c0sc0s\/orbit\/sdk["']/u.test(source); + if (!typeOnly) throw new Error(`Blur plugin must import @c0sc0s/orbit/sdk as types only: ${file}`); + } } } } -const documentation = ["README.md", "README.zh-CN.md", "CONTRIBUTING.md", "AGENTS.md", "CHANGELOG.md", "packages/orbit/README.md", "packages/orbit-tags/README.md", "packages/orbit-tags/README.zh-CN.md", "packages/orbit-tags/CHANGELOG.md"]; +const documentation = ["README.md", "README.zh-CN.md", "CONTRIBUTING.md", "AGENTS.md", "CHANGELOG.md", "packages/orbit/README.md", "packages/orbit-tags/README.md", "packages/orbit-tags/README.zh-CN.md", "packages/orbit-tags/CHANGELOG.md", "packages/orbit-blur/README.md"]; for (const directory of ["docs", "packages/orbit/assets", "packages/orbit-tags/assets", "packages/orbit-tags/skills"]) { for (const file of await readdir(directory, { recursive: true })) { if (file.endsWith(".md")) documentation.push(join(directory, file));