username-checker
v0.2.1
Published
TypeScript-first username checking for Node.js, inspired by Sherlock's site intelligence.
Maintainers
Readme
Username Checker
TypeScript-first username checking for Node.js, inspired by Sherlock's site intelligence.
username-checker checks username availability across 478 bundled sites. Use it as a CLI for quick reconnaissance, or as a library inside scripts, apps, and internal tooling.
Table of Contents
- Highlights
- Installation
- CLI Quick Start
- Common CLI Workflows
- CLI Reference
- Configuration
- Library Quick Start
- Library Examples
- Library Reference
- How Detection Works
- Site Catalog
- Operational Notes
- Attribution
Highlights
- 478 bundled sites derived from Sherlock's site intelligence
- Concurrent checks with per-domain rate limiting, retry, and timeout controls
- Memory, file, and hybrid cache modes with configurable TTL
- Proxy (HTTP, HTTPS, SOCKS4, SOCKS5) and Tor support
- Three output formats: text, JSON, CSV — with per-username file output
- Batch API for checking multiple usernames in one pass
- Live progress callbacks for both single and batch checks
- Cancellation via
AbortSignal - Debug diagnostics for request and detection troubleshooting
- Config file (
.usernamerc) and environment variable support
Installation
Requires Node.js 20.10 or later.
Install locally:
npm install username-checkerInstall globally:
npm install -g username-checkerAfter a global install, both commands are available:
username-checker --help
uc --helpCLI Quick Start
Check one username across all enabled sites:
uc octocatThis writes octocat.txt in the current directory. To print to stdout instead:
uc octocat --stdout --no-writeCheck specific sites (case-insensitive names):
uc octocat -s github,gitlab,redditCheck multiple usernames and write per-user reports into a folder:
uc octocat torvalds --output-dir reportsShow live progress during a long run:
uc octocat --verbosePrint machine-readable JSON without writing files:
uc octocat --format json --stdout --no-writeCommon CLI Workflows
Developer handle discovery:
uc myhandle -s github,gitlab,stackoverflow,npmBrand sweep with CSV output:
uc mybrand --format csv -o mybrand.csvBatch check a team list with per-user reports:
uc alice bob charlie --output-dir team-resultsWrite sidecar JSON and CSV alongside the primary text report:
uc octocat --json results.json --csv results.csvCI-friendly stdout JSON (available handles only):
uc candidate --format json --stdout --no-write --available-onlyTargeted troubleshooting for one site:
uc octocat -s github --debug --debug-headers --debug-body --no-writeFilter results:
uc octocat --available-only # show only available
uc octocat --taken-only # show only takenUse Tor or a custom proxy:
uc octocat --tor
uc octocat --proxy socks5://127.0.0.1:1080Include sites normally excluded due to unreliable detection:
uc octocat --include-excludedCLI Reference
Usage
uc <usernames...> [options]Site Selection
| Flag | Short | Description |
| -------------------- | ----- | ----------------------------------------------- |
| --sites <sites> | -s | Comma-separated list of site names to check |
| --nsfw | | Include NSFW sites |
| --include-excluded | | Include sites marked unreliable in the manifest |
Output
| Flag | Short | Default | Description |
| -------------------- | ----- | ------- | ---------------------------------------------------- |
| --format <fmt> | -f | text | Primary output format: text, json, or csv |
| --output <path> | -o | | Write an aggregate primary report to a specific path |
| --output-dir <dir> | | | Write per-username primary reports into a directory |
| --stdout | | false | Print the primary output to stdout |
| --no-write | | | Disable all file writes |
| --json <filename> | | | Write an additional JSON sidecar report |
| --csv <filename> | | | Write an additional CSV sidecar report |
| --available-only | | false | Only include available results in output |
| --taken-only | | false | Only include taken results in output |
| --verbose | -v | false | Print live progress to stderr |
Network and Execution
| Flag | Short | Default | Description |
| ------------------- | ----- | ------- | ---------------------------------------------- |
| --timeout <ms> | -t | 15000 | Request timeout in milliseconds |
| --concurrency <n> | -c | 50 | Maximum concurrent requests |
| --retries <n> | -r | 2 | Retry attempts on failure |
| --proxy <url> | | | HTTP, HTTPS, SOCKS4, or SOCKS5 proxy URL |
| --tor | | false | Route requests through Tor on localhost:9050 |
Cache
| Flag | Default | Description |
| ------------------- | --------------------------- | ------------------------------------------------- |
| --cache <type> | memory | Cache mode: none, memory, file, or hybrid |
| --cache-dir <dir> | ./.username-checker-cache | Directory for file-based cache |
| --cache-ttl <ms> | 3600000 | Cache time-to-live in milliseconds (1 hour) |
Debugging
| Flag | Default | Description |
| ---------------------- | ------- | ----------------------------------------------- |
| --debug | false | Print per-site debug details to stderr |
| --debug-headers | false | Include response headers in debug output |
| --debug-body | false | Include response bodies in debug output |
| --debug-max-body <n> | 2000 | Maximum characters to capture per response body |
Configuration
| Flag | Description |
| ------------- | ----------------------------------------------------------------- |
| --no-config | Disable config file and environment variable loading for this run |
Output Behaviour
The CLI is file-oriented by default:
- One username → one primary report file (
<username>.<ext>) - Multiple usernames → one primary file per username, unless
--outputis given --output-dirplaces per-username files inside that directory--stdoutprints the primary report to stdout; use with--no-writeto suppress file creation--jsonand--csvwrite sidecar reports alongside the primary output- Debug output always goes to stderr so stdout pipelines remain machine-readable
Configuration
Configuration is layered. Later sources override earlier ones for the same key:
- Built-in defaults
- Config file
- Environment variables
- Explicit CLI flags (highest priority)
Config File
Config files are searched in this order:
- Path in
USERNAME_CHECKER_CONFIGenv var ./.usernamerc./.usernamerc.json~/.usernamerc~/.usernamerc.json$XDG_CONFIG_HOME/usernamerc.json$XDG_CONFIG_HOME/username-checker/usernamerc.json
Config files use JSON format:
{
"timeout": 15000,
"maxConcurrency": 50,
"retries": 2,
"includeNSFW": false,
"includeExcluded": false,
"format": "text",
"defaultSites": ["GitHub", "GitLab", "Reddit"],
"cache": {
"type": "hybrid",
"ttl": 3600000,
"dir": "./.username-checker-cache"
}
}defaultSites restricts which sites are checked when --sites is omitted.
Environment Variables
| Variable | Description |
| ---------------------------------- | --------------------------------------- |
| USERNAME_CHECKER_TIMEOUT | Request timeout in ms |
| USERNAME_CHECKER_CONCURRENCY | Alias for maxConcurrency |
| USERNAME_CHECKER_MAX_CONCURRENCY | Maximum concurrent requests |
| USERNAME_CHECKER_RETRIES | Retry attempts |
| USERNAME_CHECKER_NSFW | true/false — include NSFW sites |
| USERNAME_CHECKER_EXCLUDED | true/false — include excluded sites |
| USERNAME_CHECKER_TOR | true/false — use Tor |
| USERNAME_CHECKER_PROXY | Proxy URL |
| USERNAME_CHECKER_FORMAT | Default output format |
| USERNAME_CHECKER_CACHE_TYPE | Cache mode |
| USERNAME_CHECKER_CACHE_TTL | Cache TTL in ms |
| USERNAME_CHECKER_DEFAULT_SITES | Comma-separated default site list |
| USERNAME_CHECKER_CONFIG | Explicit path to config file |
Boolean env vars accept true/1/yes or false/0/no.
Examples:
export USERNAME_CHECKER_TIMEOUT=10000
export USERNAME_CHECKER_DEFAULT_SITES=GitHub,GitLab,Reddit
uc octocatUSERNAME_CHECKER_PROXY=socks5://127.0.0.1:1080 uc octocat --format json --stdout --no-writeLibrary Quick Start
import { UsernameChecker } from 'username-checker';
const checker = new UsernameChecker();
const results = await checker.check('octocat');
const available = results.filter((r) => r.status === 'available');
console.log(`Available on ${available.length} sites`);Library Examples
Check with site filter and custom options
import { UsernameChecker } from 'username-checker';
const checker = new UsernameChecker({ timeout: 12000, retries: 1 });
const results = await checker.check('octocat', {
sites: ['GitHub', 'GitLab', 'npm'],
});
for (const result of results) {
console.log(result.siteName, result.status, result.url);
}Live progress during a single-username check
import { UsernameChecker } from 'username-checker';
const checker = new UsernameChecker();
const results = await checker.check('octocat', {
onProgress: (p) => {
process.stdout.write(`\r${p.completed}/${p.total} (${p.percentage}%)`);
},
});
console.log('\nDone');Batch check multiple usernames
import { UsernameChecker } from 'username-checker';
const checker = new UsernameChecker({ maxConcurrency: 25 });
const batch = await checker.checkBatch(['octocat', 'torvalds'], {
sites: ['GitHub', 'GitLab'],
onBatchProgress: (p) => {
console.log(`${p.currentUsername} — ${p.currentUsernameIndex + 1}/${p.totalUsernames}`, `(${p.totalPercentage}%)`);
},
});
for (const entry of batch) {
console.log(entry.username, entry.summary);
}Cancellation with AbortController
import { UsernameChecker } from 'username-checker';
const checker = new UsernameChecker();
const controller = new AbortController();
// Cancel after 10 seconds
setTimeout(() => controller.abort(), 10_000);
const results = await checker.check('octocat', {
signal: controller.signal,
});Proxy and Tor
import { UsernameChecker } from 'username-checker';
// HTTP/HTTPS/SOCKS proxy
const checker = new UsernameChecker({ proxy: 'socks5://127.0.0.1:1080' });
// Tor (requires Tor running on localhost:9050)
const torChecker = new UsernameChecker({ useTor: true });Cache modes
import { UsernameChecker } from 'username-checker';
// Memory cache (default) — fast, not persistent across runs
const memChecker = new UsernameChecker({ cache: { type: 'memory', ttl: 60_000 } });
// File cache — persists to disk across runs
const fileChecker = new UsernameChecker({
cache: { type: 'file', ttl: 3_600_000, dir: './.username-checker-cache' },
});
// Hybrid — memory for speed, file for persistence
const hybridChecker = new UsernameChecker({
cache: { type: 'hybrid', ttl: 3_600_000 },
});
// Disable caching entirely
const noCache = new UsernameChecker({ cache: false });Debug diagnostics
import { UsernameChecker } from 'username-checker';
const checker = new UsernameChecker();
const results = await checker.check('octocat', {
sites: ['GitHub'],
debug: {
includeHeaders: true,
includeBody: true,
maxBodyLength: 1500,
},
});
const r = results[0];
console.log(r.diagnostics); // probeUrl, requestMethod, detectionMethods, followRedirects, finalUrl
console.log(r.debug); // statusCode, responseHeaders, responseBodyCustom site repository
import { ManifestRepository, UsernameChecker } from 'username-checker';
const repository = ManifestRepository.fromRawData({
Example: {
name: 'Example',
url: 'https://example.com/{}',
urlMain: 'https://example.com/',
errorType: 'status_code',
},
});
const checker = new UsernameChecker({ repository });
const results = await checker.check('octocat');
console.log(results);Error handling
import { UsernameChecker } from 'username-checker';
const checker = new UsernameChecker();
try {
const results = await checker.check('octocat', {
sites: ['GitHub', 'UnknownSite'],
});
} catch (err) {
// Thrown when a site name cannot be resolved.
// The error message includes fuzzy suggestions:
// "Unknown site: "UnknownSite" (did you mean GitHub?)"
console.error(err.message);
}
try {
await checker.check(''); // throws: "Invalid username: Username cannot be empty"
} catch (err) {
console.error(err.message);
}TypeScript type imports
import type {
CheckResult,
CheckOptions,
CheckProgress,
BatchCheckResult,
BatchCheckOptions,
BatchCheckProgress,
CacheOptions,
DebugOptions,
CheckDiagnostics,
CheckDebugData,
ErrorCategory,
AvailabilityStatus,
} from 'username-checker';Library Reference
UsernameChecker
new UsernameChecker(options?: CheckOptions)Constructor options
| Option | Type | Default | Description |
| ----------------- | ----------------------- | ---------------- | ------------------------------------------ |
| timeout | number | 15000 | Request timeout in ms |
| maxConcurrency | number | 50 | Maximum concurrent requests |
| retries | number | 2 | Retry attempts on failure |
| includeNSFW | boolean | false | Include NSFW sites by default |
| includeExcluded | boolean | false | Include excluded sites by default |
| useTor | boolean | false | Route requests through Tor |
| proxy | string | | HTTP, HTTPS, SOCKS4, or SOCKS5 proxy URL |
| cache | CacheOptions \| false | false | Cache configuration, or false to disable |
| repository | SiteRepository | bundled manifest | Custom site repository |
checker.check(username, options?)
Check a single username across sites. Returns Promise<CheckResult[]>.
Throws if the username is invalid or if any specified site name cannot be resolved.
| Option | Type | Description |
| ----------------- | ---------------------------- | -------------------------------------------- |
| sites | string[] | Limit to these site names (case-insensitive) |
| includeNSFW | boolean | Override instance-level setting |
| includeExcluded | boolean | Override instance-level setting |
| onProgress | (p: CheckProgress) => void | Called after each site completes |
| signal | AbortSignal | Cancellation signal |
| debug | DebugOptions | Enable diagnostic data on results |
checker.checkBatch(usernames, options?)
Check multiple usernames in sequence. Returns Promise<BatchCheckResult[]>.
Accepts all options from check(), plus:
| Option | Type | Description |
| ----------------- | --------------------------------- | ----------------------------------------------------------------- |
| onBatchProgress | (p: BatchCheckProgress) => void | Called at the start of each username and on every site completion |
checker.checkSite(username, siteKey, config?, signal?, debug?)
Check a single username against a single named site. Returns Promise<CheckResult>. Useful for targeted checks or custom retry loops.
CheckResult
interface CheckResult {
site: string; // canonical site key
siteName: string; // human-readable name
url: string; // profile URL for this username
status: AvailabilityStatus; // 'available' | 'taken' | 'invalid' | 'unknown' | 'error'
httpStatus?: number; // raw HTTP status code
responseTime: number; // request duration in ms
errorCategory: ErrorCategory;
errorMessage?: string;
diagnostics?: CheckDiagnostics; // present when debug is enabled
debug?: CheckDebugData; // present when debug is enabled
}AvailabilityStatus values
| Value | Meaning |
| ----------- | ---------------------------------------------------------- |
| available | Username appears to be free on that site |
| taken | Username appears to exist on that site |
| invalid | Username does not match the site's own format constraints |
| unknown | Request succeeded but detection was inconclusive |
| error | Transport, rate-limit, block, timeout, or upstream failure |
CheckDiagnostics (when debug is enabled)
interface CheckDiagnostics {
probeUrl: string;
requestMethod: 'GET' | 'POST' | 'HEAD' | 'PUT';
detectionMethods: DetectionMethod[];
followRedirects: boolean;
finalUrl?: string;
errorCodes?: number[];
}CheckDebugData (when debug is enabled)
interface CheckDebugData {
statusCode?: number;
responseHeaders?: Record<string, string>;
responseBody?: string;
}ErrorCategory
The errorCategory field distinguishes the root cause when status is error:
| Value | Meaning |
| ------------------ | ------------------------------------------------------------------ |
| timeout | Request exceeded the configured timeout |
| rate_limited | Site returned HTTP 429 or equivalent |
| blocked | WAF, Cloudflare challenge, or bot detection blocked the request |
| server_error | Site returned a 5xx response |
| connection_error | DNS, TCP, or TLS failure |
| unknown | Error could not be classified |
| none | No error — result is available, taken, invalid, or unknown |
BatchCheckResult
interface BatchCheckResult {
username: string;
normalizedUsername: string; // trimmed and lowercased form
results: CheckResult[];
summary: {
total: number;
available: number;
taken: number;
errors: number;
};
}BatchCheckProgress
interface BatchCheckProgress {
currentUsername: string;
currentUsernameIndex: number; // 0-based
totalUsernames: number;
usernamePercentage: number; // 0–100 for the current username's sites
totalPercentage: number; // 0–100 across all usernames and sites
siteProgress?: CheckProgress; // site-level progress for the current username
}CheckResultCache
CheckResultCache is a standalone cache you can use independently of UsernameChecker, for example to pre-warm entries or share a single cache across multiple checker instances.
import { CheckResultCache } from 'username-checker';
const cache = new CheckResultCache({ type: 'hybrid', ttl: 3_600_000 });
// Query
const cached = cache.get('GitHub', 'octocat'); // CheckResult | null
// Store
cache.set('GitHub', 'octocat', result);
// Check existence without reading
cache.has('GitHub', 'octocat'); // boolean
// Inspect cache size
const { type, memorySize, fileSize } = cache.stats();
// Clear all entries
cache.clear();CacheOptions
| Option | Type | Default | Description |
| --------- | -------------------------------- | --------------------------- | ------------------------------------ |
| type | 'memory' \| 'file' \| 'hybrid' | 'memory' | Cache backend |
| ttl | number | 3600000 | Time-to-live in ms (1 hour) |
| dir | string | ./.username-checker-cache | Directory for file-based caches |
| maxSize | number | 1000 | Maximum entries for the memory cache |
ManifestRepository
ManifestRepository implements SiteRepository and provides case-insensitive site resolution with fuzzy suggestions.
import { ManifestRepository } from 'username-checker';
// Use the pre-built repository for the bundled manifest
import { defaultManifestRepository } from 'username-checker'; // re-exported from Sites.tsStatic factories
// From Sherlock-format raw JSON data
const repo = ManifestRepository.fromRawData({
GitHub: {
name: 'GitHub',
url: 'https://github.com/{}',
urlMain: 'https://github.com/',
errorType: 'status_code',
},
});
// From an array of SiteConfig objects
const repo = ManifestRepository.fromSiteConfigs([
{ name: 'GitHub', url: 'https://github.com/{}', urlMain: 'https://github.com/', errorType: 'status_code' },
]);Instance methods
repo.has('GitHub'); // boolean — case-insensitive
repo.get('github'); // SiteConfig | undefined
repo.resolveKey('GITHUB'); // 'GitHub' | undefined (canonical key)
repo.suggestKeys('githb', 3); // string[] — fuzzy suggestions
repo.count(); // number of non-excluded sites
repo.count({ includeExcluded: true }); // total including excluded
repo.filter({ includeNSFW: true }); // SiteEntry[] — { key, config }
repo.resolveKeys(['GitHub', 'typo']); // { resolvedKeys: string[], missing: [...] }ConfigLoader
ConfigLoader exposes the same configuration loading pipeline the CLI uses internally.
import { ConfigLoader } from 'username-checker';
const config = ConfigLoader.loadConfig();
// Returns a ConfigOptions object merged from config file + env vars.
// CLI flags are not included — apply those on top manually.
console.log(config.timeout);
console.log(config.defaultSites);How Detection Works
Each site entry in the manifest specifies one of three detection strategies:
| Strategy | Description |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| status_code | The site returns a distinct HTTP status for missing profiles (typically 404). A 2xx indicates the username is taken. |
| message | The site always returns 200. Detection compares the response body against a known error string present only when the profile is missing. If the string is absent, the username is taken. |
| response_url | The site redirects missing profiles to a known URL. Detection checks the final URL after following redirects. |
Sites may declare multiple strategies as an array; all must pass for a taken determination. Sites marked isExcluded are in the manifest but excluded from default runs because their detection is known to produce false positives.
Site Catalog
The bundled manifest contains 478 sites (419 enabled, 38 excluded, 19 NSFW). See SITES.md for the full list with detection strategy and URL for each entry.
To regenerate the manifest from the upstream Sherlock project:
node scripts/sync-sites.mjsOperational Notes
- Site name matching is case-insensitive everywhere:
--sites Githuband--sites githubare equivalent. - When a site name cannot be resolved, the error message includes fuzzy suggestions.
- Multiple usernames are processed in order;
onBatchProgressfires at the start of each username. - Runtime depends heavily on network conditions, site selection, timeout, retries, and concurrency.
- The CLI's debug output always goes to stderr so stdout can remain machine-readable.
- The
--cachedefault ismemory, which only persists for the lifetime of a single CLI invocation. Usefileorhybridfor cross-run persistence.
Attribution
This project is independent software, but it builds on upstream site intelligence from the Sherlock project:
- Sherlock repository: https://github.com/sherlock-project/sherlock
- Published site manifest: https://data.sherlockproject.xyz
The bundled site catalog, schema checks, and false-positive exclusions are synced from Sherlock sources and adapted to this package's TypeScript runtime and CLI/library API. username-checker is not the Sherlock CLI, and it is not affiliated with or endorsed by the Sherlock maintainers.
