mirror of
https://github.com/prdlk/jobyssey.git
synced 2026-11-08 07:57:39 +00:00
65 lines
3.8 KiB
TypeScript
Executable File
65 lines
3.8 KiB
TypeScript
Executable File
#!/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 "<role>" [--location "<city>"] [--remote] [--jobage <days>] [--limit <n>] [--format json|table|plain] [--no-description]
|
|
cli.ts detail <id-slug|url> [--format json|plain]
|
|
|
|
Search flags:
|
|
-q, --query <role> Role title, slugified into Wellfound's /role/ path (e.g. "senior software engineer", "backend engineer", "blockchain engineer"). Required.
|
|
-l, --location <city> City slug/name (e.g. "new-york", "San Francisco"). Omit or pass --remote for remote roles.
|
|
--remote Use the remote landing page (/role/r/<role>).
|
|
--jobage <days> Keep only jobs posted within N days (client-side, from liveStartAt).
|
|
--page <n> Only page 1 is available server-side; any other value errors.
|
|
-n, --limit <n> 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<string, string | boolean>
|
|
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 <id|url>", "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) }
|