On this page
Documentation
Everything you need to build, publish, and promote CLI tools with shipcli.
Getting Started
shipcli is a CLI-as-a-Product toolkit. It helps you scaffold a new CLI project, add shareable output, publish to npm, build cross-platform binaries, and generate landing pages.
Node.js 24 or newer is required. Binary builds additionally require Bun.
Install
npm install -g @shipcli/cliCreate your first CLI
npx @shipcli/create my-cli
cd my-cli
# Run your CLI
npm start -- --help
# Publish to npm
shipcli publish --bump patchProject Structure
A scaffolded project looks like this:
my-cli/
package.json # @shipcli/core + @shipcli/share deps
tsconfig.json # Strict TypeScript checks
tsconfig.build.json # Compile source to dist/
shipcli.config.ts # Typed shipcli defaults
test/ # Starter test
.github/workflows/ # CI workflow
src/
cli.ts # createCLI() entry point
share-card.ts # --share image template
commands/
index.ts # Main commandYour CLI imports @shipcli/core at runtime — it's a framework, not a template. This means you get updates by bumping a dependency version.
Project Configuration
Keep repeatable defaults in shipcli.config.ts. Command-line options take precedence, so the same config works locally and in CI.
import { defineConfig } from "@shipcli/core";
export default defineConfig({
name: "my-cli",
description: "A useful command-line tool",
build: {
entrypoint: "./src/cli.ts",
outDir: "dist",
targets: ["macos-arm64", "linux-x64"],
},
publish: { bump: "patch", access: "public" },
share: { enabled: true },
landing: { outDir: "web" },
});CLI Commands
shipcli init [name]
Scaffold a new CLI tool. Delegates to npx @shipcli/create.
shipcli init my-cli
shipcli init my-cli --description "A useful CLI" --no-install
shipcli init my-cli --no-gitshipcli publish
Version bump, git tag, and npm publish in one command.
shipcli publish
shipcli publish --bump minor
shipcli publish --dry-run
shipcli publish --skip-git
shipcli publish --access restricted| --bump <type> | Version bump type: major, minor, patch | patch |
| --dry-run | Run without actually publishing | false |
| --skip-git | Skip git commit and tag | false |
| --access <type> | npm access level | public |
A real publish requires a clean git working tree unless --skip-gitis supplied intentionally. Start with --dry-run.
shipcli build
Build cross-platform standalone binaries using Bun.
shipcli build
shipcli build --targets macos-arm64,linux-x64
shipcli build --out-dir bin
shipcli build --entrypoint src/cli.ts| --targets <list> | Comma-separated: macos-arm64, macos-x64, linux-x64, linux-arm64, windows-x64 | all |
| --out-dir <dir> | Output directory | dist |
| --entrypoint <path> | TypeScript or JavaScript CLI entry point | package metadata, bin, or src/cli.ts |
shipcli homebrew
Generate a Homebrew formula from your package.json.
shipcli homebrew
shipcli homebrew --repo owner/repo --output formula.rbshipcli changelog
Generate a CHANGELOG.md from git commits.
shipcli changelogshipcli landing init
Scaffold a Next.js landing page with terminal demo, install instructions, and feature showcase.
shipcli landing init
shipcli landing init --name my-cli --description "My awesome CLI"
shipcli landing init --out-dir site
shipcli landing init --force| --name <name> | Tool name | package.json name |
| --description <desc> | Tool description | package.json description |
| --out-dir <path> | Generated website directory | web |
| --force | Overwrite files in an existing web directory | false |
createCLI
Create a CLI application. Wraps Commander.js with shipcli conventions.
import { createCLI } from "@shipcli/core/cli";
const cli = createCLI({
name: "my-cli",
packageName: "my-cli", // npm package used by the update check
description: "My awesome CLI tool",
version: "0.1.0",
});
cli.command("run <target>")
.option("--verbose", "Verbose output")
.action((target, options) => {
// your logic
});
await cli.run();Automatically adds:
--jsonglobal flag- Auto update check (cached 24h, skipped in CI)
- Global error handler for uncaught exceptions
Output
Terminal output utilities. All status output goes to stderr so stdout stays clean for piping.
import { phase, status, success, error, fatal, hint } from "@shipcli/core/output";
phase("Analyzing repository"); // ==> Analyzing repository
status("Fetching commits..."); // Fetching commits...
success("Analysis complete"); // --> Analysis complete
error("Not found", "Check URL"); // Error: Not found
fatal("Cannot continue"); // Error + process.exit(1)
hint("Tip", "Use --verbose"); // Tip: Use --verbosetable(headers, rows)
ANSI-aware table with automatic column padding.
import { table, fmt } from "@shipcli/core/output";
console.log(table(
["Name", "Score", "Status"],
[
["react", fmt.score(95), "alive"],
["atom", fmt.score(0), "dead"],
]
));box(title, lines)
Unicode box drawing for highlighted output.
import { box } from "@shipcli/core/output";
console.log(box("Results", [
"Total: 42",
"Passed: 40",
"Failed: 2",
]));progressBar(current, total)
import { progressBar } from "@shipcli/core/output";
console.log(progressBar(7, 10));
// █████████████████████░░░░░░░░░ 70%Formatting
Color and style helpers via fmt.
import { fmt } from "@shipcli/core/output";
fmt.app("my-cli") // cyan
fmt.cmd("npm install") // bold cyan
fmt.key("name") // green
fmt.val("42") // yellow
fmt.dim("optional") // dimmed
fmt.bold("important") // bold
fmt.url("https://...") // underline cyan
fmt.score(85) // green (80+), yellow (50-79), red (<50)
fmt.grade("A") // green (A/B), yellow (C), red (D/F)Config
Persistent configuration stored at ~/.toolname/config.json. Supports dot-notation and migrations.
import { Config } from "@shipcli/core/config";
const config = new Config("my-cli").load();
config.set("github.token", "ghp_...");
config.get("github.token"); // "ghp_..."
config.has("github.token"); // true
config.delete("github.token");
config.save();Migrations
Transform config between versions:
const config = new Config("my-cli", {
migrations: [
(data) => {
// Rename old key
if (data.apiKey) {
data.token = data.apiKey;
delete data.apiKey;
}
return data;
},
],
}).load();Spinner
Lightweight spinner for async operations. Wraps nanospinner.
import { spinner } from "@shipcli/core/spinner";
const s = spinner("Loading...").start();
// On success:
s.success({ text: "Done!" });
// On error:
s.error({ text: "Failed" });Build
Build and distribution pipeline. Used by the shipcli CLI commands.
npm Publish
import { publish } from "@shipcli/build/npm-publish";
publish({
cwd: process.cwd(),
bump: "minor", // major | minor | patch
dryRun: false,
skipGit: false,
access: "public",
});Binary Build
Compile standalone executables using bun build --compile.
import { build } from "@shipcli/build/binary";
build({
cwd: process.cwd(),
outDir: "dist",
targets: [
{ name: "macos-arm64", bun: "bun-darwin-arm64", label: "macOS (Apple Silicon)" },
{ name: "linux-x64", bun: "bun-linux-x64", label: "Linux (x64)" },
],
});Homebrew Formula
import { generateFormula } from "@shipcli/build/homebrew";
generateFormula({
cwd: process.cwd(),
repo: "owner/repo",
output: "formula.rb",
});Changelog
import { generateChangelog } from "@shipcli/build/changelog";
generateChangelog({ cwd: process.cwd() });
// → Writes CHANGELOG.md grouped by Features / Fixes / OtherLanding
Scaffold a Next.js landing page for your CLI tool.
shipcli landing initGenerates a web/ directory with:
- Animated terminal demo component
- Click-to-copy installation tabs for npm and npx
- Feature showcase grid
- "Built with shipcli" footer (flywheel)
- Responsive navigation for workflow, output, and installation sections
- Tailwind CSS + dark mode
- SEO meta tags and Open Graph
Deploy to Vercel with cd web && vercel.
Packages
| @shipcli/core | CLI framework: output, config, commands, spinner |
| @shipcli/share | Shareable OG image generation |
| @shipcli/build | npm publish, binary builds, homebrew formulas |
| @shipcli/landing | Next.js landing page scaffolding |
| @shipcli/create | npx @shipcli/create scaffolding |
| @shipcli/cli | shipcli CLI orchestrator |
Packages are authored in strict TypeScript and published as ESM JavaScript with TypeScript declarations and source maps.
Browse the packages on npm or read the release changelog.