@khotan/cli 0.3.0 → 0.4.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.
Files changed (3) hide show
  1. package/README.md +11 -0
  2. package/dist/khotan.js +352 -148
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -43,6 +43,17 @@ It writes an MCP config wired to `khotan mcp serve` (credentials referenced by
43
43
  MCP config merges into an existing one, preserving any other servers. Authenticate
44
44
  separately with `khotan auth set-key` or environment variables — never commit a key.
45
45
 
46
+ `init` also installs catalog-derived agent **skills** for using the CLI into
47
+ `.cursor/skills/`, `.agents/skills/`, and `.claude/skills/` (regardless of
48
+ `--client`): an overview `khotan` skill plus one per command domain
49
+ (`khotan-apps`, `khotan-databases`, …). The skills are generated from the same
50
+ capability catalog that drives the commands, MCP tools, and `khotan help`, so
51
+ they can never drift. They are **managed assets**: every `khotan init` run
52
+ regenerates them in place (no `--force` needed), so re-running `init` is how a
53
+ workspace picks up new CLI commands — like the `databases branches` group. Your
54
+ hand-authored rule/guide and any MCP edits stay non-destructive (preserved
55
+ unless `--force`).
56
+
46
57
  ## Authentication & profiles
47
58
 
48
59
  The CLI authenticates with an organization-scoped **API key**. Credentials are
package/dist/khotan.js CHANGED
@@ -19,7 +19,7 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
19
19
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
20
20
 
21
21
  // ../khotan-core/src/version.ts
22
- var CATALOG_SCHEMA_VERSION = 1, CATALOG_VERSION = "2026-06-18", KHOTAN_ADAPTER_NAME = "khotan", KHOTAN_ADAPTER_VERSION = "0.3.0", SUPPORTED_CATALOG_SCHEMA_VERSIONS;
22
+ var CATALOG_SCHEMA_VERSION = 1, CATALOG_VERSION = "2026-06-18", KHOTAN_ADAPTER_NAME = "khotan", KHOTAN_ADAPTER_VERSION = "0.4.0", SUPPORTED_CATALOG_SCHEMA_VERSIONS;
23
23
  var init_version = __esm(() => {
24
24
  SUPPORTED_CATALOG_SCHEMA_VERSIONS = [
25
25
  CATALOG_SCHEMA_VERSION
@@ -2040,155 +2040,9 @@ async function downloadFile(deps, options) {
2040
2040
  init_io();
2041
2041
  import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
2042
2042
  import { dirname as dirname2, join as join2, relative } from "node:path";
2043
- var MCP_SERVER_KEY = "khotan";
2044
- function mcpServerEntry() {
2045
- return {
2046
- command: "khotan",
2047
- args: ["mcp", "serve"],
2048
- env: {
2049
- KHOTAN_API_URL: "<your-khotan-api-url>",
2050
- KHOTAN_API_KEY: "<your-khotan-api-key>"
2051
- }
2052
- };
2053
- }
2054
- var GUIDE_BODY = `This workspace is wired to Khotan through the \`khotan\` CLI and its MCP server.
2055
- Use them to inspect and configure apps, pipelines, databases, files, folders, and
2056
- context documents through the Khotan \`/api/v1\` surface.
2057
-
2058
- ## Authentication (do this once — never commit secrets)
2059
-
2060
- Khotan reads an organization-scoped API key from a stored profile or the
2061
- environment:
2062
-
2063
- - Profile (recommended): \`khotan auth set-key --api-url <url> --api-key <key>\`
2064
- stores the key in an owner-only file outside the repository.
2065
- - Environment: export \`KHOTAN_API_URL\` and \`KHOTAN_API_KEY\` in your shell or
2066
- agent sandbox.
2067
-
2068
- Never put an API key in a file committed to the repository. The generated MCP
2069
- config contains only placeholders.
2070
-
2071
- ## CLI surface
2072
-
2073
- - \`khotan apps list|get|create|delete|redeploy\` and \`khotan apps env …\`
2074
- - \`khotan pipelines …\`, \`khotan databases …\`
2075
- - \`khotan files list|upload|download|…\`, \`khotan folders …\`
2076
- - \`khotan context list|get|create|update|delete\`
2077
- - Add \`--json\` to any command for machine-readable output.
2078
-
2079
- Run \`khotan help\` or \`khotan <command> --help\` for the full, catalog-derived
2080
- surface.
2081
-
2082
- ## Safety
2083
-
2084
- - Destructive commands (deletes, env unset) require confirmation; pass \`--yes\`
2085
- only when you intend it.
2086
- - Secret values (env reveal, database connection strings, credential rotation)
2087
- are only available through explicit commands and are never printed in lists.
2088
-
2089
- ## MCP
2090
-
2091
- \`khotan mcp serve\` exposes the same operations as MCP tools and durable reads as
2092
- MCP resources, reusing your stored profile or environment credentials.
2093
- `;
2094
- var CURSOR_RULES = `---
2095
- description: Use the Khotan CLI and MCP tools to configure and operate this workspace's Khotan resources
2096
- alwaysApply: false
2097
- ---
2098
-
2099
- # Khotan
2100
-
2101
- ${GUIDE_BODY}`;
2102
- var GENERIC_GUIDE = `# Khotan agent guide
2103
-
2104
- ${GUIDE_BODY}`;
2105
- function toRelative(cwd, absolutePath) {
2106
- return relative(cwd, absolutePath) || absolutePath;
2107
- }
2108
- function writeTextAsset(ctx, absolutePath, content) {
2109
- const path = toRelative(ctx.cwd, absolutePath);
2110
- const existed = existsSync(absolutePath);
2111
- if (existed && !ctx.force) {
2112
- ctx.results.push({ path, action: "skipped" });
2113
- return;
2114
- }
2115
- mkdirSync2(dirname2(absolutePath), { recursive: true });
2116
- writeFileSync2(absolutePath, content);
2117
- ctx.results.push({ path, action: existed ? "forced" : "created" });
2118
- }
2119
- function writeMcpConfig(ctx, absolutePath) {
2120
- const path = toRelative(ctx.cwd, absolutePath);
2121
- let config = {};
2122
- const existed = existsSync(absolutePath);
2123
- if (existed) {
2124
- try {
2125
- config = JSON.parse(readFileSync2(absolutePath, "utf8"));
2126
- } catch {
2127
- ctx.results.push({ path, action: "skipped" });
2128
- return;
2129
- }
2130
- }
2131
- if (!config.mcpServers || typeof config.mcpServers !== "object") {
2132
- config.mcpServers = {};
2133
- }
2134
- const servers = config.mcpServers;
2135
- const hasKhotan = Object.prototype.hasOwnProperty.call(servers, MCP_SERVER_KEY);
2136
- if (hasKhotan && !ctx.force) {
2137
- ctx.results.push({ path, action: "skipped" });
2138
- return;
2139
- }
2140
- servers[MCP_SERVER_KEY] = mcpServerEntry();
2141
- mkdirSync2(dirname2(absolutePath), { recursive: true });
2142
- writeFileSync2(absolutePath, `${JSON.stringify(config, null, 2)}
2143
- `);
2144
- ctx.results.push({
2145
- path,
2146
- action: existed ? hasKhotan ? "forced" : "updated" : "created"
2147
- });
2148
- }
2149
- function runInit(options) {
2150
- const { io, cwd, client, force, json } = options;
2151
- const ctx = { cwd, force, results: [] };
2152
- if (client === "cursor") {
2153
- writeMcpConfig(ctx, join2(cwd, ".cursor", "mcp.json"));
2154
- writeTextAsset(ctx, join2(cwd, ".cursor", "rules", "khotan.mdc"), CURSOR_RULES);
2155
- } else {
2156
- writeMcpConfig(ctx, join2(cwd, "mcp.json"));
2157
- writeTextAsset(ctx, join2(cwd, "khotan-agents.md"), GENERIC_GUIDE);
2158
- }
2159
- if (json) {
2160
- io.out(`${JSON.stringify({ client, actions: ctx.results }, null, 2)}
2161
- `);
2162
- return 0;
2163
- }
2164
- errLine(io, `Khotan workspace integration (${client}):`);
2165
- for (const result of ctx.results) {
2166
- errLine(io, ` ${result.action.padEnd(8)} ${result.path}`);
2167
- }
2168
- errLine(io);
2169
- errLine(io, "Next steps:");
2170
- errLine(io, " 1. Authenticate: khotan auth set-key --api-url <url> --api-key <key>");
2171
- errLine(io, " 2. Open this workspace in your agent and ask it to configure Khotan.");
2172
- return 0;
2173
- }
2174
2043
 
2175
- // src/cli/confirm.ts
2044
+ // src/cli/skills.ts
2176
2045
  init_src();
2177
- init_io();
2178
- async function ensureConfirmed(options) {
2179
- const { io, yes, summary } = options;
2180
- if (yes) {
2181
- return;
2182
- }
2183
- if (!io.isTty) {
2184
- throw new KhotanConfirmationRequiredError(`${summary} This is a destructive action; re-run with --yes to confirm in a non-interactive session.`);
2185
- }
2186
- errLine(io, summary);
2187
- const answer = (await io.prompt("Type 'yes' to confirm: ")).trim().toLowerCase();
2188
- if (answer !== "yes" && answer !== "y") {
2189
- throw new KhotanConfirmationRequiredError("Aborted; no changes were made.");
2190
- }
2191
- }
2192
2046
 
2193
2047
  // src/cli/help.ts
2194
2048
  init_src();
@@ -2397,6 +2251,356 @@ function printCommandHelp(io, capability) {
2397
2251
  }
2398
2252
  }
2399
2253
 
2254
+ // src/cli/skills.ts
2255
+ var AUTH_LOGIN_CAPABILITY_ID = "api-keys.create-from-credentials";
2256
+ var OVERVIEW_DOMAINS = new Set([
2257
+ "identity",
2258
+ "api-keys"
2259
+ ]);
2260
+ var DOMAIN_TITLES2 = {
2261
+ apps: "Apps",
2262
+ pipelines: "Pipelines",
2263
+ databases: "Databases",
2264
+ files: "Files",
2265
+ folders: "Folders",
2266
+ context: "Context documents"
2267
+ };
2268
+ var DOMAIN_LABELS = {
2269
+ apps: "apps",
2270
+ pipelines: "pipelines",
2271
+ databases: "databases and database branches",
2272
+ files: "files",
2273
+ folders: "folders",
2274
+ context: "context documents"
2275
+ };
2276
+ function domainTitle(domain) {
2277
+ return DOMAIN_TITLES2[domain] ?? domain;
2278
+ }
2279
+ function domainLabel(domain) {
2280
+ return DOMAIN_LABELS[domain] ?? domain;
2281
+ }
2282
+ function yamlQuote(value) {
2283
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
2284
+ }
2285
+ function frontmatter(name, description) {
2286
+ return ["---", `name: ${name}`, `description: ${yamlQuote(description)}`, "---"].join(`
2287
+ `);
2288
+ }
2289
+ function stamp() {
2290
+ return `<!-- generated from khotan catalog v${CATALOG_VERSION} (adapter ${KHOTAN_ADAPTER_VERSION}); do not edit by hand -->`;
2291
+ }
2292
+ function operationsForDomain(domain) {
2293
+ return listCapabilities().filter(isOperationCapability).filter((capability) => capability.domain === domain).filter((capability) => capability.id !== AUTH_LOGIN_CAPABILITY_ID).sort((a, b) => a.cli.command.join(" ").localeCompare(b.cli.command.join(" ")));
2294
+ }
2295
+ function resourcesForDomain(domain) {
2296
+ return listCapabilities().filter(isResourceCapability).filter((capability) => capability.domain === domain).sort((a, b) => a.id.localeCompare(b.id));
2297
+ }
2298
+ function fieldLine(field) {
2299
+ const label = field.location === "path" ? `<${field.name}>` : `--${fieldFlag(field)}`;
2300
+ const notes = [
2301
+ field.required ? "required" : "optional",
2302
+ field.location,
2303
+ field.secret ? "secret" : null
2304
+ ].filter(Boolean).join(", ");
2305
+ return `- \`${label}\` — ${field.description ?? ""} (${notes})`;
2306
+ }
2307
+ function renderOperation(capability) {
2308
+ const command = capability.cli.command.join(" ");
2309
+ const lines = [
2310
+ `### khotan ${command}`,
2311
+ "",
2312
+ `Usage: \`${commandUsage(capability)}\``,
2313
+ "",
2314
+ capability.description
2315
+ ];
2316
+ if (capability.input.length > 0) {
2317
+ lines.push("", "Arguments & options:");
2318
+ for (const field of capability.input) {
2319
+ lines.push(fieldLine(field));
2320
+ }
2321
+ }
2322
+ if (requiresConfirmation(capability.safety)) {
2323
+ lines.push("", "Safety: destructive — prompts for confirmation in a terminal and refuses in non-interactive sessions unless you pass `--yes`.");
2324
+ } else if (isSecret(capability.safety)) {
2325
+ lines.push("", "Safety: secret — returns credential-bearing values; never shown in list/table output and never exposed as an MCP resource.");
2326
+ }
2327
+ lines.push("", `MCP tool: \`${capability.mcp.tool.name}\``);
2328
+ if (capability.cli.examples && capability.cli.examples.length > 0) {
2329
+ lines.push("", "Examples:");
2330
+ for (const example of capability.cli.examples) {
2331
+ lines.push(`- \`${example}\``);
2332
+ }
2333
+ }
2334
+ return lines.join(`
2335
+ `);
2336
+ }
2337
+ function fileTransferSection() {
2338
+ return [
2339
+ "## Local file transfer (presigned)",
2340
+ "",
2341
+ "These commands move bytes between your machine and object storage using presigned URLs. They are CLI-only helpers (not catalog operations) that wrap the upload/download-url operations:",
2342
+ "",
2343
+ "- `khotan files upload <localPath> [--name <n>] [--folder-id <id>] [--folder-path <p>] [--content-type <t>]` — upload a local file.",
2344
+ "- `khotan files download <fileId> --output <path>` — download a file to a local path."
2345
+ ].join(`
2346
+ `);
2347
+ }
2348
+ function renderDomainSkill(domain) {
2349
+ const operations = operationsForDomain(domain);
2350
+ const resources = resourcesForDomain(domain);
2351
+ const label = domainLabel(domain);
2352
+ const commandTails = operations.map((capability) => capability.cli.command.slice(1).join(" ")).filter((tail) => tail.length > 0);
2353
+ const description = `Use the khotan CLI and MCP server to work with Khotan ${label} — commands ${commandTails.join(", ")}. Use when a user wants to inspect or operate Khotan ${label} via the khotan CLI or its MCP tools.`;
2354
+ const sections = [
2355
+ frontmatter(`khotan-${domain}`, description),
2356
+ "",
2357
+ `# Khotan ${domainTitle(domain)}`,
2358
+ "",
2359
+ `Use the \`khotan\` CLI (and its MCP server) to operate Khotan ${label}. Every command also exists as an MCP tool. Add \`--json\` for machine-readable output; results go to stdout, diagnostics to stderr.`,
2360
+ "",
2361
+ "## Commands",
2362
+ "",
2363
+ operations.map(renderOperation).join(`
2364
+
2365
+ `)
2366
+ ];
2367
+ if (domain === "files") {
2368
+ sections.push("", fileTransferSection());
2369
+ }
2370
+ if (resources.length > 0) {
2371
+ sections.push("", "## Resources", "");
2372
+ for (const resource of resources) {
2373
+ sections.push(`- \`${resource.mcp.resource.uriTemplate}\` — ${resource.mcp.resource.description}`);
2374
+ }
2375
+ }
2376
+ sections.push("", stamp(), "");
2377
+ return { name: `khotan-${domain}`, content: sections.join(`
2378
+ `) };
2379
+ }
2380
+ function overviewSkill() {
2381
+ const domains = listDomains().filter((domain) => !OVERVIEW_DOMAINS.has(domain));
2382
+ const description = "Use the khotan CLI and MCP server to operate this workspace's Khotan resources — authenticate, choose profiles, control output, and run apps, pipelines, databases, files, folders, and context commands. Use when setting up khotan auth or deciding which khotan command or domain skill to use.";
2383
+ const indexLines = domains.map((domain) => `- \`khotan-${domain}\` — ${domainLabel(domain)}.`);
2384
+ const content = [
2385
+ frontmatter("khotan", description),
2386
+ "",
2387
+ "# Khotan",
2388
+ "",
2389
+ "This workspace is wired to Khotan through the `khotan` CLI and its MCP server. Both are thin adapters over the Khotan `/api/v1` surface and expose the same operations: the CLI for humans, MCP tools for agents.",
2390
+ "",
2391
+ "## Authentication (do this once — never commit secrets)",
2392
+ "",
2393
+ "Khotan reads an organization-scoped API key from a stored profile or the environment:",
2394
+ "",
2395
+ "- Profile (recommended): `khotan auth set-key --api-url <url> --api-key <key>` stores the key in an owner-only file outside the repository.",
2396
+ "- Exchange credentials: `khotan login --api-url <url> --email <email> --password <pw> --organization-id <org>` mints and stores a key (CLI-only; never exposed as an MCP tool).",
2397
+ "- Switch profiles: `khotan auth use <profile>`; list them with `khotan auth list` (keys are never printed).",
2398
+ "- Environment overrides (CI/agents): `KHOTAN_API_URL`, `KHOTAN_API_KEY`, `KHOTAN_PROFILE` take precedence over stored profiles.",
2399
+ "- Verify with `khotan whoami`.",
2400
+ "",
2401
+ "Never put an API key in a file committed to the repository. Generated MCP config contains only placeholders.",
2402
+ "",
2403
+ "## Output modes",
2404
+ "",
2405
+ "- Human-readable tables/detail by default; add `--json` for API-shaped JSON.",
2406
+ "- Results go to stdout; prompts, progress, and diagnostics go to stderr, so `khotan ... --json | jq` stays clean.",
2407
+ "",
2408
+ "## Safety model",
2409
+ "",
2410
+ "Each capability has a safety class:",
2411
+ "",
2412
+ "- `read` — safe; freely listable.",
2413
+ "- `write` — mutating; summarized before running.",
2414
+ "- `destructive` — deletes/irreversible; prompts for confirmation and refuses non-interactively unless you pass `--yes`.",
2415
+ "- `secret` — returns credential-bearing values (env reveal, connection strings, credential rotation); explicit-only and never printed in lists or exposed as MCP resources.",
2416
+ "",
2417
+ "## MCP server",
2418
+ "",
2419
+ "`khotan mcp serve` starts a stdio MCP server reusing your stored profile or environment credentials. Operations are exposed as tools (e.g. `khotan_apps_create`); durable read data is exposed as URI-addressed resources. stdout carries JSON-RPC only; logs go to stderr.",
2420
+ "",
2421
+ "## Command skills",
2422
+ "",
2423
+ "Per-domain skills cover the full, catalog-derived command surface:",
2424
+ "",
2425
+ ...indexLines,
2426
+ "",
2427
+ "Run `khotan help` or `khotan <command> --help` for the authoritative usage.",
2428
+ "",
2429
+ stamp(),
2430
+ ""
2431
+ ].join(`
2432
+ `);
2433
+ return { name: "khotan", content };
2434
+ }
2435
+ function generateSkills() {
2436
+ const domains = listDomains().filter((domain) => !OVERVIEW_DOMAINS.has(domain));
2437
+ return [overviewSkill(), ...domains.map(renderDomainSkill)];
2438
+ }
2439
+
2440
+ // src/cli/commands/init.ts
2441
+ var MCP_SERVER_KEY = "khotan";
2442
+ var SKILL_BASES = [".cursor", ".agents", ".claude"];
2443
+ function mcpServerEntry() {
2444
+ return {
2445
+ command: "khotan",
2446
+ args: ["mcp", "serve"],
2447
+ env: {
2448
+ KHOTAN_API_URL: "<your-khotan-api-url>",
2449
+ KHOTAN_API_KEY: "<your-khotan-api-key>"
2450
+ }
2451
+ };
2452
+ }
2453
+ var GUIDE_BODY = `This workspace is wired to Khotan through the \`khotan\` CLI and its MCP server.
2454
+ Use them to inspect and configure apps, pipelines, databases, files, folders, and
2455
+ context documents through the Khotan \`/api/v1\` surface.
2456
+
2457
+ ## Authentication (do this once — never commit secrets)
2458
+
2459
+ Khotan reads an organization-scoped API key from a stored profile or the
2460
+ environment:
2461
+
2462
+ - Profile (recommended): \`khotan auth set-key --api-url <url> --api-key <key>\`
2463
+ stores the key in an owner-only file outside the repository.
2464
+ - Environment: export \`KHOTAN_API_URL\` and \`KHOTAN_API_KEY\` in your shell or
2465
+ agent sandbox.
2466
+
2467
+ Never put an API key in a file committed to the repository. The generated MCP
2468
+ config contains only placeholders.
2469
+
2470
+ ## CLI surface
2471
+
2472
+ - \`khotan apps list|get|create|delete|redeploy\` and \`khotan apps env …\`
2473
+ - \`khotan pipelines …\`, \`khotan databases …\`
2474
+ - \`khotan files list|upload|download|…\`, \`khotan folders …\`
2475
+ - \`khotan context list|get|create|update|delete\`
2476
+ - Add \`--json\` to any command for machine-readable output.
2477
+
2478
+ Run \`khotan help\` or \`khotan <command> --help\` for the full, catalog-derived
2479
+ surface.
2480
+
2481
+ ## Safety
2482
+
2483
+ - Destructive commands (deletes, env unset) require confirmation; pass \`--yes\`
2484
+ only when you intend it.
2485
+ - Secret values (env reveal, database connection strings, credential rotation)
2486
+ are only available through explicit commands and are never printed in lists.
2487
+
2488
+ ## MCP
2489
+
2490
+ \`khotan mcp serve\` exposes the same operations as MCP tools and durable reads as
2491
+ MCP resources, reusing your stored profile or environment credentials.
2492
+ `;
2493
+ var CURSOR_RULES = `---
2494
+ description: Use the Khotan CLI and MCP tools to configure and operate this workspace's Khotan resources
2495
+ alwaysApply: false
2496
+ ---
2497
+
2498
+ # Khotan
2499
+
2500
+ ${GUIDE_BODY}`;
2501
+ var GENERIC_GUIDE = `# Khotan agent guide
2502
+
2503
+ ${GUIDE_BODY}`;
2504
+ function toRelative(cwd, absolutePath) {
2505
+ return relative(cwd, absolutePath) || absolutePath;
2506
+ }
2507
+ function writeTextAsset(ctx, absolutePath, content) {
2508
+ const path = toRelative(ctx.cwd, absolutePath);
2509
+ const existed = existsSync(absolutePath);
2510
+ if (existed && !ctx.force) {
2511
+ ctx.results.push({ path, action: "skipped" });
2512
+ return;
2513
+ }
2514
+ mkdirSync2(dirname2(absolutePath), { recursive: true });
2515
+ writeFileSync2(absolutePath, content);
2516
+ ctx.results.push({ path, action: existed ? "forced" : "created" });
2517
+ }
2518
+ function writeManagedAsset(ctx, absolutePath, content) {
2519
+ const path = toRelative(ctx.cwd, absolutePath);
2520
+ const existed = existsSync(absolutePath);
2521
+ mkdirSync2(dirname2(absolutePath), { recursive: true });
2522
+ writeFileSync2(absolutePath, content);
2523
+ ctx.results.push({ path, action: existed ? "updated" : "created" });
2524
+ }
2525
+ function writeMcpConfig(ctx, absolutePath) {
2526
+ const path = toRelative(ctx.cwd, absolutePath);
2527
+ let config = {};
2528
+ const existed = existsSync(absolutePath);
2529
+ if (existed) {
2530
+ try {
2531
+ config = JSON.parse(readFileSync2(absolutePath, "utf8"));
2532
+ } catch {
2533
+ ctx.results.push({ path, action: "skipped" });
2534
+ return;
2535
+ }
2536
+ }
2537
+ if (!config.mcpServers || typeof config.mcpServers !== "object") {
2538
+ config.mcpServers = {};
2539
+ }
2540
+ const servers = config.mcpServers;
2541
+ const hasKhotan = Object.prototype.hasOwnProperty.call(servers, MCP_SERVER_KEY);
2542
+ if (hasKhotan && !ctx.force) {
2543
+ ctx.results.push({ path, action: "skipped" });
2544
+ return;
2545
+ }
2546
+ servers[MCP_SERVER_KEY] = mcpServerEntry();
2547
+ mkdirSync2(dirname2(absolutePath), { recursive: true });
2548
+ writeFileSync2(absolutePath, `${JSON.stringify(config, null, 2)}
2549
+ `);
2550
+ ctx.results.push({
2551
+ path,
2552
+ action: existed ? hasKhotan ? "forced" : "updated" : "created"
2553
+ });
2554
+ }
2555
+ function runInit(options) {
2556
+ const { io, cwd, client, force, json } = options;
2557
+ const ctx = { cwd, force, results: [] };
2558
+ if (client === "cursor") {
2559
+ writeMcpConfig(ctx, join2(cwd, ".cursor", "mcp.json"));
2560
+ writeTextAsset(ctx, join2(cwd, ".cursor", "rules", "khotan.mdc"), CURSOR_RULES);
2561
+ } else {
2562
+ writeMcpConfig(ctx, join2(cwd, "mcp.json"));
2563
+ writeTextAsset(ctx, join2(cwd, "khotan-agents.md"), GENERIC_GUIDE);
2564
+ }
2565
+ for (const skill of generateSkills()) {
2566
+ for (const base of SKILL_BASES) {
2567
+ writeManagedAsset(ctx, join2(cwd, base, "skills", skill.name, "SKILL.md"), skill.content);
2568
+ }
2569
+ }
2570
+ if (json) {
2571
+ io.out(`${JSON.stringify({ client, actions: ctx.results }, null, 2)}
2572
+ `);
2573
+ return 0;
2574
+ }
2575
+ errLine(io, `Khotan workspace integration (${client}):`);
2576
+ for (const result of ctx.results) {
2577
+ errLine(io, ` ${result.action.padEnd(8)} ${result.path}`);
2578
+ }
2579
+ errLine(io);
2580
+ errLine(io, "Next steps:");
2581
+ errLine(io, " 1. Authenticate: khotan auth set-key --api-url <url> --api-key <key>");
2582
+ errLine(io, " 2. Open this workspace in your agent and ask it to configure Khotan.");
2583
+ return 0;
2584
+ }
2585
+
2586
+ // src/cli/confirm.ts
2587
+ init_src();
2588
+ init_io();
2589
+ async function ensureConfirmed(options) {
2590
+ const { io, yes, summary } = options;
2591
+ if (yes) {
2592
+ return;
2593
+ }
2594
+ if (!io.isTty) {
2595
+ throw new KhotanConfirmationRequiredError(`${summary} This is a destructive action; re-run with --yes to confirm in a non-interactive session.`);
2596
+ }
2597
+ errLine(io, summary);
2598
+ const answer = (await io.prompt("Type 'yes' to confirm: ")).trim().toLowerCase();
2599
+ if (answer !== "yes" && answer !== "y") {
2600
+ throw new KhotanConfirmationRequiredError("Aborted; no changes were made.");
2601
+ }
2602
+ }
2603
+
2400
2604
  // src/cli/run.ts
2401
2605
  init_io();
2402
2606
  init_session();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khotan/cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "description": "Khotan CLI and MCP server: human- and agent-facing adapters over the Khotan /api/v1 surface.",
6
6
  "license": "SEE LICENSE IN LICENSE.md",