#!/usr/bin/env bun // wellfound-cli: search public Wellfound role pages. Zero runtime dependencies. import { runSearch, type SearchOpts } from "./commands/search.js" import { runDetail } from "./commands/detail.js" import { writeError } from "./helpers.js" const HELP = `wellfound-cli - search Wellfound (wellfound.com) startup jobs by role and location Usage: cli.ts search -q "" [--location ""] [--remote] [--jobage ] [--limit ] [--format json|table|plain] [--no-description] cli.ts detail [--format json|plain] Search flags: -q, --query Role title, slugified into Wellfound's /role/ path (e.g. "senior software engineer", "backend engineer", "blockchain engineer"). Required. -l, --location City slug/name (e.g. "new-york", "San Francisco"). Omit or pass --remote for remote roles. --remote Use the remote landing page (/role/r/). --jobage Keep only jobs posted within N days (client-side, from liveStartAt). --page Only page 1 is available server-side; any other value errors. -n, --limit Cap results (client-side). --format json|table|plain Default json. --no-description Drop the description bodies from JSON output (cheap discovery pass). --all-listings Keep every highlighted listing of matching startups, not just titles sharing a token with the query. ` type Flags = Record function parse(argv: string[]) { const [cmd = "", ...rest] = argv; const pos: string[] = []; const flags: Flags = {} for (let i = 0; i < rest.length; i++) { const a = rest[i] if (a === "-q") { flags.query = rest[++i] ?? ""; continue } if (a === "-l") { flags.location = rest[++i] ?? ""; continue } if (a === "-n") { flags.limit = rest[++i] ?? ""; continue } if (a.startsWith("--")) { const [k, inline] = a.slice(2).split("=", 2) if (inline !== undefined) { flags[k] = inline; continue } const next = rest[i + 1] if (k === "remote" || k === "no-description" || k === "all-listings" || next === undefined || next.startsWith("-")) flags[k] = true else flags[k] = rest[++i] continue } pos.push(a) } return { cmd, pos, flags } } const { cmd, pos, flags } = parse(process.argv.slice(2)) const KNOWN = new Set(["query", "location", "remote", "jobage", "page", "limit", "format", "no-description", "all-listings"]) for (const k of Object.keys(flags)) if (!KNOWN.has(k)) { writeError(`Unknown flag --${k}`, "BAD_ARG"); process.exit(1) } function num(name: string, dflt?: number): number | undefined { const raw = flags[name]; if (raw === undefined) return dflt const n = Number(raw); if (!Number.isFinite(n) || n < 0) { writeError(`--${name} must be a non-negative number, got "${raw}"`, "BAD_ARG"); process.exit(1) } return n } const str = (v: string | boolean | undefined) => (typeof v === "string" && v ? v : undefined) if (!cmd || cmd === "help" || cmd === "--help" || cmd === "-h") { process.stdout.write(HELP); process.exit(0) } if (cmd === "search") { const format = (str(flags.format) ?? "json") as SearchOpts["format"] if (!["json", "table", "plain"].includes(format)) { writeError("--format must be json, table or plain", "BAD_ARG"); process.exit(1) } await runSearch({ query: str(flags.query) ?? "", location: str(flags.location), remote: flags.remote === true, jobage: num("jobage"), page: num("page", 1)!, limit: num("limit"), format, noDescription: flags["no-description"] === true, allListings: flags["all-listings"] === true }) } else if (cmd === "detail") { if (!pos[0]) { writeError("detail requires an ", "NO_ID"); process.exit(1) } const format = (str(flags.format) ?? "json") as "json" | "plain" await runDetail(pos[0], format) } else { writeError(`Unknown command "${cmd}"`, "BAD_CMD"); process.exit(1) }