@takuhon/core 0.11.0 → 0.12.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.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/0.4.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", "links", "careers", "projects", "skills", "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)." }, "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 }, "description": "Map of BCP-47 locale tag to short title-like string (max 200 chars per value)." }, "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 }, "description": "Map of BCP-47 locale tag to body-length string (max 5000 chars per value)." }, "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": true, "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": true, "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": true, "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" }, "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": true, "required": ["id", "organization", "role", "startDate"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "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": true, "required": ["id", "title"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "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": true, "required": ["id", "label"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "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": true, "properties": { "email": { "$ref": "#/$defs/Email" }, "showEmail": { "type": "boolean", "default": false }, "formUrl": { "$ref": "#/$defs/Url" } } }, "Settings": { "type": "object", "additionalProperties": true, "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." } } }, "Meta": { "type": "object", "additionalProperties": true, "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": true, "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": true, "required": ["id", "title", "issuingOrganization", "issueDate"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "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": true, "required": ["id", "organization", "startDate"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "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": true, "required": ["id", "organization", "role", "startDate"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "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": true, "required": ["id", "title", "issuer", "date"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "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": true, "required": ["id", "institution", "startDate"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "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": true, "required": ["id", "title", "date"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "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": true, "required": ["id", "language", "proficiency"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "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": true, "required": ["id", "title"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "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": true, "required": ["id", "title", "patentNumber", "status"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "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": true, "required": ["id", "title", "score", "date"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "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": true, "required": ["id", "body", "author"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "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": true, "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/0.5.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", "links", "careers", "projects", "skills", "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)." }, "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 }, "description": "Map of BCP-47 locale tag to short title-like string (max 200 chars per value)." }, "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 }, "description": "Map of BCP-47 locale tag to body-length string (max 5000 chars per value)." }, "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": true, "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": true, "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": true, "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" }, "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": true, "required": ["id", "organization", "role", "startDate"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "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": true, "required": ["id", "title"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "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": true, "required": ["id", "label"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "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": true, "properties": { "email": { "$ref": "#/$defs/Email" }, "showEmail": { "type": "boolean", "default": false }, "formUrl": { "$ref": "#/$defs/Url" } } }, "Settings": { "type": "object", "additionalProperties": true, "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" } } }, "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": true, "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": true, "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": true, "required": ["id", "title", "issuingOrganization", "issueDate"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "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": true, "required": ["id", "organization", "startDate"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "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": true, "required": ["id", "organization", "role", "startDate"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "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": true, "required": ["id", "title", "issuer", "date"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "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": true, "required": ["id", "institution", "startDate"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "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": true, "required": ["id", "title", "date"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "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": true, "required": ["id", "language", "proficiency"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "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": true, "required": ["id", "title"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "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": true, "required": ["id", "title", "patentNumber", "status"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "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": true, "required": ["id", "title", "score", "date"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "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": true, "required": ["id", "body", "author"], "properties": { "id": { "$ref": "#/$defs/Slug" }, "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": true, "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");
@@ -8222,12 +8222,251 @@ var require_takuhon_validator = __commonJS({
8222
8222
  errors++;
8223
8223
  }
8224
8224
  }
8225
+ if (data.activity !== void 0) {
8226
+ let data9 = data.activity;
8227
+ if (data9 && typeof data9 == "object" && !Array.isArray(data9)) {
8228
+ for (const key0 in data9) {
8229
+ if (!(key0 === "enabled" || key0 === "github" || key0 === "wakatime" || key0 === "showRank" || key0 === "refreshHintHours")) {
8230
+ const err25 = { instancePath: instancePath + "/activity", schemaPath: "#/$defs/ActivitySettings/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" };
8231
+ if (vErrors === null) {
8232
+ vErrors = [err25];
8233
+ } else {
8234
+ vErrors.push(err25);
8235
+ }
8236
+ errors++;
8237
+ }
8238
+ }
8239
+ if (data9.enabled !== void 0) {
8240
+ if (typeof data9.enabled !== "boolean") {
8241
+ const err26 = { instancePath: instancePath + "/activity/enabled", schemaPath: "#/$defs/ActivitySettings/properties/enabled/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" };
8242
+ if (vErrors === null) {
8243
+ vErrors = [err26];
8244
+ } else {
8245
+ vErrors.push(err26);
8246
+ }
8247
+ errors++;
8248
+ }
8249
+ }
8250
+ if (data9.github !== void 0) {
8251
+ let data11 = data9.github;
8252
+ if (data11 && typeof data11 == "object" && !Array.isArray(data11)) {
8253
+ if (data11.username === void 0) {
8254
+ const err27 = { instancePath: instancePath + "/activity/github", schemaPath: "#/$defs/ActivitySettings/properties/github/required", keyword: "required", params: { missingProperty: "username" }, message: "must have required property 'username'" };
8255
+ if (vErrors === null) {
8256
+ vErrors = [err27];
8257
+ } else {
8258
+ vErrors.push(err27);
8259
+ }
8260
+ errors++;
8261
+ }
8262
+ for (const key1 in data11) {
8263
+ if (!(key1 === "username" || key1 === "showLanguages" || key1 === "showContributions")) {
8264
+ const err28 = { instancePath: instancePath + "/activity/github", schemaPath: "#/$defs/ActivitySettings/properties/github/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key1 }, message: "must NOT have additional properties" };
8265
+ if (vErrors === null) {
8266
+ vErrors = [err28];
8267
+ } else {
8268
+ vErrors.push(err28);
8269
+ }
8270
+ errors++;
8271
+ }
8272
+ }
8273
+ if (data11.username !== void 0) {
8274
+ let data12 = data11.username;
8275
+ if (typeof data12 === "string") {
8276
+ if (func2(data12) > 39) {
8277
+ const err29 = { instancePath: instancePath + "/activity/github/username", schemaPath: "#/$defs/ActivitySettings/properties/github/properties/username/maxLength", keyword: "maxLength", params: { limit: 39 }, message: "must NOT have more than 39 characters" };
8278
+ if (vErrors === null) {
8279
+ vErrors = [err29];
8280
+ } else {
8281
+ vErrors.push(err29);
8282
+ }
8283
+ errors++;
8284
+ }
8285
+ if (func2(data12) < 1) {
8286
+ const err30 = { instancePath: instancePath + "/activity/github/username", schemaPath: "#/$defs/ActivitySettings/properties/github/properties/username/minLength", keyword: "minLength", params: { limit: 1 }, message: "must NOT have fewer than 1 characters" };
8287
+ if (vErrors === null) {
8288
+ vErrors = [err30];
8289
+ } else {
8290
+ vErrors.push(err30);
8291
+ }
8292
+ errors++;
8293
+ }
8294
+ } else {
8295
+ const err31 = { instancePath: instancePath + "/activity/github/username", schemaPath: "#/$defs/ActivitySettings/properties/github/properties/username/type", keyword: "type", params: { type: "string" }, message: "must be string" };
8296
+ if (vErrors === null) {
8297
+ vErrors = [err31];
8298
+ } else {
8299
+ vErrors.push(err31);
8300
+ }
8301
+ errors++;
8302
+ }
8303
+ }
8304
+ if (data11.showLanguages !== void 0) {
8305
+ if (typeof data11.showLanguages !== "boolean") {
8306
+ const err32 = { instancePath: instancePath + "/activity/github/showLanguages", schemaPath: "#/$defs/ActivitySettings/properties/github/properties/showLanguages/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" };
8307
+ if (vErrors === null) {
8308
+ vErrors = [err32];
8309
+ } else {
8310
+ vErrors.push(err32);
8311
+ }
8312
+ errors++;
8313
+ }
8314
+ }
8315
+ if (data11.showContributions !== void 0) {
8316
+ if (typeof data11.showContributions !== "boolean") {
8317
+ const err33 = { instancePath: instancePath + "/activity/github/showContributions", schemaPath: "#/$defs/ActivitySettings/properties/github/properties/showContributions/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" };
8318
+ if (vErrors === null) {
8319
+ vErrors = [err33];
8320
+ } else {
8321
+ vErrors.push(err33);
8322
+ }
8323
+ errors++;
8324
+ }
8325
+ }
8326
+ } else {
8327
+ const err34 = { instancePath: instancePath + "/activity/github", schemaPath: "#/$defs/ActivitySettings/properties/github/type", keyword: "type", params: { type: "object" }, message: "must be object" };
8328
+ if (vErrors === null) {
8329
+ vErrors = [err34];
8330
+ } else {
8331
+ vErrors.push(err34);
8332
+ }
8333
+ errors++;
8334
+ }
8335
+ }
8336
+ if (data9.wakatime !== void 0) {
8337
+ let data15 = data9.wakatime;
8338
+ if (data15 && typeof data15 == "object" && !Array.isArray(data15)) {
8339
+ if (data15.username === void 0) {
8340
+ const err35 = { instancePath: instancePath + "/activity/wakatime", schemaPath: "#/$defs/ActivitySettings/properties/wakatime/required", keyword: "required", params: { missingProperty: "username" }, message: "must have required property 'username'" };
8341
+ if (vErrors === null) {
8342
+ vErrors = [err35];
8343
+ } else {
8344
+ vErrors.push(err35);
8345
+ }
8346
+ errors++;
8347
+ }
8348
+ for (const key2 in data15) {
8349
+ if (!(key2 === "username" || key2 === "showCodingTime")) {
8350
+ const err36 = { instancePath: instancePath + "/activity/wakatime", schemaPath: "#/$defs/ActivitySettings/properties/wakatime/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key2 }, message: "must NOT have additional properties" };
8351
+ if (vErrors === null) {
8352
+ vErrors = [err36];
8353
+ } else {
8354
+ vErrors.push(err36);
8355
+ }
8356
+ errors++;
8357
+ }
8358
+ }
8359
+ if (data15.username !== void 0) {
8360
+ let data16 = data15.username;
8361
+ if (typeof data16 === "string") {
8362
+ if (func2(data16) > 255) {
8363
+ const err37 = { instancePath: instancePath + "/activity/wakatime/username", schemaPath: "#/$defs/ActivitySettings/properties/wakatime/properties/username/maxLength", keyword: "maxLength", params: { limit: 255 }, message: "must NOT have more than 255 characters" };
8364
+ if (vErrors === null) {
8365
+ vErrors = [err37];
8366
+ } else {
8367
+ vErrors.push(err37);
8368
+ }
8369
+ errors++;
8370
+ }
8371
+ if (func2(data16) < 1) {
8372
+ const err38 = { instancePath: instancePath + "/activity/wakatime/username", schemaPath: "#/$defs/ActivitySettings/properties/wakatime/properties/username/minLength", keyword: "minLength", params: { limit: 1 }, message: "must NOT have fewer than 1 characters" };
8373
+ if (vErrors === null) {
8374
+ vErrors = [err38];
8375
+ } else {
8376
+ vErrors.push(err38);
8377
+ }
8378
+ errors++;
8379
+ }
8380
+ } else {
8381
+ const err39 = { instancePath: instancePath + "/activity/wakatime/username", schemaPath: "#/$defs/ActivitySettings/properties/wakatime/properties/username/type", keyword: "type", params: { type: "string" }, message: "must be string" };
8382
+ if (vErrors === null) {
8383
+ vErrors = [err39];
8384
+ } else {
8385
+ vErrors.push(err39);
8386
+ }
8387
+ errors++;
8388
+ }
8389
+ }
8390
+ if (data15.showCodingTime !== void 0) {
8391
+ if (typeof data15.showCodingTime !== "boolean") {
8392
+ const err40 = { instancePath: instancePath + "/activity/wakatime/showCodingTime", schemaPath: "#/$defs/ActivitySettings/properties/wakatime/properties/showCodingTime/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" };
8393
+ if (vErrors === null) {
8394
+ vErrors = [err40];
8395
+ } else {
8396
+ vErrors.push(err40);
8397
+ }
8398
+ errors++;
8399
+ }
8400
+ }
8401
+ } else {
8402
+ const err41 = { instancePath: instancePath + "/activity/wakatime", schemaPath: "#/$defs/ActivitySettings/properties/wakatime/type", keyword: "type", params: { type: "object" }, message: "must be object" };
8403
+ if (vErrors === null) {
8404
+ vErrors = [err41];
8405
+ } else {
8406
+ vErrors.push(err41);
8407
+ }
8408
+ errors++;
8409
+ }
8410
+ }
8411
+ if (data9.showRank !== void 0) {
8412
+ if (typeof data9.showRank !== "boolean") {
8413
+ const err42 = { instancePath: instancePath + "/activity/showRank", schemaPath: "#/$defs/ActivitySettings/properties/showRank/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" };
8414
+ if (vErrors === null) {
8415
+ vErrors = [err42];
8416
+ } else {
8417
+ vErrors.push(err42);
8418
+ }
8419
+ errors++;
8420
+ }
8421
+ }
8422
+ if (data9.refreshHintHours !== void 0) {
8423
+ let data19 = data9.refreshHintHours;
8424
+ if (!(typeof data19 == "number" && (!(data19 % 1) && !isNaN(data19)) && isFinite(data19))) {
8425
+ const err43 = { instancePath: instancePath + "/activity/refreshHintHours", schemaPath: "#/$defs/ActivitySettings/properties/refreshHintHours/type", keyword: "type", params: { type: "integer" }, message: "must be integer" };
8426
+ if (vErrors === null) {
8427
+ vErrors = [err43];
8428
+ } else {
8429
+ vErrors.push(err43);
8430
+ }
8431
+ errors++;
8432
+ }
8433
+ if (typeof data19 == "number" && isFinite(data19)) {
8434
+ if (data19 > 168 || isNaN(data19)) {
8435
+ const err44 = { instancePath: instancePath + "/activity/refreshHintHours", schemaPath: "#/$defs/ActivitySettings/properties/refreshHintHours/maximum", keyword: "maximum", params: { comparison: "<=", limit: 168 }, message: "must be <= 168" };
8436
+ if (vErrors === null) {
8437
+ vErrors = [err44];
8438
+ } else {
8439
+ vErrors.push(err44);
8440
+ }
8441
+ errors++;
8442
+ }
8443
+ if (data19 < 1 || isNaN(data19)) {
8444
+ const err45 = { instancePath: instancePath + "/activity/refreshHintHours", schemaPath: "#/$defs/ActivitySettings/properties/refreshHintHours/minimum", keyword: "minimum", params: { comparison: ">=", limit: 1 }, message: "must be >= 1" };
8445
+ if (vErrors === null) {
8446
+ vErrors = [err45];
8447
+ } else {
8448
+ vErrors.push(err45);
8449
+ }
8450
+ errors++;
8451
+ }
8452
+ }
8453
+ }
8454
+ } else {
8455
+ const err46 = { instancePath: instancePath + "/activity", schemaPath: "#/$defs/ActivitySettings/type", keyword: "type", params: { type: "object" }, message: "must be object" };
8456
+ if (vErrors === null) {
8457
+ vErrors = [err46];
8458
+ } else {
8459
+ vErrors.push(err46);
8460
+ }
8461
+ errors++;
8462
+ }
8463
+ }
8225
8464
  } else {
8226
- const err25 = { instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" };
8465
+ const err47 = { instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" };
8227
8466
  if (vErrors === null) {
8228
- vErrors = [err25];
8467
+ vErrors = [err47];
8229
8468
  } else {
8230
- vErrors.push(err25);
8469
+ vErrors.push(err47);
8231
8470
  }
8232
8471
  errors++;
8233
8472
  }
@@ -9212,7 +9451,7 @@ var require_takuhon_validator = __commonJS({
9212
9451
  // takuhon.schema.json
9213
9452
  var takuhon_schema_default = {
9214
9453
  $schema: "https://json-schema.org/draft/2020-12/schema",
9215
- $id: "https://takuhon.example/schemas/0.4.0/takuhon.schema.json",
9454
+ $id: "https://takuhon.example/schemas/0.5.0/takuhon.schema.json",
9216
9455
  title: "Takuhon Profile",
9217
9456
  description: "Portable profile data format consumed by @takuhon/core. The canonical contract for profile content authored as takuhon.json.",
9218
9457
  type: "object",
@@ -9571,6 +9810,59 @@ var takuhon_schema_default = {
9571
9810
  type: "boolean",
9572
9811
  default: false,
9573
9812
  description: "Opt-in flag for first-party analytics. Default is false to keep takuhon privacy-respecting by default."
9813
+ },
9814
+ activity: { $ref: "#/$defs/ActivitySettings" }
9815
+ }
9816
+ },
9817
+ ActivitySettings: {
9818
+ type: "object",
9819
+ additionalProperties: false,
9820
+ 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.",
9821
+ properties: {
9822
+ enabled: {
9823
+ type: "boolean",
9824
+ default: false,
9825
+ description: "Master switch. When false (the default), the activity section is not rendered even if a snapshot exists."
9826
+ },
9827
+ github: {
9828
+ type: "object",
9829
+ additionalProperties: false,
9830
+ required: ["username"],
9831
+ properties: {
9832
+ username: {
9833
+ type: "string",
9834
+ minLength: 1,
9835
+ maxLength: 39,
9836
+ description: "GitHub login whose public activity is summarized."
9837
+ },
9838
+ showLanguages: { type: "boolean", default: true },
9839
+ showContributions: { type: "boolean", default: true }
9840
+ }
9841
+ },
9842
+ wakatime: {
9843
+ type: "object",
9844
+ additionalProperties: false,
9845
+ required: ["username"],
9846
+ properties: {
9847
+ username: {
9848
+ type: "string",
9849
+ minLength: 1,
9850
+ maxLength: 255,
9851
+ description: "WakaTime username whose coding-time stats are summarized."
9852
+ },
9853
+ showCodingTime: { type: "boolean", default: true }
9854
+ }
9855
+ },
9856
+ showRank: {
9857
+ type: "boolean",
9858
+ default: true,
9859
+ description: "Display the derived activity rank / badge."
9860
+ },
9861
+ refreshHintHours: {
9862
+ type: "integer",
9863
+ minimum: 1,
9864
+ maximum: 168,
9865
+ description: "Advisory refresh cadence in hours. The real cadence is how often the sync step (CLI command or scheduled job) runs."
9574
9866
  }
9575
9867
  }
9576
9868
  },
@@ -9903,7 +10195,7 @@ var schema = takuhon_schema_default;
9903
10195
 
9904
10196
  // src/validate.ts
9905
10197
  var import_takuhon_validator = __toESM(require_takuhon_validator(), 1);
9906
- var SUPPORTED_SCHEMA_VERSIONS = ["0.1.0", "0.2.0", "0.3.0", "0.4.0"];
10198
+ var SUPPORTED_SCHEMA_VERSIONS = ["0.1.0", "0.2.0", "0.3.0", "0.4.0", "0.5.0"];
9907
10199
  function validate(data) {
9908
10200
  if (typeof data !== "object" || data === null || Array.isArray(data)) {
9909
10201
  return {
@@ -10981,11 +11273,24 @@ var v0_3_0_to_v0_4_0 = {
10981
11273
  }
10982
11274
  };
10983
11275
 
11276
+ // src/migrations/v0.4.0-to-v0.5.0.ts
11277
+ var v0_4_0_to_v0_5_0 = {
11278
+ from: "0.4.0",
11279
+ to: "0.5.0",
11280
+ migrate(data) {
11281
+ return {
11282
+ ...data,
11283
+ schemaVersion: "0.5.0"
11284
+ };
11285
+ }
11286
+ };
11287
+
10984
11288
  // src/migrations/index.ts
10985
11289
  var migrations = [
10986
11290
  v0_1_0_to_v0_2_0,
10987
11291
  v0_2_0_to_v0_3_0,
10988
- v0_3_0_to_v0_4_0
11292
+ v0_3_0_to_v0_4_0,
11293
+ v0_4_0_to_v0_5_0
10989
11294
  ];
10990
11295
 
10991
11296
  // src/migrate.ts
@@ -11033,27 +11338,593 @@ var ConflictError = class extends StorageError {
11033
11338
  }
11034
11339
  };
11035
11340
 
11341
+ // src/activity.ts
11342
+ function isObject(value) {
11343
+ return typeof value === "object" && value !== null;
11344
+ }
11345
+ function isLanguageBreakdown(value) {
11346
+ return isObject(value) && typeof value.name === "string" && typeof value.bytes === "number" && typeof value.percent === "number";
11347
+ }
11348
+ function isContributionCalendar(value) {
11349
+ return isObject(value) && typeof value.total === "number" && Array.isArray(value.days) && value.days.every(
11350
+ (d) => isObject(d) && typeof d.date === "string" && typeof d.count === "number"
11351
+ );
11352
+ }
11353
+ function isCodingTime(value) {
11354
+ return isObject(value) && typeof value.totalSeconds === "number" && typeof value.hours === "number" && typeof value.minutes === "number" && typeof value.seconds === "number";
11355
+ }
11356
+ function isRankTier(value) {
11357
+ return isObject(value) && typeof value.score === "number" && (value.tier === "S" || value.tier === "A" || value.tier === "B" || value.tier === "C" || value.tier === "D");
11358
+ }
11359
+ function isActivitySnapshot(value) {
11360
+ if (!isObject(value) || typeof value.lastSyncedAt !== "string") return false;
11361
+ if (value.languages !== void 0) {
11362
+ if (!Array.isArray(value.languages) || !value.languages.every(isLanguageBreakdown)) {
11363
+ return false;
11364
+ }
11365
+ }
11366
+ if (value.contributions !== void 0 && !isContributionCalendar(value.contributions)) {
11367
+ return false;
11368
+ }
11369
+ if (value.codingTime !== void 0 && !isCodingTime(value.codingTime)) return false;
11370
+ if (value.rank !== void 0 && !isRankTier(value.rank)) return false;
11371
+ return true;
11372
+ }
11373
+ function computeLanguagePercentages(bytesByLanguage) {
11374
+ const entries = Object.entries(bytesByLanguage).filter(([, bytes]) => bytes > 0);
11375
+ const total = entries.reduce((sum, [, bytes]) => sum + bytes, 0);
11376
+ if (total === 0) return [];
11377
+ return entries.map(([name, bytes]) => ({ name, bytes, percent: Math.round(bytes / total * 1e3) / 10 })).sort((a, b) => b.bytes - a.bytes || a.name.localeCompare(b.name));
11378
+ }
11379
+ function formatCodingTime(totalSeconds) {
11380
+ const safe = Math.max(0, Math.floor(totalSeconds));
11381
+ return {
11382
+ totalSeconds: safe,
11383
+ hours: Math.floor(safe / 3600),
11384
+ minutes: Math.floor(safe % 3600 / 60),
11385
+ seconds: safe % 60
11386
+ };
11387
+ }
11388
+ var RANK_FULL_CONTRIBUTIONS = 2e3;
11389
+ var RANK_FULL_CODING_HOURS = 2e3;
11390
+ var RANK_TIER_THRESHOLDS = { S: 80, A: 60, B: 40, C: 20 };
11391
+ function tierForScore(score) {
11392
+ if (score >= RANK_TIER_THRESHOLDS.S) return "S";
11393
+ if (score >= RANK_TIER_THRESHOLDS.A) return "A";
11394
+ if (score >= RANK_TIER_THRESHOLDS.B) return "B";
11395
+ if (score >= RANK_TIER_THRESHOLDS.C) return "C";
11396
+ return "D";
11397
+ }
11398
+ function deriveRankTier(input) {
11399
+ const subScores = [];
11400
+ if (input.contributions !== void 0) {
11401
+ const c = Math.max(0, input.contributions);
11402
+ subScores.push(100 * Math.min(1, c / RANK_FULL_CONTRIBUTIONS));
11403
+ }
11404
+ if (input.codingSeconds !== void 0) {
11405
+ const hours = Math.max(0, input.codingSeconds) / 3600;
11406
+ subScores.push(100 * Math.min(1, hours / RANK_FULL_CODING_HOURS));
11407
+ }
11408
+ const score = subScores.length === 0 ? 0 : Math.round(subScores.reduce((sum, s) => sum + s, 0) / subScores.length);
11409
+ return { tier: tierForScore(score), score };
11410
+ }
11411
+
11412
+ // src/activity-svg.ts
11413
+ var WIDTH = 520;
11414
+ var PAD = 16;
11415
+ var INNER = WIDTH - PAD * 2;
11416
+ var TEXT_COLOR = "#1a1a1a";
11417
+ var MUTED_COLOR = "#666666";
11418
+ var ACCENT_COLOR = "#0b5fff";
11419
+ var FONT_FAMILY = "system-ui,-apple-system,Segoe UI,Roboto,sans-serif";
11420
+ var LANGUAGE_COLORS = ["#0b5fff", "#7c3aed", "#059669", "#d97706", "#dc2626", "#0891b2"];
11421
+ var OTHER_COLOR = "#9ca3af";
11422
+ var MAX_LANGUAGES = 6;
11423
+ var HEAT_COLORS = ["#ebedf0", "#cce0ff", "#99c2ff", "#4d8aff", "#0b5fff"];
11424
+ var CELL = 7;
11425
+ var CELL_PITCH = 9;
11426
+ var CALENDAR_ROWS = 7;
11427
+ var MAX_CALENDAR_DAYS = 53 * CALENDAR_ROWS;
11428
+ function escapeXml(value) {
11429
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
11430
+ }
11431
+ function groupDigits(value) {
11432
+ const digits = String(Math.trunc(Math.abs(value)));
11433
+ const grouped = digits.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
11434
+ return value < 0 ? `-${grouped}` : grouped;
11435
+ }
11436
+ function round2(value) {
11437
+ return Math.round(value * 100) / 100;
11438
+ }
11439
+ function text(x, y, content, opts) {
11440
+ const weight = opts.weight !== void 0 ? ` font-weight="${String(opts.weight)}"` : "";
11441
+ const anchor = opts.anchor !== void 0 ? ` text-anchor="${opts.anchor}"` : "";
11442
+ return `<text x="${String(x)}" y="${String(y)}" font-family="${FONT_FAMILY}" font-size="${String(opts.size)}" fill="${opts.fill}"${weight}${anchor}>${escapeXml(content)}</text>`;
11443
+ }
11444
+ function caption(x, y, label) {
11445
+ return text(x, y, label, { size: 12, fill: MUTED_COLOR, weight: 600 });
11446
+ }
11447
+ function rect(x, y, w, h, fill, rx = 0) {
11448
+ const corner = rx > 0 ? ` rx="${String(rx)}"` : "";
11449
+ return `<rect x="${String(round2(x))}" y="${String(y)}" width="${String(round2(w))}" height="${String(h)}" fill="${fill}"${corner}/>`;
11450
+ }
11451
+ function languageSegments(languages) {
11452
+ const segments = languages.slice(0, MAX_LANGUAGES).map((l, i) => ({
11453
+ name: l.name,
11454
+ percent: l.percent,
11455
+ color: LANGUAGE_COLORS[i] ?? OTHER_COLOR
11456
+ }));
11457
+ const rest = languages.slice(MAX_LANGUAGES);
11458
+ if (rest.length > 0) {
11459
+ const percent = round2(rest.reduce((sum, l) => sum + l.percent, 0));
11460
+ segments.push({ name: "Other", percent, color: OTHER_COLOR });
11461
+ }
11462
+ return segments.filter((s) => s.percent > 0);
11463
+ }
11464
+ function heatLevel(count, max) {
11465
+ if (count <= 0) return 0;
11466
+ return Math.min(4, Math.max(1, Math.ceil(count / max * 4)));
11467
+ }
11468
+ function renderActivitySvg(snapshot) {
11469
+ const hasLanguages = snapshot.languages !== void 0 && snapshot.languages.length > 0;
11470
+ const hasContributions = snapshot.contributions !== void 0 && snapshot.contributions.days.length > 0;
11471
+ const hasCodingTime = snapshot.codingTime !== void 0;
11472
+ const hasRank = snapshot.rank !== void 0;
11473
+ if (!hasLanguages && !hasContributions && !hasCodingTime && !hasRank) return "";
11474
+ const parts = [];
11475
+ let y = PAD;
11476
+ if (hasLanguages && snapshot.languages) {
11477
+ const segments = languageSegments(snapshot.languages);
11478
+ const total = segments.reduce((sum, s) => sum + s.percent, 0);
11479
+ parts.push(caption(PAD, y + 11, "Languages"));
11480
+ y += 22;
11481
+ let x = PAD;
11482
+ for (const segment of segments) {
11483
+ const w = segment.percent / total * INNER;
11484
+ parts.push(rect(x, y, w, 12, segment.color));
11485
+ x += w;
11486
+ }
11487
+ y += 22;
11488
+ const columnWidth = INNER / 2;
11489
+ segments.forEach((segment, i) => {
11490
+ const cx = PAD + i % 2 * columnWidth;
11491
+ const cy = y + Math.floor(i / 2) * 18;
11492
+ parts.push(rect(cx, cy, 10, 10, segment.color, 2));
11493
+ parts.push(
11494
+ text(cx + 16, cy + 9, `${segment.name} ${String(segment.percent)}%`, {
11495
+ size: 12,
11496
+ fill: TEXT_COLOR
11497
+ })
11498
+ );
11499
+ });
11500
+ y += Math.ceil(segments.length / 2) * 18 + 10;
11501
+ }
11502
+ if (hasContributions && snapshot.contributions) {
11503
+ const days = snapshot.contributions.days.slice(-MAX_CALENDAR_DAYS);
11504
+ const max = days.reduce((m, d) => Math.max(m, d.count), 1);
11505
+ parts.push(
11506
+ caption(PAD, y + 11, `Contributions \xB7 ${groupDigits(snapshot.contributions.total)}`)
11507
+ );
11508
+ y += 22;
11509
+ days.forEach((day, i) => {
11510
+ const column = Math.floor(i / CALENDAR_ROWS);
11511
+ const row = i % CALENDAR_ROWS;
11512
+ const fill = HEAT_COLORS[heatLevel(day.count, max)] ?? HEAT_COLORS[0];
11513
+ parts.push(rect(PAD + column * CELL_PITCH, y + row * CELL_PITCH, CELL, CELL, fill, 1.5));
11514
+ });
11515
+ y += CALENDAR_ROWS * CELL_PITCH - (CELL_PITCH - CELL) + 12;
11516
+ }
11517
+ if (hasCodingTime || hasRank) {
11518
+ const rankX = PAD + INNER - 120;
11519
+ if (hasCodingTime && snapshot.codingTime) {
11520
+ const t = snapshot.codingTime;
11521
+ parts.push(caption(PAD, y + 11, "Coding time"));
11522
+ parts.push(
11523
+ text(PAD, y + 38, `${groupDigits(t.hours)}h ${String(t.minutes)}m`, {
11524
+ size: 22,
11525
+ fill: TEXT_COLOR,
11526
+ weight: 700
11527
+ })
11528
+ );
11529
+ }
11530
+ if (hasRank && snapshot.rank) {
11531
+ const rank = snapshot.rank;
11532
+ parts.push(caption(rankX, y + 11, "Rank"));
11533
+ const centerY = y + 32;
11534
+ parts.push(
11535
+ `<circle cx="${String(rankX + 14)}" cy="${String(centerY)}" r="14" fill="${ACCENT_COLOR}"/>`
11536
+ );
11537
+ parts.push(
11538
+ `<text x="${String(rankX + 14)}" y="${String(centerY)}" font-family="${FONT_FAMILY}" font-size="14" font-weight="700" fill="#ffffff" text-anchor="middle" dominant-baseline="central">${escapeXml(rank.tier)}</text>`
11539
+ );
11540
+ parts.push(
11541
+ text(rankX + 36, centerY + 4, `score ${String(rank.score)}`, {
11542
+ size: 12,
11543
+ fill: MUTED_COLOR
11544
+ })
11545
+ );
11546
+ }
11547
+ y += 50;
11548
+ }
11549
+ parts.push(
11550
+ text(WIDTH - PAD, y + 11, `Last synced ${snapshot.lastSyncedAt.slice(0, 10)}`, {
11551
+ size: 11,
11552
+ fill: MUTED_COLOR,
11553
+ anchor: "end"
11554
+ })
11555
+ );
11556
+ y += 22;
11557
+ const height = y + PAD - 12;
11558
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${String(WIDTH)}" height="${String(height)}" viewBox="0 0 ${String(WIDTH)} ${String(height)}" role="img" aria-label="Developer activity"><title>Developer activity</title>${parts.join("")}</svg>`;
11559
+ }
11560
+
11561
+ // src/image.ts
11562
+ var ACCEPTED_IMAGE_MIME_TYPES = [
11563
+ "image/jpeg",
11564
+ "image/png",
11565
+ "image/webp",
11566
+ "image/gif"
11567
+ ];
11568
+ var MAX_IMAGE_BYTES = 5 * 1024 * 1024;
11569
+ var MAX_IMAGE_DIMENSION = 4096;
11570
+ var MAX_IMAGE_FRAMES = 100;
11571
+ var IMAGE_EXTENSIONS = {
11572
+ "image/jpeg": "jpg",
11573
+ "image/png": "png",
11574
+ "image/webp": "webp",
11575
+ "image/gif": "gif"
11576
+ };
11577
+ function u16be(b, o) {
11578
+ return b[o] << 8 | b[o + 1];
11579
+ }
11580
+ function u32be(b, o) {
11581
+ return (b[o] << 24 | b[o + 1] << 16 | b[o + 2] << 8 | b[o + 3]) >>> 0;
11582
+ }
11583
+ function u16le(b, o) {
11584
+ return b[o] | b[o + 1] << 8;
11585
+ }
11586
+ function u24le(b, o) {
11587
+ return b[o] | b[o + 1] << 8 | b[o + 2] << 16;
11588
+ }
11589
+ function u32le(b, o) {
11590
+ return (b[o] | b[o + 1] << 8 | b[o + 2] << 16 | b[o + 3] << 24) >>> 0;
11591
+ }
11592
+ function ascii(b, o, len) {
11593
+ let s = "";
11594
+ for (let i = 0; i < len; i++) s += String.fromCharCode(b[o + i]);
11595
+ return s;
11596
+ }
11597
+ function startsWith(b, sig) {
11598
+ if (b.length < sig.length) return false;
11599
+ for (let i = 0; i < sig.length; i++) if (b[i] !== sig[i]) return false;
11600
+ return true;
11601
+ }
11602
+ function concat(parts) {
11603
+ let total = 0;
11604
+ for (const p of parts) total += p.length;
11605
+ const out = new Uint8Array(total);
11606
+ let o = 0;
11607
+ for (const p of parts) {
11608
+ out.set(p, o);
11609
+ o += p.length;
11610
+ }
11611
+ return out;
11612
+ }
11613
+ var JPEG_SIG = [255, 216, 255];
11614
+ var PNG_SIG = [137, 80, 78, 71, 13, 10, 26, 10];
11615
+ function isJpeg(b) {
11616
+ return startsWith(b, JPEG_SIG);
11617
+ }
11618
+ function isPng(b) {
11619
+ return startsWith(b, PNG_SIG);
11620
+ }
11621
+ function isGif(b) {
11622
+ return b.length >= 6 && ascii(b, 0, 4) === "GIF8" && (b[4] === 55 || b[4] === 57) && b[5] === 97;
11623
+ }
11624
+ function isWebp(b) {
11625
+ return b.length >= 12 && ascii(b, 0, 4) === "RIFF" && ascii(b, 8, 4) === "WEBP";
11626
+ }
11627
+ function detectImageMime(bytes) {
11628
+ if (isJpeg(bytes)) return "image/jpeg";
11629
+ if (isPng(bytes)) return "image/png";
11630
+ if (isGif(bytes)) return "image/gif";
11631
+ if (isWebp(bytes)) return "image/webp";
11632
+ return null;
11633
+ }
11634
+ function isJpegSof(marker) {
11635
+ return marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204;
11636
+ }
11637
+ function readJpegInfo(b) {
11638
+ let i = 2;
11639
+ while (i + 1 < b.length) {
11640
+ if (b[i] !== 255) {
11641
+ i++;
11642
+ continue;
11643
+ }
11644
+ let marker = b[i + 1];
11645
+ while (marker === 255 && i + 2 < b.length) {
11646
+ i++;
11647
+ marker = b[i + 1];
11648
+ }
11649
+ i += 2;
11650
+ if (marker === 216 || marker === 217 || marker >= 208 && marker <= 215 || marker === 1) {
11651
+ continue;
11652
+ }
11653
+ if (marker === 218) break;
11654
+ if (i + 1 >= b.length) break;
11655
+ const len = u16be(b, i);
11656
+ if (isJpegSof(marker)) {
11657
+ if (i + 6 >= b.length) return null;
11658
+ return { height: u16be(b, i + 3), width: u16be(b, i + 5), frames: 1 };
11659
+ }
11660
+ i += len;
11661
+ }
11662
+ return null;
11663
+ }
11664
+ function readPngInfo(b) {
11665
+ if (b.length < 24) return null;
11666
+ const width = u32be(b, 16);
11667
+ const height = u32be(b, 20);
11668
+ let frames = 1;
11669
+ let o = 8;
11670
+ while (o + 12 <= b.length) {
11671
+ const len = u32be(b, o);
11672
+ const type = ascii(b, o + 4, 4);
11673
+ if (type === "acTL") {
11674
+ frames = u32be(b, o + 8);
11675
+ break;
11676
+ }
11677
+ if (type === "IDAT" || type === "IEND") break;
11678
+ o += 12 + len;
11679
+ }
11680
+ return { width, height, frames };
11681
+ }
11682
+ function skipGifSubBlocks(b, o) {
11683
+ while (o < b.length) {
11684
+ const len = b[o];
11685
+ o += 1;
11686
+ if (len === 0) break;
11687
+ o += len;
11688
+ }
11689
+ return o;
11690
+ }
11691
+ function readGifInfo(b) {
11692
+ if (b.length < 13) return null;
11693
+ const width = u16le(b, 6);
11694
+ const height = u16le(b, 8);
11695
+ const packed = b[10];
11696
+ let o = 13;
11697
+ if (packed & 128) o += 3 * (1 << (packed & 7) + 1);
11698
+ let frames = 0;
11699
+ while (o < b.length) {
11700
+ const block = b[o];
11701
+ if (block === 59) break;
11702
+ if (block === 33) {
11703
+ o = skipGifSubBlocks(b, o + 2);
11704
+ } else if (block === 44) {
11705
+ frames += 1;
11706
+ const localPacked = b[o + 9];
11707
+ o += 10;
11708
+ if (localPacked & 128) o += 3 * (1 << (localPacked & 7) + 1);
11709
+ o += 1;
11710
+ o = skipGifSubBlocks(b, o);
11711
+ } else {
11712
+ break;
11713
+ }
11714
+ }
11715
+ return { width, height, frames: Math.max(frames, 1) };
11716
+ }
11717
+ function readWebpInfo(b) {
11718
+ if (b.length < 16) return null;
11719
+ const format = ascii(b, 12, 4);
11720
+ if (format === "VP8X") {
11721
+ const width = u24le(b, 24) + 1;
11722
+ const height = u24le(b, 27) + 1;
11723
+ const animated = (b[20] & 2) !== 0;
11724
+ let frames = 1;
11725
+ if (animated) {
11726
+ frames = 0;
11727
+ let o = 12;
11728
+ while (o + 8 <= b.length) {
11729
+ const fourcc = ascii(b, o, 4);
11730
+ const size = u32le(b, o + 4);
11731
+ if (fourcc === "ANMF") frames += 1;
11732
+ o += 8 + size + (size & 1);
11733
+ }
11734
+ frames = Math.max(frames, 1);
11735
+ }
11736
+ return { width, height, frames };
11737
+ }
11738
+ if (format === "VP8 ") {
11739
+ const width = u16le(b, 26) & 16383;
11740
+ const height = u16le(b, 28) & 16383;
11741
+ return { width, height, frames: 1 };
11742
+ }
11743
+ if (format === "VP8L") {
11744
+ const bits = u32le(b, 21);
11745
+ const width = (bits & 16383) + 1;
11746
+ const height = (bits >> 14 & 16383) + 1;
11747
+ return { width, height, frames: 1 };
11748
+ }
11749
+ return null;
11750
+ }
11751
+ function readImageInfo(bytes, mime) {
11752
+ switch (mime) {
11753
+ case "image/jpeg":
11754
+ return readJpegInfo(bytes);
11755
+ case "image/png":
11756
+ return readPngInfo(bytes);
11757
+ case "image/gif":
11758
+ return readGifInfo(bytes);
11759
+ case "image/webp":
11760
+ return readWebpInfo(bytes);
11761
+ }
11762
+ }
11763
+ function stripJpegMetadata(b) {
11764
+ const out = [b.subarray(0, 2)];
11765
+ let i = 2;
11766
+ while (i + 1 < b.length) {
11767
+ if (b[i] !== 255) break;
11768
+ let marker = b[i + 1];
11769
+ let markerStart = i;
11770
+ while (marker === 255 && i + 2 < b.length) {
11771
+ i++;
11772
+ marker = b[i + 1];
11773
+ }
11774
+ if (i !== markerStart) out.push(b.subarray(markerStart, i));
11775
+ markerStart = i;
11776
+ i += 2;
11777
+ if (marker === 217 || marker >= 208 && marker <= 215 || marker === 1) {
11778
+ out.push(b.subarray(markerStart, i));
11779
+ continue;
11780
+ }
11781
+ if (marker === 218) {
11782
+ out.push(b.subarray(markerStart));
11783
+ break;
11784
+ }
11785
+ if (i + 1 >= b.length) {
11786
+ out.push(b.subarray(markerStart));
11787
+ break;
11788
+ }
11789
+ const len = u16be(b, i);
11790
+ const segmentEnd = i + len;
11791
+ const isMetadata = marker === 225 || marker === 226 || marker === 237 || marker === 254;
11792
+ if (!isMetadata) out.push(b.subarray(markerStart, segmentEnd));
11793
+ i = segmentEnd;
11794
+ }
11795
+ return concat(out);
11796
+ }
11797
+ var PNG_METADATA_CHUNKS = /* @__PURE__ */ new Set(["eXIf", "tEXt", "iTXt", "zTXt", "tIME"]);
11798
+ function stripPngMetadata(b) {
11799
+ const out = [b.subarray(0, 8)];
11800
+ let o = 8;
11801
+ while (o + 12 <= b.length) {
11802
+ const len = u32be(b, o);
11803
+ const type = ascii(b, o + 4, 4);
11804
+ const end = o + 12 + len;
11805
+ if (end > b.length) {
11806
+ out.push(b.subarray(o));
11807
+ break;
11808
+ }
11809
+ if (!PNG_METADATA_CHUNKS.has(type)) out.push(b.subarray(o, end));
11810
+ o = end;
11811
+ }
11812
+ return concat(out);
11813
+ }
11814
+ function stripGifMetadata(b) {
11815
+ if (b.length < 13) return b;
11816
+ const packed = b[10];
11817
+ let headerEnd = 13;
11818
+ if (packed & 128) headerEnd += 3 * (1 << (packed & 7) + 1);
11819
+ const out = [b.subarray(0, headerEnd)];
11820
+ let o = headerEnd;
11821
+ while (o < b.length) {
11822
+ const block = b[o];
11823
+ if (block === 59) {
11824
+ out.push(b.subarray(o, o + 1));
11825
+ break;
11826
+ }
11827
+ if (block === 33) {
11828
+ const label = b[o + 1];
11829
+ const blockEnd = skipGifSubBlocks(b, o + 2);
11830
+ const isMetadata = label === 254 || label === 255;
11831
+ if (!isMetadata) out.push(b.subarray(o, blockEnd));
11832
+ o = blockEnd;
11833
+ } else if (block === 44) {
11834
+ const localPacked = b[o + 9];
11835
+ let dataStart = o + 10;
11836
+ if (localPacked & 128) dataStart += 3 * (1 << (localPacked & 7) + 1);
11837
+ dataStart += 1;
11838
+ const blockEnd = skipGifSubBlocks(b, dataStart);
11839
+ out.push(b.subarray(o, blockEnd));
11840
+ o = blockEnd;
11841
+ } else {
11842
+ out.push(b.subarray(o));
11843
+ break;
11844
+ }
11845
+ }
11846
+ return concat(out);
11847
+ }
11848
+ var WEBP_METADATA_CHUNKS = /* @__PURE__ */ new Set(["EXIF", "XMP ", "ICCP"]);
11849
+ function stripWebpMetadata(b) {
11850
+ if (b.length < 12) return b;
11851
+ const chunks = [];
11852
+ let o = 12;
11853
+ while (o + 8 <= b.length) {
11854
+ const fourcc = ascii(b, o, 4);
11855
+ const size = u32le(b, o + 4);
11856
+ const padded = size + (size & 1);
11857
+ const end = o + 8 + padded;
11858
+ if (end > b.length) {
11859
+ chunks.push(b.subarray(o));
11860
+ break;
11861
+ }
11862
+ if (!WEBP_METADATA_CHUNKS.has(fourcc)) chunks.push(b.subarray(o, end));
11863
+ o = end;
11864
+ }
11865
+ const body = concat(chunks);
11866
+ const out = new Uint8Array(12 + body.length);
11867
+ out.set(b.subarray(0, 12), 0);
11868
+ out.set(body, 12);
11869
+ out[4] = 4 + body.length & 255;
11870
+ out[5] = 4 + body.length >> 8 & 255;
11871
+ out[6] = 4 + body.length >> 16 & 255;
11872
+ out[7] = 4 + body.length >> 24 & 255;
11873
+ if (ascii(out, 12, 4) === "VP8X") {
11874
+ out[20] = out[20] & ~44;
11875
+ }
11876
+ return out;
11877
+ }
11878
+ function stripImageMetadata(bytes, mime) {
11879
+ switch (mime) {
11880
+ case "image/jpeg":
11881
+ return stripJpegMetadata(bytes);
11882
+ case "image/png":
11883
+ return stripPngMetadata(bytes);
11884
+ case "image/gif":
11885
+ return stripGifMetadata(bytes);
11886
+ case "image/webp":
11887
+ return stripWebpMetadata(bytes);
11888
+ }
11889
+ }
11890
+
11036
11891
  // src/index.ts
11037
- var SCHEMA_VERSION = "0.4.0";
11892
+ var SCHEMA_VERSION = "0.5.0";
11038
11893
  export {
11894
+ ACCEPTED_IMAGE_MIME_TYPES,
11039
11895
  ConflictError,
11896
+ IMAGE_EXTENSIONS,
11040
11897
  ImportError,
11898
+ MAX_IMAGE_BYTES,
11899
+ MAX_IMAGE_DIMENSION,
11900
+ MAX_IMAGE_FRAMES,
11041
11901
  MigrationError,
11042
11902
  NotFoundError,
11903
+ RANK_FULL_CODING_HOURS,
11904
+ RANK_FULL_CONTRIBUTIONS,
11905
+ RANK_TIER_THRESHOLDS,
11043
11906
  SCHEMA_VERSION,
11044
11907
  SUPPORTED_SCHEMA_VERSIONS,
11045
11908
  StorageError,
11046
11909
  applyPublicPrivacyFilter,
11910
+ computeLanguagePercentages,
11911
+ deriveRankTier,
11912
+ detectImageMime,
11047
11913
  exportTakuhon,
11914
+ formatCodingTime,
11048
11915
  generateJsonLd,
11049
11916
  generatePersonJsonLd,
11050
11917
  generateProfilePageJsonLd,
11051
11918
  importTakuhon,
11919
+ isActivitySnapshot,
11052
11920
  migrateTakuhon,
11053
11921
  migrations,
11054
11922
  normalize,
11923
+ readImageInfo,
11924
+ renderActivitySvg,
11055
11925
  resolveLocale,
11056
11926
  schema,
11927
+ stripImageMetadata,
11057
11928
  validate
11058
11929
  };
11059
11930
  //# sourceMappingURL=index.js.map