open-multi-agent-kit 0.78.5 → 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 (176) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +64 -0
  3. package/MATURITY.md +2 -2
  4. package/README.md +20 -5
  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 +23 -11
  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 +11 -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-10/critical-issues.md +20 -0
  144. package/docs/2026-06-10/improvements.md +14 -0
  145. package/docs/2026-06-10/init-checklist.md +25 -0
  146. package/docs/2026-06-10/plan.md +20 -0
  147. package/docs/2026-06-11/critical-issues.md +20 -0
  148. package/docs/2026-06-11/improvements.md +14 -0
  149. package/docs/2026-06-11/init-checklist.md +25 -0
  150. package/docs/2026-06-11/plan.md +20 -0
  151. package/docs/ARCHITECTURE_ANALYSIS_CODEGRAPH.md +223 -0
  152. package/docs/adr/0001-no-native-rust-lane.md +52 -0
  153. package/docs/codex-oauth-setup.md +14 -0
  154. package/docs/decisions/2026-06-10-git-history-bloat.md +61 -0
  155. package/docs/decisions/ADR-theme-dark-only-assets.md +43 -0
  156. package/docs/getting-started.md +1 -1
  157. package/docs/headroom-omk-final-summary.md +188 -0
  158. package/docs/headroom-omk-integration.md +394 -0
  159. package/docs/headroom-omk-setup-guide.md +422 -0
  160. package/docs/headroom-omk-usage-examples.md +371 -0
  161. package/docs/provider-maturity.md +1 -1
  162. package/docs/versioning.md +3 -3
  163. package/package.json +10 -10
  164. package/readmeasset/.npmignore +2 -0
  165. package/readmeasset/ASSET_INDEX.md +7 -5
  166. package/readmeasset/ASSET_PROVENANCE.md +21 -5
  167. package/readmeasset/omk-control-surfaces.svg +55 -0
  168. package/readmeasset/omk-core-loop.svg +1 -0
  169. package/readmeasset/omk-logo-mark.svg +1 -0
  170. package/readmeasset/omk-release-assertions.svg +51 -0
  171. package/dist/native/linux-x64/omk-safety +0 -0
  172. package/dist/util/native-safety.d.ts +0 -28
  173. package/dist/util/native-safety.js +0 -118
  174. package/templates/skills/agents/omk-adaptorch-orchestration-review/SKILL.md +0 -52
  175. package/templates/skills/kimi/mcp-install/SKILL.md +0 -66
  176. package/templates/skills/kimi/omk-adaptorch-orchestration-review/SKILL.md +0 -52
@@ -0,0 +1,495 @@
1
+ import { access, constants, readdir, symlink, rm, unlink, lstat, copyFile, } from "fs/promises";
2
+ import { join, relative, resolve } from "path";
3
+ import { GLOBAL_MEMORY_CONFIG_TOML, getGlobalMemoryConfigPath } from "../../memory/memory-config.js";
4
+ import { sha256, simpleDiff } from "../sync-manifest.js";
5
+ import { ensureDir, pathExists, readTextFile, writeFileSafe } from "./core.js";
6
+ import { getKimiConfigPath, getOmkPath, getProjectRootAsync, getUserHome } from "./paths.js";
7
+ import { backupFile, getBackupDir } from "./manifest.js";
8
+ import { isShellInlineMcpArg, PACKAGE_MANAGER_COMMANDS } from "./internal.js";
9
+ function isGlobalWriteAllowed() {
10
+ return /^(?:1|true|yes|on)$/i.test(process.env.OMK_MCP_ALLOW_WRITE_CONFIG ?? "");
11
+ }
12
+ function shouldRewriteMcpArgPath(server, arg, index) {
13
+ if (typeof arg !== "string")
14
+ return false;
15
+ if (arg.startsWith("/") || arg.startsWith("http") || arg.startsWith("-"))
16
+ return false;
17
+ if (isShellInlineMcpArg(server, index))
18
+ return false;
19
+ if (/[\s;"'|&<>]/.test(arg))
20
+ return false;
21
+ if (isPackageManagerMcpServer(server) && isNpmPackageSpecifierArg(arg))
22
+ return false;
23
+ return isExplicitRelativeMcpPathArg(arg, server, index);
24
+ }
25
+ function isPackageManagerMcpServer(server) {
26
+ const command = typeof server.command === "string" ? server.command : "";
27
+ const commandName = command.replace(/\\/g, "/").split("/").pop()?.toLowerCase() ?? command.toLowerCase();
28
+ return PACKAGE_MANAGER_COMMANDS.has(commandName);
29
+ }
30
+ function isNpmPackageSpecifierArg(arg) {
31
+ if (arg.startsWith(".") || arg.startsWith("/") || arg.includes("\\") || arg.includes(":"))
32
+ return false;
33
+ return /^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+(?:@[a-z0-9._~+-]+)?$/i.test(arg);
34
+ }
35
+ function isExplicitRelativeMcpPathArg(arg, server, index) {
36
+ const args = Array.isArray(server.args) ? server.args : [];
37
+ const previous = args[index - 1];
38
+ if (typeof previous === "string" && /^(?:--?(?:config|file|path|root|dir|directory|cwd|database|db|schema|mount|workspace)|--(?:config|file|path|root|dir|directory|cwd|database|db|schema|mount|workspace)=)$/i.test(previous)) {
39
+ return true;
40
+ }
41
+ if (arg.startsWith("./") || arg.startsWith("../"))
42
+ return true;
43
+ if (arg.includes("/") || arg.includes("\\"))
44
+ return true;
45
+ return /\.(?:[cm]?[jt]s|json|toml|ya?ml|py|sh|db|sqlite3?|wasm|bin)$/i.test(arg);
46
+ }
47
+ const OMK_START_MARKER = "# >>> omk managed hooks — do not edit manually";
48
+ const OMK_END_MARKER = "# >>> end omk managed hooks";
49
+ /**
50
+ * .omk/kimi.config.toml 의 hooks 를 ~/.kimi/config.toml 에 병합.
51
+ * 상대 경로를 절대 경로로 변환하여 어디서 실행돼도 작동하도록 함.
52
+ */
53
+ export async function mergeKimiHooks(omkConfigPath, options = {}) {
54
+ const manifest = options.manifest ?? [];
55
+ const timestamp = options.timestamp ?? new Date().toISOString();
56
+ const kimiConfigPath = getKimiConfigPath();
57
+ const omkContent = await readTextFile(omkConfigPath, "");
58
+ if (!omkContent.trim())
59
+ return false;
60
+ const root = await getProjectRootAsync();
61
+ const resolvedContent = resolveHookPaths(omkContent, root);
62
+ const hooksContent = extractHooksBlocks(resolvedContent);
63
+ if (!hooksContent)
64
+ return false;
65
+ let kimiContent = await readTextFile(kimiConfigPath, "");
66
+ const previousContent = kimiContent;
67
+ // 기존 omk 섹션 제거
68
+ const startIdx = kimiContent.indexOf(OMK_START_MARKER);
69
+ if (startIdx !== -1) {
70
+ const endIdx = kimiContent.indexOf(OMK_END_MARKER, startIdx);
71
+ if (endIdx !== -1) {
72
+ const before = kimiContent.slice(0, startIdx).trimEnd();
73
+ const after = kimiContent.slice(endIdx + OMK_END_MARKER.length).trimStart();
74
+ kimiContent = before + (before && after ? "\n\n" : before ? "\n" : "") + after;
75
+ }
76
+ }
77
+ const omkSection = `${OMK_START_MARKER}\n${hooksContent}\n${OMK_END_MARKER}\n`;
78
+ if (!kimiContent.trim()) {
79
+ kimiContent = omkSection;
80
+ }
81
+ else {
82
+ kimiContent = kimiContent.trimEnd() + "\n\n" + omkSection;
83
+ }
84
+ if (previousContent === kimiContent)
85
+ return false;
86
+ if (!isGlobalWriteAllowed()) {
87
+ if (!options.quiet) {
88
+ console.warn(`⚠️ Skipping global write to ${kimiConfigPath} (set OMK_MCP_ALLOW_WRITE_CONFIG=1 to allow)`);
89
+ }
90
+ manifest.push({
91
+ path: kimiConfigPath,
92
+ scope: "global",
93
+ action: "blocked",
94
+ previousHash: previousContent.trim() ? sha256(previousContent) : null,
95
+ newHash: sha256(kimiContent),
96
+ backupPath: null,
97
+ timestamp,
98
+ });
99
+ return true;
100
+ }
101
+ if (options.diff && previousContent.trim()) {
102
+ console.log(`--- ${kimiConfigPath}`);
103
+ console.log(`+++ ${kimiConfigPath}`);
104
+ console.log(simpleDiff(previousContent, kimiContent));
105
+ }
106
+ if (!options.dryRun) {
107
+ let backupPath = null;
108
+ if (await pathExists(kimiConfigPath)) {
109
+ const backupDir = getBackupDir(timestamp);
110
+ backupPath = await backupFile(kimiConfigPath, backupDir, relative(getUserHome(), kimiConfigPath));
111
+ }
112
+ await writeFileSafe(kimiConfigPath, kimiContent);
113
+ manifest.push({
114
+ path: kimiConfigPath,
115
+ scope: "global",
116
+ action: previousContent.trim() ? "update" : "create",
117
+ previousHash: previousContent.trim() ? sha256(previousContent) : null,
118
+ newHash: sha256(kimiContent),
119
+ backupPath,
120
+ timestamp,
121
+ });
122
+ }
123
+ else {
124
+ manifest.push({
125
+ path: kimiConfigPath,
126
+ scope: "global",
127
+ action: previousContent.trim() ? "update" : "create",
128
+ previousHash: previousContent.trim() ? sha256(previousContent) : null,
129
+ newHash: sha256(kimiContent),
130
+ backupPath: null,
131
+ timestamp,
132
+ });
133
+ }
134
+ return true;
135
+ }
136
+ function resolveHookPaths(content, root) {
137
+ return content.replace(/command\s*=\s*["'](\.omk\/hooks\/[^"']+)["']/g, (_match, p1) => {
138
+ const absPath = join(root, p1).replace(/\\/g, "/");
139
+ return `command = "${absPath}"`;
140
+ });
141
+ }
142
+ export function extractHooksBlocks(content) {
143
+ const lines = content.split("\n");
144
+ const result = [];
145
+ let foundHooks = false;
146
+ for (const line of lines) {
147
+ if (line.trim().startsWith("[[hooks]]"))
148
+ foundHooks = true;
149
+ if (foundHooks)
150
+ result.push(line);
151
+ }
152
+ return result.join("\n").trim();
153
+ }
154
+ /* ──────────────────────────────────────────────
155
+ * Global sync: .kimi/ → ~/.kimi/
156
+ * ────────────────────────────────────────────── */
157
+ /** 프로젝트의 .omk/mcp.json + .kimi/mcp.json → ~/.kimi/mcp.json 병합 */
158
+ export async function syncKimiMcpGlobal(options = {}) {
159
+ const manifest = options.manifest ?? [];
160
+ const timestamp = options.timestamp ?? new Date().toISOString();
161
+ const globalMcpPath = join(getUserHome(), ".kimi", "mcp.json");
162
+ const root = await getProjectRootAsync();
163
+ const projectConfigs = [join(root, ".omk", "mcp.json"), join(root, ".kimi", "mcp.json")];
164
+ const mergedServers = {};
165
+ let hasAny = false;
166
+ for (const p of projectConfigs) {
167
+ if (!(await pathExists(p)))
168
+ continue;
169
+ try {
170
+ const content = await readTextFile(p, "{}");
171
+ const parsed = JSON.parse(content);
172
+ if (parsed.mcpServers && typeof parsed.mcpServers === "object") {
173
+ const root = await getProjectRootAsync();
174
+ for (const [name, server] of Object.entries(parsed.mcpServers)) {
175
+ const s = server;
176
+ // args의 상대 경로를 MCP 설정 파일 기준 절대 경로로 변환.
177
+ // Shell snippets such as `set -a; source ...; exec npx ...` must
178
+ // remain untouched or MCP configs become broken Windows/WSL paths.
179
+ if (Array.isArray(s.args)) {
180
+ s.args = s.args.map((arg, index) => {
181
+ if (shouldRewriteMcpArgPath(s, arg, index)) {
182
+ return join(root, arg);
183
+ }
184
+ return arg;
185
+ });
186
+ }
187
+ mergedServers[name] = s;
188
+ }
189
+ hasAny = true;
190
+ }
191
+ }
192
+ catch {
193
+ // ignore invalid JSON
194
+ }
195
+ }
196
+ if (!hasAny)
197
+ return false;
198
+ // 기존 글로벌 mcp.json 읽기
199
+ let globalParsed = {};
200
+ let previousContent = "";
201
+ if (await pathExists(globalMcpPath)) {
202
+ try {
203
+ previousContent = await readTextFile(globalMcpPath, "{}");
204
+ globalParsed = JSON.parse(previousContent);
205
+ }
206
+ catch {
207
+ // ignore
208
+ }
209
+ }
210
+ // 병합: 글로벌 먼저, 프로젝트가 같은 키 덮어씀
211
+ const finalServers = { ...(globalParsed.mcpServers ?? {}), ...mergedServers };
212
+ const newContent = JSON.stringify({ mcpServers: finalServers }, null, 2) + "\n";
213
+ if (previousContent === newContent)
214
+ return false;
215
+ if (!isGlobalWriteAllowed()) {
216
+ if (!options.quiet) {
217
+ console.warn(`⚠️ Skipping global write to ${globalMcpPath} (set OMK_MCP_ALLOW_WRITE_CONFIG=1 to allow)`);
218
+ }
219
+ manifest.push({
220
+ path: globalMcpPath,
221
+ scope: "global",
222
+ action: "blocked",
223
+ previousHash: previousContent ? sha256(previousContent) : null,
224
+ newHash: sha256(newContent),
225
+ backupPath: null,
226
+ timestamp,
227
+ });
228
+ return true;
229
+ }
230
+ if (options.diff && previousContent) {
231
+ console.log(`--- ${globalMcpPath}`);
232
+ console.log(`+++ ${globalMcpPath}`);
233
+ console.log(simpleDiff(previousContent, newContent));
234
+ }
235
+ if (!options.dryRun) {
236
+ let backupPath = null;
237
+ if (await pathExists(globalMcpPath)) {
238
+ const backupDir = getBackupDir(timestamp);
239
+ backupPath = await backupFile(globalMcpPath, backupDir, relative(getUserHome(), globalMcpPath));
240
+ }
241
+ await writeFileSafe(globalMcpPath, newContent);
242
+ manifest.push({
243
+ path: globalMcpPath,
244
+ scope: "global",
245
+ action: previousContent ? "update" : "create",
246
+ previousHash: previousContent ? sha256(previousContent) : null,
247
+ newHash: sha256(newContent),
248
+ backupPath,
249
+ timestamp,
250
+ });
251
+ }
252
+ else {
253
+ manifest.push({
254
+ path: globalMcpPath,
255
+ scope: "global",
256
+ action: previousContent ? "update" : "create",
257
+ previousHash: previousContent ? sha256(previousContent) : null,
258
+ newHash: sha256(newContent),
259
+ backupPath: null,
260
+ timestamp,
261
+ });
262
+ }
263
+ return true;
264
+ }
265
+ /** 프로젝트의 .kimi/skills/* → ~/.kimi/skills/ 심링크 */
266
+ export async function syncKimiSkillsGlobal(options = {}) {
267
+ const root = await getProjectRootAsync();
268
+ const projectSkillsDir = join(root, ".kimi", "skills");
269
+ const globalSkillsDir = join(getUserHome(), ".kimi", "skills");
270
+ if (!(await pathExists(projectSkillsDir)))
271
+ return false;
272
+ const entries = await readdir(projectSkillsDir, { withFileTypes: true });
273
+ const skillDirs = entries.filter((e) => e.isDirectory());
274
+ if (skillDirs.length === 0)
275
+ return false;
276
+ if (options.dryRun) {
277
+ for (const dir of skillDirs) {
278
+ options.manifest?.push({
279
+ path: join(globalSkillsDir, dir.name),
280
+ scope: "global",
281
+ action: "symlink",
282
+ previousHash: null,
283
+ newHash: null,
284
+ backupPath: null,
285
+ timestamp: options.timestamp ?? new Date().toISOString(),
286
+ });
287
+ }
288
+ return true;
289
+ }
290
+ if (!isGlobalWriteAllowed()) {
291
+ if (!options.quiet) {
292
+ console.warn(`⚠️ Skipping global skills sync to ${globalSkillsDir} (set OMK_MCP_ALLOW_WRITE_CONFIG=1 to allow)`);
293
+ }
294
+ for (const dir of skillDirs) {
295
+ options.manifest?.push({
296
+ path: join(globalSkillsDir, dir.name),
297
+ scope: "global",
298
+ action: "blocked",
299
+ previousHash: null,
300
+ newHash: null,
301
+ backupPath: null,
302
+ timestamp: options.timestamp ?? new Date().toISOString(),
303
+ });
304
+ }
305
+ return true;
306
+ }
307
+ await ensureDir(globalSkillsDir);
308
+ // 깨진 심링크 정리
309
+ try {
310
+ const globalEntries = await readdir(globalSkillsDir, { withFileTypes: true });
311
+ for (const e of globalEntries) {
312
+ if (!e.isSymbolicLink())
313
+ continue;
314
+ const linkPath = join(globalSkillsDir, e.name);
315
+ try {
316
+ await access(linkPath, constants.F_OK);
317
+ }
318
+ catch {
319
+ await unlink(linkPath);
320
+ }
321
+ }
322
+ }
323
+ catch {
324
+ // ignore
325
+ }
326
+ for (const dir of skillDirs) {
327
+ const src = resolve(join(projectSkillsDir, dir.name));
328
+ const dest = join(globalSkillsDir, dir.name);
329
+ // 사용자가 직접 설치한 폴더(심링크 아님)는 건드리지 않음
330
+ try {
331
+ const destStat = await lstat(dest);
332
+ if (!destStat.isSymbolicLink())
333
+ continue;
334
+ await unlink(dest);
335
+ }
336
+ catch {
337
+ // dest 없음 → OK
338
+ }
339
+ try {
340
+ await symlink(src, dest, "dir");
341
+ }
342
+ catch {
343
+ // 심링크 실패 시 복사 fallback — dest 가 실제 사용자 디렉토리가 아닌지 재확인
344
+ try {
345
+ const st = await lstat(dest);
346
+ if (!st.isSymbolicLink())
347
+ continue; // 사용자 데이터 보호
348
+ await rm(dest, { recursive: true, force: true });
349
+ }
350
+ catch {
351
+ // dest 없음 → 복사만 진행
352
+ }
353
+ await copyDir(src, dest);
354
+ }
355
+ }
356
+ return true;
357
+ }
358
+ async function copyDir(src, dest) {
359
+ await ensureDir(dest);
360
+ const entries = await readdir(src, { withFileTypes: true });
361
+ for (const entry of entries) {
362
+ const srcPath = join(src, entry.name);
363
+ const destPath = join(dest, entry.name);
364
+ if (entry.isDirectory()) {
365
+ await copyDir(srcPath, destPath);
366
+ }
367
+ else {
368
+ await copyFile(srcPath, destPath);
369
+ }
370
+ }
371
+ }
372
+ /** local graph memory policy를 ~/.kimi/omk.memory.toml 에 동기화 */
373
+ export async function syncKimiMemoryGlobal(options = {}) {
374
+ const manifest = options.manifest ?? [];
375
+ const timestamp = options.timestamp ?? new Date().toISOString();
376
+ const memoryPath = getGlobalMemoryConfigPath();
377
+ const previousContent = await readTextFile(memoryPath, "");
378
+ const newContent = GLOBAL_MEMORY_CONFIG_TOML;
379
+ if (previousContent === newContent)
380
+ return false;
381
+ if (!isGlobalWriteAllowed()) {
382
+ if (!options.quiet) {
383
+ console.warn(`⚠️ Skipping global write to ${memoryPath} (set OMK_MCP_ALLOW_WRITE_CONFIG=1 to allow)`);
384
+ }
385
+ manifest.push({
386
+ path: memoryPath,
387
+ scope: "global",
388
+ action: "blocked",
389
+ previousHash: previousContent.trim() ? sha256(previousContent) : null,
390
+ newHash: sha256(newContent),
391
+ backupPath: null,
392
+ timestamp,
393
+ });
394
+ return true;
395
+ }
396
+ if (options.diff && previousContent.trim()) {
397
+ console.log(`--- ${memoryPath}`);
398
+ console.log(`+++ ${memoryPath}`);
399
+ console.log(simpleDiff(previousContent, newContent));
400
+ }
401
+ if (!options.dryRun) {
402
+ let backupPath = null;
403
+ if (await pathExists(memoryPath)) {
404
+ const backupDir = getBackupDir(timestamp);
405
+ backupPath = await backupFile(memoryPath, backupDir, relative(getUserHome(), memoryPath));
406
+ }
407
+ await writeFileSafe(memoryPath, newContent);
408
+ manifest.push({
409
+ path: memoryPath,
410
+ scope: "global",
411
+ action: previousContent.trim() ? "update" : "create",
412
+ previousHash: previousContent.trim() ? sha256(previousContent) : null,
413
+ newHash: sha256(newContent),
414
+ backupPath,
415
+ timestamp,
416
+ });
417
+ }
418
+ else {
419
+ manifest.push({
420
+ path: memoryPath,
421
+ scope: "global",
422
+ action: previousContent.trim() ? "update" : "create",
423
+ previousHash: previousContent.trim() ? sha256(previousContent) : null,
424
+ newHash: sha256(newContent),
425
+ backupPath: null,
426
+ timestamp,
427
+ });
428
+ }
429
+ return true;
430
+ }
431
+ /** Sync hooks + MCP + skills to ~/.kimi/ at once */
432
+ export async function syncAllKimiGlobals(options = {}) {
433
+ const manifest = options.manifest ?? [];
434
+ const steps = [];
435
+ const actions = [];
436
+ const skipped = [];
437
+ const errors = [];
438
+ async function runStep(name, fn) {
439
+ const before = manifest.length;
440
+ try {
441
+ const changed = await fn();
442
+ const stepManifest = manifest.slice(before);
443
+ const blockedEntries = stepManifest.filter((entry) => entry.action === "blocked");
444
+ const blocked = blockedEntries.length > 0;
445
+ const step = {
446
+ name,
447
+ changed: changed && !blocked,
448
+ blocked,
449
+ skipped: !changed,
450
+ manifest: stepManifest,
451
+ };
452
+ steps.push(step);
453
+ if (blocked) {
454
+ skipped.push(`${name}: global write blocked for ${blockedEntries.map((entry) => entry.path).join(", ")}`);
455
+ }
456
+ else if (changed) {
457
+ actions.push(`${name}: synced`);
458
+ }
459
+ }
460
+ catch (err) {
461
+ const message = err instanceof Error ? err.message : String(err);
462
+ if (!options.quiet) {
463
+ console.warn(`⚠️ ${name} global sync failed:`, message);
464
+ }
465
+ errors.push(`${name}: ${message}`);
466
+ steps.push({
467
+ name,
468
+ changed: false,
469
+ blocked: false,
470
+ skipped: true,
471
+ error: message,
472
+ manifest: manifest.slice(before),
473
+ });
474
+ }
475
+ }
476
+ const configFile = getOmkPath("kimi.config.toml");
477
+ if (await pathExists(configFile)) {
478
+ await runStep("hooks", () => mergeKimiHooks(configFile, { ...options, manifest }));
479
+ }
480
+ else {
481
+ steps.push({ name: "hooks", changed: false, blocked: false, skipped: true, manifest: [] });
482
+ }
483
+ await runStep("mcp", () => syncKimiMcpGlobal({ ...options, manifest }));
484
+ await runStep("skills", () => syncKimiSkillsGlobal({ ...options, manifest }));
485
+ await runStep("memory", () => syncKimiMemoryGlobal({ ...options, manifest }));
486
+ return {
487
+ changed: actions.length > 0,
488
+ blocked: steps.some((step) => step.blocked),
489
+ steps,
490
+ actions,
491
+ skipped,
492
+ errors,
493
+ manifest,
494
+ };
495
+ }
@@ -0,0 +1,2 @@
1
+ /** .omk/config.toml 에서 안전한 logo_image 경로 읽기 (상대경로는 프로젝트 루트 기준) */
2
+ export declare function getOmkLogoImagePath(): Promise<string | null>;
@@ -0,0 +1,78 @@
1
+ import { lstat, readFile } from "fs/promises";
2
+ import { extname, isAbsolute, relative, resolve } from "path";
3
+ import { readTextFile } from "./core.js";
4
+ import { getOmkPath, getProjectRootAsync } from "./paths.js";
5
+ const MAX_LOGO_IMAGE_BYTES = 4 * 1024 * 1024;
6
+ const ALLOWED_LOGO_EXTENSIONS = new Set([".png", ".jpg", ".jpeg", ".gif", ".webp"]);
7
+ /** .omk/config.toml 에서 안전한 logo_image 경로 읽기 (상대경로는 프로젝트 루트 기준) */
8
+ export async function getOmkLogoImagePath() {
9
+ const configPath = getOmkPath("config.toml");
10
+ try {
11
+ const content = await readTextFile(configPath, "");
12
+ const match = content.match(/^\s*logo_image\s*=\s*["']([^"']+)["']/m);
13
+ if (!match)
14
+ return null;
15
+ const p = match[1].trim();
16
+ const root = await getProjectRootAsync();
17
+ const absoluteInput = isAbsolute(p) || p.startsWith("\\") || /^[A-Za-z]:/.test(p);
18
+ if (absoluteInput && !isTrustedLocalFlag(process.env.OMK_TRUST_ABSOLUTE_LOGO_PATH)) {
19
+ return null;
20
+ }
21
+ const candidate = absoluteInput ? resolve(p) : resolve(root, p);
22
+ if (!absoluteInput && isOutsideRoot(root, candidate)) {
23
+ return null;
24
+ }
25
+ return await isSafeLogoImage(candidate) ? candidate : null;
26
+ }
27
+ catch {
28
+ return null;
29
+ }
30
+ }
31
+ function isTrustedLocalFlag(value) {
32
+ return value === "1" || value === "true" || value === "yes";
33
+ }
34
+ function isOutsideRoot(root, candidate) {
35
+ const rel = relative(root, candidate);
36
+ return rel === ".." || rel.startsWith(`..${"/"}`) || rel.startsWith(`..${"\\"}`) || isAbsolute(rel);
37
+ }
38
+ async function isSafeLogoImage(path) {
39
+ const ext = extname(path).toLowerCase();
40
+ if (!ALLOWED_LOGO_EXTENSIONS.has(ext))
41
+ return false;
42
+ const info = await lstat(path);
43
+ if (!info.isFile() || info.isSymbolicLink() || info.size <= 0 || info.size > MAX_LOGO_IMAGE_BYTES) {
44
+ return false;
45
+ }
46
+ const bytes = await readFile(path);
47
+ return hasAllowedImageMagic(bytes);
48
+ }
49
+ function hasAllowedImageMagic(bytes) {
50
+ return isPng(bytes) || isJpeg(bytes) || isGif(bytes) || isWebp(bytes);
51
+ }
52
+ function isPng(bytes) {
53
+ return bytes.length >= 8
54
+ && bytes[0] === 0x89
55
+ && bytes[1] === 0x50
56
+ && bytes[2] === 0x4e
57
+ && bytes[3] === 0x47
58
+ && bytes[4] === 0x0d
59
+ && bytes[5] === 0x0a
60
+ && bytes[6] === 0x1a
61
+ && bytes[7] === 0x0a;
62
+ }
63
+ function isJpeg(bytes) {
64
+ return bytes.length >= 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff;
65
+ }
66
+ function isGif(bytes) {
67
+ if (bytes.length < 6)
68
+ return false;
69
+ const header = String.fromCharCode(...bytes.slice(0, 6));
70
+ return header === "GIF87a" || header === "GIF89a";
71
+ }
72
+ function isWebp(bytes) {
73
+ if (bytes.length < 12)
74
+ return false;
75
+ const riff = String.fromCharCode(...bytes.slice(0, 4));
76
+ const webp = String.fromCharCode(...bytes.slice(8, 12));
77
+ return riff === "RIFF" && webp === "WEBP";
78
+ }
@@ -0,0 +1,6 @@
1
+ import { SyncManifestEntry } from "../sync-manifest.js";
2
+ export declare function getManifestPath(): string;
3
+ export declare function getBackupDir(timestamp: string): string;
4
+ export declare function readManifest(): Promise<SyncManifestEntry[]>;
5
+ export declare function writeManifest(entries: SyncManifestEntry[]): Promise<void>;
6
+ export declare function backupFile(sourcePath: string, backupDir: string, relativePath: string): Promise<string>;
@@ -0,0 +1,40 @@
1
+ import { readFile, writeFile, copyFile } from "fs/promises";
2
+ import { dirname, join } from "path";
3
+ import { ensureDir, pathExists } from "./core.js";
4
+ import { getOmkPath } from "./paths.js";
5
+ export function getManifestPath() {
6
+ return getOmkPath("sync-manifest.json");
7
+ }
8
+ export function getBackupDir(timestamp) {
9
+ return getOmkPath(join("sync-backups", sanitizeBackupTimestamp(timestamp)));
10
+ }
11
+ function sanitizeBackupTimestamp(timestamp) {
12
+ const sanitized = timestamp.replace(/[^a-zA-Z0-9._-]/g, "-").replace(/-+/g, "-");
13
+ return sanitized.replace(/^-|-$/g, "") || "backup";
14
+ }
15
+ export async function readManifest() {
16
+ const path = getManifestPath();
17
+ if (!(await pathExists(path)))
18
+ return [];
19
+ try {
20
+ const content = await readFile(path, "utf-8");
21
+ const parsed = JSON.parse(content);
22
+ if (Array.isArray(parsed))
23
+ return parsed;
24
+ }
25
+ catch {
26
+ // ignore invalid manifest
27
+ }
28
+ return [];
29
+ }
30
+ export async function writeManifest(entries) {
31
+ const path = getManifestPath();
32
+ await ensureDir(dirname(path));
33
+ await writeFile(path, JSON.stringify(entries, null, 2) + "\n", "utf-8");
34
+ }
35
+ export async function backupFile(sourcePath, backupDir, relativePath) {
36
+ const dest = join(backupDir, relativePath);
37
+ await ensureDir(dirname(dest));
38
+ await copyFile(sourcePath, dest);
39
+ return dest;
40
+ }
@@ -0,0 +1,15 @@
1
+ import type { OmkRuntimeScope } from "../resource-profile.js";
2
+ export interface RuntimeMcpPruneDiagnostic {
3
+ name: string;
4
+ kind: string;
5
+ message: string;
6
+ }
7
+ export interface RuntimeMcpNormalization {
8
+ name: string;
9
+ kind: string;
10
+ message: string;
11
+ }
12
+ /** Canonical OMK MCP config collection with project `.kimi/mcp.json` compatibility. */
13
+ export declare function collectMcpConfigs(scope?: OmkRuntimeScope): Promise<string[]>;
14
+ export declare const STALE_PACKAGE_NAMES: Record<string, string>;
15
+ export declare function diagnoseRuntimeMcpServer(name: string, server: unknown): Promise<RuntimeMcpPruneDiagnostic[]>;