@takuhon/cli 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.js CHANGED
@@ -2,10 +2,10 @@
2
2
  import {
3
3
  ADMIN_DIST_DIRNAME,
4
4
  resolveAdminBundleDir
5
- } from "./chunk-TNTKQL7R.js";
5
+ } from "./chunk-DKOAVXLZ.js";
6
6
 
7
7
  // src/index.ts
8
- import { readFileSync as readFileSync15, realpathSync } from "fs";
8
+ import { readFileSync as readFileSync16, realpathSync } from "fs";
9
9
  import { stdin, stdout } from "process";
10
10
  import { createInterface } from "readline/promises";
11
11
  import { fileURLToPath } from "url";
@@ -1693,7 +1693,13 @@ Run \`takuhon admin --help\` for usage.
1693
1693
  // src/build-command.ts
1694
1694
  import { mkdirSync as mkdirSync3, readFileSync as readFileSync7 } from "fs";
1695
1695
  import { dirname as dirname5, join as join6 } from "path";
1696
- import { applyPublicPrivacyFilter as applyPublicPrivacyFilter2, normalize as normalize2, validate as validate2 } from "@takuhon/core";
1696
+ import {
1697
+ DARK_PALETTE,
1698
+ applyPublicPrivacyFilter as applyPublicPrivacyFilter2,
1699
+ normalize as normalize2,
1700
+ renderActivitySvg as renderActivitySvg2,
1701
+ validate as validate2
1702
+ } from "@takuhon/core";
1697
1703
  var DEFAULT_PATH4 = "takuhon.json";
1698
1704
  var DEFAULT_OUTPUT = "dist";
1699
1705
  var USAGE3 = `Usage: takuhon build [path] [--output <dir>] [--base-url <url>] [--cv]
@@ -1715,6 +1721,10 @@ The public privacy filter is applied (meta.privacy is honoured). Asset URLs are
1715
1721
  referenced as-is and are not copied. The output directory is written into, not
1716
1722
  cleaned \u2014 use a dedicated/empty directory so stale pages do not linger.
1717
1723
 
1724
+ When settings.activity.enabled is true and an activity.json sits beside the
1725
+ profile, the activity card is also written as activity.svg and
1726
+ activity-dark.svg for embedding as a GitHub README badge.
1727
+
1718
1728
  Exit codes: 0 = built, 1 = source is not a valid profile,
1719
1729
  2 = bad arguments / file missing / unreadable / not JSON / write failed.
1720
1730
  `;
@@ -1829,6 +1839,7 @@ ${lines.join("\n")}
1829
1839
  const filtered = applyPublicPrivacyFilter2(normalize2(result.data));
1830
1840
  const activitySnapshot = filtered.settings.activity?.enabled === true ? readActivitySnapshotSync(path) : null;
1831
1841
  const written = [];
1842
+ const assets = [];
1832
1843
  try {
1833
1844
  for (const page of generateSite(filtered, { baseUrl, activitySnapshot, cv })) {
1834
1845
  const outFile = join6(output, page.file);
@@ -1836,15 +1847,29 @@ ${lines.join("\n")}
1836
1847
  writeFileAtomic(outFile, page.html);
1837
1848
  written.push(outFile);
1838
1849
  }
1850
+ if (activitySnapshot) {
1851
+ const variants = [
1852
+ ["activity.svg", renderActivitySvg2(activitySnapshot)],
1853
+ ["activity-dark.svg", renderActivitySvg2(activitySnapshot, { palette: DARK_PALETTE })]
1854
+ ];
1855
+ for (const [file, svg] of variants) {
1856
+ if (svg === "") continue;
1857
+ const outFile = join6(output, file);
1858
+ mkdirSync3(dirname5(outFile), { recursive: true });
1859
+ writeFileAtomic(outFile, svg);
1860
+ assets.push(outFile);
1861
+ }
1862
+ }
1839
1863
  } catch (error) {
1840
1864
  const detail = error instanceof Error ? error.message : String(error);
1841
1865
  return { code: 2, stdout: "", stderr: `takuhon: failed to write the site: ${detail}
1842
1866
  ` };
1843
1867
  }
1844
- const summary = written.map((w) => ` ${w}`).join("\n");
1868
+ const summary = [...written, ...assets].map((w) => ` ${w}`).join("\n");
1869
+ const assetNote = assets.length > 0 ? ` and ${assets.length} asset${assets.length === 1 ? "" : "s"}` : "";
1845
1870
  return {
1846
1871
  code: 0,
1847
- stdout: `built ${written.length} page${written.length === 1 ? "" : "s"} from ${path}:
1872
+ stdout: `built ${written.length} page${written.length === 1 ? "" : "s"}${assetNote} from ${path}:
1848
1873
  ${summary}
1849
1874
  `,
1850
1875
  stderr: ""
@@ -2147,18 +2172,144 @@ function isNotFound5(error) {
2147
2172
  return typeof error === "object" && error !== null && error.code === "ENOENT";
2148
2173
  }
2149
2174
 
2150
- // src/migrate-command.ts
2175
+ // src/mcp-command.ts
2151
2176
  import { readFileSync as readFileSync10 } from "fs";
2177
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2178
+ import { validate as validate4 } from "@takuhon/core";
2179
+ import { createTakuhonMcpServer } from "@takuhon/mcp";
2180
+ var DEFAULT_PATH7 = "takuhon.json";
2181
+ var SERVER_NAME = "takuhon";
2182
+ var USAGE6 = `Usage: takuhon mcp [path]
2183
+
2184
+ Serve a takuhon.json over the Model Context Protocol on stdio, so an MCP client
2185
+ (e.g. Claude Desktop) can read the profile read-only. With no path, serves
2186
+ ./takuhon.json. The file is re-read on every request, so edits are reflected
2187
+ without restarting.
2188
+
2189
+ Exposes (read-only, the same surface as the public API, privacy filter applied):
2190
+ tools: get_profile, get_section, get_jsonld, list_locales
2191
+ resources: takuhon://profile, takuhon://schema
2192
+
2193
+ stdin/stdout carry the protocol stream; diagnostics go to stderr. The server
2194
+ runs until the client disconnects or you press Ctrl-C.
2195
+
2196
+ Example Claude Desktop config (claude_desktop_config.json):
2197
+ {
2198
+ "mcpServers": {
2199
+ "my-profile": { "command": "takuhon", "args": ["mcp", "/path/to/takuhon.json"] }
2200
+ }
2201
+ }
2202
+
2203
+ Exit codes: 0 = served then stopped, 2 = bad arguments / file missing /
2204
+ unreadable / invalid.
2205
+ `;
2206
+ var VERSION = readCliVersion();
2207
+ function loadMcpProfile(path) {
2208
+ let raw;
2209
+ try {
2210
+ raw = readFileSync10(path, "utf8");
2211
+ } catch {
2212
+ throw new Error(`cannot read '${path}'.`);
2213
+ }
2214
+ let data;
2215
+ try {
2216
+ data = JSON.parse(raw);
2217
+ } catch (error) {
2218
+ const detail = error instanceof Error ? error.message : String(error);
2219
+ throw new Error(`'${path}' is not valid JSON: ${detail}`, { cause: error });
2220
+ }
2221
+ const result = validate4(data);
2222
+ if (!result.ok) {
2223
+ const lines = result.errors.map((e) => ` ${e.pointer || "/"}: ${e.message}`);
2224
+ throw new Error(`'${path}' is not a valid takuhon profile:
2225
+ ${lines.join("\n")}`);
2226
+ }
2227
+ return result.data;
2228
+ }
2229
+ async function runMcp(args = [], deps = {}) {
2230
+ const err = deps.stderr ?? ((text) => void process.stderr.write(text));
2231
+ if (args[0] === "--help" || args[0] === "-h") {
2232
+ const out = deps.stdout ?? ((text) => void process.stdout.write(text));
2233
+ out(USAGE6);
2234
+ return 0;
2235
+ }
2236
+ const parsed = parseArgs6(args);
2237
+ if ("error" in parsed) {
2238
+ err(`${parsed.error}
2239
+ Run \`takuhon mcp --help\` for usage.
2240
+ `);
2241
+ return 2;
2242
+ }
2243
+ try {
2244
+ loadMcpProfile(parsed.path);
2245
+ } catch (error) {
2246
+ err(`takuhon: ${error instanceof Error ? error.message : String(error)}
2247
+ `);
2248
+ return 2;
2249
+ }
2250
+ const server = createTakuhonMcpServer({
2251
+ // Re-read per request so edits are reflected; if the file later becomes
2252
+ // invalid, the load failure surfaces to the client as a tool error result.
2253
+ loadProfile: () => loadMcpProfile(parsed.path),
2254
+ name: SERVER_NAME,
2255
+ version: VERSION
2256
+ });
2257
+ const transport = (deps.createTransport ?? (() => new StdioServerTransport()))();
2258
+ await server.connect(transport);
2259
+ err(`takuhon mcp: serving ${parsed.path} (read-only) on stdio
2260
+ `);
2261
+ return await new Promise((resolve7) => {
2262
+ let done = false;
2263
+ function finish() {
2264
+ if (done) return;
2265
+ done = true;
2266
+ process.removeListener("SIGINT", shutdown);
2267
+ process.removeListener("SIGTERM", shutdown);
2268
+ resolve7(0);
2269
+ }
2270
+ function shutdown() {
2271
+ void server.close();
2272
+ finish();
2273
+ }
2274
+ server.server.onclose = finish;
2275
+ process.once("SIGINT", shutdown);
2276
+ process.once("SIGTERM", shutdown);
2277
+ });
2278
+ }
2279
+ function parseArgs6(args) {
2280
+ let path;
2281
+ for (const arg of args) {
2282
+ if (arg.startsWith("-")) {
2283
+ return { error: `takuhon: unknown option \`${arg}\` for \`mcp\`.` };
2284
+ }
2285
+ if (path !== void 0) {
2286
+ return { error: "takuhon: `mcp` takes at most one path argument." };
2287
+ }
2288
+ path = arg;
2289
+ }
2290
+ return { path: path ?? DEFAULT_PATH7 };
2291
+ }
2292
+ function readCliVersion() {
2293
+ try {
2294
+ const pkg2 = JSON.parse(readFileSync10(new URL("../package.json", import.meta.url), "utf8"));
2295
+ return pkg2.version;
2296
+ } catch {
2297
+ return "0.0.0";
2298
+ }
2299
+ }
2300
+
2301
+ // src/migrate-command.ts
2302
+ import { readFileSync as readFileSync11 } from "fs";
2152
2303
  import { resolve as resolve5 } from "path";
2153
2304
  import {
2154
2305
  MigrationError as MigrationError2,
2155
2306
  SCHEMA_VERSION as SCHEMA_VERSION2,
2156
2307
  SUPPORTED_SCHEMA_VERSIONS,
2157
2308
  migrateTakuhon as migrateTakuhon2,
2158
- validate as validate4
2309
+ validate as validate5
2159
2310
  } from "@takuhon/core";
2160
- var DEFAULT_PATH7 = "takuhon.json";
2161
- var USAGE6 = `Usage: takuhon migrate [path] [--to <version>] [--out <file>] [--dry-run]
2311
+ var DEFAULT_PATH8 = "takuhon.json";
2312
+ var USAGE7 = `Usage: takuhon migrate [path] [--to <version>] [--out <file>] [--dry-run]
2162
2313
 
2163
2314
  Forward-migrate a takuhon.json to a newer schema version. The source version
2164
2315
  is read from the file's own schemaVersion. With no path, migrates
@@ -2180,9 +2331,9 @@ Exit codes: 0 = migrated / already current / dry-run, 1 = cannot migrate,
2180
2331
  `;
2181
2332
  function runMigrate(args = [], deps = {}) {
2182
2333
  if (args[0] === "--help" || args[0] === "-h") {
2183
- return { code: 0, stdout: USAGE6, stderr: "" };
2334
+ return { code: 0, stdout: USAGE7, stderr: "" };
2184
2335
  }
2185
- const parsed = parseArgs6(args);
2336
+ const parsed = parseArgs7(args);
2186
2337
  if ("error" in parsed) {
2187
2338
  return {
2188
2339
  code: 2,
@@ -2195,7 +2346,7 @@ Run \`takuhon migrate --help\` for usage.
2195
2346
  const now = deps.now ?? (() => /* @__PURE__ */ new Date());
2196
2347
  return migrateFile(parsed, now);
2197
2348
  }
2198
- function parseArgs6(args) {
2349
+ function parseArgs7(args) {
2199
2350
  let path;
2200
2351
  let to;
2201
2352
  let out;
@@ -2238,13 +2389,13 @@ function parseArgs6(args) {
2238
2389
  error: `takuhon: unsupported --to version "${target}". Supported: ${SUPPORTED_SCHEMA_VERSIONS.join(", ")}.`
2239
2390
  };
2240
2391
  }
2241
- return { path: path ?? DEFAULT_PATH7, to: target, out, dryRun };
2392
+ return { path: path ?? DEFAULT_PATH8, to: target, out, dryRun };
2242
2393
  }
2243
2394
  function migrateFile(parsed, now) {
2244
2395
  const { path, to: target, out, dryRun } = parsed;
2245
2396
  let raw;
2246
2397
  try {
2247
- raw = readFileSync10(path, "utf8");
2398
+ raw = readFileSync11(path, "utf8");
2248
2399
  } catch {
2249
2400
  return {
2250
2401
  code: 2,
@@ -2295,7 +2446,7 @@ Migrations are forward-only; to move to an older schema, restore from a backup w
2295
2446
  }
2296
2447
  throw error;
2297
2448
  }
2298
- const revalidated = validate4(migrated);
2449
+ const revalidated = validate5(migrated);
2299
2450
  if (!revalidated.ok) {
2300
2451
  const lines2 = revalidated.errors.map((e) => ` ${e.pointer || "/"}: ${e.message}`);
2301
2452
  return {
@@ -2358,11 +2509,11 @@ ${lines2.join("\n")}
2358
2509
  }
2359
2510
 
2360
2511
  // src/refresh-admin-command.ts
2361
- import { readFileSync as readFileSync11 } from "fs";
2512
+ import { readFileSync as readFileSync12 } from "fs";
2362
2513
  import { cp, mkdtemp, readdir, rename, rm, stat } from "fs/promises";
2363
2514
  import { join as join7, resolve as resolve6 } from "path";
2364
2515
  var PROFILE_FILENAME = "takuhon.json";
2365
- var USAGE7 = `Usage: takuhon admin update [path]
2516
+ var USAGE8 = `Usage: takuhon admin update [path]
2366
2517
 
2367
2518
  Refresh a project's ${ADMIN_DIST_DIRNAME}/ with the admin form-UI bundle shipped in
2368
2519
  this @takuhon/cli. Use it after upgrading @takuhon/cli to pick up a newer admin
@@ -2375,7 +2526,7 @@ no ${ADMIN_DIST_DIRNAME}/ to refresh / copy failed.
2375
2526
  `;
2376
2527
  async function runRefreshAdmin(args = [], opts = {}) {
2377
2528
  if (args[0] === "--help" || args[0] === "-h") {
2378
- return { code: 0, stdout: USAGE7, stderr: "" };
2529
+ return { code: 0, stdout: USAGE8, stderr: "" };
2379
2530
  }
2380
2531
  const unknownFlag = args.find((arg) => arg.startsWith("-"));
2381
2532
  if (unknownFlag !== void 0) {
@@ -2458,7 +2609,7 @@ Run \`takuhon admin update --help\` for usage.
2458
2609
  };
2459
2610
  }
2460
2611
  function readVersion() {
2461
- const pkg2 = JSON.parse(readFileSync11(new URL("../package.json", import.meta.url), "utf8"));
2612
+ const pkg2 = JSON.parse(readFileSync12(new URL("../package.json", import.meta.url), "utf8"));
2462
2613
  return pkg2.version;
2463
2614
  }
2464
2615
  function isNodeErrnoException(err) {
@@ -2466,10 +2617,10 @@ function isNodeErrnoException(err) {
2466
2617
  }
2467
2618
 
2468
2619
  // src/restore-command.ts
2469
- import { readFileSync as readFileSync12 } from "fs";
2470
- import { validate as validate5 } from "@takuhon/core";
2471
- var DEFAULT_PATH8 = "takuhon.json";
2472
- var USAGE8 = `Usage: takuhon restore --from <backup> [path] [--yes]
2620
+ import { readFileSync as readFileSync13 } from "fs";
2621
+ import { validate as validate6 } from "@takuhon/core";
2622
+ var DEFAULT_PATH9 = "takuhon.json";
2623
+ var USAGE9 = `Usage: takuhon restore --from <backup> [path] [--yes]
2473
2624
 
2474
2625
  Overwrite a profile with a previously saved backup. With no path, restores
2475
2626
  ./takuhon.json in the current working directory.
@@ -2486,9 +2637,9 @@ Exit codes: 0 = restored / aborted, 1 = backup failed validation,
2486
2637
  `;
2487
2638
  async function runRestore(args = [], deps = {}) {
2488
2639
  if (args[0] === "--help" || args[0] === "-h") {
2489
- return { code: 0, stdout: USAGE8, stderr: "" };
2640
+ return { code: 0, stdout: USAGE9, stderr: "" };
2490
2641
  }
2491
- const parsed = parseArgs7(args);
2642
+ const parsed = parseArgs8(args);
2492
2643
  if ("error" in parsed) {
2493
2644
  return {
2494
2645
  code: 2,
@@ -2501,7 +2652,7 @@ Run \`takuhon restore --help\` for usage.
2501
2652
  const now = deps.now ?? (() => /* @__PURE__ */ new Date());
2502
2653
  return restoreFile(parsed, now, deps.confirm);
2503
2654
  }
2504
- function parseArgs7(args) {
2655
+ function parseArgs8(args) {
2505
2656
  let from;
2506
2657
  let path;
2507
2658
  let yes = false;
@@ -2535,13 +2686,13 @@ function parseArgs7(args) {
2535
2686
  if (from === void 0 || from.length === 0) {
2536
2687
  return { error: "takuhon: `restore` requires `--from <backup>`." };
2537
2688
  }
2538
- return { from, path: path ?? DEFAULT_PATH8, yes };
2689
+ return { from, path: path ?? DEFAULT_PATH9, yes };
2539
2690
  }
2540
2691
  async function restoreFile(parsed, now, confirm) {
2541
2692
  const { from, path, yes } = parsed;
2542
2693
  let backupRaw;
2543
2694
  try {
2544
- backupRaw = readFileSync12(from, "utf8");
2695
+ backupRaw = readFileSync13(from, "utf8");
2545
2696
  } catch {
2546
2697
  return { code: 2, stdout: "", stderr: `takuhon: cannot read backup '${from}'.
2547
2698
  ` };
@@ -2558,7 +2709,7 @@ async function restoreFile(parsed, now, confirm) {
2558
2709
  `
2559
2710
  };
2560
2711
  }
2561
- const result = validate5(backupData);
2712
+ const result = validate6(backupData);
2562
2713
  if (!result.ok) {
2563
2714
  const lines2 = result.errors.map((e) => ` ${e.pointer || "/"}: ${e.message}`);
2564
2715
  return {
@@ -2571,7 +2722,7 @@ ${lines2.join("\n")}
2571
2722
  }
2572
2723
  let currentRaw;
2573
2724
  try {
2574
- currentRaw = readFileSync12(path, "utf8");
2725
+ currentRaw = readFileSync13(path, "utf8");
2575
2726
  } catch (error) {
2576
2727
  if (!isNotFound6(error)) {
2577
2728
  return { code: 2, stdout: "", stderr: `takuhon: cannot read current profile '${path}'.
@@ -2641,12 +2792,12 @@ function isNotFound6(error) {
2641
2792
  }
2642
2793
 
2643
2794
  // src/sync-command.ts
2644
- import { readFileSync as readFileSync13 } from "fs";
2645
- import { validate as validate6 } from "@takuhon/core";
2646
- var DEFAULT_PATH9 = "takuhon.json";
2795
+ import { readFileSync as readFileSync14 } from "fs";
2796
+ import { validate as validate7 } from "@takuhon/core";
2797
+ var DEFAULT_PATH10 = "takuhon.json";
2647
2798
  var ADMIN_PROFILE_PATH = "/api/admin/profile";
2648
2799
  var TOKEN_ENV = "TAKUHON_ADMIN_TOKEN";
2649
- var USAGE9 = `Usage: takuhon sync [path] --url <base-url> [--if-match <etag>] [--dry-run]
2800
+ var USAGE10 = `Usage: takuhon sync [path] --url <base-url> [--if-match <etag>] [--dry-run]
2650
2801
 
2651
2802
  Push a local takuhon.json to a deployed takuhon instance by calling its admin
2652
2803
  write endpoint (PUT <base-url>/api/admin/profile). With no path, syncs
@@ -2672,9 +2823,9 @@ unset / auth failure / network error / other non-success response.
2672
2823
  `;
2673
2824
  async function runSync(args = [], deps = {}) {
2674
2825
  if (args[0] === "--help" || args[0] === "-h") {
2675
- return { code: 0, stdout: USAGE9, stderr: "" };
2826
+ return { code: 0, stdout: USAGE10, stderr: "" };
2676
2827
  }
2677
- const parsed = parseArgs8(args);
2828
+ const parsed = parseArgs9(args);
2678
2829
  if ("error" in parsed) {
2679
2830
  return {
2680
2831
  code: 2,
@@ -2686,7 +2837,7 @@ Run \`takuhon sync --help\` for usage.
2686
2837
  }
2687
2838
  return syncProfile(parsed, deps);
2688
2839
  }
2689
- function parseArgs8(args) {
2840
+ function parseArgs9(args) {
2690
2841
  let path;
2691
2842
  let url;
2692
2843
  let ifMatch;
@@ -2732,7 +2883,7 @@ function parseArgs8(args) {
2732
2883
  }
2733
2884
  const base = parseOrigin(url);
2734
2885
  if ("error" in base) return base;
2735
- return { path: path ?? DEFAULT_PATH9, url: base.origin, ifMatch, dryRun };
2886
+ return { path: path ?? DEFAULT_PATH10, url: base.origin, ifMatch, dryRun };
2736
2887
  }
2737
2888
  function parseOrigin(value) {
2738
2889
  let parsed;
@@ -2765,7 +2916,7 @@ async function syncProfile(parsed, deps) {
2765
2916
  const { path, url, ifMatch, dryRun } = parsed;
2766
2917
  let raw;
2767
2918
  try {
2768
- raw = readFileSync13(path, "utf8");
2919
+ raw = readFileSync14(path, "utf8");
2769
2920
  } catch {
2770
2921
  return { code: 2, stdout: "", stderr: `takuhon: cannot read '${path}'.
2771
2922
  ` };
@@ -2778,7 +2929,7 @@ async function syncProfile(parsed, deps) {
2778
2929
  return { code: 2, stdout: "", stderr: `takuhon: '${path}' is not valid JSON: ${detail}
2779
2930
  ` };
2780
2931
  }
2781
- const result = validate6(data);
2932
+ const result = validate7(data);
2782
2933
  if (!result.ok) {
2783
2934
  const lines = result.errors.map((e) => ` ${e.pointer || "/"}: ${e.message}`);
2784
2935
  return {
@@ -2902,10 +3053,10 @@ async function readProblem(res) {
2902
3053
  }
2903
3054
 
2904
3055
  // src/validate-command.ts
2905
- import { readFileSync as readFileSync14 } from "fs";
2906
- import { validate as validate7 } from "@takuhon/core";
2907
- var DEFAULT_PATH10 = "takuhon.json";
2908
- var USAGE10 = `Usage: takuhon validate [path]
3056
+ import { readFileSync as readFileSync15 } from "fs";
3057
+ import { validate as validate8 } from "@takuhon/core";
3058
+ var DEFAULT_PATH11 = "takuhon.json";
3059
+ var USAGE11 = `Usage: takuhon validate [path]
2909
3060
 
2910
3061
  Validate a takuhon.json against the takuhon schema. With no path, validates
2911
3062
  ./takuhon.json in the current working directory.
@@ -2914,7 +3065,7 @@ Exit codes: 0 = valid, 1 = invalid, 2 = file missing / unreadable / not JSON.
2914
3065
  `;
2915
3066
  function runValidate(args = []) {
2916
3067
  if (args[0] === "--help" || args[0] === "-h") {
2917
- return { code: 0, stdout: USAGE10, stderr: "" };
3068
+ return { code: 0, stdout: USAGE11, stderr: "" };
2918
3069
  }
2919
3070
  if (args.length > 1) {
2920
3071
  return {
@@ -2926,10 +3077,10 @@ function runValidate(args = []) {
2926
3077
  return validateFile(args[0]);
2927
3078
  }
2928
3079
  function validateFile(pathArg) {
2929
- const target = pathArg ?? DEFAULT_PATH10;
3080
+ const target = pathArg ?? DEFAULT_PATH11;
2930
3081
  let raw;
2931
3082
  try {
2932
- raw = readFileSync14(target, "utf8");
3083
+ raw = readFileSync15(target, "utf8");
2933
3084
  } catch {
2934
3085
  return {
2935
3086
  code: 2,
@@ -2950,7 +3101,7 @@ function validateFile(pathArg) {
2950
3101
  `
2951
3102
  };
2952
3103
  }
2953
- const result = validate7(data);
3104
+ const result = validate8(data);
2954
3105
  if (result.ok) {
2955
3106
  return {
2956
3107
  code: 0,
@@ -2971,9 +3122,9 @@ ${lines.join("\n")}
2971
3122
  }
2972
3123
 
2973
3124
  // src/index.ts
2974
- var pkg = JSON.parse(readFileSync15(new URL("../package.json", import.meta.url), "utf8"));
2975
- var VERSION = pkg.version;
2976
- var HELP = `takuhon ${VERSION}
3125
+ var pkg = JSON.parse(readFileSync16(new URL("../package.json", import.meta.url), "utf8"));
3126
+ var VERSION2 = pkg.version;
3127
+ var HELP = `takuhon ${VERSION2}
2977
3128
 
2978
3129
  Takuhon \u2014 open-source portable profile API server.
2979
3130
 
@@ -3004,6 +3155,9 @@ Commands:
3004
3155
  takuhon admin update [path] Refresh a project's admin-dist/ with the admin form UI
3005
3156
  bundled in this @takuhon/cli (use after upgrading the
3006
3157
  CLI). Updates an existing admin-dist/ only.
3158
+ takuhon mcp [path] Serve a takuhon.json over the Model Context Protocol on
3159
+ stdio (read-only), so an MCP client such as Claude Desktop
3160
+ can read the profile. Re-reads the file per request.
3007
3161
  takuhon sync [path] --url <url> Push a takuhon.json to a deployment's admin API
3008
3162
  (PUT <url>/api/admin/profile). Reads the admin token
3009
3163
  from TAKUHON_ADMIN_TOKEN. --if-match <etag> opts into
@@ -3021,7 +3175,7 @@ Scaffolding a new profile project:
3021
3175
  async function main(argv) {
3022
3176
  const first = argv[0];
3023
3177
  if (first === "--version" || first === "-v") {
3024
- process.stdout.write(`${VERSION}
3178
+ process.stdout.write(`${VERSION2}
3025
3179
  `);
3026
3180
  return 0;
3027
3181
  }
@@ -3050,6 +3204,9 @@ async function main(argv) {
3050
3204
  }
3051
3205
  return runAdmin(argv.slice(1));
3052
3206
  }
3207
+ if (first === "mcp") {
3208
+ return runMcp(argv.slice(1));
3209
+ }
3053
3210
  if (first === "import") {
3054
3211
  return emit(runImport(argv.slice(1)));
3055
3212
  }