open-multi-agent-kit 0.78.6 → 0.78.7

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 (169) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +26 -0
  3. package/MATURITY.md +2 -2
  4. package/README.md +19 -4
  5. package/dist/brand/night-city.theme.json +80 -0
  6. package/dist/brand/palette.d.ts +73 -177
  7. package/dist/brand/palette.js +115 -39
  8. package/dist/brand/rust-forge.theme.json +216 -0
  9. package/dist/brand/theme-compiled.d.ts +61 -0
  10. package/dist/brand/theme-compiled.js +241 -0
  11. package/dist/brand/theme.js +50 -37
  12. package/dist/cli/release-promotion-gate.d.ts +8 -1
  13. package/dist/cli/release-promotion-gate.js +20 -9
  14. package/dist/cli/root.d.ts +1 -0
  15. package/dist/cli/root.js +2 -1
  16. package/dist/cli/theme/index.d.ts +11 -2
  17. package/dist/cli/theme/index.js +6 -1
  18. package/dist/cli/theme/oklab-quantize.d.ts +29 -0
  19. package/dist/cli/theme/oklab-quantize.js +105 -0
  20. package/dist/cli/theme/render-table.d.ts +59 -0
  21. package/dist/cli/theme/render-table.js +112 -0
  22. package/dist/cli/theme/status-frame.d.ts +10 -0
  23. package/dist/cli/theme/status-frame.js +22 -0
  24. package/dist/cli/theme/terminal-capability.d.ts +6 -1
  25. package/dist/cli/theme/terminal-capability.js +20 -4
  26. package/dist/cli/theme/theme-doc.d.ts +22 -0
  27. package/dist/cli/theme/theme-doc.js +97 -0
  28. package/dist/cli/theme/theme-registry.js +39 -18
  29. package/dist/cli/theme/tier-explain.d.ts +21 -0
  30. package/dist/cli/theme/tier-explain.js +60 -0
  31. package/dist/cli/ui/green-rain-renderer.js +3 -2
  32. package/dist/cli/ui/neon-grid-renderer.js +4 -3
  33. package/dist/cli/ui/rust-forge-renderer.js +17 -3
  34. package/dist/cli/ui/system24-renderer.d.ts +18 -1
  35. package/dist/cli/ui/system24-renderer.js +39 -16
  36. package/dist/cli/v2/chat-repl.js +3 -1
  37. package/dist/cli/v2/cli-v2-skeleton.d.ts +6 -0
  38. package/dist/cli/v2/cli-v2-skeleton.js +121 -9
  39. package/dist/cli/v2/interactive-prompt.d.ts +1 -0
  40. package/dist/cli/v2/interactive-prompt.js +6 -2
  41. package/dist/commands/chat/core.js +2 -2
  42. package/dist/commands/chat/runtime.js +2 -0
  43. package/dist/commands/chat/utils.js +12 -4
  44. package/dist/commands/cockpit/render.js +102 -64
  45. package/dist/commands/cockpit/utils.d.ts +2 -0
  46. package/dist/commands/design.js +18 -16
  47. package/dist/commands/doctor/checks.d.ts +0 -1
  48. package/dist/commands/doctor/checks.js +1 -36
  49. package/dist/commands/doctor/report.js +0 -11
  50. package/dist/commands/init/content.d.ts +1 -1
  51. package/dist/commands/init/content.js +3 -11
  52. package/dist/commands/init.js +2 -2
  53. package/dist/commands/mcp/config.d.ts +32 -0
  54. package/dist/commands/mcp/config.js +264 -0
  55. package/dist/commands/mcp/doctor-fix.d.ts +12 -0
  56. package/dist/commands/mcp/doctor-fix.js +261 -0
  57. package/dist/commands/mcp/doctor.d.ts +55 -0
  58. package/dist/commands/mcp/doctor.js +401 -0
  59. package/dist/commands/mcp/list.d.ts +1 -0
  60. package/dist/commands/mcp/list.js +56 -0
  61. package/dist/commands/mcp/shared.d.ts +47 -0
  62. package/dist/commands/mcp/shared.js +192 -0
  63. package/dist/commands/mcp/test.d.ts +5 -0
  64. package/dist/commands/mcp/test.js +457 -0
  65. package/dist/commands/mcp.d.ts +8 -104
  66. package/dist/commands/mcp.js +5 -1603
  67. package/dist/contracts/proof.d.ts +1 -1
  68. package/dist/goal/control-loop.d.ts +15 -0
  69. package/dist/goal/control-loop.js +39 -5
  70. package/dist/hud/render.d.ts +2 -0
  71. package/dist/hud/render.js +7 -1
  72. package/dist/mcp/secret-scanner.d.ts +6 -0
  73. package/dist/mcp/secret-scanner.js +44 -11
  74. package/dist/memory/graph-delta-log.d.ts +137 -0
  75. package/dist/memory/graph-delta-log.js +650 -0
  76. package/dist/memory/graph-viewer.js +13 -32
  77. package/dist/memory/local-graph-memory-store.d.ts +36 -3
  78. package/dist/memory/local-graph-memory-store.js +255 -10
  79. package/dist/orchestration/log-streamer.js +13 -8
  80. package/dist/orchestration/routing.d.ts +4 -0
  81. package/dist/orchestration/routing.js +28 -3
  82. package/dist/orchestration/state-persister.js +8 -1
  83. package/dist/providers/anthropic-messages-runner.d.ts +17 -0
  84. package/dist/providers/anthropic-messages-runner.js +197 -0
  85. package/dist/providers/model-registry.d.ts +1 -1
  86. package/dist/providers/model-registry.js +37 -3
  87. package/dist/providers/provider-runtime.js +27 -11
  88. package/dist/providers/thinking-levels.js +4 -1
  89. package/dist/providers/types.d.ts +3 -3
  90. package/dist/runtime/contracts/weakness-remediation.d.ts +9 -1
  91. package/dist/runtime/contracts/weakness-remediation.js +9 -1
  92. package/dist/runtime/router-v2-scoring.d.ts +8 -3
  93. package/dist/runtime/router-v2-scoring.js +27 -22
  94. package/dist/runtime/runtime-router.d.ts +5 -0
  95. package/dist/runtime/runtime-router.js +40 -8
  96. package/dist/runtime/sandbox-profile.d.ts +29 -0
  97. package/dist/runtime/sandbox-profile.js +101 -0
  98. package/dist/runtime/slash-commands.js +300 -2
  99. package/dist/runtime/tool-dispatch-contracts.d.ts +15 -0
  100. package/dist/runtime/tool-dispatch-contracts.js +12 -0
  101. package/dist/runtime/weights-config.d.ts +90 -0
  102. package/dist/runtime/weights-config.js +249 -0
  103. package/dist/schema/envelope.schema.d.ts +18 -18
  104. package/dist/schema/evidence.schema.d.ts +2 -2
  105. package/dist/schema/proof-bundle.schema.d.ts +6 -6
  106. package/dist/schema/proof-bundle.schema.js +0 -1
  107. package/dist/schema/provider.schema.d.ts +2 -2
  108. package/dist/schema/run-manifest.schema.d.ts +16 -16
  109. package/dist/theme/ansi.js +9 -2
  110. package/dist/theme/extended-palette.d.ts +85 -0
  111. package/dist/theme/extended-palette.js +108 -0
  112. package/dist/theme/layout.js +29 -8
  113. package/dist/theme/metrics.js +5 -5
  114. package/dist/theme/parallel.js +25 -15
  115. package/dist/ui/omk-sigil.js +5 -14
  116. package/dist/ui/omk-working-sweep.js +5 -15
  117. package/dist/util/chat-agent-mode.d.ts +11 -0
  118. package/dist/util/chat-agent-mode.js +41 -8
  119. package/dist/util/chat-cockpit.d.ts +9 -0
  120. package/dist/util/chat-cockpit.js +77 -20
  121. package/dist/util/fs/core.d.ts +5 -0
  122. package/dist/util/fs/core.js +35 -0
  123. package/dist/util/fs/internal.d.ts +14 -0
  124. package/dist/util/fs/internal.js +169 -0
  125. package/dist/util/fs/kimi-sync.d.ts +41 -0
  126. package/dist/util/fs/kimi-sync.js +495 -0
  127. package/dist/util/fs/logo.d.ts +2 -0
  128. package/dist/util/fs/logo.js +78 -0
  129. package/dist/util/fs/manifest.d.ts +6 -0
  130. package/dist/util/fs/manifest.js +40 -0
  131. package/dist/util/fs/mcp-diagnose.d.ts +15 -0
  132. package/dist/util/fs/mcp-diagnose.js +204 -0
  133. package/dist/util/fs/mcp-runtime-config.d.ts +23 -0
  134. package/dist/util/fs/mcp-runtime-config.js +412 -0
  135. package/dist/util/fs/paths.d.ts +13 -0
  136. package/dist/util/fs/paths.js +82 -0
  137. package/dist/util/fs/preflight.d.ts +27 -0
  138. package/dist/util/fs/preflight.js +271 -0
  139. package/dist/util/fs.d.ts +8 -128
  140. package/dist/util/fs.js +8 -1757
  141. package/dist/util/terminal-layout.js +5 -1
  142. package/dist/util/version.js +2 -1
  143. package/docs/2026-06-11/critical-issues.md +20 -0
  144. package/docs/2026-06-11/improvements.md +14 -0
  145. package/docs/2026-06-11/init-checklist.md +25 -0
  146. package/docs/2026-06-11/plan.md +20 -0
  147. package/docs/ARCHITECTURE_ANALYSIS_CODEGRAPH.md +223 -0
  148. package/docs/adr/0001-no-native-rust-lane.md +52 -0
  149. package/docs/codex-oauth-setup.md +14 -0
  150. package/docs/decisions/2026-06-10-git-history-bloat.md +61 -0
  151. package/docs/decisions/ADR-theme-dark-only-assets.md +43 -0
  152. package/docs/getting-started.md +1 -1
  153. package/docs/headroom-omk-final-summary.md +188 -0
  154. package/docs/headroom-omk-integration.md +394 -0
  155. package/docs/headroom-omk-setup-guide.md +422 -0
  156. package/docs/headroom-omk-usage-examples.md +371 -0
  157. package/docs/provider-maturity.md +1 -1
  158. package/docs/versioning.md +3 -3
  159. package/package.json +10 -10
  160. package/readmeasset/.npmignore +2 -0
  161. package/readmeasset/ASSET_INDEX.md +7 -5
  162. package/readmeasset/ASSET_PROVENANCE.md +21 -5
  163. package/readmeasset/omk-control-surfaces.svg +55 -0
  164. package/readmeasset/omk-core-loop.svg +1 -0
  165. package/readmeasset/omk-logo-mark.svg +1 -0
  166. package/readmeasset/omk-release-assertions.svg +51 -0
  167. package/dist/native/linux-x64/omk-safety +0 -0
  168. package/dist/util/native-safety.d.ts +0 -28
  169. package/dist/util/native-safety.js +0 -118
@@ -363,23 +363,15 @@ OMK is the root orchestrator. Kimi is an authority-capable compatibility provide
363
363
 
364
364
  ## v1.2 — Hardening the current surface
365
365
 
366
- ### P0: native runtime safety hardening
367
-
368
- - Remaining: native chat turn capabilities must be intent-based: read-only by default, write only for edits, shell only for explicit command/build/test work, merge only for merge/release operations.
369
- - Remaining: \`--execution ask|auto|never\` and sandbox mode must propagate into runtime adapter metadata; provider adapters must not silently collapse ask into never.
370
- - Remaining: \`authority\`, \`primary\`, and \`omk\` provider policy must resolve to a concrete provider before bootstrap/router execution.
371
- - Remaining: CLI provider bootstrap must distinguish runtime/binary availability from auth/model/quota readiness.
372
- - Remaining: MCP, skills, hooks, and tool-plane parse or resolution failures must surface as diagnostics; runtime-required MCP failures should hard fail.
373
-
374
366
  ### P0: release and contract gates
375
367
 
376
368
  - Done: YAML validation now runs in local \`verify\` plus CI/smoke workflows.
377
- - Done: package dry-pack, package audit, tarball smoke, native safety build, and release matrix gates were re-verified against v1.1.17 artifacts.
369
+ - Done: package dry-pack, package audit, tarball smoke, and release matrix gates were re-verified against v1.1.17 artifacts.
378
370
  - Done: provider/deepseek and screenshot JSON command contracts gained hermetic regression tests.
379
371
  - Done: current AGENTS/init templates and packaged workflow skills were aligned with the active skills/MCP/agents/harness surface, including all generated agent MCP/skills/hooks flags and parallel subagent orchestration guidance.
380
372
  - Remaining: lock broader provider fallback metadata with tests for rate limit, timeout, and authority fallback variants.
381
373
  - Remaining: define minimum machine-readable CLI envelopes for the rest of the automation-critical commands.
382
- - Remaining: release/tag only after local gates, native safety packaging, package audit, smoke-pack, tarball install smoke, GitHub Smoke Test, and GitHub CI pass on the exact target commit.
374
+ - Remaining: release/tag only after local gates, package audit, smoke-pack, tarball install smoke, GitHub Smoke Test, and GitHub CI pass on the exact target commit.
383
375
 
384
376
  ### P1: observability and diagnostics
385
377
 
@@ -419,7 +411,7 @@ OMK is the root orchestrator. Kimi is an authority-capable compatibility provide
419
411
  | v0.4 | Google DESIGN.md integration, Stitch skills installer, screenshot UI review, Spec Kit planning + DAG execution, agent registry, project index, run summary |
420
412
  | v0.5 | MCP project server, plugin pack, CI agent mode |
421
413
  | v1.1.6 | provider/deepseek commands, provider policy flags, graph view, goal lifecycle, expanded run history and update JSON |
422
- | v1.1.9 | chat harness manifest, capability DAG lanes, Rust native safety loader, Windows clipboard screenshot bridge, release native matrix |
414
+ | v1.1.9 | chat harness manifest, capability DAG lanes, Windows clipboard screenshot bridge, release matrix |
423
415
  | v1.1.12 | Replay system, skill assigner, decision trace coverage, evidence gates, and repair policy |
424
416
  | v1.1.13 | Bundled MCP server entrypoints, ACP/host transport groundwork, deployment-ready package metadata |
425
417
  | v1.1.14 | Current harness docs, external-inspired workflow skills, and release-safe public wording |
@@ -410,7 +410,7 @@ Provider routing and graph viewing are no longer purely future work:
410
410
  ### P0: release and contract gates
411
411
 
412
412
  - Done: YAML validation now runs in local \`verify\` plus CI/smoke workflows.
413
- - Done: package dry-pack, package audit, tarball smoke, native safety build, and release matrix gates were re-verified against v1.1.17 artifacts.
413
+ - Done: package dry-pack, package audit, tarball smoke, and release matrix gates were re-verified against v1.1.17 artifacts.
414
414
  - Done: provider/deepseek and screenshot JSON command contracts gained hermetic regression tests.
415
415
  - Done: current AGENTS/init templates and packaged workflow skills were aligned with the active skills/MCP/agents/harness surface, including all generated agent MCP/skills/hooks flags and parallel subagent orchestration guidance.
416
416
  - Remaining: lock broader provider fallback metadata with tests for rate limit, timeout, and Kimi fallback variants.
@@ -454,7 +454,7 @@ Provider routing and graph viewing are no longer purely future work:
454
454
  | v0.4 | Google DESIGN.md integration, Stitch skills installer, screenshot UI review, Spec Kit planning + DAG execution, agent registry, project index, run summary |
455
455
  | v0.5 | MCP project server, plugin pack, CI agent mode |
456
456
  | v1.1.6 | provider/deepseek commands, provider policy flags, graph view, goal lifecycle, expanded run history and update JSON |
457
- | v1.1.9 | chat harness manifest, capability DAG lanes, Rust native safety loader, Windows clipboard screenshot bridge, release native matrix |
457
+ | v1.1.9 | chat harness manifest, capability DAG lanes, Windows clipboard screenshot bridge, release matrix |
458
458
  | v1.1.12 | Replay system, skill assigner, decision trace coverage, evidence gates, and repair policy |
459
459
  | v1.1.13 | Bundled MCP server entrypoints, ACP/host transport groundwork, deployment-ready package metadata |
460
460
  | v1.1.14 | Current harness docs, external-inspired workflow skills, and release-safe public wording |
@@ -0,0 +1,32 @@
1
+ export declare function mcpRemoveCommand(serverName: string, options?: {
2
+ global?: boolean;
3
+ }): Promise<void>;
4
+ export declare function mcpAddCommand(serverName: string): Promise<void>;
5
+ export declare function mcpInstallCommand(name: string, command: string, args: string[], options?: {
6
+ env?: string[];
7
+ startupTimeoutSec?: number;
8
+ }): Promise<void>;
9
+ export interface BulkInstallEntry {
10
+ name: string;
11
+ command: string;
12
+ args: string[];
13
+ env?: string[] | Record<string, string>;
14
+ startupTimeoutSec?: number;
15
+ }
16
+ export interface BulkInstallResult {
17
+ installed: string[];
18
+ failed: Array<{
19
+ name: string;
20
+ error: string;
21
+ }>;
22
+ skipped: string[];
23
+ }
24
+ export declare function mcpBulkInstallCommand(entries: BulkInstallEntry[]): Promise<BulkInstallResult>;
25
+ export declare function mcpSyncGlobalCommand(options: {
26
+ overwrite?: boolean;
27
+ omk?: boolean;
28
+ }): Promise<void>;
29
+ export declare function mcpMigrateCommand(options?: {
30
+ global?: boolean;
31
+ dryRun?: boolean;
32
+ }): Promise<void>;
@@ -0,0 +1,264 @@
1
+ import { mkdir, writeFile } from "fs/promises";
2
+ import { join } from "path";
3
+ import { getProjectRoot, pathExists, getUserHome, STALE_PACKAGE_NAMES, } from "../../util/fs.js";
4
+ import { style, header, status, label } from "../../util/theme.js";
5
+ import { maskSensitiveText } from "../../util/secret-mask.js";
6
+ import { formatArgsForDisplay, isHttpUrl, isSecretEnvName, loadConfig, RAILWAY_REMOTE_MCP_URL, sanitizeMcpServerForProject, sanitizeMcpUrlForDisplay, } from "./shared.js";
7
+ export async function mcpRemoveCommand(serverName, options = {}) {
8
+ const root = getProjectRoot();
9
+ const localPath = join(root, ".kimi", "mcp.json");
10
+ const omkPath = join(root, ".omk", "mcp.json");
11
+ const globalPath = join(getUserHome(), ".kimi", "mcp.json");
12
+ const sources = options.global
13
+ ? [{ path: globalPath, label: "global" }]
14
+ : [
15
+ { path: localPath, label: "project-local" },
16
+ { path: omkPath, label: "omk-project" },
17
+ ];
18
+ let removed = false;
19
+ for (const src of sources) {
20
+ const cfg = await loadConfig(src.path);
21
+ if (!cfg.parsed || !cfg.config.mcpServers || !(serverName in cfg.config.mcpServers)) {
22
+ continue;
23
+ }
24
+ const next = { ...cfg.config.mcpServers };
25
+ delete next[serverName];
26
+ await writeFile(src.path, JSON.stringify({ mcpServers: next }, null, 2) + "\n", "utf-8");
27
+ console.log(status.ok(`Removed "${serverName}" from ${src.label} MCP config: ${src.path}`));
28
+ removed = true;
29
+ break;
30
+ }
31
+ if (!removed) {
32
+ console.log(status.error(`Server "${serverName}" not found in ${options.global ? "global" : "project-local"} MCP configs.`));
33
+ console.log(style.gray(`Checked: ${sources.map((s) => s.path).join(", ")}`));
34
+ if (!options.global) {
35
+ console.log(style.gray(`To remove from global config, run \`omk mcp remove ${serverName} --global\`, or edit ~/.kimi/mcp.json manually.`));
36
+ }
37
+ else {
38
+ console.log(style.gray(`To remove from project-local configs, run \`omk mcp remove ${serverName}\`.`));
39
+ }
40
+ process.exit(1);
41
+ }
42
+ }
43
+ export async function mcpAddCommand(serverName) {
44
+ const root = getProjectRoot();
45
+ const globalPath = join(getUserHome(), ".kimi", "mcp.json");
46
+ const projectMcpPath = join(root, ".kimi", "mcp.json");
47
+ const globalSource = await loadConfig(globalPath);
48
+ if (!globalSource.parsed || !globalSource.config.mcpServers || !(serverName in globalSource.config.mcpServers)) {
49
+ console.log(status.error(`Server "${serverName}" not found in global MCP config: ${globalPath}`));
50
+ process.exit(1);
51
+ }
52
+ const projectMcpSource = await loadConfig(projectMcpPath);
53
+ const projectMcpServers = projectMcpSource.parsed && projectMcpSource.config.mcpServers
54
+ ? { ...projectMcpSource.config.mcpServers }
55
+ : {};
56
+ if (serverName in projectMcpServers) {
57
+ console.log(status.error(`Server "${serverName}" already exists in ${projectMcpPath}`));
58
+ process.exit(1);
59
+ }
60
+ projectMcpServers[serverName] = sanitizeMcpServerForProject(globalSource.config.mcpServers[serverName]);
61
+ await mkdir(join(root, ".kimi"), { recursive: true });
62
+ await writeFile(projectMcpPath, JSON.stringify({ mcpServers: projectMcpServers }, null, 2) + "\n", "utf-8");
63
+ console.log(header("MCP Add"));
64
+ console.log(status.ok(`Added "${serverName}" to ${projectMcpPath}`));
65
+ }
66
+ export async function mcpInstallCommand(name, command, args, options = {}) {
67
+ const root = getProjectRoot();
68
+ const projectMcpPath = join(root, ".kimi", "mcp.json");
69
+ const projectMcpSource = await loadConfig(projectMcpPath);
70
+ const projectMcpServers = projectMcpSource.parsed && projectMcpSource.config.mcpServers
71
+ ? { ...projectMcpSource.config.mcpServers }
72
+ : {};
73
+ if (name in projectMcpServers) {
74
+ console.log(status.error(`Server "${name}" already exists in ${projectMcpPath}`));
75
+ process.exit(1);
76
+ }
77
+ const server = sanitizeMcpServerForProject(createInstallServer(name, command, args, options));
78
+ projectMcpServers[name] = server;
79
+ await mkdir(join(root, ".kimi"), { recursive: true });
80
+ await writeFile(projectMcpPath, JSON.stringify({ mcpServers: projectMcpServers }, null, 2) + "\n", "utf-8");
81
+ console.log(header("MCP Install"));
82
+ console.log(status.ok(`Installed "${name}" into ${projectMcpPath}`));
83
+ if (server.url) {
84
+ console.log(label("URL", sanitizeMcpUrlForDisplay(server.url)));
85
+ }
86
+ else {
87
+ console.log(label("Command", maskSensitiveText(server.command ?? command)));
88
+ if (args.length > 0)
89
+ console.log(label("Args", formatArgsForDisplay(server.args ?? args)));
90
+ }
91
+ }
92
+ function createInstallServer(name, command, args, options = {}) {
93
+ if (isRailwayInstallPreset(name, command, args, options)) {
94
+ return { url: RAILWAY_REMOTE_MCP_URL };
95
+ }
96
+ if (isHttpUrl(command) && args.length === 0) {
97
+ return { url: sanitizeMcpUrlForDisplay(command) };
98
+ }
99
+ const server = { command, args: [...args] };
100
+ if (options.env) {
101
+ server.env = {};
102
+ if (Array.isArray(options.env)) {
103
+ for (const pair of options.env) {
104
+ const idx = pair.indexOf("=");
105
+ if (idx > 0) {
106
+ const key = pair.slice(0, idx);
107
+ server.env[key] = sanitizeInstallEnvValue(key, pair.slice(idx + 1));
108
+ }
109
+ }
110
+ }
111
+ else {
112
+ for (const [key, value] of Object.entries(options.env)) {
113
+ server.env[key] = sanitizeInstallEnvValue(key, value);
114
+ }
115
+ }
116
+ }
117
+ if (Number.isFinite(options.startupTimeoutSec) && options.startupTimeoutSec && options.startupTimeoutSec > 0) {
118
+ server.startup_timeout_sec = Math.trunc(options.startupTimeoutSec);
119
+ }
120
+ return server;
121
+ }
122
+ function sanitizeInstallEnvValue(key, value) {
123
+ if (!isSecretEnvName(key))
124
+ return value;
125
+ const trimmed = value.trim();
126
+ if (/^\$\{[A-Za-z_][A-Za-z0-9_]*\}$/.test(trimmed))
127
+ return trimmed;
128
+ return `\${${key}}`;
129
+ }
130
+ function isRailwayInstallPreset(name, command, args, options) {
131
+ return name.toLowerCase() === "railway"
132
+ && command === "railway"
133
+ && args.length === 0
134
+ && (!options.env || options.env.length === 0);
135
+ }
136
+ export async function mcpBulkInstallCommand(entries) {
137
+ const root = getProjectRoot();
138
+ const projectMcpPath = join(root, ".kimi", "mcp.json");
139
+ const projectMcpSource = await loadConfig(projectMcpPath);
140
+ const projectMcpServers = projectMcpSource.parsed && projectMcpSource.config.mcpServers
141
+ ? { ...projectMcpSource.config.mcpServers }
142
+ : {};
143
+ const results = { installed: [], failed: [], skipped: [] };
144
+ await Promise.all(entries.map(async (entry) => {
145
+ try {
146
+ if (entry.name in projectMcpServers) {
147
+ results.skipped.push(entry.name);
148
+ return;
149
+ }
150
+ const server = sanitizeMcpServerForProject(createInstallServer(entry.name, entry.command, entry.args, {
151
+ env: entry.env,
152
+ startupTimeoutSec: entry.startupTimeoutSec,
153
+ }));
154
+ projectMcpServers[entry.name] = server;
155
+ results.installed.push(entry.name);
156
+ }
157
+ catch (err) {
158
+ const message = err instanceof Error ? err.message : String(err);
159
+ results.failed.push({ name: entry.name, error: message });
160
+ }
161
+ }));
162
+ if (results.installed.length > 0 || results.skipped.length > 0) {
163
+ await mkdir(join(root, ".kimi"), { recursive: true });
164
+ await writeFile(projectMcpPath, JSON.stringify({ mcpServers: projectMcpServers }, null, 2) + "\n", "utf-8");
165
+ }
166
+ return results;
167
+ }
168
+ export async function mcpSyncGlobalCommand(options) {
169
+ const root = getProjectRoot();
170
+ const globalPath = join(getUserHome(), ".kimi", "mcp.json");
171
+ const targetPath = options.omk ? join(root, ".omk", "mcp.json") : join(root, ".kimi", "mcp.json");
172
+ const globalSource = await loadConfig(globalPath);
173
+ if (!globalSource.parsed || !globalSource.config.mcpServers) {
174
+ console.log(status.error(`No global MCP config found at ${globalPath}`));
175
+ process.exit(1);
176
+ }
177
+ const targetSource = await loadConfig(targetPath);
178
+ const targetServers = targetSource.parsed && targetSource.config.mcpServers ? targetSource.config.mcpServers : {};
179
+ let imported = 0;
180
+ let skipped = 0;
181
+ const merged = options.overwrite ? {} : { ...targetServers };
182
+ for (const [name, server] of Object.entries(globalSource.config.mcpServers)) {
183
+ if (name === "omk-project") {
184
+ skipped++;
185
+ continue;
186
+ }
187
+ if (!options.overwrite && name in merged) {
188
+ skipped++;
189
+ continue;
190
+ }
191
+ merged[name] = sanitizeMcpServerForProject(server);
192
+ imported++;
193
+ }
194
+ const output = { mcpServers: merged };
195
+ await writeFile(targetPath, JSON.stringify(output, null, 2) + "\n", "utf-8");
196
+ console.log(header("MCP Sync Global"));
197
+ console.log(status.ok(`Imported ${imported} global MCP server(s)`));
198
+ if (skipped > 0) {
199
+ console.log(style.gray(`Skipped ${skipped} (omk-project or existing local)`));
200
+ }
201
+ console.log(label("Written to", targetPath));
202
+ }
203
+ export async function mcpMigrateCommand(options = {}) {
204
+ const root = getProjectRoot();
205
+ const globalPath = join(getUserHome(), ".kimi", "mcp.json");
206
+ const projectPath = join(root, ".kimi", "mcp.json");
207
+ const omkPath = join(root, ".omk", "mcp.json");
208
+ const configs = [];
209
+ if (options.global) {
210
+ configs.push({ path: globalPath, label: "global" });
211
+ }
212
+ else {
213
+ configs.push({ path: projectPath, label: "project" });
214
+ if (await pathExists(omkPath)) {
215
+ configs.push({ path: omkPath, label: "project-omk" });
216
+ }
217
+ }
218
+ let totalFixed = 0;
219
+ const fixedServers = [];
220
+ for (const { path: configPath } of configs) {
221
+ const source = await loadConfig(configPath);
222
+ if (!source.parsed || !source.config.mcpServers)
223
+ continue;
224
+ const servers = source.config.mcpServers;
225
+ let changed = false;
226
+ for (const [name, server] of Object.entries(servers)) {
227
+ const args = server.args ?? [];
228
+ for (let i = 0; i < args.length; i++) {
229
+ const arg = args[i];
230
+ if (typeof arg !== "string")
231
+ continue;
232
+ for (const [stale, current] of Object.entries(STALE_PACKAGE_NAMES)) {
233
+ if (arg.includes(stale)) {
234
+ const newArg = arg.replace(stale, current);
235
+ if (!options.dryRun) {
236
+ args[i] = newArg;
237
+ changed = true;
238
+ }
239
+ totalFixed++;
240
+ fixedServers.push({ name, file: configPath, from: stale, to: current });
241
+ }
242
+ }
243
+ }
244
+ }
245
+ if (changed) {
246
+ await writeFile(configPath, JSON.stringify(source.config, null, 2) + "\n", "utf-8");
247
+ }
248
+ }
249
+ console.log(header("MCP Migrate"));
250
+ if (totalFixed === 0) {
251
+ console.log(status.ok("No stale package names found"));
252
+ }
253
+ else {
254
+ if (options.dryRun) {
255
+ console.log(style.skin(`${totalFixed} stale package reference(s) would be fixed:`));
256
+ }
257
+ else {
258
+ console.log(status.ok(`Fixed ${totalFixed} stale package reference(s):`));
259
+ }
260
+ for (const entry of fixedServers) {
261
+ console.log(` ${label(entry.name, `${entry.from} → ${entry.to}`)} (${entry.file})`);
262
+ }
263
+ }
264
+ }
@@ -0,0 +1,12 @@
1
+ export interface McpDoctorFixReport {
2
+ changed: boolean;
3
+ dryRun: boolean;
4
+ global: boolean;
5
+ actions: string[];
6
+ skipped: string[];
7
+ backups: string[];
8
+ }
9
+ export declare function repairMcpDoctorIssues(options?: {
10
+ dryRun?: boolean;
11
+ global?: boolean;
12
+ }): Promise<McpDoctorFixReport>;
@@ -0,0 +1,261 @@
1
+ import { chmod, mkdir, readFile, writeFile } from "fs/promises";
2
+ import { dirname, join } from "path";
3
+ import { collectMcpConfigs, diagnoseRuntimeMcpServer, getProjectRoot, pathExists, getUserHome, STALE_PACKAGE_NAMES, } from "../../util/fs.js";
4
+ import { getOmkResourceSettings } from "../../util/resource-profile.js";
5
+ import { maskSensitiveText } from "../../util/secret-mask.js";
6
+ import { collectServers, isHttpUrl, isNpmLauncherCommand, resolveAllConfigs, sanitizeMcpServerForProject, } from "./shared.js";
7
+ const AUTO_DISABLE_RUNTIME_BLOCKERS = new Set([
8
+ "missing-command",
9
+ "command-path-not-found",
10
+ "arg-path-not-found",
11
+ "shell-builtin-command",
12
+ "windows-set-inline",
13
+ "inline-script-path-not-found",
14
+ "stale-home-reference",
15
+ "stdio-http-transport",
16
+ "invalid-server",
17
+ ]);
18
+ async function ensureMcpConfigFile(filePath, config, actions, options = {}) {
19
+ if (await pathExists(filePath))
20
+ return;
21
+ if (options.dryRun) {
22
+ actions.push(`would create MCP config ${filePath}`);
23
+ return;
24
+ }
25
+ await mkdir(dirname(filePath), { recursive: true });
26
+ await writeFile(filePath, JSON.stringify(config, null, 2) + "\n", "utf-8");
27
+ actions.push(`created MCP config ${filePath}`);
28
+ }
29
+ function hasConfiguredMcpServers(config) {
30
+ return Object.keys(config?.mcpServers ?? {}).length > 0;
31
+ }
32
+ async function readParsedMcpConfig(filePath) {
33
+ if (!(await pathExists(filePath)))
34
+ return null;
35
+ try {
36
+ const parsed = JSON.parse(await readFile(filePath, "utf-8"));
37
+ return parsed && typeof parsed === "object" ? parsed : null;
38
+ }
39
+ catch {
40
+ return null;
41
+ }
42
+ }
43
+ async function migrateLegacyOmkMcpBeforeEnsure(omkMcpPath, projectMcpPath, actions, options = {}) {
44
+ const legacyConfig = await readParsedMcpConfig(omkMcpPath);
45
+ if (!hasConfiguredMcpServers(legacyConfig))
46
+ return;
47
+ const projectConfig = await readParsedMcpConfig(projectMcpPath);
48
+ if (projectConfig && hasConfiguredMcpServers(projectConfig))
49
+ return;
50
+ if (options.dryRun) {
51
+ actions.push(`would migrate legacy MCP servers from ${omkMcpPath} to ${projectMcpPath}`);
52
+ return;
53
+ }
54
+ const migratedConfig = {
55
+ ...(projectConfig ?? {}),
56
+ _comment: "Project-local MCP config migrated from legacy .omk/mcp.json; review secrets before sharing.",
57
+ mcpServers: { ...(legacyConfig?.mcpServers ?? {}) },
58
+ };
59
+ if (projectConfig) {
60
+ await createSanitizedMcpBackup(projectMcpPath, projectConfig, options.backups ?? [], actions);
61
+ }
62
+ await mkdir(dirname(projectMcpPath), { recursive: true });
63
+ await writeFile(projectMcpPath, JSON.stringify(migratedConfig, null, 2) + "\n", "utf-8");
64
+ actions.push(`migrated legacy MCP servers from ${omkMcpPath} to ${projectMcpPath}`);
65
+ }
66
+ function cloneMcpConfig(config) {
67
+ return JSON.parse(JSON.stringify(config));
68
+ }
69
+ function timestampForBackup() {
70
+ return new Date().toISOString().replace(/[:.]/g, "-");
71
+ }
72
+ async function createSanitizedMcpBackup(filePath, config, backups, actions) {
73
+ if (!(await pathExists(filePath)))
74
+ return;
75
+ const backupPath = `${filePath}.omk-backup-${timestampForBackup()}-${backups.length + 1}.json`;
76
+ const dir = dirname(filePath);
77
+ await mkdir(dir, { recursive: true, mode: 0o700 });
78
+ await chmod(dir, 0o700).catch(() => undefined);
79
+ await writeFile(backupPath, JSON.stringify(sanitizeMcpConfigForBackup(config), null, 2) + "\n", { encoding: "utf-8", mode: 0o600 });
80
+ await chmod(backupPath, 0o600).catch(() => undefined);
81
+ backups.push(backupPath);
82
+ actions.push(`created sanitized MCP backup ${backupPath}`);
83
+ }
84
+ function sanitizeMcpConfigForBackup(config) {
85
+ const cleaned = cloneMcpConfig(config);
86
+ if (cleaned.mcpServers && typeof cleaned.mcpServers === "object") {
87
+ cleaned.mcpServers = Object.fromEntries(Object.entries(cleaned.mcpServers).map(([name, server]) => [name, sanitizeMcpServerForProject(server)]));
88
+ }
89
+ const disabled = cleaned._omkDisabledMcpServers;
90
+ if (disabled && typeof disabled === "object" && !Array.isArray(disabled)) {
91
+ cleaned._omkDisabledMcpServers = Object.fromEntries(Object.entries(disabled).map(([name, server]) => [
92
+ name,
93
+ server && typeof server === "object" && !Array.isArray(server)
94
+ ? sanitizeMcpServerForProject(server)
95
+ : server,
96
+ ]));
97
+ }
98
+ return cleaned;
99
+ }
100
+ function repairMcpServerConfig(name, server) {
101
+ const actions = [];
102
+ if (server.command && isHttpUrl(server.command) && (!server.args || server.args.length === 0)) {
103
+ server.url = server.command;
104
+ delete server.command;
105
+ delete server.args;
106
+ actions.push(`converted "${name}" command URL to remote url transport`);
107
+ }
108
+ if (!server.url && server.args?.includes("@modelcontextprotocol/server-pdf") && !server.args.includes("--stdio")) {
109
+ server.args = [...server.args, "--stdio"];
110
+ actions.push(`added --stdio to "${name}" PDF MCP server`);
111
+ }
112
+ if (!server.url && server.args) {
113
+ let replaced = false;
114
+ server.args = server.args.map((arg) => {
115
+ const replacement = STALE_PACKAGE_NAMES[arg];
116
+ if (!replacement)
117
+ return arg;
118
+ replaced = true;
119
+ return replacement;
120
+ });
121
+ if (replaced)
122
+ actions.push(`replaced stale MCP package argument for "${name}"`);
123
+ }
124
+ if (!server.url && isNpmLauncherCommand(server.command) && !server.startup_timeout_sec) {
125
+ server.startup_timeout_sec = 15;
126
+ actions.push(`set 15s startup timeout for npm-based MCP "${name}"`);
127
+ }
128
+ return actions;
129
+ }
130
+ function disabledMcpServers(config) {
131
+ const current = config._omkDisabledMcpServers;
132
+ if (current && typeof current === "object" && !Array.isArray(current)) {
133
+ return current;
134
+ }
135
+ const next = {};
136
+ config._omkDisabledMcpServers = next;
137
+ return next;
138
+ }
139
+ function moveMcpServerToDisabled(source, name, server, reason) {
140
+ const disabled = disabledMcpServers(source.config);
141
+ disabled[name] = {
142
+ ...server,
143
+ _omkDisabledAt: new Date().toISOString(),
144
+ _omkDisabledReason: reason,
145
+ };
146
+ delete source.config.mcpServers?.[name];
147
+ source.changed = true;
148
+ }
149
+ export async function repairMcpDoctorIssues(options = {}) {
150
+ const root = getProjectRoot();
151
+ const projectOmkDir = join(root, ".omk");
152
+ const projectKimiDir = join(root, ".kimi");
153
+ const omkMcpPath = join(projectOmkDir, "mcp.json");
154
+ const projectMcpPath = join(projectKimiDir, "mcp.json");
155
+ const dryRun = Boolean(options.dryRun);
156
+ const includeGlobal = Boolean(options.global);
157
+ const actions = [];
158
+ const skipped = [];
159
+ const backups = [];
160
+ if (!dryRun) {
161
+ await Promise.all([
162
+ mkdir(projectOmkDir, { recursive: true }),
163
+ mkdir(projectKimiDir, { recursive: true }),
164
+ ]);
165
+ }
166
+ await migrateLegacyOmkMcpBeforeEnsure(omkMcpPath, projectMcpPath, actions, { dryRun, backups });
167
+ await ensureMcpConfigFile(omkMcpPath, { mcpServers: {} }, actions, { dryRun });
168
+ await ensureMcpConfigFile(projectMcpPath, {
169
+ _comment: "Project-local MCP config. omk-project is virtual runtime MCP injected; global MCP servers remain in ~/.kimi/mcp.json and must be imported explicitly only after secret review.",
170
+ mcpServers: {},
171
+ }, actions, { dryRun });
172
+ const sources = await resolveAllConfigs();
173
+ const resources = await getOmkResourceSettings();
174
+ const activePathOrder = await collectMcpConfigs(resources.mcpScope);
175
+ const activePaths = new Set(activePathOrder);
176
+ const activePreferredPath = activePathOrder.at(-1);
177
+ const globalMcpPath = join(getUserHome(), ".kimi", "mcp.json");
178
+ const mutableSources = new Map();
179
+ for (const source of sources) {
180
+ if (!source.exists)
181
+ continue;
182
+ if (!source.parsed) {
183
+ skipped.push(`invalid JSON left unchanged: ${source.path}`);
184
+ continue;
185
+ }
186
+ const clonedConfig = cloneMcpConfig(source.config);
187
+ mutableSources.set(source.path, {
188
+ path: source.path,
189
+ config: clonedConfig,
190
+ originalConfig: cloneMcpConfig(source.config),
191
+ changed: false,
192
+ });
193
+ }
194
+ const writableConfigPaths = new Set([omkMcpPath, projectMcpPath]);
195
+ if (includeGlobal)
196
+ writableConfigPaths.add(globalMcpPath);
197
+ const servers = collectServers(sources);
198
+ for (const [name, info] of servers) {
199
+ const activeSources = info.sources.filter((sourcePath) => activePaths.has(sourcePath));
200
+ if (activeSources.length <= 1)
201
+ continue;
202
+ const projectActiveSources = activeSources.filter((sourcePath) => sourcePath !== globalMcpPath);
203
+ if (activeSources.includes(globalMcpPath) && projectActiveSources.length > 0) {
204
+ skipped.push(`duplicate MCP "${name}" spans global/project; kept project override: ${activeSources.join(", ")}`);
205
+ continue;
206
+ }
207
+ const removableSources = activeSources.includes(globalMcpPath)
208
+ ? activeSources.filter((sourcePath) => sourcePath !== globalMcpPath)
209
+ : activeSources.filter((sourcePath) => sourcePath !== activePreferredPath);
210
+ for (const sourcePath of removableSources) {
211
+ if (!writableConfigPaths.has(sourcePath)) {
212
+ skipped.push(`duplicate MCP "${name}" not modified outside requested repair scope: ${sourcePath}`);
213
+ continue;
214
+ }
215
+ const source = mutableSources.get(sourcePath);
216
+ if (!source?.config.mcpServers?.[name])
217
+ continue;
218
+ delete source.config.mcpServers[name];
219
+ source.changed = true;
220
+ actions.push(`removed duplicate MCP "${name}" from ${sourcePath}`);
221
+ }
222
+ if (removableSources.length === 0) {
223
+ skipped.push(`duplicate MCP "${name}" requires manual choice: ${activeSources.join(", ")}`);
224
+ }
225
+ }
226
+ for (const source of mutableSources.values()) {
227
+ if (!writableConfigPaths.has(source.path))
228
+ continue;
229
+ const serversByName = source.config.mcpServers ?? {};
230
+ for (const [name, server] of Object.entries(serversByName)) {
231
+ const serverActions = repairMcpServerConfig(name, server);
232
+ if (serverActions.length > 0) {
233
+ source.changed = true;
234
+ actions.push(...serverActions.map((action) => `${source.path}: ${action}`));
235
+ }
236
+ const diagnostics = await diagnoseRuntimeMcpServer(name, server);
237
+ const blockers = diagnostics.filter((diagnostic) => AUTO_DISABLE_RUNTIME_BLOCKERS.has(diagnostic.kind));
238
+ if (blockers.length > 0) {
239
+ const reason = blockers.map((diagnostic) => diagnostic.message).join("; ");
240
+ moveMcpServerToDisabled(source, name, server, reason);
241
+ actions.push(`${source.path}: disabled MCP "${name}" due to runtime startup blocker: ${reason}`);
242
+ }
243
+ }
244
+ }
245
+ for (const source of mutableSources.values()) {
246
+ if (!source.changed)
247
+ continue;
248
+ if (dryRun)
249
+ continue;
250
+ await createSanitizedMcpBackup(source.path, source.originalConfig, backups, actions);
251
+ await writeFile(source.path, JSON.stringify(source.config, null, 2) + "\n", "utf-8");
252
+ }
253
+ return {
254
+ changed: !dryRun && actions.length > 0,
255
+ dryRun,
256
+ global: includeGlobal,
257
+ actions: actions.map(maskSensitiveText),
258
+ skipped: skipped.map(maskSensitiveText),
259
+ backups,
260
+ };
261
+ }