@takuhon/cloudflare 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,6 +14,7 @@ local edge cache, and `console.log`-based audit logging.
14
14
  | `GET` | `/api/jsonld` | `@takuhon/api` `createPublicApp` |
15
15
  | `GET` | `/takuhon.json` | `@takuhon/api` `createPublicApp` |
16
16
  | `GET` | `/.well-known/takuhon.json` | `@takuhon/api` `createPublicApp` |
17
+ | `POST` | `/mcp` | `@takuhon/mcp` (read-only, stateless) |
17
18
  | `GET` | `/admin` | `@takuhon/api` `createAdminUiApp` (HTML) |
18
19
  | `PUT` | `/api/admin/profile` | `@takuhon/api` `createAdminApiApp` |
19
20
  | `DELETE` | `/api/admin/profile` | `@takuhon/api` `createAdminApiApp` |
@@ -192,6 +193,16 @@ Once bound:
192
193
  Asset delivery is intentionally locale-agnostic: only the literal `/assets/*`
193
194
  prefix is served, so `/{locale}/assets/...` 404s.
194
195
 
196
+ ### MCP endpoint (`/mcp`)
197
+
198
+ The deployed profile is readable over the [Model Context Protocol](https://modelcontextprotocol.io) at `POST /mcp` — the remote counterpart of the CLI's `takuhon mcp`. It reuses `@takuhon/mcp`'s server (the same core catalog) over the SDK's Web Standard Streamable HTTP transport.
199
+
200
+ - **Stateless**: no Durable Object, no session, no extra binding. Each request builds a fresh server + transport, reads the profile from the same KV (bundled fallback before the first write), and returns a single JSON response (`enableJsonResponse`). It is enabled automatically — nothing to configure.
201
+ - **Read-only and unauthenticated**, at parity with `GET /api/profile`: every answer is privacy-filtered and no admin/write surface is exposed. Responses carry `X-Content-Type-Options: nosniff` and `Cache-Control: no-store`.
202
+ - Exposes the tools `get_profile`, `get_section`, `get_jsonld`, `list_locales` and the resources `takuhon://profile`, `takuhon://schema`. The endpoint is advertised as `mcp` in `/.well-known/takuhon.json`.
203
+
204
+ Like `/assets/*` and `/health`, `/mcp` is locale-agnostic — `/{locale}/mcp` 404s.
205
+
195
206
  ## Limitations & deferred work
196
207
 
197
208
  | Concern | Status | Tracked phase |
package/dist/index.js CHANGED
@@ -683,12 +683,44 @@ var KvTakuhonStorage = class {
683
683
  }
684
684
  };
685
685
 
686
+ // src/mcp.ts
687
+ import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
688
+ import { NotFoundError as NotFoundError3 } from "@takuhon/core";
689
+ import { createTakuhonMcpServer } from "@takuhon/mcp";
690
+ var MCP_SERVER_NAME = "takuhon";
691
+ async function serveMcp(request, kv, fallback) {
692
+ const storage = new KvTakuhonStorage(kv);
693
+ const loadProfile = async () => {
694
+ try {
695
+ return (await storage.getProfile()).data;
696
+ } catch (e) {
697
+ if (e instanceof NotFoundError3) return fallback();
698
+ throw e;
699
+ }
700
+ };
701
+ const server = createTakuhonMcpServer({ loadProfile, name: MCP_SERVER_NAME });
702
+ const transport = new WebStandardStreamableHTTPServerTransport({
703
+ sessionIdGenerator: void 0,
704
+ enableJsonResponse: true
705
+ });
706
+ await server.connect(transport);
707
+ const response = await transport.handleRequest(request);
708
+ const headers = new Headers(response.headers);
709
+ headers.set("x-content-type-options", "nosniff");
710
+ if (!headers.has("cache-control")) headers.set("cache-control", "no-store");
711
+ return new Response(response.body, {
712
+ status: response.status,
713
+ statusText: response.statusText,
714
+ headers
715
+ });
716
+ }
717
+
686
718
  // src/r2-storage.ts
687
719
  import {
688
720
  ACCEPTED_IMAGE_MIME_TYPES,
689
721
  detectImageMime,
690
722
  IMAGE_EXTENSIONS,
691
- NotFoundError as NotFoundError3,
723
+ NotFoundError as NotFoundError4,
692
724
  readImageInfo
693
725
  } from "@takuhon/core";
694
726
  var ASSET_KEY_PREFIX = "assets/";
@@ -735,7 +767,7 @@ var R2TakuhonAssetStorage = class {
735
767
  }
736
768
  async getPublicUrl(assetId) {
737
769
  const head = await this.bucket.head(assetId);
738
- if (head === null) throw new NotFoundError3(`No asset is stored at R2 key "${assetId}".`);
770
+ if (head === null) throw new NotFoundError4(`No asset is stored at R2 key "${assetId}".`);
739
771
  return this.absoluteUrl(`/${assetId}`);
740
772
  }
741
773
  async deleteAsset(assetId) {
@@ -777,6 +809,9 @@ function isAdminUiPath(pathname) {
777
809
  function isAssetPath(pathname) {
778
810
  return pathname.startsWith("/assets/");
779
811
  }
812
+ function isMcpPath(pathname) {
813
+ return pathname === "/mcp";
814
+ }
780
815
  async function serveAsset(request, bucket, url) {
781
816
  if (request.method !== "GET" && request.method !== "HEAD") {
782
817
  return new Response("Method Not Allowed", { status: 405 });
@@ -823,6 +858,9 @@ function createTakuhonWorker(opts) {
823
858
  if (env.TAKUHON_R2 && isAssetPath(url.pathname)) {
824
859
  return serveAsset(request, env.TAKUHON_R2, url);
825
860
  }
861
+ if (isMcpPath(url.pathname)) {
862
+ return serveMcp(request, env.TAKUHON_KV, opts.fallback);
863
+ }
826
864
  const storage = new KvTakuhonStorage(env.TAKUHON_KV);
827
865
  const assetStorage = env.TAKUHON_R2 ? new R2TakuhonAssetStorage(env.TAKUHON_R2, { publicBaseUrl: url.origin }) : void 0;
828
866
  const cachePurger = new CloudflareCachePurger(() => caches.default, {
@@ -857,7 +895,9 @@ function createTakuhonWorker(opts) {
857
895
  fallback: opts.fallback,
858
896
  // Serves `GET /api/activity` from the synced snapshot; the route
859
897
  // 404s while no snapshot is stored or activity is not enabled.
860
- activityStorage: new KvActivityStorage(env.TAKUHON_KV)
898
+ activityStorage: new KvActivityStorage(env.TAKUHON_KV),
899
+ // Advertise the read-only MCP endpoint in `/.well-known/takuhon.json`.
900
+ mcpPath: "/mcp"
861
901
  })
862
902
  );
863
903
  return router.fetch(request, env);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../../../examples/personal-profile/takuhon.json","../src/activity-sync.ts","../src/admin/cloudflare-cache-purger.ts","../src/admin/console-audit-logger.ts","../src/kv-activity-storage.ts","../src/kv-storage.ts","../src/r2-storage.ts"],"sourcesContent":["import {\n ERROR_SLUGS,\n adminAssetSecurityHeaders,\n createAdminApiApp,\n createAdminUiApp,\n createPublicApp,\n localePrefixGetPath,\n problemResponse,\n type AuditLogger,\n type CachePurger,\n} from '@takuhon/api';\nimport { validate, type Takuhon } from '@takuhon/core';\nimport { Hono } from 'hono';\n\nimport exampleJson from '../../../examples/personal-profile/takuhon.json' with { type: 'json' };\n\nimport { syncActivity } from './activity-sync.js';\nimport { CloudflareCachePurger } from './admin/cloudflare-cache-purger.js';\nimport { consoleAuditLogger } from './admin/console-audit-logger.js';\nimport { KvActivityStorage } from './kv-activity-storage.js';\nimport { KvTakuhonStorage } from './kv-storage.js';\nimport { ASSET_CACHE_CONTROL, R2TakuhonAssetStorage } from './r2-storage.js';\n\nexport interface Env {\n TAKUHON_KV: KVNamespace;\n /**\n * Admin bearer token. Provision via `wrangler secret put TAKUHON_ADMIN_TOKEN`.\n * Leave unset to disable admin writes entirely (every PUT/DELETE returns 401).\n */\n TAKUHON_ADMIN_TOKEN?: string;\n /**\n * Comma-separated Origin allowlist for browser-originating admin requests.\n * Empty / unset disables the check (deploy without a configured allowlist is\n * acceptable when the admin UI is same-origin; documented in the README).\n */\n TAKUHON_ADMIN_ORIGIN?: string;\n /**\n * Workers Assets binding holding the bundled admin SPA (`apps/admin`). When\n * present, `/admin/*` is served from it under a strict CSP; when absent, the\n * Worker falls back to the inline `createAdminUiApp` editor, so deployments\n * without Workers Assets configured still have a working admin.\n */\n ASSETS?: Fetcher;\n /**\n * R2 bucket holding uploaded image assets. Optional, mirroring {@link ASSETS}:\n * when bound, `POST /api/admin/assets` stores images here and `GET /assets/*`\n * serves them; when absent, the upload endpoint stays unregistered (404) and\n * avatars remain URL-only.\n */\n TAKUHON_R2?: R2Bucket;\n /**\n * GitHub token for the scheduled activity sync. Optional: languages are\n * fetched unauthenticated without it; the contribution calendar (GraphQL,\n * token-only) is simply skipped. Provision via\n * `wrangler secret put TAKUHON_GITHUB_TOKEN` — never in `wrangler.toml`.\n */\n TAKUHON_GITHUB_TOKEN?: string;\n /**\n * WakaTime API key for the scheduled activity sync; required to read coding\n * time (WakaTime has no unauthenticated mode). Provision via\n * `wrangler secret put TAKUHON_WAKATIME_KEY` — never in `wrangler.toml`.\n * Only flows through the sync step; never persisted.\n */\n TAKUHON_WAKATIME_KEY?: string;\n}\n\n/** Options accepted by {@link createTakuhonWorker}. */\nexport interface CreateTakuhonWorkerOptions {\n /**\n * Lazy producer for the fallback Takuhon document served when KV has no\n * stored profile yet. Called at most once per Worker invocation, on the\n * cold path where the storage layer returns no entry. Implementations\n * typically import a bundled `takuhon.json`, validate it once, and return\n * the resulting value.\n */\n readonly fallback: () => Takuhon;\n}\n\nfunction parseOrigins(raw: string | undefined): string[] {\n if (raw === undefined || raw === '') return [];\n return raw\n .split(',')\n .map((s) => s.trim())\n .filter((s) => s !== '');\n}\n\n/** Admin UI (not the `/api/admin` API) request paths served from the SPA bundle. */\nfunction isAdminUiPath(pathname: string): boolean {\n return pathname === '/admin' || pathname.startsWith('/admin/');\n}\n\n/**\n * Public asset-delivery paths served from R2. Matches only the literal\n * `/assets/...` prefix, so a locale-prefixed `/{locale}/assets/...` is left to\n * the router (which 404s it) — asset delivery is intentionally locale-agnostic,\n * like `/health` and the admin surface.\n */\nfunction isAssetPath(pathname: string): boolean {\n return pathname.startsWith('/assets/');\n}\n\n/**\n * Serve an uploaded asset from R2 (`GET`/`HEAD /assets/*`). This is the public,\n * unauthenticated delivery proxy: the bucket stays private and the Worker\n * mediates every read so it can force `X-Content-Type-Options: nosniff`\n * (`security.md` §4.7) and an immutable cache policy. A missing object is 404.\n */\nasync function serveAsset(request: Request, bucket: R2Bucket, url: URL): Promise<Response> {\n if (request.method !== 'GET' && request.method !== 'HEAD') {\n return new Response('Method Not Allowed', { status: 405 });\n }\n // The R2 object key is the pathname without its leading slash, e.g.\n // `/assets/1715432100-a8f3.webp` → `assets/1715432100-a8f3.webp`.\n const key = url.pathname.slice(1);\n const object = await bucket.get(key);\n if (object === null) {\n return new Response('Not Found', { status: 404 });\n }\n const headers = new Headers();\n const contentType = object.httpMetadata?.contentType;\n if (contentType !== undefined) headers.set('content-type', contentType);\n // SECURITY-CRITICAL (security.md §4.7): force nosniff so a stored object can\n // never be reinterpreted as an active type (e.g. HTML) by the browser.\n headers.set('x-content-type-options', 'nosniff');\n headers.set('cache-control', ASSET_CACHE_CONTROL);\n headers.set('etag', object.httpEtag);\n const body = request.method === 'HEAD' ? null : object.body;\n return new Response(body, { status: 200, headers });\n}\n\n/**\n * Serve the admin SPA from the Workers Assets binding. The bundle's files live\n * at the assets root, so the `/admin` prefix is stripped before lookup; `/admin`\n * and `/admin/` map to `index.html`. The strict admin CSP / security headers are\n * applied to every response (the binding's own headers would otherwise cache\n * the operator-only UI and omit the policy).\n */\nasync function serveAdminSpa(request: Request, assets: Fetcher, url: URL): Promise<Response> {\n if (request.method !== 'GET' && request.method !== 'HEAD') {\n return new Response('Method Not Allowed', { status: 405 });\n }\n // Strip the `/admin` mount prefix; `/admin` and `/admin/` map to the bundle\n // root `/` (which Workers Assets serves as index.html). Requesting\n // `/index.html` directly would 307-redirect under `auto-trailing-slash`.\n const rest = url.pathname.slice('/admin'.length);\n const assetPath = rest === '' ? '/' : rest;\n const assetResponse = await assets.fetch(new Request(new URL(assetPath, url.origin), request));\n const headers = new Headers(assetResponse.headers);\n for (const [name, value] of Object.entries(adminAssetSecurityHeaders())) {\n headers.set(name, value);\n }\n // The admin UI is `private, no-store`, so a conditional-request ETag carried\n // over from the binding would be misleading — drop it.\n headers.delete('etag');\n return new Response(assetResponse.body, {\n status: assetResponse.status,\n statusText: assetResponse.statusText,\n headers,\n });\n}\n\n/**\n * Build a Cloudflare Worker handler for the takuhon adapter. Wires\n * `@takuhon/api`'s public/admin app factories to the KV-backed storage,\n * Cloudflare edge cache purger, and console audit logger that ship with\n * this package.\n *\n * This is the entry point used by projects scaffolded with\n * `create-takuhon`: their `src/index.ts` imports `createTakuhonWorker`,\n * passes a `fallback` that loads the project's own `takuhon.json`, and\n * `export default`s the returned handler. The default export of this\n * module is a convenience that calls the same factory with the monorepo's\n * bundled `personal-profile` fixture.\n */\nexport function createTakuhonWorker(opts: CreateTakuhonWorkerOptions): {\n fetch: (request: Request, env: Env) => Response | Promise<Response>;\n scheduled: (controller: ScheduledController, env: Env, ctx: ExecutionContext) => Promise<void>;\n} {\n return {\n fetch(request: Request, env: Env): Response | Promise<Response> {\n const url = new URL(request.url);\n\n // Serve the bundled admin SPA when a Workers Assets binding is present.\n // Without it, the request falls through to the inline editor mounted on\n // the router below, so admin stays available either way.\n if (env.ASSETS && isAdminUiPath(url.pathname)) {\n return serveAdminSpa(request, env.ASSETS, url);\n }\n\n // Serve uploaded assets from R2 when the bucket is bound. Without it,\n // `/assets/*` falls through to the router and 404s, matching the\n // unregistered upload endpoint.\n if (env.TAKUHON_R2 && isAssetPath(url.pathname)) {\n return serveAsset(request, env.TAKUHON_R2, url);\n }\n\n const storage = new KvTakuhonStorage(env.TAKUHON_KV);\n // Enable image uploads only when an R2 bucket is bound; otherwise the\n // admin API leaves `POST /assets` unregistered, so uploads are disabled\n // and avatars stay URL-only. The public URL is built on this request's\n // origin since the Worker proxies delivery from its own `/assets/*` route.\n const assetStorage = env.TAKUHON_R2\n ? new R2TakuhonAssetStorage(env.TAKUHON_R2, { publicBaseUrl: url.origin })\n : undefined;\n const cachePurger: CachePurger = new CloudflareCachePurger(() => caches.default, {\n origin: url.origin,\n });\n const auditLogger: AuditLogger = consoleAuditLogger;\n\n // `getPath` strips a leading `/{locale}` prefix before route\n // matching. This is the production-critical placement: Hono's\n // `route()` flattens each mounted sub-app's routes into this router\n // and dispatches with this router's `getPath` only, so a `getPath`\n // set on the public sub-app alone would not run here. The shared\n // function's allowlist guard never strips admin remainders, so\n // `/api/admin/*` and `/admin/*` mounts stay locale-agnostic.\n const router = new Hono({ getPath: localePrefixGetPath });\n router.notFound((c) =>\n problemResponse(c, {\n slug: ERROR_SLUGS.notFound,\n status: 404,\n title: 'Not Found',\n detail: `No route matches ${new URL(c.req.url).pathname}.`,\n }),\n );\n router.route(\n '/api/admin',\n createAdminApiApp({\n storage,\n assetStorage,\n getAdminToken: () => env.TAKUHON_ADMIN_TOKEN,\n getAdminOrigins: () => parseOrigins(env.TAKUHON_ADMIN_ORIGIN),\n cachePurger,\n auditLogger,\n }),\n );\n router.route('/admin', createAdminUiApp());\n router.route(\n '/',\n createPublicApp({\n storage,\n fallback: opts.fallback,\n // Serves `GET /api/activity` from the synced snapshot; the route\n // 404s while no snapshot is stored or activity is not enabled.\n activityStorage: new KvActivityStorage(env.TAKUHON_KV),\n }),\n );\n\n return router.fetch(request, env);\n },\n\n /**\n * Cron-driven activity sync (enable with a `[triggers] crons` entry in\n * `wrangler.toml`; daily is the recommended cadence). MUST NOT throw: a\n * failed sync keeps the last-known snapshot and surfaces through the\n * structured log line below (captured by Workers Tail / Logpush), never by\n * failing the cron run.\n */\n async scheduled(_controller: ScheduledController, env: Env, _ctx: ExecutionContext) {\n const timestamp = new Date().toISOString();\n try {\n const result = await syncActivity({\n profileStorage: new KvTakuhonStorage(env.TAKUHON_KV),\n activityStorage: new KvActivityStorage(env.TAKUHON_KV),\n secrets: {\n githubToken: env.TAKUHON_GITHUB_TOKEN,\n wakatimeKey: env.TAKUHON_WAKATIME_KEY,\n },\n fallback: opts.fallback,\n });\n const type =\n result.status === 'synced'\n ? 'activity.sync.success'\n : result.status === 'empty'\n ? 'activity.sync.failure'\n : 'activity.sync.skipped';\n console.log(\n JSON.stringify({ type, timestamp, reason: result.reason, failures: result.failures }),\n );\n } catch (err) {\n console.error(\n JSON.stringify({\n type: 'activity.sync.failure',\n timestamp,\n reason: err instanceof Error ? err.message : String(err),\n failures: [],\n }),\n );\n }\n },\n };\n}\n\nfunction bundledFallback(): Takuhon {\n const r = validate(exampleJson);\n if (!r.ok) throw new Error('Bundled fixture failed validation.');\n return r.data;\n}\n\nexport default createTakuhonWorker({ fallback: bundledFallback });\n","{\n \"schemaVersion\": \"0.5.0\",\n \"profile\": {\n \"displayName\": {\n \"en\": \"Pat Rivera\",\n \"ja\": \"パット・リベラ\"\n },\n \"tagline\": {\n \"en\": \"Open-source maintainer and accessibility advocate\",\n \"ja\": \"オープンソースメンテナ / アクセシビリティ実践者\"\n },\n \"bio\": {\n \"en\": \"Pat Rivera is a fictional persona used to exercise every field of the takuhon profile schema. They maintain a handful of open-source libraries focused on accessibility tooling and frequently speak at local meetups.\",\n \"ja\": \"Pat Rivera は takuhon プロフィール schema の全フィールドを示すための架空の人物です。アクセシビリティ系のオープンソースライブラリを保守し、地域コミュニティで登壇しています。\"\n },\n \"avatar\": {\n \"url\": \"/assets/avatar.webp\",\n \"alt\": {\n \"en\": \"Pat Rivera smiling, wearing round glasses, in front of a soft gradient.\",\n \"ja\": \"ソフトなグラデーションを背景に微笑む Pat Rivera。丸眼鏡を着用。\"\n }\n },\n \"location\": {\n \"country\": \"PT\",\n \"region\": \"Lisbon\",\n \"locality\": {\n \"en\": \"Lisbon\",\n \"ja\": \"リスボン\"\n },\n \"display\": {\n \"en\": \"Lisbon, Portugal\",\n \"ja\": \"ポルトガル・リスボン\"\n }\n }\n },\n \"links\": [\n {\n \"id\": \"website\",\n \"type\": \"website\",\n \"label\": { \"en\": \"Personal site\" },\n \"url\": \"https://example.com/pat\",\n \"featured\": true,\n \"order\": 0\n },\n {\n \"id\": \"github\",\n \"type\": \"github\",\n \"url\": \"https://example.com/pat/github\",\n \"featured\": true,\n \"order\": 1\n },\n {\n \"id\": \"mastodon\",\n \"type\": \"mastodon\",\n \"url\": \"https://example.social/@pat\",\n \"order\": 2\n },\n {\n \"id\": \"blog\",\n \"type\": \"blog\",\n \"label\": {\n \"en\": \"Notes on accessible UI\",\n \"ja\": \"アクセシブル UI の覚え書き\"\n },\n \"url\": \"https://example.com/pat/blog\",\n \"order\": 3\n },\n {\n \"id\": \"newsletter\",\n \"type\": \"custom\",\n \"label\": {\n \"en\": \"Weekly newsletter\",\n \"ja\": \"週次ニュースレター\"\n },\n \"url\": \"https://example.com/pat/newsletter\",\n \"iconUrl\": \"https://example.com/assets/icons/newsletter.svg\",\n \"order\": 4\n }\n ],\n \"careers\": [\n {\n \"id\": \"stellar-ux\",\n \"organization\": {\n \"en\": \"Stellar UX Studio\",\n \"ja\": \"ステラ UX スタジオ\"\n },\n \"role\": {\n \"en\": \"Principal Accessibility Engineer\",\n \"ja\": \"プリンシパル・アクセシビリティエンジニア\"\n },\n \"description\": {\n \"en\": \"Lead the accessibility engineering practice across product surfaces, drive WCAG 2.2 conformance reviews, and mentor a team of five engineers.\",\n \"ja\": \"プロダクト全体のアクセシビリティ設計を統括。WCAG 2.2 適合レビューを主導し、5 名のエンジニアをメンタリング。\"\n },\n \"startDate\": \"2023-04\",\n \"endDate\": null,\n \"isCurrent\": true,\n \"url\": \"https://example.com/stellar\",\n \"order\": 0\n },\n {\n \"id\": \"harbor-labs\",\n \"organization\": {\n \"en\": \"Harbor Labs\",\n \"ja\": \"ハーバーラボ\"\n },\n \"role\": {\n \"en\": \"Senior Frontend Engineer\",\n \"ja\": \"シニアフロントエンドエンジニア\"\n },\n \"description\": {\n \"en\": \"Built the design system foundation and an accessible component library used across nine internal products.\",\n \"ja\": \"デザインシステム基盤と、社内 9 プロダクトで利用されるアクセシブルなコンポーネントライブラリを設計。\"\n },\n \"startDate\": \"2019-06\",\n \"endDate\": \"2023-03\",\n \"order\": 1\n }\n ],\n \"projects\": [\n {\n \"id\": \"axe-helpers\",\n \"title\": {\n \"en\": \"axe-helpers\",\n \"ja\": \"axe-helpers\"\n },\n \"description\": {\n \"en\": \"A tiny set of utilities that wraps axe-core for use in component-level integration tests.\",\n \"ja\": \"コンポーネント単位の統合テスト向けに axe-core をラップする小規模ユーティリティ集。\"\n },\n \"url\": \"https://example.com/axe-helpers\",\n \"tags\": [\"accessibility\", \"testing\", \"typescript\"],\n \"relatedCareerId\": \"stellar-ux\",\n \"startDate\": \"2023-09\",\n \"highlighted\": true,\n \"order\": 0\n },\n {\n \"id\": \"color-contrast-cli\",\n \"title\": {\n \"en\": \"color-contrast-cli\",\n \"ja\": \"color-contrast-cli\"\n },\n \"description\": {\n \"en\": \"Command-line tool that audits design tokens for WCAG contrast ratios.\",\n \"ja\": \"デザイントークンの WCAG コントラスト比を監査するコマンドラインツール。\"\n },\n \"url\": \"https://example.com/color-contrast-cli\",\n \"tags\": [\"accessibility\", \"cli\", \"design-tokens\"],\n \"startDate\": \"2021-02\",\n \"endDate\": \"2022-08\",\n \"order\": 1\n },\n {\n \"id\": \"meetup-talks\",\n \"title\": {\n \"en\": \"Local meetup talks\",\n \"ja\": \"地域コミュニティ登壇\"\n },\n \"order\": 2\n }\n ],\n \"skills\": [\n { \"id\": \"typescript\", \"label\": \"TypeScript\", \"category\": \"programming\", \"order\": 0 },\n { \"id\": \"react\", \"label\": \"React\", \"category\": \"programming\", \"order\": 1 },\n { \"id\": \"wcag-2-2\", \"label\": \"WCAG 2.2\", \"category\": \"design\", \"order\": 2 },\n { \"id\": \"aria\", \"label\": \"ARIA\", \"category\": \"design\", \"order\": 3 },\n { \"id\": \"storybook\", \"label\": \"Storybook\", \"category\": \"programming\", \"order\": 4 },\n { \"id\": \"playwright\", \"label\": \"Playwright\", \"category\": \"programming\", \"order\": 5 },\n { \"id\": \"design-tokens\", \"label\": \"Design tokens\", \"category\": \"design\", \"order\": 6 },\n { \"id\": \"portuguese\", \"label\": \"Portuguese (B2)\", \"category\": \"language\", \"order\": 7 }\n ],\n \"certifications\": [\n {\n \"id\": \"iaap-cpacc-2022\",\n \"title\": {\n \"en\": \"Certified Professional in Accessibility Core Competencies (CPACC)\",\n \"ja\": \"アクセシビリティ専門家認定 (CPACC)\"\n },\n \"issuingOrganization\": {\n \"en\": \"International Association of Accessibility Professionals\",\n \"ja\": \"国際アクセシビリティ専門家協会\"\n },\n \"issueDate\": \"2022-06\",\n \"expirationDate\": null,\n \"credentialId\": \"CPACC-2022-PR-09231\",\n \"url\": \"https://example.org/iaap/credentials/cpacc\",\n \"order\": 0\n },\n {\n \"id\": \"iaap-was-2023\",\n \"title\": {\n \"en\": \"Web Accessibility Specialist (WAS)\",\n \"ja\": \"Web アクセシビリティスペシャリスト (WAS)\"\n },\n \"issuingOrganization\": {\n \"en\": \"International Association of Accessibility Professionals\",\n \"ja\": \"国際アクセシビリティ専門家協会\"\n },\n \"issueDate\": \"2023-03\",\n \"expirationDate\": \"2026-03\",\n \"credentialId\": \"WAS-2023-PR-04522\",\n \"url\": \"https://example.org/iaap/credentials/was\",\n \"order\": 1\n }\n ],\n \"memberships\": [\n {\n \"id\": \"iaap-senior\",\n \"organization\": {\n \"en\": \"International Association of Accessibility Professionals\",\n \"ja\": \"国際アクセシビリティ専門家協会\"\n },\n \"role\": {\n \"en\": \"Senior Member\",\n \"ja\": \"シニアメンバー\"\n },\n \"description\": {\n \"en\": \"Active participant in the Web Accessibility special interest group, contributing to monthly working sessions on WCAG 2.2 interpretation.\",\n \"ja\": \"Web Accessibility 専門部会に参加し、WCAG 2.2 解釈の月例セッションに貢献。\"\n },\n \"startDate\": \"2022-09\",\n \"endDate\": null,\n \"isCurrent\": true,\n \"url\": \"https://example.org/iaap\",\n \"order\": 0\n }\n ],\n \"volunteering\": [\n {\n \"id\": \"code-org-instructor\",\n \"organization\": {\n \"en\": \"Code.org\",\n \"ja\": \"Code.org\"\n },\n \"role\": {\n \"en\": \"Volunteer Instructor\",\n \"ja\": \"ボランティア講師\"\n },\n \"cause\": {\n \"en\": \"Education\",\n \"ja\": \"教育\"\n },\n \"description\": {\n \"en\": \"Taught introductory programming concepts to middle-school students in weekend workshops, with a focus on inclusive curriculum design.\",\n \"ja\": \"週末ワークショップで中学生にプログラミング入門を指導。インクルーシブなカリキュラム設計を重視。\"\n },\n \"startDate\": \"2021-09\",\n \"endDate\": \"2024-06\",\n \"isCurrent\": false,\n \"url\": \"https://example.org/code-org\",\n \"order\": 0\n }\n ],\n \"honors\": [\n {\n \"id\": \"wai-recognized-contributor-2024\",\n \"title\": {\n \"en\": \"Recognized Contributor, Web Accessibility Initiative\",\n \"ja\": \"Web Accessibility Initiative 貢献者表彰\"\n },\n \"issuer\": {\n \"en\": \"W3C Web Accessibility Initiative\",\n \"ja\": \"W3C Web Accessibility Initiative\"\n },\n \"description\": {\n \"en\": \"Recognized for sustained reviews of the WCAG 2.2 Authoring Practices documentation and for contributions to non-visual focus management patterns.\",\n \"ja\": \"WCAG 2.2 Authoring Practices ドキュメントの継続レビュー、および非視覚的フォーカス管理パターンへの貢献を評価。\"\n },\n \"date\": \"2024-04\",\n \"url\": \"https://example.org/wai/recognitions/2024\",\n \"order\": 0\n }\n ],\n \"education\": [\n {\n \"id\": \"westbrook-bsc-2018\",\n \"institution\": {\n \"en\": \"Westbrook University\",\n \"ja\": \"ウェストブルック大学\"\n },\n \"degree\": {\n \"en\": \"BSc in Cognitive Science\",\n \"ja\": \"認知科学学士\"\n },\n \"fieldOfStudy\": {\n \"en\": \"Human-Computer Interaction\",\n \"ja\": \"ヒューマン・コンピュータ・インタラクション\"\n },\n \"description\": {\n \"en\": \"Senior thesis on screen-reader interaction patterns for non-visual users navigating dynamic web interfaces.\",\n \"ja\": \"卒業研究は動的 Web インターフェースを操作する非視覚ユーザー向けスクリーンリーダー対話パターンの分析。\"\n },\n \"grade\": \"magna cum laude\",\n \"startDate\": \"2014-09\",\n \"endDate\": \"2018-06\",\n \"isCurrent\": false,\n \"url\": \"https://example.org/westbrook\",\n \"order\": 0\n }\n ],\n \"publications\": [\n {\n \"id\": \"design-tokens-wcag-2024\",\n \"title\": {\n \"en\": \"Auditing Design Tokens for WCAG 2.2 Conformance\",\n \"ja\": \"デザイントークンの WCAG 2.2 適合性監査\"\n },\n \"publisher\": {\n \"en\": \"ACM SIGACCESS\",\n \"ja\": \"ACM SIGACCESS\"\n },\n \"description\": {\n \"en\": \"A method for statically analyzing design-token files to surface color-contrast violations before they reach implementation.\",\n \"ja\": \"実装前にデザイントークンファイルを静的解析し、色コントラスト違反を発見する手法を提案。\"\n },\n \"date\": \"2024-03\",\n \"url\": \"https://example.org/papers/design-tokens-wcag\",\n \"doi\": \"10.1145/3678901.3678910\",\n \"coAuthors\": [\"Jamie Chen\", \"Sofia Almeida\"],\n \"order\": 0\n }\n ],\n \"languages\": [\n {\n \"id\": \"lang-en\",\n \"language\": \"en\",\n \"displayName\": {\n \"en\": \"English\",\n \"ja\": \"英語\"\n },\n \"proficiency\": \"native\",\n \"order\": 0\n },\n {\n \"id\": \"lang-pt\",\n \"language\": \"pt\",\n \"displayName\": {\n \"en\": \"Portuguese\",\n \"ja\": \"ポルトガル語\"\n },\n \"proficiency\": \"intermediate\",\n \"order\": 1\n }\n ],\n \"courses\": [\n {\n \"id\": \"coursera-advanced-a11y-2023\",\n \"title\": {\n \"en\": \"Advanced Web Accessibility Patterns\",\n \"ja\": \"Web アクセシビリティ応用パターン\"\n },\n \"provider\": {\n \"en\": \"Coursera (University of Michigan)\",\n \"ja\": \"Coursera (ミシガン大学)\"\n },\n \"courseNumber\": \"UMICH-A11Y-301\",\n \"description\": {\n \"en\": \"Covered ARIA authoring practices, accessible component patterns, and assistive technology testing workflows.\",\n \"ja\": \"ARIA オーサリングプラクティス、アクセシブルコンポーネントパターン、支援技術テストワークフローを扱う。\"\n },\n \"completionDate\": \"2023-11\",\n \"certificateUrl\": \"https://example.org/certificates/coursera/UMICH-A11Y-301\",\n \"order\": 0\n }\n ],\n \"patents\": [\n {\n \"id\": \"us-2024-a11y-test\",\n \"title\": {\n \"en\": \"Method and System for Automated Screen-Reader Output Verification\",\n \"ja\": \"スクリーンリーダー出力の自動検証手法およびシステム\"\n },\n \"patentNumber\": \"US 99,999,999 B2\",\n \"office\": \"USPTO\",\n \"status\": \"issued\",\n \"description\": {\n \"en\": \"An automated harness that captures screen-reader output and compares it against an authoring-time accessibility annotation.\",\n \"ja\": \"スクリーンリーダーの出力を自動的にキャプチャし、オーサリング時のアクセシビリティ注釈と照合する自動化ハーネス。\"\n },\n \"filingDate\": \"2022-04\",\n \"grantDate\": \"2024-03\",\n \"url\": \"https://patents.example.org/us-11987654\",\n \"coInventors\": [\"Jamie Chen\"],\n \"order\": 0\n },\n {\n \"id\": \"us-pending-focus\",\n \"title\": {\n \"en\": \"Predictive Focus Management for Single-Page Applications\",\n \"ja\": \"シングルページアプリケーション向け予測的フォーカス管理\"\n },\n \"patentNumber\": \"US 99/999,999\",\n \"office\": \"USPTO\",\n \"status\": \"pending\",\n \"description\": {\n \"en\": \"An algorithm that predicts the next likely focus target based on user navigation patterns to reduce focus loss in dynamic content.\",\n \"ja\": \"ユーザーのナビゲーションパターンから次のフォーカス候補を予測し、動的コンテンツにおけるフォーカス喪失を低減するアルゴリズム。\"\n },\n \"filingDate\": \"2024-08\",\n \"url\": \"https://patents.example.org/us-pending-focus\",\n \"order\": 1\n }\n ],\n \"testScores\": [\n {\n \"id\": \"celpe-bras-2023\",\n \"title\": {\n \"en\": \"CELPE-Bras (Brazilian Portuguese Proficiency)\",\n \"ja\": \"CELPE-Bras (ブラジルポルトガル語能力試験)\"\n },\n \"score\": \"Upper Intermediate\",\n \"date\": \"2023-05\",\n \"description\": {\n \"en\": \"Brazilian government certificate of proficiency in Portuguese as a foreign language.\",\n \"ja\": \"ブラジル政府認定の外国語としてのポルトガル語能力証明書。\"\n },\n \"url\": \"https://example.org/scores/celpe-bras-2023\",\n \"order\": 0\n },\n {\n \"id\": \"gre-general-2013\",\n \"title\": {\n \"en\": \"GRE General Test\",\n \"ja\": \"GRE 一般試験\"\n },\n \"score\": \"332 / 340\",\n \"date\": \"2013-10\",\n \"relatedEducationId\": \"westbrook-bsc-2018\",\n \"description\": {\n \"en\": \"Combined verbal and quantitative reasoning score submitted with the Westbrook University application.\",\n \"ja\": \"ウェストブルック大学出願時に提出した言語・数的推論の合計スコア。\"\n },\n \"url\": \"https://example.org/scores/gre-general-2013\",\n \"order\": 1\n }\n ],\n \"recommendations\": [\n {\n \"id\": \"rec-jordan-stellar\",\n \"body\": {\n \"en\": \"Pat is one of the most rigorous accessibility engineers I have worked with. They turned our design-token audit into a repeatable process and mentored the whole team on assistive-technology testing.\",\n \"ja\": \"Pat は私が共に働いた中で最も厳格なアクセシビリティエンジニアの一人です。デザイントークン監査を再現可能なプロセスに変え、支援技術テストについてチーム全体を指導してくれました。\"\n },\n \"author\": {\n \"name\": \"Jordan Avery\",\n \"headline\": {\n \"en\": \"Engineering Manager at Stellar UX\",\n \"ja\": \"Stellar UX エンジニアリングマネージャー\"\n },\n \"url\": \"https://example.org/in/jordan-avery\"\n },\n \"relationship\": {\n \"en\": \"Managed Pat directly at Stellar UX\",\n \"ja\": \"Stellar UX で Pat を直属で管理\"\n },\n \"date\": \"2023-09\",\n \"relatedCareerId\": \"stellar-ux\",\n \"order\": 0\n },\n {\n \"id\": \"rec-sofia-harbor\",\n \"body\": {\n \"en\": \"I collaborated with Pat on a screen-reader testing harness at Harbor Labs. Their attention to non-visual user journeys raised the bar for the entire engineering org.\",\n \"ja\": \"Harbor Labs でスクリーンリーダーのテストハーネスを Pat と共同開発しました。非視覚ユーザーの導線への配慮は、エンジニアリング組織全体の基準を引き上げました。\"\n },\n \"author\": {\n \"name\": \"Sofia Almeida\",\n \"headline\": {\n \"en\": \"Staff Engineer at Harbor Labs\",\n \"ja\": \"Harbor Labs スタッフエンジニア\"\n }\n },\n \"date\": \"2021-06\",\n \"relatedCareerId\": \"harbor-labs\",\n \"order\": 1\n }\n ],\n \"contact\": {\n \"email\": \"pat@example.com\",\n \"showEmail\": false,\n \"formUrl\": \"https://example.com/pat/contact\"\n },\n \"settings\": {\n \"defaultLocale\": \"en\",\n \"fallbackLocale\": \"en\",\n \"availableLocales\": [\"en\", \"ja\"],\n \"theme\": \"default\",\n \"showPoweredBy\": true,\n \"enableJsonLd\": true,\n \"enableApi\": true,\n \"enableAnalytics\": false\n },\n \"meta\": {\n \"createdAt\": \"2026-01-15T09:00:00Z\",\n \"updatedAt\": \"2026-05-12T08:30:00Z\",\n \"generator\": \"Takuhon\",\n \"contentLicense\": {\n \"spdxId\": \"CC-BY-4.0\",\n \"url\": \"https://creativecommons.org/licenses/by/4.0/\",\n \"attribution\": {\n \"name\": \"Pat Rivera\",\n \"url\": \"https://example.com/pat\"\n }\n },\n \"privacy\": {\n \"hideCredentialIds\": true,\n \"hideEducationGrades\": true\n }\n }\n}\n","/**\n * Scheduled developer-activity sync for the Cloudflare worker.\n *\n * The cron-driven counterpart of the CLI's `takuhon activity sync`: it reads\n * `settings.activity` from the stored profile, fetches the configured GitHub /\n * WakaTime metrics through `@takuhon/activity`, and persists the snapshot via\n * an `ActivityStorage` — so public rendering stays a static read.\n *\n * The function reports its outcome instead of throwing wherever it can, and a\n * sync that gathers no data never overwrites a good snapshot: the last-known\n * one is kept and the result says so, letting the `scheduled` handler log the\n * failure without failing the cron run.\n */\n\nimport { fetchActivitySnapshot, isEmptySnapshot, type ActivitySecrets } from '@takuhon/activity';\nimport {\n NotFoundError,\n type ActivityStorage,\n type Takuhon,\n type TakuhonStorage,\n} from '@takuhon/core';\n\nexport interface ActivitySyncOptions {\n /** Source of the profile whose `settings.activity` configures the sync. */\n profileStorage: TakuhonStorage;\n /** Destination for the synced snapshot. */\n activityStorage: ActivityStorage;\n /** Secrets from the Worker env; never persisted. */\n secrets: ActivitySecrets;\n /** Fallback profile used when storage has none (same as the public app). */\n fallback?: () => Takuhon;\n /** HTTP client override for tests. Defaults to the global `fetch`. */\n fetch?: typeof fetch;\n /** Clock for `lastSyncedAt`; injectable for deterministic tests. */\n now?: () => Date;\n}\n\n/** A per-source fetch failure, with any secret value already redacted. */\nexport interface ActivitySourceFailure {\n source: string;\n message: string;\n}\n\nexport interface ActivitySyncResult {\n /**\n * `synced` — a snapshot was gathered and stored. `skipped` — activity is not\n * configured / enabled, so nothing was attempted. `empty` — every configured\n * source came back empty or failing; the last-known snapshot was kept.\n */\n status: 'synced' | 'skipped' | 'empty';\n /** Why a `skipped` / `empty` run did not store anything. */\n reason?: string;\n /** Per-source failures reported by the fetcher (may be non-empty on `synced`). */\n failures: ActivitySourceFailure[];\n}\n\n/** Remove any literal occurrence of the secrets from a string before it is logged. */\nfunction redactSecrets(text: string, secrets: ActivitySecrets): string {\n let out = text;\n for (const secret of [secrets.githubToken, secrets.wakatimeKey]) {\n if (secret !== undefined && secret !== '') out = out.split(secret).join('***');\n }\n return out;\n}\n\n/**\n * Run one activity sync. Resolves with a result for every configuration state;\n * it only rejects if the storage layer itself fails (the `scheduled` handler\n * catches that, so a broken sync never fails the cron run).\n */\nexport async function syncActivity(opts: ActivitySyncOptions): Promise<ActivitySyncResult> {\n let profile: Takuhon;\n try {\n profile = (await opts.profileStorage.getProfile()).data;\n } catch (err) {\n if (err instanceof NotFoundError && opts.fallback) {\n profile = opts.fallback();\n } else if (err instanceof NotFoundError) {\n return { status: 'skipped', reason: 'no stored profile', failures: [] };\n } else {\n throw err;\n }\n }\n\n const config = profile.settings.activity;\n if (config?.enabled !== true) {\n return {\n status: 'skipped',\n reason: 'activity is not enabled in settings.activity',\n failures: [],\n };\n }\n const hasGithub = config.github?.username !== undefined && config.github.username !== '';\n const hasWakatime = config.wakatime?.username !== undefined && config.wakatime.username !== '';\n if (!hasGithub && !hasWakatime) {\n return { status: 'skipped', reason: 'no github or wakatime username configured', failures: [] };\n }\n\n const failures: ActivitySourceFailure[] = [];\n const snapshot = await fetchActivitySnapshot(config, opts.secrets, {\n fetch: opts.fetch,\n now: opts.now,\n onError: (source, err) => {\n const detail = err instanceof Error ? err.message : String(err);\n failures.push({ source, message: redactSecrets(detail, opts.secrets) });\n },\n });\n\n if (isEmptySnapshot(snapshot)) {\n // Never replace a good snapshot with an empty one (design §6): keep the\n // last-known document and report the dry run.\n return {\n status: 'empty',\n reason: 'the sync gathered no activity data; kept the last-known snapshot',\n failures,\n };\n }\n\n await opts.activityStorage.saveActivitySnapshot(snapshot);\n return { status: 'synced', failures };\n}\n","import type { CachePurger } from '@takuhon/api';\n\nexport interface CloudflareCachePurgerOptions {\n /**\n * Absolute origin (e.g. `https://example.com`) used to build the URLs\n * passed to `Cache.delete`. The Worker derives this from the incoming\n * request's URL so the same code works under any production hostname.\n */\n origin: string;\n /**\n * Locale codes to include when purging language-keyed cache entries.\n * Cloudflare caches `?lang=` query variants as distinct keys; we purge\n * a representative set on every write. Adapters can extend the list to\n * cover other locales the deploy serves.\n */\n langs?: string[];\n}\n\n/**\n * `CachePurger` backed by Cloudflare's colo-local `caches.default`.\n *\n * Limitations (documented in the adapter README):\n * - Cloudflare's `Cache.delete` clears the current colo only, not the\n * entire edge. Other colos honour the response's `Cache-Control`\n * `s-maxage` (5 minutes today) before refreshing.\n * - Truly global invalidation requires the REST `/purge_cache` API,\n * which needs a zone-scoped token; that's deferred to a later phase.\n */\nexport class CloudflareCachePurger implements CachePurger {\n private readonly origin: string;\n private readonly langs: string[];\n\n /**\n * `getCache` is a thunk so the Workers-only `caches` global is touched\n * lazily — public-only requests on this Worker never run admin handlers\n * and must not pay (or fail under Node tests) for the lookup.\n */\n constructor(\n private readonly getCache: () => Cache,\n opts: CloudflareCachePurgerOptions,\n ) {\n this.origin = opts.origin.replace(/\\/$/, '');\n this.langs = opts.langs ?? ['en', 'ja'];\n }\n\n async profileUpdated(): Promise<void> {\n await this.purge();\n }\n\n async profileDeleted(): Promise<void> {\n await this.purge();\n }\n\n private async purge(): Promise<void> {\n const cache = this.getCache();\n const targets = ['/', '/api/profile', '/api/jsonld', '/takuhon.json'];\n for (const lang of this.langs) {\n const q = `?lang=${encodeURIComponent(lang)}`;\n targets.push(`/api/profile${q}`, `/api/jsonld${q}`);\n }\n for (const path of targets) {\n await cache.delete(new Request(this.origin + path), { ignoreMethod: true });\n }\n }\n}\n","import type { AuditEvent, AuditLogger } from '@takuhon/api';\n\n/**\n * `AuditLogger` that writes a single line of JSON per event to `console.log`.\n *\n * Cloudflare captures these via Workers Tail / Logpush, where they can be\n * routed to R2, S3, or any downstream SIEM. Token bodies never reach this\n * sink — the upstream middleware only emits `sha256:<hex>` digests in\n * `actor.tokenHash`.\n */\nexport const consoleAuditLogger: AuditLogger = (event: AuditEvent): void => {\n console.log(JSON.stringify(event));\n};\n","import { isActivitySnapshot, type ActivitySnapshot, type ActivityStorage } from '@takuhon/core';\n\n/**\n * KV key holding the synced developer-activity snapshot. A second key in the\n * same namespace as the profile (`TAKUHON_DATA`): the snapshot is a sibling\n * document, machine-written by the scheduled sync and deliberately kept out of\n * the canonical profile document.\n */\nexport const ACTIVITY_KV_KEY = 'TAKUHON_ACTIVITY';\n\n/**\n * Cloudflare KV implementation of the `ActivityStorage` contract.\n *\n * Reads are forgiving: an absent, unparseable, or malformed value resolves to\n * `null` (never throws), because a missing snapshot is the normal pre-sync /\n * opt-out state and the renderer simply omits the activity section. Writes are\n * last-writer-wins with no version metadata — the snapshot carries its own\n * `lastSyncedAt`, and the only writer is the scheduled sync.\n */\nexport class KvActivityStorage implements ActivityStorage {\n constructor(private readonly kv: KVNamespace) {}\n\n async getActivitySnapshot(): Promise<ActivitySnapshot | null> {\n const raw = await this.kv.get(ACTIVITY_KV_KEY, 'text');\n if (raw === null) return null;\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n // A corrupt snapshot is treated as absent, not fatal: the next sync\n // rewrites it whole, and the renderer omits the section meanwhile.\n return null;\n }\n return isActivitySnapshot(parsed) ? parsed : null;\n }\n\n async saveActivitySnapshot(snapshot: ActivitySnapshot): Promise<void> {\n await this.kv.put(ACTIVITY_KV_KEY, JSON.stringify(snapshot));\n }\n}\n","import { ConflictError, NotFoundError, type Takuhon, type TakuhonStorage } from '@takuhon/core';\n\nexport const KV_KEY = 'TAKUHON_DATA';\n\nexport interface KvMetadata {\n version: string;\n updatedAt: string;\n}\n\n/**\n * Cloudflare KV implementation of the `TakuhonStorage` contract. Stores the\n * profile document as JSON under a single key (`TAKUHON_DATA`) and tracks the\n * optimistic-locking token inside KV value metadata.\n *\n * `version` is a fresh UUIDv4 on every successful write. Callers compare it\n * verbatim against the `If-Match` precondition; mismatches raise\n * `ConflictError` with `currentVersion` so the API layer can build the RFC\n * 7807 envelope without an extra round trip.\n */\nexport class KvTakuhonStorage implements TakuhonStorage {\n constructor(private readonly kv: KVNamespace) {}\n\n async getProfile(): Promise<{ data: Takuhon; version: string }> {\n const result = await this.kv.getWithMetadata<Takuhon, KvMetadata>(KV_KEY, 'json');\n if (result.value === null || !result.metadata?.version) {\n throw new NotFoundError(`No profile is stored at KV key \"${KV_KEY}\".`);\n }\n return { data: result.value, version: result.metadata.version };\n }\n\n async saveProfile(data: Takuhon, ifMatch?: string): Promise<{ version: string }> {\n if (ifMatch !== undefined) {\n const current = await this.kv.getWithMetadata<Takuhon, KvMetadata>(KV_KEY, 'json');\n const currentVersion = current.metadata?.version;\n if (currentVersion !== ifMatch) {\n throw new ConflictError(\n `If-Match preconditioned on version \"${ifMatch}\" but current is \"${currentVersion ?? 'absent'}\".`,\n { currentVersion },\n );\n }\n }\n const version = crypto.randomUUID();\n const updatedAt = new Date().toISOString();\n await this.kv.put(KV_KEY, JSON.stringify(data), {\n metadata: { version, updatedAt } satisfies KvMetadata,\n });\n return { version };\n }\n\n async deleteProfile(): Promise<void> {\n await this.kv.delete(KV_KEY);\n }\n}\n","import {\n ACCEPTED_IMAGE_MIME_TYPES,\n detectImageMime,\n IMAGE_EXTENSIONS,\n NotFoundError,\n readImageInfo,\n type AcceptedImageMime,\n type AssetOptions,\n type AssetRecord,\n type TakuhonAssetStorage,\n} from '@takuhon/core';\n\n/** Key prefix every uploaded asset lives under (`security.md` §4.6). */\nconst ASSET_KEY_PREFIX = 'assets/';\n\n/** Extension used when the bytes are not one of the accepted image types. */\nconst DEFAULT_EXTENSION = 'bin';\n\n/**\n * Long-lived immutable cache policy for delivered assets. Object keys embed a\n * timestamp and a random hash, so a given key never names different bytes —\n * the response can be cached forever.\n */\nexport const ASSET_CACHE_CONTROL = 'public, max-age=31536000, immutable';\n\n/** Narrow an arbitrary content-type string to an accepted image MIME, or null. */\nfunction asAcceptedMime(value: string | undefined): AcceptedImageMime | null {\n if (value === undefined) return null;\n return (ACCEPTED_IMAGE_MIME_TYPES as readonly string[]).includes(value)\n ? (value as AcceptedImageMime)\n : null;\n}\n\n/** Seconds since the Unix epoch, used as the leading object-key component. */\nfunction timestampSeconds(): number {\n return Math.floor(Date.now() / 1000);\n}\n\n/**\n * Four hex characters of cryptographic randomness. Combined with the timestamp\n * this defeats key enumeration (`security.md` §4.7) without an idempotency\n * guarantee — every upload gets a fresh key.\n */\nfunction shortHash(): string {\n const bytes = new Uint8Array(2);\n crypto.getRandomValues(bytes);\n return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');\n}\n\n/**\n * Cloudflare R2 implementation of the {@link TakuhonAssetStorage} contract.\n *\n * The bytes arrive already validated and metadata-stripped by `@takuhon/api`'s\n * admin app (magic-byte check, size / dimension / frame limits, EXIF removal),\n * so this adapter only persists them and mints the public URL. Object keys\n * follow `assets/{timestamp}-{shortHash}.{ext}` (`security.md` §4.6); the file\n * extension comes from `@takuhon/core`'s {@link IMAGE_EXTENSIONS}.\n *\n * Dimensions are read back from the container header via\n * {@link readImageInfo} (no pixel decode) so the returned {@link AssetRecord}\n * is complete; this is the same header-only parse the API layer already\n * performed and matches the deliberate \"no codec\" approach.\n *\n * Assets are served by the Worker's `GET /assets/*` proxy route rather than a\n * public R2 bucket, so `publicUrl` is an absolute URL on the Worker's own\n * origin when `publicBaseUrl` is supplied, falling back to the relative `url`\n * otherwise.\n */\nexport class R2TakuhonAssetStorage implements TakuhonAssetStorage {\n private readonly bucket: R2Bucket;\n private readonly publicBaseUrl: string;\n\n constructor(bucket: R2Bucket, options: { publicBaseUrl?: string } = {}) {\n this.bucket = bucket;\n this.publicBaseUrl = options.publicBaseUrl ?? '';\n }\n\n async putAsset(file: File | Blob, options?: AssetOptions): Promise<AssetRecord> {\n const bytes = new Uint8Array(await file.arrayBuffer());\n // Trust the API-validated content-type when present; otherwise authenticate\n // from the bytes so the adapter is self-sufficient for direct callers.\n const mime = asAcceptedMime(options?.contentType) ?? detectImageMime(bytes);\n const ext = mime !== null ? IMAGE_EXTENSIONS[mime] : DEFAULT_EXTENSION;\n const contentType = mime ?? options?.contentType ?? file.type ?? 'application/octet-stream';\n\n const key = `${ASSET_KEY_PREFIX}${String(timestampSeconds())}-${shortHash()}.${ext}`;\n await this.bucket.put(key, bytes, { httpMetadata: { contentType } });\n\n const info = mime !== null ? readImageInfo(bytes, mime) : null;\n const url = `/${key}`;\n return {\n id: key,\n url,\n publicUrl: this.absoluteUrl(url),\n mimeType: contentType,\n size: bytes.length,\n width: info?.width,\n height: info?.height,\n createdAt: new Date().toISOString(),\n };\n }\n\n async getPublicUrl(assetId: string): Promise<string> {\n const head = await this.bucket.head(assetId);\n if (head === null) throw new NotFoundError(`No asset is stored at R2 key \"${assetId}\".`);\n return this.absoluteUrl(`/${assetId}`);\n }\n\n async deleteAsset(assetId: string): Promise<void> {\n // R2 delete is idempotent: deleting an absent key is a no-op, matching the\n // contract.\n await this.bucket.delete(assetId);\n }\n\n async listAssets(): Promise<AssetRecord[]> {\n const records: AssetRecord[] = [];\n let cursor: string | undefined;\n do {\n const page = await this.bucket.list({ prefix: ASSET_KEY_PREFIX, cursor });\n for (const object of page.objects) {\n const url = `/${object.key}`;\n records.push({\n id: object.key,\n url,\n publicUrl: this.absoluteUrl(url),\n mimeType: object.httpMetadata?.contentType ?? 'application/octet-stream',\n size: object.size,\n createdAt: object.uploaded.toISOString(),\n });\n }\n cursor = page.truncated ? page.cursor : undefined;\n } while (cursor !== undefined);\n return records;\n }\n\n private absoluteUrl(path: string): string {\n return this.publicBaseUrl !== '' ? `${this.publicBaseUrl}${path}` : path;\n }\n}\n"],"mappings":"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","names":["NotFoundError","NotFoundError"]}
1
+ {"version":3,"sources":["../src/index.ts","../../../examples/personal-profile/takuhon.json","../src/activity-sync.ts","../src/admin/cloudflare-cache-purger.ts","../src/admin/console-audit-logger.ts","../src/kv-activity-storage.ts","../src/kv-storage.ts","../src/mcp.ts","../src/r2-storage.ts"],"sourcesContent":["import {\n ERROR_SLUGS,\n adminAssetSecurityHeaders,\n createAdminApiApp,\n createAdminUiApp,\n createPublicApp,\n localePrefixGetPath,\n problemResponse,\n type AuditLogger,\n type CachePurger,\n} from '@takuhon/api';\nimport { validate, type Takuhon } from '@takuhon/core';\nimport { Hono } from 'hono';\n\nimport exampleJson from '../../../examples/personal-profile/takuhon.json' with { type: 'json' };\n\nimport { syncActivity } from './activity-sync.js';\nimport { CloudflareCachePurger } from './admin/cloudflare-cache-purger.js';\nimport { consoleAuditLogger } from './admin/console-audit-logger.js';\nimport { KvActivityStorage } from './kv-activity-storage.js';\nimport { KvTakuhonStorage } from './kv-storage.js';\nimport { serveMcp } from './mcp.js';\nimport { ASSET_CACHE_CONTROL, R2TakuhonAssetStorage } from './r2-storage.js';\n\nexport interface Env {\n TAKUHON_KV: KVNamespace;\n /**\n * Admin bearer token. Provision via `wrangler secret put TAKUHON_ADMIN_TOKEN`.\n * Leave unset to disable admin writes entirely (every PUT/DELETE returns 401).\n */\n TAKUHON_ADMIN_TOKEN?: string;\n /**\n * Comma-separated Origin allowlist for browser-originating admin requests.\n * Empty / unset disables the check (deploy without a configured allowlist is\n * acceptable when the admin UI is same-origin; documented in the README).\n */\n TAKUHON_ADMIN_ORIGIN?: string;\n /**\n * Workers Assets binding holding the bundled admin SPA (`apps/admin`). When\n * present, `/admin/*` is served from it under a strict CSP; when absent, the\n * Worker falls back to the inline `createAdminUiApp` editor, so deployments\n * without Workers Assets configured still have a working admin.\n */\n ASSETS?: Fetcher;\n /**\n * R2 bucket holding uploaded image assets. Optional, mirroring {@link ASSETS}:\n * when bound, `POST /api/admin/assets` stores images here and `GET /assets/*`\n * serves them; when absent, the upload endpoint stays unregistered (404) and\n * avatars remain URL-only.\n */\n TAKUHON_R2?: R2Bucket;\n /**\n * GitHub token for the scheduled activity sync. Optional: languages are\n * fetched unauthenticated without it; the contribution calendar (GraphQL,\n * token-only) is simply skipped. Provision via\n * `wrangler secret put TAKUHON_GITHUB_TOKEN` — never in `wrangler.toml`.\n */\n TAKUHON_GITHUB_TOKEN?: string;\n /**\n * WakaTime API key for the scheduled activity sync; required to read coding\n * time (WakaTime has no unauthenticated mode). Provision via\n * `wrangler secret put TAKUHON_WAKATIME_KEY` — never in `wrangler.toml`.\n * Only flows through the sync step; never persisted.\n */\n TAKUHON_WAKATIME_KEY?: string;\n}\n\n/** Options accepted by {@link createTakuhonWorker}. */\nexport interface CreateTakuhonWorkerOptions {\n /**\n * Lazy producer for the fallback Takuhon document served when KV has no\n * stored profile yet. Called at most once per Worker invocation, on the\n * cold path where the storage layer returns no entry. Implementations\n * typically import a bundled `takuhon.json`, validate it once, and return\n * the resulting value.\n */\n readonly fallback: () => Takuhon;\n}\n\nfunction parseOrigins(raw: string | undefined): string[] {\n if (raw === undefined || raw === '') return [];\n return raw\n .split(',')\n .map((s) => s.trim())\n .filter((s) => s !== '');\n}\n\n/** Admin UI (not the `/api/admin` API) request paths served from the SPA bundle. */\nfunction isAdminUiPath(pathname: string): boolean {\n return pathname === '/admin' || pathname.startsWith('/admin/');\n}\n\n/**\n * Public asset-delivery paths served from R2. Matches only the literal\n * `/assets/...` prefix, so a locale-prefixed `/{locale}/assets/...` is left to\n * the router (which 404s it) — asset delivery is intentionally locale-agnostic,\n * like `/health` and the admin surface.\n */\nfunction isAssetPath(pathname: string): boolean {\n return pathname.startsWith('/assets/');\n}\n\n/**\n * The read-only MCP endpoint. Matches only the literal `/mcp`, so a\n * locale-prefixed `/{locale}/mcp` is left to the router (which 404s it) — the\n * endpoint is intentionally locale-agnostic, like `/health` and `/assets/*`.\n */\nfunction isMcpPath(pathname: string): boolean {\n return pathname === '/mcp';\n}\n\n/**\n * Serve an uploaded asset from R2 (`GET`/`HEAD /assets/*`). This is the public,\n * unauthenticated delivery proxy: the bucket stays private and the Worker\n * mediates every read so it can force `X-Content-Type-Options: nosniff`\n * (`security.md` §4.7) and an immutable cache policy. A missing object is 404.\n */\nasync function serveAsset(request: Request, bucket: R2Bucket, url: URL): Promise<Response> {\n if (request.method !== 'GET' && request.method !== 'HEAD') {\n return new Response('Method Not Allowed', { status: 405 });\n }\n // The R2 object key is the pathname without its leading slash, e.g.\n // `/assets/1715432100-a8f3.webp` → `assets/1715432100-a8f3.webp`.\n const key = url.pathname.slice(1);\n const object = await bucket.get(key);\n if (object === null) {\n return new Response('Not Found', { status: 404 });\n }\n const headers = new Headers();\n const contentType = object.httpMetadata?.contentType;\n if (contentType !== undefined) headers.set('content-type', contentType);\n // SECURITY-CRITICAL (security.md §4.7): force nosniff so a stored object can\n // never be reinterpreted as an active type (e.g. HTML) by the browser.\n headers.set('x-content-type-options', 'nosniff');\n headers.set('cache-control', ASSET_CACHE_CONTROL);\n headers.set('etag', object.httpEtag);\n const body = request.method === 'HEAD' ? null : object.body;\n return new Response(body, { status: 200, headers });\n}\n\n/**\n * Serve the admin SPA from the Workers Assets binding. The bundle's files live\n * at the assets root, so the `/admin` prefix is stripped before lookup; `/admin`\n * and `/admin/` map to `index.html`. The strict admin CSP / security headers are\n * applied to every response (the binding's own headers would otherwise cache\n * the operator-only UI and omit the policy).\n */\nasync function serveAdminSpa(request: Request, assets: Fetcher, url: URL): Promise<Response> {\n if (request.method !== 'GET' && request.method !== 'HEAD') {\n return new Response('Method Not Allowed', { status: 405 });\n }\n // Strip the `/admin` mount prefix; `/admin` and `/admin/` map to the bundle\n // root `/` (which Workers Assets serves as index.html). Requesting\n // `/index.html` directly would 307-redirect under `auto-trailing-slash`.\n const rest = url.pathname.slice('/admin'.length);\n const assetPath = rest === '' ? '/' : rest;\n const assetResponse = await assets.fetch(new Request(new URL(assetPath, url.origin), request));\n const headers = new Headers(assetResponse.headers);\n for (const [name, value] of Object.entries(adminAssetSecurityHeaders())) {\n headers.set(name, value);\n }\n // The admin UI is `private, no-store`, so a conditional-request ETag carried\n // over from the binding would be misleading — drop it.\n headers.delete('etag');\n return new Response(assetResponse.body, {\n status: assetResponse.status,\n statusText: assetResponse.statusText,\n headers,\n });\n}\n\n/**\n * Build a Cloudflare Worker handler for the takuhon adapter. Wires\n * `@takuhon/api`'s public/admin app factories to the KV-backed storage,\n * Cloudflare edge cache purger, and console audit logger that ship with\n * this package.\n *\n * This is the entry point used by projects scaffolded with\n * `create-takuhon`: their `src/index.ts` imports `createTakuhonWorker`,\n * passes a `fallback` that loads the project's own `takuhon.json`, and\n * `export default`s the returned handler. The default export of this\n * module is a convenience that calls the same factory with the monorepo's\n * bundled `personal-profile` fixture.\n */\nexport function createTakuhonWorker(opts: CreateTakuhonWorkerOptions): {\n fetch: (request: Request, env: Env) => Response | Promise<Response>;\n scheduled: (controller: ScheduledController, env: Env, ctx: ExecutionContext) => Promise<void>;\n} {\n return {\n fetch(request: Request, env: Env): Response | Promise<Response> {\n const url = new URL(request.url);\n\n // Serve the bundled admin SPA when a Workers Assets binding is present.\n // Without it, the request falls through to the inline editor mounted on\n // the router below, so admin stays available either way.\n if (env.ASSETS && isAdminUiPath(url.pathname)) {\n return serveAdminSpa(request, env.ASSETS, url);\n }\n\n // Serve uploaded assets from R2 when the bucket is bound. Without it,\n // `/assets/*` falls through to the router and 404s, matching the\n // unregistered upload endpoint.\n if (env.TAKUHON_R2 && isAssetPath(url.pathname)) {\n return serveAsset(request, env.TAKUHON_R2, url);\n }\n\n // Read-only MCP endpoint. Stateless (no Durable Object / session); reads\n // the profile from the same KV the public API uses.\n if (isMcpPath(url.pathname)) {\n return serveMcp(request, env.TAKUHON_KV, opts.fallback);\n }\n\n const storage = new KvTakuhonStorage(env.TAKUHON_KV);\n // Enable image uploads only when an R2 bucket is bound; otherwise the\n // admin API leaves `POST /assets` unregistered, so uploads are disabled\n // and avatars stay URL-only. The public URL is built on this request's\n // origin since the Worker proxies delivery from its own `/assets/*` route.\n const assetStorage = env.TAKUHON_R2\n ? new R2TakuhonAssetStorage(env.TAKUHON_R2, { publicBaseUrl: url.origin })\n : undefined;\n const cachePurger: CachePurger = new CloudflareCachePurger(() => caches.default, {\n origin: url.origin,\n });\n const auditLogger: AuditLogger = consoleAuditLogger;\n\n // `getPath` strips a leading `/{locale}` prefix before route\n // matching. This is the production-critical placement: Hono's\n // `route()` flattens each mounted sub-app's routes into this router\n // and dispatches with this router's `getPath` only, so a `getPath`\n // set on the public sub-app alone would not run here. The shared\n // function's allowlist guard never strips admin remainders, so\n // `/api/admin/*` and `/admin/*` mounts stay locale-agnostic.\n const router = new Hono({ getPath: localePrefixGetPath });\n router.notFound((c) =>\n problemResponse(c, {\n slug: ERROR_SLUGS.notFound,\n status: 404,\n title: 'Not Found',\n detail: `No route matches ${new URL(c.req.url).pathname}.`,\n }),\n );\n router.route(\n '/api/admin',\n createAdminApiApp({\n storage,\n assetStorage,\n getAdminToken: () => env.TAKUHON_ADMIN_TOKEN,\n getAdminOrigins: () => parseOrigins(env.TAKUHON_ADMIN_ORIGIN),\n cachePurger,\n auditLogger,\n }),\n );\n router.route('/admin', createAdminUiApp());\n router.route(\n '/',\n createPublicApp({\n storage,\n fallback: opts.fallback,\n // Serves `GET /api/activity` from the synced snapshot; the route\n // 404s while no snapshot is stored or activity is not enabled.\n activityStorage: new KvActivityStorage(env.TAKUHON_KV),\n // Advertise the read-only MCP endpoint in `/.well-known/takuhon.json`.\n mcpPath: '/mcp',\n }),\n );\n\n return router.fetch(request, env);\n },\n\n /**\n * Cron-driven activity sync (enable with a `[triggers] crons` entry in\n * `wrangler.toml`; daily is the recommended cadence). MUST NOT throw: a\n * failed sync keeps the last-known snapshot and surfaces through the\n * structured log line below (captured by Workers Tail / Logpush), never by\n * failing the cron run.\n */\n async scheduled(_controller: ScheduledController, env: Env, _ctx: ExecutionContext) {\n const timestamp = new Date().toISOString();\n try {\n const result = await syncActivity({\n profileStorage: new KvTakuhonStorage(env.TAKUHON_KV),\n activityStorage: new KvActivityStorage(env.TAKUHON_KV),\n secrets: {\n githubToken: env.TAKUHON_GITHUB_TOKEN,\n wakatimeKey: env.TAKUHON_WAKATIME_KEY,\n },\n fallback: opts.fallback,\n });\n const type =\n result.status === 'synced'\n ? 'activity.sync.success'\n : result.status === 'empty'\n ? 'activity.sync.failure'\n : 'activity.sync.skipped';\n console.log(\n JSON.stringify({ type, timestamp, reason: result.reason, failures: result.failures }),\n );\n } catch (err) {\n console.error(\n JSON.stringify({\n type: 'activity.sync.failure',\n timestamp,\n reason: err instanceof Error ? err.message : String(err),\n failures: [],\n }),\n );\n }\n },\n };\n}\n\nfunction bundledFallback(): Takuhon {\n const r = validate(exampleJson);\n if (!r.ok) throw new Error('Bundled fixture failed validation.');\n return r.data;\n}\n\nexport default createTakuhonWorker({ fallback: bundledFallback });\n","{\n \"schemaVersion\": \"0.5.0\",\n \"profile\": {\n \"displayName\": {\n \"en\": \"Pat Rivera\",\n \"ja\": \"パット・リベラ\"\n },\n \"tagline\": {\n \"en\": \"Open-source maintainer and accessibility advocate\",\n \"ja\": \"オープンソースメンテナ / アクセシビリティ実践者\"\n },\n \"bio\": {\n \"en\": \"Pat Rivera is a fictional persona used to exercise every field of the takuhon profile schema. They maintain a handful of open-source libraries focused on accessibility tooling and frequently speak at local meetups.\",\n \"ja\": \"Pat Rivera は takuhon プロフィール schema の全フィールドを示すための架空の人物です。アクセシビリティ系のオープンソースライブラリを保守し、地域コミュニティで登壇しています。\"\n },\n \"avatar\": {\n \"url\": \"/assets/avatar.webp\",\n \"alt\": {\n \"en\": \"Pat Rivera smiling, wearing round glasses, in front of a soft gradient.\",\n \"ja\": \"ソフトなグラデーションを背景に微笑む Pat Rivera。丸眼鏡を着用。\"\n }\n },\n \"location\": {\n \"country\": \"PT\",\n \"region\": \"Lisbon\",\n \"locality\": {\n \"en\": \"Lisbon\",\n \"ja\": \"リスボン\"\n },\n \"display\": {\n \"en\": \"Lisbon, Portugal\",\n \"ja\": \"ポルトガル・リスボン\"\n }\n }\n },\n \"links\": [\n {\n \"id\": \"website\",\n \"type\": \"website\",\n \"label\": { \"en\": \"Personal site\" },\n \"url\": \"https://example.com/pat\",\n \"featured\": true,\n \"order\": 0\n },\n {\n \"id\": \"github\",\n \"type\": \"github\",\n \"url\": \"https://example.com/pat/github\",\n \"featured\": true,\n \"order\": 1\n },\n {\n \"id\": \"mastodon\",\n \"type\": \"mastodon\",\n \"url\": \"https://example.social/@pat\",\n \"order\": 2\n },\n {\n \"id\": \"blog\",\n \"type\": \"blog\",\n \"label\": {\n \"en\": \"Notes on accessible UI\",\n \"ja\": \"アクセシブル UI の覚え書き\"\n },\n \"url\": \"https://example.com/pat/blog\",\n \"order\": 3\n },\n {\n \"id\": \"newsletter\",\n \"type\": \"custom\",\n \"label\": {\n \"en\": \"Weekly newsletter\",\n \"ja\": \"週次ニュースレター\"\n },\n \"url\": \"https://example.com/pat/newsletter\",\n \"iconUrl\": \"https://example.com/assets/icons/newsletter.svg\",\n \"order\": 4\n }\n ],\n \"careers\": [\n {\n \"id\": \"stellar-ux\",\n \"organization\": {\n \"en\": \"Stellar UX Studio\",\n \"ja\": \"ステラ UX スタジオ\"\n },\n \"role\": {\n \"en\": \"Principal Accessibility Engineer\",\n \"ja\": \"プリンシパル・アクセシビリティエンジニア\"\n },\n \"description\": {\n \"en\": \"Lead the accessibility engineering practice across product surfaces, drive WCAG 2.2 conformance reviews, and mentor a team of five engineers.\",\n \"ja\": \"プロダクト全体のアクセシビリティ設計を統括。WCAG 2.2 適合レビューを主導し、5 名のエンジニアをメンタリング。\"\n },\n \"startDate\": \"2023-04\",\n \"endDate\": null,\n \"isCurrent\": true,\n \"url\": \"https://example.com/stellar\",\n \"order\": 0\n },\n {\n \"id\": \"harbor-labs\",\n \"organization\": {\n \"en\": \"Harbor Labs\",\n \"ja\": \"ハーバーラボ\"\n },\n \"role\": {\n \"en\": \"Senior Frontend Engineer\",\n \"ja\": \"シニアフロントエンドエンジニア\"\n },\n \"description\": {\n \"en\": \"Built the design system foundation and an accessible component library used across nine internal products.\",\n \"ja\": \"デザインシステム基盤と、社内 9 プロダクトで利用されるアクセシブルなコンポーネントライブラリを設計。\"\n },\n \"startDate\": \"2019-06\",\n \"endDate\": \"2023-03\",\n \"order\": 1\n }\n ],\n \"projects\": [\n {\n \"id\": \"axe-helpers\",\n \"title\": {\n \"en\": \"axe-helpers\",\n \"ja\": \"axe-helpers\"\n },\n \"description\": {\n \"en\": \"A tiny set of utilities that wraps axe-core for use in component-level integration tests.\",\n \"ja\": \"コンポーネント単位の統合テスト向けに axe-core をラップする小規模ユーティリティ集。\"\n },\n \"url\": \"https://example.com/axe-helpers\",\n \"tags\": [\"accessibility\", \"testing\", \"typescript\"],\n \"relatedCareerId\": \"stellar-ux\",\n \"startDate\": \"2023-09\",\n \"highlighted\": true,\n \"order\": 0\n },\n {\n \"id\": \"color-contrast-cli\",\n \"title\": {\n \"en\": \"color-contrast-cli\",\n \"ja\": \"color-contrast-cli\"\n },\n \"description\": {\n \"en\": \"Command-line tool that audits design tokens for WCAG contrast ratios.\",\n \"ja\": \"デザイントークンの WCAG コントラスト比を監査するコマンドラインツール。\"\n },\n \"url\": \"https://example.com/color-contrast-cli\",\n \"tags\": [\"accessibility\", \"cli\", \"design-tokens\"],\n \"startDate\": \"2021-02\",\n \"endDate\": \"2022-08\",\n \"order\": 1\n },\n {\n \"id\": \"meetup-talks\",\n \"title\": {\n \"en\": \"Local meetup talks\",\n \"ja\": \"地域コミュニティ登壇\"\n },\n \"order\": 2\n }\n ],\n \"skills\": [\n { \"id\": \"typescript\", \"label\": \"TypeScript\", \"category\": \"programming\", \"order\": 0 },\n { \"id\": \"react\", \"label\": \"React\", \"category\": \"programming\", \"order\": 1 },\n { \"id\": \"wcag-2-2\", \"label\": \"WCAG 2.2\", \"category\": \"design\", \"order\": 2 },\n { \"id\": \"aria\", \"label\": \"ARIA\", \"category\": \"design\", \"order\": 3 },\n { \"id\": \"storybook\", \"label\": \"Storybook\", \"category\": \"programming\", \"order\": 4 },\n { \"id\": \"playwright\", \"label\": \"Playwright\", \"category\": \"programming\", \"order\": 5 },\n { \"id\": \"design-tokens\", \"label\": \"Design tokens\", \"category\": \"design\", \"order\": 6 },\n { \"id\": \"portuguese\", \"label\": \"Portuguese (B2)\", \"category\": \"language\", \"order\": 7 }\n ],\n \"certifications\": [\n {\n \"id\": \"iaap-cpacc-2022\",\n \"title\": {\n \"en\": \"Certified Professional in Accessibility Core Competencies (CPACC)\",\n \"ja\": \"アクセシビリティ専門家認定 (CPACC)\"\n },\n \"issuingOrganization\": {\n \"en\": \"International Association of Accessibility Professionals\",\n \"ja\": \"国際アクセシビリティ専門家協会\"\n },\n \"issueDate\": \"2022-06\",\n \"expirationDate\": null,\n \"credentialId\": \"CPACC-2022-PR-09231\",\n \"url\": \"https://example.org/iaap/credentials/cpacc\",\n \"order\": 0\n },\n {\n \"id\": \"iaap-was-2023\",\n \"title\": {\n \"en\": \"Web Accessibility Specialist (WAS)\",\n \"ja\": \"Web アクセシビリティスペシャリスト (WAS)\"\n },\n \"issuingOrganization\": {\n \"en\": \"International Association of Accessibility Professionals\",\n \"ja\": \"国際アクセシビリティ専門家協会\"\n },\n \"issueDate\": \"2023-03\",\n \"expirationDate\": \"2026-03\",\n \"credentialId\": \"WAS-2023-PR-04522\",\n \"url\": \"https://example.org/iaap/credentials/was\",\n \"order\": 1\n }\n ],\n \"memberships\": [\n {\n \"id\": \"iaap-senior\",\n \"organization\": {\n \"en\": \"International Association of Accessibility Professionals\",\n \"ja\": \"国際アクセシビリティ専門家協会\"\n },\n \"role\": {\n \"en\": \"Senior Member\",\n \"ja\": \"シニアメンバー\"\n },\n \"description\": {\n \"en\": \"Active participant in the Web Accessibility special interest group, contributing to monthly working sessions on WCAG 2.2 interpretation.\",\n \"ja\": \"Web Accessibility 専門部会に参加し、WCAG 2.2 解釈の月例セッションに貢献。\"\n },\n \"startDate\": \"2022-09\",\n \"endDate\": null,\n \"isCurrent\": true,\n \"url\": \"https://example.org/iaap\",\n \"order\": 0\n }\n ],\n \"volunteering\": [\n {\n \"id\": \"code-org-instructor\",\n \"organization\": {\n \"en\": \"Code.org\",\n \"ja\": \"Code.org\"\n },\n \"role\": {\n \"en\": \"Volunteer Instructor\",\n \"ja\": \"ボランティア講師\"\n },\n \"cause\": {\n \"en\": \"Education\",\n \"ja\": \"教育\"\n },\n \"description\": {\n \"en\": \"Taught introductory programming concepts to middle-school students in weekend workshops, with a focus on inclusive curriculum design.\",\n \"ja\": \"週末ワークショップで中学生にプログラミング入門を指導。インクルーシブなカリキュラム設計を重視。\"\n },\n \"startDate\": \"2021-09\",\n \"endDate\": \"2024-06\",\n \"isCurrent\": false,\n \"url\": \"https://example.org/code-org\",\n \"order\": 0\n }\n ],\n \"honors\": [\n {\n \"id\": \"wai-recognized-contributor-2024\",\n \"title\": {\n \"en\": \"Recognized Contributor, Web Accessibility Initiative\",\n \"ja\": \"Web Accessibility Initiative 貢献者表彰\"\n },\n \"issuer\": {\n \"en\": \"W3C Web Accessibility Initiative\",\n \"ja\": \"W3C Web Accessibility Initiative\"\n },\n \"description\": {\n \"en\": \"Recognized for sustained reviews of the WCAG 2.2 Authoring Practices documentation and for contributions to non-visual focus management patterns.\",\n \"ja\": \"WCAG 2.2 Authoring Practices ドキュメントの継続レビュー、および非視覚的フォーカス管理パターンへの貢献を評価。\"\n },\n \"date\": \"2024-04\",\n \"url\": \"https://example.org/wai/recognitions/2024\",\n \"order\": 0\n }\n ],\n \"education\": [\n {\n \"id\": \"westbrook-bsc-2018\",\n \"institution\": {\n \"en\": \"Westbrook University\",\n \"ja\": \"ウェストブルック大学\"\n },\n \"degree\": {\n \"en\": \"BSc in Cognitive Science\",\n \"ja\": \"認知科学学士\"\n },\n \"fieldOfStudy\": {\n \"en\": \"Human-Computer Interaction\",\n \"ja\": \"ヒューマン・コンピュータ・インタラクション\"\n },\n \"description\": {\n \"en\": \"Senior thesis on screen-reader interaction patterns for non-visual users navigating dynamic web interfaces.\",\n \"ja\": \"卒業研究は動的 Web インターフェースを操作する非視覚ユーザー向けスクリーンリーダー対話パターンの分析。\"\n },\n \"grade\": \"magna cum laude\",\n \"startDate\": \"2014-09\",\n \"endDate\": \"2018-06\",\n \"isCurrent\": false,\n \"url\": \"https://example.org/westbrook\",\n \"order\": 0\n }\n ],\n \"publications\": [\n {\n \"id\": \"design-tokens-wcag-2024\",\n \"title\": {\n \"en\": \"Auditing Design Tokens for WCAG 2.2 Conformance\",\n \"ja\": \"デザイントークンの WCAG 2.2 適合性監査\"\n },\n \"publisher\": {\n \"en\": \"ACM SIGACCESS\",\n \"ja\": \"ACM SIGACCESS\"\n },\n \"description\": {\n \"en\": \"A method for statically analyzing design-token files to surface color-contrast violations before they reach implementation.\",\n \"ja\": \"実装前にデザイントークンファイルを静的解析し、色コントラスト違反を発見する手法を提案。\"\n },\n \"date\": \"2024-03\",\n \"url\": \"https://example.org/papers/design-tokens-wcag\",\n \"doi\": \"10.1145/3678901.3678910\",\n \"coAuthors\": [\"Jamie Chen\", \"Sofia Almeida\"],\n \"order\": 0\n }\n ],\n \"languages\": [\n {\n \"id\": \"lang-en\",\n \"language\": \"en\",\n \"displayName\": {\n \"en\": \"English\",\n \"ja\": \"英語\"\n },\n \"proficiency\": \"native\",\n \"order\": 0\n },\n {\n \"id\": \"lang-pt\",\n \"language\": \"pt\",\n \"displayName\": {\n \"en\": \"Portuguese\",\n \"ja\": \"ポルトガル語\"\n },\n \"proficiency\": \"intermediate\",\n \"order\": 1\n }\n ],\n \"courses\": [\n {\n \"id\": \"coursera-advanced-a11y-2023\",\n \"title\": {\n \"en\": \"Advanced Web Accessibility Patterns\",\n \"ja\": \"Web アクセシビリティ応用パターン\"\n },\n \"provider\": {\n \"en\": \"Coursera (University of Michigan)\",\n \"ja\": \"Coursera (ミシガン大学)\"\n },\n \"courseNumber\": \"UMICH-A11Y-301\",\n \"description\": {\n \"en\": \"Covered ARIA authoring practices, accessible component patterns, and assistive technology testing workflows.\",\n \"ja\": \"ARIA オーサリングプラクティス、アクセシブルコンポーネントパターン、支援技術テストワークフローを扱う。\"\n },\n \"completionDate\": \"2023-11\",\n \"certificateUrl\": \"https://example.org/certificates/coursera/UMICH-A11Y-301\",\n \"order\": 0\n }\n ],\n \"patents\": [\n {\n \"id\": \"us-2024-a11y-test\",\n \"title\": {\n \"en\": \"Method and System for Automated Screen-Reader Output Verification\",\n \"ja\": \"スクリーンリーダー出力の自動検証手法およびシステム\"\n },\n \"patentNumber\": \"US 99,999,999 B2\",\n \"office\": \"USPTO\",\n \"status\": \"issued\",\n \"description\": {\n \"en\": \"An automated harness that captures screen-reader output and compares it against an authoring-time accessibility annotation.\",\n \"ja\": \"スクリーンリーダーの出力を自動的にキャプチャし、オーサリング時のアクセシビリティ注釈と照合する自動化ハーネス。\"\n },\n \"filingDate\": \"2022-04\",\n \"grantDate\": \"2024-03\",\n \"url\": \"https://patents.example.org/us-11987654\",\n \"coInventors\": [\"Jamie Chen\"],\n \"order\": 0\n },\n {\n \"id\": \"us-pending-focus\",\n \"title\": {\n \"en\": \"Predictive Focus Management for Single-Page Applications\",\n \"ja\": \"シングルページアプリケーション向け予測的フォーカス管理\"\n },\n \"patentNumber\": \"US 99/999,999\",\n \"office\": \"USPTO\",\n \"status\": \"pending\",\n \"description\": {\n \"en\": \"An algorithm that predicts the next likely focus target based on user navigation patterns to reduce focus loss in dynamic content.\",\n \"ja\": \"ユーザーのナビゲーションパターンから次のフォーカス候補を予測し、動的コンテンツにおけるフォーカス喪失を低減するアルゴリズム。\"\n },\n \"filingDate\": \"2024-08\",\n \"url\": \"https://patents.example.org/us-pending-focus\",\n \"order\": 1\n }\n ],\n \"testScores\": [\n {\n \"id\": \"celpe-bras-2023\",\n \"title\": {\n \"en\": \"CELPE-Bras (Brazilian Portuguese Proficiency)\",\n \"ja\": \"CELPE-Bras (ブラジルポルトガル語能力試験)\"\n },\n \"score\": \"Upper Intermediate\",\n \"date\": \"2023-05\",\n \"description\": {\n \"en\": \"Brazilian government certificate of proficiency in Portuguese as a foreign language.\",\n \"ja\": \"ブラジル政府認定の外国語としてのポルトガル語能力証明書。\"\n },\n \"url\": \"https://example.org/scores/celpe-bras-2023\",\n \"order\": 0\n },\n {\n \"id\": \"gre-general-2013\",\n \"title\": {\n \"en\": \"GRE General Test\",\n \"ja\": \"GRE 一般試験\"\n },\n \"score\": \"332 / 340\",\n \"date\": \"2013-10\",\n \"relatedEducationId\": \"westbrook-bsc-2018\",\n \"description\": {\n \"en\": \"Combined verbal and quantitative reasoning score submitted with the Westbrook University application.\",\n \"ja\": \"ウェストブルック大学出願時に提出した言語・数的推論の合計スコア。\"\n },\n \"url\": \"https://example.org/scores/gre-general-2013\",\n \"order\": 1\n }\n ],\n \"recommendations\": [\n {\n \"id\": \"rec-jordan-stellar\",\n \"body\": {\n \"en\": \"Pat is one of the most rigorous accessibility engineers I have worked with. They turned our design-token audit into a repeatable process and mentored the whole team on assistive-technology testing.\",\n \"ja\": \"Pat は私が共に働いた中で最も厳格なアクセシビリティエンジニアの一人です。デザイントークン監査を再現可能なプロセスに変え、支援技術テストについてチーム全体を指導してくれました。\"\n },\n \"author\": {\n \"name\": \"Jordan Avery\",\n \"headline\": {\n \"en\": \"Engineering Manager at Stellar UX\",\n \"ja\": \"Stellar UX エンジニアリングマネージャー\"\n },\n \"url\": \"https://example.org/in/jordan-avery\"\n },\n \"relationship\": {\n \"en\": \"Managed Pat directly at Stellar UX\",\n \"ja\": \"Stellar UX で Pat を直属で管理\"\n },\n \"date\": \"2023-09\",\n \"relatedCareerId\": \"stellar-ux\",\n \"order\": 0\n },\n {\n \"id\": \"rec-sofia-harbor\",\n \"body\": {\n \"en\": \"I collaborated with Pat on a screen-reader testing harness at Harbor Labs. Their attention to non-visual user journeys raised the bar for the entire engineering org.\",\n \"ja\": \"Harbor Labs でスクリーンリーダーのテストハーネスを Pat と共同開発しました。非視覚ユーザーの導線への配慮は、エンジニアリング組織全体の基準を引き上げました。\"\n },\n \"author\": {\n \"name\": \"Sofia Almeida\",\n \"headline\": {\n \"en\": \"Staff Engineer at Harbor Labs\",\n \"ja\": \"Harbor Labs スタッフエンジニア\"\n }\n },\n \"date\": \"2021-06\",\n \"relatedCareerId\": \"harbor-labs\",\n \"order\": 1\n }\n ],\n \"contact\": {\n \"email\": \"pat@example.com\",\n \"showEmail\": false,\n \"formUrl\": \"https://example.com/pat/contact\"\n },\n \"settings\": {\n \"defaultLocale\": \"en\",\n \"fallbackLocale\": \"en\",\n \"availableLocales\": [\"en\", \"ja\"],\n \"theme\": \"default\",\n \"showPoweredBy\": true,\n \"enableJsonLd\": true,\n \"enableApi\": true,\n \"enableAnalytics\": false\n },\n \"meta\": {\n \"createdAt\": \"2026-01-15T09:00:00Z\",\n \"updatedAt\": \"2026-05-12T08:30:00Z\",\n \"generator\": \"Takuhon\",\n \"contentLicense\": {\n \"spdxId\": \"CC-BY-4.0\",\n \"url\": \"https://creativecommons.org/licenses/by/4.0/\",\n \"attribution\": {\n \"name\": \"Pat Rivera\",\n \"url\": \"https://example.com/pat\"\n }\n },\n \"privacy\": {\n \"hideCredentialIds\": true,\n \"hideEducationGrades\": true\n }\n }\n}\n","/**\n * Scheduled developer-activity sync for the Cloudflare worker.\n *\n * The cron-driven counterpart of the CLI's `takuhon activity sync`: it reads\n * `settings.activity` from the stored profile, fetches the configured GitHub /\n * WakaTime metrics through `@takuhon/activity`, and persists the snapshot via\n * an `ActivityStorage` — so public rendering stays a static read.\n *\n * The function reports its outcome instead of throwing wherever it can, and a\n * sync that gathers no data never overwrites a good snapshot: the last-known\n * one is kept and the result says so, letting the `scheduled` handler log the\n * failure without failing the cron run.\n */\n\nimport { fetchActivitySnapshot, isEmptySnapshot, type ActivitySecrets } from '@takuhon/activity';\nimport {\n NotFoundError,\n type ActivityStorage,\n type Takuhon,\n type TakuhonStorage,\n} from '@takuhon/core';\n\nexport interface ActivitySyncOptions {\n /** Source of the profile whose `settings.activity` configures the sync. */\n profileStorage: TakuhonStorage;\n /** Destination for the synced snapshot. */\n activityStorage: ActivityStorage;\n /** Secrets from the Worker env; never persisted. */\n secrets: ActivitySecrets;\n /** Fallback profile used when storage has none (same as the public app). */\n fallback?: () => Takuhon;\n /** HTTP client override for tests. Defaults to the global `fetch`. */\n fetch?: typeof fetch;\n /** Clock for `lastSyncedAt`; injectable for deterministic tests. */\n now?: () => Date;\n}\n\n/** A per-source fetch failure, with any secret value already redacted. */\nexport interface ActivitySourceFailure {\n source: string;\n message: string;\n}\n\nexport interface ActivitySyncResult {\n /**\n * `synced` — a snapshot was gathered and stored. `skipped` — activity is not\n * configured / enabled, so nothing was attempted. `empty` — every configured\n * source came back empty or failing; the last-known snapshot was kept.\n */\n status: 'synced' | 'skipped' | 'empty';\n /** Why a `skipped` / `empty` run did not store anything. */\n reason?: string;\n /** Per-source failures reported by the fetcher (may be non-empty on `synced`). */\n failures: ActivitySourceFailure[];\n}\n\n/** Remove any literal occurrence of the secrets from a string before it is logged. */\nfunction redactSecrets(text: string, secrets: ActivitySecrets): string {\n let out = text;\n for (const secret of [secrets.githubToken, secrets.wakatimeKey]) {\n if (secret !== undefined && secret !== '') out = out.split(secret).join('***');\n }\n return out;\n}\n\n/**\n * Run one activity sync. Resolves with a result for every configuration state;\n * it only rejects if the storage layer itself fails (the `scheduled` handler\n * catches that, so a broken sync never fails the cron run).\n */\nexport async function syncActivity(opts: ActivitySyncOptions): Promise<ActivitySyncResult> {\n let profile: Takuhon;\n try {\n profile = (await opts.profileStorage.getProfile()).data;\n } catch (err) {\n if (err instanceof NotFoundError && opts.fallback) {\n profile = opts.fallback();\n } else if (err instanceof NotFoundError) {\n return { status: 'skipped', reason: 'no stored profile', failures: [] };\n } else {\n throw err;\n }\n }\n\n const config = profile.settings.activity;\n if (config?.enabled !== true) {\n return {\n status: 'skipped',\n reason: 'activity is not enabled in settings.activity',\n failures: [],\n };\n }\n const hasGithub = config.github?.username !== undefined && config.github.username !== '';\n const hasWakatime = config.wakatime?.username !== undefined && config.wakatime.username !== '';\n if (!hasGithub && !hasWakatime) {\n return { status: 'skipped', reason: 'no github or wakatime username configured', failures: [] };\n }\n\n const failures: ActivitySourceFailure[] = [];\n const snapshot = await fetchActivitySnapshot(config, opts.secrets, {\n fetch: opts.fetch,\n now: opts.now,\n onError: (source, err) => {\n const detail = err instanceof Error ? err.message : String(err);\n failures.push({ source, message: redactSecrets(detail, opts.secrets) });\n },\n });\n\n if (isEmptySnapshot(snapshot)) {\n // Never replace a good snapshot with an empty one (design §6): keep the\n // last-known document and report the dry run.\n return {\n status: 'empty',\n reason: 'the sync gathered no activity data; kept the last-known snapshot',\n failures,\n };\n }\n\n await opts.activityStorage.saveActivitySnapshot(snapshot);\n return { status: 'synced', failures };\n}\n","import type { CachePurger } from '@takuhon/api';\n\nexport interface CloudflareCachePurgerOptions {\n /**\n * Absolute origin (e.g. `https://example.com`) used to build the URLs\n * passed to `Cache.delete`. The Worker derives this from the incoming\n * request's URL so the same code works under any production hostname.\n */\n origin: string;\n /**\n * Locale codes to include when purging language-keyed cache entries.\n * Cloudflare caches `?lang=` query variants as distinct keys; we purge\n * a representative set on every write. Adapters can extend the list to\n * cover other locales the deploy serves.\n */\n langs?: string[];\n}\n\n/**\n * `CachePurger` backed by Cloudflare's colo-local `caches.default`.\n *\n * Limitations (documented in the adapter README):\n * - Cloudflare's `Cache.delete` clears the current colo only, not the\n * entire edge. Other colos honour the response's `Cache-Control`\n * `s-maxage` (5 minutes today) before refreshing.\n * - Truly global invalidation requires the REST `/purge_cache` API,\n * which needs a zone-scoped token; that's deferred to a later phase.\n */\nexport class CloudflareCachePurger implements CachePurger {\n private readonly origin: string;\n private readonly langs: string[];\n\n /**\n * `getCache` is a thunk so the Workers-only `caches` global is touched\n * lazily — public-only requests on this Worker never run admin handlers\n * and must not pay (or fail under Node tests) for the lookup.\n */\n constructor(\n private readonly getCache: () => Cache,\n opts: CloudflareCachePurgerOptions,\n ) {\n this.origin = opts.origin.replace(/\\/$/, '');\n this.langs = opts.langs ?? ['en', 'ja'];\n }\n\n async profileUpdated(): Promise<void> {\n await this.purge();\n }\n\n async profileDeleted(): Promise<void> {\n await this.purge();\n }\n\n private async purge(): Promise<void> {\n const cache = this.getCache();\n const targets = ['/', '/api/profile', '/api/jsonld', '/takuhon.json'];\n for (const lang of this.langs) {\n const q = `?lang=${encodeURIComponent(lang)}`;\n targets.push(`/api/profile${q}`, `/api/jsonld${q}`);\n }\n for (const path of targets) {\n await cache.delete(new Request(this.origin + path), { ignoreMethod: true });\n }\n }\n}\n","import type { AuditEvent, AuditLogger } from '@takuhon/api';\n\n/**\n * `AuditLogger` that writes a single line of JSON per event to `console.log`.\n *\n * Cloudflare captures these via Workers Tail / Logpush, where they can be\n * routed to R2, S3, or any downstream SIEM. Token bodies never reach this\n * sink — the upstream middleware only emits `sha256:<hex>` digests in\n * `actor.tokenHash`.\n */\nexport const consoleAuditLogger: AuditLogger = (event: AuditEvent): void => {\n console.log(JSON.stringify(event));\n};\n","import { isActivitySnapshot, type ActivitySnapshot, type ActivityStorage } from '@takuhon/core';\n\n/**\n * KV key holding the synced developer-activity snapshot. A second key in the\n * same namespace as the profile (`TAKUHON_DATA`): the snapshot is a sibling\n * document, machine-written by the scheduled sync and deliberately kept out of\n * the canonical profile document.\n */\nexport const ACTIVITY_KV_KEY = 'TAKUHON_ACTIVITY';\n\n/**\n * Cloudflare KV implementation of the `ActivityStorage` contract.\n *\n * Reads are forgiving: an absent, unparseable, or malformed value resolves to\n * `null` (never throws), because a missing snapshot is the normal pre-sync /\n * opt-out state and the renderer simply omits the activity section. Writes are\n * last-writer-wins with no version metadata — the snapshot carries its own\n * `lastSyncedAt`, and the only writer is the scheduled sync.\n */\nexport class KvActivityStorage implements ActivityStorage {\n constructor(private readonly kv: KVNamespace) {}\n\n async getActivitySnapshot(): Promise<ActivitySnapshot | null> {\n const raw = await this.kv.get(ACTIVITY_KV_KEY, 'text');\n if (raw === null) return null;\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n // A corrupt snapshot is treated as absent, not fatal: the next sync\n // rewrites it whole, and the renderer omits the section meanwhile.\n return null;\n }\n return isActivitySnapshot(parsed) ? parsed : null;\n }\n\n async saveActivitySnapshot(snapshot: ActivitySnapshot): Promise<void> {\n await this.kv.put(ACTIVITY_KV_KEY, JSON.stringify(snapshot));\n }\n}\n","import { ConflictError, NotFoundError, type Takuhon, type TakuhonStorage } from '@takuhon/core';\n\nexport const KV_KEY = 'TAKUHON_DATA';\n\nexport interface KvMetadata {\n version: string;\n updatedAt: string;\n}\n\n/**\n * Cloudflare KV implementation of the `TakuhonStorage` contract. Stores the\n * profile document as JSON under a single key (`TAKUHON_DATA`) and tracks the\n * optimistic-locking token inside KV value metadata.\n *\n * `version` is a fresh UUIDv4 on every successful write. Callers compare it\n * verbatim against the `If-Match` precondition; mismatches raise\n * `ConflictError` with `currentVersion` so the API layer can build the RFC\n * 7807 envelope without an extra round trip.\n */\nexport class KvTakuhonStorage implements TakuhonStorage {\n constructor(private readonly kv: KVNamespace) {}\n\n async getProfile(): Promise<{ data: Takuhon; version: string }> {\n const result = await this.kv.getWithMetadata<Takuhon, KvMetadata>(KV_KEY, 'json');\n if (result.value === null || !result.metadata?.version) {\n throw new NotFoundError(`No profile is stored at KV key \"${KV_KEY}\".`);\n }\n return { data: result.value, version: result.metadata.version };\n }\n\n async saveProfile(data: Takuhon, ifMatch?: string): Promise<{ version: string }> {\n if (ifMatch !== undefined) {\n const current = await this.kv.getWithMetadata<Takuhon, KvMetadata>(KV_KEY, 'json');\n const currentVersion = current.metadata?.version;\n if (currentVersion !== ifMatch) {\n throw new ConflictError(\n `If-Match preconditioned on version \"${ifMatch}\" but current is \"${currentVersion ?? 'absent'}\".`,\n { currentVersion },\n );\n }\n }\n const version = crypto.randomUUID();\n const updatedAt = new Date().toISOString();\n await this.kv.put(KV_KEY, JSON.stringify(data), {\n metadata: { version, updatedAt } satisfies KvMetadata,\n });\n return { version };\n }\n\n async deleteProfile(): Promise<void> {\n await this.kv.delete(KV_KEY);\n }\n}\n","/**\n * Read-only MCP endpoint (`GET`/`POST /mcp`) for the Cloudflare adapter.\n *\n * The deployed profile is exposed over the Model Context Protocol so any MCP\n * client can read it — the remote counterpart of the CLI's `takuhon mcp`. It\n * reuses the transport-agnostic server from `@takuhon/mcp` (the same core\n * catalog the CLI serves) and connects it to the SDK's **Web Standard**\n * Streamable HTTP transport, which speaks `fetch` `Request`/`Response` and runs\n * on Workers.\n *\n * Stateless by design — no Durable Object, no new binding, no session: each\n * request builds a fresh server + transport (the SDK requires a fresh transport\n * per request in stateless mode, and skips session validation), reads the\n * profile from the existing KV, and answers. `enableJsonResponse` returns a\n * single JSON body instead of an SSE stream, which suits read-only\n * request/response with no server-initiated messages.\n *\n * Unauthenticated public read, at parity with `GET /api/profile`: every answer\n * is privacy-filtered by core and no admin/write surface is exposed.\n */\n\nimport { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';\nimport { NotFoundError, type Takuhon } from '@takuhon/core';\nimport { createTakuhonMcpServer } from '@takuhon/mcp';\n\nimport { KvTakuhonStorage } from './kv-storage.js';\n\nconst MCP_SERVER_NAME = 'takuhon';\n\n/**\n * Handle one MCP request against the KV-backed profile. Builds a fresh,\n * stateless server + transport per request and returns its `Response` with\n * `nosniff` and a no-store cache policy layered on.\n */\nexport async function serveMcp(\n request: Request,\n kv: KVNamespace,\n fallback: () => Takuhon,\n): Promise<Response> {\n const storage = new KvTakuhonStorage(kv);\n const loadProfile = async (): Promise<Takuhon> => {\n try {\n return (await storage.getProfile()).data;\n } catch (e) {\n // Before the first admin write KV is empty; serve the bundled fixture,\n // exactly as the public API's loadProfile does.\n if (e instanceof NotFoundError) return fallback();\n throw e;\n }\n };\n\n const server = createTakuhonMcpServer({ loadProfile, name: MCP_SERVER_NAME });\n // Stateless: `sessionIdGenerator: undefined` disables sessions, and the SDK\n // requires a fresh transport per request in this mode (it rejects reuse to\n // avoid cross-client message-id collisions). `enableJsonResponse` returns a\n // buffered JSON body rather than an SSE stream.\n const transport = new WebStandardStreamableHTTPServerTransport({\n sessionIdGenerator: undefined,\n enableJsonResponse: true,\n });\n await server.connect(transport);\n\n // The server / transport are per-request locals with no cross-request state,\n // so the Worker can let them be collected when the request ends.\n const response = await transport.handleRequest(request);\n\n const headers = new Headers(response.headers);\n headers.set('x-content-type-options', 'nosniff');\n if (!headers.has('cache-control')) headers.set('cache-control', 'no-store');\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n}\n","import {\n ACCEPTED_IMAGE_MIME_TYPES,\n detectImageMime,\n IMAGE_EXTENSIONS,\n NotFoundError,\n readImageInfo,\n type AcceptedImageMime,\n type AssetOptions,\n type AssetRecord,\n type TakuhonAssetStorage,\n} from '@takuhon/core';\n\n/** Key prefix every uploaded asset lives under (`security.md` §4.6). */\nconst ASSET_KEY_PREFIX = 'assets/';\n\n/** Extension used when the bytes are not one of the accepted image types. */\nconst DEFAULT_EXTENSION = 'bin';\n\n/**\n * Long-lived immutable cache policy for delivered assets. Object keys embed a\n * timestamp and a random hash, so a given key never names different bytes —\n * the response can be cached forever.\n */\nexport const ASSET_CACHE_CONTROL = 'public, max-age=31536000, immutable';\n\n/** Narrow an arbitrary content-type string to an accepted image MIME, or null. */\nfunction asAcceptedMime(value: string | undefined): AcceptedImageMime | null {\n if (value === undefined) return null;\n return (ACCEPTED_IMAGE_MIME_TYPES as readonly string[]).includes(value)\n ? (value as AcceptedImageMime)\n : null;\n}\n\n/** Seconds since the Unix epoch, used as the leading object-key component. */\nfunction timestampSeconds(): number {\n return Math.floor(Date.now() / 1000);\n}\n\n/**\n * Four hex characters of cryptographic randomness. Combined with the timestamp\n * this defeats key enumeration (`security.md` §4.7) without an idempotency\n * guarantee — every upload gets a fresh key.\n */\nfunction shortHash(): string {\n const bytes = new Uint8Array(2);\n crypto.getRandomValues(bytes);\n return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');\n}\n\n/**\n * Cloudflare R2 implementation of the {@link TakuhonAssetStorage} contract.\n *\n * The bytes arrive already validated and metadata-stripped by `@takuhon/api`'s\n * admin app (magic-byte check, size / dimension / frame limits, EXIF removal),\n * so this adapter only persists them and mints the public URL. Object keys\n * follow `assets/{timestamp}-{shortHash}.{ext}` (`security.md` §4.6); the file\n * extension comes from `@takuhon/core`'s {@link IMAGE_EXTENSIONS}.\n *\n * Dimensions are read back from the container header via\n * {@link readImageInfo} (no pixel decode) so the returned {@link AssetRecord}\n * is complete; this is the same header-only parse the API layer already\n * performed and matches the deliberate \"no codec\" approach.\n *\n * Assets are served by the Worker's `GET /assets/*` proxy route rather than a\n * public R2 bucket, so `publicUrl` is an absolute URL on the Worker's own\n * origin when `publicBaseUrl` is supplied, falling back to the relative `url`\n * otherwise.\n */\nexport class R2TakuhonAssetStorage implements TakuhonAssetStorage {\n private readonly bucket: R2Bucket;\n private readonly publicBaseUrl: string;\n\n constructor(bucket: R2Bucket, options: { publicBaseUrl?: string } = {}) {\n this.bucket = bucket;\n this.publicBaseUrl = options.publicBaseUrl ?? '';\n }\n\n async putAsset(file: File | Blob, options?: AssetOptions): Promise<AssetRecord> {\n const bytes = new Uint8Array(await file.arrayBuffer());\n // Trust the API-validated content-type when present; otherwise authenticate\n // from the bytes so the adapter is self-sufficient for direct callers.\n const mime = asAcceptedMime(options?.contentType) ?? detectImageMime(bytes);\n const ext = mime !== null ? IMAGE_EXTENSIONS[mime] : DEFAULT_EXTENSION;\n const contentType = mime ?? options?.contentType ?? file.type ?? 'application/octet-stream';\n\n const key = `${ASSET_KEY_PREFIX}${String(timestampSeconds())}-${shortHash()}.${ext}`;\n await this.bucket.put(key, bytes, { httpMetadata: { contentType } });\n\n const info = mime !== null ? readImageInfo(bytes, mime) : null;\n const url = `/${key}`;\n return {\n id: key,\n url,\n publicUrl: this.absoluteUrl(url),\n mimeType: contentType,\n size: bytes.length,\n width: info?.width,\n height: info?.height,\n createdAt: new Date().toISOString(),\n };\n }\n\n async getPublicUrl(assetId: string): Promise<string> {\n const head = await this.bucket.head(assetId);\n if (head === null) throw new NotFoundError(`No asset is stored at R2 key \"${assetId}\".`);\n return this.absoluteUrl(`/${assetId}`);\n }\n\n async deleteAsset(assetId: string): Promise<void> {\n // R2 delete is idempotent: deleting an absent key is a no-op, matching the\n // contract.\n await this.bucket.delete(assetId);\n }\n\n async listAssets(): Promise<AssetRecord[]> {\n const records: AssetRecord[] = [];\n let cursor: string | undefined;\n do {\n const page = await this.bucket.list({ prefix: ASSET_KEY_PREFIX, cursor });\n for (const object of page.objects) {\n const url = `/${object.key}`;\n records.push({\n id: object.key,\n url,\n publicUrl: this.absoluteUrl(url),\n mimeType: object.httpMetadata?.contentType ?? 'application/octet-stream',\n size: object.size,\n createdAt: object.uploaded.toISOString(),\n });\n }\n cursor = page.truncated ? page.cursor : undefined;\n } while (cursor !== undefined);\n return records;\n }\n\n private absoluteUrl(path: string): string {\n return this.publicBaseUrl !== '' ? `${this.publicBaseUrl}${path}` : path;\n }\n}\n"],"mappings":"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","names":["NotFoundError","NotFoundError","NotFoundError","NotFoundError"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takuhon/cloudflare",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "Cloudflare Workers adapter for takuhon — public API, Workers Assets, KV, R2 integrations",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Takuhon contributors",
@@ -44,10 +44,12 @@
44
44
  "node": ">=22.0.0"
45
45
  },
46
46
  "dependencies": {
47
+ "@modelcontextprotocol/sdk": "^1.29.0",
47
48
  "hono": "^4.12.21",
48
- "@takuhon/activity": "0.13.0",
49
- "@takuhon/core": "0.13.0",
50
- "@takuhon/api": "0.13.0"
49
+ "@takuhon/api": "0.14.0",
50
+ "@takuhon/activity": "0.14.0",
51
+ "@takuhon/core": "0.14.0",
52
+ "@takuhon/mcp": "0.14.0"
51
53
  },
52
54
  "devDependencies": {
53
55
  "@cloudflare/workers-types": "^4.20260518.1",