diff --git a/.agents/skills/freehire-search/cli/src/cli.ts b/.agents/skills/freehire-search/cli/src/cli.ts index b0d66a07..16ec3022 100644 --- a/.agents/skills/freehire-search/cli/src/cli.ts +++ b/.agents/skills/freehire-search/cli/src/cli.ts @@ -168,7 +168,13 @@ async function main(): Promise { } if (cmd === "search") { - const fmt = (flags.format as string) || "json" + const fmt = flags.format !== undefined ? String(flags.format) : "json" + if (!["json", "table", "plain"].includes(fmt)) { + process.stderr.write( + JSON.stringify({ error: `--format must be one of json|table|plain, got "${fmt}"`, code: "BAD_ARG" }) + "\n", + ) + return 1 + } // Validated here rather than server-side: the API answers an unrecognized // format with raw HTML instead of an error, so a typo would silently change @@ -209,7 +215,7 @@ async function main(): Promise { jobage: flags.jobage ? parseInt(flags.jobage as string, 10) : 9999, page: flags.page ? Math.max(1, parseInt(flags.page as string, 10)) : 1, limit: flags.limit ? Math.max(1, parseInt(flags.limit as string, 10)) : 25, - format: (["json", "table", "plain"].includes(fmt) ? fmt : "json") as SearchOpts["format"], + format: fmt as SearchOpts["format"], descriptionFormat: descFmt as DescriptionFormat, includeDescription: flags["no-description"] === undefined, regions: commaList(flags.region), @@ -232,8 +238,14 @@ async function main(): Promise { process.stderr.write(JSON.stringify({ error: "detail requires a ", code: "NO_ID" }) + "\n") return 1 } - const fmt = (flags.format as string) || "json" - const opts: DetailOpts = { id, format: fmt === "plain" ? "plain" : "json" } + const fmt = flags.format !== undefined ? String(flags.format) : "json" + if (!["json", "plain"].includes(fmt)) { + process.stderr.write( + JSON.stringify({ error: `--format must be one of json|plain, got "${fmt}"`, code: "BAD_ARG" }) + "\n", + ) + return 1 + } + const opts: DetailOpts = { id, format: fmt as DetailOpts["format"] } return runDetail(opts) } diff --git a/.agents/skills/freehire-search/cli/tests/cli-flag-validation.test.ts b/.agents/skills/freehire-search/cli/tests/cli-flag-validation.test.ts index 9691d6ab..372f94eb 100644 --- a/.agents/skills/freehire-search/cli/tests/cli-flag-validation.test.ts +++ b/.agents/skills/freehire-search/cli/tests/cli-flag-validation.test.ts @@ -75,6 +75,24 @@ describe("freehire CLI flag validation", () => { }); }); + describe("--format validation", () => { + test("search with an unsupported format exits 1 with BAD_ARG", async () => { + const result = await runCLI(["search", "--format", "invalid"]); + expect(result.exitCode).not.toBe(0); + const err = parsedStderr(result.stderr); + expect(err.code).toBe("BAD_ARG"); + expect(err.error).toMatch(/format/); + }); + + test("detail with an unsupported format exits 1 with BAD_ARG", async () => { + const result = await runCLI(["detail", "example-slug", "--format", "table"]); + expect(result.exitCode).not.toBe(0); + const err = parsedStderr(result.stderr); + expect(err.code).toBe("BAD_ARG"); + expect(err.error).toMatch(/format/); + }); + }); + describe("detail argument validation", () => { test("missing slug exits 1 with NO_ID", async () => { const result = await runCLI(["detail"]); diff --git a/.agents/skills/linkedin-search/cli/src/cli.ts b/.agents/skills/linkedin-search/cli/src/cli.ts index 5d413a53..d3f7e597 100644 --- a/.agents/skills/linkedin-search/cli/src/cli.ts +++ b/.agents/skills/linkedin-search/cli/src/cli.ts @@ -113,7 +113,13 @@ async function main(): Promise { ) return 1 } - const fmt = (flags.format as string) || "json" + const fmt = flags.format !== undefined ? String(flags.format) : "json" + if (!["json", "table", "plain"].includes(fmt)) { + process.stderr.write( + JSON.stringify({ error: `--format must be one of json|table|plain, got "${fmt}"`, code: "BAD_ARG" }) + "\n", + ) + return 1 + } if (flags.jobage !== undefined && flags["jobage-minutes"] !== undefined) { process.stderr.write( @@ -168,7 +174,7 @@ async function main(): Promise { remote: typeof flags.remote === "string" ? flags.remote : undefined, page: flags.page ? Math.max(1, parseInt(flags.page as string, 10)) : 1, limit: flags.limit ? parseInt(flags.limit as string, 10) : undefined, - format: (["json", "table", "plain"].includes(fmt) ? fmt : "json") as SearchOpts["format"], + format: fmt as SearchOpts["format"], } return runSearch(opts) } @@ -179,10 +185,16 @@ async function main(): Promise { process.stderr.write(JSON.stringify({ error: "detail requires an ", code: "NO_ID" }) + "\n") return 1 } - const fmt = (flags.format as string) || "json" + const fmt = flags.format !== undefined ? String(flags.format) : "json" + if (!["json", "plain"].includes(fmt)) { + process.stderr.write( + JSON.stringify({ error: `--format must be one of json|plain, got "${fmt}"`, code: "BAD_ARG" }) + "\n", + ) + return 1 + } const opts: DetailOpts = { id, - format: (fmt === "plain" ? "plain" : "json") as DetailOpts["format"], + format: fmt as DetailOpts["format"], } return runDetail(opts) } diff --git a/.agents/skills/linkedin-search/cli/tests/cli-flag-validation.test.ts b/.agents/skills/linkedin-search/cli/tests/cli-flag-validation.test.ts index cf09bac1..af96989f 100644 --- a/.agents/skills/linkedin-search/cli/tests/cli-flag-validation.test.ts +++ b/.agents/skills/linkedin-search/cli/tests/cli-flag-validation.test.ts @@ -134,6 +134,24 @@ describe("LinkedIn CLI flag validation", () => { expect(err.code).not.toBe("BAD_ARG"); }); }); + + describe("--format validation", () => { + test("search with an unsupported format exits 1 with BAD_ARG", async () => { + const result = await runCLI(["search", "-l", LOCATION, "--format", "invalid"]); + expect(result.exitCode).not.toBe(0); + const err = parsedStderr(result.stderr); + expect(err.code).toBe("BAD_ARG"); + expect(err.error).toMatch(/format/); + }); + + test("detail with an unsupported format exits 1 with BAD_ARG", async () => { + const result = await runCLI(["detail", "123456", "--format", "table"]); + expect(result.exitCode).not.toBe(0); + const err = parsedStderr(result.stderr); + expect(err.code).toBe("BAD_ARG"); + expect(err.error).toMatch(/format/); + }); + }); }); diff --git a/CHANGELOG.md b/CHANGELOG.md index 848e3c4a..ca424f9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,13 @@ per-file diff commands. ### Fixed +- **`linkedin-search` and `freehire-search` reject invalid `--format` flags with `BAD_ARG`** (#555, + `.agents/skills/linkedin-search/cli/src/cli.ts`, `.agents/skills/freehire-search/cli/src/cli.ts`) - + unrecognized `--format` flags previously fell back silently to `"json"` with exit code 0, masking + formatting typos and violating the CLI contract. Both CLIs now validate format arguments strictly + (`json|table|plain` for search, `json|plain` for detail) and exit 1 with `{ error, code: "BAD_ARG" }` + on invalid options. + - **`jobdanmark-search detail` no longer keeps a malformed closing tag as text on the rendered-HTML branch** (#525, `detail.ts`, `tests/detail-parsing.test.ts`) - a live page wrote its Jobtype item as `Fuldtid `, and because `node-html-parser` does not recognise a