Everything that you need to create CLI easy! TypeScript or JavaScript → single native binary. No Node.js, no node_modules, no build step.
curl -fsSL https://raw.githubusercontent.com/snatvb/imp/main/scripts/install.sh | bashThis will:
- Auto-detect your OS and architecture
- Download the latest release to
~/.local/bin/imp - Add
~/.local/binto your PATH if needed
curl -fsSL https://raw.githubusercontent.com/snatvb/imp/main/scripts/install.sh | bash -s -- v0.1.0brew tap snatvb/brew
brew install snatvb/brew/impscoop bucket add imp https://github.com/snatvb/scoop-imp
scoop install impcargo install imp-cliDownload the latest binary from GitHub Releases.
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.js | ImpJS | |
|---|---|---|
| Distribution | needs Node + deps | one binary (.exe or no ext) |
| Cold start | ~80ms | <5ms |
| TypeScript | needs build | native (oxc at parse) |
| Bin size | ~100MB | ~11MB |
.tsruns directly, no transpilerimp:fs—readFile,writeFile,walk,glob,FileHandle,WriteHandleimp:parsers— JSON, YAML, TOML, XML, RON, CSV, MessagePack in one moduleimp:crypto—randomBytes,randomHex,randomUUID,randomInt,hmac,aesEncrypt,aesDecrypt,timingSafeEqualimp:encoding—base64,hex,utf8,uriencode/decodeimp:hash—md5,sha1,sha256,sha512,blake3imp:env—parseIni,parseDotenv,expand,merge,loadFileimp:inq—confirm,select,prompt,multiSelect,password,editorimp:subprocess—run(cmd, args, options)withcwd/env/input/timeout/signal/encodingimp:signal— OS signal handlers (SIGINT, SIGTERM, SIGHUP, SIGQUIT, SIGBREAK)imp:time—Duration,ImpDate,ImpTime,ImpDateTime,ImpLocalDateTimeimp:clap— CLI argument parsing withParserimp:sys—inputSimulate,stdinfor system-level I/ODuration,ByteBuffer,RsString— Rust-backed primitives
# 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")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
./helloOn 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.
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")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")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)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")import{sha256,blake3}from"imp:hash"consthash=sha256("hello world")consth3=blake3("data","hex")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")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"])import{run}from"imp:subprocess"constresult=awaitrun("git",["status"])console.log(result.stdout,result.code)constr2=awaitrun("echo",["hello"],{timeout: 5000})import{signal}from"imp:signal"constdispose=signal.on("SIGINT",()=>{console.log("Interrupted!")process.exit(0)})signal.once("SIGTERM",()=>cleanup())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")No imports needed — available everywhere:
| Global | Description |
|---|---|
console | log, error, warn, info, assert, trace |
process | cwd(), exit(), env, argv, pid, platform |
path | resolve, join, basename, dirname, extname, relative, normalize |
fetch | HTTP client (Web standard fetch, Request, Response, Headers) |
URL, URLSearchParams | URL parsing and manipulation |
AbortController, AbortSignal | Request cancellation |
setTimeout, setInterval, setImmediate | Async timers |
TextEncoder, TextDecoder | UTF-8 encode/decode |
Buffer | Byte buffer (Node.js compatible subset) |
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]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.jsonSee 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.
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 ARM64Output binaries:
| Target | Path | Size |
|---|---|---|
| macOS arm64 | target/release/imp | ~11 MB |
| Windows x64 | target/x86_64-pc-windows-gnu/release/imp.exe | ~9.6 MB |
| Linux x86_64 | target/x86_64-unknown-linux-gnu/release/imp | ~10 MB |
| Linux ARM64 | target/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.