@lpm-registry/cli 0.58.0 → 0.59.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 +25 -44
- package/bin/lpm.js +2 -354
- package/bin/lpx.js +4 -0
- package/bin/native.js +154 -0
- package/package.json +42 -82
- package/scripts/install-binary.js +219 -550
- package/CHANGELOG.md +0 -197
- package/LICENSE +0 -15
- package/index.d.ts +0 -131
- package/index.js +0 -31
- package/lib/api.js +0 -398
- package/lib/commands/add.js +0 -1217
- package/lib/commands/audit.js +0 -283
- package/lib/commands/cache.js +0 -209
- package/lib/commands/check-name.js +0 -112
- package/lib/commands/config.js +0 -186
- package/lib/commands/doctor.js +0 -140
- package/lib/commands/info.js +0 -215
- package/lib/commands/init.js +0 -146
- package/lib/commands/install.js +0 -264
- package/lib/commands/login.js +0 -595
- package/lib/commands/logout.js +0 -94
- package/lib/commands/marketplace-compare.js +0 -164
- package/lib/commands/marketplace-earnings.js +0 -89
- package/lib/commands/mcp-setup.js +0 -363
- package/lib/commands/npmrc.js +0 -178
- package/lib/commands/open.js +0 -82
- package/lib/commands/outdated.js +0 -291
- package/lib/commands/pool-stats.js +0 -100
- package/lib/commands/publish.js +0 -949
- package/lib/commands/quality.js +0 -211
- package/lib/commands/remove.js +0 -82
- package/lib/commands/run.js +0 -14
- package/lib/commands/search.js +0 -143
- package/lib/commands/setup.js +0 -221
- package/lib/commands/skills.js +0 -898
- package/lib/commands/token-rotate.js +0 -25
- package/lib/commands/uninstall.js +0 -107
- package/lib/commands/whoami.js +0 -129
- package/lib/config.js +0 -328
- package/lib/constants.js +0 -190
- package/lib/ecosystem.js +0 -501
- package/lib/editor-skills.js +0 -321
- package/lib/editors.js +0 -215
- package/lib/import-rewriter.js +0 -364
- package/lib/install-targets/mcp-server.js +0 -245
- package/lib/install-targets/vscode-extension.js +0 -178
- package/lib/install-targets.js +0 -82
- package/lib/integrity.js +0 -179
- package/lib/lpm-config-prompts.js +0 -102
- package/lib/lpm-config.js +0 -408
- package/lib/oidc.js +0 -166
- package/lib/project-utils.js +0 -152
- package/lib/quality/checks.js +0 -687
- package/lib/quality/display.js +0 -155
- package/lib/quality/score.js +0 -118
- package/lib/quality/swift-checks.js +0 -447
- package/lib/safe-path.js +0 -180
- package/lib/secure-store.js +0 -321
- package/lib/swift-project.js +0 -637
- package/lib/ui.js +0 -40
- package/scripts/__tests__/install-binary.test.js +0 -346
package/lib/editor-skills.js
DELETED
|
@@ -1,321 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Auto-integrate Agent Skills with AI editor config files.
|
|
3
|
-
*
|
|
4
|
-
* After `lpm skills install` writes skill files to `.lpm/skills/`,
|
|
5
|
-
* this module detects which AI editors are configured in the project
|
|
6
|
-
* and wires the skills in automatically (append reference, symlink, etc.).
|
|
7
|
-
*
|
|
8
|
-
* @module cli/lib/editor-skills
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import fs from "node:fs"
|
|
12
|
-
import path from "node:path"
|
|
13
|
-
import chalk from "chalk"
|
|
14
|
-
import { log } from "./ui.js"
|
|
15
|
-
|
|
16
|
-
// ============================================================================
|
|
17
|
-
// Constants
|
|
18
|
-
// ============================================================================
|
|
19
|
-
|
|
20
|
-
const SKILLS_DIR = ".lpm/skills"
|
|
21
|
-
|
|
22
|
-
const SKILLS_REFERENCE_LINE =
|
|
23
|
-
"See .lpm/skills/ for package-specific Agent Skills and guidelines."
|
|
24
|
-
|
|
25
|
-
/** Marker comment so we can detect our own additions and avoid duplicates. */
|
|
26
|
-
const LPM_MARKER = "<!-- lpm:skills -->"
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* The block we append to markdown/text config files.
|
|
30
|
-
* Contains both a human-readable reference and a machine-detectable marker.
|
|
31
|
-
*/
|
|
32
|
-
function makeReferenceBlock() {
|
|
33
|
-
return `\n\n${LPM_MARKER}\n## LPM Agent Skills\n\n${SKILLS_REFERENCE_LINE}\n`
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// ============================================================================
|
|
37
|
-
// AI Editor definitions (for skills wiring — separate from MCP editors)
|
|
38
|
-
// ============================================================================
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* @typedef {object} AIEditor
|
|
42
|
-
* @property {string} id - Unique identifier
|
|
43
|
-
* @property {string} name - Display name
|
|
44
|
-
* @property {string} configPath - Relative path to the config file from project root
|
|
45
|
-
* @property {"append" | "symlink"} action - How to integrate skills
|
|
46
|
-
* @property {(projectRoot: string) => boolean} detect - Whether this editor is configured in the project
|
|
47
|
-
*/
|
|
48
|
-
|
|
49
|
-
/** @type {AIEditor[]} */
|
|
50
|
-
const AI_EDITORS = [
|
|
51
|
-
{
|
|
52
|
-
id: "claude-code",
|
|
53
|
-
name: "Claude Code",
|
|
54
|
-
configPath: "CLAUDE.md",
|
|
55
|
-
action: "append",
|
|
56
|
-
detect: projectRoot => fs.existsSync(path.join(projectRoot, "CLAUDE.md")),
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
id: "cursor",
|
|
60
|
-
name: "Cursor",
|
|
61
|
-
configPath: ".cursor/rules/",
|
|
62
|
-
action: "symlink",
|
|
63
|
-
detect: projectRoot =>
|
|
64
|
-
fs.existsSync(path.join(projectRoot, ".cursor", "rules")),
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
id: "cursor-legacy",
|
|
68
|
-
name: "Cursor (legacy)",
|
|
69
|
-
configPath: ".cursorrules",
|
|
70
|
-
action: "append",
|
|
71
|
-
detect: projectRoot =>
|
|
72
|
-
fs.existsSync(path.join(projectRoot, ".cursorrules")),
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
id: "windsurf",
|
|
76
|
-
name: "Windsurf",
|
|
77
|
-
configPath: ".windsurfrules",
|
|
78
|
-
action: "append",
|
|
79
|
-
detect: projectRoot =>
|
|
80
|
-
fs.existsSync(path.join(projectRoot, ".windsurfrules")),
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
id: "github-copilot",
|
|
84
|
-
name: "GitHub Copilot",
|
|
85
|
-
configPath: ".github/copilot-instructions.md",
|
|
86
|
-
action: "append",
|
|
87
|
-
detect: projectRoot =>
|
|
88
|
-
fs.existsSync(
|
|
89
|
-
path.join(projectRoot, ".github", "copilot-instructions.md"),
|
|
90
|
-
),
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
id: "augment",
|
|
94
|
-
name: "Augment",
|
|
95
|
-
configPath: ".augment/instructions.md",
|
|
96
|
-
action: "append",
|
|
97
|
-
detect: projectRoot =>
|
|
98
|
-
fs.existsSync(path.join(projectRoot, ".augment", "instructions.md")),
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
id: "cline",
|
|
102
|
-
name: "Cline",
|
|
103
|
-
configPath: ".clinerules",
|
|
104
|
-
action: "append",
|
|
105
|
-
detect: projectRoot => fs.existsSync(path.join(projectRoot, ".clinerules")),
|
|
106
|
-
},
|
|
107
|
-
]
|
|
108
|
-
|
|
109
|
-
// ============================================================================
|
|
110
|
-
// Core logic
|
|
111
|
-
// ============================================================================
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Detect which AI editors are configured in the project.
|
|
115
|
-
*
|
|
116
|
-
* @param {string} projectRoot
|
|
117
|
-
* @returns {AIEditor[]}
|
|
118
|
-
*/
|
|
119
|
-
export function detectAIEditors(projectRoot) {
|
|
120
|
-
return AI_EDITORS.filter(editor => editor.detect(projectRoot))
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Check if a file already references .lpm/skills/.
|
|
125
|
-
*
|
|
126
|
-
* @param {string} filePath
|
|
127
|
-
* @returns {boolean}
|
|
128
|
-
*/
|
|
129
|
-
export function hasSkillsReference(filePath) {
|
|
130
|
-
if (!fs.existsSync(filePath)) return false
|
|
131
|
-
const content = fs.readFileSync(filePath, "utf-8")
|
|
132
|
-
return content.includes(".lpm/skills") || content.includes(LPM_MARKER)
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Append a skills reference block to a markdown/text config file.
|
|
137
|
-
*
|
|
138
|
-
* @param {string} filePath - Absolute path to the config file
|
|
139
|
-
* @returns {boolean} Whether the reference was added (false if already present)
|
|
140
|
-
*/
|
|
141
|
-
export function appendSkillsReference(filePath) {
|
|
142
|
-
if (hasSkillsReference(filePath)) return false
|
|
143
|
-
fs.appendFileSync(filePath, makeReferenceBlock())
|
|
144
|
-
return true
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Create a new CLAUDE.md with skills reference (fallback when no editor detected).
|
|
149
|
-
*
|
|
150
|
-
* @param {string} projectRoot
|
|
151
|
-
* @returns {boolean} Whether the file was created
|
|
152
|
-
*/
|
|
153
|
-
export function createClaudeMdWithReference(projectRoot) {
|
|
154
|
-
const claudeMdPath = path.join(projectRoot, "CLAUDE.md")
|
|
155
|
-
if (fs.existsSync(claudeMdPath)) return false
|
|
156
|
-
|
|
157
|
-
const content = `# Project Guidelines\n\n${LPM_MARKER}\n## LPM Agent Skills\n\n${SKILLS_REFERENCE_LINE}\n`
|
|
158
|
-
fs.writeFileSync(claudeMdPath, content, "utf-8")
|
|
159
|
-
return true
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Collect all .md skill files from .lpm/skills/ (recursively through package subdirs).
|
|
164
|
-
*
|
|
165
|
-
* @param {string} projectRoot
|
|
166
|
-
* @returns {{ packageName: string, fileName: string, filePath: string }[]}
|
|
167
|
-
*/
|
|
168
|
-
export function collectSkillFiles(projectRoot) {
|
|
169
|
-
const skillsDir = path.join(projectRoot, SKILLS_DIR)
|
|
170
|
-
if (!fs.existsSync(skillsDir)) return []
|
|
171
|
-
|
|
172
|
-
const results = []
|
|
173
|
-
const entries = fs.readdirSync(skillsDir, { withFileTypes: true })
|
|
174
|
-
|
|
175
|
-
for (const entry of entries) {
|
|
176
|
-
if (entry.isDirectory()) {
|
|
177
|
-
// Package subdirectory (e.g., "neo.colors")
|
|
178
|
-
const pkgDir = path.join(skillsDir, entry.name)
|
|
179
|
-
const files = fs.readdirSync(pkgDir).filter(f => f.endsWith(".md"))
|
|
180
|
-
for (const file of files) {
|
|
181
|
-
results.push({
|
|
182
|
-
packageName: entry.name,
|
|
183
|
-
fileName: file,
|
|
184
|
-
filePath: path.join(pkgDir, file),
|
|
185
|
-
})
|
|
186
|
-
}
|
|
187
|
-
} else if (entry.name.endsWith(".md")) {
|
|
188
|
-
// Top-level skill file
|
|
189
|
-
results.push({
|
|
190
|
-
packageName: null,
|
|
191
|
-
fileName: entry.name,
|
|
192
|
-
filePath: path.join(skillsDir, entry.name),
|
|
193
|
-
})
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return results
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Create symlinks for Cursor's .cursor/rules/ directory.
|
|
202
|
-
* Uses relative symlinks so they work across machines.
|
|
203
|
-
*
|
|
204
|
-
* @param {string} projectRoot
|
|
205
|
-
* @returns {{ created: string[], skipped: string[] }}
|
|
206
|
-
*/
|
|
207
|
-
export function symlinkForCursor(projectRoot) {
|
|
208
|
-
const rulesDir = path.join(projectRoot, ".cursor", "rules")
|
|
209
|
-
const skillFiles = collectSkillFiles(projectRoot)
|
|
210
|
-
|
|
211
|
-
const created = []
|
|
212
|
-
const skipped = []
|
|
213
|
-
|
|
214
|
-
for (const { packageName, fileName, filePath } of skillFiles) {
|
|
215
|
-
// Build symlink name: "neo.colors-getting-started.md"
|
|
216
|
-
const linkName = packageName
|
|
217
|
-
? `${packageName}-${fileName}`
|
|
218
|
-
: `lpm-${fileName}`
|
|
219
|
-
|
|
220
|
-
const linkPath = path.join(rulesDir, linkName)
|
|
221
|
-
|
|
222
|
-
if (fs.existsSync(linkPath)) {
|
|
223
|
-
skipped.push(linkName)
|
|
224
|
-
continue
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Compute relative path from .cursor/rules/ to the skill file
|
|
228
|
-
const relativePath = path.relative(rulesDir, filePath)
|
|
229
|
-
|
|
230
|
-
try {
|
|
231
|
-
fs.symlinkSync(relativePath, linkPath)
|
|
232
|
-
created.push(linkName)
|
|
233
|
-
} catch {
|
|
234
|
-
// Fallback: copy if symlink fails (e.g., Windows without developer mode)
|
|
235
|
-
try {
|
|
236
|
-
fs.copyFileSync(filePath, linkPath)
|
|
237
|
-
created.push(linkName)
|
|
238
|
-
} catch {
|
|
239
|
-
skipped.push(linkName)
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
return { created, skipped }
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// ============================================================================
|
|
248
|
-
// Main entry point
|
|
249
|
-
// ============================================================================
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* Auto-integrate installed skills with detected AI editors.
|
|
253
|
-
*
|
|
254
|
-
* @param {object} options
|
|
255
|
-
* @param {string} [options.projectRoot] - Project root (defaults to cwd)
|
|
256
|
-
* @param {boolean} [options.json] - Output JSON instead of human-readable
|
|
257
|
-
* @param {string[]} [options.installedPackages] - Names of packages that had skills installed
|
|
258
|
-
* @returns {{ configured: { id: string, name: string, configPath: string }[], fallback: boolean }}
|
|
259
|
-
*/
|
|
260
|
-
export function autoIntegrateSkills(options = {}) {
|
|
261
|
-
const projectRoot = options.projectRoot || process.cwd()
|
|
262
|
-
const isJson = options.json
|
|
263
|
-
const configured = []
|
|
264
|
-
let fallback = false
|
|
265
|
-
|
|
266
|
-
const detectedEditors = detectAIEditors(projectRoot)
|
|
267
|
-
|
|
268
|
-
if (detectedEditors.length === 0) {
|
|
269
|
-
// No editor detected — print a helpful hint, don't auto-create files
|
|
270
|
-
fallback = true
|
|
271
|
-
if (!isJson) {
|
|
272
|
-
console.log("")
|
|
273
|
-
log.info(
|
|
274
|
-
"To use these skills with your AI coding tool, point your agent to the .lpm/skills/ folder.",
|
|
275
|
-
)
|
|
276
|
-
}
|
|
277
|
-
} else {
|
|
278
|
-
for (const editor of detectedEditors) {
|
|
279
|
-
if (editor.action === "symlink" && editor.id === "cursor") {
|
|
280
|
-
const { created } = symlinkForCursor(projectRoot)
|
|
281
|
-
if (created.length > 0) {
|
|
282
|
-
configured.push({
|
|
283
|
-
id: editor.id,
|
|
284
|
-
name: editor.name,
|
|
285
|
-
configPath: editor.configPath,
|
|
286
|
-
action: "symlinked",
|
|
287
|
-
files: created,
|
|
288
|
-
})
|
|
289
|
-
}
|
|
290
|
-
} else if (editor.action === "append") {
|
|
291
|
-
const filePath = path.join(projectRoot, editor.configPath)
|
|
292
|
-
const added = appendSkillsReference(filePath)
|
|
293
|
-
if (added) {
|
|
294
|
-
configured.push({
|
|
295
|
-
id: editor.id,
|
|
296
|
-
name: editor.name,
|
|
297
|
-
configPath: editor.configPath,
|
|
298
|
-
action: "appended",
|
|
299
|
-
})
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// Output
|
|
306
|
-
if (!isJson && configured.length > 0) {
|
|
307
|
-
console.log("")
|
|
308
|
-
console.log(chalk.dim(" Auto-configured for:"))
|
|
309
|
-
for (const c of configured) {
|
|
310
|
-
if (c.action === "symlinked") {
|
|
311
|
-
log.success(
|
|
312
|
-
`${c.name} (${c.configPath}) — ${c.files.length} file${c.files.length !== 1 ? "s" : ""} linked`,
|
|
313
|
-
)
|
|
314
|
-
} else {
|
|
315
|
-
log.success(`${c.name} (${c.configPath})`)
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
return { configured, fallback }
|
|
321
|
-
}
|
package/lib/editors.js
DELETED
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared editor detection and MCP config helpers.
|
|
3
|
-
*
|
|
4
|
-
* Used by both `lpm mcp setup` (for the LPM registry MCP server)
|
|
5
|
-
* and `lpm add` (for installing third-party MCP server packages).
|
|
6
|
-
*
|
|
7
|
-
* @module cli/lib/editors
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import fs from "node:fs"
|
|
11
|
-
import os from "node:os"
|
|
12
|
-
import path from "node:path"
|
|
13
|
-
|
|
14
|
-
const HOME = os.homedir()
|
|
15
|
-
|
|
16
|
-
// ============================================================================
|
|
17
|
-
// Editor definitions
|
|
18
|
-
// ============================================================================
|
|
19
|
-
|
|
20
|
-
export const EDITORS = [
|
|
21
|
-
{
|
|
22
|
-
id: "claude-code",
|
|
23
|
-
name: "Claude Code",
|
|
24
|
-
globalPath: path.join(HOME, ".claude.json"),
|
|
25
|
-
projectPath: ".mcp.json",
|
|
26
|
-
serverKey: "mcpServers",
|
|
27
|
-
detect: () =>
|
|
28
|
-
fs.existsSync(path.join(HOME, ".claude")) ||
|
|
29
|
-
fs.existsSync(path.join(HOME, ".claude.json")),
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
id: "cursor",
|
|
33
|
-
name: "Cursor",
|
|
34
|
-
globalPath: path.join(HOME, ".cursor", "mcp.json"),
|
|
35
|
-
projectPath: path.join(".cursor", "mcp.json"),
|
|
36
|
-
serverKey: "mcpServers",
|
|
37
|
-
detect: () => fs.existsSync(path.join(HOME, ".cursor")),
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
id: "vscode",
|
|
41
|
-
name: "VS Code",
|
|
42
|
-
globalPath:
|
|
43
|
-
process.platform === "darwin"
|
|
44
|
-
? path.join(
|
|
45
|
-
HOME,
|
|
46
|
-
"Library",
|
|
47
|
-
"Application Support",
|
|
48
|
-
"Code",
|
|
49
|
-
"User",
|
|
50
|
-
"mcp.json",
|
|
51
|
-
)
|
|
52
|
-
: process.platform === "win32"
|
|
53
|
-
? path.join(process.env.APPDATA || "", "Code", "User", "mcp.json")
|
|
54
|
-
: path.join(HOME, ".config", "Code", "User", "mcp.json"),
|
|
55
|
-
projectPath: path.join(".vscode", "mcp.json"),
|
|
56
|
-
serverKey: "servers",
|
|
57
|
-
detect: () => {
|
|
58
|
-
if (process.platform === "darwin") {
|
|
59
|
-
return fs.existsSync(
|
|
60
|
-
path.join(HOME, "Library", "Application Support", "Code"),
|
|
61
|
-
)
|
|
62
|
-
}
|
|
63
|
-
if (process.platform === "win32") {
|
|
64
|
-
return fs.existsSync(path.join(process.env.APPDATA || "", "Code"))
|
|
65
|
-
}
|
|
66
|
-
return fs.existsSync(path.join(HOME, ".config", "Code"))
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
id: "claude-desktop",
|
|
71
|
-
name: "Claude Desktop",
|
|
72
|
-
globalPath:
|
|
73
|
-
process.platform === "darwin"
|
|
74
|
-
? path.join(
|
|
75
|
-
HOME,
|
|
76
|
-
"Library",
|
|
77
|
-
"Application Support",
|
|
78
|
-
"Claude",
|
|
79
|
-
"claude_desktop_config.json",
|
|
80
|
-
)
|
|
81
|
-
: process.platform === "win32"
|
|
82
|
-
? path.join(
|
|
83
|
-
process.env.APPDATA || "",
|
|
84
|
-
"Claude",
|
|
85
|
-
"claude_desktop_config.json",
|
|
86
|
-
)
|
|
87
|
-
: path.join(HOME, ".config", "Claude", "claude_desktop_config.json"),
|
|
88
|
-
projectPath: null,
|
|
89
|
-
serverKey: "mcpServers",
|
|
90
|
-
detect: () => {
|
|
91
|
-
if (process.platform === "darwin") {
|
|
92
|
-
return fs.existsSync(
|
|
93
|
-
path.join(HOME, "Library", "Application Support", "Claude"),
|
|
94
|
-
)
|
|
95
|
-
}
|
|
96
|
-
if (process.platform === "win32") {
|
|
97
|
-
return fs.existsSync(path.join(process.env.APPDATA || "", "Claude"))
|
|
98
|
-
}
|
|
99
|
-
return fs.existsSync(path.join(HOME, ".config", "Claude"))
|
|
100
|
-
},
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
id: "windsurf",
|
|
104
|
-
name: "Windsurf",
|
|
105
|
-
globalPath: path.join(HOME, ".codeium", "windsurf", "mcp_config.json"),
|
|
106
|
-
projectPath: null,
|
|
107
|
-
serverKey: "mcpServers",
|
|
108
|
-
detect: () => fs.existsSync(path.join(HOME, ".codeium", "windsurf")),
|
|
109
|
-
},
|
|
110
|
-
]
|
|
111
|
-
|
|
112
|
-
// ============================================================================
|
|
113
|
-
// JSON file helpers
|
|
114
|
-
// ============================================================================
|
|
115
|
-
|
|
116
|
-
export function readJsonSafe(filePath) {
|
|
117
|
-
try {
|
|
118
|
-
return JSON.parse(fs.readFileSync(filePath, "utf-8"))
|
|
119
|
-
} catch {
|
|
120
|
-
return {}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export function writeJson(filePath, data) {
|
|
125
|
-
const dir = path.dirname(filePath)
|
|
126
|
-
if (!fs.existsSync(dir)) {
|
|
127
|
-
fs.mkdirSync(dir, { recursive: true })
|
|
128
|
-
}
|
|
129
|
-
fs.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`)
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// ============================================================================
|
|
133
|
-
// MCP config helpers (generic — work with any server name)
|
|
134
|
-
// ============================================================================
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Add an MCP server entry to an editor config file.
|
|
138
|
-
*
|
|
139
|
-
* @param {string} filePath - Path to the editor's MCP config JSON
|
|
140
|
-
* @param {string} serverKey - Top-level key ('mcpServers' or 'servers')
|
|
141
|
-
* @param {string} serverName - Name for the server entry
|
|
142
|
-
* @param {object} serverConfig - Server config (command, args, env)
|
|
143
|
-
*/
|
|
144
|
-
export function addMcpServer(filePath, serverKey, serverName, serverConfig) {
|
|
145
|
-
const config = readJsonSafe(filePath)
|
|
146
|
-
if (!config[serverKey]) {
|
|
147
|
-
config[serverKey] = {}
|
|
148
|
-
}
|
|
149
|
-
config[serverKey][serverName] = serverConfig
|
|
150
|
-
writeJson(filePath, config)
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Remove an MCP server entry from an editor config file.
|
|
155
|
-
*
|
|
156
|
-
* @param {string} filePath - Path to the editor's MCP config JSON
|
|
157
|
-
* @param {string} serverKey - Top-level key ('mcpServers' or 'servers')
|
|
158
|
-
* @param {string} serverName - Name of the server entry to remove
|
|
159
|
-
* @returns {boolean} Whether the server was found and removed
|
|
160
|
-
*/
|
|
161
|
-
export function removeMcpServerEntry(filePath, serverKey, serverName) {
|
|
162
|
-
if (!fs.existsSync(filePath)) return false
|
|
163
|
-
const config = readJsonSafe(filePath)
|
|
164
|
-
if (config[serverKey]?.[serverName]) {
|
|
165
|
-
delete config[serverKey][serverName]
|
|
166
|
-
writeJson(filePath, config)
|
|
167
|
-
return true
|
|
168
|
-
}
|
|
169
|
-
return false
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Check if an MCP server entry exists in an editor config file.
|
|
174
|
-
*
|
|
175
|
-
* @param {string} filePath - Path to the editor's MCP config JSON
|
|
176
|
-
* @param {string} serverKey - Top-level key ('mcpServers' or 'servers')
|
|
177
|
-
* @param {string} serverName - Name of the server entry
|
|
178
|
-
* @returns {boolean}
|
|
179
|
-
*/
|
|
180
|
-
export function hasMcpServer(filePath, serverKey, serverName) {
|
|
181
|
-
if (!fs.existsSync(filePath)) return false
|
|
182
|
-
const config = readJsonSafe(filePath)
|
|
183
|
-
return !!config[serverKey]?.[serverName]
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Read MCP server config entry from an editor config file.
|
|
188
|
-
*
|
|
189
|
-
* @param {string} filePath - Path to the editor's MCP config JSON
|
|
190
|
-
* @param {string} serverKey - Top-level key ('mcpServers' or 'servers')
|
|
191
|
-
* @param {string} serverName - Name of the server entry
|
|
192
|
-
* @returns {object|null}
|
|
193
|
-
*/
|
|
194
|
-
export function getMcpServerConfig(filePath, serverKey, serverName) {
|
|
195
|
-
if (!fs.existsSync(filePath)) return null
|
|
196
|
-
const config = readJsonSafe(filePath)
|
|
197
|
-
return config[serverKey]?.[serverName] || null
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Detect installed editors.
|
|
202
|
-
* @returns {Array} Array of editor definitions that are installed
|
|
203
|
-
*/
|
|
204
|
-
export function detectEditors() {
|
|
205
|
-
return EDITORS.filter(e => e.detect())
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Shorten a path by replacing HOME with ~
|
|
210
|
-
* @param {string} fullPath
|
|
211
|
-
* @returns {string}
|
|
212
|
-
*/
|
|
213
|
-
export function shortPath(fullPath) {
|
|
214
|
-
return fullPath.replace(HOME, "~")
|
|
215
|
-
}
|