- src/move.ts entry point with CLI parsing, --help, --version - src/cli.ts: parseCliArgs, printHelp, ParsedCliArgs, CliError, VERSION - src/config.ts: Config type, DEFAULT_CONFIG, resolveConfig - src/keeper.ts: synthetic-activity sweep + idle-watch loop - package.json bin entry + shebang for 'bun link' global install - install.sh: contributor bootstrap (will be repurposed; see DISTRIBUTION-PLAN.md for the end-user installer design) - DISTRIBUTION-PLAN.md captures the tabled end-user distribution work
8.5 KiB
Distribution Plan for move
Status: Tabled 2026-06-15. Revisit after the initial Gitea push lands and the repo is publicly reachable at https://gitea.cahlen.com/nokeo08/Move.
This document captures the analysis and decisions for distributing the
move CLI as an end-user-friendly tool, so we can resume without redoing
the discussion.
Decisions locked in
| Decision | Choice |
|---|---|
| Primary hosting | Self-hosted Gitea (https://gitea.cahlen.com/nokeo08/Move) |
| Default branch | master |
| Audience | Anyone who finds the repo (broader public-ish) |
| Effort budget for now | Smallest viable curl-able install.sh |
| Bun handling | Fail with a clear message when missing (no auto-install) |
| Reinstall behavior | Idempotent via a version-marker file; MOVE_FORCE=1 overrides |
| Install location | ~/.local/bin + ~/.local/share/move (XDG, no sudo), env-overridable |
move shim |
Bash wrapper script, not symlink |
| PATH rc files | Never modified; print instructions if PATH isn't right |
| Uninstaller | In scope; same curl ... | sh pattern |
| npm publishing | Out of scope |
Standalone binaries (bun build --compile) |
Future work; depends on nut.js native-bundling testing |
| Homebrew tap | Future work; cheap once releases exist |
Distribution-option survey (from the discussion)
Four options were weighed:
- Compiled standalone binary (
bun build --compile). Best UX (single binary, no Bun needed) but needs per-platform builds, a release pipeline, code signing on macOS, and depends on nut.js's prebuilt native.nodefiles bundling correctly under--compile— unverified. - Curl-piped installer — chosen. Single command, no clone, source-and-Bun model. Conventions used by bun, deno, rustup, nvm. Slight cold-start cost vs. compiled binary; tradeoff accepted.
- Package-manager distribution (
bun add -g, brew).moveis taken on npm; brew is macOS-centric; both add publishing overhead. Out of scope for first cut. - Status quo (clone +
./install.sh+bun link). Developer flow only; will be renamed todev-setup.sh.
Target end-user UX
curl -fsSL https://gitea.cahlen.com/nokeo08/Move/raw/branch/master/install.sh | sh
move --help
One command. No clone. No bun link. No chmod. After this the user has
a global move on their PATH.
install.sh design (end-user, curl-able)
- Shebang:
#!/usr/bin/env sh(POSIX). - Strict mode:
set -eu. Trap on errors with a clear "install failed at " message.
Top-to-bottom behavior
- Platform detection:
uname -s→Darwin/Linux/ else → "unsupported OS" + exit 1.uname -m→arm64/x86_64(mapped tox64) / else → "unsupported arch" + exit 1.- nut.js supports darwin-arm64/x64, linux-x64, win32-x64. WSL users fall under Linux.
- Bun check:
command -v bunpresent → continue.- Absent → print
Error: bun is not installed. Install from https://bun.sh (e.g. 'curl -fsSL https://bun.sh/install | bash'), then re-run.→ exit 1.
- Resolve paths:
INSTALL_DIR=${MOVE_PREFIX:-$HOME/.local/share/move}BIN_DIR=${MOVE_BIN_DIR:-$HOME/.local/bin}mkdir -pboth.
- Resolve version:
VERSION=${MOVE_VERSION:-master}.
- Idempotence check:
- If
$INSTALL_DIR/.installed-versionexists AND its content equals$VERSIONAND${MOVE_FORCE:-0}is not1:- Print
move <version> already installed at $BIN_DIR/move. Set MOVE_FORCE=1 to reinstall, or pass a different MOVE_VERSION. - Exit 0.
- Print
- If
- Clean install dir:
rm -rf "$INSTALL_DIR"/*(preserving the dir itself so user-set permissions on it are kept). - Download source:
TARBALL="https://gitea.cahlen.com/nokeo08/Move/archive/${VERSION}.tar.gz"curl -fsSL "$TARBALL" -o "$TMP"(mktemp) → on failure, fail loudly with the offending URL.tar -xzf "$TMP" -C "$INSTALL_DIR" --strip-components=1rm -f "$TMP"
- Install deps:
cd "$INSTALL_DIR" && bun install --production.--productionskips devDependencies (TypeScript, @types/bun); saves tens of MB and seconds per install. - Drop the wrapper:
Wrapper, not a symlink, so the path is absolute and stable regardless of how the user's shell resolves things.
cat > "$BIN_DIR/move" <<EOF #!/usr/bin/env sh exec bun "$INSTALL_DIR/src/move.ts" "\$@" EOF chmod +x "$BIN_DIR/move" - Write version marker:
printf '%s\n' "$VERSION" > "$INSTALL_DIR/.installed-version". - PATH sanity check:
- If
$BIN_DIRis not on$PATH, print:Warning: $BIN_DIR is not on your PATH. Add this to your shell rc: export PATH="$BIN_DIR:$PATH" - Do not mutate the user's rc files — that breaks the "clean uninstall" contract.
- If
- macOS Accessibility hint (carried over from current
install.sh). - Final message:
Installed move $VERSION at $BIN_DIR/move. Run 'move --help' to get started.
Env vars
| Var | Default | Purpose |
|---|---|---|
MOVE_VERSION |
master |
Branch or tag to fetch from Gitea. |
MOVE_PREFIX |
$HOME/.local/share/move |
Source install location. |
MOVE_BIN_DIR |
$HOME/.local/bin |
Wrapper install location. |
MOVE_FORCE |
unset | Force reinstall even if version marker matches. |
uninstall.sh design
curl -fsSL https://gitea.cahlen.com/nokeo08/Move/raw/branch/master/uninstall.sh | sh
Behavior:
set -eu.- Respect
MOVE_PREFIXandMOVE_BIN_DIRenv vars (same defaults as install). rm -f "$BIN_DIR/move".rm -rf "$INSTALL_DIR".- Print
Uninstalled. (Bun was not touched.).
Does not uninstall Bun — that's the user's runtime, not ours.
dev-setup.sh design (renamed from current install.sh)
The current install.sh is appropriate for contributors but inappropriate
as the end-user installer. Rename to dev-setup.sh. Content stays nearly
identical:
- Verify Bun is installed (hard-error if missing).
bun install.- macOS Accessibility hint.
- Trailing message updated to:
Done. Dev workflow: 'bun run start' to run, or 'bun link' to install the global 'move' command. End-user installer is install.sh.
README changes (when we resume)
## Installsection becomes the curl one-liner targetingmaster, plus a short note about env-var overrides and a pointer to uninstall.## Runsection collapses tomove/move --helpfor the global install path.- New
## Uninstallsection with the curl-pipe uninstall command. - New
## For contributorssection absorbing the existingbun run start/bun run src/move.ts/bun linkexamples; points atdev-setup.sh. ## Filestable updates: addinstall.sh(end-user, curl-able),uninstall.sh(end-user, curl-able),dev-setup.sh(contributor bootstrap).
Verification plan (when we resume)
sh -n install.shandsh -n uninstall.sh— POSIX syntax check.- Local end-to-end with overridden paths:
MOVE_PREFIX=/tmp/movetest MOVE_BIN_DIR=/tmp/movetest/bin ./install.sh /tmp/movetest/bin/move --help ./install.sh # second run: already-installed message MOVE_FORCE=1 ./install.sh # forced reinstall - Uninstall against the test prefix; confirm nothing left under it.
- Re-run existing acceptance tests (
tsc,--help,--version,--bogus) — should be unaffected; no source changes in this milestone. - After committing/pushing, end-to-end against the actual Gitea URL.
Caveat: Gitea archive URL shape
The plan assumes
https://gitea.cahlen.com/nokeo08/Move/archive/<ref>.tar.gz returns a
tarball whose top-level directory contains the project files (peeled by
--strip-components=1). This is the standard Gitea archive format, but
if your Gitea version produces a different layout the installer will need
a small tweak. The installer should error loudly with the offending URL
if extraction fails.
Open future work (out of scope for the resume session)
- Switch
MOVE_VERSIONdefault frommasterto "latest tag" via the Gitea API (/api/v1/repos/nokeo08/Move/tags) once tagged releases are routine. - Investigate
bun build --compile+ nut.js native-binary bundling for true zero-Bun installs. If it works, layer prebuilt binaries onto the curl installer so users without Bun also get one-command installs. - Homebrew tap for macOS users (cheap once a release pipeline exists).
- Possible: PowerShell installer (
install.ps1) for native Windows users without WSL.