@plumpslabs/kuma 2.1.8 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +309 -20
- package/dist/agentDetector-AHKFSZGQ.js +186 -0
- package/dist/chunk-7Q3YUJSM.js +161 -0
- package/dist/{chunk-2BFTJMBP.js → chunk-EM6IK2TB.js} +73 -225
- package/dist/chunk-QPVPFUCQ.js +612 -0
- package/dist/index.js +1096 -855
- package/dist/{init-AMRMKI4X.js → init-WZADRE7F.js} +2 -1
- package/dist/sessionMemory-LGQFVHHE.js +14 -0
- package/dist/skillGenerator-F6YO4H5D.js +278 -0
- package/package.json +5 -2
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// src/utils/pathValidator.ts
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
function validateFilePath(filePath, projectRoot) {
|
|
5
|
+
try {
|
|
6
|
+
const root = projectRoot ?? getProjectRoot();
|
|
7
|
+
const resolvedRoot = path.resolve(root);
|
|
8
|
+
const normalizedRoot = path.normalize(resolvedRoot).toLowerCase();
|
|
9
|
+
let resolvedPath;
|
|
10
|
+
if (!path.isAbsolute(filePath)) {
|
|
11
|
+
const cwdPath = path.resolve(process.cwd(), filePath);
|
|
12
|
+
const normalizedCwdPath = path.normalize(cwdPath).toLowerCase();
|
|
13
|
+
if (fs.existsSync(cwdPath) && normalizedCwdPath.startsWith(normalizedRoot)) {
|
|
14
|
+
resolvedPath = cwdPath;
|
|
15
|
+
} else {
|
|
16
|
+
resolvedPath = path.resolve(resolvedRoot, filePath);
|
|
17
|
+
}
|
|
18
|
+
} else {
|
|
19
|
+
resolvedPath = path.resolve(resolvedRoot, filePath);
|
|
20
|
+
}
|
|
21
|
+
const normalizedPath = path.normalize(resolvedPath).toLowerCase();
|
|
22
|
+
if (!normalizedPath.startsWith(normalizedRoot)) {
|
|
23
|
+
return {
|
|
24
|
+
valid: false,
|
|
25
|
+
error: new Error(
|
|
26
|
+
`PATH_TRAVERSAL: Access denied. Path "${filePath}" resolves outside project root "${resolvedRoot}".`
|
|
27
|
+
)
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
if (normalizedPath.includes("..")) {
|
|
31
|
+
return {
|
|
32
|
+
valid: false,
|
|
33
|
+
error: new Error(
|
|
34
|
+
`PATH_TRAVERSAL: Path traversal detected in "${filePath}". Relative paths with ".." are not allowed.`
|
|
35
|
+
)
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const blockedPatterns = [
|
|
39
|
+
"/etc/",
|
|
40
|
+
"/sys/",
|
|
41
|
+
"/proc/",
|
|
42
|
+
"/dev/",
|
|
43
|
+
"/boot/",
|
|
44
|
+
"/usr/",
|
|
45
|
+
"C:\\Windows",
|
|
46
|
+
"C:\\Program Files",
|
|
47
|
+
"C:\\Program Files (x86)",
|
|
48
|
+
"C:\\System32",
|
|
49
|
+
"~/.ssh",
|
|
50
|
+
"/root/"
|
|
51
|
+
];
|
|
52
|
+
for (const pattern of blockedPatterns) {
|
|
53
|
+
if (normalizedPath.includes(pattern.toLowerCase())) {
|
|
54
|
+
return {
|
|
55
|
+
valid: false,
|
|
56
|
+
error: new Error(
|
|
57
|
+
`PATH_TRAVERSAL: Access to system directory "${pattern}" is blocked.`
|
|
58
|
+
)
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
if (fs.existsSync(resolvedPath)) {
|
|
64
|
+
const realPath = fs.realpathSync(resolvedPath);
|
|
65
|
+
const normalizedRealPath = path.normalize(realPath).toLowerCase();
|
|
66
|
+
if (!normalizedRealPath.startsWith(normalizedRoot)) {
|
|
67
|
+
return {
|
|
68
|
+
valid: false,
|
|
69
|
+
error: new Error(
|
|
70
|
+
`PATH_TRAVERSAL: Symlink escape detected. Path "${filePath}" resolves to "${realPath}" which is outside project root "${resolvedRoot}".`
|
|
71
|
+
)
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
} catch {
|
|
76
|
+
}
|
|
77
|
+
if (normalizedPath.includes("node_modules")) {
|
|
78
|
+
return {
|
|
79
|
+
valid: true,
|
|
80
|
+
resolvedPath
|
|
81
|
+
// Warning will be handled by caller
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return { valid: true, resolvedPath };
|
|
85
|
+
} catch (err) {
|
|
86
|
+
return {
|
|
87
|
+
valid: false,
|
|
88
|
+
error: err instanceof Error ? err : new Error(`Path validation error: ${String(err)}`)
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function validateFileExtension(filePath) {
|
|
93
|
+
const allowedExtensions = [
|
|
94
|
+
".ts",
|
|
95
|
+
".tsx",
|
|
96
|
+
".js",
|
|
97
|
+
".jsx",
|
|
98
|
+
".mjs",
|
|
99
|
+
".cjs",
|
|
100
|
+
".json",
|
|
101
|
+
".md",
|
|
102
|
+
".css",
|
|
103
|
+
".html",
|
|
104
|
+
".htm",
|
|
105
|
+
".env",
|
|
106
|
+
".env.example",
|
|
107
|
+
".env.local",
|
|
108
|
+
".yml",
|
|
109
|
+
".yaml",
|
|
110
|
+
".toml",
|
|
111
|
+
".svg",
|
|
112
|
+
".png",
|
|
113
|
+
".jpg",
|
|
114
|
+
".gif",
|
|
115
|
+
".sh",
|
|
116
|
+
".bat",
|
|
117
|
+
".ps1",
|
|
118
|
+
".sql",
|
|
119
|
+
".graphql",
|
|
120
|
+
".gql",
|
|
121
|
+
".vue",
|
|
122
|
+
".svelte",
|
|
123
|
+
".astro",
|
|
124
|
+
".prisma",
|
|
125
|
+
".proto",
|
|
126
|
+
".txt",
|
|
127
|
+
".log"
|
|
128
|
+
];
|
|
129
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
130
|
+
return allowedExtensions.includes(ext);
|
|
131
|
+
}
|
|
132
|
+
function getProjectRoot() {
|
|
133
|
+
return process.env.AGENT_PROJECT_ROOT ?? process.cwd();
|
|
134
|
+
}
|
|
135
|
+
function getBackupPath(filePath, timestamp) {
|
|
136
|
+
const ts = timestamp ?? Date.now();
|
|
137
|
+
const root = getProjectRoot();
|
|
138
|
+
const relativePath = path.relative(root, filePath);
|
|
139
|
+
return path.join(root, ".kuma", "backups", String(ts), relativePath);
|
|
140
|
+
}
|
|
141
|
+
function ensureBackupDir() {
|
|
142
|
+
const root = getProjectRoot();
|
|
143
|
+
const backupDir = path.join(root, ".kuma", "backups");
|
|
144
|
+
if (!fs.existsSync(backupDir)) {
|
|
145
|
+
fs.mkdirSync(backupDir, { recursive: true });
|
|
146
|
+
}
|
|
147
|
+
return backupDir;
|
|
148
|
+
}
|
|
149
|
+
function getKumaDir() {
|
|
150
|
+
const root = getProjectRoot();
|
|
151
|
+
return path.join(root, ".kuma");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export {
|
|
155
|
+
validateFilePath,
|
|
156
|
+
validateFileExtension,
|
|
157
|
+
getProjectRoot,
|
|
158
|
+
getBackupPath,
|
|
159
|
+
ensureBackupDir,
|
|
160
|
+
getKumaDir
|
|
161
|
+
};
|
|
@@ -1,157 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
// src/utils/pathValidator.ts
|
|
6
|
-
import path from "path";
|
|
7
|
-
import fs from "fs";
|
|
8
|
-
function validateFilePath(filePath, projectRoot) {
|
|
9
|
-
try {
|
|
10
|
-
const root = projectRoot ?? getProjectRoot();
|
|
11
|
-
const resolvedRoot = path.resolve(root);
|
|
12
|
-
const normalizedRoot = path.normalize(resolvedRoot).toLowerCase();
|
|
13
|
-
let resolvedPath;
|
|
14
|
-
if (!path.isAbsolute(filePath)) {
|
|
15
|
-
const cwdPath = path.resolve(process.cwd(), filePath);
|
|
16
|
-
const normalizedCwdPath = path.normalize(cwdPath).toLowerCase();
|
|
17
|
-
if (fs.existsSync(cwdPath) && normalizedCwdPath.startsWith(normalizedRoot)) {
|
|
18
|
-
resolvedPath = cwdPath;
|
|
19
|
-
} else {
|
|
20
|
-
resolvedPath = path.resolve(resolvedRoot, filePath);
|
|
21
|
-
}
|
|
22
|
-
} else {
|
|
23
|
-
resolvedPath = path.resolve(resolvedRoot, filePath);
|
|
24
|
-
}
|
|
25
|
-
const normalizedPath = path.normalize(resolvedPath).toLowerCase();
|
|
26
|
-
if (!normalizedPath.startsWith(normalizedRoot)) {
|
|
27
|
-
return {
|
|
28
|
-
valid: false,
|
|
29
|
-
error: new Error(
|
|
30
|
-
`PATH_TRAVERSAL: Access denied. Path "${filePath}" resolves outside project root "${resolvedRoot}".`
|
|
31
|
-
)
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
if (normalizedPath.includes("..")) {
|
|
35
|
-
return {
|
|
36
|
-
valid: false,
|
|
37
|
-
error: new Error(
|
|
38
|
-
`PATH_TRAVERSAL: Path traversal detected in "${filePath}". Relative paths with ".." are not allowed.`
|
|
39
|
-
)
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
const blockedPatterns = [
|
|
43
|
-
"/etc/",
|
|
44
|
-
"/sys/",
|
|
45
|
-
"/proc/",
|
|
46
|
-
"/dev/",
|
|
47
|
-
"/boot/",
|
|
48
|
-
"/usr/",
|
|
49
|
-
"C:\\Windows",
|
|
50
|
-
"C:\\Program Files",
|
|
51
|
-
"C:\\Program Files (x86)",
|
|
52
|
-
"C:\\System32",
|
|
53
|
-
"~/.ssh",
|
|
54
|
-
"/root/"
|
|
55
|
-
];
|
|
56
|
-
for (const pattern of blockedPatterns) {
|
|
57
|
-
if (normalizedPath.includes(pattern.toLowerCase())) {
|
|
58
|
-
return {
|
|
59
|
-
valid: false,
|
|
60
|
-
error: new Error(
|
|
61
|
-
`PATH_TRAVERSAL: Access to system directory "${pattern}" is blocked.`
|
|
62
|
-
)
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
try {
|
|
67
|
-
if (fs.existsSync(resolvedPath)) {
|
|
68
|
-
const realPath = fs.realpathSync(resolvedPath);
|
|
69
|
-
const normalizedRealPath = path.normalize(realPath).toLowerCase();
|
|
70
|
-
if (!normalizedRealPath.startsWith(normalizedRoot)) {
|
|
71
|
-
return {
|
|
72
|
-
valid: false,
|
|
73
|
-
error: new Error(
|
|
74
|
-
`PATH_TRAVERSAL: Symlink escape detected. Path "${filePath}" resolves to "${realPath}" which is outside project root "${resolvedRoot}".`
|
|
75
|
-
)
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
} catch {
|
|
80
|
-
}
|
|
81
|
-
if (normalizedPath.includes("node_modules")) {
|
|
82
|
-
return {
|
|
83
|
-
valid: true,
|
|
84
|
-
resolvedPath
|
|
85
|
-
// Warning will be handled by caller
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
return { valid: true, resolvedPath };
|
|
89
|
-
} catch (err) {
|
|
90
|
-
return {
|
|
91
|
-
valid: false,
|
|
92
|
-
error: err instanceof Error ? err : new Error(`Path validation error: ${String(err)}`)
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
function validateFileExtension(filePath) {
|
|
97
|
-
const allowedExtensions = [
|
|
98
|
-
".ts",
|
|
99
|
-
".tsx",
|
|
100
|
-
".js",
|
|
101
|
-
".jsx",
|
|
102
|
-
".mjs",
|
|
103
|
-
".cjs",
|
|
104
|
-
".json",
|
|
105
|
-
".md",
|
|
106
|
-
".css",
|
|
107
|
-
".html",
|
|
108
|
-
".htm",
|
|
109
|
-
".env",
|
|
110
|
-
".env.example",
|
|
111
|
-
".env.local",
|
|
112
|
-
".yml",
|
|
113
|
-
".yaml",
|
|
114
|
-
".toml",
|
|
115
|
-
".svg",
|
|
116
|
-
".png",
|
|
117
|
-
".jpg",
|
|
118
|
-
".gif",
|
|
119
|
-
".sh",
|
|
120
|
-
".bat",
|
|
121
|
-
".ps1",
|
|
122
|
-
".sql",
|
|
123
|
-
".graphql",
|
|
124
|
-
".gql",
|
|
125
|
-
".vue",
|
|
126
|
-
".svelte",
|
|
127
|
-
".astro",
|
|
128
|
-
".prisma",
|
|
129
|
-
".proto",
|
|
130
|
-
".txt",
|
|
131
|
-
".log"
|
|
132
|
-
];
|
|
133
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
134
|
-
return allowedExtensions.includes(ext);
|
|
135
|
-
}
|
|
136
|
-
function getProjectRoot() {
|
|
137
|
-
return process.env.AGENT_PROJECT_ROOT ?? process.cwd();
|
|
138
|
-
}
|
|
139
|
-
function getBackupPath(filePath, timestamp) {
|
|
140
|
-
const ts = timestamp ?? Date.now();
|
|
141
|
-
const root = getProjectRoot();
|
|
142
|
-
const relativePath = path.relative(root, filePath);
|
|
143
|
-
return path.join(root, ".agent-backups", String(ts), relativePath);
|
|
144
|
-
}
|
|
145
|
-
function ensureBackupDir() {
|
|
146
|
-
const root = getProjectRoot();
|
|
147
|
-
const backupDir = path.join(root, ".agent-backups");
|
|
148
|
-
if (!fs.existsSync(backupDir)) {
|
|
149
|
-
fs.mkdirSync(backupDir, { recursive: true });
|
|
150
|
-
}
|
|
151
|
-
return backupDir;
|
|
152
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
getProjectRoot
|
|
3
|
+
} from "./chunk-7Q3YUJSM.js";
|
|
153
4
|
|
|
154
5
|
// src/cli/init.ts
|
|
6
|
+
import fs from "fs";
|
|
7
|
+
import path from "path";
|
|
155
8
|
var ALL_CONFIG_TYPES = [
|
|
156
9
|
"claude",
|
|
157
10
|
"cursor",
|
|
@@ -467,21 +320,21 @@ var APPEND_SEPARATOR = "\n\n---\n_Generated by Kuma MCP - https://github.com/plu
|
|
|
467
320
|
function handleOpencodeSecondary(_root, _results) {
|
|
468
321
|
}
|
|
469
322
|
function handleCodexSecondary(root, results) {
|
|
470
|
-
const tomlPath =
|
|
323
|
+
const tomlPath = path.resolve(root, ".codex/config.toml");
|
|
471
324
|
if (results.some((r) => r.filePath === ".codex/config.toml")) return;
|
|
472
325
|
try {
|
|
473
|
-
const dir =
|
|
474
|
-
if (!
|
|
475
|
-
if (
|
|
476
|
-
const existingContent =
|
|
326
|
+
const dir = path.dirname(tomlPath);
|
|
327
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
328
|
+
if (fs.existsSync(tomlPath)) {
|
|
329
|
+
const existingContent = fs.readFileSync(tomlPath, "utf-8");
|
|
477
330
|
if (existingContent.includes("kuma")) {
|
|
478
331
|
results.push({ type: "codex", filePath: ".codex/config.toml", action: "skipped" });
|
|
479
332
|
return;
|
|
480
333
|
}
|
|
481
|
-
|
|
334
|
+
fs.writeFileSync(tomlPath, existingContent.trimEnd() + "\n\n" + codexConfigTomlTemplate(), "utf-8");
|
|
482
335
|
results.push({ type: "codex", filePath: ".codex/config.toml", action: "appended" });
|
|
483
336
|
} else {
|
|
484
|
-
|
|
337
|
+
fs.writeFileSync(tomlPath, codexConfigTomlTemplate(), "utf-8");
|
|
485
338
|
results.push({ type: "codex", filePath: ".codex/config.toml", action: "created" });
|
|
486
339
|
}
|
|
487
340
|
} catch (err) {
|
|
@@ -494,11 +347,11 @@ function handleCodexSecondary(root, results) {
|
|
|
494
347
|
}
|
|
495
348
|
}
|
|
496
349
|
function handleQwenSecondary(root, results) {
|
|
497
|
-
const settingsPath =
|
|
350
|
+
const settingsPath = path.resolve(root, "settings.json");
|
|
498
351
|
if (results.some((r) => r.filePath === "settings.json")) return;
|
|
499
352
|
try {
|
|
500
|
-
if (
|
|
501
|
-
const existingContent =
|
|
353
|
+
if (fs.existsSync(settingsPath)) {
|
|
354
|
+
const existingContent = fs.readFileSync(settingsPath, "utf-8");
|
|
502
355
|
if (existingContent.includes("kuma")) {
|
|
503
356
|
if (!existingContent.includes("_Generated by Kuma MCP_")) {
|
|
504
357
|
try {
|
|
@@ -506,7 +359,7 @@ function handleQwenSecondary(root, results) {
|
|
|
506
359
|
parsed.mcpServers = parsed.mcpServers || {};
|
|
507
360
|
if (!parsed.mcpServers.kuma) {
|
|
508
361
|
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
509
|
-
|
|
362
|
+
fs.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
510
363
|
results.push({ type: "qwen", filePath: "settings.json", action: "appended" });
|
|
511
364
|
return;
|
|
512
365
|
}
|
|
@@ -521,13 +374,13 @@ function handleQwenSecondary(root, results) {
|
|
|
521
374
|
parsed.mcpServers = parsed.mcpServers || {};
|
|
522
375
|
if (!parsed.mcpServers.kuma) {
|
|
523
376
|
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
524
|
-
|
|
377
|
+
fs.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
525
378
|
results.push({ type: "qwen", filePath: "settings.json", action: "appended" });
|
|
526
379
|
}
|
|
527
380
|
} catch {
|
|
528
381
|
}
|
|
529
382
|
} else {
|
|
530
|
-
|
|
383
|
+
fs.writeFileSync(settingsPath, qwenSettingsTemplate(), "utf-8");
|
|
531
384
|
results.push({ type: "qwen", filePath: "settings.json", action: "created" });
|
|
532
385
|
}
|
|
533
386
|
} catch (err) {
|
|
@@ -563,18 +416,18 @@ function getCombinedAgentsMd(selectedTypes) {
|
|
|
563
416
|
return sections.join("\n\n---\n\n");
|
|
564
417
|
}
|
|
565
418
|
function handleAntigravityMcpConfig(root, results) {
|
|
566
|
-
const mcpPath =
|
|
419
|
+
const mcpPath = path.resolve(root, ".agents/mcp_config.json");
|
|
567
420
|
if (results.some((r) => r.filePath === ".agents/mcp_config.json")) return;
|
|
568
421
|
try {
|
|
569
|
-
const mcpDir =
|
|
570
|
-
if (
|
|
571
|
-
const existingContent =
|
|
422
|
+
const mcpDir = path.dirname(mcpPath);
|
|
423
|
+
if (fs.existsSync(mcpPath)) {
|
|
424
|
+
const existingContent = fs.readFileSync(mcpPath, "utf-8");
|
|
572
425
|
if (existingContent.includes("kuma")) {
|
|
573
426
|
if (!existingContent.includes("_Generated by Kuma MCP_")) {
|
|
574
427
|
const trimmed = existingContent.trimEnd();
|
|
575
428
|
if (trimmed.endsWith("}")) {
|
|
576
429
|
const updated = trimmed.slice(0, -1).trimEnd() + ',\n "_kuma_note": "Kuma MCP - Generated by kuma init"\n}\n';
|
|
577
|
-
|
|
430
|
+
fs.writeFileSync(mcpPath, updated, "utf-8");
|
|
578
431
|
results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "appended" });
|
|
579
432
|
}
|
|
580
433
|
}
|
|
@@ -583,11 +436,11 @@ function handleAntigravityMcpConfig(root, results) {
|
|
|
583
436
|
const parsed = JSON.parse(existingContent);
|
|
584
437
|
parsed.mcpServers = parsed.mcpServers || {};
|
|
585
438
|
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
586
|
-
|
|
439
|
+
fs.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
587
440
|
results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "appended" });
|
|
588
441
|
} else {
|
|
589
|
-
if (!
|
|
590
|
-
|
|
442
|
+
if (!fs.existsSync(mcpDir)) fs.mkdirSync(mcpDir, { recursive: true });
|
|
443
|
+
fs.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
|
|
591
444
|
results.push({ type: "antigravity", filePath: ".agents/mcp_config.json", action: "created" });
|
|
592
445
|
}
|
|
593
446
|
} catch (err) {
|
|
@@ -600,18 +453,18 @@ function handleAntigravityMcpConfig(root, results) {
|
|
|
600
453
|
}
|
|
601
454
|
}
|
|
602
455
|
function handleAiderSecondary(root, results) {
|
|
603
|
-
const ymlPath =
|
|
456
|
+
const ymlPath = path.resolve(root, ".aider.conf.yml");
|
|
604
457
|
if (results.some((r) => r.filePath === ".aider.conf.yml")) return;
|
|
605
458
|
try {
|
|
606
459
|
const conventionsRef = "read: CONVENTIONS.md";
|
|
607
|
-
if (
|
|
608
|
-
const existingContent =
|
|
460
|
+
if (fs.existsSync(ymlPath)) {
|
|
461
|
+
const existingContent = fs.readFileSync(ymlPath, "utf-8");
|
|
609
462
|
if (existingContent.includes("CONVENTIONS.md") || existingContent.includes("kuma")) {
|
|
610
463
|
results.push({ type: "aider", filePath: ".aider.conf.yml", action: "skipped" });
|
|
611
464
|
return;
|
|
612
465
|
}
|
|
613
466
|
const newContent = existingContent.trimEnd() + "\n\n# Kuma MCP conventions\n" + conventionsRef + "\n";
|
|
614
|
-
|
|
467
|
+
fs.writeFileSync(ymlPath, newContent, "utf-8");
|
|
615
468
|
results.push({ type: "aider", filePath: ".aider.conf.yml", action: "appended" });
|
|
616
469
|
} else {
|
|
617
470
|
const content = [
|
|
@@ -621,7 +474,7 @@ function handleAiderSecondary(root, results) {
|
|
|
621
474
|
conventionsRef,
|
|
622
475
|
""
|
|
623
476
|
].join("\n");
|
|
624
|
-
|
|
477
|
+
fs.writeFileSync(ymlPath, content, "utf-8");
|
|
625
478
|
results.push({ type: "aider", filePath: ".aider.conf.yml", action: "created" });
|
|
626
479
|
}
|
|
627
480
|
} catch (err) {
|
|
@@ -634,10 +487,10 @@ function handleAiderSecondary(root, results) {
|
|
|
634
487
|
}
|
|
635
488
|
}
|
|
636
489
|
function handleCopilotSecondary(root, results) {
|
|
637
|
-
const skillPath =
|
|
490
|
+
const skillPath = path.resolve(root, ".github/skills/kuma/SKILL.md");
|
|
638
491
|
if (results.some((r) => r.filePath === ".github/skills/kuma/SKILL.md")) return;
|
|
639
492
|
try {
|
|
640
|
-
const dir =
|
|
493
|
+
const dir = path.dirname(skillPath);
|
|
641
494
|
const content = [
|
|
642
495
|
"---",
|
|
643
496
|
"name: kuma-mcp",
|
|
@@ -648,17 +501,17 @@ function handleCopilotSecondary(root, results) {
|
|
|
648
501
|
"",
|
|
649
502
|
"\u{1F4D6} Read `.kuma/init.md` for detailed rules."
|
|
650
503
|
].join("\n");
|
|
651
|
-
if (
|
|
652
|
-
const existingContent =
|
|
504
|
+
if (fs.existsSync(skillPath)) {
|
|
505
|
+
const existingContent = fs.readFileSync(skillPath, "utf-8");
|
|
653
506
|
if (existingContent.includes("kuma")) {
|
|
654
507
|
results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "skipped" });
|
|
655
508
|
return;
|
|
656
509
|
}
|
|
657
|
-
|
|
510
|
+
fs.writeFileSync(skillPath, existingContent.trimEnd() + "\n\n" + content, "utf-8");
|
|
658
511
|
results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "appended" });
|
|
659
512
|
} else {
|
|
660
|
-
if (!
|
|
661
|
-
|
|
513
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
514
|
+
fs.writeFileSync(skillPath, content, "utf-8");
|
|
662
515
|
results.push({ type: "copilot", filePath: ".github/skills/kuma/SKILL.md", action: "created" });
|
|
663
516
|
}
|
|
664
517
|
} catch (err) {
|
|
@@ -671,21 +524,21 @@ function handleCopilotSecondary(root, results) {
|
|
|
671
524
|
}
|
|
672
525
|
}
|
|
673
526
|
function handleOpenclawSecondary(root, results) {
|
|
674
|
-
const mcpPath =
|
|
527
|
+
const mcpPath = path.resolve(root, ".agents/mcp_config.json");
|
|
675
528
|
if (results.some((r) => r.filePath === ".agents/mcp_config.json")) return;
|
|
676
529
|
try {
|
|
677
|
-
const dir =
|
|
678
|
-
if (
|
|
679
|
-
const existingContent =
|
|
530
|
+
const dir = path.dirname(mcpPath);
|
|
531
|
+
if (fs.existsSync(mcpPath)) {
|
|
532
|
+
const existingContent = fs.readFileSync(mcpPath, "utf-8");
|
|
680
533
|
if (existingContent.includes("kuma")) return;
|
|
681
534
|
const parsed = JSON.parse(existingContent);
|
|
682
535
|
parsed.mcpServers = parsed.mcpServers || {};
|
|
683
536
|
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
684
|
-
|
|
537
|
+
fs.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
685
538
|
results.push({ type: "openclaw", filePath: ".agents/mcp_config.json", action: "appended" });
|
|
686
539
|
} else {
|
|
687
|
-
if (!
|
|
688
|
-
|
|
540
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
541
|
+
fs.writeFileSync(mcpPath, antigravityMcpConfigTemplate(), "utf-8");
|
|
689
542
|
results.push({ type: "openclaw", filePath: ".agents/mcp_config.json", action: "created" });
|
|
690
543
|
}
|
|
691
544
|
} catch (err) {
|
|
@@ -698,20 +551,20 @@ function handleOpenclawSecondary(root, results) {
|
|
|
698
551
|
}
|
|
699
552
|
}
|
|
700
553
|
function handleInitMdGeneration(root, results) {
|
|
701
|
-
const initMdPath =
|
|
554
|
+
const initMdPath = path.resolve(root, ".kuma/init.md");
|
|
702
555
|
try {
|
|
703
|
-
const kumaDir =
|
|
704
|
-
if (!
|
|
705
|
-
if (
|
|
706
|
-
const existing =
|
|
556
|
+
const kumaDir = path.dirname(initMdPath);
|
|
557
|
+
if (!fs.existsSync(kumaDir)) fs.mkdirSync(kumaDir, { recursive: true });
|
|
558
|
+
if (fs.existsSync(initMdPath)) {
|
|
559
|
+
const existing = fs.readFileSync(initMdPath, "utf-8");
|
|
707
560
|
if (existing.includes("_Generated by Kuma MCP_")) {
|
|
708
561
|
results.push({ type: "claude", filePath: ".kuma/init.md", action: "skipped" });
|
|
709
562
|
return;
|
|
710
563
|
}
|
|
711
|
-
|
|
564
|
+
fs.writeFileSync(initMdPath, existing.trimEnd() + "\n\n" + generateInitMdContent(), "utf-8");
|
|
712
565
|
results.push({ type: "claude", filePath: ".kuma/init.md", action: "appended" });
|
|
713
566
|
} else {
|
|
714
|
-
|
|
567
|
+
fs.writeFileSync(initMdPath, generateInitMdContent(), "utf-8");
|
|
715
568
|
results.push({ type: "claude", filePath: ".kuma/init.md", action: "created" });
|
|
716
569
|
}
|
|
717
570
|
} catch (err) {
|
|
@@ -724,20 +577,20 @@ function handleInitMdGeneration(root, results) {
|
|
|
724
577
|
}
|
|
725
578
|
}
|
|
726
579
|
function handleCodewhaleSecondary(root, results) {
|
|
727
|
-
const mcpPath =
|
|
580
|
+
const mcpPath = path.resolve(root, ".codewhale/mcp.json");
|
|
728
581
|
if (results.some((r) => r.filePath === ".codewhale/mcp.json")) return;
|
|
729
582
|
try {
|
|
730
|
-
const dir =
|
|
731
|
-
if (
|
|
732
|
-
const existingContent =
|
|
583
|
+
const dir = path.dirname(mcpPath);
|
|
584
|
+
if (fs.existsSync(mcpPath)) {
|
|
585
|
+
const existingContent = fs.readFileSync(mcpPath, "utf-8");
|
|
733
586
|
if (existingContent.includes("kuma")) return;
|
|
734
587
|
const parsed = JSON.parse(existingContent);
|
|
735
588
|
parsed.mcpServers = parsed.mcpServers || {};
|
|
736
589
|
parsed.mcpServers.kuma = { command: "npx", args: ["-y", "@plumpslabs/kuma"], env: {} };
|
|
737
|
-
|
|
590
|
+
fs.writeFileSync(mcpPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
738
591
|
results.push({ type: "codewhale", filePath: ".codewhale/mcp.json", action: "appended" });
|
|
739
592
|
} else {
|
|
740
|
-
if (!
|
|
593
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
741
594
|
const config = {
|
|
742
595
|
mcpServers: {
|
|
743
596
|
kuma: {
|
|
@@ -747,7 +600,7 @@ function handleCodewhaleSecondary(root, results) {
|
|
|
747
600
|
}
|
|
748
601
|
}
|
|
749
602
|
};
|
|
750
|
-
|
|
603
|
+
fs.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
751
604
|
results.push({ type: "codewhale", filePath: ".codewhale/mcp.json", action: "created" });
|
|
752
605
|
}
|
|
753
606
|
} catch (err) {
|
|
@@ -769,28 +622,28 @@ function runInit(options) {
|
|
|
769
622
|
let agentsMdHandled = false;
|
|
770
623
|
for (const type of selected) {
|
|
771
624
|
const relativePath = configFilePath(type);
|
|
772
|
-
const fullPath =
|
|
625
|
+
const fullPath = path.resolve(root, relativePath);
|
|
773
626
|
const getTemplate = TEMPLATES[type];
|
|
774
627
|
try {
|
|
775
628
|
if (AGENTS_MD_TYPES.includes(type) && !agentsMdHandled) {
|
|
776
629
|
agentsMdHandled = true;
|
|
777
630
|
const combinedContent = getCombinedAgentsMd(new Set(agentsMdSelected));
|
|
778
|
-
if (
|
|
631
|
+
if (fs.existsSync(fullPath)) {
|
|
779
632
|
if (options.skipExisting) {
|
|
780
633
|
results.push({ type, filePath: relativePath, action: "skipped" });
|
|
781
634
|
} else {
|
|
782
|
-
const existingContent =
|
|
635
|
+
const existingContent = fs.readFileSync(fullPath, "utf-8");
|
|
783
636
|
if (existingContent.includes("_Generated by Kuma MCP_")) {
|
|
784
637
|
results.push({ type, filePath: relativePath, action: "skipped" });
|
|
785
638
|
} else {
|
|
786
|
-
|
|
639
|
+
fs.writeFileSync(fullPath, existingContent.trimEnd() + "\n\n" + combinedContent, "utf-8");
|
|
787
640
|
results.push({ type, filePath: relativePath, action: "appended" });
|
|
788
641
|
}
|
|
789
642
|
}
|
|
790
643
|
} else {
|
|
791
|
-
const dir =
|
|
792
|
-
if (!
|
|
793
|
-
|
|
644
|
+
const dir = path.dirname(fullPath);
|
|
645
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
646
|
+
fs.writeFileSync(fullPath, combinedContent, "utf-8");
|
|
794
647
|
results.push({ type, filePath: relativePath, action: "created" });
|
|
795
648
|
}
|
|
796
649
|
if (selectedSet.has("codex")) handleCodexSecondary(root, results);
|
|
@@ -801,12 +654,12 @@ function runInit(options) {
|
|
|
801
654
|
continue;
|
|
802
655
|
} else {
|
|
803
656
|
const template = getTemplate();
|
|
804
|
-
if (
|
|
657
|
+
if (fs.existsSync(fullPath)) {
|
|
805
658
|
if (options.skipExisting) {
|
|
806
659
|
results.push({ type, filePath: relativePath, action: "skipped" });
|
|
807
660
|
continue;
|
|
808
661
|
}
|
|
809
|
-
const existingContent =
|
|
662
|
+
const existingContent = fs.readFileSync(fullPath, "utf-8");
|
|
810
663
|
if (existingContent.includes("_Generated by Kuma MCP_")) {
|
|
811
664
|
if (type === "antigravity") {
|
|
812
665
|
handleAntigravityMcpConfig(root, results);
|
|
@@ -819,14 +672,14 @@ function runInit(options) {
|
|
|
819
672
|
continue;
|
|
820
673
|
}
|
|
821
674
|
const newContent = existingContent.trimEnd() + APPEND_SEPARATOR + template;
|
|
822
|
-
|
|
675
|
+
fs.writeFileSync(fullPath, newContent, "utf-8");
|
|
823
676
|
results.push({ type, filePath: relativePath, action: "appended" });
|
|
824
677
|
} else {
|
|
825
|
-
const dir =
|
|
826
|
-
if (!
|
|
827
|
-
|
|
678
|
+
const dir = path.dirname(fullPath);
|
|
679
|
+
if (!fs.existsSync(dir)) {
|
|
680
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
828
681
|
}
|
|
829
|
-
|
|
682
|
+
fs.writeFileSync(fullPath, template, "utf-8");
|
|
830
683
|
results.push({ type, filePath: relativePath, action: "created" });
|
|
831
684
|
}
|
|
832
685
|
if (type === "antigravity") {
|
|
@@ -894,11 +747,6 @@ function formatInitResults(results) {
|
|
|
894
747
|
}
|
|
895
748
|
|
|
896
749
|
export {
|
|
897
|
-
validateFilePath,
|
|
898
|
-
validateFileExtension,
|
|
899
|
-
getProjectRoot,
|
|
900
|
-
getBackupPath,
|
|
901
|
-
ensureBackupDir,
|
|
902
750
|
ALL_CONFIG_TYPES,
|
|
903
751
|
CONFIG_LABELS,
|
|
904
752
|
generateInitMdContent,
|