Skip to content

Commit 6815c61

Browse files
committed
update readme
1 parent 8a03ae7 commit 6815c61

File tree

1 file changed

+179
-58
lines changed

1 file changed

+179
-58
lines changed

README.2.md

Lines changed: 179 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,108 +1,229 @@
1-
> A video showcasing how pnpm autocompletions work on a test CLI command like `my-cli`
1+
> A video showcasing how pnpm autocompletions work on a test CLI command
2+
like `my-cli`
23

34
# tab
5+
Shell autocompletions are largely missing in the JavaScript CLI ecosystem. This tool bridges that gap by autocompletions for `pnpm`, `npm`, `yarn`, and `bun` with dynamic option parsing and context-aware suggestions and also Easy-to-use adapters for popular JavaScript CLI frameworks like CAC, Citty, and Commander.js
46

5-
> Instant feedback for your CLI tool when hitting [TAB] in your terminal
7+
As CLI tooling authors, if we can spare our users a second or two by not checking documentation or writing the `-h` flag, we're doing them a huge favor. The unconscious mind loves hitting the [TAB] key and always expects feedback. When nothing happens, it breaks the user's flow - a frustration apparent across the whole JavaScript CLI tooling ecosystem.
68

7-
As CLI tooling authors, if we can spare our users a second or two by not checking the documentation or writing the `-h` option, we're doing them a huge favor. The unconscious loves hitting the [TAB] key. It always expects feedback. So it feels disappointing when hitting that key in the terminal but then nothing happens. That frustration is apparent across the whole JavaScript CLI tooling ecosystem.
9+
Tab solves this complexity by providing autocompletions that work consistently across `zsh`, `bash`, `fish`, and `powershell`.
810

9-
Autocompletions are the solution to not break the user's flow. The issue is they're not simple to add. `zsh` expects them in one way, and `bash` in another way. Then where do we provide them so the shell environment parses them? Too many headaches to ease the user's experience. Whether it's worth it or not is out of the question. Because tab is the solution to this complexity.
1011

11-
`my-cli.ts`:
12+
### Installation
1213

13-
```typescript
14-
import t from '@bomb.sh/tab';
14+
```bash
15+
npm install @bomb.sh/tab
16+
# or
17+
pnpm add @bomb.sh/tab
18+
# or
19+
yarn add @bomb.sh/tab
20+
# or
21+
bun add @bomb.sh/tab
22+
```
1523

16-
t.name('my-cli');
24+
### Package Manager Completions
1725

18-
t.command('start', 'start the development server');
26+
Get autocompletions for your package manager with zero configuration:
1927

20-
if (process.argv[2] === 'complete') {
21-
const [shell, ...args] = process.argv.slice(3);
22-
if (shell === '--') {
23-
t.parse(args);
24-
} else {
25-
t.setup(shell, x);
26-
}
27-
}
28+
```bash
29+
# this generates a completion script for your shell
30+
npx @bomb.sh/tab pnpm zsh >> ~/.zshrc
31+
npx @bomb.sh/tab npm bash >> ~/.bashrc
32+
npx @bomb.sh/tab yarn fish > ~/.config/fish/completions/yarn.fish
33+
npx @bomb.sh/tab bun powershell >> $PROFILE
2834
```
2935

30-
This `my-cli.ts` would be equipped with all the tools required to provide autocompletions.
36+
You'd get smart completions for all commands and options, and dynamic option values e.g., `--reporter=<TAB>`. and its always up-to-date (parsed from live help output)
3137

38+
**Example in action:**
3239
```bash
33-
node my-cli.ts complete -- "st"
34-
```
40+
pnpm install --reporter=<TAB>
41+
# Shows append-only, default, ndjson, silent
3542

36-
```
37-
start start the development server
38-
:0
43+
npm remove <TAB>
44+
# Shows the packages from package.json
45+
46+
yarn add --emoji=<TAB>
47+
# Show true, false
3948
```
4049

41-
This output was generated by the `t.parse` method to autocomplete "st" to "start".
50+
### CLI Framework Integration
4251

43-
Obviously, the user won't be running that command directly in their terminal. They'd be running something like this.
52+
For your own CLI tools, tab provides seamless integration with popular frameworks:
4453

45-
```bash
46-
source <(node my-cli.ts complete zsh)
47-
```
54+
#### Using the Core API
4855

49-
Now whenever the shell sees `my-cli`, it would bring the autocompletions we wrote for this CLI tool. The `node my-cli.ts complete zsh` part would output the zsh script that loads the autocompletions provided by `t.parse` which then would be executed using `source`.
56+
```typescript
57+
import t from '@bomb.sh/tab';
5058

51-
The autocompletions only live through the current shell session. To set them up across all terminal sessions, the autocompletion script should be injected in the `.zshrc` file.
59+
t.command('dev', 'Start development server');
60+
t.option('port', 'Specify port', (complete) => {
61+
complete('3000', 'Development port');
62+
complete('8080', 'Production port');
63+
});
5264

53-
```bash
54-
my-cli complete zsh > ~/completion-for-my-cli.zsh && echo 'source ~/completion-for-my-cli.zsh' >> ~/.zshrc
65+
// handle completion requests
66+
if (process.argv[2] === 'complete') {
67+
const shell = process.argv[3];
68+
if (shell === '--') {
69+
// parse completion arguments
70+
const args = process.argv.slice(4);
71+
t.parse(args);
72+
} else {
73+
// generate shell script
74+
t.setup('my-cli', 'node my-cli.js', shell);
75+
}
76+
}
5577
```
5678

57-
Or
58-
79+
**Test your completions:**
5980
```bash
60-
echo 'source <(npx --offline my-cli complete zsh)' >> ~/.zshrc
81+
node my-cli.js complete -- dev --p<TAB>
82+
# Output: --port Specify port
83+
84+
node my-cli.js complete -- dev --port=<TAB>
85+
# Output: --port=3000 Development port
86+
# --port=8080 Production port
6187
```
6288

63-
This is an example of autocompletions on a global CLI command that is usually installed using the `-g` flag (e.g. `npm add -g my-cli`) which is available across the computer.
89+
**Install for users:**
90+
```bash
91+
# One-time setup
92+
source <(my-cli complete zsh)
6493

65-
---
94+
# Permanent setup
95+
my-cli complete zsh >> ~/.zshrc
96+
```
6697

67-
While working on tab, we came to the realization that most JavaScript CLIs are not global CLI commands but rather, per-project dependencies.
98+
## Framework Adapters
6899

69-
For instance, Vite won't be installed globally and instead it'd be always installed on a project. Here's an example usage:
100+
Tab provides adapters for popular JavaScript CLI frameworks.
70101

71-
```bash
72-
pnpm vite dev
73-
```
102+
### CAC Integration
74103

75-
Rather than installing it globally. This example is pretty rare:
104+
```typescript
105+
import cac from 'cac';
106+
import tab from '@bomb.sh/tab/cac';
107+
108+
const cli = cac('my-cli');
109+
110+
// Define your CLI
111+
cli.command('dev', 'Start dev server')
112+
.option('--port <port>', 'Specify port')
113+
.option('--host <host>', 'Specify host');
114+
115+
// Initialize tab completions
116+
const completion = tab(cli);
117+
118+
// Add custom completions for option values
119+
const devCommand = completion.commands.get('dev');
120+
const portOption = devCommand?.options.get('--port');
121+
if (portOption) {
122+
portOption.handler = async () => [
123+
{ value: '3000', description: 'Development port' },
124+
{ value: '8080', description: 'Production port' },
125+
];
126+
}
76127

77-
```bash
78-
vite dev
128+
cli.parse();
79129
```
80130

81-
So in this case, a computer might have hundreds of Vite instances each installed separately and potentially from different versions on different projects.
131+
### Citty Integration
82132

83-
We were looking for a fluid strategy that would be able to load the autocompletions from each of these dependencies on a per-project basis.
133+
```typescript
134+
import { defineCommand, createMain } from 'citty';
135+
import tab from '@bomb.sh/tab/citty';
136+
137+
const main = defineCommand({
138+
meta: { name: 'my-cli', description: 'My CLI tool' },
139+
subCommands: {
140+
dev: defineCommand({
141+
meta: { name: 'dev', description: 'Start dev server' },
142+
args: {
143+
port: { type: 'string', description: 'Specify port' },
144+
host: { type: 'string', description: 'Specify host' },
145+
},
146+
}),
147+
},
148+
});
149+
150+
// Initialize tab completions
151+
const completion = await tab(main);
152+
153+
// Add custom completions
154+
const devCommand = completion.commands.get('dev');
155+
const portOption = devCommand?.options.get('--port');
156+
if (portOption) {
157+
portOption.handler = async () => [
158+
{ value: '3000', description: 'Development port' },
159+
{ value: '8080', description: 'Production port' },
160+
];
161+
}
84162

85-
And that made us develop our own autocompletion abstraction over npm, pnpm and yarn. This would help tab identify which binaries are available in a project and which of these binaries provide autocompletions. So the user would not have to `source` anything or inject any script in their `.zshrc`.
163+
const cli = createMain(main);
164+
cli();
165+
```
86166

87-
They'd only have to run this command once and inject it in their shell config.
167+
### Commander.js Integration
88168

89-
```bash
90-
npx @bomb.sh/tab pnpm zsh
169+
```typescript
170+
import { Command } from 'commander';
171+
import tab from '@bomb.sh/tab/commander';
172+
173+
const program = new Command('my-cli');
174+
program.version('1.0.0');
175+
176+
// Define commands
177+
program
178+
.command('serve')
179+
.description('Start the server')
180+
.option('-p, --port <number>', 'port to use', '3000')
181+
.option('-H, --host <host>', 'host to use', 'localhost')
182+
.action((options) => {
183+
console.log('Starting server...');
184+
});
185+
186+
// Initialize tab completions
187+
const completion = tab(program);
188+
189+
// Add custom completions
190+
for (const command of completion.commands.values()) {
191+
if (command.value === 'serve') {
192+
const portOption = command.options.get('--port');
193+
if (portOption) {
194+
portOption.handler = async () => [
195+
{ value: '3000', description: 'Default port' },
196+
{ value: '8080', description: 'Alternative port' },
197+
];
198+
}
199+
}
200+
}
201+
202+
program.parse();
91203
```
92204

93-
These autocompletions on top of the normal autocompletions that these package managers provide are going to be way more powerful.
94205

95-
These new autocompletions on top of package managers would help us with autocompletions on commands like `pnpm vite` and other global or per-project binaries. The only requirement would be that the npm binary itself would be a tab-compatible binary.
206+
Tab's package manager completions are dynamically generated from the actual help output of each tool:
207+
96208

97-
What is a tab-compatible binary? It's a tool that provides the `complete` subcommand that was showcased above. Basically any CLI tool that uses tab for its autocompletions is a tab-compatible binary.
209+
Tab uses a standardized completion protocol that any CLI can implement:
98210

99211
```bash
100-
pnpm my-cli complete --
212+
# Generate shell completion script
213+
my-cli complete zsh
214+
215+
# Parse completion request (called by shell)
216+
my-cli complete -- install --port=""
101217
```
102218

219+
**Output Format:**
103220
```
104-
start start the development server
105-
:0
221+
--port=3000 Development port
222+
--port=8080 Production port
223+
:4
106224
```
225+
## Contributing
226+
227+
We welcome contributions! Tab's architecture makes it easy to add support for new package managers or CLI frameworks.
228+
107229

108-
We are planning to maintain these package manager autocompletions on our own and turn them into full-fledged autocompletions that touch on every part of our package managers.

0 commit comments

Comments
 (0)