LPM CLI

Project setup

What goes in package.json, lpm.json, and lpm.toml — and which file owns each kind of setting.

LPM CLI splits project configuration across three files. Each owns a different kind of setting, by design:

FileWhat it ownsCommit?
package.jsonStandard npm fields (dependencies, scripts, engines) plus an "lpm" block for project-shared LPM CLI behavior (script policy, trust, linker, sandbox, overrides)Yes
lpm.jsonDev-server, task-runner, env-file mapping, env schema, services, publish targets — runtime infrastructure that isn't publishable metadataYes
lpm.tomlPer-project CLI defaults and reviewable policy exceptions — tool behavior, not publishableYes

The split is deliberate: anything that ships to consumers of your package lives in package.json. Anything that's about running the project locally lives in lpm.json. Anything that's about how the team wants the CLI to behave in this project lives in lpm.toml. Your personal preferences go in ~/.lpm/config.toml.

LPM CLI also creates other files in a project, and a .lpm/ directory that stays on your machine. See Files LPM CLI creates for which ones to commit.

package.json

Most LPM CLI behavior keys live under the "lpm" block — committed alongside dependencies so every contributor picks them up:

package.json
{
  "name": "my-app",
  "version": "1.0.0",
  "engines": { "node": ">=22.0.0", "lpm": ">=0.40.0" },
  "workspaces": ["packages/*"],

  "dependencies": { "react": "^19.0.0" },

  "lpm": {
    "linker": "isolated",
    "scriptPolicy": "deny",
    "trustedDependencies": ["esbuild", "sharp"],
    "scripts": {
      "autoBuild": false,
      "sandboxWriteDirs": ["build/"]
    }
  }
}

Full field reference: package.json "lpm" key.

lpm.json

Optional. Sits next to package.json and configures runtime / dev infrastructure:

lpm.json
{
  "$schema": "https://cli.lpm.dev/schemas/lpm.json",
  "runtime": { "node": ">=22.0.0", "bun": "1.3.14" },
  "tools": { "oxlint": "1.57.0", "biome": "2.4.8" },

  "env": {
    "dev":  ".env.development",
    "prod": ".env.production"
  },
  "envSchema": {
    "vars": {
      "DATABASE_URL": { "required": true, "format": "url" }
    }
  },

  "tasks": {
    "build": {
      "command": "tsup",
      "dependsOn": ["^build"],
      "cache": true,
      "outputs": ["dist/**"]
    }
  },

  "services": {
    "db":  { "command": "docker compose up postgres", "readyPort": 5432 },
    "web": { "command": "next dev", "port": 3000, "primary": true }
  },

  "publish": {
    "registries": ["lpm", "npm"]
  }
}

Full field reference: lpm.json.

The $schema line is optional but enables inline validation and field completion in any JSON-schema-aware editor.

lpm.toml

Optional. Sits next to package.json and pins per-project CLI defaults. It owns the save policy, workspace/test defaults, tidy ignores, sandbox defaults, and reviewable local policy exceptions:

lpm.toml
save-prefix = "~"     # team prefers tilde over caret
save-exact  = false

[[policy.typosquat.allow]]
package = "crossenv"
similar-to = "cross-env"
reason = "Internal migration package kept for compatibility"

Commit it. It holds team CLI defaults and reviewable policy exceptions, such as the policy.typosquat.allow entries that lpm install writes when you allow a suspicious package name. Keep personal preferences in ~/.lpm/config.toml. The keys here override ~/.lpm/config.toml (user-level) but lose to CLI flags.

Full field reference: lpm.toml.

Local configuration size limits

LPM CLI bounds local configuration before UTF-8 decoding or parsing. The limit applies to the bytes read from the opened file, including the target of a supported symlink.

InputLimit
JSON, TOML, YAML, and dotenv configuration or manifests — including package.json, lpm.toml, ~/.lpm/config.toml, lpm.json, lpm.config.json, pnpm-workspace.yaml, .env*, security/trust policy, and CLI-managed MCP, proxy, or Swift registry JSON16 MiB per file
.npmrc1 MiB per configuration layer
File-referenced CA bundles, client certificates, and private keys1 MiB per file

A missing optional file keeps its normal default or fallback behavior. A file that exists but exceeds its limit is not treated as missing: LPM CLI returns an error naming the path and byte limit. Install-time configuration fails before dependency resolution or registry access, and run-time configuration fails before a script or child process is spawned. This prevents an oversized higher-precedence file from silently changing registry, source, or security-policy selection.

Malformed or semantically invalid lpm.json fails before any project-bound environment operation can mutate local or remote state. That includes local secret operations, named --env resolution, validation, aliases, inheritance, schema operations, OIDC policy environment resolution, and lpm env push, which needs the complete alias, inheritance, and schema metadata to preserve environment boundaries. lpm env pull and lpm env rotate-key validate before network access or local vault replacement; lpm env share validates before sharing-key classification or registration. Only account-level operations that do not consult project configuration, such as browser pairing and sharing-key rotation, are unaffected by an unrelated malformed file. Oversized or unreadable configuration remains a hard error whenever the command needs it.

These small-file limits do not apply to lpm.lock / lpm.lockb, package tarballs and downloaded archives, package source or build outputs, patch and skill source payloads, README content, growing task and audit logs, tunnel captures in .lpm/inspector.db, or operating-system pseudo-files such as /proc. Those inputs retain their own streaming, integrity, rotation, or format-specific controls.

Safe atomic rewrites

LPM-managed atomic rewrites of project manifests, configuration, and state use an exclusively created, collision-resistant file in the destination directory. They do not follow an attacker-preplanted temporary symlink, hardlink, junction, or reparse entry. The final destination is replaced as a path entry rather than dereferenced, while existing regular-file modes and restricted 0600 outputs retain their intended Unix permissions.

The boundary is the audited atomic-rewrite paths. This does not provide arbitrary parent-directory confinement, cover every file written by LPM CLI or a package script, or imply crash durability unless a particular operation also synchronizes its file and parent directory.

Which file owns what

Quick decision table when you're not sure where a setting belongs:

You want to…Goes in
Pin a runtime dep with a version rangepackage.json > dependencies
Pin Node version for the projectlpm.json > runtime.node (or .nvmrc / .node-version)
Declare compatible Node versions without selecting onepackage.json > engines.node
Make Bun available to scriptslpm.json > runtime.bun
Block dependency lifecycle scripts for everyone on the teampackage.json > lpm.scriptPolicy = "deny"
Approve a specific package to run scriptspackage.json > lpm.trustedDependencies (or lpm approve-scripts)
Define a custom build task with cachinglpm.json > tasks.<name>
Map lpm run dev to a specific .env filelpm.json > env.dev
Configure dev services with readiness checkslpm.json > services
Set a save-prefix that overrides every developer's preferencelpm.toml > save-prefix
Allow an intentional suspicious package namelpm.toml > policy.typosquat.allow
Set your personal default save prefix machine-wide~/.lpm/config.toml > save-prefix
Publish to multiple registries from one lpm publishlpm.json > publish.registries

.gitignore essentials

LPM CLI needs no .gitignore entry of its own. Ignore what your tools and runtime produce:

.gitignore
node_modules/
# Local-only env overrides
.env*.local

The project's .lpm/ directory holds machine-local state: install checkpoints, certificates, tunnel captures, env sync state, and recovery backups. It ignores itself through .lpm/.gitignore, so Git never sees it. Commit the lpm.* and lpm-* files at the project root.

Secret files that LPM CLI writes, such as an lpm env export file or the .npmrc from lpm setup local, get their own ignore rule before the secret reaches disk. Your login, local env secrets, store, and caches live in the LPM home (~/.lpm, or $LPM_HOME). Some of its records are keyed by project path, such as per-project port choices, but none belong in a repository.

See Files LPM CLI creates for the full list.

See also