@takuhon/core 0.13.0 → 0.15.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 +161 -3
- package/dist/index.js +206 -24
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -3697,20 +3697,69 @@ declare class ConflictError extends StorageError {
|
|
|
3697
3697
|
* snapshot always renders the same markup, so both rendering surfaces (the
|
|
3698
3698
|
* static HTML export and the React profile) and their tests stay in lockstep.
|
|
3699
3699
|
*
|
|
3700
|
+
* The card paints an **opaque background rectangle** first, so it stays legible
|
|
3701
|
+
* wherever it is embedded — including a GitHub README served through the Camo
|
|
3702
|
+
* image proxy, where no surrounding page styles apply and a transparent card
|
|
3703
|
+
* would render invisibly on a dark theme. The colour set is supplied by a
|
|
3704
|
+
* {@link Palette}: {@link renderActivitySvg} defaults to {@link LIGHT_PALETTE},
|
|
3705
|
+
* and a transport (a Worker route, the CLI build) can pass {@link DARK_PALETTE}
|
|
3706
|
+
* to serve a dark variant. Both palettes are plain data, so the render stays
|
|
3707
|
+
* deterministic.
|
|
3708
|
+
*
|
|
3700
3709
|
* Every snapshot-derived string (language names, dates) is XML-escaped before
|
|
3701
3710
|
* it reaches the markup: language names come from an external API and are
|
|
3702
3711
|
* treated as untrusted. An empty snapshot (no metric fields) renders to `''`
|
|
3703
3712
|
* so callers can omit the section with a truthiness check.
|
|
3704
3713
|
*/
|
|
3705
3714
|
|
|
3715
|
+
/**
|
|
3716
|
+
* Colour set for an activity card. Kept as plain data (no CSS variables, no
|
|
3717
|
+
* `currentColor`) so the rendered SVG is fully self-contained and legible when
|
|
3718
|
+
* served as an image through Camo, where no page styles apply.
|
|
3719
|
+
*/
|
|
3720
|
+
interface Palette {
|
|
3721
|
+
/** Fill of the opaque background rectangle behind the whole card. */
|
|
3722
|
+
background: string;
|
|
3723
|
+
/** Primary text (language legend, coding-time figure). */
|
|
3724
|
+
text: string;
|
|
3725
|
+
/** Secondary text (captions, rank score, "Last synced"). */
|
|
3726
|
+
muted: string;
|
|
3727
|
+
/** Accent fill (the rank badge disc). */
|
|
3728
|
+
accent: string;
|
|
3729
|
+
/**
|
|
3730
|
+
* Order-based palette for the language bar and its legend chips; entries are
|
|
3731
|
+
* assigned to the top languages in order, and anything past the end (or the
|
|
3732
|
+
* folded "Other" segment) uses {@link Palette.otherColor}.
|
|
3733
|
+
*/
|
|
3734
|
+
languageColors: readonly string[];
|
|
3735
|
+
/** Colour of the folded "Other" language segment. */
|
|
3736
|
+
otherColor: string;
|
|
3737
|
+
/** Five-step contribution heat ramp, lowest (zero) first. */
|
|
3738
|
+
heatColors: readonly string[];
|
|
3739
|
+
}
|
|
3740
|
+
/** Default palette: dark text on an opaque white card. */
|
|
3741
|
+
declare const LIGHT_PALETTE: Palette;
|
|
3742
|
+
/**
|
|
3743
|
+
* Dark variant: light text on an opaque dark card (tuned to read on a GitHub
|
|
3744
|
+
* dark-theme README). The heat ramp stays in the card's blue family rather than
|
|
3745
|
+
* borrowing GitHub's green, so light and dark cards look like the same design.
|
|
3746
|
+
*/
|
|
3747
|
+
declare const DARK_PALETTE: Palette;
|
|
3748
|
+
/** Options accepted by {@link renderActivitySvg}. */
|
|
3749
|
+
interface RenderActivitySvgOptions {
|
|
3750
|
+
/** Colour set to render with. Defaults to {@link LIGHT_PALETTE}. */
|
|
3751
|
+
palette?: Palette;
|
|
3752
|
+
}
|
|
3706
3753
|
/**
|
|
3707
3754
|
* Render the activity snapshot as a self-contained `<svg>` string, or `''`
|
|
3708
3755
|
* when the snapshot carries no metric data (so callers can omit the section).
|
|
3709
3756
|
* Sections render independently — whatever the snapshot holds is shown, the
|
|
3710
3757
|
* rest is left out — and `lastSyncedAt`'s date is always stamped in the footer
|
|
3711
|
-
* so staleness stays visible.
|
|
3758
|
+
* so staleness stays visible. Colours come from `options.palette`
|
|
3759
|
+
* ({@link LIGHT_PALETTE} by default); the card always paints an opaque
|
|
3760
|
+
* background so it stays legible when embedded as an image.
|
|
3712
3761
|
*/
|
|
3713
|
-
declare function renderActivitySvg(snapshot: ActivitySnapshot): string;
|
|
3762
|
+
declare function renderActivitySvg(snapshot: ActivitySnapshot, options?: RenderActivitySvgOptions): string;
|
|
3714
3763
|
|
|
3715
3764
|
/**
|
|
3716
3765
|
* Résumé / CV projection of a profile.
|
|
@@ -3801,6 +3850,115 @@ interface CvDocument {
|
|
|
3801
3850
|
*/
|
|
3802
3851
|
declare function deriveCv(localized: LocalizedTakuhon): CvDocument;
|
|
3803
3852
|
|
|
3853
|
+
/**
|
|
3854
|
+
* MCP (Model Context Protocol) projection of a profile.
|
|
3855
|
+
*
|
|
3856
|
+
* This module describes how a takuhon profile is exposed to an AI agent as a
|
|
3857
|
+
* set of read-only MCP tools and resources, and how each one is answered from a
|
|
3858
|
+
* profile document — *without* committing to a transport (stdio / HTTP) or to
|
|
3859
|
+
* any MCP SDK.
|
|
3860
|
+
*
|
|
3861
|
+
* The split mirrors the rest of core: deciding *what* to expose and *what it
|
|
3862
|
+
* returns* is a pure, deterministic projection over the canonical document
|
|
3863
|
+
* (exactly like {@link generateJsonLd}), so it lives here in `@takuhon/core` and
|
|
3864
|
+
* stays runtime-free (spec §2.3). The transport, JSON-RPC framing, and server
|
|
3865
|
+
* lifecycle are runtime-aware; they live in `@takuhon/mcp` and the CLI /
|
|
3866
|
+
* Cloudflare adapters, which call the pure executors below.
|
|
3867
|
+
*
|
|
3868
|
+
* Everything here is read-only. No tool or resource touches the admin surface,
|
|
3869
|
+
* and every answer passes through {@link applyPublicPrivacyFilter}, so an MCP
|
|
3870
|
+
* client sees exactly what `GET /api/profile`, `GET /api/jsonld`, `GET
|
|
3871
|
+
* /api/schema`, and `GET /takuhon.json` already expose — no more.
|
|
3872
|
+
*
|
|
3873
|
+
* The catalog ({@link MCP_TOOLS} / {@link MCP_RESOURCES}) is plain data with no
|
|
3874
|
+
* SDK dependency, so the `@takuhon/mcp` wiring can register it against the
|
|
3875
|
+
* official `@modelcontextprotocol/sdk` and later be swapped to a hand-rolled
|
|
3876
|
+
* dispatcher without touching this file.
|
|
3877
|
+
*/
|
|
3878
|
+
|
|
3879
|
+
/**
|
|
3880
|
+
* Profile sections a `get_section` call may request: the content-bearing keys
|
|
3881
|
+
* of a locale-resolved profile (the spec §6 data sections). `settings`,
|
|
3882
|
+
* `meta`, `schemaVersion`, and `resolvedLocale` are intentionally excluded —
|
|
3883
|
+
* they are configuration / bookkeeping, not profile content an agent reads.
|
|
3884
|
+
*/
|
|
3885
|
+
declare const MCP_PROFILE_SECTIONS: readonly ["profile", "links", "careers", "projects", "skills", "certifications", "memberships", "volunteering", "honors", "education", "publications", "languages", "courses", "patents", "testScores", "recommendations", "contact"];
|
|
3886
|
+
/** One of the {@link MCP_PROFILE_SECTIONS} a `get_section` call accepts. */
|
|
3887
|
+
type McpProfileSection = (typeof MCP_PROFILE_SECTIONS)[number];
|
|
3888
|
+
/** A JSON Schema (draft 2020-12) object describing a tool's input arguments. */
|
|
3889
|
+
type McpInputSchema = Readonly<Record<string, unknown>>;
|
|
3890
|
+
/** A read-only MCP tool definition — plain data, no SDK dependency. */
|
|
3891
|
+
interface McpToolDefinition {
|
|
3892
|
+
/** Stable tool id used in `tools/call`. */
|
|
3893
|
+
name: string;
|
|
3894
|
+
/** Human-readable title for display. */
|
|
3895
|
+
title: string;
|
|
3896
|
+
/** What the tool returns; shown to the agent when it chooses a tool. */
|
|
3897
|
+
description: string;
|
|
3898
|
+
/** JSON Schema for the tool's `arguments` object. */
|
|
3899
|
+
inputSchema: McpInputSchema;
|
|
3900
|
+
}
|
|
3901
|
+
/** A read-only MCP resource definition — plain data, no SDK dependency. */
|
|
3902
|
+
interface McpResourceDefinition {
|
|
3903
|
+
/** Stable resource URI used in `resources/read`. */
|
|
3904
|
+
uri: string;
|
|
3905
|
+
name: string;
|
|
3906
|
+
title: string;
|
|
3907
|
+
description: string;
|
|
3908
|
+
/** MIME type of the resource contents. */
|
|
3909
|
+
mimeType: string;
|
|
3910
|
+
}
|
|
3911
|
+
/** Result of {@link executeMcpTool}: the structured value the tool produced. */
|
|
3912
|
+
interface McpToolResult {
|
|
3913
|
+
/**
|
|
3914
|
+
* The JSON value the tool produced — already normalized, locale-resolved, and
|
|
3915
|
+
* privacy-filtered. The transport layer serializes this to MCP wire content.
|
|
3916
|
+
*/
|
|
3917
|
+
data: unknown;
|
|
3918
|
+
/** Locale the result was resolved at, when the tool resolved one. */
|
|
3919
|
+
resolvedLocale?: string;
|
|
3920
|
+
}
|
|
3921
|
+
/** Result of {@link readMcpResource}: one resource's contents. */
|
|
3922
|
+
interface McpResourceResult {
|
|
3923
|
+
uri: string;
|
|
3924
|
+
mimeType: string;
|
|
3925
|
+
/** The JSON value of the resource. The transport layer serializes it. */
|
|
3926
|
+
data: unknown;
|
|
3927
|
+
}
|
|
3928
|
+
/**
|
|
3929
|
+
* Thrown by {@link executeMcpTool} / {@link readMcpResource} for an unknown
|
|
3930
|
+
* tool/resource name or invalid arguments. The transport layer maps it to the
|
|
3931
|
+
* appropriate MCP error (a JSON-RPC error for a malformed request, or an
|
|
3932
|
+
* `isError` tool result for a bad argument) — core stays transport-agnostic.
|
|
3933
|
+
*/
|
|
3934
|
+
declare class McpRequestError extends Error {
|
|
3935
|
+
constructor(message: string, options?: {
|
|
3936
|
+
cause?: unknown;
|
|
3937
|
+
});
|
|
3938
|
+
}
|
|
3939
|
+
/** The read-only tools an MCP client may call against the profile. */
|
|
3940
|
+
declare const MCP_TOOLS: readonly McpToolDefinition[];
|
|
3941
|
+
/** The read-only resources an MCP client may read. */
|
|
3942
|
+
declare const MCP_RESOURCES: readonly McpResourceDefinition[];
|
|
3943
|
+
/**
|
|
3944
|
+
* Execute a read-only MCP tool against a profile document.
|
|
3945
|
+
*
|
|
3946
|
+
* `profile` is the raw validated {@link Takuhon} (the transport layer loaded it
|
|
3947
|
+
* from KV / a file). Locale-aware tools normalize, locale-resolve, and
|
|
3948
|
+
* privacy-filter it exactly as `GET /api/profile` does, then project the
|
|
3949
|
+
* requested view. Pure and deterministic — no clock, no randomness, no I/O.
|
|
3950
|
+
*
|
|
3951
|
+
* @throws {McpRequestError} for an unknown tool name or invalid arguments.
|
|
3952
|
+
*/
|
|
3953
|
+
declare function executeMcpTool(name: string, args: Readonly<Record<string, unknown>>, profile: Takuhon): McpToolResult;
|
|
3954
|
+
/**
|
|
3955
|
+
* Read a read-only MCP resource from a profile document. Pure and
|
|
3956
|
+
* deterministic.
|
|
3957
|
+
*
|
|
3958
|
+
* @throws {McpRequestError} for an unknown resource URI.
|
|
3959
|
+
*/
|
|
3960
|
+
declare function readMcpResource(uri: string, profile: Takuhon): McpResourceResult;
|
|
3961
|
+
|
|
3804
3962
|
/**
|
|
3805
3963
|
* Image-asset helpers for uploaded media (avatars, project images): magic-byte
|
|
3806
3964
|
* type detection, header-only dimension / frame reading, and metadata stripping
|
|
@@ -3901,4 +4059,4 @@ declare function stripImageMetadata(bytes: Uint8Array, mime: AcceptedImageMime):
|
|
|
3901
4059
|
*/
|
|
3902
4060
|
declare const SCHEMA_VERSION = "0.5.0";
|
|
3903
4061
|
|
|
3904
|
-
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, type Education, type ExportOptions, type ExportedTakuhon, type Honor, IMAGE_EXTENSIONS, type ImageInfo, ImportError, type Iso3166Alpha2, type IsoDateTime, 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, type Membership, type Meta, type MetaPrivacy, type Migration, MigrationError, type NormalizedTakuhon, NotFoundError, type Patent, type PatentStatus, type Profile, type Project, type Publication, RANK_FULL_CODING_HOURS, RANK_FULL_CONTRIBUTIONS, RANK_TIER_THRESHOLDS, type RankInput, type RankTier, type RankTierLabel, type Recommendation, type RecommendationAuthor, 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, exportTakuhon, formatCodingTime, generateJsonLd, generatePersonJsonLd, generateProfilePageJsonLd, importTakuhon, isActivitySnapshot, migrateTakuhon, migrations, normalize, readImageInfo, renderActivitySvg, resolveLocale, schema, stripImageMetadata, validate };
|
|
4062
|
+
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 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 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, generateJsonLd, generatePersonJsonLd, generateProfilePageJsonLd, importTakuhon, isActivitySnapshot, migrateTakuhon, migrations, normalize, readImageInfo, readMcpResource, renderActivitySvg, resolveLocale, schema, stripImageMetadata, validate };
|
package/dist/index.js
CHANGED
|
@@ -11413,18 +11413,30 @@ function deriveRankTier(input) {
|
|
|
11413
11413
|
var WIDTH = 520;
|
|
11414
11414
|
var PAD = 16;
|
|
11415
11415
|
var INNER = WIDTH - PAD * 2;
|
|
11416
|
-
var TEXT_COLOR = "#1a1a1a";
|
|
11417
|
-
var MUTED_COLOR = "#666666";
|
|
11418
|
-
var ACCENT_COLOR = "#0b5fff";
|
|
11419
11416
|
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
11417
|
var MAX_LANGUAGES = 6;
|
|
11423
|
-
var HEAT_COLORS = ["#ebedf0", "#cce0ff", "#99c2ff", "#4d8aff", "#0b5fff"];
|
|
11424
11418
|
var CELL = 7;
|
|
11425
11419
|
var CELL_PITCH = 9;
|
|
11426
11420
|
var CALENDAR_ROWS = 7;
|
|
11427
11421
|
var MAX_CALENDAR_DAYS = 53 * CALENDAR_ROWS;
|
|
11422
|
+
var LIGHT_PALETTE = {
|
|
11423
|
+
background: "#ffffff",
|
|
11424
|
+
text: "#1a1a1a",
|
|
11425
|
+
muted: "#666666",
|
|
11426
|
+
accent: "#0b5fff",
|
|
11427
|
+
languageColors: ["#0b5fff", "#7c3aed", "#059669", "#d97706", "#dc2626", "#0891b2"],
|
|
11428
|
+
otherColor: "#9ca3af",
|
|
11429
|
+
heatColors: ["#ebedf0", "#cce0ff", "#99c2ff", "#4d8aff", "#0b5fff"]
|
|
11430
|
+
};
|
|
11431
|
+
var DARK_PALETTE = {
|
|
11432
|
+
background: "#0d1117",
|
|
11433
|
+
text: "#e6edf3",
|
|
11434
|
+
muted: "#9198a1",
|
|
11435
|
+
accent: "#4493f8",
|
|
11436
|
+
languageColors: ["#4493f8", "#a371f7", "#3fb950", "#d29922", "#f85149", "#39c5cf"],
|
|
11437
|
+
otherColor: "#6e7681",
|
|
11438
|
+
heatColors: ["#161b22", "#0a2c5e", "#11468f", "#1f6fdb", "#4493f8"]
|
|
11439
|
+
};
|
|
11428
11440
|
function escapeXml(value) {
|
|
11429
11441
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
11430
11442
|
}
|
|
@@ -11441,23 +11453,23 @@ function text(x, y, content, opts) {
|
|
|
11441
11453
|
const anchor = opts.anchor !== void 0 ? ` text-anchor="${opts.anchor}"` : "";
|
|
11442
11454
|
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
11455
|
}
|
|
11444
|
-
function caption(x, y, label) {
|
|
11445
|
-
return text(x, y, label, { size: 12, fill
|
|
11456
|
+
function caption(x, y, label, fill) {
|
|
11457
|
+
return text(x, y, label, { size: 12, fill, weight: 600 });
|
|
11446
11458
|
}
|
|
11447
11459
|
function rect(x, y, w, h, fill, rx = 0) {
|
|
11448
11460
|
const corner = rx > 0 ? ` rx="${String(rx)}"` : "";
|
|
11449
11461
|
return `<rect x="${String(round2(x))}" y="${String(y)}" width="${String(round2(w))}" height="${String(h)}" fill="${fill}"${corner}/>`;
|
|
11450
11462
|
}
|
|
11451
|
-
function languageSegments(languages) {
|
|
11463
|
+
function languageSegments(languages, palette) {
|
|
11452
11464
|
const segments = languages.slice(0, MAX_LANGUAGES).map((l, i) => ({
|
|
11453
11465
|
name: l.name,
|
|
11454
11466
|
percent: l.percent,
|
|
11455
|
-
color:
|
|
11467
|
+
color: palette.languageColors[i] ?? palette.otherColor
|
|
11456
11468
|
}));
|
|
11457
11469
|
const rest = languages.slice(MAX_LANGUAGES);
|
|
11458
11470
|
if (rest.length > 0) {
|
|
11459
11471
|
const percent = round2(rest.reduce((sum, l) => sum + l.percent, 0));
|
|
11460
|
-
segments.push({ name: "Other", percent, color:
|
|
11472
|
+
segments.push({ name: "Other", percent, color: palette.otherColor });
|
|
11461
11473
|
}
|
|
11462
11474
|
return segments.filter((s) => s.percent > 0);
|
|
11463
11475
|
}
|
|
@@ -11465,7 +11477,8 @@ function heatLevel(count, max) {
|
|
|
11465
11477
|
if (count <= 0) return 0;
|
|
11466
11478
|
return Math.min(4, Math.max(1, Math.ceil(count / max * 4)));
|
|
11467
11479
|
}
|
|
11468
|
-
function renderActivitySvg(snapshot) {
|
|
11480
|
+
function renderActivitySvg(snapshot, options = {}) {
|
|
11481
|
+
const palette = options.palette ?? LIGHT_PALETTE;
|
|
11469
11482
|
const hasLanguages = snapshot.languages !== void 0 && snapshot.languages.length > 0;
|
|
11470
11483
|
const hasContributions = snapshot.contributions !== void 0 && snapshot.contributions.days.length > 0;
|
|
11471
11484
|
const hasCodingTime = snapshot.codingTime !== void 0;
|
|
@@ -11474,9 +11487,9 @@ function renderActivitySvg(snapshot) {
|
|
|
11474
11487
|
const parts = [];
|
|
11475
11488
|
let y = PAD;
|
|
11476
11489
|
if (hasLanguages && snapshot.languages) {
|
|
11477
|
-
const segments = languageSegments(snapshot.languages);
|
|
11490
|
+
const segments = languageSegments(snapshot.languages, palette);
|
|
11478
11491
|
const total = segments.reduce((sum, s) => sum + s.percent, 0);
|
|
11479
|
-
parts.push(caption(PAD, y + 11, "Languages"));
|
|
11492
|
+
parts.push(caption(PAD, y + 11, "Languages", palette.muted));
|
|
11480
11493
|
y += 22;
|
|
11481
11494
|
let x = PAD;
|
|
11482
11495
|
for (const segment of segments) {
|
|
@@ -11493,7 +11506,7 @@ function renderActivitySvg(snapshot) {
|
|
|
11493
11506
|
parts.push(
|
|
11494
11507
|
text(cx + 16, cy + 9, `${segment.name} ${String(segment.percent)}%`, {
|
|
11495
11508
|
size: 12,
|
|
11496
|
-
fill:
|
|
11509
|
+
fill: palette.text
|
|
11497
11510
|
})
|
|
11498
11511
|
);
|
|
11499
11512
|
});
|
|
@@ -11503,13 +11516,18 @@ function renderActivitySvg(snapshot) {
|
|
|
11503
11516
|
const days = snapshot.contributions.days.slice(-MAX_CALENDAR_DAYS);
|
|
11504
11517
|
const max = days.reduce((m, d) => Math.max(m, d.count), 1);
|
|
11505
11518
|
parts.push(
|
|
11506
|
-
caption(
|
|
11519
|
+
caption(
|
|
11520
|
+
PAD,
|
|
11521
|
+
y + 11,
|
|
11522
|
+
`Contributions \xB7 ${groupDigits(snapshot.contributions.total)}`,
|
|
11523
|
+
palette.muted
|
|
11524
|
+
)
|
|
11507
11525
|
);
|
|
11508
11526
|
y += 22;
|
|
11509
11527
|
days.forEach((day, i) => {
|
|
11510
11528
|
const column = Math.floor(i / CALENDAR_ROWS);
|
|
11511
11529
|
const row = i % CALENDAR_ROWS;
|
|
11512
|
-
const fill =
|
|
11530
|
+
const fill = palette.heatColors[heatLevel(day.count, max)] ?? palette.heatColors[0];
|
|
11513
11531
|
parts.push(rect(PAD + column * CELL_PITCH, y + row * CELL_PITCH, CELL, CELL, fill, 1.5));
|
|
11514
11532
|
});
|
|
11515
11533
|
y += CALENDAR_ROWS * CELL_PITCH - (CELL_PITCH - CELL) + 12;
|
|
@@ -11518,21 +11536,21 @@ function renderActivitySvg(snapshot) {
|
|
|
11518
11536
|
const rankX = PAD + INNER - 120;
|
|
11519
11537
|
if (hasCodingTime && snapshot.codingTime) {
|
|
11520
11538
|
const t = snapshot.codingTime;
|
|
11521
|
-
parts.push(caption(PAD, y + 11, "Coding time"));
|
|
11539
|
+
parts.push(caption(PAD, y + 11, "Coding time", palette.muted));
|
|
11522
11540
|
parts.push(
|
|
11523
11541
|
text(PAD, y + 38, `${groupDigits(t.hours)}h ${String(t.minutes)}m`, {
|
|
11524
11542
|
size: 22,
|
|
11525
|
-
fill:
|
|
11543
|
+
fill: palette.text,
|
|
11526
11544
|
weight: 700
|
|
11527
11545
|
})
|
|
11528
11546
|
);
|
|
11529
11547
|
}
|
|
11530
11548
|
if (hasRank && snapshot.rank) {
|
|
11531
11549
|
const rank = snapshot.rank;
|
|
11532
|
-
parts.push(caption(rankX, y + 11, "Rank"));
|
|
11550
|
+
parts.push(caption(rankX, y + 11, "Rank", palette.muted));
|
|
11533
11551
|
const centerY = y + 32;
|
|
11534
11552
|
parts.push(
|
|
11535
|
-
`<circle cx="${String(rankX + 14)}" cy="${String(centerY)}" r="14" fill="${
|
|
11553
|
+
`<circle cx="${String(rankX + 14)}" cy="${String(centerY)}" r="14" fill="${palette.accent}"/>`
|
|
11536
11554
|
);
|
|
11537
11555
|
parts.push(
|
|
11538
11556
|
`<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>`
|
|
@@ -11540,7 +11558,7 @@ function renderActivitySvg(snapshot) {
|
|
|
11540
11558
|
parts.push(
|
|
11541
11559
|
text(rankX + 36, centerY + 4, `score ${String(rank.score)}`, {
|
|
11542
11560
|
size: 12,
|
|
11543
|
-
fill:
|
|
11561
|
+
fill: palette.muted
|
|
11544
11562
|
})
|
|
11545
11563
|
);
|
|
11546
11564
|
}
|
|
@@ -11549,13 +11567,14 @@ function renderActivitySvg(snapshot) {
|
|
|
11549
11567
|
parts.push(
|
|
11550
11568
|
text(WIDTH - PAD, y + 11, `Last synced ${snapshot.lastSyncedAt.slice(0, 10)}`, {
|
|
11551
11569
|
size: 11,
|
|
11552
|
-
fill:
|
|
11570
|
+
fill: palette.muted,
|
|
11553
11571
|
anchor: "end"
|
|
11554
11572
|
})
|
|
11555
11573
|
);
|
|
11556
11574
|
y += 22;
|
|
11557
11575
|
const height = y + PAD - 12;
|
|
11558
|
-
|
|
11576
|
+
const background = rect(0, 0, WIDTH, height, palette.background);
|
|
11577
|
+
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>${background}${parts.join("")}</svg>`;
|
|
11559
11578
|
}
|
|
11560
11579
|
|
|
11561
11580
|
// src/cv.ts
|
|
@@ -11587,6 +11606,161 @@ function deriveCv(localized) {
|
|
|
11587
11606
|
};
|
|
11588
11607
|
}
|
|
11589
11608
|
|
|
11609
|
+
// src/mcp.ts
|
|
11610
|
+
var MCP_PROFILE_SECTIONS = [
|
|
11611
|
+
"profile",
|
|
11612
|
+
"links",
|
|
11613
|
+
"careers",
|
|
11614
|
+
"projects",
|
|
11615
|
+
"skills",
|
|
11616
|
+
"certifications",
|
|
11617
|
+
"memberships",
|
|
11618
|
+
"volunteering",
|
|
11619
|
+
"honors",
|
|
11620
|
+
"education",
|
|
11621
|
+
"publications",
|
|
11622
|
+
"languages",
|
|
11623
|
+
"courses",
|
|
11624
|
+
"patents",
|
|
11625
|
+
"testScores",
|
|
11626
|
+
"recommendations",
|
|
11627
|
+
"contact"
|
|
11628
|
+
];
|
|
11629
|
+
var McpRequestError = class extends Error {
|
|
11630
|
+
constructor(message, options) {
|
|
11631
|
+
super(message, options);
|
|
11632
|
+
this.name = "McpRequestError";
|
|
11633
|
+
}
|
|
11634
|
+
};
|
|
11635
|
+
var LANG_PROPERTY = {
|
|
11636
|
+
type: "string",
|
|
11637
|
+
description: 'BCP-47 locale tag (e.g. "ja", "en"). Resolves to the requested locale, falling back to the profile default when omitted or unavailable.'
|
|
11638
|
+
};
|
|
11639
|
+
var MCP_TOOLS = [
|
|
11640
|
+
{
|
|
11641
|
+
name: "get_profile",
|
|
11642
|
+
title: "Get profile",
|
|
11643
|
+
description: "Return the owner's full public profile, resolved to one locale and privacy-filtered (identical to the public `GET /api/profile`).",
|
|
11644
|
+
inputSchema: {
|
|
11645
|
+
type: "object",
|
|
11646
|
+
properties: { lang: LANG_PROPERTY },
|
|
11647
|
+
additionalProperties: false
|
|
11648
|
+
}
|
|
11649
|
+
},
|
|
11650
|
+
{
|
|
11651
|
+
name: "get_section",
|
|
11652
|
+
title: "Get profile section",
|
|
11653
|
+
description: "Return a single section of the public profile (e.g. careers, education, skills), resolved to one locale and privacy-filtered.",
|
|
11654
|
+
inputSchema: {
|
|
11655
|
+
type: "object",
|
|
11656
|
+
properties: {
|
|
11657
|
+
section: {
|
|
11658
|
+
type: "string",
|
|
11659
|
+
enum: [...MCP_PROFILE_SECTIONS],
|
|
11660
|
+
description: "Which profile section to return."
|
|
11661
|
+
},
|
|
11662
|
+
lang: LANG_PROPERTY
|
|
11663
|
+
},
|
|
11664
|
+
required: ["section"],
|
|
11665
|
+
additionalProperties: false
|
|
11666
|
+
}
|
|
11667
|
+
},
|
|
11668
|
+
{
|
|
11669
|
+
name: "get_jsonld",
|
|
11670
|
+
title: "Get JSON-LD",
|
|
11671
|
+
description: "Return the Schema.org JSON-LD (a `ProfilePage` wrapping a `Person`) for the profile, resolved to one locale (identical to `GET /api/jsonld`).",
|
|
11672
|
+
inputSchema: {
|
|
11673
|
+
type: "object",
|
|
11674
|
+
properties: { lang: LANG_PROPERTY },
|
|
11675
|
+
additionalProperties: false
|
|
11676
|
+
}
|
|
11677
|
+
},
|
|
11678
|
+
{
|
|
11679
|
+
name: "list_locales",
|
|
11680
|
+
title: "List locales",
|
|
11681
|
+
description: "List the locales this profile is available in, plus the default \u2014 useful before calling another tool with a `lang` argument.",
|
|
11682
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false }
|
|
11683
|
+
}
|
|
11684
|
+
];
|
|
11685
|
+
var MCP_RESOURCES = [
|
|
11686
|
+
{
|
|
11687
|
+
uri: "takuhon://profile",
|
|
11688
|
+
name: "profile",
|
|
11689
|
+
title: "Canonical profile (takuhon.json)",
|
|
11690
|
+
description: "The full canonical takuhon.json, privacy-filtered, with every locale retained (identical to `GET /takuhon.json`).",
|
|
11691
|
+
mimeType: "application/json"
|
|
11692
|
+
},
|
|
11693
|
+
{
|
|
11694
|
+
uri: "takuhon://schema",
|
|
11695
|
+
name: "schema",
|
|
11696
|
+
title: "Takuhon JSON Schema",
|
|
11697
|
+
description: "The public JSON Schema contract the profile conforms to (identical to `GET /api/schema`). Lets an agent understand the document shape.",
|
|
11698
|
+
mimeType: "application/json"
|
|
11699
|
+
}
|
|
11700
|
+
];
|
|
11701
|
+
function executeMcpTool(name, args, profile) {
|
|
11702
|
+
switch (name) {
|
|
11703
|
+
case "get_profile": {
|
|
11704
|
+
const localized = publicView(profile, readLang(args));
|
|
11705
|
+
return { data: localized, resolvedLocale: localized.resolvedLocale };
|
|
11706
|
+
}
|
|
11707
|
+
case "get_section": {
|
|
11708
|
+
const section = readSection(args);
|
|
11709
|
+
const localized = publicView(profile, readLang(args));
|
|
11710
|
+
return { data: localized[section], resolvedLocale: localized.resolvedLocale };
|
|
11711
|
+
}
|
|
11712
|
+
case "get_jsonld": {
|
|
11713
|
+
const localized = publicView(profile, readLang(args));
|
|
11714
|
+
return { data: generateJsonLd(localized), resolvedLocale: localized.resolvedLocale };
|
|
11715
|
+
}
|
|
11716
|
+
case "list_locales":
|
|
11717
|
+
return {
|
|
11718
|
+
data: {
|
|
11719
|
+
defaultLocale: profile.settings.defaultLocale,
|
|
11720
|
+
availableLocales: profile.settings.availableLocales,
|
|
11721
|
+
...profile.settings.fallbackLocale !== void 0 ? { fallbackLocale: profile.settings.fallbackLocale } : {}
|
|
11722
|
+
}
|
|
11723
|
+
};
|
|
11724
|
+
default:
|
|
11725
|
+
throw new McpRequestError(`Unknown MCP tool: ${name}`);
|
|
11726
|
+
}
|
|
11727
|
+
}
|
|
11728
|
+
function readMcpResource(uri, profile) {
|
|
11729
|
+
switch (uri) {
|
|
11730
|
+
case "takuhon://profile":
|
|
11731
|
+
return {
|
|
11732
|
+
uri,
|
|
11733
|
+
mimeType: "application/json",
|
|
11734
|
+
data: applyPublicPrivacyFilter(profile)
|
|
11735
|
+
};
|
|
11736
|
+
case "takuhon://schema":
|
|
11737
|
+
return { uri, mimeType: "application/json", data: schema };
|
|
11738
|
+
default:
|
|
11739
|
+
throw new McpRequestError(`Unknown MCP resource: ${uri}`);
|
|
11740
|
+
}
|
|
11741
|
+
}
|
|
11742
|
+
function publicView(profile, lang) {
|
|
11743
|
+
return applyPublicPrivacyFilter(resolveLocale(normalize(profile), lang));
|
|
11744
|
+
}
|
|
11745
|
+
function readLang(args) {
|
|
11746
|
+
const lang = args.lang;
|
|
11747
|
+
if (lang === void 0) return void 0;
|
|
11748
|
+
if (typeof lang !== "string") {
|
|
11749
|
+
throw new McpRequestError("`lang` must be a string (a BCP-47 locale tag).");
|
|
11750
|
+
}
|
|
11751
|
+
return lang;
|
|
11752
|
+
}
|
|
11753
|
+
function readSection(args) {
|
|
11754
|
+
const section = args.section;
|
|
11755
|
+
if (typeof section !== "string" || !isProfileSection(section)) {
|
|
11756
|
+
throw new McpRequestError(`\`section\` must be one of: ${MCP_PROFILE_SECTIONS.join(", ")}.`);
|
|
11757
|
+
}
|
|
11758
|
+
return section;
|
|
11759
|
+
}
|
|
11760
|
+
function isProfileSection(value) {
|
|
11761
|
+
return MCP_PROFILE_SECTIONS.includes(value);
|
|
11762
|
+
}
|
|
11763
|
+
|
|
11590
11764
|
// src/image.ts
|
|
11591
11765
|
var ACCEPTED_IMAGE_MIME_TYPES = [
|
|
11592
11766
|
"image/jpeg",
|
|
@@ -11922,11 +12096,17 @@ var SCHEMA_VERSION = "0.5.0";
|
|
|
11922
12096
|
export {
|
|
11923
12097
|
ACCEPTED_IMAGE_MIME_TYPES,
|
|
11924
12098
|
ConflictError,
|
|
12099
|
+
DARK_PALETTE,
|
|
11925
12100
|
IMAGE_EXTENSIONS,
|
|
11926
12101
|
ImportError,
|
|
12102
|
+
LIGHT_PALETTE,
|
|
11927
12103
|
MAX_IMAGE_BYTES,
|
|
11928
12104
|
MAX_IMAGE_DIMENSION,
|
|
11929
12105
|
MAX_IMAGE_FRAMES,
|
|
12106
|
+
MCP_PROFILE_SECTIONS,
|
|
12107
|
+
MCP_RESOURCES,
|
|
12108
|
+
MCP_TOOLS,
|
|
12109
|
+
McpRequestError,
|
|
11930
12110
|
MigrationError,
|
|
11931
12111
|
NotFoundError,
|
|
11932
12112
|
RANK_FULL_CODING_HOURS,
|
|
@@ -11940,6 +12120,7 @@ export {
|
|
|
11940
12120
|
deriveCv,
|
|
11941
12121
|
deriveRankTier,
|
|
11942
12122
|
detectImageMime,
|
|
12123
|
+
executeMcpTool,
|
|
11943
12124
|
exportTakuhon,
|
|
11944
12125
|
formatCodingTime,
|
|
11945
12126
|
generateJsonLd,
|
|
@@ -11951,6 +12132,7 @@ export {
|
|
|
11951
12132
|
migrations,
|
|
11952
12133
|
normalize,
|
|
11953
12134
|
readImageInfo,
|
|
12135
|
+
readMcpResource,
|
|
11954
12136
|
renderActivitySvg,
|
|
11955
12137
|
resolveLocale,
|
|
11956
12138
|
schema,
|