@alexgorbatchev/dotfiles 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +397 -0
  3. package/cli-fj2hdbnx.js +5 -0
  4. package/cli-fj2hdbnx.js.map +9 -0
  5. package/cli-w822cqdk.js +4 -0
  6. package/cli-w822cqdk.js.map +10 -0
  7. package/cli.js +449 -0
  8. package/cli.js.map +283 -0
  9. package/dashboard-0ebz5sqb.js +159 -0
  10. package/dashboard-0ebz5sqb.js.map +102 -0
  11. package/dashboard-3axqywva.css +1 -0
  12. package/dashboard.js +13 -0
  13. package/package.json +63 -0
  14. package/prerender-kpxyx916.js +3 -0
  15. package/prerender-kpxyx916.js.map +11 -0
  16. package/schemas.d.ts +2730 -0
  17. package/skill/SKILL.md +74 -0
  18. package/skill/references/api-reference.md +614 -0
  19. package/skill/references/configuration.md +1154 -0
  20. package/skill/references/installation-methods/brew.md +62 -0
  21. package/skill/references/installation-methods/cargo.md +86 -0
  22. package/skill/references/installation-methods/curl-binary.md +73 -0
  23. package/skill/references/installation-methods/curl-script.md +132 -0
  24. package/skill/references/installation-methods/curl-tar.md +58 -0
  25. package/skill/references/installation-methods/dmg.md +113 -0
  26. package/skill/references/installation-methods/gitea-release.md +106 -0
  27. package/skill/references/installation-methods/github-release.md +97 -0
  28. package/skill/references/installation-methods/manual.md +74 -0
  29. package/skill/references/installation-methods/npm.md +75 -0
  30. package/skill/references/installation-methods/overview.md +293 -0
  31. package/skill/references/installation-methods/zsh-plugin.md +156 -0
  32. package/skill/references/make-tool.md +866 -0
  33. package/skill/references/shell-and-hooks.md +833 -0
  34. package/tool-types.d.ts +14 -0
  35. package/wasm-n3cagcre.js +3 -0
  36. package/wasm-n3cagcre.js.map +10 -0
@@ -0,0 +1,833 @@
1
+ # Shell Integration and Hooks
2
+
3
+ ## Table of Contents
4
+
5
+ - [Shell Integration](#shell-integration)
6
+ - [Shell Methods](#shell-methods)
7
+ - [Configurator Methods](#configurator-methods)
8
+ - [Basic Example](#basic-example)
9
+ - [PATH Modifications](#path-modifications)
10
+ - [Shell Functions](#shell-functions)
11
+ - [Sourcing Files and Functions](#sourcing-files-and-functions)
12
+ - [Script Timing](#script-timing)
13
+ - [Cross-Shell Configuration](#cross-shell-configuration)
14
+ - [Path References](#path-references)
15
+ - [Best Practices](#best-practices)
16
+ - [Symbolic Links](#symbolic-links)
17
+ - [Command Completions](#command-completions)
18
+ - [Configuration Options](#configuration-options)
19
+ - [Shell Callback Context](#shell-callback-context)
20
+ - [Static Completions (source)](#static-completions-source)
21
+ - [URL-Based Completions](#url-based-completions)
22
+ - [Dynamic Completions (cmd)](#dynamic-completions-cmd)
23
+ - [Binary Name Override](#binary-name-override)
24
+ - [CLI Completions](#cli-completions)
25
+ - [Hooks](#hooks)
26
+ - [Basic Usage](#basic-usage)
27
+ - [Hook Events](#hook-events)
28
+ - [Context Properties](#context-properties)
29
+ - [Examples](#examples)
30
+ - [Error Handling](#error-handling)
31
+ - [Environment Variables in Installation](#environment-variables-in-installation)
32
+ - [Best Practices](#best-practices-1)
33
+ - [Hook Execution Order](#hook-execution-order)
34
+ - [Complete Example](#complete-example)
35
+
36
+ ---
37
+
38
+ # Shell Integration
39
+
40
+ Configure shell environments, aliases, completions, and functions.
41
+
42
+ ## Shell Methods
43
+
44
+ | Method | Shell |
45
+ | ----------------------- | ---------- |
46
+ | `.zsh(callback)` | Zsh |
47
+ | `.bash(callback)` | Bash |
48
+ | `.powershell(callback)` | PowerShell |
49
+
50
+ Each callback receives:
51
+
52
+ - `shell` - Shell configurator for setting up environment, aliases, completions, etc.
53
+ - `ctx` - Context with `version` property (only available after installation)
54
+
55
+ For other context properties (`toolDir`, `currentDir`, `projectConfig`, etc.), use the outer `ctx` from `defineTool`.
56
+
57
+ ## Configurator Methods
58
+
59
+ ```typescript
60
+ .zsh((shell) =>
61
+ shell
62
+ .env({ VAR: 'value' }) // Environment variables (PATH prohibited)
63
+ .path('$HOME/.local/bin') // Add directory to PATH
64
+ .aliases({ t: 'tool' }) // Shell aliases
65
+ .functions({ myFunc: 'cmd' }) // Shell functions
66
+ .completions('completions/_tool') // Completion file path
67
+ .sourceFile('shell/init.zsh') // Source a file (skips if missing)
68
+ .sourceFunction('myFunc') // Source output of a function (source <(myFunc))
69
+ .source('tool env --shell zsh') // Source output of inline shell code
70
+ .always(`eval "$(tool init)"`) // Run every shell startup
71
+ .once(`tool gen-completions`) // Run once after install
72
+ )
73
+ ```
74
+
75
+ ## Basic Example
76
+
77
+ ```typescript
78
+ export default defineTool((install, ctx) =>
79
+ install('github-release', { repo: 'owner/tool' })
80
+ .bin('tool')
81
+ .zsh((shell) =>
82
+ shell
83
+ .env({ TOOL_HOME: ctx.currentDir })
84
+ .path(`${ctx.currentDir}/bin`) // Add tool's bin directory to PATH
85
+ .aliases({ t: 'tool', ts: 'tool status' })
86
+ .completions('completions/_tool')
87
+ .functions({
88
+ 'tool-helper': 'tool --config "$TOOL_HOME/config.toml" "$@"',
89
+ })
90
+ )
91
+ );
92
+ ```
93
+
94
+ ## PATH Modifications
95
+
96
+ ### `.path()` - Add Directory to PATH
97
+
98
+ Add a directory to the PATH environment variable. Paths are deduplicated during shell init generation.
99
+
100
+ ```typescript
101
+ .zsh((shell) =>
102
+ shell
103
+ .path('$HOME/.local/bin') // Static path with shell variable
104
+ .path(`${ctx.currentDir}/bin`) // Dynamic path using context
105
+ )
106
+ ```
107
+
108
+ **Why use `.path()` instead of `.env({ PATH: ... })`?**
109
+
110
+ - Paths are automatically deduplicated across all tools
111
+ - Proper ordering is maintained (prepended to PATH by default)
112
+ - TypeScript prevents using `PATH` in `.env()` with a clear error message
113
+
114
+ **Note**: Setting `PATH` via `.env({ PATH: '...' })` is prohibited. Use `.path()` instead.
115
+
116
+ ## Shell Functions
117
+
118
+ ### `.functions()` - Define Shell Functions
119
+
120
+ Define shell functions that are generated into the shell init file.
121
+
122
+ ```typescript
123
+ .zsh((shell) =>
124
+ shell.functions({
125
+ 'my-command': 'echo "Hello, world!"',
126
+ 'tool-setup': 'cd /some/path && ./setup.sh',
127
+ })
128
+ )
129
+ ```
130
+
131
+ **Generated output:**
132
+
133
+ ```zsh
134
+ my-command() {
135
+ echo "Hello, world!"
136
+ }
137
+
138
+ tool-setup() {
139
+ cd /some/path && ./setup.sh
140
+ }
141
+ ```
142
+
143
+ This is useful for defining wrapper functions or custom commands.
144
+
145
+ ## Sourcing Files and Functions
146
+
147
+ ### `.sourceFile()` - Source a Script File
148
+
149
+ Source a script file during shell initialization. If the file doesn't exist, it's silently skipped.
150
+ The file is sourced in a way that respects the configured HOME directory while still affecting the current shell.
151
+
152
+ ```typescript
153
+ .zsh((shell) =>
154
+ shell
155
+ .sourceFile('init.zsh') // Relative to toolDir
156
+ .sourceFile(`${ctx.currentDir}/shell.zsh`) // Absolute path for installed archives
157
+ )
158
+ ```
159
+
160
+ - **Relative paths** -> resolve to `toolDir` (directory containing `.tool.ts`)
161
+ - **Absolute paths** -> used as-is
162
+ - File existence is checked before sourcing
163
+
164
+ **Generated output (zsh/bash):**
165
+
166
+ ```zsh
167
+ __dotfiles_source_mytool_0() {
168
+ [[ -f "/path/to/init.zsh" ]] && cat "/path/to/init.zsh"
169
+ }
170
+ source <(__dotfiles_source_mytool_0)
171
+ unset -f __dotfiles_source_mytool_0
172
+ ```
173
+
174
+ The function is automatically cleaned up after sourcing to avoid shell pollution.
175
+
176
+ ### `.sourceFunction()` - Source Function Output
177
+
178
+ Source the output of a shell function defined via `.functions()`. This is ideal for tools requiring dynamic initialization (e.g., `eval "$(tool init)"`).
179
+
180
+ **Important**: When a function is used with `.sourceFunction()`, its body must **output shell code to stdout**. This output is then sourced (executed) in the current shell. Common tools like `fnm`, `pyenv`, `rbenv`, and `zoxide` have commands that print shell code for this purpose.
181
+
182
+ ```typescript
183
+ .zsh((shell) =>
184
+ shell
185
+ .functions({
186
+ // fnm env --use-on-cd PRINTS shell code like:
187
+ // export FNM_DIR="/Users/me/.fnm"
188
+ // export PATH="...fnm/bin:$PATH"
189
+ initFnm: 'fnm env --use-on-cd',
190
+ })
191
+ .sourceFunction('initFnm')
192
+ )
193
+ ```
194
+
195
+ **Generated output (zsh/bash):**
196
+
197
+ ```zsh
198
+ initFnm() {
199
+ fnm env --use-on-cd
200
+ }
201
+ source <(initFnm)
202
+ ```
203
+
204
+ **Key differences from `.always()`:**
205
+
206
+ - `.sourceFunction()` emits `source <(fnName)` directly without any wrapping
207
+ - The function's stdout is sourced as shell code, running in the current shell
208
+ - Type-safe: only accepts function names defined via `.functions()`
209
+
210
+ ### `.source()` - Source Inline Shell Code Output
211
+
212
+ Source the output of inline shell code without defining a named function. The content must **print shell code to stdout** - this output is then sourced.
213
+
214
+ ```typescript
215
+ .zsh((shell) =>
216
+ shell
217
+ // fnm env prints shell code like "export PATH=..."
218
+ .source('fnm env --use-on-cd')
219
+ // Or echo shell code directly
220
+ .source('echo "export MY_VAR=value"')
221
+ )
222
+ ```
223
+
224
+ **Generated output (zsh/bash):**
225
+
226
+ ```zsh
227
+ __dotfiles_source_mytool_0() {
228
+ fnm env --use-on-cd
229
+ }
230
+ source <(__dotfiles_source_mytool_0)
231
+ unset -f __dotfiles_source_mytool_0
232
+ ```
233
+
234
+ Use `.source()` when:
235
+
236
+ - You need to source command output inline without a named function
237
+ - The command prints shell code that should be executed in the current shell
238
+ - You don't need to call the function by name elsewhere
239
+
240
+ For reusable functions, use `.functions()` + `.sourceFunction()` instead.
241
+
242
+ ## Script Timing
243
+
244
+ ### `.always()` - Every Shell Startup
245
+
246
+ For fast inline operations that run on every shell startup:
247
+
248
+ ```typescript
249
+ .zsh((shell) =>
250
+ shell.always(`
251
+ eval "$(tool init zsh)"
252
+ `)
253
+ )
254
+ ```
255
+
256
+ ### `.once()` - After Installation
257
+
258
+ For expensive operations:
259
+
260
+ ```typescript
261
+ export default defineTool((install, ctx) =>
262
+ install('github-release', { repo: 'owner/tool' })
263
+ .bin('tool')
264
+ .zsh((shell) =>
265
+ shell.once(`
266
+ tool gen-completions --zsh > "${ctx.projectConfig.paths.generatedDir}/completions/_tool"
267
+ `)
268
+ )
269
+ );
270
+ ```
271
+
272
+ ## Cross-Shell Configuration
273
+
274
+ Share configuration across shells using the outer `ctx` from `defineTool`:
275
+
276
+ ```typescript
277
+ export default defineTool((install, ctx) => {
278
+ const configureShell = (shell) => shell.env({ TOOL_HOME: ctx.currentDir }).aliases({ t: 'tool' });
279
+
280
+ return install('github-release', { repo: 'owner/tool' }).bin('tool').zsh(configureShell).bash(configureShell);
281
+ });
282
+ ```
283
+
284
+ ## Path References
285
+
286
+ Always use context variables from the outer `ctx`:
287
+
288
+ ```typescript
289
+ export default defineTool((install, ctx) =>
290
+ install('github-release', { repo: 'owner/tool' })
291
+ .bin('tool')
292
+ .zsh((shell) =>
293
+ shell.env({
294
+ TOOL_CONFIG: ctx.toolDir, // Tool config directory
295
+ TOOL_DATA: '~/.local/share/tool',
296
+ }).always(`
297
+ FZF_DIR="${ctx.projectConfig.paths.binariesDir}/fzf"
298
+ [[ -d "$FZF_DIR" ]] && export FZF_BASE="$FZF_DIR"
299
+ `)
300
+ )
301
+ );
302
+ ```
303
+
304
+ ## Best Practices
305
+
306
+ - Use declarative methods (`.env()`, `.aliases()`) for simple config
307
+ - Use `.always()` for fast runtime setup only
308
+ - Use `.once()` for expensive operations (completion generation, cache building)
309
+ - Use context variables for all paths - never hardcode
310
+
311
+ ## Symbolic Links
312
+
313
+ Create symlinks for configuration files with `.symlink()`.
314
+
315
+ ### Syntax
316
+
317
+ ```typescript
318
+ .symlink(source, target)
319
+ ```
320
+
321
+ | Parameter | Description |
322
+ | --------- | ------------------------------------------------------------------------ |
323
+ | `source` | Path to source file/directory. `./` is relative to tool config directory |
324
+ | `target` | Absolute path for symlink. Use context variables or `~` |
325
+
326
+ ### Path Resolution
327
+
328
+ | Source | Resolution |
329
+ | ---------------- | -------------------------------- |
330
+ | `./config.toml` | Relative to `.tool.ts` directory |
331
+ | `/etc/tool.conf` | Absolute path |
332
+
333
+ | Target | Resolution |
334
+ | ---------------- | ---------------------------------------------- |
335
+ | `~/.config/tool` | Expanded automatically via home path expansion |
336
+
337
+ ### Example
338
+
339
+ ```
340
+ tools/my-tool/
341
+ ├── my-tool.tool.ts
342
+ ├── config.toml
343
+ └── themes/
344
+ ├── dark.toml
345
+ └── light.toml
346
+ ```
347
+
348
+ ```typescript
349
+ export default defineTool((install) =>
350
+ install('github-release', { repo: 'owner/my-tool' })
351
+ .bin('my-tool')
352
+ .symlink('./config.toml', '~/.config/my-tool/config.toml')
353
+ .symlink('./themes/', '~/.config/my-tool/themes')
354
+ .zsh((shell) =>
355
+ shell.env({
356
+ MY_TOOL_CONFIG: '~/.config/my-tool/config.toml',
357
+ })
358
+ )
359
+ );
360
+ ```
361
+
362
+ ### Common Patterns
363
+
364
+ ```typescript
365
+ // Configuration files
366
+ .symlink('./gitconfig', '~/.gitconfig')
367
+
368
+ // Directories
369
+ .symlink('./themes/', '~/.config/tool/themes')
370
+
371
+ // Scripts
372
+ .symlink('./scripts/helper.sh', '~/bin/helper')
373
+ ```
374
+
375
+ ### Correct vs Incorrect
376
+
377
+ ```typescript
378
+ // ✅ Tilde expansion (recommended)
379
+ .symlink('./config.toml', '~/.config/tool/config.toml')
380
+
381
+ // ❌ Hardcoded path
382
+ .symlink('./config.toml', '/home/user/.config/tool/config.toml')
383
+ ```
384
+
385
+ ---
386
+
387
+ # Command Completions
388
+
389
+ Tab completions are configured per-shell using `.completions()`:
390
+
391
+ ```typescript
392
+ .zsh((shell) => shell.completions('completions/_tool.zsh'))
393
+ .bash((shell) => shell.completions('completions/tool.bash'))
394
+ ```
395
+
396
+ > **Lifecycle**: All completions are generated only after `dotfiles install <tool>` succeeds,
397
+ > not during `dotfiles generate`. This ensures cmd-based completions can execute the installed
398
+ > binary and callbacks receive the actual installed version in `ctx.version`.
399
+
400
+ ## Configuration Options
401
+
402
+ | Property | Description |
403
+ | -------- | ---------------------------------------------------------------------------------------- |
404
+ | `source` | Path to completion file (relative to toolDir, or absolute path within extracted archive) |
405
+ | `url` | URL to download completion file or archive from |
406
+ | `cmd` | Command to generate completions dynamically |
407
+ | `bin` | Binary name for completion filename (when different from tool name) |
408
+
409
+ **Note**: Use one of these combinations:
410
+
411
+ - `'_tool.zsh'` - String path (relative to toolDir or absolute)
412
+ - `{ source }` - Static file (relative to toolDir or absolute)
413
+ - `{ cmd }` - Generate dynamically by running a command
414
+ - `{ url }` - Download direct completion file from URL (filename derived from URL)
415
+ - `{ url, source }` - Download archive, extract, use source as path to file within
416
+
417
+ ## Shell Callback Context
418
+
419
+ The shell callback receives two parameters:
420
+
421
+ - `shell` - The shell configurator for setting up completions, aliases, etc.
422
+ - `ctx` - Context with `version` property (only available after installation)
423
+
424
+ For other context properties (`toolDir`, `currentDir`, `projectConfig`, etc.), use the outer `ctx` from `defineTool`.
425
+
426
+ ```typescript
427
+ export default defineTool((install, ctx) =>
428
+ install('github-release', { repo: 'owner/tool' })
429
+ .bin('tool')
430
+ .zsh((shell) => shell.completions('completions/_tool.zsh'))
431
+ );
432
+ ```
433
+
434
+ ## Static Completions (source)
435
+
436
+ For completion files bundled in tool archives:
437
+
438
+ ```typescript
439
+ // Simple path relative to extracted archive
440
+ .zsh((shell) => shell.completions('completions/_tool.zsh'))
441
+
442
+ // Glob pattern for versioned directories
443
+ .zsh((shell) => shell.completions('*/complete/_rg'))
444
+ ```
445
+
446
+ **Supported glob patterns**: `*`, `**`, `?`, `[abc]`
447
+
448
+ ## URL-Based Completions
449
+
450
+ For downloading completions from external sources. Supports both direct files and archives.
451
+
452
+ ### Direct File Download
453
+
454
+ ```typescript
455
+ // Direct completion file download (source is optional - derived from URL)
456
+ .zsh((shell) => shell.completions({
457
+ url: 'https://raw.githubusercontent.com/user/repo/main/completions/_tool'
458
+ }))
459
+ ```
460
+
461
+ ### Archive Download
462
+
463
+ ```typescript
464
+ // Archive download with source path to file within
465
+ .zsh((shell) => shell.completions({
466
+ url: 'https://github.com/user/repo/releases/download/v1.0/completions.tar.gz',
467
+ source: `${ctx.currentDir}/completions/_tool.zsh`
468
+ }))
469
+ ```
470
+
471
+ **Note**: For archives, `source` specifies the absolute path to the completion file within the extracted archive. For direct files, `source` is optional - the filename is derived from the URL.
472
+
473
+ ### Version-Dependent URLs (Callback)
474
+
475
+ For completions that need the installed version in the URL, use a callback:
476
+
477
+ ```typescript
478
+ // Direct file with version in URL
479
+ .zsh((shell) => shell.completions((ctx) => ({
480
+ url: `https://raw.githubusercontent.com/user/repo/${ctx.version}/completions/_tool`
481
+ })))
482
+
483
+ // Archive with version in URL (requires source)
484
+ .zsh((shell) => shell.completions((ctx) => ({
485
+ url: `https://github.com/user/repo/releases/download/${ctx.version}/completions.tar.gz`,
486
+ source: `${ctx.currentDir}/completions/_tool.zsh`
487
+ })))
488
+ ```
489
+
490
+ The callback receives `ctx` with:
491
+
492
+ - `version` - The installed version of the tool (e.g., `'v10.3.0'`, `'15.1.0'`), only available after installation completes
493
+
494
+ URL-based completions are downloaded to `ctx.currentDir`. For archives, they are automatically extracted and `source` specifies the path to the completion file within.
495
+
496
+ **Supported archive formats**: `.tar.gz`, `.tar.xz`, `.tar.bz2`, `.zip`, `.tar`, `.tar.lzma`, `.7z`
497
+
498
+ ## Dynamic Completions (cmd)
499
+
500
+ For tools that generate completions at runtime (recommended for version-dependent completions):
501
+
502
+ ```typescript
503
+ .zsh((shell) => shell.completions({ cmd: 'tool completion zsh' }))
504
+ .bash((shell) => shell.completions({ cmd: 'tool completion bash' }))
505
+ ```
506
+
507
+ ## Binary Name Override
508
+
509
+ When tool filename differs from binary name (e.g., `curl-script--fnm.tool.ts` for binary `fnm`):
510
+
511
+ ```typescript
512
+ .zsh((shell) => shell.completions({
513
+ cmd: 'fnm completions --shell zsh',
514
+ bin: 'fnm' // Results in '_fnm' instead of '_curl-script--fnm'
515
+ }))
516
+ ```
517
+
518
+ ## CLI Completions
519
+
520
+ The CLI generates its own completions to `<generatedDir>/shell-scripts/zsh/completions/_dotfiles`. Commands that accept tool names include all configured tools in their completions.
521
+
522
+ Reload completions after running `dotfiles generate`:
523
+
524
+ ```bash
525
+ autoload -U compinit && compinit
526
+ ```
527
+
528
+ ---
529
+
530
+ # Hooks
531
+
532
+ Hooks allow custom logic at different stages of the installation process.
533
+
534
+ ## Basic Usage
535
+
536
+ ```typescript
537
+ import { defineTool } from '@alexgorbatchev/dotfiles';
538
+
539
+ export default defineTool((install, ctx) =>
540
+ install('github-release', { repo: 'owner/tool' })
541
+ .bin('tool')
542
+ .hook('after-install', async (context) => {
543
+ const { $, log, fileSystem } = context;
544
+ await $`./tool init`;
545
+ log.info('Tool initialized');
546
+ })
547
+ );
548
+ ```
549
+
550
+ ## Hook Events
551
+
552
+ | Event | When | Available Properties |
553
+ | ---------------- | ---------------------------- | ------------------------------------------ |
554
+ | `before-install` | Before installation starts | `stagingDir` |
555
+ | `after-download` | After file download | `stagingDir`, `downloadPath` |
556
+ | `after-extract` | After archive extraction | `stagingDir`, `downloadPath`, `extractDir` |
557
+ | `after-install` | After installation completes | `installedDir`, `binaryPaths`, `version` |
558
+
559
+ ## Context Properties
560
+
561
+ All hooks receive a context object with:
562
+
563
+ | Property | Description |
564
+ | --------------- | ---------------------------------------------------- |
565
+ | `toolName` | Name of the tool |
566
+ | `currentDir` | Stable path (symlink) for this tool |
567
+ | `stagingDir` | Temporary installation directory |
568
+ | `systemInfo` | Platform, architecture, home directory |
569
+ | `fileSystem` | File operations (mkdir, writeFile, exists, etc.) |
570
+ | `replaceInFile` | Regex-based file text replacement |
571
+ | `log` | Structured logging (trace, debug, info, warn, error) |
572
+ | `projectConfig` | Project configuration |
573
+ | `toolConfig` | Tool configuration |
574
+ | `$` | Bun shell executor |
575
+
576
+ > **Note:** The `stagingDir` and `projectConfig` properties form the base environment context (`IEnvContext`) that is also available to dynamic `env` functions in install parameters.
577
+
578
+ ## Examples
579
+
580
+ ### File Operations
581
+
582
+ ```typescript
583
+ .hook('after-install', async ({ fileSystem, systemInfo, log }) => {
584
+ const configDir = `${systemInfo.homeDir}/.config/tool`;
585
+ await fileSystem.mkdir(configDir, { recursive: true });
586
+ await fileSystem.writeFile(`${configDir}/config.toml`, 'theme = "dark"');
587
+ log.info('Configuration created');
588
+ })
589
+ ```
590
+
591
+ ### Shell Commands
592
+
593
+ ```typescript
594
+ .hook('after-install', async ({ $, installedDir }) => {
595
+ // Run tool command
596
+ await $`${installedDir}/tool init`;
597
+
598
+ // Capture output
599
+ const version = await $`./tool --version`.text();
600
+ })
601
+ ```
602
+
603
+ ### Executing Installed Binaries by Name
604
+
605
+ In `after-install` hooks, the shell's PATH is automatically enhanced to include the directories containing the installed binaries. This means you can execute freshly installed tools by name without specifying the full path:
606
+
607
+ ```typescript
608
+ .hook('after-install', async ({ $ }) => {
609
+ // The installed binary is automatically available by name
610
+ await $`my-tool --version`;
611
+
612
+ // No need to use full paths like:
613
+ // await $`${installedDir}/bin/my-tool --version`;
614
+ })
615
+ ```
616
+
617
+ This PATH enhancement only applies to `after-install` hooks where `binaryPaths` is available in the context.
618
+
619
+ ### Shell Command Logging
620
+
621
+ Shell commands executed in hooks are automatically logged to help with debugging and visibility:
622
+
623
+ - Commands are logged as `$ command` at info level before execution
624
+ - Stdout lines are logged as `| line` at info level
625
+ - Stderr lines are logged as `| line` at error level (only if stderr has content)
626
+
627
+ Example output:
628
+
629
+ ```
630
+ $ my-tool init
631
+ | Initializing configuration...
632
+ | Configuration complete!
633
+ ```
634
+
635
+ This logging happens regardless of whether `.quiet()` is used on the shell command, since logging occurs at the hook executor level.
636
+
637
+ ### Platform-Specific Setup
638
+
639
+ ```typescript
640
+ .hook('after-install', async ({ systemInfo, $ }) => {
641
+ if (systemInfo.platform === 'darwin') {
642
+ await $`./setup-macos.sh`;
643
+ } else if (systemInfo.platform === 'linux') {
644
+ await $`./setup-linux.sh`;
645
+ }
646
+ })
647
+ ```
648
+
649
+ ### File Text Replacement
650
+
651
+ ```typescript
652
+ .hook('after-install', async ({ replaceInFile, installedDir }) => {
653
+ // Replace a config value (returns true if replaced, false otherwise)
654
+ const wasReplaced = await replaceInFile(
655
+ `${installedDir}/config.toml`,
656
+ /theme = ".*"/,
657
+ 'theme = "dark"'
658
+ );
659
+
660
+ // Increment version numbers line-by-line
661
+ await replaceInFile(
662
+ `${installedDir}/versions.txt`,
663
+ /version=(\d+)/,
664
+ (match) => `version=${Number(match.captures[0]) + 1}`,
665
+ { mode: 'line' }
666
+ );
667
+
668
+ // Log error if pattern not found (helpful for debugging)
669
+ await replaceInFile(
670
+ `${installedDir}/config.toml`,
671
+ /api_key = ".*"/,
672
+ 'api_key = "secret"',
673
+ { errorMessage: 'Could not find api_key setting' }
674
+ );
675
+ })
676
+ ```
677
+
678
+ ### Build from Source
679
+
680
+ ```typescript
681
+ .hook('after-extract', async ({ extractDir, stagingDir, $ }) => {
682
+ if (extractDir) {
683
+ await $`cd ${extractDir} && make build`;
684
+ await $`mv ${extractDir}/target/release/tool ${stagingDir}/tool`;
685
+ }
686
+ })
687
+ ```
688
+
689
+ ## Error Handling
690
+
691
+ ```typescript
692
+ .hook('after-install', async ({ $, log }) => {
693
+ try {
694
+ await $`./tool self-test`;
695
+ } catch (error) {
696
+ log.error('Self-test failed');
697
+ throw error; // Re-throw to fail installation
698
+ }
699
+ });
700
+ ```
701
+
702
+ ### Custom Binary Processing
703
+
704
+ ```typescript
705
+ import { defineTool } from '@alexgorbatchev/dotfiles';
706
+ import path from 'path';
707
+
708
+ export default defineTool((install, ctx) =>
709
+ install('github-release', { repo: 'owner/custom-tool' })
710
+ .bin('custom-tool')
711
+ .hook('after-extract', async ({ extractDir, stagingDir, fileSystem, log }) => {
712
+ if (extractDir) {
713
+ // Custom binary selection and processing
714
+ const binaries = await fileSystem.readdir(path.join(extractDir, 'bin'));
715
+ const mainBinary = binaries.find((name) => name.startsWith('main-'));
716
+
717
+ if (mainBinary) {
718
+ const sourcePath = path.join(extractDir, 'bin', mainBinary);
719
+ const targetPath = path.join(stagingDir ?? '', 'tool');
720
+ await fileSystem.copy(sourcePath, targetPath);
721
+ log.info(`Selected binary: ${mainBinary}`);
722
+ }
723
+ }
724
+ })
725
+ );
726
+ ```
727
+
728
+ ### Environment-Specific Setup
729
+
730
+ ```typescript
731
+ import { defineTool } from '@alexgorbatchev/dotfiles';
732
+
733
+ export default defineTool((install, ctx) =>
734
+ install('github-release', { repo: 'owner/custom-tool' })
735
+ .bin('custom-tool')
736
+ .hook('after-install', async ({ systemInfo, fileSystem, log, $ }) => {
737
+ // Platform-specific setup
738
+ if (systemInfo.platform === 'darwin') {
739
+ // macOS-specific setup
740
+ await $`./setup-macos.sh`;
741
+ } else if (systemInfo.platform === 'linux') {
742
+ // Linux-specific setup
743
+ await $`./setup-linux.sh`;
744
+ }
745
+
746
+ // Architecture-specific setup
747
+ if (systemInfo.arch === 'arm64') {
748
+ log.info('Configuring for ARM64 architecture');
749
+ await $`./configure-arm64.sh`;
750
+ }
751
+ })
752
+ );
753
+ ```
754
+
755
+ ## Environment Variables in Installation
756
+
757
+ Set environment variables during installation (for curl-script installs):
758
+
759
+ ```typescript
760
+ import { defineTool } from '@alexgorbatchev/dotfiles';
761
+
762
+ export default defineTool((install) =>
763
+ install('curl-script', {
764
+ url: 'https://example.com/install.sh',
765
+ shell: 'bash',
766
+ env: {
767
+ INSTALL_DIR: '~/.local/bin',
768
+ ENABLE_FEATURE: 'true',
769
+ API_KEY: process.env.TOOL_API_KEY || 'default',
770
+ },
771
+ }).bin('my-tool')
772
+ );
773
+ ```
774
+
775
+ ## Best Practices
776
+
777
+ 1. **Use `$` for shell operations** that need to work with files relative to your tool config
778
+ 2. **Use `fileSystem` methods** for cross-platform file operations that don't require shell features
779
+ 3. **Always handle errors appropriately** in hooks to provide clear feedback
780
+ 4. **Use `log` for all output** - avoid `console.log()` in favor of structured logging:
781
+ - `log.info()` for general information
782
+ - `log.warn()` for warnings
783
+ - `log.error()` for error conditions
784
+ - `log.debug()` for debugging and troubleshooting
785
+ 5. **Test your hooks** on different platforms to ensure compatibility
786
+ 6. **Keep hooks focused** - each hook should have a single responsibility
787
+ 7. **Document complex logic** - explain what your hooks are doing and why
788
+
789
+ ## Hook Execution Order
790
+
791
+ 1. **`beforeInstall`**: Before any installation steps
792
+ 2. **`afterDownload`**: After downloading but before extraction
793
+ 3. **`afterExtract`**: After extraction but before binary setup
794
+ 4. **`afterInstall`**: After all installation steps are complete
795
+
796
+ ## Complete Example
797
+
798
+ ```typescript
799
+ import { defineTool } from '@alexgorbatchev/dotfiles';
800
+ import path from 'path';
801
+
802
+ export default defineTool((install, ctx) =>
803
+ install('github-release', { repo: 'owner/custom-tool' })
804
+ .bin('custom-tool')
805
+ .symlink('./config.yml', '~/.config/custom-tool/config.yml')
806
+ .hook('before-install', async ({ log }) => {
807
+ log.info('Starting custom-tool installation...');
808
+ })
809
+ .hook('after-extract', async ({ extractDir, log, $ }) => {
810
+ if (extractDir) {
811
+ // Build additional components
812
+ log.info('Building plugins...');
813
+ await $`cd ${extractDir} && make plugins`;
814
+ }
815
+ })
816
+ .hook('after-install', async ({ toolName, installedDir, systemInfo, fileSystem, log, $ }) => {
817
+ // Create data directory
818
+ const dataDir = path.join(systemInfo.homeDir, '.local/share', toolName);
819
+ await fileSystem.mkdir(dataDir, { recursive: true });
820
+
821
+ // Initialize tool
822
+ await $`${path.join(installedDir ?? '', toolName)} init --data-dir ${dataDir}`;
823
+
824
+ // Set up completion
825
+ await $`${
826
+ path.join(installedDir ?? '', toolName)
827
+ } completion zsh > ${ctx.projectConfig.paths.generatedDir}/completions/_${toolName}`;
828
+
829
+ log.info(`Initialized ${toolName} with data directory: ${dataDir}`);
830
+ })
831
+ .zsh((shell) => shell.env({ CUSTOM_TOOL_DATA: '~/.local/share/custom-tool' }).aliases({ ct: 'custom-tool' }))
832
+ );
833
+ ```