guest@6admin.io:~$

apt for Vim: the story of vim-apthogen (2012-2016)

Somewhere between 2012 and 2016, my Vim setup looked like a Debian system from the package manager’s point of view: a pile of bundle/ directories installed by hand and vim-pathogen doing the loading. Pathogen is honest about its role: it is dpkg, the dumb loader. Nobody had written the apt on top of it. So I did.

The result: vim-apthogen , published on vim.org on April 12, 2016 (Vim 7.0, 1136 downloads, a karma of 19/6, all numbers I am unreasonably proud of). The README line I still like: Like apt for dpkg, vim-apthogen is a frontend to vim-pathogen.

The metaphor, taken seriously

Once you commit to apt, the rest writes itself:

  • .apthogen.list: the package index, one name@https://github.com/author/repo line per plugin, exactly like a sources list.
  • .required.list, .minimal.list, .recomended.list: dependency tiers, straight from the Debian task-selection playbook.
  • bundle-availlable/ (typo included, it is authentic) holds every downloaded plugin; enabling one is a symlink into bundle/, disabling is removing the link. Apache admins will recognize a2enmod/a2dismod more than apt and I stand by that borrowing.

Two scripts, 291 lines of bash

apthogen (225 lines) speaks the verbs you expect: install, remove, enable, disable, update, upgrade, list, search, show. netinstall (66 lines) is the bootstrap: a curl | bash that builds a fresh ~/.vimnew, infects it with the required modules and asks which profile you want:

[S]lim               (Only Required Modules)
[M]inimal            (Default)
[H]ighly Recomended
[R]ecomended
[W]hynot
[A]ll                (Not Recomended For Testing Purpose)
[C]ancel
Enter your choice and press [Enter]:   R

And because a 2016 script could not just say “cloning”, the output is a biology lesson: Apthogen::Incubate, Apthogen::Infection, Apthogen::Propagate::required, Apthogen::Phagocyte::https://github.com/tpope/vim-pathogen, Apthogen::Evolve::nerdtree on upgrades. Infection as a feature. 2016 was a simpler time.

What it actually did well

The enable/disable symlink split was the good idea. It gave me “installed but off” for free, years before pack/plugins/opt became a thing in Vim 8. The show command was a nice parlor trick too: it fetched the plugin’s README from raw.githubusercontent.com and piped it to less, a proto-:help for plugins you had not installed yet.

What it did wrong (the honest list)

  • The module index was rebuilt by grepping .git/config with awk. It worked until it did not.
  • install was actually an alias for the undocumented import and the case statement’s *) arm swallows everything, including its own typos.
  • The usage line says ./aptogen (one h short). Published. For years.
  • show downloaded remote content and piped it to less in your editor. A tiny remote-code-adjacent footgun nobody exploited because nobody used it.
  • Everything is cd-based. Every script failure leaves you in bundle-availlable.

It is period-accurate bash: [[ ... ]]&&{ ... }||{ ... } chains, &>>$LOGS swallowed errors, date-stamped log files. I keep the zip (still on vim.org ) like a museum piece and it is a bigger one than I remembered: alongside the two scripts it ships a complete environment, .vimrc included (opening with runtime! debian.vim), file templates for bash/C/Perl/PHP/Python/XHTML, French and English spell files, extra syntax files and six plugin/*.vim config files. It was never just a plugin manager; it was a distribution with apt attached. The GitHub repo is dead, the script_id is the tombstone; the zip is the mausoleum.

The ladder, so far

This was rung one. A few years later the plugins moved into git submodules with a second bash script (see the next episode ), then the whole thing got rewritten in VimScript as vim-plugin-manager and hardened for years after that. Every generation kept one idea from the previous one:

  • apthogen: apt verbs, on/off split, declarative lists.
  • the submodule era: the repo itself becomes the state, no local index needed.
  • vim-plugin-manager: parallel installs, pinning re-asserted from the vimrc, lazy loading.

The design did not change as much as the plumbing did. Which is, I think, the actual lesson: get the metaphor right early, then spend a decade making it boring.

[edit: 2026] The Ansible implementation of all this: the vim role .