@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,164 +0,0 @@
1
- import chalk from "chalk"
2
- import ora from "ora"
3
- import { get } from "../api.js"
4
-
5
- /**
6
- * Format cents to a dollar string.
7
- * @param {number} cents
8
- * @returns {string}
9
- */
10
- function formatCents(cents) {
11
- if (!cents && cents !== 0) return "—"
12
- return `$${(cents / 100).toFixed(2)}`
13
- }
14
-
15
- /**
16
- * Parse package name to extract category or clean name.
17
- * @param {string} input
18
- * @returns {{ isPackage: boolean, owner?: string, name?: string, category?: string }}
19
- */
20
- function parseInput(input) {
21
- let cleaned = input
22
- if (cleaned.startsWith("@lpm.dev/")) {
23
- cleaned = cleaned.replace("@lpm.dev/", "")
24
- }
25
- const dotIndex = cleaned.indexOf(".")
26
- if (dotIndex > 0 && dotIndex < cleaned.length - 1) {
27
- return {
28
- isPackage: true,
29
- owner: cleaned.substring(0, dotIndex),
30
- name: cleaned.substring(dotIndex + 1),
31
- }
32
- }
33
- // Treat as category/keyword
34
- return { isPackage: false, category: input }
35
- }
36
-
37
- /**
38
- * Find and display comparable marketplace packages.
39
- *
40
- * @param {string} input - Package name or category
41
- * @param {Object} [options]
42
- * @param {boolean} [options.json] - Output as JSON
43
- * @param {string} [options.category] - Filter by category
44
- * @param {number} [options.limit] - Max results
45
- */
46
- export async function marketplaceCompare(input, options = {}) {
47
- if (!input || input.trim() === "") {
48
- if (options.json) {
49
- console.log(
50
- JSON.stringify({ error: "Package name or category required." }),
51
- )
52
- } else {
53
- console.error(chalk.red("Error: Package name or category required."))
54
- console.log(
55
- chalk.dim("Usage: lpm marketplace compare owner.package-name"),
56
- )
57
- console.log(
58
- chalk.dim(" lpm marketplace compare --category ui-components"),
59
- )
60
- }
61
- process.exit(1)
62
- }
63
-
64
- const parsed = parseInput(input.trim())
65
- const category =
66
- options.category || (parsed.isPackage ? null : parsed.category)
67
- const query = parsed.isPackage ? parsed.name : null
68
-
69
- const params = new URLSearchParams()
70
- if (category) params.set("category", category)
71
- if (query) params.set("q", query)
72
- if (options.limit) params.set("limit", String(options.limit))
73
-
74
- const spinner = options.json
75
- ? null
76
- : ora("Fetching comparable packages...").start()
77
-
78
- try {
79
- const response = await get(
80
- `/marketplace/comparables?${params.toString()}`,
81
- {
82
- onRetry: spinner
83
- ? (attempt, max) => {
84
- spinner.text = `Fetching (retry ${attempt}/${max})...`
85
- }
86
- : undefined,
87
- },
88
- )
89
-
90
- const data = await response.json().catch(() => ({}))
91
-
92
- if (!response.ok) {
93
- throw new Error(data.error || `Request failed: ${response.status}`)
94
- }
95
-
96
- if (options.json) {
97
- console.log(JSON.stringify(data, null, 2))
98
- return
99
- }
100
-
101
- if (spinner) spinner.stop()
102
-
103
- const comparables = data.comparables || []
104
-
105
- console.log("")
106
- if (category) {
107
- console.log(chalk.bold(` Marketplace: ${category}`))
108
- } else if (parsed.isPackage) {
109
- console.log(
110
- chalk.bold(` Similar to: @lpm.dev/${parsed.owner}.${parsed.name}`),
111
- )
112
- }
113
-
114
- if (data.stats?.priceRange) {
115
- const pr = data.stats.priceRange
116
- console.log(
117
- chalk.dim(
118
- ` Price range: ${formatCents(pr.minCents)} — ${formatCents(pr.maxCents)} (median: ${formatCents(pr.medianCents)})`,
119
- ),
120
- )
121
- }
122
- console.log(chalk.dim(` ${data.stats?.total || 0} packages found`))
123
- console.log("")
124
-
125
- if (comparables.length === 0) {
126
- console.log(chalk.dim(" No comparable packages found."))
127
- console.log("")
128
- return
129
- }
130
-
131
- // Table
132
- const nameWidth = Math.max(30, ...comparables.map(c => c.name.length + 2))
133
- console.log(
134
- ` ${chalk.dim("Package".padEnd(nameWidth))}${chalk.dim("Price".padStart(10))}${chalk.dim("Quality".padStart(10))}${chalk.dim("Downloads".padStart(12))}${chalk.dim("Mode".padStart(14))}`,
135
- )
136
- console.log(chalk.dim(` ${"─".repeat(nameWidth + 46)}`))
137
-
138
- for (const pkg of comparables) {
139
- const name = pkg.name.padEnd(nameWidth)
140
- const price = pkg.pricing
141
- ? formatCents(pkg.pricing.minPriceCents).padStart(10)
142
- : chalk.dim("Pool".padStart(10))
143
- const quality =
144
- pkg.qualityScore !== null
145
- ? `${pkg.qualityScore}/100`.padStart(10)
146
- : chalk.dim("—".padStart(10))
147
- const downloads = (pkg.downloadCount || 0).toLocaleString().padStart(12)
148
- const mode = pkg.distributionMode.padStart(14)
149
- console.log(` ${name}${price}${quality}${downloads}${chalk.dim(mode)}`)
150
- }
151
-
152
- console.log("")
153
- } catch (error) {
154
- if (spinner) spinner.fail(chalk.red("Failed to fetch comparable packages."))
155
- if (options.json) {
156
- console.log(JSON.stringify({ error: error.message }))
157
- } else {
158
- console.error(chalk.red(` ${error.message}`))
159
- }
160
- process.exit(1)
161
- }
162
- }
163
-
164
- export default marketplaceCompare
@@ -1,89 +0,0 @@
1
- import chalk from "chalk"
2
- import ora from "ora"
3
- import { get } from "../api.js"
4
-
5
- /**
6
- * Format cents to a dollar string.
7
- * @param {number} cents
8
- * @returns {string}
9
- */
10
- function formatCents(cents) {
11
- if (!cents && cents !== 0) return "$0.00"
12
- return `$${(cents / 100).toFixed(2)}`
13
- }
14
-
15
- /**
16
- * Fetch and display Marketplace earnings for the authenticated user.
17
- *
18
- * @param {Object} [options]
19
- * @param {boolean} [options.json] - Output as JSON
20
- */
21
- export async function marketplaceEarnings(options = {}) {
22
- const spinner = options.json
23
- ? null
24
- : ora("Fetching marketplace earnings...").start()
25
-
26
- try {
27
- const response = await get("/marketplace/earnings", {
28
- skipRetry: false,
29
- onRetry: spinner
30
- ? (attempt, max) => {
31
- spinner.text = `Fetching (retry ${attempt}/${max})...`
32
- }
33
- : undefined,
34
- })
35
-
36
- const data = await response.json().catch(() => ({}))
37
-
38
- if (!response.ok) {
39
- throw new Error(data.error || `Request failed: ${response.status}`)
40
- }
41
-
42
- if (options.json) {
43
- console.log(JSON.stringify(data, null, 2))
44
- return
45
- }
46
-
47
- if (spinner) spinner.stop()
48
-
49
- console.log("")
50
- console.log(chalk.bold(" Marketplace Earnings"))
51
- console.log("")
52
-
53
- if (!data.totalSales) {
54
- console.log(chalk.dim(" No marketplace sales yet."))
55
- console.log(
56
- chalk.dim(
57
- " Publish a package with Marketplace distribution to start selling.",
58
- ),
59
- )
60
- console.log("")
61
- return
62
- }
63
-
64
- console.log(
65
- ` ${chalk.dim("Total Sales:")} ${data.totalSales.toLocaleString()}`,
66
- )
67
- console.log(
68
- ` ${chalk.dim("Gross Revenue:")} ${formatCents(data.grossRevenueCents)}`,
69
- )
70
- console.log(
71
- ` ${chalk.dim("Platform Fees:")} ${chalk.red(formatCents(data.platformFeesCents))}`,
72
- )
73
- console.log(
74
- ` ${chalk.bold("Net Revenue:")} ${chalk.green(formatCents(data.netRevenueCents))}`,
75
- )
76
- console.log("")
77
- } catch (error) {
78
- if (spinner)
79
- spinner.fail(chalk.red("Failed to fetch marketplace earnings."))
80
- if (options.json) {
81
- console.log(JSON.stringify({ error: error.message }))
82
- } else {
83
- console.error(chalk.red(` ${error.message}`))
84
- }
85
- process.exit(1)
86
- }
87
- }
88
-
89
- export default marketplaceEarnings
@@ -1,363 +0,0 @@
1
- import { spawnSync } from "node:child_process"
2
- import fs from "node:fs"
3
- import path from "node:path"
4
- import { fileURLToPath } from "node:url"
5
- import * as p from "@clack/prompts"
6
- import chalk from "chalk"
7
- import { getRegistryUrl, getToken } from "../config.js"
8
- import { DEFAULT_REGISTRY_URL } from "../constants.js"
9
- import {
10
- addMcpServer,
11
- EDITORS,
12
- getMcpServerConfig,
13
- hasMcpServer,
14
- removeMcpServerEntry,
15
- shortPath,
16
- } from "../editors.js"
17
-
18
- const SERVER_NAME = "lpm-registry"
19
-
20
- const MCP_SERVER_PACKAGE = "@lpm-registry/mcp-server"
21
- const THIS_FILE_DIR = path.dirname(fileURLToPath(import.meta.url))
22
- const LOCAL_MCP_SERVER_CANDIDATES = [
23
- path.resolve(process.cwd(), "mcp-server", "bin", "mcp-server.js"),
24
- path.resolve(process.cwd(), "..", "mcp-server", "bin", "mcp-server.js"),
25
- path.resolve(
26
- THIS_FILE_DIR,
27
- "..",
28
- "..",
29
- "..",
30
- "..",
31
- "mcp-server",
32
- "bin",
33
- "mcp-server.js",
34
- ),
35
- ]
36
-
37
- function resolveNpxCommand() {
38
- const lookupCommand = process.platform === "win32" ? "where" : "which"
39
- const result = spawnSync(lookupCommand, ["npx"], { encoding: "utf-8" })
40
-
41
- if (result.status === 0) {
42
- const lines = (result.stdout || "")
43
- .split("\n")
44
- .map(line => line.trim())
45
- .filter(Boolean)
46
-
47
- if (lines.length > 0) {
48
- return lines[0]
49
- }
50
- }
51
-
52
- return "npx"
53
- }
54
-
55
- function getLocalMcpServerPath() {
56
- for (const candidate of LOCAL_MCP_SERVER_CANDIDATES) {
57
- if (fs.existsSync(candidate)) {
58
- return candidate
59
- }
60
- }
61
-
62
- return null
63
- }
64
-
65
- export function getServerConfig(registryUrl = DEFAULT_REGISTRY_URL) {
66
- const localServerPath = getLocalMcpServerPath()
67
- const hasCustomRegistry =
68
- typeof registryUrl === "string" &&
69
- registryUrl.length > 0 &&
70
- registryUrl !== DEFAULT_REGISTRY_URL
71
-
72
- const env = hasCustomRegistry ? { LPM_REGISTRY_URL: registryUrl } : undefined
73
-
74
- if (localServerPath) {
75
- return {
76
- command: process.execPath,
77
- args: [localServerPath],
78
- ...(env ? { env } : {}),
79
- }
80
- }
81
-
82
- return {
83
- command: resolveNpxCommand(),
84
- args: ["-y", `${MCP_SERVER_PACKAGE}@latest`],
85
- ...(env ? { env } : {}),
86
- }
87
- }
88
-
89
- function isCommandRunnable(command) {
90
- if (!command) return false
91
-
92
- if (path.isAbsolute(command)) {
93
- return fs.existsSync(command)
94
- }
95
-
96
- const lookupCommand = process.platform === "win32" ? "where" : "which"
97
- const result = spawnSync(lookupCommand, [command], { stdio: "ignore" })
98
- return result.status === 0
99
- }
100
-
101
- function getEditorStatus(editor, isProject = false) {
102
- const configPath =
103
- isProject && editor.projectPath
104
- ? path.resolve(process.cwd(), editor.projectPath)
105
- : editor.globalPath
106
-
107
- const entry = getMcpServerConfig(configPath, editor.serverKey, SERVER_NAME)
108
- const installed = !!entry
109
-
110
- if (!installed) {
111
- return {
112
- editor,
113
- configPath,
114
- installed: false,
115
- runnable: false,
116
- entry: null,
117
- }
118
- }
119
-
120
- const command = entry.command
121
- const runnable = isCommandRunnable(command)
122
-
123
- return {
124
- editor,
125
- configPath,
126
- installed: true,
127
- runnable,
128
- entry,
129
- }
130
- }
131
-
132
- // ============================================================================
133
- // Setup command
134
- // ============================================================================
135
-
136
- export async function mcpSetup(options = {}) {
137
- p.intro(chalk.bgCyan(chalk.black(" lpm mcp setup ")))
138
- const registryUrl = getRegistryUrl()
139
- const serverConfig = getServerConfig(registryUrl)
140
-
141
- // Check authentication and offer login
142
- let token = await getToken()
143
- if (!token) {
144
- const shouldLogin = await p.confirm({
145
- message:
146
- "Not logged in. Login now for full MCP functionality? (recommended)",
147
- initialValue: true,
148
- })
149
-
150
- if (p.isCancel(shouldLogin)) {
151
- p.cancel("Setup cancelled.")
152
- process.exit(0)
153
- }
154
-
155
- if (shouldLogin) {
156
- p.log.info("Opening browser for login...\n")
157
- const result = spawnSync("lpm", ["login"], {
158
- stdio: "inherit",
159
- })
160
-
161
- if (result.status === 0) {
162
- token = await getToken()
163
- } else {
164
- p.log.warn("Login did not complete. Continuing without authentication.")
165
- }
166
- }
167
- }
168
-
169
- const isProject = !!options.project
170
-
171
- // Detect installed editors
172
- const detected = EDITORS.filter(e => e.detect())
173
-
174
- if (detected.length === 0) {
175
- p.log.warn("No supported editors detected on this machine.")
176
- p.note(
177
- "Supported: Claude Code, Cursor, VS Code, Claude Desktop, Windsurf",
178
- "Supported editors",
179
- )
180
- p.outro("Install a supported editor and try again.")
181
- return
182
- }
183
-
184
- // Filter to editors that support project config when --project is used
185
- const eligible = isProject ? detected.filter(e => e.projectPath) : detected
186
-
187
- if (eligible.length === 0) {
188
- p.log.warn("No detected editors support project-level MCP config.")
189
- p.outro(
190
- `Try ${chalk.cyan("lpm mcp setup")} without --project for global setup.`,
191
- )
192
- return
193
- }
194
-
195
- // Build multiselect options
196
- const selectOptions = eligible.map(editor => {
197
- const configPath =
198
- isProject && editor.projectPath
199
- ? path.resolve(process.cwd(), editor.projectPath)
200
- : editor.globalPath
201
-
202
- const installed = hasMcpServer(configPath, editor.serverKey, SERVER_NAME)
203
-
204
- return {
205
- value: editor.id,
206
- label: installed
207
- ? `${editor.name} ${chalk.dim("(already configured)")}`
208
- : editor.name,
209
- hint: shortPath(configPath),
210
- }
211
- })
212
-
213
- const selected = await p.multiselect({
214
- message: isProject
215
- ? "Add LPM MCP server to (project-level):"
216
- : "Add LPM MCP server to:",
217
- options: selectOptions,
218
- initialValues: selectOptions.map(o => o.value),
219
- required: true,
220
- })
221
-
222
- if (p.isCancel(selected)) {
223
- p.cancel("Setup cancelled.")
224
- process.exit(0)
225
- }
226
-
227
- // Write configs
228
- let count = 0
229
- for (const editorId of selected) {
230
- const editor = EDITORS.find(e => e.id === editorId)
231
- const configPath =
232
- isProject && editor.projectPath
233
- ? path.resolve(process.cwd(), editor.projectPath)
234
- : editor.globalPath
235
-
236
- try {
237
- addMcpServer(configPath, editor.serverKey, SERVER_NAME, {
238
- ...serverConfig,
239
- })
240
- p.log.success(`${editor.name} ${chalk.dim(shortPath(configPath))}`)
241
- count++
242
- } catch (err) {
243
- p.log.error(`${editor.name}: ${err.message}`)
244
- }
245
- }
246
-
247
- if (count > 0) {
248
- const authLine = token
249
- ? `Auth: Using keychain token from ${chalk.cyan("lpm login")}`
250
- : `Auth: Run ${chalk.cyan("lpm login")} to enable authenticated tools`
251
-
252
- p.note(authLine, `Added to ${count} editor${count > 1 ? "s" : ""}`)
253
-
254
- if (registryUrl !== DEFAULT_REGISTRY_URL) {
255
- p.log.info(
256
- `Using custom registry URL for MCP: ${chalk.cyan(registryUrl)}`,
257
- )
258
- }
259
- }
260
-
261
- p.outro("Restart your editors to activate the MCP server.")
262
- }
263
-
264
- // ============================================================================
265
- // Remove command
266
- // ============================================================================
267
-
268
- export async function mcpRemove(options = {}) {
269
- p.intro(chalk.bgCyan(chalk.black(" lpm mcp remove ")))
270
-
271
- const isProject = !!options.project
272
- let count = 0
273
-
274
- for (const editor of EDITORS) {
275
- if (isProject && !editor.projectPath) continue
276
-
277
- const configPath =
278
- isProject && editor.projectPath
279
- ? path.resolve(process.cwd(), editor.projectPath)
280
- : editor.globalPath
281
-
282
- if (removeMcpServerEntry(configPath, editor.serverKey, SERVER_NAME)) {
283
- p.log.success(
284
- `Removed from ${editor.name} ${chalk.dim(shortPath(configPath))}`,
285
- )
286
- count++
287
- }
288
- }
289
-
290
- if (count === 0) {
291
- p.log.info("LPM MCP server was not configured in any editor.")
292
- }
293
-
294
- p.outro(
295
- count > 0 ? "Done. Restart your editors to apply." : "Nothing to remove.",
296
- )
297
- }
298
-
299
- // ============================================================================
300
- // Status command
301
- // ============================================================================
302
-
303
- export async function mcpStatus(options = {}) {
304
- p.intro(chalk.bgCyan(chalk.black(" lpm mcp status ")))
305
-
306
- const token = await getToken()
307
- const isVerbose = !!options.verbose
308
- let found = 0
309
- let notRunnable = 0
310
-
311
- for (const editor of EDITORS) {
312
- const status = getEditorStatus(editor, false)
313
- const globalInstalled = status.installed
314
-
315
- if (globalInstalled) {
316
- if (status.runnable) {
317
- p.log.success(
318
- `${editor.name} ${chalk.dim(shortPath(editor.globalPath))}`,
319
- )
320
- } else {
321
- p.log.warn(
322
- `${editor.name} ${chalk.dim(shortPath(editor.globalPath))} ${chalk.dim("(configured, command not found)")}`,
323
- )
324
- notRunnable++
325
- }
326
-
327
- if (isVerbose) {
328
- const args = Array.isArray(status.entry?.args) ? status.entry.args : []
329
- const command = status.entry?.command || "(missing)"
330
- p.log.info(` command: ${command}`)
331
- p.log.info(` args: ${args.length > 0 ? args.join(" ") : "(none)"}`)
332
- if (!status.runnable) {
333
- p.log.info(
334
- ` hint: use ${chalk.cyan("lpm mcp setup")} again to refresh config on this machine`,
335
- )
336
- }
337
- }
338
- found++
339
- } else if (isVerbose) {
340
- p.log.info(
341
- `${editor.name} ${chalk.dim(shortPath(editor.globalPath))} ${chalk.dim("(not configured)")}`,
342
- )
343
- }
344
- }
345
-
346
- if (found === 0) {
347
- p.log.info("LPM MCP server is not configured in any editor.")
348
- p.note(`Run ${chalk.cyan("lpm mcp setup")} to configure it.`, "Get started")
349
- } else if (notRunnable > 0) {
350
- p.note(
351
- `${notRunnable} configured editor${notRunnable > 1 ? "s" : ""} have a command that is not runnable in this environment.`,
352
- "Action needed",
353
- )
354
- }
355
-
356
- const authStatus = token
357
- ? chalk.green("Authenticated")
358
- : chalk.yellow(`Not logged in — run ${chalk.cyan("lpm login")}`)
359
-
360
- p.log.info(`Auth: ${authStatus}`)
361
-
362
- p.outro("")
363
- }