Repository: sindresorhus/fkill-cli Branch: main Commit: 2afe64c91106 Files: 12 Total size: 19.4 KB Directory structure: gitextract_uzc5eiyk/ ├── .editorconfig ├── .gitattributes ├── .github/ │ └── workflows/ │ └── main.yml ├── .gitignore ├── .npmrc ├── cli.js ├── fixture.js ├── interactive.js ├── license ├── package.json ├── readme.md └── test.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*] indent_style = tab end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.yml] indent_style = space indent_size = 2 ================================================ FILE: .gitattributes ================================================ * text=auto eol=lf ================================================ FILE: .github/workflows/main.yml ================================================ name: CI on: - push - pull_request jobs: test: name: Node.js ${{ matrix.node-version }} on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: node-version: - 24 - 20 os: - ubuntu-latest - macos-latest - windows-latest steps: - uses: actions/checkout@v5 - uses: actions/setup-node@v6 with: node-version: ${{ matrix.node-version }} - run: npm install - run: npm test ================================================ FILE: .gitignore ================================================ node_modules yarn.lock ================================================ FILE: .npmrc ================================================ package-lock=false ================================================ FILE: cli.js ================================================ #!/usr/bin/env node import process from 'node:process'; import meow from 'meow'; import fkill from 'fkill'; const cli = meow(` Usage $ fkill [ …] Options --force -f Force kill --verbose -v Show process arguments --silent -s Silently kill and always exit with code 0 --force-after-timeout , -t Force kill processes which didn't exit after N seconds --smart-case Case-insensitive unless pattern contains uppercase --case-sensitive Force case-sensitive matching Examples $ fkill 1337 $ fkill safari $ fkill :8080 $ fkill 1337 safari :8080 $ fkill To kill a port, prefix it with a colon. For example: :8080. Run without arguments to use the interactive mode. In interactive mode, 🚦n% indicates high CPU usage and 🐏n% indicates high memory usage. Supports fuzzy search in the interactive mode. The process name is case-insensitive by default. `, { importMeta: import.meta, inferType: true, flags: { force: { type: 'boolean', shortFlag: 'f', }, verbose: { type: 'boolean', shortFlag: 'v', }, silent: { type: 'boolean', shortFlag: 's', }, forceAfterTimeout: { type: 'number', shortFlag: 't', }, smartCase: { type: 'boolean', }, caseSensitive: { type: 'boolean', }, }, }); const shouldIgnoreCase = (inputs, flags) => { // Explicit case-sensitive flag takes precedence over smart-case if (flags.caseSensitive) { return false; } // Smart-case: ignore case unless ANY input contains uppercase // Note: With multiple inputs, if ANY has uppercase, ALL are matched case-sensitively if (flags.smartCase) { const hasUpperCase = inputs.some(input => /[A-Z]/.test(String(input))); return !hasUpperCase; } // Default: always ignore case (maintains backward compatibility) return true; }; if (cli.input.length === 0) { const interactiveInterface = await import('./interactive.js'); interactiveInterface.init(cli.flags); } else { const forceAfterTimeout = cli.flags.forceAfterTimeout === undefined ? undefined : cli.flags.forceAfterTimeout * 1000; const ignoreCase = shouldIgnoreCase(cli.input, cli.flags); const promise = fkill(cli.input, {...cli.flags, forceAfterTimeout, ignoreCase}); if (!cli.flags.force) { try { await promise; } catch (error) { if (!cli.flags.silent) { if (error.message.includes('Could not find a process with port')) { console.error(error.message); process.exit(1); } const interactiveInterface = await import('./interactive.js'); interactiveInterface.handleFkillError(cli.input, cli.flags); } } } } ================================================ FILE: fixture.js ================================================ import process from 'node:process'; import http from 'node:http'; const server = http.createServer((request, response) => { response.end(); }); server.listen(process.argv.slice(2)[0]); ================================================ FILE: interactive.js ================================================ import process from 'node:process'; import chalk from 'chalk'; import inquirer from 'inquirer'; import search from '@inquirer/search'; import psList from 'ps-list'; import {numberSortDescending} from 'num-sort'; import escExit from 'esc-exit'; import cliTruncate from 'cli-truncate'; import {allPortsWithPid} from 'pid-port'; import fkill from 'fkill'; import {processExists} from 'process-exists'; import FuzzySearch from 'fuzzy-search'; const isWindows = process.platform === 'win32'; const commandLineMargins = 4; const PROCESS_EXITED_MIN_INTERVAL = 5; const PROCESS_EXITED_MAX_INTERVAL = 1280; const delay = ms => new Promise(resolve => { setTimeout(resolve, ms); }); const processExited = async (pid, timeout) => { const endTime = Date.now() + timeout; let interval = PROCESS_EXITED_MIN_INTERVAL; if (interval > timeout) { interval = timeout; } let exists; do { await delay(interval); // eslint-disable-line no-await-in-loop exists = await processExists(pid); // eslint-disable-line no-await-in-loop interval *= 2; if (interval > PROCESS_EXITED_MAX_INTERVAL) { interval = PROCESS_EXITED_MAX_INTERVAL; } } while (Date.now() < endTime && exists); return !exists; }; const preferNotMatching = matches => (a, b) => { const aMatches = matches(a); return matches(b) === aMatches ? 0 : (aMatches ? 1 : -1); }; const deprioritizedProcesses = new Set(['iTerm', 'iTerm2', 'fkill']); const isDeprioritizedProcess = process_ => deprioritizedProcesses.has(process_.name); const preferNotDeprioritized = preferNotMatching(isDeprioritizedProcess); const preferLowAlphanumericNames = (a, b) => a.name.localeCompare(b.name); const preferHighPerformanceImpact = (a, b) => { const hasCpu = typeof a.cpu === 'number' && typeof b.cpu === 'number'; const hasMemory = typeof a.memory === 'number' && typeof b.memory === 'number'; if (hasCpu && hasMemory) { return numberSortDescending(a.cpu + a.memory, b.cpu + b.memory); } if (hasCpu) { return numberSortDescending(a.cpu, b.cpu); } if (hasMemory) { return numberSortDescending(a.memory, b.memory); } return 0; }; const preferHeurisicallyInterestingProcesses = (a, b) => { let result; result = preferNotDeprioritized(a, b); if (result !== 0) { return result; } result = preferHighPerformanceImpact(a, b); if (result !== 0) { return result; } return preferLowAlphanumericNames(a, b); }; const isHelperProcess = process_ => process_.name.endsWith('-helper') || process_.name.endsWith('Helper') || process_.name.endsWith('HelperApp'); const renderPercentage = percents => { const digits = Math.floor(percents * 10).toString().padStart(2, '0'); const whole = digits.slice(0, -1); const fraction = digits.slice(-1); return fraction === '0' ? `${whole}%` : `${whole}.${fraction}%`; }; const renderProcessForDisplay = (process_, flags, memoryThreshold, cpuThreshold) => { const lineLength = process.stdout.columns || 80; const ports = process_.ports.length === 0 ? '' : (' ' + process_.ports.slice(0, 4).map(x => `:${x}`).join(' ')); const memory = (process_.memory !== undefined && (process_.memory > memoryThreshold)) ? ` 🐏${renderPercentage(process_.memory)}` : ''; const cpu = (process_.cpu !== undefined && (process_.cpu > cpuThreshold)) ? `🚦${renderPercentage(process_.cpu)}` : ''; const margins = commandLineMargins + process_.pid.toString().length + ports.length + memory.length + cpu.length; const length = lineLength - margins; const name = cliTruncate(flags.verbose && !isWindows ? process_.cmd : process_.name, length, {position: 'middle', preferTruncationOnSpace: true}); const extraMargin = 2; const spacer = lineLength === process.stdout.columns ? ''.padEnd(length - name.length - extraMargin) : ''; return { name: `${name} ${chalk.dim(process_.pid)}${spacer}${chalk.dim(ports)}${cpu}${memory}`, value: process_.pid, }; }; const searchProcessesByPort = (processes, port) => processes.filter(process_ => process_.ports.includes(port)); const searchProcessByPid = (processes, pid) => processes.find(process_ => String(process_.pid) === pid); const searchProcessesByName = (processes, term, searcher, flags = {}) => { // Determine if we should match case-sensitively const hasUpperCase = /[A-Z]/.test(term); const shouldMatchCase = flags.caseSensitive || (flags.smartCase && hasUpperCase); const normalizedTerm = shouldMatchCase ? term : term.toLowerCase(); const exactMatches = []; const startsWithMatches = []; const containsMatches = []; for (const process_ of processes) { const normalizedName = shouldMatchCase ? process_.name : process_.name.toLowerCase(); if (normalizedName === normalizedTerm) { exactMatches.push(process_); } else if (normalizedName.startsWith(normalizedTerm)) { startsWithMatches.push(process_); } else if (normalizedName.includes(normalizedTerm)) { containsMatches.push(process_); } } // Fuzzy matches (excluding all exact/starts/contains matches) // Keep fuzzy search case-insensitive for better UX const matchedPids = new Set([...exactMatches, ...startsWithMatches, ...containsMatches].map(process_ => process_.pid)); const fuzzyResults = searcher.search(term).filter(process_ => !matchedPids.has(process_.pid)); // Combine in priority order return [...exactMatches, ...startsWithMatches, ...containsMatches, ...fuzzyResults]; }; const filterAndSortProcesses = (processes, term, searcher, flags) => { const filtered = processes.filter(process_ => !isHelperProcess(process_)); // No search term: show all sorted by performance if (!term) { return filtered.sort(preferHeurisicallyInterestingProcesses); } // Search by port if (term.startsWith(':')) { const port = term.slice(1); return searchProcessesByPort(filtered, port); } // Search by PID const pidMatch = searchProcessByPid(filtered, term); if (pidMatch) { return [pidMatch]; } // Search by name return searchProcessesByName(filtered, term, searcher, flags); }; const handleFkillError = async (inputs, flags = {}) => { const shouldForceKill = await promptForceKill(inputs, 'Error killing process.'); if (shouldForceKill) { // Determine case sensitivity based on flags and inputs // If ANY input has uppercase letter, match case-sensitively with --smart-case const hasUpperCase = inputs.some(input => /[A-Z]/.test(String(input))); const ignoreCase = flags.caseSensitive ? false : (flags.smartCase ? !hasUpperCase : true); await fkill(inputs, { force: true, ignoreCase, }); } }; const DEFAULT_EXIT_TIMEOUT = 3000; const attemptKillProcesses = async processes => { try { await fkill(processes); const exitStatuses = await Promise.all(processes.map(process_ => processExited(process_, DEFAULT_EXIT_TIMEOUT))); const survivors = processes.filter((_, index) => !exitStatuses[index]); return {survivors, hadError: false}; } catch { return {survivors: processes, hadError: true}; } }; const promptForceKill = async (survivingProcesses, message) => { if (process.stdout.isTTY === false) { console.error(`${message} Try \`fkill --force ${survivingProcesses.join(' ')}\``); process.exit(1); // eslint-disable-line unicorn/no-process-exit } const answer = await inquirer.prompt([{ type: 'confirm', name: 'forceKill', message: `${message} Would you like to use the force?`, }]); return answer.forceKill; }; const performKillSequence = async processes => { const processList = Array.isArray(processes) ? processes : [processes]; const {survivors, hadError} = await attemptKillProcesses(processList); if (survivors.length === 0) { return; } const suffix = survivors.length > 1 ? 'es' : ''; const message = hadError ? `Error killing process${suffix}.` : `Process${suffix} didn't exit in ${DEFAULT_EXIT_TIMEOUT}ms.`; const shouldForceKill = await promptForceKill(survivors, message); if (shouldForceKill) { await fkill(processList, { force: true, ignoreCase: true, }); } }; const findPortsForProcess = (processId, portToPidMap) => { const ports = []; for (const [port, pid] of portToPidMap.entries()) { if (processId === pid) { ports.push(String(port)); } } return ports; }; const listProcesses = async (processes, flags) => { const memoryThreshold = flags.verbose ? 0 : 1; const cpuThreshold = flags.verbose ? 0 : 3; const searcher = new FuzzySearch(processes, ['name'], {caseSensitive: false}); const selectedPid = await search({ message: 'Running processes:', pageSize: 10, async source(term = '') { const matchingProcesses = filterAndSortProcesses(processes, term, searcher, flags); return matchingProcesses.map(process_ => renderProcessForDisplay(process_, flags, memoryThreshold, cpuThreshold)); }, }); performKillSequence(selectedPid); }; const init = async flags => { escExit(); const [portToPidMap, processes] = await Promise.all([ allPortsWithPid(), psList({all: false}), ]); const processesWithPorts = processes.map(process_ => ({ ...process_, ports: findPortsForProcess(process_.pid, portToPidMap), })); listProcesses(processesWithPorts, flags); }; export {init, handleFkillError}; ================================================ FILE: license ================================================ MIT License Copyright (c) Sindre Sorhus (https://sindresorhus.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: package.json ================================================ { "name": "fkill-cli", "version": "9.0.0", "description": "Fabulously kill processes. Cross-platform.", "license": "MIT", "repository": "sindresorhus/fkill-cli", "funding": "https://github.com/sponsors/sindresorhus", "author": { "name": "Sindre Sorhus", "email": "sindresorhus@gmail.com", "url": "https://sindresorhus.com" }, "type": "module", "bin": { "fkill": "./cli.js" }, "sideEffects": false, "engines": { "node": ">=20" }, "scripts": { "test": "xo && ava" }, "files": [ "cli.js", "interactive.js" ], "keywords": [ "cli-app", "cli", "fkill", "kill", "killing", "killall", "taskkill", "sigkill", "sigterm", "force", "exit", "zap", "die", "ps", "proc" ], "dependencies": { "@inquirer/search": "^3.2.1", "chalk": "^5.6.2", "cli-truncate": "^5.1.1", "esc-exit": "^3.0.1", "fkill": "^10.0.0", "fuzzy-search": "^3.2.1", "inquirer": "^12.11.0", "meow": "^14.0.0", "num-sort": "^4.0.0", "pid-port": "^2.0.0", "ps-list": "^9.0.0" }, "devDependencies": { "ava": "^6.4.1", "delay": "^7.0.0", "execa": "^9.6.0", "get-port": "^7.1.0", "noop-process": "^5.0.0", "process-exists": "^5.0.0", "xo": "^1.2.3" } } ================================================ FILE: readme.md ================================================


fkill


> Fabulously kill processes. Cross-platform. Works on macOS, Linux, and Windows. ## Install ```sh npm install --global fkill-cli ``` ## Usage ``` $ fkill --help Usage $ fkill [ …] Options --force, -f Force kill --verbose, -v Show process arguments --silent, -s Silently kill and always exit with code 0 --force-timeout , -t Force kill processes which didn't exit after N seconds --smart-case Case-insensitive unless pattern contains uppercase --case-sensitive Force case-sensitive matching Examples $ fkill 1337 $ fkill safari $ fkill :8080 $ fkill 1337 safari :8080 $ fkill To kill a port, prefix it with a colon. For example: :8080. Run without arguments to use the interactive interface. In interactive mode, 🚦n% indicates high CPU usage and 🐏n% indicates high memory usage. Supports fuzzy search in the interactive mode. The process name is case-insensitive by default. ``` ## Interactive UI Run `fkill` without arguments to launch the interactive UI. ![](screenshot.svg) ## Related - [fkill](https://github.com/sindresorhus/fkill) - API for this package - [alfred-fkill](https://github.com/SamVerschueren/alfred-fkill) - Alfred workflow for this package ================================================ FILE: test.js ================================================ import process from 'node:process'; import childProcess from 'node:child_process'; import test from 'ava'; import {execa} from 'execa'; import delay from 'delay'; import noopProcess from 'noop-process'; import {processExists} from 'process-exists'; import getPort from 'get-port'; const noopProcessKilled = async (t, pid) => { // Ensure the noop process has time to exit await delay(100); t.false(await processExists(pid)); }; test('main', async t => { const {stdout} = await execa('./cli.js', ['--version']); t.true(stdout.length > 0); }); test('pid', async t => { const pid = await noopProcess(); await execa('./cli.js', ['--force', pid]); await noopProcessKilled(t, pid); }); // TODO: Upgrading AVA to latest caused this to not finish. Unclear why. // test('fuzzy search', async t => { // const pid = await noopProcess({title: '!noo00oop@'}); // await execa('./cli.js', ['o00oop@']); // await noopProcessKilled(t, pid); // }); test('kill from port', async t => { const port = await getPort(); const {pid} = childProcess.spawn('node', ['fixture.js', port]); await execa('./cli.js', ['--force', pid]); await noopProcessKilled(t, pid); }); test('error when process is not found', async t => { await t.throwsAsync( execa('./cli.js', ['--force', 'notFoundProcess']), {message: /Killing process notFoundProcess failed: Process doesn't exist/}, ); }); test('force killing process at unused port throws error', async t => { await t.throwsAsync( execa('./cli.js', ['--force', ':1337']), {message: /Killing process :1337 failed: Process doesn't exist/}, ); }); test('silently force killing process at unused port exits with code 0', async t => { const {exitCode} = await execa('./cli.js', ['--force', '--silent', ':1337']); t.is(exitCode, 0); }); // Case-sensitivity tests only work on Unix-like systems // Windows process names work differently and don't support custom titles via noopProcess if (process.platform !== 'win32') { test('default case-insensitive behavior', async t => { const pid = await noopProcess({title: 'DefaultCase'}); await execa('./cli.js', ['--force', 'defaultcase']); await noopProcessKilled(t, pid); }); test('case-sensitive flag makes matching case-sensitive', async t => { const pid = await noopProcess({title: 'CaseSensitive'}); await t.throwsAsync( execa('./cli.js', ['--case-sensitive', '--force', 'casesensitive']), {message: /Killing process casesensitive failed/}, ); // Clean up the process await execa('./cli.js', ['--force', pid]); }); test('smart-case with lowercase is case-insensitive', async t => { const pid = await noopProcess({title: 'SmartLower'}); await execa('./cli.js', ['--smart-case', '--force', 'smartlower']); await noopProcessKilled(t, pid); }); test('smart-case with uppercase is case-sensitive', async t => { const pid = await noopProcess({title: 'smartupper'}); await t.throwsAsync( execa('./cli.js', ['--smart-case', '--force', 'SmartUpper']), {message: /Killing process SmartUpper failed/}, ); // Clean up the process await execa('./cli.js', ['--force', pid]); }); } test('silent flag with -s shortflag works', async t => { const {exitCode} = await execa('./cli.js', ['-s', '--force', ':1337']); t.is(exitCode, 0); });