@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.
Files changed (62) hide show
  1. package/README.md +25 -44
  2. package/bin/lpm.js +2 -354
  3. package/bin/lpx.js +4 -0
  4. package/bin/native.js +154 -0
  5. package/package.json +42 -82
  6. package/scripts/install-binary.js +219 -550
  7. package/CHANGELOG.md +0 -197
  8. package/LICENSE +0 -15
  9. package/index.d.ts +0 -131
  10. package/index.js +0 -31
  11. package/lib/api.js +0 -398
  12. package/lib/commands/add.js +0 -1217
  13. package/lib/commands/audit.js +0 -283
  14. package/lib/commands/cache.js +0 -209
  15. package/lib/commands/check-name.js +0 -112
  16. package/lib/commands/config.js +0 -186
  17. package/lib/commands/doctor.js +0 -140
  18. package/lib/commands/info.js +0 -215
  19. package/lib/commands/init.js +0 -146
  20. package/lib/commands/install.js +0 -264
  21. package/lib/commands/login.js +0 -595
  22. package/lib/commands/logout.js +0 -94
  23. package/lib/commands/marketplace-compare.js +0 -164
  24. package/lib/commands/marketplace-earnings.js +0 -89
  25. package/lib/commands/mcp-setup.js +0 -363
  26. package/lib/commands/npmrc.js +0 -178
  27. package/lib/commands/open.js +0 -82
  28. package/lib/commands/outdated.js +0 -291
  29. package/lib/commands/pool-stats.js +0 -100
  30. package/lib/commands/publish.js +0 -949
  31. package/lib/commands/quality.js +0 -211
  32. package/lib/commands/remove.js +0 -82
  33. package/lib/commands/run.js +0 -14
  34. package/lib/commands/search.js +0 -143
  35. package/lib/commands/setup.js +0 -221
  36. package/lib/commands/skills.js +0 -898
  37. package/lib/commands/token-rotate.js +0 -25
  38. package/lib/commands/uninstall.js +0 -107
  39. package/lib/commands/whoami.js +0 -129
  40. package/lib/config.js +0 -328
  41. package/lib/constants.js +0 -190
  42. package/lib/ecosystem.js +0 -501
  43. package/lib/editor-skills.js +0 -321
  44. package/lib/editors.js +0 -215
  45. package/lib/import-rewriter.js +0 -364
  46. package/lib/install-targets/mcp-server.js +0 -245
  47. package/lib/install-targets/vscode-extension.js +0 -178
  48. package/lib/install-targets.js +0 -82
  49. package/lib/integrity.js +0 -179
  50. package/lib/lpm-config-prompts.js +0 -102
  51. package/lib/lpm-config.js +0 -408
  52. package/lib/oidc.js +0 -166
  53. package/lib/project-utils.js +0 -152
  54. package/lib/quality/checks.js +0 -687
  55. package/lib/quality/display.js +0 -155
  56. package/lib/quality/score.js +0 -118
  57. package/lib/quality/swift-checks.js +0 -447
  58. package/lib/safe-path.js +0 -180
  59. package/lib/secure-store.js +0 -321
  60. package/lib/swift-project.js +0 -637
  61. package/lib/ui.js +0 -40
  62. package/scripts/__tests__/install-binary.test.js +0 -346
@@ -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
- }