diff --git a/.agents/skills/linkedin-search/cli/src/cli.ts b/.agents/skills/linkedin-search/cli/src/cli.ts index 9815ed11..ffda2bd5 100644 --- a/.agents/skills/linkedin-search/cli/src/cli.ts +++ b/.agents/skills/linkedin-search/cli/src/cli.ts @@ -114,6 +114,15 @@ async function main(): Promise { ) return 1 } + // --query is optional, but a present one needs keywords: parseFlags stores + // a bare -q as true and -q "" as "", and both used to be treated as unset, + // returning every posting in the location as if it matched. + if (flags.query !== undefined && (typeof flags.query !== "string" || !flags.query.trim())) { + process.stderr.write( + JSON.stringify({ error: "--query requires a non-empty value", code: "BAD_ARG" }) + "\n", + ) + return 1 + } const fmt = flags.format !== undefined ? String(flags.format) : "json" if (!["json", "table", "plain"].includes(fmt)) { process.stderr.write( diff --git a/.agents/skills/linkedin-search/cli/tests/required-query-value.test.ts b/.agents/skills/linkedin-search/cli/tests/required-query-value.test.ts new file mode 100644 index 00000000..4f4f945e --- /dev/null +++ b/.agents/skills/linkedin-search/cli/tests/required-query-value.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, test } from "bun:test"; +import { runSearchFixture } from "./helpers"; + +const LOCATION = "Copenhagen, Denmark"; + +// parseFlags stores a value-less flag as `true` and an empty argument as "", +// and the search took `--query` only when it was a non-empty string. So a +// bare `-q`, `-q ""` (an unset shell variable) or a blank query dropped the +// keywords and returned every posting in the location with exit 0. These run +// the real CLI against the saved search fixture and count outbound requests. +describe("--query requires a value", () => { + for (const args of [ + ["-q"], + ["-q", "--jobage", "7"], + ["--query", ""], + ["-q", " "], + ]) { + test(`${JSON.stringify(args)} exits 1 with BAD_ARG before any request`, async () => { + const { result, requests } = await runSearchFixture(["search", "-l", LOCATION, ...args]); + expect(result.exitCode).toBe(1); + expect(result.stdout).toBe(""); + const error = JSON.parse(result.stderr); + expect(error.code).toBe("BAD_ARG"); + expect(error.error).toContain("--query"); + expect(requests).toHaveLength(0); + }); + } + + test("a query is sent as keywords", async () => { + const { result, requests } = await runSearchFixture(["search", "-l", LOCATION, "-q", "data engineer"]); + expect(result.exitCode).toBe(0); + expect(requests).toHaveLength(1); + expect(requests[0].searchParams.get("keywords")).toBe("data engineer"); + }); + + test("omitting --query is still an intentional location-only search", async () => { + const { result, requests } = await runSearchFixture(["search", "-l", LOCATION]); + expect(result.exitCode).toBe(0); + expect(requests).toHaveLength(1); + expect(requests[0].searchParams.has("keywords")).toBe(false); + }); +}); diff --git a/CHANGELOG.md b/CHANGELOG.md index 86d0ba7e..5d3bf66a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -122,6 +122,15 @@ per-file diff commands. with exit 0 and an empty stderr. Anything other than `remote`, `hybrid` or `onsite` (and the accepted `on-site` spelling) now exits 1 with `BAD_ARG` before any request is sent. +- **`linkedin-search` requires a value for `--query` instead of dropping the keywords** + (#646, `.agents/skills/linkedin-search/cli/src/cli.ts`, `tests/required-query-value.test.ts`) - the + flag parser stores a bare `-q` as `true` and `-q ""` as an empty string, and the search took + `--query` only when it was a non-empty string, so a bare flag, an empty argument (an unset + shell variable) or a blank query returned every posting in the location with exit 0. A present + `--query` now needs a non-empty value or exits 1 with `BAD_ARG` before any request; omitting + the flag is still a location-only search. The Danish portals (#573) and `freehire-search` + (#568) already reject a value-less option. + ## [1.7.3] - 2026-10-19 ### Added