@lpm-registry/cli 0.5.0 → 0.6.0

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/CHANGELOG.md CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.6.0] - 2026-04-01
6
+
7
+ ### Added
8
+
9
+ - **Platform optionalDependencies (esbuild pattern)** — Native Rust binary now delivered via platform-specific npm packages (`@lpm-registry/cli-darwin-arm64`, etc.) instead of postinstall download. npm automatically installs only the matching platform package.
10
+ - **Hard-link optimization** — Postinstall hard-links the native binary over `bin/lpm.js`, so `lpm` on PATH executes the Rust binary directly with zero Node.js overhead.
11
+ - **3-tier binary resolution** — Entry point tries: (1) platform package binary, (2) GitHub Releases download fallback, (3) JS CLI fallback. Covers `--no-optional`, cross-device mounts, and offline scenarios.
12
+ - **`LPM_BINARY_PATH` env override** — Point to a custom binary for debugging or local development.
13
+
14
+ ### Changed
15
+
16
+ - **Rust binary v0.6.0** — Sigstore provenance signing, behavioral + supply-chain security tags, `lpm self-update`, `lpm tunnel`, `lpm vault`, `lpm migrate`, `lpm graph`, TUI dashboard, Xcode project editing, import rewriting enhancements, and more.
17
+ - Quality check field renamed `maxPoints` → `max_points` for consistency with registry API responses.
18
+ - `bin/lpm.js` rewritten from simple lpm-bin delegate to full 3-tier resolver with platform package support.
19
+ - `scripts/install-binary.js` rewritten with esbuild-pattern hard-link optimization.
20
+
5
21
  ## [0.5.0] - 2026-03-25
6
22
 
7
23
  ### Changed
package/bin/lpm.js CHANGED
@@ -1,33 +1,76 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ /**
4
+ * LPM CLI Entry Point — Minimal fallback wrapper.
5
+ *
6
+ * In most cases, this file has been replaced by a hard link to the native
7
+ * Rust binary during postinstall (zero Node.js overhead). This JS wrapper
8
+ * only runs when:
9
+ *
10
+ * 1. Hard-link failed (cross-device mount, permissions)
11
+ * 2. npm rebuild recreated the symlink
12
+ * 3. Binary download failed during postinstall
13
+ *
14
+ * Resolution order:
15
+ * 1. Try platform package binary (optionalDependencies)
16
+ * 2. Try lpm-bin in bin/ directory (GitHub Releases download)
17
+ * 3. Fall through to JS CLI implementation
18
+ */
19
+
3
20
  import { execFileSync } from "node:child_process"
4
21
  import fs from "node:fs"
22
+ import { createRequire } from "node:module"
23
+ import os from "node:os"
5
24
  import path from "node:path"
6
25
  import { fileURLToPath } from "node:url"
7
26
 
8
- // ─── Try Rust binary first ──────────────────────────────────────────
9
- const __dirname_early = path.dirname(fileURLToPath(import.meta.url))
10
- const binaryName = process.platform === "win32" ? "lpm-bin.exe" : "lpm-bin"
11
- const binaryPath = path.join(__dirname_early, binaryName)
27
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
28
+ const require = createRequire(import.meta.url)
29
+
30
+ // ─── Try native binary (Tier 1: platform package) ──────────────────
31
+
32
+ const PLATFORMS = {
33
+ "darwin-arm64": { pkg: "@lpm-registry/cli-darwin-arm64", binary: "lpm" },
34
+ "darwin-x64": { pkg: "@lpm-registry/cli-darwin-x64", binary: "lpm" },
35
+ "linux-x64": { pkg: "@lpm-registry/cli-linux-x64", binary: "lpm" },
36
+ "linux-arm64": { pkg: "@lpm-registry/cli-linux-arm64", binary: "lpm" },
37
+ "win32-x64": { pkg: "@lpm-registry/cli-win32-x64", binary: "lpm.exe" },
38
+ }
39
+
40
+ // Allow override via environment variable (for debugging / custom builds)
41
+ const envBinary = process.env.LPM_BINARY_PATH
42
+ if (envBinary && fs.existsSync(envBinary)) {
43
+ runBinary(envBinary)
44
+ }
45
+
46
+ // Try platform package from optionalDependencies
47
+ const platform = `${process.platform}-${os.arch()}`
48
+ const platformInfo = PLATFORMS[platform]
12
49
 
13
- if (fs.existsSync(binaryPath)) {
50
+ if (platformInfo) {
14
51
  try {
15
- // Delegate to Rust binary with all args
16
- execFileSync(binaryPath, process.argv.slice(2), {
17
- stdio: "inherit",
18
- env: process.env,
19
- })
20
- process.exit(0)
21
- } catch (err) {
22
- // execFileSync throws on non-zero exit — propagate the exit code
23
- if (err.status != null) {
24
- process.exit(err.status)
52
+ const pkgJsonPath = require.resolve(`${platformInfo.pkg}/package.json`)
53
+ const binaryPath = path.join(path.dirname(pkgJsonPath), platformInfo.binary)
54
+ if (fs.existsSync(binaryPath)) {
55
+ runBinary(binaryPath)
25
56
  }
26
- // Binary failed to execute — fall through to JS CLI
57
+ } catch {
58
+ // Package not installed
27
59
  }
28
60
  }
29
61
 
30
- // ─── JS CLI fallback ────────────────────────────────────────────────
62
+ // ─── Try native binary (Tier 2: downloaded by postinstall) ─────────
63
+
64
+ const legacyBinaryName =
65
+ process.platform === "win32" ? "lpm-bin.exe" : "lpm-bin"
66
+ const legacyBinaryPath = path.join(__dirname, legacyBinaryName)
67
+
68
+ if (fs.existsSync(legacyBinaryPath)) {
69
+ runBinary(legacyBinaryPath)
70
+ }
71
+
72
+ // ─── JS CLI fallback (Tier 3) ──────────────────────────────────────
73
+
31
74
  import { Command } from "commander"
32
75
  import updateNotifier from "update-notifier"
33
76
  import { add } from "../lib/commands/add.js"
@@ -65,7 +108,6 @@ import { uninstall } from "../lib/commands/uninstall.js"
65
108
  import { whoami } from "../lib/commands/whoami.js"
66
109
 
67
110
  // Load package.json
68
- const __dirname = path.dirname(fileURLToPath(import.meta.url))
69
111
  const pkg = JSON.parse(
70
112
  fs.readFileSync(path.join(__dirname, "../package.json"), "utf-8"),
71
113
  )
@@ -80,16 +122,12 @@ program
80
122
  .description("CLI for Licensed Package Manager")
81
123
  .version(pkg.version)
82
124
 
83
- // ============================================================================
84
- // Authentication Commands
85
- // ============================================================================
86
-
125
+ // Authentication
87
126
  program
88
127
  .command("login")
89
128
  .alias("l")
90
129
  .description("Authenticate with the registry")
91
130
  .action(login)
92
-
93
131
  program
94
132
  .command("logout")
95
133
  .alias("lo")
@@ -97,307 +135,213 @@ program
97
135
  .option("--revoke", "Also revoke the token on the server")
98
136
  .option("--clear-cache", "Clear local package cache")
99
137
  .action(logout)
100
-
101
138
  program
102
139
  .command("whoami")
103
140
  .description("Check current authenticated user")
104
141
  .option("--json", "Output in JSON format")
105
142
  .action(whoami)
106
143
 
107
- // ============================================================================
108
- // Package Management Commands
109
- // ============================================================================
110
-
144
+ // Package Management
111
145
  program
112
146
  .command("init")
113
147
  .description("Interactively create a package.json for LPM")
114
148
  .action(init)
115
-
116
149
  program
117
150
  .command("install [packages...]")
118
151
  .alias("i")
119
- .description("Install packages with automatic registry authentication")
152
+ .description("Install packages")
120
153
  .option("--json", "Machine-readable JSON output")
121
- .option("--no-skills", "Skip fetching Agent Skills after install")
122
- .option(
123
- "--no-editor-setup",
124
- "Skip auto-configuring AI editor integration for skills",
125
- )
126
- .option("--pm <manager>", "Package manager to use (npm, pnpm, yarn, bun)")
154
+ .option("--no-skills", "Skip fetching Agent Skills")
155
+ .option("--pm <manager>", "Package manager to use")
127
156
  .action(install)
128
-
129
157
  program
130
158
  .command("uninstall <packages...>")
131
159
  .aliases(["un", "unlink"])
132
- .description("Uninstall packages via package manager")
133
- .option("--pm <manager>", "Package manager to use (npm, pnpm, yarn, bun)")
160
+ .description("Uninstall packages")
161
+ .option("--pm <manager>", "Package manager to use")
134
162
  .action(uninstall)
135
-
136
163
  program
137
164
  .command("publish")
138
165
  .alias("p")
139
166
  .description("Publish a package to the registry")
140
- .option("--check", "Run quality checks and display report without publishing")
141
- .option(
142
- "--min-score <score>",
143
- "Minimum quality score required to publish (0-100)",
144
- )
145
- .option(
146
- "--provenance",
147
- "Force OIDC token exchange for secret-free publishing (auto-detected in GitHub Actions with id-token: write)",
148
- )
149
- .option(
150
- "--dry-run",
151
- "Validate and preview the publish (files, size, OIDC status) without uploading",
152
- )
167
+ .option("--check", "Quality check only")
168
+ .option("--min-score <score>", "Minimum quality score")
169
+ .option("--provenance", "Force OIDC provenance")
170
+ .option("--dry-run", "Preview without publishing")
153
171
  .action(publish)
154
-
155
172
  program
156
173
  .command("add <package>")
157
- .description("Download and extract a package source code to your project")
158
- .option("-p, --path <path>", "Target directory for the component")
159
- .option("-f, --force", "Overwrite existing files without prompting")
160
- .option("-y, --yes", "Accept defaults, skip interactive config prompts")
161
- .option("--alias <alias>", "Import alias prefix (e.g., @/components/ui)")
162
- .option("--target <name>", "Swift SPM target name")
163
- .option("--install-deps", "Auto-install npm dependencies (default: true)")
164
- .option("--no-install-deps", "Skip npm dependency installation")
165
- .option("--json", "Machine-readable JSON output")
166
- .option("--dry-run", "Preview what would happen without writing files")
167
- .option("--no-skills", "Skip fetching Agent Skills after add")
174
+ .description("Extract package source code")
175
+ .option("-p, --path <path>", "Target directory")
176
+ .option("-f, --force", "Overwrite existing files")
177
+ .option("-y, --yes", "Accept defaults")
178
+ .option("--alias <alias>", "Import alias prefix")
179
+ .option("--target <name>", "Swift SPM target")
180
+ .option("--json", "JSON output")
181
+ .option("--dry-run", "Preview only")
168
182
  .action(add)
169
-
170
183
  program
171
184
  .command("remove <package>")
172
185
  .alias("rm")
173
- .description(
174
- "Remove a previously added package (e.g., MCP servers from editors)",
175
- )
186
+ .description("Remove a previously added package")
176
187
  .action(remove)
177
188
 
178
- // ============================================================================
179
- // Package Discovery Commands
180
- // ============================================================================
181
-
189
+ // Discovery
182
190
  program
183
191
  .command("search <query>")
184
- .description("Search for packages in the marketplace")
185
- .option("--limit <n>", "Maximum number of results", "20")
186
- .option("--json", "Output in JSON format")
187
- .action((query, options) =>
188
- search(query, { ...options, limit: parseInt(options.limit, 10) }),
189
- )
190
-
192
+ .description("Search packages")
193
+ .option("--limit <n>", "Max results", "20")
194
+ .option("--json", "JSON output")
195
+ .action((q, o) => search(q, { ...o, limit: parseInt(o.limit, 10) }))
191
196
  program
192
197
  .command("info <package>")
193
- .description("Show detailed information about a package")
194
- .option("--json", "Output in JSON format")
198
+ .description("Package info")
199
+ .option("--json", "JSON output")
195
200
  .option("-a, --all-versions", "Show all versions")
196
201
  .action(info)
197
-
198
202
  program
199
203
  .command("check-name <name>")
200
- .description("Check if a package name is available on the registry")
201
- .option("--json", "Output in JSON format")
204
+ .description("Check name availability")
205
+ .option("--json", "JSON output")
202
206
  .action(checkName)
203
-
204
207
  program
205
208
  .command("quality <package>")
206
- .description("Show the server-side quality report for a package")
207
- .option("--json", "Output in JSON format")
209
+ .description("Quality report")
210
+ .option("--json", "JSON output")
208
211
  .action(quality)
209
212
 
210
- // ============================================================================
211
- // Security & Maintenance Commands
212
- // ============================================================================
213
-
213
+ // Maintenance
214
214
  program
215
215
  .command("audit [action]")
216
- .description("Scan dependencies for known vulnerabilities")
217
- .option("--json", "Output in JSON format")
218
- .option(
219
- "--level <level>",
220
- "Minimum severity to report (low, moderate, high, critical)",
221
- )
216
+ .description("Security audit")
217
+ .option("--json", "JSON output")
218
+ .option("--level <level>", "Minimum severity")
222
219
  .action(audit)
223
-
224
220
  program
225
221
  .command("outdated")
226
- .description("Check for outdated dependencies")
227
- .option("--json", "Output in JSON format")
228
- .option("--all", "Show all dependencies, not just outdated ones")
222
+ .description("Check outdated deps")
223
+ .option("--json", "JSON output")
224
+ .option("--all", "Show all deps")
229
225
  .action(outdated)
226
+ program.command("doctor").description("Health check").action(doctor)
230
227
 
231
- program
232
- .command("doctor")
233
- .description("Check the health of your LPM setup")
234
- .action(doctor)
235
-
236
- // ============================================================================
237
- // Configuration Commands
238
- // ============================================================================
239
-
228
+ // Configuration
240
229
  program
241
230
  .command("setup")
242
- .description("Configure .npmrc for LPM registry")
231
+ .description("Configure .npmrc")
243
232
  .option("-r, --registry <url>", "Custom registry URL")
244
- .option(
245
- "--oidc",
246
- "Exchange CI OIDC token for install access (no secrets needed)",
247
- )
248
- .option(
249
- "--scoped",
250
- "Only route @lpm.dev packages through LPM (don't proxy npm)",
251
- )
233
+ .option("--oidc", "Exchange OIDC token")
234
+ .option("--scoped", "Scoped registry only")
252
235
  .action(setup)
253
-
254
236
  program
255
237
  .command("npmrc")
256
- .description("Generate a read-only .npmrc token for local development")
257
- .option("-d, --days <number>", "Token expiry in days (default: 30)")
258
- .option(
259
- "--scoped",
260
- "Only route @lpm.dev packages through LPM (don't proxy npm)",
261
- )
238
+ .description("Generate read-only .npmrc token")
239
+ .option("-d, --days <number>", "Token expiry days")
240
+ .option("--scoped", "Scoped registry only")
262
241
  .action(npmrc)
263
-
264
242
  program
265
243
  .command("config [action] [key] [value]")
266
- .description("Manage CLI configuration (list, get, set, delete)")
244
+ .description("Manage CLI config")
267
245
  .action(config)
268
-
269
246
  program
270
247
  .command("set <key> <value>")
271
248
  .description('Shortcut for "lpm config set"')
272
- .action((key, value) => config("set", key, value))
273
-
249
+ .action((k, v) => config("set", k, v))
274
250
  program
275
251
  .command("cache <action>")
276
- .description("Manage local package cache (clean, list, path)")
252
+ .description("Manage local cache")
277
253
  .action(cache)
278
254
 
279
- // ============================================================================
280
- // Utility Commands
281
- // ============================================================================
282
-
255
+ // Utility
283
256
  program
284
257
  .command("open")
285
- .description("Open the dashboard or package page in your browser")
258
+ .description("Open dashboard in browser")
286
259
  .action(openDashboard)
287
-
288
260
  program
289
261
  .command("run <script>")
290
- .description("Run npm scripts (forwards to npm run)")
262
+ .description("Run npm scripts")
291
263
  .allowUnknownOption()
292
264
  .action(run)
293
265
 
294
- // ============================================================================
295
- // Token Management (Subcommand)
296
- // ============================================================================
297
-
298
- const token = program
299
- .command("token")
300
- .description("Manage authentication tokens")
301
-
302
- token
303
- .command("rotate")
304
- .description("Rotate the current token")
305
- .action(rotateToken)
306
-
307
- // ============================================================================
308
- // Pool Revenue (Subcommand)
309
- // ============================================================================
310
-
311
- const pool = program.command("pool").description("Pool revenue commands")
266
+ // Subcommands
267
+ const token = program.command("token").description("Token management")
268
+ token.command("rotate").description("Rotate token").action(rotateToken)
312
269
 
270
+ const pool = program.command("pool").description("Pool revenue")
313
271
  pool
314
272
  .command("stats")
315
- .description("Show your Pool earnings estimate for the current month")
316
- .option("--json", "Output in JSON format")
273
+ .description("Pool earnings")
274
+ .option("--json", "JSON output")
317
275
  .action(poolStats)
318
276
 
319
- // ============================================================================
320
- // MCP Server (Subcommand)
321
- // ============================================================================
322
-
323
- const mcp = program.command("mcp").description("MCP server commands")
324
-
277
+ const mcp = program.command("mcp").description("MCP server")
325
278
  mcp
326
279
  .command("setup")
327
- .description("Configure the LPM MCP server in your AI coding editors")
328
- .option("--project", "Add to project-level config instead of global")
280
+ .description("Configure MCP server")
281
+ .option("--project", "Project-level config")
329
282
  .action(mcpSetup)
330
-
331
283
  mcp
332
284
  .command("remove")
333
- .description("Remove the LPM MCP server from all configured editors")
334
- .option("--project", "Remove from project-level config only")
285
+ .description("Remove MCP server")
286
+ .option("--project", "Project-level only")
335
287
  .action(mcpRemove)
336
-
337
288
  mcp
338
289
  .command("status")
339
- .description("Show where the LPM MCP server is configured")
340
- .option("--verbose", "Show command and args diagnostics for each editor")
290
+ .description("MCP server status")
291
+ .option("--verbose", "Show diagnostics")
341
292
  .action(mcpStatus)
342
293
 
343
- // ============================================================================
344
- // Agent Skills (Subcommand)
345
- // ============================================================================
346
-
347
- const skills = program
348
- .command("skills")
349
- .description("Manage Agent Skills for AI coding assistants")
350
-
294
+ const skills = program.command("skills").description("Agent Skills")
351
295
  skills
352
296
  .command("validate")
353
- .description("Validate .lpm/skills/ files in the current directory")
354
- .option("--json", "Output in JSON format")
297
+ .description("Validate skills files")
298
+ .option("--json", "JSON output")
355
299
  .action(skillsValidate)
356
-
357
300
  skills
358
301
  .command("install [package]")
359
- .description(
360
- "Fetch and install skills from the registry (all deps or specific package)",
361
- )
362
- .option("--json", "Output in JSON format")
363
- .option(
364
- "--no-editor-setup",
365
- "Skip auto-configuring AI editor integration for skills",
366
- )
302
+ .description("Install skills")
303
+ .option("--json", "JSON output")
367
304
  .action(skillsInstall)
368
-
369
305
  skills
370
306
  .command("list")
371
- .description("List available skills for installed @lpm.dev/* packages")
372
- .option("--json", "Output in JSON format")
307
+ .description("List available skills")
308
+ .option("--json", "JSON output")
373
309
  .action(skillsList)
374
-
375
310
  skills
376
311
  .command("clean")
377
- .description("Remove locally installed skills (.lpm/skills/ directory)")
378
- .option("--json", "Output in JSON format")
312
+ .description("Remove installed skills")
313
+ .option("--json", "JSON output")
379
314
  .action(skillsClean)
380
315
 
381
- // ============================================================================
382
- // Marketplace (Subcommand)
383
- // ============================================================================
384
-
385
- const marketplace = program
386
- .command("marketplace")
387
- .description("Marketplace commands")
388
-
316
+ const marketplace = program.command("marketplace").description("Marketplace")
389
317
  marketplace
390
318
  .command("compare <input>")
391
- .description("Find comparable packages by name or category")
392
- .option("--json", "Output in JSON format")
319
+ .description("Find comparable packages")
320
+ .option("--json", "JSON output")
393
321
  .option("--category <category>", "Filter by category")
394
- .option("--limit <n>", "Maximum number of results")
322
+ .option("--limit <n>", "Max results")
395
323
  .action(marketplaceCompare)
396
-
397
324
  marketplace
398
325
  .command("earnings")
399
- .description("Show your Marketplace revenue summary")
400
- .option("--json", "Output in JSON format")
326
+ .description("Revenue summary")
327
+ .option("--json", "JSON output")
401
328
  .action(marketplaceEarnings)
402
329
 
403
330
  program.parse()
331
+
332
+ // ─── Helper ────────────────────────────────────────────────────────
333
+
334
+ function runBinary(binaryPath) {
335
+ try {
336
+ execFileSync(binaryPath, process.argv.slice(2), {
337
+ stdio: "inherit",
338
+ env: process.env,
339
+ })
340
+ process.exit(0)
341
+ } catch (err) {
342
+ if (err.status != null) {
343
+ process.exit(err.status)
344
+ }
345
+ // Binary failed to execute — fall through to JS CLI
346
+ }
347
+ }
package/index.d.ts CHANGED
@@ -62,8 +62,8 @@ export interface QualityCheck {
62
62
  label: string
63
63
  passed: boolean
64
64
  points: number
65
- maxPoints: number
66
- serverOnly?: boolean
65
+ max_points: number
66
+ server_only?: boolean
67
67
  }
68
68
 
69
69
  export interface QualityMeta {
@@ -178,8 +178,8 @@ export async function quality(nameInput, options = {}) {
178
178
  const icon = check.passed ? chalk.green("✓") : chalk.red("✗")
179
179
  const label = check.passed ? check.label : chalk.dim(check.label)
180
180
  const points = check.passed
181
- ? chalk.dim(` ${check.points}/${check.maxPoints}`)
182
- : chalk.dim(` 0/${check.maxPoints}`)
181
+ ? chalk.dim(` ${check.points}/${check.max_points}`)
182
+ : chalk.dim(` 0/${check.max_points}`)
183
183
  const detail = check.detail ? chalk.dim(` — ${check.detail}`) : ""
184
184
  console.log(` ${icon} ${label}${points}${detail}`)
185
185
  }
@@ -191,7 +191,7 @@ export async function quality(nameInput, options = {}) {
191
191
  const tips = failed.slice(0, 3)
192
192
  for (const check of tips) {
193
193
  const tip = TIPS[check.id] || `Improve: ${check.id}`
194
- console.log(chalk.dim(` Tip: ${tip} (+${check.maxPoints} points)`))
194
+ console.log(chalk.dim(` Tip: ${tip} (+${check.max_points} points)`))
195
195
  }
196
196
  }
197
197
  }