Hopper is a package manager for TVDOS.
Hopper is driven from the TVDOS shell with a verb noun command line. Every verb has a two-letter alias.
hop is a mandatory alias for Hopper.
hop search <query>
hop se <query>
By default the query is matched against each package's ProperName and HopperPackageName. A flag picks a different field:
hop search --provides <query>
hop search --requires <query>
hop search --description <query>
hop search --author <query>
Search covers both the locally-installed manifests under /tvdos/hopper/ and every mirror listed in /tvdos/hopper/mirrors.list. Each hit is prefixed with its origin — installed, or the mirror's HopperMirrorName.
hop list
hop ls
Lists every installed package — both the read-only system packages shipped with TVDOS and the user packages installed by Hopper — sorted by name. Each entry is tagged [system] or [user] so you can tell which packages remove and upgrade may touch. This is a purely local view; it reads the on-disk manifests only and never contacts a mirror.
hop install <package>
hop install <package> -v <constraint>
hop in <package>
hop upgrade <package>
hop up
Version constraints follow a small SemVer subset:
| Form | Meaning |
|---|---|
* |
any version |
1.2.3 |
exact |
1.* |
major 1, any minor/patch |
^1.2.3 |
>=1.2.3, <2.0.0 (major-compatible) |
~1.2.3 |
>=1.2.3, <1.3.0 (minor-compatible) |
>=1.2.3 |
comparison operators: >, >=, <, <=, = |
a,b |
comma-separated constraints are AND-ed |
Hopper resolves the full dependency graph before touching the disk, prints the plan — installs, upgrades, downgrades, reinstalls — and asks for confirmation. The resolver prefers the already-installed candidate when it still satisfies the constraint; otherwise it picks the newest compatible upstream candidate and backtracks (downgrading if necessary) when newer versions would conflict with the rest of the installed set.
hop remove <package>
hop out <package>
The matching manifest is looked up by HopperPackageName. Each entry in its SystemPackagePath is expanded against the system drive — * wildcards in the filename are supported — and Hopper prints every path that would be deleted before asking for confirmation.
Remote mirrors are configured in A:/tvdos/hopper/mirrors.list: one URL prefix per line, blank lines and # lines ignored, a trailing slash optional. Each mirror must serve:
<prefix>/mirror_manifest— key:value pairs describing the mirror.HopperMirrorNameis shown next to search hits.<prefix>/filelist— CSV withpackagename,version,manifest-filenamerows.
If no HTTP modem is attached, remote mirrors are skipped with a warning and Hopper continues with the installed set only.
A Hopper package is declared using the Hopper Manifest. Hopper Manifest has the following fields:
- HopperManifestVersion. The manifest version, currently
1.1. A manifest whoseHopperManifestVersionis newer than the running Hopper's own version (THE_VERSION) was written for a future Hopper:searchstill lists it (flagged "requires a newer Hopper"), butinstall/upgraderefuse it and ask you to update Hopper first. - HopperPackageName. Package name that Hopper understands
- HopperPackageVersion. The version. MUST STRICTLY follow Semantic Versioning 2.0.0
- MAJOR version when you make incompatible API changes
- MINOR version when you add functionality in a backward compatible manner
- PATCH version when you make backward compatible bug fixes
- HopperPackageMaintainer. The maintainer of the package
- HopperProvides. (plural) What does your package provides
- HopperRequires. (plural) Dependencies
- ProperName. The displayed name of the package. Must be human-readable
- ProperAuthor. The displayed author of the package. Must be human-readable
- ProperDescription. Human-readable description of the package
- Licence. Licence of the package (e.g.
MIT,GPL-2.0-only) - SupportMe. (optional, plural) Any donation links
- SystemPackagePath. (for packages shipped with TVDOS only) path descriptor for the package file(s)
- PackageFileList. (for upstream packages, plural) HTTP(S) path for the files.
- PackageFileInstallPrefix. (since version 1.1; optional) A single directory to install all of the package's payload files into together, instead of routing each file by extension. See "Install prefix" below.
User package directory: /hopper/{bin,include,manifests} — stores executables and manifests System package directory: /tvdos/hopper/*.hop.per — stores manifests for system packages only
<userdir>/bin: .js files<userdir>/include: .mjs files/manifests: .hop.per files
When a package is installed, the.hop.perfilse are downloaded, and following fields are appended:- SystemPackagePath. The actual path of the component files (relative against disk root. e.g. /hopper/bin/filename.js)
By default each payload file is routed by extension — .mjs to /hopper/include, everything else to /hopper/bin. A package that ships an executable alongside its own modules and data files needs those files to sit next to each other (e.g. so require("libfoo") resolves, or a data blob is found relative to the program). PackageFileInstallPrefix makes Hopper install the whole payload into one directory:
| Prefix form | Resolves against | Example install dir |
|---|---|---|
foo, ./foo |
the routed base dir | A:/hopper/bin/foo/ |
/foo, /foo/bar |
the system drive root A:/ |
A:/foo/, A:/foo/bar/ |
../x, A:x |
— (refused as invalid) | — |
For a local prefix the base directory is include/ only when every payload file is a .mjs (a pure library); otherwise it is bin/ so an application's modules and data land beside its executable. A prefix that starts with .. (parent escape) or a drive letter (A–Z, $) is rejected.
.alias files are special and always land directly in /hopper/bin (regardless of the prefix):
- A shipped
.aliasis installed to/hopper/binand its target command is rewritten to the executable's absolute install path under the prefix (its arguments are preserved). - For every executable (
.js,.app, …) in the payload, Hopper auto-generates an alias<executable-stem>.aliasin/hopper/binpointing at the installed executable, so it stays launchable by name. (Skipped only when the package already ships an alias file of that same name.) - If an alias name collides with a file from another package already in
/hopper/bin, Hopper asks whether to install it as<packagename>-<filename>instead, or abort.
HopperPackageName:doom
PackageFileInstallPrefix:wadplayer
PackageFileList:wadplayer.js;libwadplayer.mjs;resources.lfs;doom.alias
wadplayer.js,libwadplayer.mjs,resources.lfs→A:/hopper/bin/wadplayer/doom.alias→A:/hopper/bin/doom.alias, retargeted toA:/hopper/bin/wadplayer/wadplayer.jswadplayer.aliasis auto-generated atA:/hopper/bin/wadplayer.alias, also pointing toA:/hopper/bin/wadplayer/wadplayer.js(so bothdoomandwadplayerare launchable)
This repo ships its git hooks in the tracked githooks/ directory (so they are shared with every clone, unlike the per-machine .git/hooks/). Enable them once after cloning:
git config core.hooksPath githooks
What's there:
pre-commit— keepshopper.hop.per'sHopperPackageVersionin sync with the app version (THE_VERSION) declared inhopper.js. Whenever you commit, it readsTHE_VERSION, writes it into the manifest'sHopperPackageVersion, and re-stageshopper.hop.perso the published manifest always advertises the version of the code being shipped. So bumping a release is a one-line change toTHE_VERSION.