From eb1de23f4e1e72851e07c82193a06fccba4a0cc5 Mon Sep 17 00:00:00 2001 From: Evan Reichard Date: Sat, 2 May 2026 19:29:53 -0400 Subject: [PATCH] fix(test): prevent config file from leaking kagi token into search test The search token validation test only cleared KAGI_TOKEN from the environment but still loaded the user config file, which could supply the token and cause the test to pass incorrectly. Pass --config=/nonexistent/path so loadConfig returns an empty object. Also includes search command improvements: markdown/json format output and --format flag. --- src/index.ts | 30 ++++++++++++++++++++++++++++-- test/smoke.js | 2 +- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index 2ad7438..82d0061 100755 --- a/src/index.ts +++ b/src/index.ts @@ -2,7 +2,7 @@ import { loadConfig, type GlimpseConfig } from "./config.js"; import { createDriver, type WebDriver } from "./driver.js"; -import { searchKagi } from "./providers/kagi.js"; +import { searchKagi, type SearchResult } from "./providers/kagi.js"; import { readFileSync, writeFileSync } from "node:fs"; import TurndownService from "turndown"; @@ -118,6 +118,7 @@ Reader Options: Search Options: --provider= Search provider: kagi (default: config or kagi) --token= Kagi token (default: KAGI_TOKEN or config) + --format= Output format: markdown, json (default: markdown) Examples: glimpse snapshot https://example.com @@ -446,17 +447,33 @@ function renderReaderOutput(article: ReaderArticle, format: string) { } } +function searchResultsToMarkdown(results: SearchResult[]): string { + return results + .map((r) => `## [${r.title}](${r.url})\n> ${r.description}`) + .join("\n\n") + .trim(); +} + async function searchCommand() { const provider = getOption("--provider") ?? appConfig.search?.provider ?? "kagi"; const query = getPositionalArgs().join(" "); + const format = getOption("--format") ?? "markdown"; if (!query) usage(); + if (!["markdown", "json"].includes(format)) { + cliError( + "INVALID_OPTION", + `Unsupported search format: ${format}. Expected markdown, json.`, + ); + } + // Run Provider Search + let results: SearchResult[]; switch (provider) { case "kagi": - return searchKagi({ + results = await searchKagi({ query, token: getOption("--token"), config: appConfig, @@ -464,12 +481,21 @@ async function searchCommand() { existingUrl, timeoutMs, }); + break; default: cliError( "UNSUPPORTED_SEARCH_PROVIDER", `Unsupported search provider: ${provider}. Expected kagi.`, ); } + + // Render Output + switch (format) { + case "markdown": + return searchResultsToMarkdown(results); + case "json": + return results; + } } async function readerCommand() { diff --git a/test/smoke.js b/test/smoke.js index 5ca55d0..1367215 100755 --- a/test/smoke.js +++ b/test/smoke.js @@ -163,7 +163,7 @@ test( test("search validates kagi token in provider", ["search", "errors"], () => { const env = { ...process.env }; delete env.KAGI_TOKEN; - const result = runCli(["search", "example query"], { env }); + const result = runCli(["search", "--config=/nonexistent/path", "example query"], { env }); const output = parseJson(result.stderr); assert.notEqual(result.status, 0, result.stdout || result.stderr);