Skip to content

Repository files navigation

ImpJS

Everything that you need to create CLI easy! TypeScript or JavaScript → single native binary. No Node.js, no node_modules, no build step.

Installation

Quick install (Linux/macOS)

curl -fsSL https://raw.githubusercontent.com/snatvb/imp/main/scripts/install.sh | bash

This will:

  • Auto-detect your OS and architecture
  • Download the latest release to ~/.local/bin/imp
  • Add ~/.local/bin to your PATH if needed

Install specific version

curl -fsSL https://raw.githubusercontent.com/snatvb/imp/main/scripts/install.sh | bash -s -- v0.1.0

Homebrew (macOS/Linux)

brew tap snatvb/brew
brew install snatvb/brew/imp

Scoop (Windows)

scoop bucket add imp https://github.com/snatvb/scoop-imp
scoop install imp

Cargo (from source)

cargo install imp-cli

Manual download

Download the latest binary from GitHub Releases.

Why

You write a CLI tool in TypeScript. Today you ship a 200MB node_modules folder and hope the user has the right Node version. With imp you ship one ~11MB binary that just runs.

Node.jsImpJS
Distributionneeds Node + depsone binary (.exe or no ext)
Cold start~80ms<5ms
TypeScriptneeds buildnative (oxc at parse)
Bin size~100MB~11MB

What you get

  • .ts runs directly, no transpiler
  • imp:fsreadFile, writeFile, walk, glob, FileHandle, WriteHandle
  • imp:parsers — JSON, YAML, TOML, XML, RON, CSV, MessagePack in one module
  • imp:cryptorandomBytes, randomHex, randomUUID, randomInt, hmac, aesEncrypt, aesDecrypt, timingSafeEqual
  • imp:encodingbase64, hex, utf8, uri encode/decode
  • imp:hashmd5, sha1, sha256, sha512, blake3
  • imp:envparseIni, parseDotenv, expand, merge, loadFile
  • imp:inqconfirm, select, prompt, multiSelect, password, editor
  • imp:subprocessrun(cmd, args, options) with cwd/env/input/timeout/signal/encoding
  • imp:signal — OS signal handlers (SIGINT, SIGTERM, SIGHUP, SIGQUIT, SIGBREAK)
  • imp:timeDuration, ImpDate, ImpTime, ImpDateTime, ImpLocalDateTime
  • imp:clap — CLI argument parsing with Parser
  • imp:sysinputSimulate, stdin for system-level I/O
  • Duration, ByteBuffer, RsString — Rust-backed primitives

Quick start

# scaffold TypeScript types (optional — for IDE support)
imp init
# run a script
imp run hello.ts
imp run path/to/script.ts arg1 arg2
// hello.tsimport{readFile,writeFile}from"imp:fs"import{json,yaml}from"imp:parsers"constraw=awaitreadFile("config.json","utf8")constdata=json.parse(raw)awaitwriteFile("config.yaml",yaml.stringify(data))console.log("Converted JSON → YAML")

Compile to a binary

The whole point. One file, no runtime, no installer on the target.

# Windows
imp compile hello.ts hello.exe
.\hello.exe
# macOS / Linux
imp compile hello.ts hello
./hello

On Windows you can omit .exe and it's added automatically. On macOS / Linux the output keeps the exact name you pass.

The result is a single ~11MB binary containing the QuickJS runtime and your bundled script. No Node, no node_modules, no shared libs. Copy it to another machine, run it.

API Examples

File system

import{readFile,writeFile,walk,glob,open,openWrite}from"imp:fs"consttext=awaitreadFile("data.txt","utf8")awaitwriteFile("out.txt","result")forawait(constentryofwalk("./src")){console.log(entry.path)}constfiles=awaitglob("./src","**/*.ts")for(constfileoffiles){console.log(file)}// Streaming with FileHandle — using auto-closes via Symbol.dispose
using fh=awaitopen("large.bin",4096)constchunk=awaitfh.read()// WriteHandle
using wh=awaitopenWrite("output.txt","w")awaitwh.write("hello")

Parsers

import{json,yaml,toml,csv,xml}from"imp:parsers"constobj=json.parse('{"a": 1}')conststr=yaml.stringify({key: "value"})constcfg=toml.parse("[server]\nport = 8080")constrows=csv.parse("name,age\nAlice,30")

Crypto

import{randomBytes,randomUUID,hmac,aesEncrypt,aesDecrypt}from"imp:crypto"constbytes=randomBytes(32)constid=randomUUID()constsig=hmac("sha256","secret","message")constkey=randomBytes(32)constiv=randomBytes(12)constencrypted=aesEncrypt("aes-256-gcm",key,plaintext)constdecrypted=aesDecrypt("aes-256-gcm",key,encrypted)

Encoding

import{base64,hex,utf8,uri}from"imp:encoding"constencoded=base64.encode("hello")constdecoded=base64.decode(encoded)consthexStr=hex.encode(bytes)constbuf=utf8.encode("text")constsafe=uri.encode("path/to/file")

Hashing

import{sha256,blake3}from"imp:hash"consthash=sha256("hello world")consth3=blake3("data","hex")

Environment

import{parseIni,parseDotenv,expand,loadFile}from"imp:env"constini=parseIni("[db]\nhost = localhost\nport = 5432")constenv=parseDotenv("FOO=bar\nBAZ=qux")constexpanded=expand("HOME=$HOME/user")constconfig=awaitloadFile(".env")

Interactive prompts

import{prompt,select,confirm,multiSelect}from"imp:inq"constname=awaitprompt("What is your name?")constchoice=awaitselect("Pick one",["a","b","c"])constok=awaitconfirm("Continue?",true)constpicks=awaitmultiSelect("Pick many",["x","y","z"])

Subprocess

import{run}from"imp:subprocess"constresult=awaitrun("git",["status"])console.log(result.stdout,result.code)constr2=awaitrun("echo",["hello"],{timeout: 5000})

Signals

import{signal}from"imp:signal"constdispose=signal.on("SIGINT",()=>{console.log("Interrupted!")process.exit(0)})signal.once("SIGTERM",()=>cleanup())

Date and time

import{Duration,ImpDate,ImpDateTime}from"imp:time"constd=Duration.seconds(30)consttoday=ImpDate.today()constnow=ImpDateTime.now()constfmt=now.format("%Y-%m-%d %H:%M:%S")

Built-in globals

No imports needed — available everywhere:

GlobalDescription
consolelog, error, warn, info, assert, trace
processcwd(), exit(), env, argv, pid, platform
pathresolve, join, basename, dirname, extname, relative, normalize
fetchHTTP client (Web standard fetch, Request, Response, Headers)
URL, URLSearchParamsURL parsing and manipulation
AbortController, AbortSignalRequest cancellation
setTimeout, setInterval, setImmediateAsync timers
TextEncoder, TextDecoderUTF-8 encode/decode
BufferByte buffer (Node.js compatible subset)

CLI args

importclapfrom"imp:clap"constparser=newclap.Parser().name("hello").arg({name: "name",long: "name",action: "set",help: "who to greet"}).arg({name: "count",short: "c",long: "count",action: "set",help: "how many times"})constresult=parser.parse(clap.args)if(String(result.type)==="result"){constname=String(result.name??"world")constcount=Number(result.count??1)for(leti=0;i<count;i++){console.log(`Hello, ${name}!`)}}
$ imp run hello.ts --name Alice --count 3
Hello, Alice!
Hello, Alice!
Hello, Alice!
$ imp run hello.ts --help
Usage: hello [OPTIONS] [NAME]

CLI

imp <file># run a script
imp run <file> [args...] # same, explicit
imp compile <file><output># bundle to native binary
imp init [path] # scaffold imp.d.ts + tsconfig.json

Examples

See examples/ — 7 real-world scripts: HTTP client, config format converter, parallel CSV stats, markdown renderer, interactive scaffolder, tail -f clone, and a concurrent file sorter.

Building from source

Cross-compile Windows and Linux binaries from macOS:

# Install cross-compilation toolchains
brew install mingw-w64 # Windows x64 linker
brew install messense/macos-cross-toolchains # Linux x86_64 linker
cargo install cargo-zigbuild && brew install zig # Linux ARM64# Build all targets
cargo build --release -p cli # macOS arm64
cargo build --release --target x86_64-pc-windows-gnu -p cli # Windows x64
cargo build --release --target x86_64-unknown-linux-gnu -p cli # Linux x86_64
cargo zigbuild --release --target aarch64-unknown-linux-gnu -p cli # Linux ARM64

Output binaries:

TargetPathSize
macOS arm64target/release/imp~11 MB
Windows x64target/x86_64-pc-windows-gnu/release/imp.exe~9.6 MB
Linux x86_64target/x86_64-unknown-linux-gnu/release/imp~10 MB
Linux ARM64target/aarch64-unknown-linux-gnu/release/imp~8.7 MB

All binaries are self-contained — no system OpenSSL/libssl required. Only glibc and ca-certificates needed on target Linux machines.

License

MIT — https://github.com/snatvb/imp

About

ImpJS is tiny fast runtime envinroment for JS targets on cli tools

Resources

Stars

12 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages