/ scripts / postinstall.js
postinstall.js
  1  #!/usr/bin/env node
  2  
  3  /**
  4   * postinstall script — install shell completion files and print setup instructions.
  5   *
  6   * Detects the user's default shell and writes the completion script to the
  7   * standard completion directory.  For zsh and bash, the script prints manual
  8   * instructions instead of modifying rc files (~/.zshrc, ~/.bashrc) — this
  9   * avoids breaking multi-line shell commands and other fragile rc structures.
 10   * Fish completions work automatically without rc changes.
 11   *
 12   * Supported shells: bash, zsh, fish.
 13   *
 14   * This script is intentionally plain Node.js (no TypeScript, no imports from
 15   * the main source tree) so that it can run without a build step.
 16   */
 17  
 18  import { mkdirSync, writeFileSync, existsSync } from 'node:fs';
 19  import { join } from 'node:path';
 20  import { homedir } from 'node:os';
 21  
 22  
 23  // ── Completion script content ──────────────────────────────────────────────
 24  
 25  const BASH_COMPLETION = `# Bash completion for opencli (auto-installed)
 26  _opencli_completions() {
 27    local cur words cword
 28    _get_comp_words_by_ref -n : cur words cword
 29  
 30    local completions
 31    completions=$(opencli --get-completions --cursor "$cword" "\${words[@]:1}" 2>/dev/null)
 32  
 33    COMPREPLY=( $(compgen -W "$completions" -- "$cur") )
 34    __ltrim_colon_completions "$cur"
 35  }
 36  complete -F _opencli_completions opencli
 37  `;
 38  
 39  const ZSH_COMPLETION = `#compdef opencli
 40  # Zsh completion for opencli (auto-installed)
 41  _opencli() {
 42    local -a completions
 43    local cword=$((CURRENT - 1))
 44    completions=(\${(f)"$(opencli --get-completions --cursor "$cword" "\${words[@]:1}" 2>/dev/null)"})
 45    compadd -a completions
 46  }
 47  _opencli
 48  `;
 49  
 50  const FISH_COMPLETION = `# Fish completion for opencli (auto-installed)
 51  complete -c opencli -f -a '(
 52    set -l tokens (commandline -cop)
 53    set -l cursor (count (commandline -cop))
 54    opencli --get-completions --cursor $cursor $tokens[2..] 2>/dev/null
 55  )'
 56  `;
 57  
 58  // ── Helpers ────────────────────────────────────────────────────────────────
 59  
 60  function detectShell() {
 61    const shell = process.env.SHELL || '';
 62    if (shell.includes('zsh')) return 'zsh';
 63    if (shell.includes('bash')) return 'bash';
 64    if (shell.includes('fish')) return 'fish';
 65    return null;
 66  }
 67  
 68  function ensureDir(dir) {
 69    if (!existsSync(dir)) {
 70      mkdirSync(dir, { recursive: true });
 71    }
 72  }
 73  
 74  // ── Main ───────────────────────────────────────────────────────────────────
 75  
 76  function main() {
 77    // Skip in CI environments
 78    if (process.env.CI || process.env.CONTINUOUS_INTEGRATION) {
 79      return;
 80    }
 81  
 82    // Only install completion for global installs and npm link
 83    const isGlobal = process.env.npm_config_global === 'true';
 84    if (!isGlobal) {
 85      return;
 86    }
 87  
 88    const shell = detectShell();
 89    if (!shell) {
 90      // Cannot determine shell; silently skip
 91      return;
 92    }
 93  
 94    const home = homedir();
 95  
 96    try {
 97      switch (shell) {
 98        case 'zsh': {
 99          const completionsDir = join(home, '.zsh', 'completions');
100          const completionFile = join(completionsDir, '_opencli');
101          ensureDir(completionsDir);
102          writeFileSync(completionFile, ZSH_COMPLETION, 'utf8');
103  
104          console.log(`✓ Zsh completion installed to ${completionFile}`);
105          console.log('');
106          console.log('  \x1b[1mTo enable, add these lines to your ~/.zshrc:\x1b[0m');
107          console.log(`    fpath=(${completionsDir} $fpath)`);
108          console.log('    autoload -Uz compinit && compinit');
109          console.log('');
110          console.log('  If you already have compinit (oh-my-zsh, zinit, etc.), just add the fpath line \x1b[1mbefore\x1b[0m it.');
111          console.log('  Then restart your shell or run: \x1b[36mexec zsh\x1b[0m');
112          break;
113        }
114        case 'bash': {
115          const userCompDir = join(home, '.bash_completion.d');
116          const completionFile = join(userCompDir, 'opencli');
117          ensureDir(userCompDir);
118          writeFileSync(completionFile, BASH_COMPLETION, 'utf8');
119  
120          console.log(`✓ Bash completion installed to ${completionFile}`);
121          console.log('');
122          console.log('  \x1b[1mTo enable, add this line to your ~/.bashrc:\x1b[0m');
123          console.log(`    [ -f "${completionFile}" ] && source "${completionFile}"`);
124          console.log('');
125          console.log('  Then restart your shell or run: \x1b[36msource ~/.bashrc\x1b[0m');
126          break;
127        }
128        case 'fish': {
129          const completionsDir = join(home, '.config', 'fish', 'completions');
130          const completionFile = join(completionsDir, 'opencli.fish');
131          ensureDir(completionsDir);
132          writeFileSync(completionFile, FISH_COMPLETION, 'utf8');
133  
134          console.log(`✓ Fish completion installed to ${completionFile}`);
135          console.log(`  Restart your shell to activate.`);
136          break;
137        }
138      }
139    } catch (err) {
140      // Completion install is best-effort; never fail the package install
141      if (process.env.OPENCLI_VERBOSE) {
142        console.error(`Warning: Could not install shell completion: ${err.message}`);
143      }
144    }
145  
146    // ── Spotify credentials template ────────────────────────────────────
147    const opencliDir = join(home, '.opencli');
148    const spotifyEnvFile = join(opencliDir, 'spotify.env');
149    ensureDir(opencliDir);
150    if (!existsSync(spotifyEnvFile)) {
151      writeFileSync(spotifyEnvFile,
152        `# Spotify credentials — get them at https://developer.spotify.com/dashboard\n` +
153        `# Add http://127.0.0.1:8888/callback as a Redirect URI in your Spotify app\n` +
154        `SPOTIFY_CLIENT_ID=your_spotify_client_id_here\n` +
155        `SPOTIFY_CLIENT_SECRET=your_spotify_client_secret_here\n`,
156        'utf8'
157      );
158      console.log(`✓ Spotify credentials template created at ${spotifyEnvFile}`);
159      console.log(`  Edit the file and add your Client ID and Secret, then run: opencli spotify auth`);
160    }
161  
162    // ── Browser Bridge setup hint ───────────────────────────────────────
163    console.log('');
164    console.log('  \x1b[1mNext step — Browser Bridge setup\x1b[0m');
165    console.log('  Browser commands (bilibili, zhihu, twitter...) require the extension:');
166    console.log('  1. Download: https://github.com/jackwener/opencli/releases');
167    console.log('  2. In Chrome or Chromium, open chrome://extensions → enable Developer Mode → Load unpacked');
168    console.log('');
169    console.log('  Then run \x1b[36mopencli doctor\x1b[0m to verify.');
170    console.log('');
171  
172  }
173  
174  main();