@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-
|
|
5
|
+
} from "./chunk-DKOAVXLZ.js";
|
|
6
6
|
|
|
7
7
|
// src/index.ts
|
|
8
|
-
import { readFileSync as
|
|
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 {
|
|
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/
|
|
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
|
|
2309
|
+
validate as validate5
|
|
2159
2310
|
} from "@takuhon/core";
|
|
2160
|
-
var
|
|
2161
|
-
var
|
|
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:
|
|
2334
|
+
return { code: 0, stdout: USAGE7, stderr: "" };
|
|
2184
2335
|
}
|
|
2185
|
-
const parsed =
|
|
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
|
|
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 ??
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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:
|
|
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(
|
|
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
|
|
2470
|
-
import { validate as
|
|
2471
|
-
var
|
|
2472
|
-
var
|
|
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:
|
|
2640
|
+
return { code: 0, stdout: USAGE9, stderr: "" };
|
|
2490
2641
|
}
|
|
2491
|
-
const parsed =
|
|
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
|
|
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 ??
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
2645
|
-
import { validate as
|
|
2646
|
-
var
|
|
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
|
|
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:
|
|
2826
|
+
return { code: 0, stdout: USAGE10, stderr: "" };
|
|
2676
2827
|
}
|
|
2677
|
-
const parsed =
|
|
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
|
|
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 ??
|
|
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 =
|
|
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 =
|
|
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
|
|
2906
|
-
import { validate as
|
|
2907
|
-
var
|
|
2908
|
-
var
|
|
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:
|
|
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 ??
|
|
3080
|
+
const target = pathArg ?? DEFAULT_PATH11;
|
|
2930
3081
|
let raw;
|
|
2931
3082
|
try {
|
|
2932
|
-
raw =
|
|
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 =
|
|
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(
|
|
2975
|
-
var
|
|
2976
|
-
var HELP = `takuhon ${
|
|
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(`${
|
|
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
|
}
|