Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Check what landed, and wire up the pit aliases:

```sh
cli-tools list # * runs from here, ! is shadowed by another copy
cli-tools aliases --install # /blog /free /merge /prs /whois
cli-tools aliases --install # /aff /blog /free /merge /prs /speak /web /whois
cli-tools config # API keys: what is set, and where it came from
cli-tools update # git pull, reinstall, relink
cli-tools autoupdate --install # …or have a timer do that daily
Expand Down Expand Up @@ -606,9 +606,27 @@ moshcode install cli-tools # then /cli-tools list, /cli-tools update
## Aliases

Pit aliases live in `~/.moshcode/aliases.json`. `cli-tools aliases --install`
writes a thin default set (`/blog`, `/free`, `/merge`, `/prs`, `/whois`),
merging rather than replacing — an alias you bound yourself is kept and the
collision is reported. `cli-tools aliases` prints them without writing anything.
writes a thin default set, merging rather than replacing — an alias you bound
yourself is kept and the collision is reported. `cli-tools aliases` prints them
without writing anything.

| Alias | Expands to |
| --- | --- |
| `/aff` | `affiliate` |
| `/blog` | `blog-post` |
| `/free` | `domainfree` |
| `/merge` | `gh-prs-merge --apply` |
| `/prs` | `gh-prs` |
| `/speak` | `tts` |
| `/web` | `ask-web` |
| `/whois` | `domainjson` |

Three of those are named around a collision rather than for elegance. `/ask` and
`/say` are the words you would reach for, and both already resolve to something
else on a normal box; because a pit alias beats `PATH`, binding them would
shadow those programs *from inside the pit only*, which is about the most
confusing failure available. `/tts` would be worse — it would shadow our own
command. Hence `/web`, `/speak` and `/aff`.

To manage them by hand:

Expand Down
10 changes: 9 additions & 1 deletion bin/cli-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ Commands:
config API keys: what is set, where it came from, and how to change it
"config pull" imports them from the logicsrc team vault
where Print the checkout this command is running from
help This usage

Keys (config set <key>):
openai OPENAI_API_KEY generate-names
Expand Down Expand Up @@ -545,8 +546,11 @@ export async function run(argv: readonly string[]): Promise<number> {
// Anything that is not one of ours is one of the commands: pass it straight
// through, arguments and streams untouched, so `cli-tools gh-prs --orgs x`
// behaves exactly as `gh-prs --orgs x` does.
// `help` is here because it is what people type. Without it the word fell
// through to the passthrough below, which reported "unknown command: help"
// and exited 1 — before printing the usage that answers the question.
const known = new Set([
'list', 'update', 'autoupdate', 'link', 'unlink', 'aliases', 'config', 'where',
'help', 'list', 'update', 'autoupdate', 'link', 'unlink', 'aliases', 'config', 'where',
]);
if (!known.has(command)) {
const match = commands(root).find((entry) => entry.name === command);
Expand Down Expand Up @@ -629,6 +633,10 @@ export async function run(argv: readonly string[]): Promise<number> {
return 0;
}

case 'help':
process.stdout.write(USAGE);
return 0;

case 'update':
return options.flags.has('--auto')
? autoUpdate(root, options.flags.has('--force'))
Expand Down
8 changes: 8 additions & 0 deletions plugins/tools/commands/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,20 @@ asked with `--fix`. Taking it over silently changes what a merge run does.

| Alias | Expands to |
| --- | --- |
| `/aff` | `affiliate` |
| `/blog` | `blog-post` |
| `/free` | `domainfree` |
| `/merge` | `gh-prs-merge --apply` |
| `/prs` | `gh-prs` |
| `/speak` | `tts` |
| `/web` | `ask-web` |
| `/whois` | `domainjson` |

`/web`, `/speak` and `/aff` are named around a collision rather than for
elegance: `/ask` and `/say` both already resolve to something else on a normal
box, and `/tts` would shadow our own command. An alias beats `PATH`, so binding
any of those three would shadow the real program from inside the pit only.

An alias you already bound is never overwritten — the collision is reported and
yours is kept. The pit re-reads the file on every lookup, so an open pit picks
them up with no restart. Arguments append rather than substitute, so
Expand Down
10 changes: 10 additions & 0 deletions src/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,15 +141,25 @@ export function resolveCommand(
* having, and `merge` is the one with a flag baked in because the long form is
* what everybody types anyway.
*
* Three of these are named around a collision rather than for elegance. `/ask`
* and `/say` are the words you would reach for, and both already resolve to
* something else on a normal box — and because a pit alias beats PATH, binding
* them would shadow those programs from inside the pit only, which is about the
* most confusing failure available. `/tts` is worse still: it would shadow our
* own command. Hence `/web`, `/speak` and `/aff`.
*
* Keep them thin for a reason — `gh-prs-merge` repairs by default under
* `--apply`, so baking `--fix` in as well is what once made `/merge --fix`
* expand to `--apply --fix --fix`.
*/
export const PIT_ALIASES: Record<string, string> = {
aff: 'affiliate',
blog: 'blog-post',
free: 'domainfree',
merge: 'gh-prs-merge --apply',
prs: 'gh-prs',
speak: 'tts',
web: 'ask-web',
whois: 'domainjson',
};

Expand Down
13 changes: 13 additions & 0 deletions test/registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,19 @@ describe('mergeAliases', () => {
}
});

// /ask and /say are the words you would reach for, and both already resolve
// to something else on a normal box. A pit alias beats PATH, so binding them
// would shadow those programs from inside the pit only — which is about the
// most confusing failure available.
it('avoids the names that would shadow something on PATH', () => {
for (const taken of ['ask', 'say']) {
expect(Object.keys(PIT_ALIASES)).not.toContain(taken);
}
expect(PIT_ALIASES.web).toBe('ask-web');
expect(PIT_ALIASES.speak).toBe('tts');
expect(PIT_ALIASES.aff).toBe('affiliate');
});

// Every alias has to start with a command that actually exists here, or it is
// a `command not found` the moment somebody types it.
it('expands to a command this repository installs', () => {
Expand Down
Loading