@takuhon/core 1.0.0 → 1.1.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/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  var $schema = "https://json-schema.org/draft/2020-12/schema";
2
- var $id = "https://takuhon.example/schemas/1.0.0/takuhon.schema.json";
2
+ var $id = "https://takuhon.example/schemas/1.1.0/takuhon.schema.json";
3
3
  var title = "Takuhon Profile";
4
4
  var description = "Portable profile data format consumed by @takuhon/core. The canonical contract for profile content authored as takuhon.json.";
5
5
  var type = "object";
@@ -566,6 +566,39 @@ var $defs = {
566
566
  },
567
567
  publicVisibility: {
568
568
  $ref: "#/$defs/PublicVisibility"
569
+ },
570
+ contact: {
571
+ $ref: "#/$defs/ContactSettings"
572
+ }
573
+ }
574
+ },
575
+ ContactSettings: {
576
+ type: "object",
577
+ additionalProperties: false,
578
+ description: "Opt-in contact form configuration (added in 1.1.0). Only public values live here; the Turnstile site key is safe to embed in the page. The Turnstile secret, the recipient address, and the From label are provisioned out of band as environment configuration and are never stored in takuhon.json. Adapters that support the form mount the widget and a POST endpoint when 'enabled' is true.",
579
+ properties: {
580
+ enabled: {
581
+ type: "boolean",
582
+ "default": false,
583
+ description: "Master switch. When false (the default), no contact form is mounted even if a site key is present."
584
+ },
585
+ turnstileSiteKey: {
586
+ type: "string",
587
+ minLength: 1,
588
+ maxLength: 128,
589
+ description: "Public Cloudflare Turnstile site key, safe to embed in the page. Without it the widget is not mounted."
590
+ },
591
+ endpoint: {
592
+ type: "string",
593
+ minLength: 1,
594
+ maxLength: 2048,
595
+ description: "Override the POST endpoint the widget submits to. Defaults to /api/contact on the same origin."
596
+ },
597
+ subjectPrefix: {
598
+ type: "string",
599
+ minLength: 1,
600
+ maxLength: 128,
601
+ description: "Subject prefix for the delivered email, e.g. '[example.com contact]'."
569
602
  }
570
603
  }
571
604
  },
@@ -1882,6 +1915,39 @@ declare const schema: {
1882
1915
  publicVisibility: {
1883
1916
  $ref: string;
1884
1917
  };
1918
+ contact: {
1919
+ $ref: string;
1920
+ };
1921
+ };
1922
+ };
1923
+ ContactSettings: {
1924
+ type: string;
1925
+ additionalProperties: boolean;
1926
+ description: string;
1927
+ properties: {
1928
+ enabled: {
1929
+ type: string;
1930
+ default: boolean;
1931
+ description: string;
1932
+ };
1933
+ turnstileSiteKey: {
1934
+ type: string;
1935
+ minLength: number;
1936
+ maxLength: number;
1937
+ description: string;
1938
+ };
1939
+ endpoint: {
1940
+ type: string;
1941
+ minLength: number;
1942
+ maxLength: number;
1943
+ description: string;
1944
+ };
1945
+ subjectPrefix: {
1946
+ type: string;
1947
+ minLength: number;
1948
+ maxLength: number;
1949
+ description: string;
1950
+ };
1885
1951
  };
1886
1952
  };
1887
1953
  PublicVisibility: {
@@ -2921,6 +2987,26 @@ interface Settings {
2921
2987
  activity?: ActivitySettings;
2922
2988
  /** Per-section public visibility (added in 0.6.0). Absent = all sections visible. */
2923
2989
  publicVisibility?: PublicVisibility;
2990
+ /** Opt-in contact form (added in 1.1.0). Absent = no contact form. */
2991
+ contact?: ContactSettings;
2992
+ }
2993
+ /**
2994
+ * Owner-curated configuration for the opt-in contact form (added in 1.1.0).
2995
+ * Only public values live here: the Turnstile **site key** is safe to embed in
2996
+ * the page. The Turnstile secret, the recipient address, and the From label are
2997
+ * provisioned out of band as environment configuration and are never stored in
2998
+ * `takuhon.json`. Adapters that support the form mount the widget and a POST
2999
+ * endpoint when {@link enabled} is true; adapters that do not simply ignore it.
3000
+ */
3001
+ interface ContactSettings {
3002
+ /** Master switch; no contact form is mounted when false (the default). */
3003
+ enabled?: boolean;
3004
+ /** Public Cloudflare Turnstile site key, safe to embed; required to mount the widget. */
3005
+ turnstileSiteKey?: string;
3006
+ /** Override the POST endpoint the widget submits to. Defaults to `/api/contact` (same origin). */
3007
+ endpoint?: string;
3008
+ /** Subject prefix for the delivered email, e.g. `"[example.com contact]"`. */
3009
+ subjectPrefix?: string;
2924
3010
  }
2925
3011
  /**
2926
3012
  * Per-section public/private visibility (added in 0.6.0).
@@ -3326,7 +3412,7 @@ interface LocalizedTakuhon {
3326
3412
  * versions whose JSON Schema this package literally bundles, not the full
3327
3413
  * support window seen by end users.
3328
3414
  */
3329
- declare const SUPPORTED_SCHEMA_VERSIONS: readonly ["0.1.0", "0.2.0", "0.3.0", "0.4.0", "0.5.0", "0.6.0", "0.7.0", "1.0.0"];
3415
+ declare const SUPPORTED_SCHEMA_VERSIONS: readonly ["0.1.0", "0.2.0", "0.3.0", "0.4.0", "0.5.0", "0.6.0", "0.7.0", "1.0.0", "1.1.0"];
3330
3416
  /**
3331
3417
  * A single validation failure.
3332
3418
  *
@@ -4456,6 +4542,6 @@ declare function stripImageMetadata(bytes: Uint8Array, mime: AcceptedImageMime):
4456
4542
  * A takuhon profile document's `schemaVersion` field must be migrate-compatible
4457
4543
  * with this version. See operational-lifecycle docs for the migration policy.
4458
4544
  */
4459
- declare const SCHEMA_VERSION = "1.0.0";
4545
+ declare const SCHEMA_VERSION = "1.1.0";
4460
4546
 
4461
4547
  export { ACCEPTED_IMAGE_MIME_TYPES, type AcceptedImageMime, type ActivitySettings, type ActivitySnapshot, type ActivityStorage, type Address, type AssetOptions, type AssetRecord, type Avatar, type Career, type Certification, type CodingTime, ConflictError, type Contact, type ContentLicense, type ContentLicenseAttribution, type ContributionCalendar, type ContributionDay, type Course, type CvDocument, type CvHeader, type CvSection, type CvSectionKind, DARK_PALETTE, type Education, type ExportOptions, type ExportedTakuhon, type GravatarOptions, type Honor, IMAGE_EXTENSIONS, type ImageInfo, ImportError, type Iso3166Alpha2, type IsoDateTime, LIGHT_PALETTE, type Language, type LanguageBreakdown, type LanguageProficiency, type Link, type LinkBuiltin, type LinkCustom, type LinkType, type LocaleTag, type LocalizedAddress, type LocalizedAvatar, type LocalizedBody, type LocalizedCareer, type LocalizedCertification, type LocalizedCourse, type LocalizedEducation, type LocalizedHonor, type LocalizedLanguage, type LocalizedLink, type LocalizedLinkBuiltin, type LocalizedLinkCustom, type LocalizedMembership, type LocalizedPatent, type LocalizedProfile, type LocalizedProject, type LocalizedPublication, type LocalizedRecommendation, type LocalizedRecommendationAuthor, type LocalizedTakuhon, type LocalizedTestScore, type LocalizedTitle, type LocalizedVolunteering, MAX_IMAGE_BYTES, MAX_IMAGE_DIMENSION, MAX_IMAGE_FRAMES, MCP_PROFILE_SECTIONS, MCP_RESOURCES, MCP_TOOLS, type McpInputSchema, type McpProfileSection, McpRequestError, type McpResourceDefinition, type McpResourceResult, type McpToolDefinition, type McpToolResult, type Membership, type Meta, type MetaPrivacy, type Migration, MigrationError, type NormalizedTakuhon, NotFoundError, type Palette, type Patent, type PatentStatus, type Profile, type Project, type PublicVisibility, type Publication, RANK_FULL_CODING_HOURS, RANK_FULL_CONTRIBUTIONS, RANK_TIER_THRESHOLDS, type RankInput, type RankTier, type RankTierLabel, type Recommendation, type RecommendationAuthor, type RenderActivitySvgOptions, SCHEMA_VERSION, SUPPORTED_SCHEMA_VERSIONS, type Schema, type Settings, type Skill, type Slug, StorageError, type Takuhon, type TakuhonAssetStorage, type TakuhonStorage, type TestScore, type ValidationError, type ValidationResult, type Volunteering, type YearMonth, applyPublicPrivacyFilter, computeLanguagePercentages, deriveCv, deriveRankTier, detectImageMime, executeMcpTool, exportTakuhon, formatCodingTime, formatDate, generateJsonLd, generatePersonJsonLd, generateProfilePageJsonLd, getPresentLabel, gravatarUrl, importTakuhon, isActivitySnapshot, migrateTakuhon, migrations, normalize, readImageInfo, readMcpResource, renderActivitySvg, resolveLocale, schema, stripImageMetadata, validate };
package/dist/index.js CHANGED
@@ -305,7 +305,7 @@ var require_takuhon_validator = __commonJS({
305
305
  "use strict";
306
306
  module.exports = validate20;
307
307
  module.exports.default = validate20;
308
- var schema31 = { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://takuhon.example/schemas/1.0.0/takuhon.schema.json", "title": "Takuhon Profile", "description": "Portable profile data format consumed by @takuhon/core. The canonical contract for profile content authored as takuhon.json.", "type": "object", "additionalProperties": false, "required": ["schemaVersion", "profile", "contact", "settings", "meta"], "properties": { "schemaVersion": { "type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+(-[a-zA-Z0-9.-]+)?$", "description": "Semantic version of the takuhon schema this document conforms to." }, "profile": { "$ref": "#/$defs/Profile" }, "links": { "type": "array", "maxItems": 100, "items": { "$ref": "#/$defs/Link" } }, "careers": { "type": "array", "maxItems": 50, "items": { "$ref": "#/$defs/Career" } }, "projects": { "type": "array", "maxItems": 100, "items": { "$ref": "#/$defs/Project" } }, "skills": { "type": "array", "maxItems": 200, "items": { "$ref": "#/$defs/Skill" } }, "certifications": { "type": "array", "maxItems": 50, "items": { "$ref": "#/$defs/Certification" } }, "memberships": { "type": "array", "maxItems": 50, "items": { "$ref": "#/$defs/Membership" } }, "volunteering": { "type": "array", "maxItems": 50, "items": { "$ref": "#/$defs/Volunteering" } }, "honors": { "type": "array", "maxItems": 50, "items": { "$ref": "#/$defs/Honor" } }, "education": { "type": "array", "maxItems": 30, "items": { "$ref": "#/$defs/Education" } }, "publications": { "type": "array", "maxItems": 100, "items": { "$ref": "#/$defs/Publication" } }, "languages": { "type": "array", "maxItems": 30, "items": { "$ref": "#/$defs/Language" } }, "courses": { "type": "array", "maxItems": 100, "items": { "$ref": "#/$defs/Course" } }, "patents": { "type": "array", "maxItems": 50, "items": { "$ref": "#/$defs/Patent" } }, "testScores": { "type": "array", "maxItems": 30, "items": { "$ref": "#/$defs/TestScore" } }, "recommendations": { "type": "array", "maxItems": 50, "items": { "$ref": "#/$defs/Recommendation" } }, "contact": { "$ref": "#/$defs/Contact" }, "settings": { "$ref": "#/$defs/Settings" }, "meta": { "$ref": "#/$defs/Meta" } }, "$defs": { "LocaleTag": { "type": "string", "minLength": 2, "maxLength": 35, "pattern": "^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$", "description": "BCP-47 language tag (e.g., 'en', 'ja', 'zh-Hant', 'pt-BR')." }, "Iso3166Alpha2": { "type": "string", "pattern": "^[A-Z]{2}$", "description": "ISO 3166-1 alpha-2 country code (uppercase, two letters)." }, "YearMonth": { "type": "string", "pattern": "^[0-9]{4}-(0[1-9]|1[0-2])$", "description": "Year-month in 'YYYY-MM' format (Gregorian calendar)." }, "IsoDateTime": { "type": "string", "format": "date-time", "description": "ISO 8601 date-time (e.g., 2026-05-11T12:34:56Z)." }, "Url": { "type": "string", "format": "uri", "maxLength": 2048 }, "Email": { "type": "string", "format": "email", "maxLength": 254 }, "Slug": { "type": "string", "minLength": 1, "maxLength": 64, "pattern": "^[a-z0-9][a-z0-9-]*$", "description": "URL-safe identifier (lowercase alphanumerics and hyphens, must start with alphanumeric)." }, "Visibility": { "type": "string", "enum": ["public", "private"], "description": "Per-item public visibility (added in 0.7.0). 'private' removes the item from every public surface via applyPublicPrivacyFilter; an absent value or 'public' keeps the item public." }, "LocalizedTitle": { "type": "object", "minProperties": 1, "propertyNames": { "type": "string", "pattern": "^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$" }, "additionalProperties": { "type": "string", "minLength": 1, "maxLength": 200, "pattern": "\\S" }, "description": "Map of BCP-47 locale tag to short title-like string (max 200 chars per value). Each value must contain at least one non-whitespace character." }, "LocalizedBody": { "type": "object", "minProperties": 1, "propertyNames": { "type": "string", "pattern": "^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$" }, "additionalProperties": { "type": "string", "minLength": 1, "maxLength": 5e3, "pattern": "\\S" }, "description": "Map of BCP-47 locale tag to body-length string (max 5000 chars per value). Each value must contain at least one non-whitespace character." }, "LinkType": { "type": "string", "enum": ["website", "blog", "github", "gitlab", "linkedin", "x", "mastodon", "bluesky", "instagram", "youtube", "threads", "facebook", "email", "rss", "custom"], "description": "Identifies the kind of link. 'custom' requires iconUrl." }, "Profile": { "type": "object", "additionalProperties": false, "required": ["displayName"], "properties": { "displayName": { "$ref": "#/$defs/LocalizedTitle" }, "tagline": { "$ref": "#/$defs/LocalizedTitle" }, "bio": { "$ref": "#/$defs/LocalizedBody" }, "avatar": { "$ref": "#/$defs/Avatar" }, "location": { "$ref": "#/$defs/Address" } } }, "Avatar": { "type": "object", "additionalProperties": false, "required": ["url"], "properties": { "url": { "type": "string", "format": "uri-reference", "maxLength": 2048, "description": "URL or path to the avatar image." }, "alt": { "$ref": "#/$defs/LocalizedTitle" } } }, "Address": { "type": "object", "additionalProperties": false, "properties": { "country": { "$ref": "#/$defs/Iso3166Alpha2" }, "region": { "type": "string", "maxLength": 100 }, "locality": { "$ref": "#/$defs/LocalizedTitle" }, "display": { "$ref": "#/$defs/LocalizedTitle" } } }, "Link": { "type": "object", "additionalProperties": false, "required": ["id", "type", "url"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "type": { "$ref": "#/$defs/LinkType" }, "label": { "$ref": "#/$defs/LocalizedTitle" }, "url": { "$ref": "#/$defs/Url" }, "featured": { "type": "boolean" }, "order": { "type": "integer", "minimum": 0 }, "iconUrl": { "$ref": "#/$defs/Url" } }, "allOf": [{ "if": { "properties": { "type": { "const": "custom" } }, "required": ["type"] }, "then": { "properties": { "iconUrl": { "$ref": "#/$defs/Url" } }, "required": ["iconUrl"] } }] }, "Career": { "type": "object", "additionalProperties": false, "required": ["id", "organization", "role", "startDate"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "organization": { "$ref": "#/$defs/LocalizedTitle" }, "role": { "$ref": "#/$defs/LocalizedTitle" }, "description": { "$ref": "#/$defs/LocalizedBody" }, "startDate": { "$ref": "#/$defs/YearMonth" }, "endDate": { "anyOf": [{ "$ref": "#/$defs/YearMonth" }, { "type": "null" }] }, "isCurrent": { "type": "boolean" }, "url": { "$ref": "#/$defs/Url" }, "location": { "$ref": "#/$defs/Address" }, "order": { "type": "integer", "minimum": 0 } } }, "Project": { "type": "object", "additionalProperties": false, "required": ["id", "title"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "title": { "$ref": "#/$defs/LocalizedTitle" }, "description": { "$ref": "#/$defs/LocalizedBody" }, "url": { "$ref": "#/$defs/Url" }, "tags": { "type": "array", "maxItems": 30, "items": { "type": "string", "minLength": 1, "maxLength": 50 } }, "relatedCareerId": { "$ref": "#/$defs/Slug" }, "startDate": { "$ref": "#/$defs/YearMonth" }, "endDate": { "anyOf": [{ "$ref": "#/$defs/YearMonth" }, { "type": "null" }] }, "highlighted": { "type": "boolean" }, "order": { "type": "integer", "minimum": 0 } } }, "Skill": { "type": "object", "additionalProperties": false, "required": ["id", "label"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "label": { "type": "string", "minLength": 1, "maxLength": 100 }, "category": { "type": "string", "minLength": 1, "maxLength": 64, "description": "Recommended values (extensible): programming, design, business, communication, language, music, art, sports, other." }, "order": { "type": "integer", "minimum": 0 } } }, "Contact": { "type": "object", "additionalProperties": false, "properties": { "email": { "$ref": "#/$defs/Email" }, "showEmail": { "type": "boolean", "default": false }, "formUrl": { "$ref": "#/$defs/Url" } } }, "Settings": { "type": "object", "additionalProperties": false, "required": ["defaultLocale", "availableLocales"], "properties": { "defaultLocale": { "$ref": "#/$defs/LocaleTag" }, "fallbackLocale": { "$ref": "#/$defs/LocaleTag" }, "availableLocales": { "type": "array", "minItems": 1, "maxItems": 50, "uniqueItems": true, "items": { "$ref": "#/$defs/LocaleTag" } }, "theme": { "type": "string", "minLength": 1, "maxLength": 64, "description": "UI theme identifier. 'default' is the built-in theme; adapters may add more." }, "showPoweredBy": { "type": "boolean", "default": true, "description": "Display the 'Powered by takuhon' attribution in the rendered profile." }, "enableJsonLd": { "type": "boolean", "default": true, "description": "Emit Schema.org JSON-LD on the rendered profile page." }, "enableApi": { "type": "boolean", "default": true, "description": "Expose the public read API endpoints (GET /api/profile, /api/jsonld, /api/schema, /takuhon.json)." }, "enableAnalytics": { "type": "boolean", "default": false, "description": "Opt-in flag for first-party analytics. Default is false to keep takuhon privacy-respecting by default." }, "activity": { "$ref": "#/$defs/ActivitySettings" }, "publicVisibility": { "$ref": "#/$defs/PublicVisibility" } } }, "PublicVisibility": { "type": "object", "additionalProperties": false, "description": "Per-section public visibility (added in 0.6.0). Each key is a content section; setting it to false hides that whole section from every public surface (GET /, /api/profile, /api/jsonld, /takuhon.json, MCP, and the derived CV) via the shared applyPublicPrivacyFilter. An absent key \u2014 or an absent object \u2014 means the section is public, so the default is all-visible and omitting this block is fully backwards-compatible. The profile identity (profile.displayName, \u2026) is always public and is intentionally not a key here. Field-level controls (contact.showEmail, meta.privacy.*) still apply within a visible section: visibility is the AND of the feature toggle, this section flag, and the field flag.", "properties": { "links": { "type": "boolean", "description": "Default true. When false, links[] is emitted empty on public surfaces." }, "careers": { "type": "boolean", "description": "Default true. When false, careers[] is emitted empty on public surfaces." }, "projects": { "type": "boolean", "description": "Default true. When false, projects[] is emitted empty on public surfaces." }, "skills": { "type": "boolean", "description": "Default true. When false, skills[] is emitted empty on public surfaces." }, "certifications": { "type": "boolean", "description": "Default true. When false, certifications[] is emitted empty on public surfaces." }, "memberships": { "type": "boolean", "description": "Default true. When false, memberships[] is emitted empty on public surfaces." }, "volunteering": { "type": "boolean", "description": "Default true. When false, volunteering[] is emitted empty on public surfaces." }, "honors": { "type": "boolean", "description": "Default true. When false, honors[] is emitted empty on public surfaces." }, "education": { "type": "boolean", "description": "Default true. When false, education[] is emitted empty on public surfaces." }, "publications": { "type": "boolean", "description": "Default true. When false, publications[] is emitted empty on public surfaces." }, "languages": { "type": "boolean", "description": "Default true. When false, languages[] is emitted empty on public surfaces." }, "courses": { "type": "boolean", "description": "Default true. When false, courses[] is emitted empty on public surfaces." }, "patents": { "type": "boolean", "description": "Default true. When false, patents[] is emitted empty on public surfaces." }, "testScores": { "type": "boolean", "description": "Default true. When false, testScores[] is emitted empty on public surfaces." }, "recommendations": { "type": "boolean", "description": "Default true. When false, recommendations[] is emitted empty on public surfaces." }, "contact": { "type": "boolean", "description": "Default true. When false, the contact section (email, formUrl) is emitted empty on public surfaces." } } }, "ActivitySettings": { "type": "object", "additionalProperties": false, "description": "Opt-in developer-activity dashboard configuration (GitHub / WakaTime). Only the owner-curated settings live here; secrets are provisioned out of band and the synced metrics are stored in a separate document, never in takuhon.json.", "properties": { "enabled": { "type": "boolean", "default": false, "description": "Master switch. When false (the default), the activity section is not rendered even if a snapshot exists." }, "github": { "type": "object", "additionalProperties": false, "required": ["username"], "properties": { "username": { "type": "string", "minLength": 1, "maxLength": 39, "description": "GitHub login whose public activity is summarized." }, "showLanguages": { "type": "boolean", "default": true }, "showContributions": { "type": "boolean", "default": true } } }, "wakatime": { "type": "object", "additionalProperties": false, "required": ["username"], "properties": { "username": { "type": "string", "minLength": 1, "maxLength": 255, "description": "WakaTime username whose coding-time stats are summarized." }, "showCodingTime": { "type": "boolean", "default": true } } }, "showRank": { "type": "boolean", "default": true, "description": "Display the derived activity rank / badge." }, "refreshHintHours": { "type": "integer", "minimum": 1, "maximum": 168, "description": "Advisory refresh cadence in hours. The real cadence is how often the sync step (CLI command or scheduled job) runs." } } }, "Meta": { "type": "object", "additionalProperties": false, "required": ["contentLicense"], "properties": { "createdAt": { "$ref": "#/$defs/IsoDateTime" }, "updatedAt": { "$ref": "#/$defs/IsoDateTime" }, "generator": { "type": "string", "minLength": 1, "maxLength": 100, "description": "Tool that produced this document (e.g. 'Takuhon', 'create-takuhon@0.1.0')." }, "contentLicense": { "$ref": "#/$defs/ContentLicense" }, "privacy": { "$ref": "#/$defs/MetaPrivacy" } } }, "ContentLicense": { "type": "object", "additionalProperties": false, "required": ["spdxId"], "properties": { "spdxId": { "type": "string", "minLength": 1, "maxLength": 64, "description": "SPDX identifier (e.g., 'CC-BY-4.0', 'CC0-1.0') or 'Proprietary'. No default; the profile owner must choose explicitly." }, "url": { "$ref": "#/$defs/Url" }, "attribution": { "type": "object", "additionalProperties": false, "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 200 }, "url": { "$ref": "#/$defs/Url" } } }, "rights": { "type": "string", "minLength": 1, "maxLength": 1e3, "description": "Free-form rights statement (used when spdxId='Proprietary' or for additional notices)." } } }, "MetaPrivacy": { "type": "object", "additionalProperties": false, "description": "Opt-out flags that strip personally identifying fields from public API output (GET /api/profile, /api/jsonld, /takuhon.json). Admin endpoints (PUT /api/admin/*, GET /api/admin/export) ignore these flags. Privacy-by-default: omitting the object or individual flags is equivalent to true.", "properties": { "hideCredentialIds": { "type": "boolean", "default": true, "description": "When true (default), strip certifications[*].credentialId from public responses." }, "hideEducationGrades": { "type": "boolean", "default": true, "description": "When true (default), strip education[*].grade from public responses." } } }, "Certification": { "type": "object", "additionalProperties": false, "required": ["id", "title", "issuingOrganization", "issueDate"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "title": { "$ref": "#/$defs/LocalizedTitle" }, "issuingOrganization": { "$ref": "#/$defs/LocalizedTitle" }, "issueDate": { "$ref": "#/$defs/YearMonth" }, "expirationDate": { "anyOf": [{ "$ref": "#/$defs/YearMonth" }, { "type": "null" }], "description": "null = explicitly 'no expiration'. Omit if unknown/unstated." }, "credentialId": { "type": "string", "minLength": 1, "maxLength": 100, "description": "License or certificate number. Public exposure controlled by meta.privacy.hideCredentialIds." }, "url": { "$ref": "#/$defs/Url" }, "order": { "type": "integer", "minimum": 0 } } }, "Membership": { "type": "object", "additionalProperties": false, "required": ["id", "organization", "startDate"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "organization": { "$ref": "#/$defs/LocalizedTitle" }, "role": { "$ref": "#/$defs/LocalizedTitle" }, "description": { "$ref": "#/$defs/LocalizedBody" }, "startDate": { "$ref": "#/$defs/YearMonth" }, "endDate": { "anyOf": [{ "$ref": "#/$defs/YearMonth" }, { "type": "null" }], "description": "null = ongoing. Omit if unknown." }, "isCurrent": { "type": "boolean" }, "url": { "$ref": "#/$defs/Url" }, "order": { "type": "integer", "minimum": 0 } } }, "Volunteering": { "type": "object", "additionalProperties": false, "required": ["id", "organization", "role", "startDate"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "organization": { "$ref": "#/$defs/LocalizedTitle" }, "role": { "$ref": "#/$defs/LocalizedTitle" }, "cause": { "$ref": "#/$defs/LocalizedTitle" }, "description": { "$ref": "#/$defs/LocalizedBody" }, "startDate": { "$ref": "#/$defs/YearMonth" }, "endDate": { "anyOf": [{ "$ref": "#/$defs/YearMonth" }, { "type": "null" }] }, "isCurrent": { "type": "boolean" }, "url": { "$ref": "#/$defs/Url" }, "order": { "type": "integer", "minimum": 0 } } }, "Honor": { "type": "object", "additionalProperties": false, "required": ["id", "title", "issuer", "date"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "title": { "$ref": "#/$defs/LocalizedTitle" }, "issuer": { "$ref": "#/$defs/LocalizedTitle" }, "description": { "$ref": "#/$defs/LocalizedBody" }, "date": { "$ref": "#/$defs/YearMonth" }, "url": { "$ref": "#/$defs/Url" }, "order": { "type": "integer", "minimum": 0 } } }, "Education": { "type": "object", "additionalProperties": false, "required": ["id", "institution", "startDate"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "institution": { "$ref": "#/$defs/LocalizedTitle" }, "degree": { "$ref": "#/$defs/LocalizedTitle" }, "fieldOfStudy": { "$ref": "#/$defs/LocalizedTitle" }, "description": { "$ref": "#/$defs/LocalizedBody" }, "grade": { "type": "string", "minLength": 1, "maxLength": 50, "description": "Free-form grade / class / GPA. Public exposure controlled by meta.privacy.hideEducationGrades." }, "startDate": { "$ref": "#/$defs/YearMonth" }, "endDate": { "anyOf": [{ "$ref": "#/$defs/YearMonth" }, { "type": "null" }], "description": "null = currently enrolled." }, "isCurrent": { "type": "boolean" }, "url": { "$ref": "#/$defs/Url" }, "order": { "type": "integer", "minimum": 0 } } }, "Publication": { "type": "object", "additionalProperties": false, "required": ["id", "title", "date"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "title": { "$ref": "#/$defs/LocalizedTitle" }, "publisher": { "$ref": "#/$defs/LocalizedTitle" }, "description": { "$ref": "#/$defs/LocalizedBody" }, "date": { "$ref": "#/$defs/YearMonth" }, "url": { "$ref": "#/$defs/Url" }, "doi": { "type": "string", "minLength": 1, "maxLength": 200, "description": "DOI identifier (e.g. '10.1145/3548643.3548644'). The full URL goes in 'url'." }, "coAuthors": { "type": "array", "maxItems": 50, "items": { "type": "string", "minLength": 1, "maxLength": 100 }, "description": "Co-author names in original script. Excludes the profile owner." }, "order": { "type": "integer", "minimum": 0 } } }, "Language": { "type": "object", "additionalProperties": false, "required": ["id", "language", "proficiency"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "language": { "$ref": "#/$defs/LocaleTag" }, "displayName": { "$ref": "#/$defs/LocalizedTitle" }, "proficiency": { "type": "string", "enum": ["native", "fluent", "professional", "intermediate", "basic"], "description": "LinkedIn-compatible 5-level proficiency." }, "order": { "type": "integer", "minimum": 0 } } }, "Course": { "type": "object", "additionalProperties": false, "required": ["id", "title"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "title": { "$ref": "#/$defs/LocalizedTitle" }, "provider": { "$ref": "#/$defs/LocalizedTitle" }, "courseNumber": { "type": "string", "minLength": 1, "maxLength": 50 }, "description": { "$ref": "#/$defs/LocalizedBody" }, "completionDate": { "$ref": "#/$defs/YearMonth" }, "certificateUrl": { "$ref": "#/$defs/Url" }, "relatedEducationId": { "$ref": "#/$defs/Slug", "description": "Optional reference to an education[].id (e.g. for university coursework)." }, "order": { "type": "integer", "minimum": 0 } } }, "Patent": { "type": "object", "additionalProperties": false, "required": ["id", "title", "patentNumber", "status"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "title": { "$ref": "#/$defs/LocalizedTitle" }, "patentNumber": { "type": "string", "minLength": 1, "maxLength": 100 }, "office": { "type": "string", "maxLength": 100, "description": "Patent office name (e.g. 'USPTO', 'JPO', 'EPO')." }, "status": { "type": "string", "enum": ["pending", "issued", "expired", "abandoned"] }, "description": { "$ref": "#/$defs/LocalizedBody" }, "filingDate": { "$ref": "#/$defs/YearMonth" }, "grantDate": { "$ref": "#/$defs/YearMonth" }, "url": { "$ref": "#/$defs/Url" }, "coInventors": { "type": "array", "maxItems": 20, "items": { "type": "string", "minLength": 1, "maxLength": 100 } }, "order": { "type": "integer", "minimum": 0 } } }, "TestScore": { "type": "object", "additionalProperties": false, "required": ["id", "title", "score", "date"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "title": { "$ref": "#/$defs/LocalizedTitle" }, "score": { "type": "string", "minLength": 1, "maxLength": 50, "description": "Free-form score string (e.g. '112 / 120', '330', 'N1 Pass', or a percentile). The validator does not interpret its contents." }, "date": { "$ref": "#/$defs/YearMonth" }, "relatedEducationId": { "$ref": "#/$defs/Slug", "description": "Optional reference to an education[].id (e.g. for a university course exam)." }, "description": { "$ref": "#/$defs/LocalizedBody" }, "url": { "$ref": "#/$defs/Url" }, "order": { "type": "integer", "minimum": 0 } } }, "Recommendation": { "type": "object", "additionalProperties": false, "required": ["id", "body", "author"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "body": { "$ref": "#/$defs/LocalizedBody" }, "author": { "$ref": "#/$defs/RecommendationAuthor" }, "relationship": { "$ref": "#/$defs/LocalizedTitle", "description": "How the recommender relates to the profile owner (e.g. 'managed directly', 'worked together')." }, "date": { "$ref": "#/$defs/YearMonth" }, "relatedCareerId": { "$ref": "#/$defs/Slug", "description": "Optional reference to a careers[].id (the position the recommendation pertains to)." }, "relatedEducationId": { "$ref": "#/$defs/Slug", "description": "Optional reference to an education[].id (e.g. a recommendation from a professor)." }, "order": { "type": "integer", "minimum": 0 } } }, "RecommendationAuthor": { "type": "object", "additionalProperties": false, "required": ["name"], "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 100, "description": "Recommender's name, in its original script. Owner-curated; takuhon does not verify it." }, "headline": { "$ref": "#/$defs/LocalizedTitle", "description": "Recommender's title / role / organization at the time of the recommendation." }, "url": { "$ref": "#/$defs/Url", "description": "Link to the recommender's profile, for external verification by the reader." } } } } };
308
+ var schema31 = { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://takuhon.example/schemas/1.1.0/takuhon.schema.json", "title": "Takuhon Profile", "description": "Portable profile data format consumed by @takuhon/core. The canonical contract for profile content authored as takuhon.json.", "type": "object", "additionalProperties": false, "required": ["schemaVersion", "profile", "contact", "settings", "meta"], "properties": { "schemaVersion": { "type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+(-[a-zA-Z0-9.-]+)?$", "description": "Semantic version of the takuhon schema this document conforms to." }, "profile": { "$ref": "#/$defs/Profile" }, "links": { "type": "array", "maxItems": 100, "items": { "$ref": "#/$defs/Link" } }, "careers": { "type": "array", "maxItems": 50, "items": { "$ref": "#/$defs/Career" } }, "projects": { "type": "array", "maxItems": 100, "items": { "$ref": "#/$defs/Project" } }, "skills": { "type": "array", "maxItems": 200, "items": { "$ref": "#/$defs/Skill" } }, "certifications": { "type": "array", "maxItems": 50, "items": { "$ref": "#/$defs/Certification" } }, "memberships": { "type": "array", "maxItems": 50, "items": { "$ref": "#/$defs/Membership" } }, "volunteering": { "type": "array", "maxItems": 50, "items": { "$ref": "#/$defs/Volunteering" } }, "honors": { "type": "array", "maxItems": 50, "items": { "$ref": "#/$defs/Honor" } }, "education": { "type": "array", "maxItems": 30, "items": { "$ref": "#/$defs/Education" } }, "publications": { "type": "array", "maxItems": 100, "items": { "$ref": "#/$defs/Publication" } }, "languages": { "type": "array", "maxItems": 30, "items": { "$ref": "#/$defs/Language" } }, "courses": { "type": "array", "maxItems": 100, "items": { "$ref": "#/$defs/Course" } }, "patents": { "type": "array", "maxItems": 50, "items": { "$ref": "#/$defs/Patent" } }, "testScores": { "type": "array", "maxItems": 30, "items": { "$ref": "#/$defs/TestScore" } }, "recommendations": { "type": "array", "maxItems": 50, "items": { "$ref": "#/$defs/Recommendation" } }, "contact": { "$ref": "#/$defs/Contact" }, "settings": { "$ref": "#/$defs/Settings" }, "meta": { "$ref": "#/$defs/Meta" } }, "$defs": { "LocaleTag": { "type": "string", "minLength": 2, "maxLength": 35, "pattern": "^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$", "description": "BCP-47 language tag (e.g., 'en', 'ja', 'zh-Hant', 'pt-BR')." }, "Iso3166Alpha2": { "type": "string", "pattern": "^[A-Z]{2}$", "description": "ISO 3166-1 alpha-2 country code (uppercase, two letters)." }, "YearMonth": { "type": "string", "pattern": "^[0-9]{4}-(0[1-9]|1[0-2])$", "description": "Year-month in 'YYYY-MM' format (Gregorian calendar)." }, "IsoDateTime": { "type": "string", "format": "date-time", "description": "ISO 8601 date-time (e.g., 2026-05-11T12:34:56Z)." }, "Url": { "type": "string", "format": "uri", "maxLength": 2048 }, "Email": { "type": "string", "format": "email", "maxLength": 254 }, "Slug": { "type": "string", "minLength": 1, "maxLength": 64, "pattern": "^[a-z0-9][a-z0-9-]*$", "description": "URL-safe identifier (lowercase alphanumerics and hyphens, must start with alphanumeric)." }, "Visibility": { "type": "string", "enum": ["public", "private"], "description": "Per-item public visibility (added in 0.7.0). 'private' removes the item from every public surface via applyPublicPrivacyFilter; an absent value or 'public' keeps the item public." }, "LocalizedTitle": { "type": "object", "minProperties": 1, "propertyNames": { "type": "string", "pattern": "^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$" }, "additionalProperties": { "type": "string", "minLength": 1, "maxLength": 200, "pattern": "\\S" }, "description": "Map of BCP-47 locale tag to short title-like string (max 200 chars per value). Each value must contain at least one non-whitespace character." }, "LocalizedBody": { "type": "object", "minProperties": 1, "propertyNames": { "type": "string", "pattern": "^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$" }, "additionalProperties": { "type": "string", "minLength": 1, "maxLength": 5e3, "pattern": "\\S" }, "description": "Map of BCP-47 locale tag to body-length string (max 5000 chars per value). Each value must contain at least one non-whitespace character." }, "LinkType": { "type": "string", "enum": ["website", "blog", "github", "gitlab", "linkedin", "x", "mastodon", "bluesky", "instagram", "youtube", "threads", "facebook", "email", "rss", "custom"], "description": "Identifies the kind of link. 'custom' requires iconUrl." }, "Profile": { "type": "object", "additionalProperties": false, "required": ["displayName"], "properties": { "displayName": { "$ref": "#/$defs/LocalizedTitle" }, "tagline": { "$ref": "#/$defs/LocalizedTitle" }, "bio": { "$ref": "#/$defs/LocalizedBody" }, "avatar": { "$ref": "#/$defs/Avatar" }, "location": { "$ref": "#/$defs/Address" } } }, "Avatar": { "type": "object", "additionalProperties": false, "required": ["url"], "properties": { "url": { "type": "string", "format": "uri-reference", "maxLength": 2048, "description": "URL or path to the avatar image." }, "alt": { "$ref": "#/$defs/LocalizedTitle" } } }, "Address": { "type": "object", "additionalProperties": false, "properties": { "country": { "$ref": "#/$defs/Iso3166Alpha2" }, "region": { "type": "string", "maxLength": 100 }, "locality": { "$ref": "#/$defs/LocalizedTitle" }, "display": { "$ref": "#/$defs/LocalizedTitle" } } }, "Link": { "type": "object", "additionalProperties": false, "required": ["id", "type", "url"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "type": { "$ref": "#/$defs/LinkType" }, "label": { "$ref": "#/$defs/LocalizedTitle" }, "url": { "$ref": "#/$defs/Url" }, "featured": { "type": "boolean" }, "order": { "type": "integer", "minimum": 0 }, "iconUrl": { "$ref": "#/$defs/Url" } }, "allOf": [{ "if": { "properties": { "type": { "const": "custom" } }, "required": ["type"] }, "then": { "properties": { "iconUrl": { "$ref": "#/$defs/Url" } }, "required": ["iconUrl"] } }] }, "Career": { "type": "object", "additionalProperties": false, "required": ["id", "organization", "role", "startDate"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "organization": { "$ref": "#/$defs/LocalizedTitle" }, "role": { "$ref": "#/$defs/LocalizedTitle" }, "description": { "$ref": "#/$defs/LocalizedBody" }, "startDate": { "$ref": "#/$defs/YearMonth" }, "endDate": { "anyOf": [{ "$ref": "#/$defs/YearMonth" }, { "type": "null" }] }, "isCurrent": { "type": "boolean" }, "url": { "$ref": "#/$defs/Url" }, "location": { "$ref": "#/$defs/Address" }, "order": { "type": "integer", "minimum": 0 } } }, "Project": { "type": "object", "additionalProperties": false, "required": ["id", "title"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "title": { "$ref": "#/$defs/LocalizedTitle" }, "description": { "$ref": "#/$defs/LocalizedBody" }, "url": { "$ref": "#/$defs/Url" }, "tags": { "type": "array", "maxItems": 30, "items": { "type": "string", "minLength": 1, "maxLength": 50 } }, "relatedCareerId": { "$ref": "#/$defs/Slug" }, "startDate": { "$ref": "#/$defs/YearMonth" }, "endDate": { "anyOf": [{ "$ref": "#/$defs/YearMonth" }, { "type": "null" }] }, "highlighted": { "type": "boolean" }, "order": { "type": "integer", "minimum": 0 } } }, "Skill": { "type": "object", "additionalProperties": false, "required": ["id", "label"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "label": { "type": "string", "minLength": 1, "maxLength": 100 }, "category": { "type": "string", "minLength": 1, "maxLength": 64, "description": "Recommended values (extensible): programming, design, business, communication, language, music, art, sports, other." }, "order": { "type": "integer", "minimum": 0 } } }, "Contact": { "type": "object", "additionalProperties": false, "properties": { "email": { "$ref": "#/$defs/Email" }, "showEmail": { "type": "boolean", "default": false }, "formUrl": { "$ref": "#/$defs/Url" } } }, "Settings": { "type": "object", "additionalProperties": false, "required": ["defaultLocale", "availableLocales"], "properties": { "defaultLocale": { "$ref": "#/$defs/LocaleTag" }, "fallbackLocale": { "$ref": "#/$defs/LocaleTag" }, "availableLocales": { "type": "array", "minItems": 1, "maxItems": 50, "uniqueItems": true, "items": { "$ref": "#/$defs/LocaleTag" } }, "theme": { "type": "string", "minLength": 1, "maxLength": 64, "description": "UI theme identifier. 'default' is the built-in theme; adapters may add more." }, "showPoweredBy": { "type": "boolean", "default": true, "description": "Display the 'Powered by takuhon' attribution in the rendered profile." }, "enableJsonLd": { "type": "boolean", "default": true, "description": "Emit Schema.org JSON-LD on the rendered profile page." }, "enableApi": { "type": "boolean", "default": true, "description": "Expose the public read API endpoints (GET /api/profile, /api/jsonld, /api/schema, /takuhon.json)." }, "enableAnalytics": { "type": "boolean", "default": false, "description": "Opt-in flag for first-party analytics. Default is false to keep takuhon privacy-respecting by default." }, "activity": { "$ref": "#/$defs/ActivitySettings" }, "publicVisibility": { "$ref": "#/$defs/PublicVisibility" }, "contact": { "$ref": "#/$defs/ContactSettings" } } }, "ContactSettings": { "type": "object", "additionalProperties": false, "description": "Opt-in contact form configuration (added in 1.1.0). Only public values live here; the Turnstile site key is safe to embed in the page. The Turnstile secret, the recipient address, and the From label are provisioned out of band as environment configuration and are never stored in takuhon.json. Adapters that support the form mount the widget and a POST endpoint when 'enabled' is true.", "properties": { "enabled": { "type": "boolean", "default": false, "description": "Master switch. When false (the default), no contact form is mounted even if a site key is present." }, "turnstileSiteKey": { "type": "string", "minLength": 1, "maxLength": 128, "description": "Public Cloudflare Turnstile site key, safe to embed in the page. Without it the widget is not mounted." }, "endpoint": { "type": "string", "minLength": 1, "maxLength": 2048, "description": "Override the POST endpoint the widget submits to. Defaults to /api/contact on the same origin." }, "subjectPrefix": { "type": "string", "minLength": 1, "maxLength": 128, "description": "Subject prefix for the delivered email, e.g. '[example.com contact]'." } } }, "PublicVisibility": { "type": "object", "additionalProperties": false, "description": "Per-section public visibility (added in 0.6.0). Each key is a content section; setting it to false hides that whole section from every public surface (GET /, /api/profile, /api/jsonld, /takuhon.json, MCP, and the derived CV) via the shared applyPublicPrivacyFilter. An absent key \u2014 or an absent object \u2014 means the section is public, so the default is all-visible and omitting this block is fully backwards-compatible. The profile identity (profile.displayName, \u2026) is always public and is intentionally not a key here. Field-level controls (contact.showEmail, meta.privacy.*) still apply within a visible section: visibility is the AND of the feature toggle, this section flag, and the field flag.", "properties": { "links": { "type": "boolean", "description": "Default true. When false, links[] is emitted empty on public surfaces." }, "careers": { "type": "boolean", "description": "Default true. When false, careers[] is emitted empty on public surfaces." }, "projects": { "type": "boolean", "description": "Default true. When false, projects[] is emitted empty on public surfaces." }, "skills": { "type": "boolean", "description": "Default true. When false, skills[] is emitted empty on public surfaces." }, "certifications": { "type": "boolean", "description": "Default true. When false, certifications[] is emitted empty on public surfaces." }, "memberships": { "type": "boolean", "description": "Default true. When false, memberships[] is emitted empty on public surfaces." }, "volunteering": { "type": "boolean", "description": "Default true. When false, volunteering[] is emitted empty on public surfaces." }, "honors": { "type": "boolean", "description": "Default true. When false, honors[] is emitted empty on public surfaces." }, "education": { "type": "boolean", "description": "Default true. When false, education[] is emitted empty on public surfaces." }, "publications": { "type": "boolean", "description": "Default true. When false, publications[] is emitted empty on public surfaces." }, "languages": { "type": "boolean", "description": "Default true. When false, languages[] is emitted empty on public surfaces." }, "courses": { "type": "boolean", "description": "Default true. When false, courses[] is emitted empty on public surfaces." }, "patents": { "type": "boolean", "description": "Default true. When false, patents[] is emitted empty on public surfaces." }, "testScores": { "type": "boolean", "description": "Default true. When false, testScores[] is emitted empty on public surfaces." }, "recommendations": { "type": "boolean", "description": "Default true. When false, recommendations[] is emitted empty on public surfaces." }, "contact": { "type": "boolean", "description": "Default true. When false, the contact section (email, formUrl) is emitted empty on public surfaces." } } }, "ActivitySettings": { "type": "object", "additionalProperties": false, "description": "Opt-in developer-activity dashboard configuration (GitHub / WakaTime). Only the owner-curated settings live here; secrets are provisioned out of band and the synced metrics are stored in a separate document, never in takuhon.json.", "properties": { "enabled": { "type": "boolean", "default": false, "description": "Master switch. When false (the default), the activity section is not rendered even if a snapshot exists." }, "github": { "type": "object", "additionalProperties": false, "required": ["username"], "properties": { "username": { "type": "string", "minLength": 1, "maxLength": 39, "description": "GitHub login whose public activity is summarized." }, "showLanguages": { "type": "boolean", "default": true }, "showContributions": { "type": "boolean", "default": true } } }, "wakatime": { "type": "object", "additionalProperties": false, "required": ["username"], "properties": { "username": { "type": "string", "minLength": 1, "maxLength": 255, "description": "WakaTime username whose coding-time stats are summarized." }, "showCodingTime": { "type": "boolean", "default": true } } }, "showRank": { "type": "boolean", "default": true, "description": "Display the derived activity rank / badge." }, "refreshHintHours": { "type": "integer", "minimum": 1, "maximum": 168, "description": "Advisory refresh cadence in hours. The real cadence is how often the sync step (CLI command or scheduled job) runs." } } }, "Meta": { "type": "object", "additionalProperties": false, "required": ["contentLicense"], "properties": { "createdAt": { "$ref": "#/$defs/IsoDateTime" }, "updatedAt": { "$ref": "#/$defs/IsoDateTime" }, "generator": { "type": "string", "minLength": 1, "maxLength": 100, "description": "Tool that produced this document (e.g. 'Takuhon', 'create-takuhon@0.1.0')." }, "contentLicense": { "$ref": "#/$defs/ContentLicense" }, "privacy": { "$ref": "#/$defs/MetaPrivacy" } } }, "ContentLicense": { "type": "object", "additionalProperties": false, "required": ["spdxId"], "properties": { "spdxId": { "type": "string", "minLength": 1, "maxLength": 64, "description": "SPDX identifier (e.g., 'CC-BY-4.0', 'CC0-1.0') or 'Proprietary'. No default; the profile owner must choose explicitly." }, "url": { "$ref": "#/$defs/Url" }, "attribution": { "type": "object", "additionalProperties": false, "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 200 }, "url": { "$ref": "#/$defs/Url" } } }, "rights": { "type": "string", "minLength": 1, "maxLength": 1e3, "description": "Free-form rights statement (used when spdxId='Proprietary' or for additional notices)." } } }, "MetaPrivacy": { "type": "object", "additionalProperties": false, "description": "Opt-out flags that strip personally identifying fields from public API output (GET /api/profile, /api/jsonld, /takuhon.json). Admin endpoints (PUT /api/admin/*, GET /api/admin/export) ignore these flags. Privacy-by-default: omitting the object or individual flags is equivalent to true.", "properties": { "hideCredentialIds": { "type": "boolean", "default": true, "description": "When true (default), strip certifications[*].credentialId from public responses." }, "hideEducationGrades": { "type": "boolean", "default": true, "description": "When true (default), strip education[*].grade from public responses." } } }, "Certification": { "type": "object", "additionalProperties": false, "required": ["id", "title", "issuingOrganization", "issueDate"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "title": { "$ref": "#/$defs/LocalizedTitle" }, "issuingOrganization": { "$ref": "#/$defs/LocalizedTitle" }, "issueDate": { "$ref": "#/$defs/YearMonth" }, "expirationDate": { "anyOf": [{ "$ref": "#/$defs/YearMonth" }, { "type": "null" }], "description": "null = explicitly 'no expiration'. Omit if unknown/unstated." }, "credentialId": { "type": "string", "minLength": 1, "maxLength": 100, "description": "License or certificate number. Public exposure controlled by meta.privacy.hideCredentialIds." }, "url": { "$ref": "#/$defs/Url" }, "order": { "type": "integer", "minimum": 0 } } }, "Membership": { "type": "object", "additionalProperties": false, "required": ["id", "organization", "startDate"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "organization": { "$ref": "#/$defs/LocalizedTitle" }, "role": { "$ref": "#/$defs/LocalizedTitle" }, "description": { "$ref": "#/$defs/LocalizedBody" }, "startDate": { "$ref": "#/$defs/YearMonth" }, "endDate": { "anyOf": [{ "$ref": "#/$defs/YearMonth" }, { "type": "null" }], "description": "null = ongoing. Omit if unknown." }, "isCurrent": { "type": "boolean" }, "url": { "$ref": "#/$defs/Url" }, "order": { "type": "integer", "minimum": 0 } } }, "Volunteering": { "type": "object", "additionalProperties": false, "required": ["id", "organization", "role", "startDate"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "organization": { "$ref": "#/$defs/LocalizedTitle" }, "role": { "$ref": "#/$defs/LocalizedTitle" }, "cause": { "$ref": "#/$defs/LocalizedTitle" }, "description": { "$ref": "#/$defs/LocalizedBody" }, "startDate": { "$ref": "#/$defs/YearMonth" }, "endDate": { "anyOf": [{ "$ref": "#/$defs/YearMonth" }, { "type": "null" }] }, "isCurrent": { "type": "boolean" }, "url": { "$ref": "#/$defs/Url" }, "order": { "type": "integer", "minimum": 0 } } }, "Honor": { "type": "object", "additionalProperties": false, "required": ["id", "title", "issuer", "date"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "title": { "$ref": "#/$defs/LocalizedTitle" }, "issuer": { "$ref": "#/$defs/LocalizedTitle" }, "description": { "$ref": "#/$defs/LocalizedBody" }, "date": { "$ref": "#/$defs/YearMonth" }, "url": { "$ref": "#/$defs/Url" }, "order": { "type": "integer", "minimum": 0 } } }, "Education": { "type": "object", "additionalProperties": false, "required": ["id", "institution", "startDate"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "institution": { "$ref": "#/$defs/LocalizedTitle" }, "degree": { "$ref": "#/$defs/LocalizedTitle" }, "fieldOfStudy": { "$ref": "#/$defs/LocalizedTitle" }, "description": { "$ref": "#/$defs/LocalizedBody" }, "grade": { "type": "string", "minLength": 1, "maxLength": 50, "description": "Free-form grade / class / GPA. Public exposure controlled by meta.privacy.hideEducationGrades." }, "startDate": { "$ref": "#/$defs/YearMonth" }, "endDate": { "anyOf": [{ "$ref": "#/$defs/YearMonth" }, { "type": "null" }], "description": "null = currently enrolled." }, "isCurrent": { "type": "boolean" }, "url": { "$ref": "#/$defs/Url" }, "order": { "type": "integer", "minimum": 0 } } }, "Publication": { "type": "object", "additionalProperties": false, "required": ["id", "title", "date"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "title": { "$ref": "#/$defs/LocalizedTitle" }, "publisher": { "$ref": "#/$defs/LocalizedTitle" }, "description": { "$ref": "#/$defs/LocalizedBody" }, "date": { "$ref": "#/$defs/YearMonth" }, "url": { "$ref": "#/$defs/Url" }, "doi": { "type": "string", "minLength": 1, "maxLength": 200, "description": "DOI identifier (e.g. '10.1145/3548643.3548644'). The full URL goes in 'url'." }, "coAuthors": { "type": "array", "maxItems": 50, "items": { "type": "string", "minLength": 1, "maxLength": 100 }, "description": "Co-author names in original script. Excludes the profile owner." }, "order": { "type": "integer", "minimum": 0 } } }, "Language": { "type": "object", "additionalProperties": false, "required": ["id", "language", "proficiency"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "language": { "$ref": "#/$defs/LocaleTag" }, "displayName": { "$ref": "#/$defs/LocalizedTitle" }, "proficiency": { "type": "string", "enum": ["native", "fluent", "professional", "intermediate", "basic"], "description": "LinkedIn-compatible 5-level proficiency." }, "order": { "type": "integer", "minimum": 0 } } }, "Course": { "type": "object", "additionalProperties": false, "required": ["id", "title"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "title": { "$ref": "#/$defs/LocalizedTitle" }, "provider": { "$ref": "#/$defs/LocalizedTitle" }, "courseNumber": { "type": "string", "minLength": 1, "maxLength": 50 }, "description": { "$ref": "#/$defs/LocalizedBody" }, "completionDate": { "$ref": "#/$defs/YearMonth" }, "certificateUrl": { "$ref": "#/$defs/Url" }, "relatedEducationId": { "$ref": "#/$defs/Slug", "description": "Optional reference to an education[].id (e.g. for university coursework)." }, "order": { "type": "integer", "minimum": 0 } } }, "Patent": { "type": "object", "additionalProperties": false, "required": ["id", "title", "patentNumber", "status"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "title": { "$ref": "#/$defs/LocalizedTitle" }, "patentNumber": { "type": "string", "minLength": 1, "maxLength": 100 }, "office": { "type": "string", "maxLength": 100, "description": "Patent office name (e.g. 'USPTO', 'JPO', 'EPO')." }, "status": { "type": "string", "enum": ["pending", "issued", "expired", "abandoned"] }, "description": { "$ref": "#/$defs/LocalizedBody" }, "filingDate": { "$ref": "#/$defs/YearMonth" }, "grantDate": { "$ref": "#/$defs/YearMonth" }, "url": { "$ref": "#/$defs/Url" }, "coInventors": { "type": "array", "maxItems": 20, "items": { "type": "string", "minLength": 1, "maxLength": 100 } }, "order": { "type": "integer", "minimum": 0 } } }, "TestScore": { "type": "object", "additionalProperties": false, "required": ["id", "title", "score", "date"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "title": { "$ref": "#/$defs/LocalizedTitle" }, "score": { "type": "string", "minLength": 1, "maxLength": 50, "description": "Free-form score string (e.g. '112 / 120', '330', 'N1 Pass', or a percentile). The validator does not interpret its contents." }, "date": { "$ref": "#/$defs/YearMonth" }, "relatedEducationId": { "$ref": "#/$defs/Slug", "description": "Optional reference to an education[].id (e.g. for a university course exam)." }, "description": { "$ref": "#/$defs/LocalizedBody" }, "url": { "$ref": "#/$defs/Url" }, "order": { "type": "integer", "minimum": 0 } } }, "Recommendation": { "type": "object", "additionalProperties": false, "required": ["id", "body", "author"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "visibility": { "$ref": "#/$defs/Visibility" }, "body": { "$ref": "#/$defs/LocalizedBody" }, "author": { "$ref": "#/$defs/RecommendationAuthor" }, "relationship": { "$ref": "#/$defs/LocalizedTitle", "description": "How the recommender relates to the profile owner (e.g. 'managed directly', 'worked together')." }, "date": { "$ref": "#/$defs/YearMonth" }, "relatedCareerId": { "$ref": "#/$defs/Slug", "description": "Optional reference to a careers[].id (the position the recommendation pertains to)." }, "relatedEducationId": { "$ref": "#/$defs/Slug", "description": "Optional reference to an education[].id (e.g. a recommendation from a professor)." }, "order": { "type": "integer", "minimum": 0 } } }, "RecommendationAuthor": { "type": "object", "additionalProperties": false, "required": ["name"], "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 100, "description": "Recommender's name, in its original script. Owner-curated; takuhon does not verify it." }, "headline": { "$ref": "#/$defs/LocalizedTitle", "description": "Recommender's title / role / organization at the time of the recommendation." }, "url": { "$ref": "#/$defs/Url", "description": "Link to the recommender's profile, for external verification by the reader." } } } } };
309
309
  var func1 = Object.prototype.hasOwnProperty;
310
310
  var pattern4 = new RegExp("^[0-9]+\\.[0-9]+\\.[0-9]+(-[a-zA-Z0-9.-]+)?$", "u");
311
311
  var pattern5 = new RegExp("^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$", "u");
@@ -8861,7 +8861,7 @@ var require_takuhon_validator = __commonJS({
8861
8861
  return errors === 0;
8862
8862
  }
8863
8863
  validate60.evaluated = { "props": true, "dynamicProps": false, "dynamicItems": false };
8864
- var schema168 = { "type": "object", "additionalProperties": false, "required": ["defaultLocale", "availableLocales"], "properties": { "defaultLocale": { "$ref": "#/$defs/LocaleTag" }, "fallbackLocale": { "$ref": "#/$defs/LocaleTag" }, "availableLocales": { "type": "array", "minItems": 1, "maxItems": 50, "uniqueItems": true, "items": { "$ref": "#/$defs/LocaleTag" } }, "theme": { "type": "string", "minLength": 1, "maxLength": 64, "description": "UI theme identifier. 'default' is the built-in theme; adapters may add more." }, "showPoweredBy": { "type": "boolean", "default": true, "description": "Display the 'Powered by takuhon' attribution in the rendered profile." }, "enableJsonLd": { "type": "boolean", "default": true, "description": "Emit Schema.org JSON-LD on the rendered profile page." }, "enableApi": { "type": "boolean", "default": true, "description": "Expose the public read API endpoints (GET /api/profile, /api/jsonld, /api/schema, /takuhon.json)." }, "enableAnalytics": { "type": "boolean", "default": false, "description": "Opt-in flag for first-party analytics. Default is false to keep takuhon privacy-respecting by default." }, "activity": { "$ref": "#/$defs/ActivitySettings" }, "publicVisibility": { "$ref": "#/$defs/PublicVisibility" } } };
8864
+ var schema168 = { "type": "object", "additionalProperties": false, "required": ["defaultLocale", "availableLocales"], "properties": { "defaultLocale": { "$ref": "#/$defs/LocaleTag" }, "fallbackLocale": { "$ref": "#/$defs/LocaleTag" }, "availableLocales": { "type": "array", "minItems": 1, "maxItems": 50, "uniqueItems": true, "items": { "$ref": "#/$defs/LocaleTag" } }, "theme": { "type": "string", "minLength": 1, "maxLength": 64, "description": "UI theme identifier. 'default' is the built-in theme; adapters may add more." }, "showPoweredBy": { "type": "boolean", "default": true, "description": "Display the 'Powered by takuhon' attribution in the rendered profile." }, "enableJsonLd": { "type": "boolean", "default": true, "description": "Emit Schema.org JSON-LD on the rendered profile page." }, "enableApi": { "type": "boolean", "default": true, "description": "Expose the public read API endpoints (GET /api/profile, /api/jsonld, /api/schema, /takuhon.json)." }, "enableAnalytics": { "type": "boolean", "default": false, "description": "Opt-in flag for first-party analytics. Default is false to keep takuhon privacy-respecting by default." }, "activity": { "$ref": "#/$defs/ActivitySettings" }, "publicVisibility": { "$ref": "#/$defs/PublicVisibility" }, "contact": { "$ref": "#/$defs/ContactSettings" } } };
8865
8865
  var schema173 = { "type": "object", "additionalProperties": false, "description": "Per-section public visibility (added in 0.6.0). Each key is a content section; setting it to false hides that whole section from every public surface (GET /, /api/profile, /api/jsonld, /takuhon.json, MCP, and the derived CV) via the shared applyPublicPrivacyFilter. An absent key \u2014 or an absent object \u2014 means the section is public, so the default is all-visible and omitting this block is fully backwards-compatible. The profile identity (profile.displayName, \u2026) is always public and is intentionally not a key here. Field-level controls (contact.showEmail, meta.privacy.*) still apply within a visible section: visibility is the AND of the feature toggle, this section flag, and the field flag.", "properties": { "links": { "type": "boolean", "description": "Default true. When false, links[] is emitted empty on public surfaces." }, "careers": { "type": "boolean", "description": "Default true. When false, careers[] is emitted empty on public surfaces." }, "projects": { "type": "boolean", "description": "Default true. When false, projects[] is emitted empty on public surfaces." }, "skills": { "type": "boolean", "description": "Default true. When false, skills[] is emitted empty on public surfaces." }, "certifications": { "type": "boolean", "description": "Default true. When false, certifications[] is emitted empty on public surfaces." }, "memberships": { "type": "boolean", "description": "Default true. When false, memberships[] is emitted empty on public surfaces." }, "volunteering": { "type": "boolean", "description": "Default true. When false, volunteering[] is emitted empty on public surfaces." }, "honors": { "type": "boolean", "description": "Default true. When false, honors[] is emitted empty on public surfaces." }, "education": { "type": "boolean", "description": "Default true. When false, education[] is emitted empty on public surfaces." }, "publications": { "type": "boolean", "description": "Default true. When false, publications[] is emitted empty on public surfaces." }, "languages": { "type": "boolean", "description": "Default true. When false, languages[] is emitted empty on public surfaces." }, "courses": { "type": "boolean", "description": "Default true. When false, courses[] is emitted empty on public surfaces." }, "patents": { "type": "boolean", "description": "Default true. When false, patents[] is emitted empty on public surfaces." }, "testScores": { "type": "boolean", "description": "Default true. When false, testScores[] is emitted empty on public surfaces." }, "recommendations": { "type": "boolean", "description": "Default true. When false, recommendations[] is emitted empty on public surfaces." }, "contact": { "type": "boolean", "description": "Default true. When false, the contact section (email, formUrl) is emitted empty on public surfaces." } } };
8866
8866
  var func0 = require_equal().default;
8867
8867
  function validate62(data, { instancePath = "", parentData, parentDataProperty, rootData = data, dynamicAnchors = {} } = {}) {
@@ -9588,12 +9588,140 @@ var require_takuhon_validator = __commonJS({
9588
9588
  errors++;
9589
9589
  }
9590
9590
  }
9591
+ if (data.contact !== void 0) {
9592
+ let data37 = data.contact;
9593
+ if (data37 && typeof data37 == "object" && !Array.isArray(data37)) {
9594
+ for (const key5 in data37) {
9595
+ if (!(key5 === "enabled" || key5 === "turnstileSiteKey" || key5 === "endpoint" || key5 === "subjectPrefix")) {
9596
+ const err66 = { instancePath: instancePath + "/contact", schemaPath: "#/$defs/ContactSettings/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key5 }, message: "must NOT have additional properties" };
9597
+ if (vErrors === null) {
9598
+ vErrors = [err66];
9599
+ } else {
9600
+ vErrors.push(err66);
9601
+ }
9602
+ errors++;
9603
+ }
9604
+ }
9605
+ if (data37.enabled !== void 0) {
9606
+ if (typeof data37.enabled !== "boolean") {
9607
+ const err67 = { instancePath: instancePath + "/contact/enabled", schemaPath: "#/$defs/ContactSettings/properties/enabled/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" };
9608
+ if (vErrors === null) {
9609
+ vErrors = [err67];
9610
+ } else {
9611
+ vErrors.push(err67);
9612
+ }
9613
+ errors++;
9614
+ }
9615
+ }
9616
+ if (data37.turnstileSiteKey !== void 0) {
9617
+ let data39 = data37.turnstileSiteKey;
9618
+ if (typeof data39 === "string") {
9619
+ if (func2(data39) > 128) {
9620
+ const err68 = { instancePath: instancePath + "/contact/turnstileSiteKey", schemaPath: "#/$defs/ContactSettings/properties/turnstileSiteKey/maxLength", keyword: "maxLength", params: { limit: 128 }, message: "must NOT have more than 128 characters" };
9621
+ if (vErrors === null) {
9622
+ vErrors = [err68];
9623
+ } else {
9624
+ vErrors.push(err68);
9625
+ }
9626
+ errors++;
9627
+ }
9628
+ if (func2(data39) < 1) {
9629
+ const err69 = { instancePath: instancePath + "/contact/turnstileSiteKey", schemaPath: "#/$defs/ContactSettings/properties/turnstileSiteKey/minLength", keyword: "minLength", params: { limit: 1 }, message: "must NOT have fewer than 1 characters" };
9630
+ if (vErrors === null) {
9631
+ vErrors = [err69];
9632
+ } else {
9633
+ vErrors.push(err69);
9634
+ }
9635
+ errors++;
9636
+ }
9637
+ } else {
9638
+ const err70 = { instancePath: instancePath + "/contact/turnstileSiteKey", schemaPath: "#/$defs/ContactSettings/properties/turnstileSiteKey/type", keyword: "type", params: { type: "string" }, message: "must be string" };
9639
+ if (vErrors === null) {
9640
+ vErrors = [err70];
9641
+ } else {
9642
+ vErrors.push(err70);
9643
+ }
9644
+ errors++;
9645
+ }
9646
+ }
9647
+ if (data37.endpoint !== void 0) {
9648
+ let data40 = data37.endpoint;
9649
+ if (typeof data40 === "string") {
9650
+ if (func2(data40) > 2048) {
9651
+ const err71 = { instancePath: instancePath + "/contact/endpoint", schemaPath: "#/$defs/ContactSettings/properties/endpoint/maxLength", keyword: "maxLength", params: { limit: 2048 }, message: "must NOT have more than 2048 characters" };
9652
+ if (vErrors === null) {
9653
+ vErrors = [err71];
9654
+ } else {
9655
+ vErrors.push(err71);
9656
+ }
9657
+ errors++;
9658
+ }
9659
+ if (func2(data40) < 1) {
9660
+ const err72 = { instancePath: instancePath + "/contact/endpoint", schemaPath: "#/$defs/ContactSettings/properties/endpoint/minLength", keyword: "minLength", params: { limit: 1 }, message: "must NOT have fewer than 1 characters" };
9661
+ if (vErrors === null) {
9662
+ vErrors = [err72];
9663
+ } else {
9664
+ vErrors.push(err72);
9665
+ }
9666
+ errors++;
9667
+ }
9668
+ } else {
9669
+ const err73 = { instancePath: instancePath + "/contact/endpoint", schemaPath: "#/$defs/ContactSettings/properties/endpoint/type", keyword: "type", params: { type: "string" }, message: "must be string" };
9670
+ if (vErrors === null) {
9671
+ vErrors = [err73];
9672
+ } else {
9673
+ vErrors.push(err73);
9674
+ }
9675
+ errors++;
9676
+ }
9677
+ }
9678
+ if (data37.subjectPrefix !== void 0) {
9679
+ let data41 = data37.subjectPrefix;
9680
+ if (typeof data41 === "string") {
9681
+ if (func2(data41) > 128) {
9682
+ const err74 = { instancePath: instancePath + "/contact/subjectPrefix", schemaPath: "#/$defs/ContactSettings/properties/subjectPrefix/maxLength", keyword: "maxLength", params: { limit: 128 }, message: "must NOT have more than 128 characters" };
9683
+ if (vErrors === null) {
9684
+ vErrors = [err74];
9685
+ } else {
9686
+ vErrors.push(err74);
9687
+ }
9688
+ errors++;
9689
+ }
9690
+ if (func2(data41) < 1) {
9691
+ const err75 = { instancePath: instancePath + "/contact/subjectPrefix", schemaPath: "#/$defs/ContactSettings/properties/subjectPrefix/minLength", keyword: "minLength", params: { limit: 1 }, message: "must NOT have fewer than 1 characters" };
9692
+ if (vErrors === null) {
9693
+ vErrors = [err75];
9694
+ } else {
9695
+ vErrors.push(err75);
9696
+ }
9697
+ errors++;
9698
+ }
9699
+ } else {
9700
+ const err76 = { instancePath: instancePath + "/contact/subjectPrefix", schemaPath: "#/$defs/ContactSettings/properties/subjectPrefix/type", keyword: "type", params: { type: "string" }, message: "must be string" };
9701
+ if (vErrors === null) {
9702
+ vErrors = [err76];
9703
+ } else {
9704
+ vErrors.push(err76);
9705
+ }
9706
+ errors++;
9707
+ }
9708
+ }
9709
+ } else {
9710
+ const err77 = { instancePath: instancePath + "/contact", schemaPath: "#/$defs/ContactSettings/type", keyword: "type", params: { type: "object" }, message: "must be object" };
9711
+ if (vErrors === null) {
9712
+ vErrors = [err77];
9713
+ } else {
9714
+ vErrors.push(err77);
9715
+ }
9716
+ errors++;
9717
+ }
9718
+ }
9591
9719
  } else {
9592
- const err66 = { instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" };
9720
+ const err78 = { instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" };
9593
9721
  if (vErrors === null) {
9594
- vErrors = [err66];
9722
+ vErrors = [err78];
9595
9723
  } else {
9596
- vErrors.push(err66);
9724
+ vErrors.push(err78);
9597
9725
  }
9598
9726
  errors++;
9599
9727
  }
@@ -10564,7 +10692,7 @@ var require_takuhon_validator = __commonJS({
10564
10692
  // takuhon.schema.json
10565
10693
  var takuhon_schema_default = {
10566
10694
  $schema: "https://json-schema.org/draft/2020-12/schema",
10567
- $id: "https://takuhon.example/schemas/1.0.0/takuhon.schema.json",
10695
+ $id: "https://takuhon.example/schemas/1.1.0/takuhon.schema.json",
10568
10696
  title: "Takuhon Profile",
10569
10697
  description: "Portable profile data format consumed by @takuhon/core. The canonical contract for profile content authored as takuhon.json.",
10570
10698
  type: "object",
@@ -10926,7 +11054,38 @@ var takuhon_schema_default = {
10926
11054
  description: "Opt-in flag for first-party analytics. Default is false to keep takuhon privacy-respecting by default."
10927
11055
  },
10928
11056
  activity: { $ref: "#/$defs/ActivitySettings" },
10929
- publicVisibility: { $ref: "#/$defs/PublicVisibility" }
11057
+ publicVisibility: { $ref: "#/$defs/PublicVisibility" },
11058
+ contact: { $ref: "#/$defs/ContactSettings" }
11059
+ }
11060
+ },
11061
+ ContactSettings: {
11062
+ type: "object",
11063
+ additionalProperties: false,
11064
+ description: "Opt-in contact form configuration (added in 1.1.0). Only public values live here; the Turnstile site key is safe to embed in the page. The Turnstile secret, the recipient address, and the From label are provisioned out of band as environment configuration and are never stored in takuhon.json. Adapters that support the form mount the widget and a POST endpoint when 'enabled' is true.",
11065
+ properties: {
11066
+ enabled: {
11067
+ type: "boolean",
11068
+ default: false,
11069
+ description: "Master switch. When false (the default), no contact form is mounted even if a site key is present."
11070
+ },
11071
+ turnstileSiteKey: {
11072
+ type: "string",
11073
+ minLength: 1,
11074
+ maxLength: 128,
11075
+ description: "Public Cloudflare Turnstile site key, safe to embed in the page. Without it the widget is not mounted."
11076
+ },
11077
+ endpoint: {
11078
+ type: "string",
11079
+ minLength: 1,
11080
+ maxLength: 2048,
11081
+ description: "Override the POST endpoint the widget submits to. Defaults to /api/contact on the same origin."
11082
+ },
11083
+ subjectPrefix: {
11084
+ type: "string",
11085
+ minLength: 1,
11086
+ maxLength: 128,
11087
+ description: "Subject prefix for the delivered email, e.g. '[example.com contact]'."
11088
+ }
10930
11089
  }
10931
11090
  },
10932
11091
  PublicVisibility: {
@@ -11400,7 +11559,8 @@ var SUPPORTED_SCHEMA_VERSIONS = [
11400
11559
  "0.5.0",
11401
11560
  "0.6.0",
11402
11561
  "0.7.0",
11403
- "1.0.0"
11562
+ "1.0.0",
11563
+ "1.1.0"
11404
11564
  ];
11405
11565
  function validate(data) {
11406
11566
  if (typeof data !== "object" || data === null || Array.isArray(data)) {
@@ -12634,6 +12794,18 @@ var v0_7_0_to_v1_0_0 = {
12634
12794
  }
12635
12795
  };
12636
12796
 
12797
+ // src/migrations/v1.0.0-to-v1.1.0.ts
12798
+ var v1_0_0_to_v1_1_0 = {
12799
+ from: "1.0.0",
12800
+ to: "1.1.0",
12801
+ migrate(data) {
12802
+ return {
12803
+ ...data,
12804
+ schemaVersion: "1.1.0"
12805
+ };
12806
+ }
12807
+ };
12808
+
12637
12809
  // src/migrations/index.ts
12638
12810
  var migrations = [
12639
12811
  v0_1_0_to_v0_2_0,
@@ -12642,7 +12814,8 @@ var migrations = [
12642
12814
  v0_4_0_to_v0_5_0,
12643
12815
  v0_5_0_to_v0_6_0,
12644
12816
  v0_6_0_to_v0_7_0,
12645
- v0_7_0_to_v1_0_0
12817
+ v0_7_0_to_v1_0_0,
12818
+ v1_0_0_to_v1_1_0
12646
12819
  ];
12647
12820
 
12648
12821
  // src/migrate.ts
@@ -13632,7 +13805,7 @@ function stripImageMetadata(bytes, mime) {
13632
13805
  }
13633
13806
 
13634
13807
  // src/index.ts
13635
- var SCHEMA_VERSION = "1.0.0";
13808
+ var SCHEMA_VERSION = "1.1.0";
13636
13809
  export {
13637
13810
  ACCEPTED_IMAGE_MIME_TYPES,
13638
13811
  ConflictError,