cxpher 0.1.0 → 2.0.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/README.md ADDED
@@ -0,0 +1,512 @@
1
+
2
+ <div align="center">
3
+
4
+ <img src="https://gitlab.com/agentics-ai/cxpher/-/raw/master/assets/brand/cXpher.png?ref_type=heads&inline=false" alt="Agentics">
5
+
6
+ ### cXpher · The Agentics Package Manager
7
+
8
+ **Just cXpher it.**
9
+
10
+ The safest way to ship JavaScript. Full stop. The only JavaScript toolchain that ships encrypted, native binaries with multi-layer runtime protection, debugger detection, and zero-disk source delivery. Package manager. Bundler. Encryptor. Compiler. One tool.
11
+
12
+ [![npm](https://img.shields.io/npm/v/cxpher)](https://www.npmjs.com/package/cxpher)
13
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
14
+ [![GitLab](https://img.shields.io/badge/GitLab-agentics--ai%2Fcxpher-orange)](https://gitlab.com/agentics-ai/cxpher)
15
+
16
+ </div>
17
+
18
+ ---
19
+
20
+ ## What is cXpher?
21
+
22
+ cXpher is a complete JavaScript toolchain that does what no other tool on the market does: it takes your JavaScript or TypeScript project, bundles it, encrypts the source, generates a native binary with the encrypted payload embedded inside, and compiles it into a single executable wrapped in a defense-in-depth runtime envelope. **Your source code never reaches disk at runtime, your binary refuses to run under debuggers, and every build is cryptographically unique.**
23
+
24
+ It is also a fully functional package manager with dependency resolution, a global content-addressable store, lockfile management, and parallel installation — competing directly with npm, yarn, pnpm, and bun on the package management side, while offering a compilation pipeline that none of them have and a security posture none of them attempt.
25
+
26
+ Output is a single native binary for the OS / arch of your choice — **Linux, macOS, Windows, x64, x86, ARM64 or ARM32** — produced from any Node-compatible JavaScript or TypeScript source. A single `cxpher compile-all` produces every binary plus a generated Node CLI wrapper into `dist/` in one pass. Scripts in your `package.json` run with a single command (`cxpher <script>`, yarn-style), and the interactive surface is a clack-prompts-style flow with select menus, validated text inputs, and a clean two-accent palette.
27
+
28
+ ## Why cXpher Exists
29
+
30
+ Every JavaScript bundler on the market produces readable output. Minification is obfuscation theatre — any competent engineer can reverse it in an afternoon. Bytecode compilation (V8 snapshots, Bun's bytecode) is equally reversible with widely available decompilers. Even tools that produce "binaries" (pkg, nexe, bun --compile) embed the source as plaintext or trivially-decompiled bytecode inside the executable.
31
+
32
+ If you're shipping a commercial Node.js application, a proprietary CLI tool, a paid API server, or an internal enterprise tool, **with every other toolchain on the market, your source code is exposed.** There is currently no production-grade JavaScript packaging solution that takes source protection seriously — except cXpher.
33
+
34
+ cXpher doesn't obfuscate. It doesn't minify and hope. It encrypts with modern symmetric crypto, hardens the runtime against debuggers, removes the disk surface entirely, randomises every component of every build, and wraps the result in a binary that requires real reverse-engineering effort to crack. **Software-only protection has a mathematical ceiling — but cXpher pushes that ceiling as high as it will go.**
35
+
36
+ ## The Safest Package Manager Built for JavaScript
37
+
38
+ cXpher is the only JavaScript toolchain that combines a full package manager with a binary protection stack designed by treating "what would a determined attacker do?" as a first-principles question. The result is a defense-in-depth model with layered protections at every stage of the build and runtime lifecycle.
39
+
40
+ ### Defense in Depth
41
+
42
+ **At build time:**
43
+
44
+ - Strong, industry-standard symmetric encryption with per-build cryptographically random keying material.
45
+ - Per-build randomised key handling. No two binaries built from the same source share a key, a layout, or an ordering. Bulk extraction across versions is mathematically impossible.
46
+ - Per-build randomised IVs, per-build randomised auxiliary material. Every build is unique even to itself.
47
+ - Build artifacts isolated to a unique temporary directory under the OS temp root, wiped before the binary is returned. **Nothing intermediate ever sits in your project tree.**
48
+ - No `--keep-build` escape hatch. The build pipeline does not allow C source, header files, or intermediate state to be preserved on disk for inspection.
49
+
50
+ **At runtime:**
51
+
52
+ - **Zero-disk source delivery.** The primary execution path streams decrypted source directly into the JavaScript interpreter through an in-memory channel. No filesystem entry is ever created in the primary path — your source has no path on the disk, no entry in process listings, no name to grep for.
53
+ - **Native debugger-resistance on every platform.** Linux, macOS, and Windows each ship platform-specific runtime checks that detect debugger attachment and exit silently. The exits are silent on purpose: an attacker cannot learn which check fired or what triggered it.
54
+ - **Timing-attack detection.** The runtime self-monitors execution timing against a tight budget. Single-step debugging blows the budget and the process exits silently within milliseconds.
55
+ - **Key material never exists whole.** The encryption key is reconstructed inside the running process only at the moment of decryption, wiped immediately after use, and never appears as a contiguous byte sequence anywhere in the binary's data sections.
56
+ - **Out-of-band argument passthrough.** Command-line arguments are forwarded to the wrapped script through a channel that doesn't materialise them on disk or in process metadata.
57
+ - **Hardened control flow.** The runtime stub is generated with opaque predicates and a non-trivial decryption flow that resists static analysis.
58
+
59
+ **At distribution time:**
60
+
61
+ - One command (`cxpher compile-all`) produces native binaries for **every supported platform and architecture in a single pass**, plus a generated Node wrapper that auto-selects the right binary at install time. Ship the entire matrix as one npm package.
62
+ - Compile cache disabled by default — `--cache` is opt-in. Every fresh build is a clean build with new key material unless you explicitly opt back in.
63
+
64
+ ### What this means in practice
65
+
66
+ A determined reverse engineer with significant time and the right tooling can eventually defeat any software-only protection. That's not specific to cXpher, that's the mathematical floor of running encrypted code on hardware you don't control. What cXpher does is **push the cost of extraction from "five minutes with a tutorial" to "one to two weeks of dedicated, skilled reverse engineering effort"** — which rules out 99.9% of would-be attackers, leaves only adversaries with serious budget and intent, and protects against every realistic threat short of a nation-state.
67
+
68
+ For the remaining cases — where you need a hard floor against any conceivable attacker — cXpher is the strongest building block. Combine it with server-side key fetch, hardware TEE (SGX, TrustZone, Secure Enclave), or licensed runtime checks and you have a protection model nothing in the JavaScript ecosystem can match.
69
+
70
+ ## How the Pipeline Works (High Level)
71
+
72
+ The compilation flow has four high-level stages:
73
+
74
+ 1. **Bundle.** Detect your project's entry point and bundle all source files and `node_modules` into a single JavaScript blob. Self-contained, zero external requirements.
75
+ 2. **Encrypt + randomise.** The bundle is wrapped with an argv-forwarding shim and encrypted with per-build random keying material. The key is decomposed across the binary in a layout that's different for every build.
76
+ 3. **Generate C runtime.** A platform-specific runtime stub is generated, containing the encrypted payload, the key reconstruction logic, the anti-tampering checks, and the in-memory source delivery channel. The stub is compiled into the final binary with full optimizations.
77
+ 4. **Compile to native binary.** GCC, Clang, MinGW, or MSVC produces a standard ELF (Linux), Mach-O (macOS), or PE (Windows) executable. The binary is fully self-contained — no runtime dependency on OpenSSL, libcrypto, or any other shared library beyond libc.
78
+
79
+ The runtime stub's internal mechanics are intentionally not enumerated. They are designed to be effective, not catalogued for would-be attackers. What matters externally: the binary runs anywhere the target platform's libc runs, your source never reaches disk, and your binary refuses to run under inspection.
80
+
81
+ ### Standalone Mode (Node SEA)
82
+
83
+ With `--standalone`, cXpher uses Node.js Single Executable Applications (SEA) to embed the entire Node.js runtime into the binary alongside the encrypted payload. The result is a single file that runs on any machine without Node.js installed:
84
+
85
+ ```bash
86
+ cxpher compile --standalone
87
+ # Produces a ~50-90MB binary that runs anywhere, encrypted, zero dependencies
88
+ ```
89
+
90
+ The SEA path runs the same protection stack as the standard path, plus eliminates the dependency on a system Node.js. No temp files, no disk I/O for the source, pure in-memory execution inside the embedded V8 runtime.
91
+
92
+ ## cXpher vs Everything Else
93
+
94
+ | Capability | npm | yarn | pnpm | bun | cXpher |
95
+ |---|---|---|---|---|---|
96
+ | Package management | ✓ | ✓ | ✓ | ✓ | ✓ |
97
+ | Bundling | ✗ | ✗ | ✗ | ✓ | ✓ |
98
+ | Source encryption | ✗ | ✗ | ✗ | ✗ | **✓ (strong symmetric)** |
99
+ | Native binary output | ✗ | ✗ | ✗ | ✗¹ | **✓** |
100
+ | Standalone binaries | ✗ | ✗ | ✗ | ✗ | **✓ (Node SEA)** |
101
+ | Per-build randomised keying | ✗ | ✗ | ✗ | ✗ | **✓** |
102
+ | Zero-disk source delivery at runtime | ✗ | ✗ | ✗ | ✗ | **✓** |
103
+ | Native debugger detection | ✗ | ✗ | ✗ | ✗ | **✓ (Linux + macOS + Windows)** |
104
+ | Anti-tampering timing checks | ✗ | ✗ | ✗ | ✗ | **✓** |
105
+ | Cross-platform compilation | ✗ | ✗ | ✗ | ✗ | **✓ (10 targets)** |
106
+ | Single-pass multi-target build | ✗ | ✗ | ✗ | ✗ | **✓ (`compile-all`)** |
107
+ | Content-addressable store | ✗ | ✗ | ✓ | ✗ | **✓** |
108
+ | Hardlink installs | ✗ | ✗ | ✓ | ✗ | **✓** |
109
+ | Parallel resolution | ✗ | ✗ | ✓ | ✓ | **✓** |
110
+ | Multi-registry fallback | ✗ | ✗ | ✗ | ✗ | **✓** |
111
+ | Local package resolution (`file:`/`link:`) | ✓ | ✓ | ✓ | partial | **✓** |
112
+ | HTTP keep-alive pooling | ✗ | ✗ | ✓ | ✓ | **✓** |
113
+ | Framework auto-detection | ✗ | ✗ | ✗ | ✗ | **✓** |
114
+
115
+ <sup>¹ Bun's `bun build --compile` produces a binary, but the source is embedded as bytecode that can be trivially decompiled. It is not protected.</sup>
116
+
117
+ ### Why not just use Bun's `--compile`?
118
+
119
+ Bun's compile flag produces a binary that contains JavaScriptCore bytecode. Bytecode is not source protection — it is a performance optimization. Decompilers for JS bytecode exist and are actively maintained. The original source structure, variable names, and logic are recoverable in minutes by anyone with the right tool.
120
+
121
+ cXpher ships a fundamentally different class of artifact: a compiled native binary with encrypted source, per-build randomised key material that never exists whole on disk, runtime debugger detection, timing-attack defences, and a delivery channel to Node that never touches the filesystem. Recovering source from an cXpher binary requires real reverse-engineering against a hardened runtime — typically days to weeks of dedicated effort by a skilled adversary, not minutes with a decompiler.
122
+
123
+ ### Why not pkg or nexe?
124
+
125
+ `pkg` and `nexe` are unmaintained, do not encrypt source code, have poor Node.js version support, and embed raw JavaScript or V8 snapshots in the binary. They solve distribution (single-file output) and nothing else.
126
+
127
+ cXpher solves both distribution AND protection. And it's also your package manager.
128
+
129
+ ## Installation
130
+
131
+ ```bash
132
+ npm install -g cxpher
133
+ ```
134
+
135
+ ## Quick Start
136
+
137
+ ```bash
138
+ # Initialize a project (interactive)
139
+ cxpher init
140
+
141
+ # Or scaffold instantly with sensible defaults
142
+ cxpher init -y
143
+
144
+ # Install dependencies
145
+ cxpher
146
+
147
+ # Add a package
148
+ cxpher add express
149
+
150
+ # Run any script defined in package.json - yarn-style
151
+ cxpher start
152
+ cxpher test
153
+ cxpher dev
154
+
155
+ # Build (runs your build script)
156
+ cxpher build
157
+
158
+ # Compile to encrypted native binary
159
+ cxpher compile
160
+
161
+ # Compile to standalone binary (embeds Node.js)
162
+ cxpher compile --standalone
163
+
164
+ # Cross-compile to a 32-bit Windows machine
165
+ cxpher compile -w32
166
+
167
+ # Or target a specific OS / arch combo
168
+ cxpher compile --target linux-x86
169
+ cxpher compile --target win-arm64
170
+
171
+ # Build every binary plus a CLI wrapper into dist/ in one shot
172
+ cxpher compile-all
173
+
174
+ # Same, plus ARM64 + ARM32 for linux and win (8 total)
175
+ cxpher compile-all --arm
176
+
177
+ # Just refresh the dist/cli.js wrapper, no binaries
178
+ cxpher compile-all --cli-only
179
+
180
+ # Add a local package (no npm round-trip, ever)
181
+ cxpher add ./packages/shared
182
+ cxpher add file:../sibling-pkg
183
+ cxpher add link:./local-dev-pkg # symlink instead of hardlink
184
+ cxpher add myname@file:./other-package # explicit alias
185
+ ```
186
+
187
+ ## Commands
188
+
189
+ ### Package Management
190
+
191
+ | Command | Description |
192
+ |---------|-------------|
193
+ | `cxpher` | Install all dependencies (like `yarn` with no args) |
194
+ | `cxpher init` | Initialize a new project - interactive flow with select menus for template, module system and license |
195
+ | `cxpher init -y` | Scaffold with defaults, skip prompts |
196
+ | `cxpher init --template <tpl>` | Use a template: `default`, `cli`, `server`, `bare` |
197
+ | `cxpher add <pkg>` | Add a registry dependency (npm → yarn → jsr → github fallback chain) |
198
+ | `cxpher add -D <pkg>` | Add a dev dependency |
199
+ | `cxpher add <path>` | Add a **local** package by relative or absolute path (no registry round-trip) |
200
+ | `cxpher add file:<path>` | Same as above, explicit protocol — hardlinks the package tree into `node_modules/` |
201
+ | `cxpher add link:<path>` | Symlink the local package into `node_modules/` instead of hardlinking. Live edits picked up instantly. |
202
+ | `cxpher add portal:<path>` | Alias of `link:` |
203
+ | `cxpher add <name>@file:<path>` | Local install under an explicit dependency name |
204
+ | `cxpher remove <pkg>` | Remove a dependency |
205
+ | `cxpher install` | Install all from lockfile or `package.json` |
206
+ | `cxpher <script>` | **Run any script from `package.json` directly** - yarn-style. `cxpher test` runs the `test` script, `cxpher lint` runs the `lint` script, etc. Built-in subcommands still win precedence. |
207
+ | `cxpher run <script>` | Explicit form of the above |
208
+
209
+ ### Build & Compile
210
+
211
+ | Command | Description |
212
+ |---------|-------------|
213
+ | `cxpher build` | Run the project's build script (like `yarn build`) |
214
+ | `cxpher compile` | Auto-detect entry, encrypt, compile to native binary |
215
+ | `cxpher compile <file>` | Compile a specific file |
216
+ | `cxpher compile <dir>` | Compile a project directory |
217
+ | `cxpher compile-all` (`cxpher ca`) | Produce 4 binaries (linux/win × x64/x86) + a generated `dist/cli.js` Node wrapper in one pass. Prompts to backup or wipe an existing `dist/`. |
218
+ | `cxpher compile-all --arm` | As above plus arm64 + arm32 for both linux and win (8 binaries total). |
219
+ | `cxpher compile-all --cli-only` | Skip every binary, only regenerate `dist/cli.js` using the package name from `cxpher.json` / `package.json`. |
220
+ | `cxpher compiler` (`cxpher cp`) | Interactive picker. Multi-select every architecture (incl. macOS + ARM) plus a CLI-wrapper checkbox, then choose Standard / Standalone / Plain. Missing toolchains trigger the install-or-skip prompt before any compile fires. |
221
+ | `cxpher install-toolchain` (`cxpher it`) | Auto-detect package manager and install missing cross-compilers. Interactive multiselect when no target is specified. |
222
+ | `cxpher install-toolchain --all` | Install every cross-compiler cxpher knows about (linux/win × x64/x86/arm64/arm32). |
223
+ | `cxpher install-toolchain --arm` | Install only ARM cross-compilers (linux-arm64, linux-arm32, win-arm64, win-arm32). |
224
+ | `cxpher install-toolchain linux-arm32 win-x86` | Install only the named targets. |
225
+
226
+ ### Compile Flags
227
+
228
+ | Flag | Description |
229
+ |------|-------------|
230
+ | `--out <name>` | Output binary name. When omitted and a platform flag is set, the binary is suffixed `-<platform>-<arch>` automatically (e.g. `myapp-linux-x64`, `myapp-win-arm32`). |
231
+ | `--target <os[-arch]>` | Cross-compile target. Accepts plain OS (`linux`, `darwin`, `win`) or compound `<os>-<arch>` like `linux-x86`, `win-arm64`, `darwin-x64`, `linux-arm32`. The arch portion is parsed out and applied independently. |
232
+ | `-l` / `-w` / `-m` | Shorthand: Linux / Windows / macOS (defaults to `x64`) |
233
+ | `-l32` / `-w32` / `-m32` | Shorthand: 32-bit (`x86`) Linux / Windows / macOS |
234
+ | `-l64` / `-w64` / `-m64` | Explicit 64-bit shorthand - aliases of `-l` / `-w` / `-m` |
235
+ | `--arm` | ARM target. Defaults to `arm64`; combine with `-l` / `-w` for cross-compile. |
236
+ | `--arm64` / `--arm32` | Explicit ARM 64-bit or 32-bit. |
237
+ | `--arch <arch>` | Target architecture independently of `--target`. Accepts `x64` (default), `x86`, `arm64`, `arm32`, plus aliases (`ia32`, `i386`, `i686`, `amd64`, `x86_64`, `aarch64`, `armv7`, `armv7l`, `armhf`, `arm`). |
238
+ | `--minify` | Minify source before encryption |
239
+ | `--static` | Static linking (musl on Linux) |
240
+ | `--standalone` | Embed Node.js runtime for zero-dependency distribution. Host arch must match target arch - for cross-arch use the default (non-`--standalone`) path, which supports it via `gcc` / `mingw`. |
241
+ | `--no-encrypt` | Output bundled JS without encryption (debugging/development) |
242
+ | `--external <pkg,pkg>` | Exclude packages from the bundle |
243
+ | `--no-cache` | Skip the build cache |
244
+ | `--entry <file>` | Override entry-point detection |
245
+ | `--skip-build` | Skip framework auto-detection and pre-build step |
246
+
247
+ #### 32-bit (x86) toolchain requirements
248
+
249
+ To produce `x86` binaries on a 64-bit host, install the 32-bit cross-toolchain:
250
+
251
+ | Distro | Install |
252
+ |--------|---------|
253
+ | Arch / Manjaro | Enable `[multilib]` in `/etc/pacman.conf`, then `pacman -S lib32-gcc-libs lib32-glibc` |
254
+ | Debian / Ubuntu | `apt install gcc-multilib` |
255
+ | Fedora / RHEL | `dnf install glibc-devel.i686 libgcc.i686` |
256
+ | Windows targets | `pacman -S mingw-w64-gcc` (Arch) - provides both `x86_64-w64-mingw32-gcc` and `i686-w64-mingw32-gcc` |
257
+
258
+ #### ARM toolchain requirements
259
+
260
+ To cross-compile to ARM from an x64 host, install the matching cross-toolchain:
261
+
262
+ | Distro | Install |
263
+ |--------|---------|
264
+ | Arch / Manjaro | `pacman -S aarch64-linux-gnu-gcc arm-linux-gnueabihf-gcc` |
265
+ | Debian / Ubuntu | `apt install gcc-aarch64-linux-gnu gcc-arm-linux-gnueabihf` |
266
+ | Fedora / RHEL | `dnf install gcc-aarch64-linux-gnu gcc-arm-linux-gnu` |
267
+ | Windows ARM targets | Install [llvm-mingw](https://github.com/mstorsjo/llvm-mingw) (provides `aarch64-w64-mingw32-clang` and `armv7-w64-mingw32-clang`) |
268
+
269
+ ### Utilities
270
+
271
+ | Command | Description |
272
+ |---------|-------------|
273
+ | `cxpher info` | Show project configuration and build settings |
274
+ | `cxpher why <pkg>` | Explain why a package is installed |
275
+ | `cxpher list` | List all dependencies with installed versions |
276
+ | `cxpher clean` | Clear caches and build artifacts |
277
+ | `cxpher clean --store` | Also clear the global package store |
278
+
279
+ ## Framework Auto-Detection
280
+
281
+ When you run `cxpher compile` in a framework project, cXpher automatically detects the framework and runs the appropriate build step before compiling:
282
+
283
+ | Framework | Detection | Pre-build Command |
284
+ |-----------|-----------|-------------------|
285
+ | **Next.js** | `next.config.{js,mjs,ts}` | `next build` → compiles `.next/standalone/server.js` |
286
+ | **Nuxt** | `nuxt.config.{js,ts}` | `nuxt build` → compiles `.output/server/index.mjs` |
287
+ | **Vite** | `vite.config.{js,ts,mjs}` | `vite build` |
288
+ | **Vue CLI** | `@vue/cli-service` in deps | `vue-cli-service build` |
289
+ | **Create React App** | `react-scripts` in deps | `react-scripts build` |
290
+ | **TypeScript** | `tsconfig.json` | `tsc` → compiles from configured `outDir` |
291
+
292
+ If your project has a custom `build` script in `package.json`, cXpher uses that instead of the default framework command.
293
+
294
+ ## Configuration
295
+
296
+ cXpher uses standard `package.json` - no custom config files needed. Drop into any npm project and it just works. Build configuration goes under the `cxpher` key:
297
+
298
+ ```json
299
+ {
300
+ "name": "my-app",
301
+ "version": "0.1.0",
302
+ "main": "src/index.js",
303
+ "type": "module",
304
+ "scripts": {
305
+ "start": "node src/index.js",
306
+ "compile": "cxpher compile",
307
+ "dev": "node --watch src/index.js"
308
+ },
309
+ "cxpher": {
310
+ "encrypt": true,
311
+ "minify": false,
312
+ "target": "auto",
313
+ "static": false
314
+ },
315
+ "dependencies": {},
316
+ "devDependencies": {}
317
+ }
318
+ ```
319
+
320
+ The `cxpher` section defines defaults that are applied whenever `cxpher compile` is run. Flags passed on the command line override these defaults. The `cxpher` key is entirely optional - cXpher works with any standard `package.json` out of the box.
321
+
322
+ ## Architecture
323
+
324
+ ### Content-Addressable Store
325
+
326
+ Like pnpm, cXpher maintains a global package store at `~/.cxpher/store`. Packages are extracted once into the store and hardlinked into each project's `node_modules`. Installing the same version of a package across 100 projects consumes disk space once.
327
+
328
+ ### Parallel Resolution
329
+
330
+ All dependencies at each level of the tree are resolved concurrently. Combined with HTTP connection pooling (48 concurrent keep-alive sockets) and request deduplication (multiple packages requiring the same transitive dependency result in a single registry fetch), cXpher's resolution speed scales with your CPU and network, not your dependency count.
331
+
332
+ ### Build Cache
333
+
334
+ cXpher hashes your source tree before compilation. If the hash matches a previous build, the cached binary is returned instantly - no bundling, no encryption, no C compilation. The cache is stored at `~/.cxpher/build-cache` and is keyed on both source hash and build flags (target, minify, static, standalone, encrypt).
335
+
336
+ ### Lockfile
337
+
338
+ `cxpher.lock` uses a JSON format with integrity hashes for deterministic installs. The lockfile includes a top-level integrity hash of the entire dependency tree, enabling fast skip-resolution on subsequent installs when nothing has changed. Local-spec entries store the user-supplied spec (`file:./pkg`) rather than the resolved absolute path, so a project can move between machines and the local references re-resolve against the new project root.
339
+
340
+ ### Local Packages
341
+
342
+ `cxpher add` skips the registry entirely when given a local path. The package's own `package.json` is read off disk to pull the name, version, and transitive dependencies, and the resulting tree entry is marked `local: true` so the installer takes the local-install path. Three protocols are recognised:
343
+
344
+ - **`file:`** — copy/hardlink the local package tree into `node_modules/`. Default for bare paths (`./pkg`, `../sibling`, `/abs/path`, `~/lib`).
345
+ - **`link:`** — create a directory symlink instead. Live edits to the source pick up immediately. Equivalent to `npm link` / `yarn link` but scoped to a single dependency.
346
+ - **`portal:`** — alias of `link:`.
347
+
348
+ The local install hardlink skips the source package's own `node_modules/`, `.git/`, and `.cxpher-stored` markers so consumers don't recurse into the dependency's dependency tree.
349
+
350
+ ### Multi-Registry Fallback
351
+
352
+ `fetchPackument` walks an ordered list of registries on every lookup and short-circuits on the first 200. Default chain:
353
+
354
+ 1. `https://registry.npmjs.org` (npm)
355
+ 2. `https://registry.yarnpkg.com` (yarn — npm mirror with different caching)
356
+ 3. `https://npm.jsr.io` (jsr — the JS Registry)
357
+ 4. `https://npm.pkg.github.com` (github packages)
358
+
359
+ 404 / 401 / 403 from any registry is treated as "not here, try the next one" — the chain only throws when every registry has rejected the name. A 200 response is cached against the package name (not the registry), so subsequent lookups hit the cache regardless of which registry served the packument.
360
+
361
+ Override the chain via `~/.cxpher/config.json`:
362
+
363
+ ```json
364
+ {
365
+ "registry": "https://my-private-registry.example.com",
366
+ "registries": [
367
+ { "name": "company", "url": "https://npm.internal.example.com" }
368
+ ],
369
+ "scopeRegistries": {
370
+ "@my-org": "https://npm.internal.example.com"
371
+ }
372
+ }
373
+ ```
374
+
375
+ `registry` is the primary and prepended to the chain. `registries[]` is inserted before the defaults. `scopeRegistries` routes specific scopes (`@my-org/anything`) directly at a single registry, bypassing the chain.
376
+
377
+ ### `compile-all` and the Generated CLI Wrapper
378
+
379
+ `compile-all` produces every binary plus a generated Node script (`dist/cli.js`) in a single pass. The wrapper detects `process.platform` and `process.arch` at runtime and execs the matching binary out of the same directory — install one npm package that ships all four (or eight with `--arm`) binaries plus the wrapper as the `bin` entry, and the right binary runs on every platform the user installs it on.
380
+
381
+ When `dist/` already exists, a select prompt asks (the same prompt is shared with `cxpher compiler`):
382
+
383
+ - **Install alongside existing files** — overwrite only the targets being built and leave everything else in `dist/` untouched.
384
+ - **Backup current dist** — copies `dist/` to the next free `dist_backup_NNN/` (zero-padded 3-digit, based on the highest existing number in the project root), then wipes `dist/` and continues. Default.
385
+ - **Wipe and start fresh** — removes `dist/` without a backup.
386
+
387
+ `--cli-only` skips every binary and just (re)writes `dist/cli.js`. Useful for refreshing the wrapper after a package rename without rebuilding everything.
388
+
389
+ The wrapper's platform / arch detection covers all 10 produced targets:
390
+
391
+ | `process.platform` | `process.arch` | Binary name |
392
+ |--------------------|----------------|-------------|
393
+ | `linux` | `x64` | `<pkg>-linux-x64` |
394
+ | `linux` | `ia32` | `<pkg>-linux-x86` |
395
+ | `linux` | `arm64` | `<pkg>-linux-arm64` |
396
+ | `linux` | `arm` | `<pkg>-linux-arm32` |
397
+ | `win32` | `x64` | `<pkg>-win-x64.exe` |
398
+ | `win32` | `ia32` | `<pkg>-win-x86.exe` |
399
+ | `win32` | `arm64` | `<pkg>-win-arm64.exe` |
400
+ | `win32` | `arm` | `<pkg>-win-arm32.exe` |
401
+ | `darwin` | `x64` | `<pkg>-darwin-x64` |
402
+ | `darwin` | `arm64` | `<pkg>-darwin-arm64` |
403
+
404
+ Any unsupported platform/arch combination throws a clear `Unsupported platform:` / `Unsupported architecture:` error at wrapper startup instead of silently failing.
405
+
406
+ Before any binary is built, `compile-all` runs a toolchain preflight check against every requested target. If any cross-compilers are missing, you get a three-way prompt:
407
+
408
+ - **Install missing toolchains and build all** — calls `install-toolchain` for the missing targets, then continues.
409
+ - **Skip missing targets, build the rest** — drops the targets whose toolchains aren't available and carries on with the ones that are.
410
+ - **Abort** — does nothing.
411
+
412
+ `--yes` / `-y` auto-picks install. Individual compile failures during the loop are warned-and-skipped rather than fatal, so one bad target never kills the rest of the batch.
413
+
414
+ ### `install-toolchain` — auto-install cross-compilers
415
+
416
+ `cxpher install-toolchain` detects the host OS, distro and package manager and installs the cross-compilers required for any compile target. Supported package managers:
417
+
418
+ | OS / Distro | Manager | Notes |
419
+ |-------------|---------|-------|
420
+ | Arch / Manjaro / EndeavourOS | `pacman` + `yay` / `paru` | Repo packages via pacman with sudo; AUR / chaotic-aur via yay/paru without sudo |
421
+ | Debian / Ubuntu | `apt-get` | sudo |
422
+ | Fedora / RHEL / CentOS | `dnf` | sudo |
423
+ | openSUSE | `zypper` | sudo |
424
+ | Alpine | `apk` | sudo |
425
+ | macOS | `brew` | no sudo |
426
+ | Windows | `scoop` → `choco` → `winget` | first found, no sudo |
427
+
428
+ Invocations:
429
+
430
+ - `cxpher install-toolchain` — interactive. Lists every target with installed/missing flags, multiselect to pick what to install.
431
+ - `cxpher install-toolchain --all` — install every cross-compiler cxpher knows about.
432
+ - `cxpher install-toolchain --arm` — install only ARM (linux/win × arm64/arm32).
433
+ - `cxpher install-toolchain --x86` — install only 32-bit x86 (linux + win).
434
+ - `cxpher install-toolchain --win` / `--linux` — install all cross-compilers for that OS.
435
+ - `cxpher install-toolchain linux-arm32 win-x86 ...` — install only the named targets.
436
+ - `cxpher install-toolchain --yes` — skip the confirm prompt.
437
+
438
+ Targets with no native package on the current distro (Windows ARM on debian/fedora, macOS cross from anything) are reported with a manual-install link rather than silently skipped.
439
+
440
+ #### Fallback chain
441
+
442
+ Each target carries an ordered list of install steps. The installer walks the chain in order, verifies after each attempt, and only moves on if the current step fails or installs without putting the expected binary on PATH. Example: `linux-arm32` on Arch tries
443
+
444
+ 1. **Linaro precompiled binary** (`arm-linux-gnueabihf-gcc13-linaro-bin` from AUR) — fast, but depends on snapshots.linaro.org being reachable.
445
+ 2. **Official ARM GNU toolchain** — downloads the `arm-gnu-toolchain-14.2.rel1-x86_64-arm-none-linux-gnueabihf.tar.xz` archive from developer.arm.com, extracts to `/opt/cxpher-toolchains/arm-none-linux-gnueabihf/`, and symlinks both the Arm-official names (`arm-none-linux-gnueabihf-*`) and the standard names (`arm-linux-gnueabihf-*`) into `/usr/local/bin/`. Reusable for `linux-arm64` (triplet `aarch64-none-linux-gnu`) as well.
446
+ 3. **Build from source** (`arm-linux-gnueabihf-gcc` from AUR) — last resort, takes 1-2 hours.
447
+
448
+ Failed steps print a one-line reason and the chain continues. If every step fails the target is reported as still-missing in the final status section.
449
+
450
+ #### macOS native targets
451
+
452
+ On a macOS host, `darwin-x64` and `darwin-arm64` are detected as installed when native `clang` plus `xcode-select -p` both resolve — those are the prerequisites for `clang -arch x86_64` and `clang -arch arm64` native compiles. The `darwin-x64` / `darwin-arm64` install step on a macOS host calls `xcode-select --install` to trigger Apple's GUI installer for the Command Line Tools. From a Linux host, the same targets fall back to a `manual` osxcross install step (no automated install path exists for osxcross — it requires the Xcode SDK which can't be redistributed). Apple dropped 32-bit Intel and 32-bit ARM support in macOS Catalina (10.15), so no `darwin-x86` or `darwin-arm32` target exists.
453
+
454
+ #### `llvm-mingw` post-install symlink hook
455
+
456
+ On Arch, `llvm-mingw` from chaotic-aur / AUR installs into `/opt/llvm-mingw/bin/` which is not on `$PATH`. The `win-arm64` / `win-arm32` install steps chain into a `llvm-mingw-postlink` post-install method that symlinks every `aarch64-w64-mingw32-*` and `armv7-w64-mingw32-*` binary into `/usr/local/bin/`, so the cross-compilers are immediately resolvable after install with no manual `PATH` editing.
457
+
458
+ ## Security Model
459
+
460
+ cXpher is the strongest software-only source protection in the JavaScript ecosystem. It is not a DRM system — pure software protection has a theoretical floor that nothing can move without external trust (server-fetched keys or hardware TEE) — but it pushes that floor as high as it goes.
461
+
462
+ **What cXpher protects against:**
463
+
464
+ - Direct source code reading from the distributed binary.
465
+ - Trivial decompilation (the entire class of attack that defeats every other JS packager).
466
+ - Casual filesystem extraction during runtime (your source never reaches the disk in the primary execution path).
467
+ - Common debugger inspection — Linux, macOS, and Windows binaries refuse to run when a debugger is attached and exit silently.
468
+ - Single-step debugging — runtime timing checks detect single-stepping and silently exit within milliseconds.
469
+ - Bulk extraction across versions — every build is cryptographically independent.
470
+ - Casual copying of proprietary logic by employees, customers, or anyone with the binary but without serious tooling.
471
+ - Source exposure in containerised, cloud, or shared-filesystem deployments — there is no source file to expose.
472
+
473
+ **What cXpher does not protect against:**
474
+
475
+ - A nation-state-level adversary with unlimited reverse-engineering budget and hardware-debugger access.
476
+ - Runtime memory inspection performed at the kernel level by an attacker with root access on the host.
477
+ - Hardware-level memory dumps from a cold-boot or DMA attack.
478
+
479
+ For the realistic threat model — protecting commercial JavaScript IP against employees, customers, competitors, security researchers without unlimited budget, and the entire long tail of opportunistic extraction attempts — **cXpher is the strongest answer the JavaScript ecosystem has.**
480
+
481
+ This is the same security model as any compiled C, C++, Go, or Rust application — but with the added per-build-randomised, debugger-aware, disk-free runtime envelope that no other JavaScript packager attempts.
482
+
483
+ ## System Requirements
484
+
485
+ - **Node.js** ≥ 18 (≥ 20 for `--standalone`)
486
+ - **C compiler**: gcc, clang, or MSVC (for native compilation)
487
+ - **Bundler**: bun or esbuild (auto-detected)
488
+ - **postject** (only for `--standalone` mode)
489
+
490
+ ## Platform Support
491
+
492
+ | Platform | Native Binary | Standalone | Cross-Compile |
493
+ |----------|:---:|:---:|:---:|
494
+ | Linux x64 | ✓ | ✓ | - |
495
+ | Linux x86 (32-bit) | ✓ | - ¹ | ✓ (via `gcc -m32`) |
496
+ | Linux ARM64 | ✓ | ✓ | ✓ (via `aarch64-linux-gnu-gcc`) |
497
+ | Linux ARM32 | ✓ | - ¹ | ✓ (via `arm-linux-gnueabihf-gcc`) |
498
+ | macOS x64 | ✓ | ✓ | - |
499
+ | macOS ARM64 | ✓ | ✓ | - |
500
+ | Windows x64 | ✓ | ✓ | ✓ (via `x86_64-w64-mingw32-gcc`) |
501
+ | Windows x86 (32-bit) | ✓ | - ¹ | ✓ (via `i686-w64-mingw32-gcc`) |
502
+ | Windows ARM64 | ✓ ² | - | ✓ (via `aarch64-w64-mingw32-clang`) |
503
+ | Windows ARM32 | ✓ ² | - | ✓ (via `armv7-w64-mingw32-clang`) |
504
+
505
+ <sup>¹ Standalone (`--standalone`) embeds the host Node.js binary, and Node has not shipped 32-bit Linux or 32-bit ARM builds for years. For these targets use the default encrypted-binary path - it cross-compiles cleanly via the system C toolchain.</sup>
506
+ <sup>² Windows ARM requires the [llvm-mingw](https://github.com/mstorsjo/llvm-mingw) toolchain; the default MinGW packages from most distros only ship x86_64 / i686 targets.</sup>
507
+
508
+ ## License
509
+
510
+ MIT - [Agentics](https://agentics.co.za)
511
+
512
+ Built by Connor Etherington.
package/bin/cXpher.js ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { createRequire } from 'module';
4
+
5
+ const require = createRequire(import.meta.url);
6
+
7
+ require('../cli-wrapper.cjs');
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Fallback launcher for cxpher.
4
+ //
5
+ // Normally install.cjs (postinstall) copies the native binary over
6
+ // bin/<brand>.exe, so this file is never invoked. It exists for environments
7
+ // where postinstall doesn't run (--ignore-scripts) — pay the Node-process
8
+ // overhead and forward to the native binary anyway.
9
+
10
+ const { spawnSync } = require('child_process');
11
+ const { arch, constants } = require('os');
12
+ const path = require('path');
13
+
14
+ const pkg = require('./package.json');
15
+ const PACKAGE_NAME = pkg.name;
16
+ const BRAND = (pkg.cxpher && (pkg.cxpher.brand || pkg.cxpher.binPrefix)) || PACKAGE_NAME;
17
+
18
+ const PLATFORMS = {
19
+ 'linux-x64': 'linux-x64',
20
+ 'linux-ia32': 'linux-x86',
21
+ 'linux-arm64': 'linux-arm64',
22
+ 'linux-arm': 'linux-arm32',
23
+ 'darwin-x64': 'darwin-x64',
24
+ 'darwin-arm64': 'darwin-arm64',
25
+ 'win32-x64': 'win-x64',
26
+ 'win32-ia32': 'win-x86',
27
+ 'win32-arm64': 'win-arm64',
28
+ 'win32-arm': 'win-arm32'
29
+ };
30
+
31
+ function detectMusl() {
32
+ if (process.platform !== 'linux') return false;
33
+ const report = typeof process.report?.getReport === 'function' ? process.report.getReport() : null;
34
+ return report != null && report.header?.glibcVersionRuntime === undefined;
35
+ }
36
+
37
+ function getPlatformKey() {
38
+ const platform = process.platform;
39
+ let cpu = arch();
40
+ if (platform === 'darwin' && cpu === 'x64') {
41
+ const r = spawnSync('sysctl', ['-n', 'sysctl.proc_translated'], { encoding: 'utf8' });
42
+ if (r.stdout && r.stdout.trim() === '1') cpu = 'arm64';
43
+ }
44
+ return platform + '-' + cpu;
45
+ }
46
+
47
+ function getBinaryPath() {
48
+ const { existsSync } = require('fs');
49
+ const key = getPlatformKey();
50
+ const suffix = PLATFORMS[key];
51
+ if (!suffix) {
52
+ console.error('[' + PACKAGE_NAME + '] Unsupported platform: ' + process.platform + ' ' + arch());
53
+ process.exit(1);
54
+ }
55
+ const subpkg = PACKAGE_NAME + '-' + suffix;
56
+ const ext = process.platform === 'win32' ? '.exe' : '';
57
+
58
+ try {
59
+ const pkgDir = path.dirname(require.resolve(subpkg + '/package.json'));
60
+ const p = path.join(pkgDir, BRAND + ext);
61
+ if (existsSync(p)) return p;
62
+ } catch {}
63
+
64
+ const localPkg = path.join(__dirname, 'packages', suffix, BRAND + ext);
65
+ if (existsSync(localPkg)) return localPkg;
66
+
67
+ const localDist = path.join(__dirname, 'dist', BRAND + '-' + suffix + ext);
68
+ if (existsSync(localDist)) return localDist;
69
+
70
+ console.error('[' + PACKAGE_NAME + '] Native binary for ' + suffix + ' not found.');
71
+ console.error(' Try: npm install -g ' + PACKAGE_NAME);
72
+ process.exit(1);
73
+ }
74
+
75
+ function main() {
76
+ const binaryPath = getBinaryPath();
77
+ const result = spawnSync(binaryPath, process.argv.slice(2), {
78
+ stdio: 'inherit',
79
+ env: process.env
80
+ });
81
+ if (result.error) {
82
+ console.error('[' + PACKAGE_NAME + '] Failed to execute native binary at ' + binaryPath);
83
+ console.error(' ' + result.error.message);
84
+ process.exit(1);
85
+ }
86
+ if (result.signal) {
87
+ const signum = constants.signals[result.signal] != null ? constants.signals[result.signal] : 0;
88
+ process.exit(128 + signum);
89
+ }
90
+ process.exit(result.status != null ? result.status : 1);
91
+ }
92
+
93
+ main();
package/install.cjs ADDED
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+ // Postinstall for cxpher: replace bin/cXpher.js placeholder with the native
3
+ // binary from the matching optional-dependency subpackage. After this runs,
4
+ // invocations of `cxpher` (and aliases) exec the native binary directly on
5
+ // Unix. The fallback launcher logic stays inside bin/cXpher.js itself in
6
+ // case postinstall is skipped (--ignore-scripts).
7
+
8
+ const { spawnSync } = require('child_process');
9
+ const { copyFileSync, linkSync, unlinkSync, chmodSync, mkdirSync } = require('fs');
10
+ const { arch } = require('os');
11
+ const path = require('path');
12
+
13
+ const pkg = require('./package.json');
14
+ const PACKAGE_NAME = pkg.name;
15
+ const BRAND = (pkg.cxpher && (pkg.cxpher.brand || pkg.cxpher.binPrefix)) || PACKAGE_NAME;
16
+
17
+ const PLATFORMS = {
18
+ 'linux-x64': 'linux-x64',
19
+ 'linux-ia32': 'linux-x86',
20
+ 'linux-arm64': 'linux-arm64',
21
+ 'linux-arm': 'linux-arm32',
22
+ 'darwin-x64': 'darwin-x64',
23
+ 'darwin-arm64': 'darwin-arm64',
24
+ 'win32-x64': 'win-x64',
25
+ 'win32-ia32': 'win-x86',
26
+ 'win32-arm64': 'win-arm64',
27
+ 'win32-arm': 'win-arm32'
28
+ };
29
+
30
+ function getPlatformKey() {
31
+ const platform = process.platform;
32
+ let cpu = arch();
33
+ if (platform === 'darwin' && cpu === 'x64') {
34
+ const r = spawnSync('sysctl', ['-n', 'sysctl.proc_translated'], { encoding: 'utf8' });
35
+ if (r.stdout && r.stdout.trim() === '1') cpu = 'arm64';
36
+ }
37
+ return platform + '-' + cpu;
38
+ }
39
+
40
+ function main() {
41
+ const key = getPlatformKey();
42
+ const suffix = PLATFORMS[key];
43
+ if (!suffix) {
44
+ console.error('[' + PACKAGE_NAME + '] Unsupported platform: ' + process.platform + ' ' + arch());
45
+ console.error(' Supported: ' + Object.values(PLATFORMS).join(', '));
46
+ return;
47
+ }
48
+
49
+ const subpkg = PACKAGE_NAME + '-' + suffix;
50
+ const optionalDeps = pkg.optionalDependencies || {};
51
+ if (!optionalDeps[subpkg]) {
52
+ console.error('[' + PACKAGE_NAME + '] Native binary not available for ' + suffix + ' on this release channel.');
53
+ return;
54
+ }
55
+
56
+ const ext = process.platform === 'win32' ? '.exe' : '';
57
+ let src;
58
+ try {
59
+ const pkgDir = path.dirname(require.resolve(subpkg + '/package.json'));
60
+ src = path.join(pkgDir, BRAND + ext);
61
+ } catch {
62
+ console.error('[' + PACKAGE_NAME + '] Subpackage "' + subpkg + '" not installed (--omit=optional?).');
63
+ console.error(' bin/' + BRAND + '.js will run as the Node fallback launcher.');
64
+ return;
65
+ }
66
+
67
+ const binDir = path.join(__dirname, 'bin');
68
+ mkdirSync(binDir, { recursive: true });
69
+ const dest = path.join(binDir, BRAND + '.js');
70
+
71
+ try {
72
+ try { unlinkSync(dest); } catch {}
73
+ try {
74
+ linkSync(src, dest);
75
+ } catch (linkErr) {
76
+ if (linkErr.code === 'EXDEV' || linkErr.code === 'EPERM' || linkErr.code === 'ENOSYS') {
77
+ copyFileSync(src, dest);
78
+ } else {
79
+ throw linkErr;
80
+ }
81
+ }
82
+ if (process.platform !== 'win32') chmodSync(dest, 0o755);
83
+ } catch (err) {
84
+ console.error('[' + PACKAGE_NAME + '] Failed to place binary: ' + err.message);
85
+ console.error(' bin/' + BRAND + '.js will run as the Node fallback launcher.');
86
+ process.exitCode = 1;
87
+ }
88
+ }
89
+
90
+ main();
package/package.json CHANGED
@@ -1,11 +1,64 @@
1
1
  {
2
2
  "name": "cxpher",
3
- "version": "0.1.0",
4
- "license": "MIT",
3
+ "version": "2.0.0",
5
4
  "type": "module",
6
- "main": "app.js",
5
+ "description": "Agentics Package Manager — Encrypted native binary compiler + package manager for JavaScript",
6
+ "main": "bin/cXpher.js",
7
+ "bin": {
8
+ "cxpher": "./bin/cXpher.js",
9
+ "cXpher": "./bin/cXpher.js",
10
+ "cx": "./bin/cXpher.js",
11
+ "cX": "./bin/cXpher.js"
12
+ },
13
+ "files": [
14
+ "bin/",
15
+ "cli-wrapper.cjs",
16
+ "install.cjs"
17
+ ],
18
+ "cxpher": {
19
+ "brand": "cXpher"
20
+ },
7
21
  "scripts": {
8
- "start": "node $npm_package_main",
9
- "dev": "nodemon $npm_package_main"
22
+ "postinstall": "node install.cjs",
23
+ "build": "make clean; make install",
24
+ "publish": "npm publish --access public"
25
+ },
26
+ "keywords": [
27
+ "package-manager",
28
+ "bundler",
29
+ "compiler",
30
+ "binary",
31
+ "encryption",
32
+ "aes",
33
+ "native",
34
+ "standalone",
35
+ "cli",
36
+ "build-tool",
37
+ "javascript",
38
+ "typescript",
39
+ "node",
40
+ "sea"
41
+ ],
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "https://gitlab.com/agentics-ai/cxpher"
45
+ },
46
+ "homepage": "https://gitlab.com/agentics-ai/cxpher",
47
+ "bugs": {
48
+ "url": "https://gitlab.com/agentics-ai/cxpher/-/issues"
49
+ },
50
+ "author": "Connor Etherington <connor@agentics.co.za>",
51
+ "license": "MIT",
52
+ "optionalDependencies": {
53
+ "cxpher-darwin-arm64": "2.0.1",
54
+ "cxpher-darwin-x64": "2.0.1",
55
+ "cxpher-linux-arm32": "2.0.1",
56
+ "cxpher-linux-arm64": "2.0.1",
57
+ "cxpher-linux-x64": "2.0.1",
58
+ "cxpher-linux-x86": "2.0.1",
59
+ "cxpher-win-arm32": "2.0.1",
60
+ "cxpher-win-arm64": "2.0.1",
61
+ "cxpher-win-x64": "2.0.1",
62
+ "cxpher-win-x86": "2.0.1"
10
63
  }
11
64
  }