synthetic-search 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 synthetic-search contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,125 @@
1
+ # synthetic-search
2
+
3
+ A user-facing CLI for Synthetic web search, quotas, and local credential management.
4
+
5
+ ## Features
6
+
7
+ - Search the public web with Synthetic (`/v2/search`)
8
+ - View account usage and limits (`/v2/quotas`)
9
+ - Interactive local auth setup (`auth login` / `auth logout` / `auth status`)
10
+ - Readable terminal output by default
11
+ - `--json` output mode for scripts and automation
12
+ - Tolerant JSON parsing for malformed control characters in API responses
13
+
14
+ ## Requirements
15
+
16
+ - Node.js 18+
17
+ - Synthetic API key
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install -g synthetic-search
23
+ ```
24
+
25
+ Or run directly with `npx`:
26
+
27
+ ```bash
28
+ npx -y synthetic-search --help
29
+ ```
30
+
31
+ ## Authentication
32
+
33
+ `SYNTHETIC_API_KEY` always takes priority over saved config.
34
+
35
+ ### Environment variable (recommended for CI)
36
+
37
+ ```bash
38
+ export SYNTHETIC_API_KEY=your_api_key_here
39
+ ```
40
+
41
+ ### Saved local config
42
+
43
+ ```bash
44
+ synthetic-search auth login
45
+ ```
46
+
47
+ This validates the key via `GET /v2/quotas` before saving.
48
+
49
+ Skip validation:
50
+
51
+ ```bash
52
+ synthetic-search auth login --no-validate
53
+ ```
54
+
55
+ Check status:
56
+
57
+ ```bash
58
+ synthetic-search auth status
59
+ ```
60
+
61
+ Remove saved key:
62
+
63
+ ```bash
64
+ synthetic-search auth logout
65
+ synthetic-search auth logout --force
66
+ ```
67
+
68
+ ## Usage
69
+
70
+ ### Search
71
+
72
+ Root command and subcommand are equivalent:
73
+
74
+ ```bash
75
+ synthetic-search latest model context protocol news
76
+ synthetic-search search latest model context protocol news
77
+ ```
78
+
79
+ Read query from piped stdin:
80
+
81
+ ```bash
82
+ echo "latest ai safety research" | synthetic-search search
83
+ ```
84
+
85
+ JSON output:
86
+
87
+ ```bash
88
+ synthetic-search search "latest mcp updates" --json
89
+ ```
90
+
91
+ Client-side result limit:
92
+
93
+ ```bash
94
+ synthetic-search search "latest mcp updates" --limit 2
95
+ ```
96
+
97
+ ### Quotas
98
+
99
+ ```bash
100
+ synthetic-search quotas
101
+ synthetic-search quotas --json
102
+ ```
103
+
104
+ ## API Endpoints
105
+
106
+ - `POST https://api.synthetic.new/v2/search`
107
+ - `GET https://api.synthetic.new/v2/quotas`
108
+
109
+ ## Development
110
+
111
+ ```bash
112
+ npm install
113
+ npm run test
114
+ npm run build
115
+ ```
116
+
117
+ Run in dev mode:
118
+
119
+ ```bash
120
+ npm run dev -- search "latest llm tooling"
121
+ ```
122
+
123
+ ## License
124
+
125
+ MIT
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { type FetchLike } from "./lib/client.js";
4
+ type ReadableLike = AsyncIterable<Uint8Array | string> & {
5
+ isTTY?: boolean;
6
+ };
7
+ type WritableLike = {
8
+ write: (chunk: string) => unknown;
9
+ isTTY?: boolean;
10
+ columns?: number;
11
+ };
12
+ export type CliIO = {
13
+ stdin: ReadableLike;
14
+ stdout: WritableLike;
15
+ stderr: WritableLike;
16
+ };
17
+ type PromptApi = {
18
+ password: (options: {
19
+ message: string;
20
+ mask?: string;
21
+ }) => Promise<string>;
22
+ confirm: (options: {
23
+ message: string;
24
+ default?: boolean;
25
+ }) => Promise<boolean>;
26
+ };
27
+ export type RunCliOptions = {
28
+ io?: CliIO;
29
+ env?: NodeJS.ProcessEnv;
30
+ fetchImpl?: FetchLike;
31
+ configDir?: string;
32
+ prompts?: Partial<PromptApi>;
33
+ };
34
+ type CommandContext = {
35
+ io: CliIO;
36
+ env: NodeJS.ProcessEnv;
37
+ fetchImpl: FetchLike;
38
+ configDir?: string;
39
+ prompts: PromptApi;
40
+ };
41
+ export declare function createProgram(context: CommandContext): Command;
42
+ export declare function runCli(argv?: string[], options?: RunCliOptions): Promise<number>;
43
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,288 @@
1
+ #!/usr/bin/env node
2
+ import { confirm as promptConfirm, password as promptPassword } from "@inquirer/prompts";
3
+ import { Command, CommanderError } from "commander";
4
+ import { pathToFileURL } from "node:url";
5
+ import { deleteSavedApiKey, hasSavedApiKey, maskApiKey, resolveCredentials, saveApiKey, } from "./lib/auth.js";
6
+ import { getQuotas, search } from "./lib/client.js";
7
+ import { SyntheticCliError, SyntheticUsageError, getErrorMessage } from "./lib/errors.js";
8
+ import { renderQuotasText, renderSearchResultsText, writeJson, writeJsonError } from "./lib/output.js";
9
+ const DEFAULT_PROMPTS = {
10
+ password: (options) => promptPassword(options),
11
+ confirm: (options) => promptConfirm(options),
12
+ };
13
+ function routeRootToSearch(argv) {
14
+ const firstArg = argv[0];
15
+ if (firstArg === undefined) {
16
+ return ["search"];
17
+ }
18
+ if (["search", "quotas", "auth", "help"].includes(firstArg)) {
19
+ return argv;
20
+ }
21
+ if (["--help", "-h", "--version", "-V"].includes(firstArg)) {
22
+ return argv;
23
+ }
24
+ if (firstArg.startsWith("-")) {
25
+ return ["search", ...argv];
26
+ }
27
+ return ["search", ...argv];
28
+ }
29
+ function wantsJsonOutput(argv) {
30
+ return argv.includes("--json");
31
+ }
32
+ function toOutputWidth(stdout) {
33
+ const columns = stdout.columns;
34
+ if (typeof columns === "number" && Number.isFinite(columns) && columns > 20) {
35
+ return columns;
36
+ }
37
+ return 80;
38
+ }
39
+ function isInteractive(io) {
40
+ return Boolean(io.stdin.isTTY && io.stdout.isTTY);
41
+ }
42
+ function parseLimit(limitValue) {
43
+ if (limitValue === undefined) {
44
+ return undefined;
45
+ }
46
+ const parsed = Number.parseInt(limitValue, 10);
47
+ if (!Number.isInteger(parsed) || parsed <= 0) {
48
+ throw new SyntheticUsageError("--limit must be a positive integer.");
49
+ }
50
+ return parsed;
51
+ }
52
+ async function readQueryFromInput(queryParts, stdin) {
53
+ if (queryParts.length > 0) {
54
+ const joinedQuery = queryParts.join(" ").trim();
55
+ return joinedQuery || null;
56
+ }
57
+ if (stdin.isTTY) {
58
+ return null;
59
+ }
60
+ let body = "";
61
+ for await (const chunk of stdin) {
62
+ body += typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
63
+ }
64
+ const trimmed = body.trim();
65
+ return trimmed || null;
66
+ }
67
+ function resolveApiKey(context) {
68
+ const resolved = resolveCredentials({
69
+ env: context.env,
70
+ configDir: context.configDir,
71
+ });
72
+ if (!resolved.apiKey) {
73
+ throw new SyntheticCliError("No Synthetic API key configured. Set SYNTHETIC_API_KEY or run `synthetic-search auth login`.");
74
+ }
75
+ return resolved.apiKey;
76
+ }
77
+ function applyLimit(items, limit) {
78
+ if (limit === undefined) {
79
+ return items;
80
+ }
81
+ return items.slice(0, limit);
82
+ }
83
+ async function runSearchCommand(queryParts, options, command, context) {
84
+ const query = await readQueryFromInput(queryParts, context.io.stdin);
85
+ if (!query) {
86
+ throw new SyntheticUsageError(`Query is required.\n\n${command.helpInformation()}`);
87
+ }
88
+ const limit = parseLimit(options.limit);
89
+ const apiKey = resolveApiKey(context);
90
+ const results = applyLimit(await search(query, apiKey, context.fetchImpl), limit);
91
+ if (options.json) {
92
+ writeJson(context.io.stdout, { results });
93
+ return;
94
+ }
95
+ context.io.stdout.write(`${renderSearchResultsText(results, toOutputWidth(context.io.stdout))}\n`);
96
+ }
97
+ async function runQuotasCommand(options, context) {
98
+ const apiKey = resolveApiKey(context);
99
+ const quotas = await getQuotas(apiKey, context.fetchImpl);
100
+ if (options.json) {
101
+ writeJson(context.io.stdout, quotas);
102
+ return;
103
+ }
104
+ context.io.stdout.write(`${renderQuotasText(quotas)}\n`);
105
+ }
106
+ async function runAuthLoginCommand(options, context) {
107
+ if (!isInteractive(context.io)) {
108
+ throw new SyntheticUsageError("auth login requires an interactive terminal.");
109
+ }
110
+ const apiKey = (await context.prompts.password({
111
+ message: "Enter your Synthetic API key",
112
+ mask: "*",
113
+ })).trim();
114
+ if (!apiKey) {
115
+ throw new SyntheticUsageError("API key cannot be empty.");
116
+ }
117
+ if (options.validate) {
118
+ try {
119
+ await getQuotas(apiKey, context.fetchImpl);
120
+ }
121
+ catch (error) {
122
+ throw new SyntheticCliError(`API key validation failed: ${getErrorMessage(error)}`);
123
+ }
124
+ }
125
+ saveApiKey(apiKey, { configDir: context.configDir });
126
+ context.io.stdout.write(options.validate ? "API key saved after validation.\n" : "API key saved without validation.\n");
127
+ }
128
+ async function runAuthLogoutCommand(options, context) {
129
+ if (!hasSavedApiKey({ configDir: context.configDir })) {
130
+ context.io.stdout.write("No saved API key found.\n");
131
+ return;
132
+ }
133
+ if (!options.force) {
134
+ if (!isInteractive(context.io)) {
135
+ throw new SyntheticUsageError("Use --force to logout in non-interactive mode.");
136
+ }
137
+ const confirmed = await context.prompts.confirm({
138
+ message: "Remove the saved Synthetic API key?",
139
+ default: false,
140
+ });
141
+ if (!confirmed) {
142
+ context.io.stdout.write("Logout cancelled.\n");
143
+ return;
144
+ }
145
+ }
146
+ deleteSavedApiKey({ configDir: context.configDir });
147
+ context.io.stdout.write("Saved API key removed.\n");
148
+ }
149
+ async function runAuthStatusCommand(context) {
150
+ const credentials = resolveCredentials({
151
+ env: context.env,
152
+ configDir: context.configDir,
153
+ });
154
+ if (credentials.source === "none" || !credentials.apiKey) {
155
+ context.io.stdout.write("Credential source: not configured\n");
156
+ return;
157
+ }
158
+ context.io.stdout.write(`Credential source: ${credentials.source}\n`);
159
+ context.io.stdout.write(`API key: ${maskApiKey(credentials.apiKey)}\n`);
160
+ try {
161
+ const quotas = await getQuotas(credentials.apiKey, context.fetchImpl);
162
+ context.io.stdout.write("Validation: ok\n");
163
+ context.io.stdout.write(`${renderQuotasText(quotas)}\n`);
164
+ }
165
+ catch (error) {
166
+ context.io.stdout.write("Validation: failed\n");
167
+ throw new SyntheticCliError(`Credential validation failed: ${getErrorMessage(error)}`);
168
+ }
169
+ }
170
+ function registerSearchCommand(command, context) {
171
+ command
172
+ .argument("[query...]", "Search query")
173
+ .option("--json", "Output results as JSON")
174
+ .option("--limit <n>", "Limit number of results")
175
+ .action(async (queryParts, options, cmd) => {
176
+ await runSearchCommand(Array.isArray(queryParts) ? queryParts : [], options, cmd, context);
177
+ });
178
+ }
179
+ export function createProgram(context) {
180
+ const program = new Command();
181
+ program
182
+ .name("synthetic-search")
183
+ .description("Search the public web with Synthetic and inspect account quotas.");
184
+ registerSearchCommand(program.command("search").description("Search the public web with Synthetic."), context);
185
+ program
186
+ .command("quotas")
187
+ .description("Show current Synthetic quota usage.")
188
+ .option("--json", "Output quotas as JSON")
189
+ .action(async (options) => {
190
+ await runQuotasCommand(options, context);
191
+ });
192
+ const authCommand = program.command("auth").description("Manage Synthetic API credentials.");
193
+ authCommand
194
+ .command("login")
195
+ .description("Save an API key after optional validation.")
196
+ .option("--no-validate", "Skip live validation against /v2/quotas")
197
+ .action(async (options) => {
198
+ await runAuthLoginCommand(options, context);
199
+ });
200
+ authCommand
201
+ .command("logout")
202
+ .description("Remove the saved API key.")
203
+ .option("--force", "Skip confirmation prompt")
204
+ .action(async (options) => {
205
+ await runAuthLogoutCommand(options, context);
206
+ });
207
+ authCommand
208
+ .command("status")
209
+ .description("Show credential source and validation status.")
210
+ .action(async () => {
211
+ await runAuthStatusCommand(context);
212
+ });
213
+ program.showHelpAfterError();
214
+ return program;
215
+ }
216
+ export async function runCli(argv = process.argv.slice(2), options = {}) {
217
+ const io = options.io ??
218
+ {
219
+ stdin: process.stdin,
220
+ stdout: process.stdout,
221
+ stderr: process.stderr,
222
+ };
223
+ const env = options.env ?? process.env;
224
+ const fetchImpl = options.fetchImpl ?? fetch;
225
+ const jsonMode = wantsJsonOutput(argv);
226
+ const context = {
227
+ io,
228
+ env,
229
+ fetchImpl,
230
+ configDir: options.configDir,
231
+ prompts: {
232
+ ...DEFAULT_PROMPTS,
233
+ ...options.prompts,
234
+ },
235
+ };
236
+ const program = createProgram(context);
237
+ program.configureOutput({
238
+ writeOut: (text) => {
239
+ io.stdout.write(text);
240
+ },
241
+ writeErr: (text) => {
242
+ if (!jsonMode) {
243
+ io.stderr.write(text);
244
+ }
245
+ },
246
+ });
247
+ program.exitOverride();
248
+ try {
249
+ const routedArgv = routeRootToSearch(argv);
250
+ await program.parseAsync(routedArgv, { from: "user" });
251
+ return 0;
252
+ }
253
+ catch (error) {
254
+ if (error instanceof CommanderError) {
255
+ if (error.exitCode !== 0 && jsonMode) {
256
+ writeJsonError(io.stderr, error.message);
257
+ }
258
+ return error.exitCode;
259
+ }
260
+ const message = getErrorMessage(error);
261
+ if (jsonMode) {
262
+ writeJsonError(io.stderr, message);
263
+ }
264
+ else {
265
+ io.stderr.write(`${message}\n`);
266
+ }
267
+ if (error instanceof SyntheticCliError) {
268
+ return error.exitCode;
269
+ }
270
+ return 1;
271
+ }
272
+ }
273
+ function isDirectExecution() {
274
+ const entry = process.argv[1];
275
+ if (!entry) {
276
+ return false;
277
+ }
278
+ return pathToFileURL(entry).href === import.meta.url;
279
+ }
280
+ if (isDirectExecution()) {
281
+ runCli().then((exitCode) => {
282
+ process.exitCode = exitCode;
283
+ }, (error) => {
284
+ process.stderr.write(`${getErrorMessage(error)}\n`);
285
+ process.exitCode = 1;
286
+ });
287
+ }
288
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { ResolvedCredentials } from "../types.js";
2
+ export type AuthOptions = {
3
+ env?: NodeJS.ProcessEnv;
4
+ configDir?: string;
5
+ };
6
+ export declare function resolveCredentials(options?: AuthOptions): ResolvedCredentials;
7
+ export declare function getSavedApiKey(options?: AuthOptions): string | null;
8
+ export declare function saveApiKey(apiKey: string, options?: AuthOptions): void;
9
+ export declare function deleteSavedApiKey(options?: AuthOptions): boolean;
10
+ export declare function hasSavedApiKey(options?: AuthOptions): boolean;
11
+ export declare function maskApiKey(apiKey: string): string;
@@ -0,0 +1,74 @@
1
+ import Conf from "conf";
2
+ import { SyntheticUsageError } from "./errors.js";
3
+ function createStore(configDir) {
4
+ return new Conf({
5
+ projectName: "synthetic-search",
6
+ cwd: configDir,
7
+ });
8
+ }
9
+ function normalizeKey(value) {
10
+ return value.trim();
11
+ }
12
+ export function resolveCredentials(options = {}) {
13
+ const env = options.env ?? process.env;
14
+ const envApiKey = env.SYNTHETIC_API_KEY?.trim();
15
+ if (envApiKey) {
16
+ return {
17
+ source: "env",
18
+ apiKey: envApiKey,
19
+ };
20
+ }
21
+ const savedApiKey = getSavedApiKey(options);
22
+ if (savedApiKey) {
23
+ return {
24
+ source: "config",
25
+ apiKey: savedApiKey,
26
+ };
27
+ }
28
+ return {
29
+ source: "none",
30
+ apiKey: null,
31
+ };
32
+ }
33
+ export function getSavedApiKey(options = {}) {
34
+ const store = createStore(options.configDir);
35
+ const value = store.get("apiKey");
36
+ if (typeof value !== "string") {
37
+ return null;
38
+ }
39
+ const normalized = normalizeKey(value);
40
+ return normalized || null;
41
+ }
42
+ export function saveApiKey(apiKey, options = {}) {
43
+ const normalized = normalizeKey(apiKey);
44
+ if (!normalized) {
45
+ throw new SyntheticUsageError("API key cannot be empty.");
46
+ }
47
+ const store = createStore(options.configDir);
48
+ store.set("apiKey", normalized);
49
+ }
50
+ export function deleteSavedApiKey(options = {}) {
51
+ const store = createStore(options.configDir);
52
+ if (!store.has("apiKey")) {
53
+ return false;
54
+ }
55
+ store.delete("apiKey");
56
+ return true;
57
+ }
58
+ export function hasSavedApiKey(options = {}) {
59
+ const store = createStore(options.configDir);
60
+ return store.has("apiKey");
61
+ }
62
+ export function maskApiKey(apiKey) {
63
+ const normalized = normalizeKey(apiKey);
64
+ if (!normalized) {
65
+ return "(empty)";
66
+ }
67
+ if (normalized.length <= 8) {
68
+ const first = normalized[0] ?? "";
69
+ const last = normalized.at(-1) ?? "";
70
+ return `${first}***${last}`;
71
+ }
72
+ return `${normalized.slice(0, 4)}...${normalized.slice(-4)}`;
73
+ }
74
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { SyntheticQuotas, SyntheticSearchResult } from "../types.js";
2
+ export type FetchLike = typeof fetch;
3
+ export declare function search(query: string, apiKey: string, fetchImpl?: FetchLike): Promise<SyntheticSearchResult[]>;
4
+ export declare function getQuotas(apiKey: string, fetchImpl?: FetchLike): Promise<SyntheticQuotas>;
@@ -0,0 +1,163 @@
1
+ import { SyntheticApiError, SyntheticCliError, getErrorMessage } from "./errors.js";
2
+ import { parseSyntheticJson, truncateText, tryParseSyntheticJson } from "./json.js";
3
+ const SYNTHETIC_SEARCH_URL = "https://api.synthetic.new/v2/search";
4
+ const SYNTHETIC_QUOTAS_URL = "https://api.synthetic.new/v2/quotas";
5
+ const MAX_TEXT_LENGTH = 2000;
6
+ function normalizeResult(rawResult) {
7
+ if (typeof rawResult !== "object" || rawResult === null) {
8
+ return null;
9
+ }
10
+ const result = rawResult;
11
+ const url = typeof result.url === "string" ? result.url : null;
12
+ const title = typeof result.title === "string" ? result.title : null;
13
+ const text = typeof result.text === "string" ? result.text : null;
14
+ const published = typeof result.published === "string" ? result.published : null;
15
+ if (!url || !title || !text) {
16
+ return null;
17
+ }
18
+ return {
19
+ url,
20
+ title,
21
+ text: truncateText(text, MAX_TEXT_LENGTH),
22
+ published,
23
+ };
24
+ }
25
+ function formatApiError(status, bodyText) {
26
+ const body = bodyText.trim();
27
+ if (!body) {
28
+ return `Synthetic API request failed with status ${status}.`;
29
+ }
30
+ const parsed = tryParseSyntheticJson(body);
31
+ if (parsed) {
32
+ const message = typeof parsed.error === "string"
33
+ ? parsed.error
34
+ : typeof parsed.message === "string"
35
+ ? parsed.message
36
+ : null;
37
+ if (message) {
38
+ return `Synthetic API request failed with status ${status}: ${message}`;
39
+ }
40
+ }
41
+ return `Synthetic API request failed with status ${status}: ${truncateText(body, 400)}`;
42
+ }
43
+ function coerceNumber(value) {
44
+ if (typeof value === "number" && Number.isFinite(value)) {
45
+ return value;
46
+ }
47
+ if (typeof value === "string" && value.trim()) {
48
+ const parsed = Number(value);
49
+ if (Number.isFinite(parsed)) {
50
+ return parsed;
51
+ }
52
+ }
53
+ return null;
54
+ }
55
+ function coerceString(value) {
56
+ if (typeof value === "string" && value.trim()) {
57
+ return value;
58
+ }
59
+ return null;
60
+ }
61
+ function getNestedRecords(raw) {
62
+ const records = [raw];
63
+ for (const key of ["quota", "quotas", "subscription", "plan", "data"]) {
64
+ const candidate = raw[key];
65
+ if (typeof candidate === "object" && candidate !== null && !Array.isArray(candidate)) {
66
+ records.push(candidate);
67
+ }
68
+ }
69
+ return records;
70
+ }
71
+ function pickNumber(records, keys) {
72
+ for (const record of records) {
73
+ for (const key of keys) {
74
+ const value = coerceNumber(record[key]);
75
+ if (value !== null) {
76
+ return value;
77
+ }
78
+ }
79
+ }
80
+ return null;
81
+ }
82
+ function pickString(records, keys) {
83
+ for (const record of records) {
84
+ for (const key of keys) {
85
+ const value = coerceString(record[key]);
86
+ if (value !== null) {
87
+ return value;
88
+ }
89
+ }
90
+ }
91
+ return null;
92
+ }
93
+ function normalizeQuotas(rawValue) {
94
+ if (typeof rawValue !== "object" || rawValue === null || Array.isArray(rawValue)) {
95
+ throw new SyntheticCliError("Synthetic API quotas response did not include a valid JSON object.");
96
+ }
97
+ const raw = rawValue;
98
+ const records = getNestedRecords(raw);
99
+ const limit = pickNumber(records, ["limit", "request_limit", "requests_limit", "quota_limit"]);
100
+ const requestsUsed = pickNumber(records, [
101
+ "requests_used",
102
+ "requestsUsed",
103
+ "used",
104
+ "usage",
105
+ "request_count",
106
+ ]);
107
+ const remaining = pickNumber(records, ["remaining", "requests_remaining", "requestsRemaining"]);
108
+ const renewsAt = pickString(records, ["renews_at", "renewsAt", "reset_at", "resetAt", "resets_at"]) ?? null;
109
+ if (limit === null || requestsUsed === null || remaining === null) {
110
+ throw new SyntheticCliError("Synthetic API quotas response did not include valid limit, requests used, and remaining values.");
111
+ }
112
+ return {
113
+ limit,
114
+ requestsUsed,
115
+ remaining,
116
+ renewsAt,
117
+ };
118
+ }
119
+ async function syntheticFetch(url, init, fetchImpl) {
120
+ let response;
121
+ try {
122
+ response = await fetchImpl(url, init);
123
+ }
124
+ catch (error) {
125
+ throw new SyntheticApiError(`Synthetic API request failed: ${getErrorMessage(error)}`);
126
+ }
127
+ const rawText = await response.text();
128
+ return { response, rawText };
129
+ }
130
+ export async function search(query, apiKey, fetchImpl = fetch) {
131
+ const { response, rawText } = await syntheticFetch(SYNTHETIC_SEARCH_URL, {
132
+ method: "POST",
133
+ headers: {
134
+ "Content-Type": "application/json",
135
+ Authorization: `Bearer ${apiKey}`,
136
+ },
137
+ body: JSON.stringify({ query }),
138
+ }, fetchImpl);
139
+ if (!response.ok) {
140
+ throw new SyntheticApiError(formatApiError(response.status, rawText), response.status);
141
+ }
142
+ const parsed = parseSyntheticJson(rawText);
143
+ if (!Array.isArray(parsed.results)) {
144
+ throw new SyntheticCliError("Synthetic API response did not include a valid results array.");
145
+ }
146
+ return parsed.results
147
+ .map((result) => normalizeResult(result))
148
+ .filter((result) => result !== null);
149
+ }
150
+ export async function getQuotas(apiKey, fetchImpl = fetch) {
151
+ const { response, rawText } = await syntheticFetch(SYNTHETIC_QUOTAS_URL, {
152
+ method: "GET",
153
+ headers: {
154
+ Authorization: `Bearer ${apiKey}`,
155
+ },
156
+ }, fetchImpl);
157
+ if (!response.ok) {
158
+ throw new SyntheticApiError(formatApiError(response.status, rawText), response.status);
159
+ }
160
+ const parsed = parseSyntheticJson(rawText);
161
+ return normalizeQuotas(parsed);
162
+ }
163
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/lib/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ export declare class SyntheticCliError extends Error {
2
+ readonly exitCode: number;
3
+ constructor(message: string, exitCode?: number);
4
+ }
5
+ export declare class SyntheticUsageError extends SyntheticCliError {
6
+ constructor(message: string);
7
+ }
8
+ export declare class SyntheticApiError extends SyntheticCliError {
9
+ readonly status: number | null;
10
+ constructor(message: string, status?: number | null);
11
+ }
12
+ export declare function getErrorMessage(error: unknown): string;
@@ -0,0 +1,29 @@
1
+ export class SyntheticCliError extends Error {
2
+ exitCode;
3
+ constructor(message, exitCode = 1) {
4
+ super(message);
5
+ this.name = "SyntheticCliError";
6
+ this.exitCode = exitCode;
7
+ }
8
+ }
9
+ export class SyntheticUsageError extends SyntheticCliError {
10
+ constructor(message) {
11
+ super(message, 1);
12
+ this.name = "SyntheticUsageError";
13
+ }
14
+ }
15
+ export class SyntheticApiError extends SyntheticCliError {
16
+ status;
17
+ constructor(message, status = null) {
18
+ super(message, 1);
19
+ this.name = "SyntheticApiError";
20
+ this.status = status;
21
+ }
22
+ }
23
+ export function getErrorMessage(error) {
24
+ if (error instanceof Error) {
25
+ return error.message;
26
+ }
27
+ return String(error);
28
+ }
29
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/lib/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IACjC,QAAQ,CAAS;IAE1B,YAAY,OAAe,EAAE,QAAQ,GAAG,CAAC;QACvC,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;QAChC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;CACF;AAED,MAAM,OAAO,mBAAoB,SAAQ,iBAAiB;IACxD,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAClB,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;IACpC,CAAC;CACF;AAED,MAAM,OAAO,iBAAkB,SAAQ,iBAAiB;IAC7C,MAAM,CAAgB;IAE/B,YAAY,OAAe,EAAE,SAAwB,IAAI;QACvD,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAClB,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;QAChC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAED,MAAM,UAAU,eAAe,CAAC,KAAc;IAC5C,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;QAC3B,OAAO,KAAK,CAAC,OAAO,CAAC;IACvB,CAAC;IAED,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;AACvB,CAAC"}
@@ -0,0 +1,4 @@
1
+ export declare function truncateText(value: string, maxLength: number): string;
2
+ export declare function sanitizeJsonResponse(rawText: string): string;
3
+ export declare function parseSyntheticJson<T>(rawText: string): T;
4
+ export declare function tryParseSyntheticJson<T>(rawText: string): T | null;
@@ -0,0 +1,76 @@
1
+ import { SyntheticCliError } from "./errors.js";
2
+ export function truncateText(value, maxLength) {
3
+ if (value.length <= maxLength) {
4
+ return value;
5
+ }
6
+ return `${value.slice(0, maxLength - 3)}...`;
7
+ }
8
+ function escapeJsonControlCharacter(charCode) {
9
+ switch (charCode) {
10
+ case 0x08:
11
+ return "\\b";
12
+ case 0x09:
13
+ return "\\t";
14
+ case 0x0a:
15
+ return "\\n";
16
+ case 0x0c:
17
+ return "\\f";
18
+ case 0x0d:
19
+ return "\\r";
20
+ default:
21
+ return `\\u${charCode.toString(16).padStart(4, "0")}`;
22
+ }
23
+ }
24
+ export function sanitizeJsonResponse(rawText) {
25
+ let sanitized = "";
26
+ let inString = false;
27
+ let isEscaping = false;
28
+ for (const char of rawText) {
29
+ const charCode = char.charCodeAt(0);
30
+ if (!inString) {
31
+ if (char === '"') {
32
+ inString = true;
33
+ }
34
+ sanitized += char;
35
+ continue;
36
+ }
37
+ if (isEscaping) {
38
+ sanitized += char;
39
+ isEscaping = false;
40
+ continue;
41
+ }
42
+ if (char === "\\") {
43
+ sanitized += char;
44
+ isEscaping = true;
45
+ continue;
46
+ }
47
+ if (char === '"') {
48
+ sanitized += char;
49
+ inString = false;
50
+ continue;
51
+ }
52
+ if (charCode <= 0x1f) {
53
+ sanitized += escapeJsonControlCharacter(charCode);
54
+ continue;
55
+ }
56
+ sanitized += char;
57
+ }
58
+ return sanitized;
59
+ }
60
+ export function parseSyntheticJson(rawText) {
61
+ try {
62
+ return JSON.parse(sanitizeJsonResponse(rawText));
63
+ }
64
+ catch (error) {
65
+ throw new SyntheticCliError(`Synthetic API returned malformed JSON that could not be parsed: ${error instanceof Error ? error.message : String(error)}`);
66
+ }
67
+ }
68
+ export function tryParseSyntheticJson(rawText) {
69
+ try {
70
+ return JSON.parse(sanitizeJsonResponse(rawText));
71
+ }
72
+ catch {
73
+ return null;
74
+ }
75
+ }
76
+ //# sourceMappingURL=json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/lib/json.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,UAAU,YAAY,CAAC,KAAa,EAAE,SAAiB;IAC3D,IAAI,KAAK,CAAC,MAAM,IAAI,SAAS,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC;AAC/C,CAAC;AAED,SAAS,0BAA0B,CAAC,QAAgB;IAClD,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,IAAI;YACP,OAAO,KAAK,CAAC;QACf,KAAK,IAAI;YACP,OAAO,KAAK,CAAC;QACf,KAAK,IAAI;YACP,OAAO,KAAK,CAAC;QACf,KAAK,IAAI;YACP,OAAO,KAAK,CAAC;QACf,KAAK,IAAI;YACP,OAAO,KAAK,CAAC;QACf;YACE,OAAO,MAAM,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;IAC1D,CAAC;AACH,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,OAAe;IAClD,IAAI,SAAS,GAAG,EAAE,CAAC;IACnB,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,IAAI,UAAU,GAAG,KAAK,CAAC;IAEvB,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;QAC3B,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QAEpC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;gBACjB,QAAQ,GAAG,IAAI,CAAC;YAClB,CAAC;YAED,SAAS,IAAI,IAAI,CAAC;YAClB,SAAS;QACX,CAAC;QAED,IAAI,UAAU,EAAE,CAAC;YACf,SAAS,IAAI,IAAI,CAAC;YAClB,UAAU,GAAG,KAAK,CAAC;YACnB,SAAS;QACX,CAAC;QAED,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAClB,SAAS,IAAI,IAAI,CAAC;YAClB,UAAU,GAAG,IAAI,CAAC;YAClB,SAAS;QACX,CAAC;QAED,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACjB,SAAS,IAAI,IAAI,CAAC;YAClB,QAAQ,GAAG,KAAK,CAAC;YACjB,SAAS;QACX,CAAC;QAED,IAAI,QAAQ,IAAI,IAAI,EAAE,CAAC;YACrB,SAAS,IAAI,0BAA0B,CAAC,QAAQ,CAAC,CAAC;YAClD,SAAS;QACX,CAAC;QAED,SAAS,IAAI,IAAI,CAAC;IACpB,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAI,OAAe;IACnD,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAM,CAAC;IACxD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,iBAAiB,CACzB,mEACE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CACvD,EAAE,CACH,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAI,OAAe;IACtD,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAM,CAAC;IACxD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { SyntheticQuotas, SyntheticSearchResult } from "../types.js";
2
+ export type WritableLike = {
3
+ write: (chunk: string) => unknown;
4
+ };
5
+ export declare function renderSearchResultsText(results: SyntheticSearchResult[], width?: number): string;
6
+ export declare function renderQuotasText(quotas: SyntheticQuotas): string;
7
+ export declare function writeJson(stdout: WritableLike, value: unknown): void;
8
+ export declare function writeJsonError(stderr: WritableLike, message: string): void;
@@ -0,0 +1,53 @@
1
+ import { truncateText } from "./json.js";
2
+ function wrapText(text, width) {
3
+ const safeWidth = Math.max(width, 20);
4
+ const words = text.split(/\s+/).filter(Boolean);
5
+ if (words.length === 0) {
6
+ return "";
7
+ }
8
+ const lines = [];
9
+ let line = words[0] ?? "";
10
+ for (let i = 1; i < words.length; i += 1) {
11
+ const word = words[i];
12
+ if (`${line} ${word}`.length > safeWidth) {
13
+ lines.push(line);
14
+ line = word;
15
+ continue;
16
+ }
17
+ line = `${line} ${word}`;
18
+ }
19
+ lines.push(line);
20
+ return lines.join("\n");
21
+ }
22
+ export function renderSearchResultsText(results, width = 80) {
23
+ if (results.length === 0) {
24
+ return "No results found.";
25
+ }
26
+ const blocks = results.map((result, index) => {
27
+ const lines = [`${index + 1}. ${result.title}`, result.url];
28
+ if (result.published) {
29
+ lines.push(`Published: ${result.published}`);
30
+ }
31
+ const snippet = truncateText(result.text.replace(/\s+/g, " ").trim(), 320);
32
+ if (snippet) {
33
+ lines.push(wrapText(snippet, width));
34
+ }
35
+ return lines.join("\n");
36
+ });
37
+ return blocks.join("\n\n");
38
+ }
39
+ export function renderQuotasText(quotas) {
40
+ return [
41
+ `Limit: ${quotas.limit}`,
42
+ `Requests used: ${quotas.requestsUsed}`,
43
+ `Remaining: ${quotas.remaining}`,
44
+ `Renews at: ${quotas.renewsAt ?? "unknown"}`,
45
+ ].join("\n");
46
+ }
47
+ export function writeJson(stdout, value) {
48
+ stdout.write(`${JSON.stringify(value, null, 2)}\n`);
49
+ }
50
+ export function writeJsonError(stderr, message) {
51
+ stderr.write(`${JSON.stringify({ error: message })}\n`);
52
+ }
53
+ //# sourceMappingURL=output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.js","sourceRoot":"","sources":["../../src/lib/output.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ export type SyntheticSearchResult = {
2
+ url: string;
3
+ title: string;
4
+ text: string;
5
+ published: string | null;
6
+ };
7
+ export type SyntheticSearchResponse = {
8
+ results?: unknown;
9
+ };
10
+ export type SyntheticQuotas = {
11
+ limit: number;
12
+ requestsUsed: number;
13
+ remaining: number;
14
+ renewsAt: string | null;
15
+ };
16
+ export type CredentialSource = "env" | "config" | "none";
17
+ export type ResolvedCredentials = {
18
+ source: CredentialSource;
19
+ apiKey: string | null;
20
+ };
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "synthetic-search",
3
+ "version": "1.0.0",
4
+ "description": "Command-line client for Synthetic web search and quotas.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "bin": {
9
+ "synthetic-search": "./dist/index.js"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "engines": {
17
+ "node": ">=18"
18
+ },
19
+ "scripts": {
20
+ "build": "tsc -p tsconfig.json",
21
+ "dev": "tsx src/index.ts",
22
+ "start": "node dist/index.js",
23
+ "test": "tsx --test test/*.test.ts",
24
+ "prepare": "npm run build"
25
+ },
26
+ "keywords": [
27
+ "synthetic",
28
+ "search",
29
+ "cli",
30
+ "web-search",
31
+ "quotas"
32
+ ],
33
+ "dependencies": {
34
+ "@inquirer/prompts": "^7.8.0",
35
+ "commander": "^12.1.0",
36
+ "conf": "^13.0.0"
37
+ },
38
+ "devDependencies": {
39
+ "@types/node": "^24.3.0",
40
+ "tsx": "^4.20.5",
41
+ "typescript": "^5.9.2"
42
+ }
43
+ }