English | Русский
Glue your local packages together without npm publishing, links, or monorepo gymnastics
Like
npm link, but with a more convenient workflow. Likeyalc, but without the dependency on Node.js.
kley is a command-line tool that simplifies local development of npm packages. It provides a robust alternative to npm link by managing a local package store. It saves packages to a central cache on your machine when you "publish" and lets you quickly install them into your local projects via direct file copying or symlinks, without needing to connect to a remote repository.
- Fast and Efficient: All operations are local, with no network latency or unnecessary publishing of intermediate versions.
- Reliable and Independent: Avoids
npm linkissues and works even if your library and projects use different versions of Node.js. - Safe by design: Works with files directly, minimizing package script execution.
- Simple API: Two core commands to get started:
publishandinstall. - Cross-Platform: Works on macOS, Linux, and Windows.
If your goal is simply to use a locally built package, two commands are enough: publish and install. Let's walk through the basic scenario:
Steps:
- In your library directory — run
kley publish: copies package files tokleyregistry. - In your project directory — run
kley install <name>: copies files to.kley/, updateskley.lock, and automatically runs the native package manager to install the package intonode_modules/. - Make changes in your library, then run
kley publish --push: updates all linked projects.
Schema with details
The diagram below shows the key steps: publishing, installing the dependency, and then pushing an update.
sequenceDiagram
actor Dev
participant Lib as test-lib
participant Store as ~/.kley
participant App as test-app
Note over Dev, App: Initial State: `test-lib` and `test-app` are separate projects.
Dev->>Lib: 1. `kley publish`
Lib->>Store: Copies files to registry
Dev->>App: 2. `kley install test-lib`
App->>Store: Reads package from registry
Store-->>App: Copies files to `test-app/.kley/test-lib`
App->>App: Runs `npm install .kley/test-lib` (or pnpm/yarn)
App->>App: Updates `package.json` and installs into `node_modules/`
Note over Dev, App: `test-lib` is now usable in `test-app`. No manual `npm install` needed.
Dev->>Lib: 3. Makes code changes...
Dev->>Lib: 4. `kley publish --push`
Lib->>Store: Copies updated files to registry
Store->>App: Pushes updates directly to `test-app/.kley/test-lib`
Note over Dev, App: `test-app` is now running the latest code automatically!
If you need more control, here are two alternative workflows
This is a more controlled workflow. It's perfect for when you prefer the traditional npm install flow, but want to move faster without publishing to a remote repository.
Steps:
- In your library directory — run
kley publish: copies package files tokleyregistry. - In your project directory — run
kley add <name>: copies files to.kley/and updatespackage.json. - Run
npm install, npm createsnode_modules/<name>from.kley/<name>. - Make changes in your library, then run
kley publish --push: updates all linked projects. Or justkley publish, but in this case you'll also need to runkley updatein the project directory to get the changed version. - Run
npm install. You can now use the project with the updated library.
Schema with details
The diagram below shows the key steps: publishing, adding the dependency, and then pushing an update.
sequenceDiagram
actor Dev
participant Lib as test-lib
participant Store as ~/.kley
participant App as test-app
Note over Dev, App: Initial State: `test-lib` and `test-app` are separate projects.
Dev->>Lib: 1. `kley publish`
Lib->>Store: Copies files to registry
Dev->>App: 2. `kley add test-lib`
App->>Store: Reads package from registry
Store-->>App: Copies files to `test-app/.kley/test-lib`
App->>App: Modifies `package.json` to point to `file:.kley/test-lib`
Dev->>App: 3. `npm install`
App->>App: `npm` creates symlink:<br/>`node_modules/test-lib` -> `.kley/test-lib`
Note over Dev, App: `test-lib` is now usable in `test-app`.
Dev->>Lib: 4. Makes code changes...
Dev->>Lib: 5. `kley publish --push`
Lib->>Store: Copies updated files to registry
Store->>App: Pushes updates directly to `test-app/.kley/test-lib`
Note over Dev, App: `test-app` is now running the latest code automatically!
This workflow is ideal for rapid, live development. It creates a direct symbolic link from your project's node_modules to your library's source directory. It's the fastest way to see changes, but like npm link, it's less durable.
Steps:
- In your library directory — run
kley publish: records the source path in thekleyregistry. - In your project directory — run
kley link <name>: creates a symlink directly innode_modules/<name>pointing to your library's source — nonpm installneeded - Make changes in your library: the project sees them instantly because of the direct link.
⚠️ Note: If you runnpm install, it will delete the symlink. Restore it instantly withkley installorkley link <name>again.
Schema with details
This diagram shows how kley link provides a direct connection to the source.
sequenceDiagram
actor Dev
participant Lib as test-lib (source)
participant Store as ~/.kley (registry)
participant App as test-app
Note over Dev, App: Prerequisite: `test-lib` has been published (source path recorded).
Dev->>App: 1. `kley link test-lib`
App->>Store: Gets source path for `test-lib`
Store-->>App: Returns `/path/to/test-lib`
App->>App: Creates symlink:<br/>`node_modules/test-lib` -> `/path/to/test-lib`
Note over Dev, App: `test-lib` is now linked directly to source.
Dev->>Lib: 2. Makes code changes...
Note over Dev, App: `test-app` sees changes instantly!
Scenario 1 publish→add→npm i |
Scenario 2 publish→link |
|
|---|---|---|
| Best for | Stable, ongoing development | Quick, temporary testing |
Modifies package.json |
Yes | No |
Requires npm install |
Yes | No |
Survives npm install |
Yes | No. Run kley link again |
You can install kley with a single command using the installer script.
Linux / macOS:
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/sumbad/kley/releases/latest/download/kley-installer.sh | shWindows:
powershell -ExecutionPolicy Bypass -c "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; irm https://github.com/sumbad/kley/releases/latest/download/kley-installer.ps1 | iex"Alternatively, you can install kley by downloading a pre-compiled binary from the Releases page.
- Download the appropriate archive for your system (e.g.,
kley-x86_64-apple-darwin.tar.gz). - Unpack the archive.
- Move the
kleybinary to a directory in your system'sPATH(e.g.,/usr/local/binon macOS/Linux).
kley binary and requires Node.js to run.
If your library and consuming project use different Node.js versions, prefer the binary installer or Cargo install instead.
npm install -g kley-cliIf you have Rust and Cargo installed, you can install kley directly from crates.io:
cargo install kleyRun this command in the directory of the package you want to share locally. Kley copies all necessary files to a central store at ~/.kley/packages/<your-package-name>.
- Use the
--pushflag to automatically update the package in all projects where it has been added or linked. This is the primary command for a fast, iterative workflow.
Run this command in the directory of a published package to remove it from the kley store.
- By default, it performs a "soft" unpublish, removing the package from the store but leaving your projects intact until the next install.
- Use the
--pushflag to perform a "hard" unpublish, which also removes the package from all projects that use it.
A universal command that combines add and the native package manager installation. It automatically detects whether your project uses npm, pnpm, or yarn, copies the package to .kley/, updates kley.lock, and delegates the installation to the appropriate package manager — all in one go.
- With a package name:
kley install <package-name>installs the specified package from the local registry. - Without a package name:
kley installinstalls all packages listed inkley.lock. This is useful for restoring all local dependencies, similar to hownpm installrefreshesnode_modulesbased onpackage.json. Packages already indevDependenciesare automatically installed with the appropriate dev flag. - Link restoration: If a package was added via
kley link,kley install(without arguments) will automatically restore the direct symlink to the source directory if it's missing (e.g., afternpm install). - Use the
--devor-Dflag to install a package as adevDependency:kley install --dev <package-name>. - Use the
--no-saveflag to install a package intonode_modules/and updatekley.lockwithout modifyingpackage.json. This is useful for temporary testing and also works withkley install --no-save(no package name) to restore all packages fromkley.lock. Note: Yarn v1 does not support this flag natively and will still modifypackage.json. - Supports
npm,pnpm, andyarnout of the box. - To explicitly specify the package manager, set the
packageManagerfield inpackage.jsonorkley.lock. - Fast reinstall: When a package's
dependenciesandpeerDependencieshaven't changed since the last install,kleyskips the package manager entirely and copies files directly tonode_modules/<pkg>. This makes iterative workflows (repeatedkley publish→kley install) significantly faster. - Faster install for packages without dependencies: When a package has no
dependenciesandpeerDependencies,kleyskips the package manager entirely and creates a direct symlink tonode_modules/<pkg>. This is even faster than the normal "fast reinstall" and is ideal for simple utility packages or components. - Lifecycle scripts (
preinstall,install,postinstall) are disabled by default (--ignore-scripts) for safety. This prevents arbitrary code execution during install. If a package requires lifecycle scripts to function (e.g., native modules), run the package manager manually.
A lower-level command that adds a local package to your project without installing it into node_modules. You'll need to run npm install (or yarn, pnpm) afterwards for the changes to take effect. Think of it as the manual counterpart to kley install — it copies the package and updates package.json + kley.lock, but leaves the actual installation to you or your package manager.
--devadds the package todevDependencies.--pureadds the package without modifyingpackage.json(and without touchingnode_modules). It only copies the package to.kley/and records it inkley.lock(sopublish --pushcan still update it). This is intended for workspaces — but note that--puredoes not make the package resolvable automatically. You still need to add.kley/*to theworkspacesglobs in yourpackage.jsonand runnpm install(oryarn,pnpm) for the workspace manager to pick it up. If your project has aworkspacesfield,kley adddefaults to--puremode. To force the usualfile:dependency injection, use--no-pure.
kley add my-lib --pure # don't touch package.json / node_modules
kley add my-lib # in a workspace project → pure by default
kley add my-lib --no-pure # force the normal file: injectionWhen a published package's dependencies or peerDependencies use the
workspace: protocol (e.g. "my-lib": "workspace:^1.2.0"), kley resolves it
automatically during add, install, update and publish --push:
- The
workspace:prefix is stripped, so the specifier becomes a plain semver range ("workspace:^1.2.0"→"^1.2.0"). dependenciesare installed into the project (copied into.kley/and recorded inkley.lock) and — unless--pure— injected as afile:.kley/<pkg>entry intopackage.json.peerDependenciesonly have theworkspace:prefix stripped to a plain semver range; they are not installed or injected asfile:.kleylinks.- If the referenced package is absent from the kley registry, or its stored
version does not satisfy the range, the specifier is still stripped to a
plain range and a warning is printed (no local
file:link is created). - Use
--no-workspace-resolveonadd,install,updateorpublishto keep the rawworkspace:specifier.
kley add my-lib # resolves my-lib's workspace: deps automatically
kley add my-lib --no-workspace-resolve # keep raw workspace: specifiersThis command provides a flexible workflow that avoids modifying package.json. It creates a symbolic link from your project's node_modules directly to the library's source directory.
- Live updates: Changes in the library are visible immediately without needing to re-publish or re-install.
- Singleton warning: If the package has dependencies that are also listed as
peerDependencies(e.g., React),kleywill issue a warning. Linking such packages may cause duplicate instances if they are also dependencies of other packages in your project. In such cases,kley installis recommended instead.
Warning: Because
package.jsonis not modified, runningnpm install(oryarn,pnpm) will delete the symlink fromnode_modules. To restore it, simply runkley install(to restore all) orkley link <package-name>again.
This command updates installed packages to the latest version from the kley store.
- If you provide a package name, only that specific package will be updated.
- If you run it without arguments,
kleywill update all packages listed inkley.lock. - Note: Linked packages are skipped during update because they point directly to the source.
Run this command to cleanly remove a kley-managed dependency from your project. It will update package.json and kley.lock, and delete the package files from the ./.kley/ directory.
- Use the
--allflag to remove all kley-managed packages from the project.
Note: When copying a package into your project,
kleyautomatically strips itsdevDependenciesfrompackage.json. This keeps yournode_moduleslean and speeds up installation. The original package in the kley registry remains unchanged.
Run this command in the directory of your local package to monitor files for changes and automatically run kley publish --push whenever changes are detected. This enables a seamless hot-reloading development workflow where the consuming projects receive updates instantly.
- With a path:
kley watch <path>(e.g.,kley watch src) only monitors the specified subdirectory for changes. This is highly recommended for large projects to reduce file system watcher load. - Without a path:
kley watchmonitors the entire package directory (excludingnode_modules,.git, and.kleydirectories automatically).
By default kley publish is a pure file copy — it never runs package.json lifecycle scripts. This keeps you safe from accidental side effects (e.g. a hook that publishes to the real npm registry). To run build/prep steps during publish, configure explicit hooks in .kley/hooks.json:
- On the first
kley publishan interactive wizard lists the npm lifecycle scripts found in yourpackage.json(prepare,prepack,prepublishOnly,postpack,publish,postpublish) and lets you choose which to run. The selection is saved to.kley/hooks.json. - Hooks run only from
.kley/hooks.json.kleynever readspackage.jsonscriptsat publish time. .kley/hooks.jsonis yours to edit freely. A hook command does not have to mirror the script inpackage.json— you can copy the original command, tweak it (add flags, change the script), save the file, and exactly that command runs.kleyexecutes the command as written inhooks.json, never thepackage.jsonone.PREhooks run before files are copied;POSThooks run after. If a hook fails,kley publishaborts (a failing pre-hook means nothing is copied).
kley publish # wizard on first run, then runs configured hooks
kley publish -y # alias for --non-interactive: no wizard; hooks run only if .kley/hooks.json exists
kley publish --no-hooks # ignore .kley/hooks.json this run (pure copy)
kley hooks list # show the current .kley/hooks.json
kley hooks edit # re-run the wizard (keeps manually-added hooks)Safety: hooks are executed exactly as written in
.kley/hooks.json. Review them carefully —kleywill not guard against dangerous commands such asnpm publish.
Compare kley against yalc and @jimsheen/yalc in two typical scenarios: cold start and iterative development.
| Operation | kley | yalc | @jimsheen/yalc |
|---|---|---|---|
| Cold start (publish → install) | ~9 ms | ~390 ms | ~437 ms |
| Iteration (publish --push) | ~7 ms | ~106 ms | ~125 ms |
Start benchmarks locally
To run all benchmarks (including yalc tools), install the required Node.js packages once:
npm install --prefix target/bench_deps yalc @jimsheen/yalcThen export the paths:
export BENCH_YALC_JS=$PWD/target/bench_deps/node_modules/yalc/src/yalc.js
export BENCH_JIMSHEEN_YALC_JS=$PWD/target/bench_deps/node_modules/@jimsheen/yalc/dist/yalc.jsRun all benchmarks:
cargo bench --bench publish_installRun only kley benchmarks (no env vars required):
cargo bench --bench publish_install -- kleyFilter by scenario:
cargo bench --bench publish_install -- cold_start
cargo bench --bench publish_install -- iteration_push| Variable | Default | Description |
|---|---|---|
KLEY_HOME |
~ (home directory) |
The directory where kley stores its registry ($KLEY_HOME/.kley/). By default, kley uses your system's home directory. Override this to store the registry in a custom location (e.g., for CI/CD or isolated test environments). |
KLEY_USE_NPM_COMMAND |
npm |
Override the npm executable path. Useful for testing or when npm is not in PATH. |
KLEY_USE_PNPM_COMMAND |
pnpm |
Override the pnpm executable path. |
KLEY_USE_YARN_COMMAND |
yarn |
Override the yarn executable path. |
Contributions are welcome! Please feel free to share your feedback and suggestions.
This project is inspired by great tools like yalc. The main advantage of kley is that it is a single, self-contained binary with no dependency on Node.js. This means you can manage packages regardless of your current Node.js version or any issues with npm itself.
This project is licensed under the MIT License - see the LICENSE file for details.


