trendsearch 0.0.1

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/dist/cli.mjs ADDED
@@ -0,0 +1,1444 @@
1
+ import { a as UnexpectedResponseError, c as RateLimitError, l as EndpointUnavailableError, n as schemas, o as TransportError, r as pickerRequestSchema, s as SchemaValidationError, t as createClient } from "./create-client-B1euQ5Of.mjs";
2
+ import { Command, CommanderError, Option } from "commander";
3
+ import Conf from "conf";
4
+ import { existsSync } from "node:fs";
5
+ import { readFile } from "node:fs/promises";
6
+ import { cancel, confirm, intro, isCancel, outro, select, text } from "@clack/prompts";
7
+
8
+ //#region src/cli/config.ts
9
+ const createCliConfigStore = (options = {}) => {
10
+ const store = new Conf({
11
+ projectName: "trendsearch",
12
+ configName: "cli",
13
+ cwd: options.cwd
14
+ });
15
+ return {
16
+ all: () => ({ ...store.store }),
17
+ get: (key) => store.get(key),
18
+ set: (key, value) => {
19
+ store.set(key, value);
20
+ },
21
+ delete: (key) => {
22
+ store.delete(key);
23
+ },
24
+ clear: () => {
25
+ store.clear();
26
+ }
27
+ };
28
+ };
29
+ const outputModes = new Set([
30
+ "pretty",
31
+ "json",
32
+ "jsonl"
33
+ ]);
34
+ const pickFirst = (...values) => {
35
+ for (const value of values) if (value !== void 0) return value;
36
+ };
37
+ const toNumberOrUndefined = (value) => {
38
+ if (typeof value === "number" && Number.isFinite(value)) return value;
39
+ if (typeof value !== "string" || value.length === 0) return;
40
+ const parsed = Number(value);
41
+ return Number.isFinite(parsed) ? parsed : void 0;
42
+ };
43
+ const toBooleanOrUndefined = (value) => {
44
+ if (typeof value === "boolean") return value;
45
+ if (typeof value !== "string") return;
46
+ const normalized = value.trim().toLowerCase();
47
+ if ([
48
+ "1",
49
+ "true",
50
+ "yes",
51
+ "on"
52
+ ].includes(normalized)) return true;
53
+ if ([
54
+ "0",
55
+ "false",
56
+ "no",
57
+ "off"
58
+ ].includes(normalized)) return false;
59
+ };
60
+ const toOutputModeOrUndefined = (value) => {
61
+ if (typeof value !== "string") return;
62
+ if (!outputModes.has(value)) return;
63
+ return value;
64
+ };
65
+ const readEnv = (env) => ({
66
+ output: toOutputModeOrUndefined(env.TRENDSEARCH_OUTPUT),
67
+ spinner: toBooleanOrUndefined(env.TRENDSEARCH_SPINNER),
68
+ hl: env.TRENDSEARCH_HL,
69
+ tz: toNumberOrUndefined(env.TRENDSEARCH_TZ),
70
+ baseUrl: env.TRENDSEARCH_BASE_URL,
71
+ timeoutMs: toNumberOrUndefined(env.TRENDSEARCH_TIMEOUT_MS),
72
+ maxRetries: toNumberOrUndefined(env.TRENDSEARCH_MAX_RETRIES),
73
+ retryBaseDelayMs: toNumberOrUndefined(env.TRENDSEARCH_RETRY_BASE_DELAY_MS),
74
+ retryMaxDelayMs: toNumberOrUndefined(env.TRENDSEARCH_RETRY_MAX_DELAY_MS),
75
+ maxConcurrent: toNumberOrUndefined(env.TRENDSEARCH_MAX_CONCURRENT),
76
+ minDelayMs: toNumberOrUndefined(env.TRENDSEARCH_MIN_DELAY_MS),
77
+ userAgent: env.TRENDSEARCH_USER_AGENT
78
+ });
79
+ const readStored = (store) => ({
80
+ output: toOutputModeOrUndefined(store.get("output")),
81
+ spinner: toBooleanOrUndefined(store.get("spinner")),
82
+ hl: typeof store.get("hl") === "string" ? store.get("hl") : void 0,
83
+ tz: toNumberOrUndefined(store.get("tz")),
84
+ baseUrl: typeof store.get("baseUrl") === "string" ? store.get("baseUrl") : void 0,
85
+ timeoutMs: toNumberOrUndefined(store.get("timeoutMs")),
86
+ maxRetries: toNumberOrUndefined(store.get("maxRetries")),
87
+ retryBaseDelayMs: toNumberOrUndefined(store.get("retryBaseDelayMs")),
88
+ retryMaxDelayMs: toNumberOrUndefined(store.get("retryMaxDelayMs")),
89
+ maxConcurrent: toNumberOrUndefined(store.get("maxConcurrent")),
90
+ minDelayMs: toNumberOrUndefined(store.get("minDelayMs")),
91
+ userAgent: typeof store.get("userAgent") === "string" ? store.get("userAgent") : void 0
92
+ });
93
+ const resolveGlobalOptions = (args) => {
94
+ const env = readEnv(args.env);
95
+ const stored = readStored(args.store);
96
+ const outputDefault = args.stdoutIsTty ? "pretty" : "json";
97
+ const output = pickFirst(args.flags.output, env.output, stored.output, outputDefault) ?? outputDefault;
98
+ const spinner = pickFirst(args.flags.spinner, env.spinner, stored.spinner, true) ?? true;
99
+ return {
100
+ output,
101
+ raw: args.flags.raw ?? false,
102
+ spinner,
103
+ hl: pickFirst(args.flags.hl, env.hl, stored.hl),
104
+ tz: pickFirst(args.flags.tz, env.tz, stored.tz),
105
+ baseUrl: pickFirst(args.flags.baseUrl, env.baseUrl, stored.baseUrl),
106
+ timeoutMs: pickFirst(args.flags.timeoutMs, env.timeoutMs, stored.timeoutMs),
107
+ maxRetries: pickFirst(args.flags.maxRetries, env.maxRetries, stored.maxRetries),
108
+ retryBaseDelayMs: pickFirst(args.flags.retryBaseDelayMs, env.retryBaseDelayMs, stored.retryBaseDelayMs),
109
+ retryMaxDelayMs: pickFirst(args.flags.retryMaxDelayMs, env.retryMaxDelayMs, stored.retryMaxDelayMs),
110
+ maxConcurrent: pickFirst(args.flags.maxConcurrent, env.maxConcurrent, stored.maxConcurrent),
111
+ minDelayMs: pickFirst(args.flags.minDelayMs, env.minDelayMs, stored.minDelayMs),
112
+ userAgent: pickFirst(args.flags.userAgent, env.userAgent, stored.userAgent),
113
+ input: args.flags.input
114
+ };
115
+ };
116
+ const toCreateClientConfig = (options) => ({
117
+ baseUrl: options.baseUrl,
118
+ timeoutMs: options.timeoutMs,
119
+ hl: options.hl,
120
+ tz: options.tz,
121
+ userAgent: options.userAgent,
122
+ retries: options.maxRetries !== void 0 || options.retryBaseDelayMs !== void 0 || options.retryMaxDelayMs !== void 0 ? {
123
+ maxRetries: options.maxRetries,
124
+ baseDelayMs: options.retryBaseDelayMs,
125
+ maxDelayMs: options.retryMaxDelayMs
126
+ } : void 0,
127
+ rateLimit: options.maxConcurrent !== void 0 || options.minDelayMs !== void 0 ? {
128
+ maxConcurrent: options.maxConcurrent,
129
+ minDelayMs: options.minDelayMs
130
+ } : void 0
131
+ });
132
+
133
+ //#endregion
134
+ //#region src/cli/errors.ts
135
+ const EXIT_CODES = {
136
+ ok: 0,
137
+ unknown: 1,
138
+ usage: 2,
139
+ endpointUnavailable: 3,
140
+ rateLimited: 4,
141
+ transport: 5,
142
+ schemaDrift: 6
143
+ };
144
+ var CliUsageError = class extends Error {
145
+ details;
146
+ constructor(message, details) {
147
+ super(message);
148
+ this.name = "CliUsageError";
149
+ this.details = details;
150
+ }
151
+ };
152
+ const exitSignalSymbol = Symbol("CliExitSignal");
153
+ const createCliExitSignal = (exitCode) => ({
154
+ [exitSignalSymbol]: true,
155
+ exitCode
156
+ });
157
+ const isCliExitSignal = (value) => typeof value === "object" && value !== null && exitSignalSymbol in value && value[exitSignalSymbol] === true;
158
+ const fromUnknownError = (error) => {
159
+ if (error instanceof CliUsageError) return {
160
+ code: "USAGE_ERROR",
161
+ message: error.message,
162
+ details: error.details,
163
+ exitCode: EXIT_CODES.usage
164
+ };
165
+ if (error instanceof EndpointUnavailableError) return {
166
+ code: error.code,
167
+ message: error.message,
168
+ details: {
169
+ endpoint: error.endpoint,
170
+ status: error.status,
171
+ replacements: error.replacements
172
+ },
173
+ exitCode: EXIT_CODES.endpointUnavailable
174
+ };
175
+ if (error instanceof RateLimitError) return {
176
+ code: error.code,
177
+ message: error.message,
178
+ details: {
179
+ status: error.status,
180
+ url: error.url
181
+ },
182
+ exitCode: EXIT_CODES.rateLimited
183
+ };
184
+ if (error instanceof TransportError) return {
185
+ code: error.code,
186
+ message: error.message,
187
+ details: {
188
+ status: error.status,
189
+ url: error.url,
190
+ responseBody: error.responseBody
191
+ },
192
+ exitCode: EXIT_CODES.transport
193
+ };
194
+ if (error instanceof SchemaValidationError || error instanceof UnexpectedResponseError) return {
195
+ code: error.code,
196
+ message: error.message,
197
+ details: error instanceof SchemaValidationError ? {
198
+ endpoint: error.endpoint,
199
+ issues: error.issues
200
+ } : { endpoint: error.endpoint },
201
+ exitCode: EXIT_CODES.schemaDrift
202
+ };
203
+ if (error instanceof CommanderError) return {
204
+ code: "USAGE_ERROR",
205
+ message: error.message,
206
+ details: { commanderCode: error.code },
207
+ exitCode: EXIT_CODES.usage
208
+ };
209
+ if (error instanceof Error) return {
210
+ code: "UNKNOWN_ERROR",
211
+ message: error.message,
212
+ exitCode: EXIT_CODES.unknown
213
+ };
214
+ return {
215
+ code: "UNKNOWN_ERROR",
216
+ message: String(error),
217
+ exitCode: EXIT_CODES.unknown
218
+ };
219
+ };
220
+ const normalizeCliError = (error) => fromUnknownError(error);
221
+ const isCommanderGracefulExit = (error) => error instanceof CommanderError && (error.code === "commander.helpDisplayed" || error.exitCode === 0);
222
+
223
+ //#endregion
224
+ //#region src/cli/output.ts
225
+ const serializeJson = (value) => `${JSON.stringify(value)}\n`;
226
+ const serializePretty = (value) => `${JSON.stringify(value, null, 2)}\n`;
227
+ const writeSuccessEnvelope = (args) => {
228
+ if (args.mode === "pretty") {
229
+ args.io.stdout.write(serializePretty(args.envelope.data));
230
+ return;
231
+ }
232
+ args.io.stdout.write(serializeJson(args.envelope));
233
+ };
234
+ const writeErrorEnvelope = (args) => {
235
+ if (args.mode === "pretty") {
236
+ const body = [`[${args.error.code}] ${args.error.message}`, args.error.details ? `details: ${JSON.stringify(args.error.details, null, 2)}` : ""].filter((line) => line.length > 0).join("\n");
237
+ args.io.stderr.write(`${body}\n`);
238
+ return;
239
+ }
240
+ const payload = {
241
+ ok: false,
242
+ error: {
243
+ code: args.error.code,
244
+ message: args.error.message,
245
+ details: args.error.details,
246
+ exitCode: args.error.exitCode
247
+ }
248
+ };
249
+ args.io.stdout.write(serializeJson(payload));
250
+ };
251
+ const writeArbitraryOutput = (args) => {
252
+ if (args.mode === "pretty") {
253
+ args.io.stdout.write(serializePretty(args.payload));
254
+ return;
255
+ }
256
+ if (args.mode === "json") {
257
+ args.io.stdout.write(serializeJson(args.payload));
258
+ return;
259
+ }
260
+ if (Array.isArray(args.payload)) {
261
+ for (const item of args.payload) args.io.stdout.write(serializeJson(item));
262
+ return;
263
+ }
264
+ args.io.stdout.write(serializeJson(args.payload));
265
+ };
266
+ const spinnerFrames = [
267
+ "-",
268
+ "\\",
269
+ "|",
270
+ "/"
271
+ ];
272
+ const createStderrSpinner = (args) => {
273
+ let frame = 0;
274
+ let message = "";
275
+ let timer;
276
+ const interactive = args.enabled && args.io.stderr.isTTY === true;
277
+ const render = () => {
278
+ if (!interactive) return;
279
+ const symbol = spinnerFrames[frame % spinnerFrames.length] ?? "-";
280
+ frame += 1;
281
+ args.io.stderr.write(`\r${symbol} ${message}`);
282
+ };
283
+ const clear = () => {
284
+ if (timer) {
285
+ clearInterval(timer);
286
+ timer = void 0;
287
+ }
288
+ };
289
+ return {
290
+ start(text) {
291
+ message = text;
292
+ if (!interactive) return;
293
+ render();
294
+ timer = setInterval(render, 80);
295
+ },
296
+ stop(text) {
297
+ clear();
298
+ if (!interactive) return;
299
+ args.io.stderr.write(`\rOK ${text}\n`);
300
+ },
301
+ fail(text) {
302
+ clear();
303
+ if (!interactive) return;
304
+ args.io.stderr.write(`\rERR ${text}\n`);
305
+ }
306
+ };
307
+ };
308
+
309
+ //#endregion
310
+ //#region src/cli/manifest.ts
311
+ const globalOptions = [
312
+ {
313
+ key: "output",
314
+ flags: "--output <mode>",
315
+ description: "Output mode: pretty, json, or jsonl.",
316
+ type: "string",
317
+ choices: [
318
+ "pretty",
319
+ "json",
320
+ "jsonl"
321
+ ]
322
+ },
323
+ {
324
+ key: "raw",
325
+ flags: "--raw",
326
+ description: "Include raw upstream payload.",
327
+ type: "boolean"
328
+ },
329
+ {
330
+ key: "spinner",
331
+ flags: "--no-spinner",
332
+ description: "Disable the loading spinner.",
333
+ type: "boolean"
334
+ },
335
+ {
336
+ key: "hl",
337
+ flags: "--hl <locale>",
338
+ description: "Override language locale.",
339
+ type: "string"
340
+ },
341
+ {
342
+ key: "tz",
343
+ flags: "--tz <minutes>",
344
+ description: "Override timezone offset in minutes.",
345
+ type: "number"
346
+ },
347
+ {
348
+ key: "baseUrl",
349
+ flags: "--base-url <url>",
350
+ description: "Override Google Trends base URL.",
351
+ type: "string"
352
+ },
353
+ {
354
+ key: "timeoutMs",
355
+ flags: "--timeout-ms <ms>",
356
+ description: "HTTP timeout in milliseconds.",
357
+ type: "number"
358
+ },
359
+ {
360
+ key: "maxRetries",
361
+ flags: "--max-retries <n>",
362
+ description: "Maximum transport retries.",
363
+ type: "number"
364
+ },
365
+ {
366
+ key: "retryBaseDelayMs",
367
+ flags: "--retry-base-delay-ms <ms>",
368
+ description: "Retry base delay in milliseconds.",
369
+ type: "number"
370
+ },
371
+ {
372
+ key: "retryMaxDelayMs",
373
+ flags: "--retry-max-delay-ms <ms>",
374
+ description: "Retry max delay in milliseconds.",
375
+ type: "number"
376
+ },
377
+ {
378
+ key: "maxConcurrent",
379
+ flags: "--max-concurrent <n>",
380
+ description: "Maximum concurrent HTTP requests.",
381
+ type: "number"
382
+ },
383
+ {
384
+ key: "minDelayMs",
385
+ flags: "--min-delay-ms <ms>",
386
+ description: "Minimum delay between requests in milliseconds.",
387
+ type: "number"
388
+ },
389
+ {
390
+ key: "userAgent",
391
+ flags: "--user-agent <ua>",
392
+ description: "Custom user-agent header value.",
393
+ type: "string"
394
+ },
395
+ {
396
+ key: "input",
397
+ flags: "--input <json-or-file-or->",
398
+ description: "Use a JSON request payload string/file/stdin.",
399
+ type: "string"
400
+ }
401
+ ];
402
+ const exploreLikeOptions = [
403
+ {
404
+ key: "geo",
405
+ flags: "--geo <geo>",
406
+ description: "Geo code (repeatable or comma-separated).",
407
+ type: "string-array"
408
+ },
409
+ {
410
+ key: "time",
411
+ flags: "--time <range>",
412
+ description: "Time range (for example: today 3-m).",
413
+ type: "string"
414
+ },
415
+ {
416
+ key: "category",
417
+ flags: "--category <id>",
418
+ description: "Category id.",
419
+ type: "number"
420
+ },
421
+ {
422
+ key: "property",
423
+ flags: "--property <property>",
424
+ description: "Google property filter.",
425
+ type: "string",
426
+ choices: [
427
+ "",
428
+ "images",
429
+ "news",
430
+ "youtube",
431
+ "froogle"
432
+ ]
433
+ }
434
+ ];
435
+ const toStringArray$1 = (value) => {
436
+ if (Array.isArray(value)) return value.filter((item) => typeof item === "string").flatMap((item) => item.split(",").map((part) => part.trim()).filter((part) => part.length > 0));
437
+ if (typeof value === "string") return value.split(",").map((part) => part.trim()).filter((part) => part.length > 0);
438
+ return [];
439
+ };
440
+ const toRawStringArray = (value) => {
441
+ if (Array.isArray(value)) return value.filter((item) => typeof item === "string");
442
+ if (typeof value === "string") return [value];
443
+ return [];
444
+ };
445
+ const readNumber = (value) => {
446
+ if (typeof value === "number" && Number.isFinite(value)) return value;
447
+ if (typeof value !== "string") return;
448
+ const parsed = Number(value);
449
+ return Number.isFinite(parsed) ? parsed : void 0;
450
+ };
451
+ const asRequest = (value) => value;
452
+ const readString = (value) => typeof value === "string" && value.length > 0 ? value : void 0;
453
+ const withGeo = (values) => {
454
+ if (values.length === 0) return;
455
+ if (values.length === 1) return values[0];
456
+ return values;
457
+ };
458
+ const parseArticleKey = (value) => {
459
+ const trimmed = value.trim();
460
+ if (trimmed.startsWith("[")) {
461
+ const parsed = JSON.parse(trimmed);
462
+ if (Array.isArray(parsed) && parsed.length === 3 && typeof parsed[0] === "number" && typeof parsed[1] === "string" && typeof parsed[2] === "string") return [
463
+ parsed[0],
464
+ parsed[1],
465
+ parsed[2]
466
+ ];
467
+ throw new Error(`Invalid article key JSON: ${value}`);
468
+ }
469
+ const parts = trimmed.split(",").map((part) => part.trim());
470
+ if (parts.length !== 3) throw new Error(`Invalid article key: ${value}`);
471
+ const index = Number(parts[0]);
472
+ if (!Number.isFinite(index)) throw new TypeError(`Invalid article key index: ${parts[0]}`);
473
+ return [
474
+ index,
475
+ parts[1] ?? "",
476
+ parts[2] ?? ""
477
+ ];
478
+ };
479
+ const buildExploreLikeRequest = (ctx) => {
480
+ return {
481
+ keywords: toStringArray$1(ctx.positionals[0]),
482
+ geo: withGeo(toStringArray$1(ctx.options.geo)),
483
+ time: readString(ctx.options.time),
484
+ category: readNumber(ctx.options.category),
485
+ property: readString(ctx.options.property)
486
+ };
487
+ };
488
+ const endpointManifest = [
489
+ {
490
+ id: "autocomplete",
491
+ path: ["autocomplete"],
492
+ summary: "Autocomplete topics by keyword.",
493
+ requestSchema: schemas.autocompleteRequestSchema,
494
+ options: [],
495
+ args: [{
496
+ label: "[keyword]",
497
+ description: "Keyword to autocomplete."
498
+ }],
499
+ buildRequest: (ctx) => ({ keyword: readString(ctx.positionals[0]) }),
500
+ invoke: (client, request, debug) => client.autocomplete(asRequest(request), debug)
501
+ },
502
+ {
503
+ id: "explore",
504
+ path: ["explore"],
505
+ summary: "Fetch explore widgets.",
506
+ requestSchema: schemas.exploreRequestSchema,
507
+ options: exploreLikeOptions,
508
+ args: [{
509
+ label: "[keywords...]",
510
+ description: "Keyword list."
511
+ }],
512
+ buildRequest: buildExploreLikeRequest,
513
+ invoke: (client, request, debug) => client.explore(asRequest(request), debug)
514
+ },
515
+ {
516
+ id: "interestOverTime",
517
+ path: ["interest-over-time"],
518
+ summary: "Fetch interest over time.",
519
+ requestSchema: schemas.interestOverTimeRequestSchema,
520
+ options: exploreLikeOptions,
521
+ args: [{
522
+ label: "[keywords...]",
523
+ description: "Keyword list."
524
+ }],
525
+ buildRequest: buildExploreLikeRequest,
526
+ invoke: (client, request, debug) => client.interestOverTime(asRequest(request), debug)
527
+ },
528
+ {
529
+ id: "interestByRegion",
530
+ path: ["interest-by-region"],
531
+ summary: "Fetch interest by region.",
532
+ requestSchema: schemas.interestByRegionRequestSchema,
533
+ options: [...exploreLikeOptions, {
534
+ key: "resolution",
535
+ flags: "--resolution <resolution>",
536
+ description: "Geo resolution (COUNTRY, REGION, CITY, DMA).",
537
+ type: "string",
538
+ choices: [
539
+ "COUNTRY",
540
+ "REGION",
541
+ "CITY",
542
+ "DMA"
543
+ ]
544
+ }],
545
+ args: [{
546
+ label: "[keywords...]",
547
+ description: "Keyword list."
548
+ }],
549
+ buildRequest: (ctx) => ({
550
+ ...buildExploreLikeRequest(ctx),
551
+ resolution: readString(ctx.options.resolution)
552
+ }),
553
+ invoke: (client, request, debug) => client.interestByRegion(asRequest(request), debug)
554
+ },
555
+ {
556
+ id: "relatedQueries",
557
+ path: ["related-queries"],
558
+ summary: "Fetch related queries.",
559
+ requestSchema: schemas.relatedQueriesRequestSchema,
560
+ options: exploreLikeOptions,
561
+ args: [{
562
+ label: "[keywords...]",
563
+ description: "Keyword list."
564
+ }],
565
+ buildRequest: buildExploreLikeRequest,
566
+ invoke: (client, request, debug) => client.relatedQueries(asRequest(request), debug)
567
+ },
568
+ {
569
+ id: "relatedTopics",
570
+ path: ["related-topics"],
571
+ summary: "Fetch related topics.",
572
+ requestSchema: schemas.relatedTopicsRequestSchema,
573
+ options: exploreLikeOptions,
574
+ args: [{
575
+ label: "[keywords...]",
576
+ description: "Keyword list."
577
+ }],
578
+ buildRequest: buildExploreLikeRequest,
579
+ invoke: (client, request, debug) => client.relatedTopics(asRequest(request), debug)
580
+ },
581
+ {
582
+ id: "dailyTrends",
583
+ path: ["daily-trends"],
584
+ summary: "Fetch daily trends (legacy-compatible endpoint).",
585
+ requestSchema: schemas.dailyTrendsRequestSchema,
586
+ options: [
587
+ {
588
+ key: "geo",
589
+ flags: "--geo <geo>",
590
+ description: "Geo code.",
591
+ type: "string"
592
+ },
593
+ {
594
+ key: "category",
595
+ flags: "--category <category>",
596
+ description: "Category id or name.",
597
+ type: "string"
598
+ },
599
+ {
600
+ key: "date",
601
+ flags: "--date <iso-date>",
602
+ description: "ISO date.",
603
+ type: "string"
604
+ },
605
+ {
606
+ key: "ns",
607
+ flags: "--ns <ns>",
608
+ description: "Namespace id.",
609
+ type: "number"
610
+ }
611
+ ],
612
+ args: [],
613
+ buildRequest: (ctx) => ({
614
+ geo: readString(ctx.options.geo),
615
+ category: readString(ctx.options.category),
616
+ date: readString(ctx.options.date),
617
+ ns: readNumber(ctx.options.ns)
618
+ }),
619
+ invoke: (client, request, debug) => client.dailyTrends(asRequest(request), debug)
620
+ },
621
+ {
622
+ id: "realTimeTrends",
623
+ path: ["real-time-trends"],
624
+ summary: "Fetch realtime trends (legacy-compatible endpoint).",
625
+ requestSchema: schemas.realTimeTrendsRequestSchema,
626
+ options: [
627
+ {
628
+ key: "geo",
629
+ flags: "--geo <geo>",
630
+ description: "Geo code.",
631
+ type: "string"
632
+ },
633
+ {
634
+ key: "category",
635
+ flags: "--category <category>",
636
+ description: "Category id or name.",
637
+ type: "string"
638
+ },
639
+ {
640
+ key: "fi",
641
+ flags: "--fi <number>",
642
+ description: "FI parameter.",
643
+ type: "number"
644
+ },
645
+ {
646
+ key: "fs",
647
+ flags: "--fs <number>",
648
+ description: "FS parameter.",
649
+ type: "number"
650
+ },
651
+ {
652
+ key: "ri",
653
+ flags: "--ri <number>",
654
+ description: "RI parameter.",
655
+ type: "number"
656
+ },
657
+ {
658
+ key: "rs",
659
+ flags: "--rs <number>",
660
+ description: "RS parameter.",
661
+ type: "number"
662
+ },
663
+ {
664
+ key: "sort",
665
+ flags: "--sort <number>",
666
+ description: "Sort mode.",
667
+ type: "number"
668
+ }
669
+ ],
670
+ args: [],
671
+ buildRequest: (ctx) => ({
672
+ geo: readString(ctx.options.geo),
673
+ category: readString(ctx.options.category),
674
+ fi: readNumber(ctx.options.fi),
675
+ fs: readNumber(ctx.options.fs),
676
+ ri: readNumber(ctx.options.ri),
677
+ rs: readNumber(ctx.options.rs),
678
+ sort: readNumber(ctx.options.sort)
679
+ }),
680
+ invoke: (client, request, debug) => client.realTimeTrends(asRequest(request), debug)
681
+ },
682
+ {
683
+ id: "trendingNow",
684
+ path: ["trending-now"],
685
+ summary: "Fetch trending now topics.",
686
+ requestSchema: schemas.trendingNowRequestSchema,
687
+ options: [
688
+ {
689
+ key: "geo",
690
+ flags: "--geo <geo>",
691
+ description: "Geo code.",
692
+ type: "string"
693
+ },
694
+ {
695
+ key: "language",
696
+ flags: "--language <language>",
697
+ description: "Language code.",
698
+ type: "string"
699
+ },
700
+ {
701
+ key: "hours",
702
+ flags: "--hours <hours>",
703
+ description: "Window in hours.",
704
+ type: "number"
705
+ }
706
+ ],
707
+ args: [],
708
+ buildRequest: (ctx) => ({
709
+ geo: readString(ctx.options.geo),
710
+ language: readString(ctx.options.language),
711
+ hours: readNumber(ctx.options.hours)
712
+ }),
713
+ invoke: (client, request, debug) => client.trendingNow(asRequest(request), debug)
714
+ },
715
+ {
716
+ id: "trendingArticles",
717
+ path: ["trending-articles"],
718
+ summary: "Fetch trending articles from article keys.",
719
+ requestSchema: schemas.trendingArticlesRequestSchema,
720
+ options: [{
721
+ key: "articleKey",
722
+ flags: "--article-key <key>",
723
+ description: "Article key tuple (repeatable).",
724
+ type: "string-array-raw"
725
+ }, {
726
+ key: "articleCount",
727
+ flags: "--article-count <count>",
728
+ description: "Maximum article count.",
729
+ type: "number"
730
+ }],
731
+ args: [],
732
+ buildRequest: (ctx) => ({
733
+ articleKeys: toRawStringArray(ctx.options.articleKey).map(parseArticleKey),
734
+ articleCount: readNumber(ctx.options.articleCount)
735
+ }),
736
+ invoke: (client, request, debug) => client.trendingArticles(asRequest(request), debug)
737
+ },
738
+ {
739
+ id: "experimental.trendingNow",
740
+ path: ["experimental", "trending-now"],
741
+ summary: "Fetch experimental trending now topics.",
742
+ requestSchema: schemas.trendingNowRequestSchema,
743
+ options: [
744
+ {
745
+ key: "geo",
746
+ flags: "--geo <geo>",
747
+ description: "Geo code.",
748
+ type: "string"
749
+ },
750
+ {
751
+ key: "language",
752
+ flags: "--language <language>",
753
+ description: "Language code.",
754
+ type: "string"
755
+ },
756
+ {
757
+ key: "hours",
758
+ flags: "--hours <hours>",
759
+ description: "Window in hours.",
760
+ type: "number"
761
+ }
762
+ ],
763
+ args: [],
764
+ buildRequest: (ctx) => ({
765
+ geo: readString(ctx.options.geo),
766
+ language: readString(ctx.options.language),
767
+ hours: readNumber(ctx.options.hours)
768
+ }),
769
+ invoke: (client, request, debug) => client.experimental.trendingNow(asRequest(request), debug)
770
+ },
771
+ {
772
+ id: "experimental.trendingArticles",
773
+ path: ["experimental", "trending-articles"],
774
+ summary: "Fetch experimental trending articles.",
775
+ requestSchema: schemas.trendingArticlesRequestSchema,
776
+ options: [{
777
+ key: "articleKey",
778
+ flags: "--article-key <key>",
779
+ description: "Article key tuple (repeatable).",
780
+ type: "string-array-raw"
781
+ }, {
782
+ key: "articleCount",
783
+ flags: "--article-count <count>",
784
+ description: "Maximum article count.",
785
+ type: "number"
786
+ }],
787
+ args: [],
788
+ buildRequest: (ctx) => ({
789
+ articleKeys: toRawStringArray(ctx.options.articleKey).map(parseArticleKey),
790
+ articleCount: readNumber(ctx.options.articleCount)
791
+ }),
792
+ invoke: (client, request, debug) => client.experimental.trendingArticles(asRequest(request), debug)
793
+ },
794
+ {
795
+ id: "experimental.geoPicker",
796
+ path: ["experimental", "geo-picker"],
797
+ summary: "Fetch experimental geo picker.",
798
+ requestSchema: pickerRequestSchema,
799
+ options: [],
800
+ args: [],
801
+ buildRequest: () => ({}),
802
+ invoke: (client, request, debug) => client.experimental.geoPicker(asRequest(request), debug)
803
+ },
804
+ {
805
+ id: "experimental.categoryPicker",
806
+ path: ["experimental", "category-picker"],
807
+ summary: "Fetch experimental category picker.",
808
+ requestSchema: pickerRequestSchema,
809
+ options: [],
810
+ args: [],
811
+ buildRequest: () => ({}),
812
+ invoke: (client, request, debug) => client.experimental.categoryPicker(asRequest(request), debug)
813
+ }
814
+ ];
815
+ const platformCommandPaths = [
816
+ ["config", "get"],
817
+ ["config", "set"],
818
+ ["config", "unset"],
819
+ ["config", "list"],
820
+ ["config", "reset"],
821
+ ["wizard"],
822
+ ["completion"],
823
+ ["__complete"]
824
+ ];
825
+ const commandPathsForCompletion = () => [...endpointManifest.map((item) => item.path), ...platformCommandPaths];
826
+ const findEndpointDefinitionByPath = (path) => endpointManifest.find((item) => item.path.length === path.length && item.path.every((segment, index) => segment === path[index]));
827
+
828
+ //#endregion
829
+ //#region src/cli/completion.ts
830
+ const parseFlagNames = (flags) => {
831
+ return flags.match(/--[a-z0-9-]+|-[a-z]/gi) ?? [];
832
+ };
833
+ const optionFlagsByPath = /* @__PURE__ */ new Map();
834
+ const valueFlags = /* @__PURE__ */ new Set();
835
+ for (const endpoint of endpointManifest) {
836
+ const key = endpoint.path.join(" ");
837
+ const names = [];
838
+ for (const option of [...globalOptions, ...endpoint.options]) {
839
+ const parsed = parseFlagNames(option.flags);
840
+ names.push(...parsed);
841
+ if (option.type !== "boolean") for (const flag of parsed) valueFlags.add(flag);
842
+ }
843
+ optionFlagsByPath.set(key, [...new Set(names)]);
844
+ }
845
+ optionFlagsByPath.set("config set", ["--json"]);
846
+ valueFlags.add("--json");
847
+ const visibleCommandPaths = commandPathsForCompletion().filter((path) => path[0] !== "__complete");
848
+ const unique = (values) => [...new Set(values)];
849
+ const extractCommandTokens = (words) => {
850
+ const commandTokens = [];
851
+ let skipValue = false;
852
+ for (const word of words) {
853
+ if (skipValue) {
854
+ skipValue = false;
855
+ continue;
856
+ }
857
+ if (word.startsWith("-")) {
858
+ const [flagName] = word.split("=", 2);
859
+ if (flagName && valueFlags.has(flagName) && !word.includes("=")) skipValue = true;
860
+ continue;
861
+ }
862
+ if (commandTokens.length < 2) commandTokens.push(word);
863
+ }
864
+ return commandTokens;
865
+ };
866
+ const flagsForPath = (path) => {
867
+ if (path.length === 0) return [];
868
+ const key = path.join(" ");
869
+ return optionFlagsByPath.get(key) ?? [];
870
+ };
871
+ const completeWords = (args) => {
872
+ const current = args.hasTrailingSpace ? "" : (args.words.at(-1) ?? "").trim();
873
+ const commandTokens = extractCommandTokens(args.hasTrailingSpace ? args.words : args.words.slice(0, -1));
874
+ if (current.startsWith("-")) return flagsForPath(commandTokens).filter((flag) => flag.startsWith(current)).toSorted();
875
+ const candidatePaths = visibleCommandPaths.filter((path) => commandTokens.every((segment, index) => path[index] === segment));
876
+ if (candidatePaths.length === 0) return visibleCommandPaths.map((path) => path[0] ?? "").filter((segment) => segment.startsWith(current)).toSorted();
877
+ const nextSegments = unique(candidatePaths.map((path) => path[commandTokens.length]).filter((segment) => typeof segment === "string")).filter((segment) => segment.startsWith(current)).toSorted();
878
+ if (nextSegments.length > 0) return nextSegments;
879
+ return flagsForPath(commandTokens).filter((flag) => flag.startsWith(current)).toSorted();
880
+ };
881
+ const scriptByShell = {
882
+ bash: (bin) => `_${bin}_complete() {
883
+ local IFS=$'\\n'
884
+ local line="$COMP_LINE"
885
+ local has_trailing_space=0
886
+ [[ "$line" == *" " ]] && has_trailing_space=1
887
+ COMPREPLY=( $( ${bin} __complete "$has_trailing_space" \${COMP_WORDS[@]:1} ) )
888
+ }
889
+ complete -F _${bin}_complete ${bin}
890
+ `,
891
+ zsh: (bin) => `#compdef ${bin}
892
+ _${bin}_complete() {
893
+ local -a suggestions
894
+ local line="$BUFFER"
895
+ local has_trailing_space=0
896
+ [[ "$line" == *" " ]] && has_trailing_space=1
897
+ suggestions=($(${bin} __complete "$has_trailing_space" \${words[@]:1}))
898
+ _describe 'values' suggestions
899
+ }
900
+ compdef _${bin}_complete ${bin}
901
+ `,
902
+ fish: (bin) => `function __${bin}_complete
903
+ set -l line (commandline -cp)
904
+ set -l has_trailing_space 0
905
+ if string match -q '* ' -- "$line"
906
+ set has_trailing_space 1
907
+ end
908
+ ${bin} __complete $has_trailing_space (commandline -opc | tail -n +2)
909
+ end
910
+ complete -c ${bin} -f -a "(__${bin}_complete)"
911
+ `
912
+ };
913
+ const renderCompletionScript = (shell, binaryName = "trendsearch") => {
914
+ if (shell === "bash" || shell === "zsh" || shell === "fish") {
915
+ const renderer = scriptByShell[shell];
916
+ return renderer(binaryName);
917
+ }
918
+ };
919
+
920
+ //#endregion
921
+ //#region src/cli/wizard.ts
922
+ const parseJsonInput$1 = (value) => {
923
+ try {
924
+ return JSON.parse(value);
925
+ } catch {
926
+ throw new Error("Request payload must be valid JSON.");
927
+ }
928
+ };
929
+ const runWizardPrompt = async () => {
930
+ intro("trendsearch wizard");
931
+ const endpoint = await select({
932
+ message: "Choose an endpoint",
933
+ options: endpointManifest.map((item) => ({
934
+ value: item.path.join(" "),
935
+ label: item.path.join(" "),
936
+ hint: item.summary
937
+ }))
938
+ });
939
+ if (isCancel(endpoint)) {
940
+ cancel("Wizard cancelled.");
941
+ return;
942
+ }
943
+ const endpointPath = String(endpoint).split(" ").map((segment) => segment.trim()).filter((segment) => segment.length > 0);
944
+ const definition = findEndpointDefinitionByPath(endpointPath);
945
+ if (!definition) {
946
+ cancel("Unknown endpoint selection.");
947
+ return;
948
+ }
949
+ const requestText = await text({
950
+ message: "Request JSON payload",
951
+ placeholder: "{\"keywords\":[\"typescript\"],\"geo\":\"US\"}",
952
+ validate(value) {
953
+ if (typeof value !== "string") return "Request JSON is required.";
954
+ if (value.trim().length === 0) return "Request JSON is required.";
955
+ try {
956
+ parseJsonInput$1(value);
957
+ } catch {
958
+ return "Request must be valid JSON.";
959
+ }
960
+ }
961
+ });
962
+ if (isCancel(requestText)) {
963
+ cancel("Wizard cancelled.");
964
+ return;
965
+ }
966
+ if (typeof requestText !== "string") {
967
+ cancel("Request JSON must be a string.");
968
+ return;
969
+ }
970
+ const output = await select({
971
+ message: "Choose output mode",
972
+ options: [
973
+ {
974
+ value: "pretty",
975
+ label: "pretty"
976
+ },
977
+ {
978
+ value: "json",
979
+ label: "json"
980
+ },
981
+ {
982
+ value: "jsonl",
983
+ label: "jsonl"
984
+ }
985
+ ]
986
+ });
987
+ if (isCancel(output)) {
988
+ cancel("Wizard cancelled.");
989
+ return;
990
+ }
991
+ const raw = await confirm({
992
+ message: "Include raw upstream response?",
993
+ initialValue: false
994
+ });
995
+ if (isCancel(raw)) {
996
+ cancel("Wizard cancelled.");
997
+ return;
998
+ }
999
+ outro(`Running ${definition.path.join(" ")}...`);
1000
+ return {
1001
+ endpointPath,
1002
+ request: parseJsonInput$1(requestText),
1003
+ output,
1004
+ raw: Boolean(raw)
1005
+ };
1006
+ };
1007
+
1008
+ //#endregion
1009
+ //#region src/cli/program.ts
1010
+ const toStringArray = (value, previous) => [...previous, ...value.split(",").map((part) => part.trim()).filter((part) => part.length > 0)];
1011
+ const addOptionDefinition = (command, option) => {
1012
+ if (option.type === "boolean") {
1013
+ command.option(option.flags, option.description);
1014
+ return;
1015
+ }
1016
+ const definition = new Option(option.flags, option.description);
1017
+ if (option.choices && option.choices.length > 0) definition.choices([...option.choices]);
1018
+ if (option.type === "number") definition.argParser(Number);
1019
+ if (option.type === "string-array") {
1020
+ definition.argParser((value, previous = []) => toStringArray(value, previous));
1021
+ definition.default([]);
1022
+ }
1023
+ if (option.type === "string-array-raw") {
1024
+ definition.argParser((value, previous = []) => [...previous, value]);
1025
+ definition.default([]);
1026
+ }
1027
+ command.addOption(definition);
1028
+ };
1029
+ const addGlobalOptions = (command) => {
1030
+ for (const option of globalOptions) addOptionDefinition(command, option);
1031
+ };
1032
+ const extractGlobalFlags = (options) => ({
1033
+ output: options.output,
1034
+ raw: options.raw,
1035
+ spinner: options.spinner,
1036
+ hl: options.hl,
1037
+ tz: options.tz,
1038
+ baseUrl: options.baseUrl,
1039
+ timeoutMs: options.timeoutMs,
1040
+ maxRetries: options.maxRetries,
1041
+ retryBaseDelayMs: options.retryBaseDelayMs,
1042
+ retryMaxDelayMs: options.retryMaxDelayMs,
1043
+ maxConcurrent: options.maxConcurrent,
1044
+ minDelayMs: options.minDelayMs,
1045
+ userAgent: options.userAgent,
1046
+ input: options.input
1047
+ });
1048
+ const readStdin = async (stdin) => new Promise((resolve, reject) => {
1049
+ const chunks = [];
1050
+ if ("setEncoding" in stdin && typeof stdin.setEncoding === "function") stdin.setEncoding("utf8");
1051
+ stdin.on("data", (chunk) => {
1052
+ chunks.push(typeof chunk === "string" ? chunk : chunk.toString("utf8"));
1053
+ });
1054
+ stdin.on("error", (error) => {
1055
+ reject(error);
1056
+ });
1057
+ stdin.on("end", () => {
1058
+ resolve(chunks.join(""));
1059
+ });
1060
+ });
1061
+ const parseJsonInput = (text) => {
1062
+ try {
1063
+ return JSON.parse(text);
1064
+ } catch {
1065
+ throw new CliUsageError("Invalid JSON input payload.", { input: text });
1066
+ }
1067
+ };
1068
+ const loadInputPayload = async (args) => {
1069
+ if (args.input === "-") return parseJsonInput(await readStdin(args.stdin));
1070
+ if (existsSync(args.input)) return parseJsonInput(await readFile(args.input, "utf8"));
1071
+ return parseJsonInput(args.input);
1072
+ };
1073
+ const withCommonRequestDefaults = (args) => {
1074
+ if (!(args.hl !== void 0 || args.tz !== void 0)) return args.request;
1075
+ const base = args.request;
1076
+ if (base === void 0 || base === null) {
1077
+ const requestDefaults = {};
1078
+ if (args.hl === void 0) {} else requestDefaults.hl = args.hl;
1079
+ if (args.tz === void 0) {} else requestDefaults.tz = args.tz;
1080
+ return requestDefaults;
1081
+ }
1082
+ if (typeof base !== "object" || Array.isArray(base)) return base;
1083
+ const draft = { ...base };
1084
+ if (args.hl !== void 0 && draft.hl === void 0) draft.hl = args.hl;
1085
+ if (args.tz !== void 0 && draft.tz === void 0) draft.tz = args.tz;
1086
+ return draft;
1087
+ };
1088
+ const toValidationIssues = (error) => error.issues.map((issue) => {
1089
+ return `${issue.path.length > 0 ? issue.path.join(".") : "request"}: ${issue.message}`;
1090
+ });
1091
+ const parseRequestWithSchema = (args) => {
1092
+ const parsed = args.definition.requestSchema.safeParse(args.request);
1093
+ if (parsed.success) return parsed.data;
1094
+ throw new CliUsageError("Request validation failed.", {
1095
+ endpoint: args.definition.path.join(" "),
1096
+ issues: toValidationIssues(parsed.error)
1097
+ });
1098
+ };
1099
+ const createEndpointEnvelope = (args) => {
1100
+ const endpointResult = args.result;
1101
+ return {
1102
+ ok: true,
1103
+ endpoint: args.endpoint,
1104
+ request: args.request,
1105
+ data: endpointResult.data,
1106
+ meta: {
1107
+ command: args.command,
1108
+ durationMs: args.durationMs,
1109
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1110
+ output: args.output
1111
+ },
1112
+ ...args.includeRaw && endpointResult.raw !== void 0 ? { raw: endpointResult.raw } : {}
1113
+ };
1114
+ };
1115
+ const executeEndpointCommand = async (args) => {
1116
+ const resolvedGlobal = resolveGlobalOptions({
1117
+ flags: extractGlobalFlags(args.options),
1118
+ env: args.env,
1119
+ store: args.configStore,
1120
+ stdoutIsTty: args.io.stdout.isTTY === true
1121
+ });
1122
+ if (args.forcedOutput) resolvedGlobal.output = args.forcedOutput;
1123
+ if (args.forcedRaw !== void 0) resolvedGlobal.raw = args.forcedRaw;
1124
+ const requestWithCommonDefaults = withCommonRequestDefaults({
1125
+ request: args.forcedRequest ?? (resolvedGlobal.input ? await loadInputPayload({
1126
+ input: resolvedGlobal.input,
1127
+ stdin: args.stdin
1128
+ }) : args.definition.buildRequest({
1129
+ positionals: args.positionals,
1130
+ options: args.options
1131
+ })),
1132
+ hl: resolvedGlobal.hl,
1133
+ tz: resolvedGlobal.tz
1134
+ });
1135
+ const request = parseRequestWithSchema({
1136
+ definition: args.definition,
1137
+ request: requestWithCommonDefaults
1138
+ });
1139
+ const spinner = createStderrSpinner({
1140
+ io: args.io,
1141
+ enabled: resolvedGlobal.spinner && resolvedGlobal.output === "pretty"
1142
+ });
1143
+ spinner.start(`Running ${args.definition.path.join(" ")}...`);
1144
+ try {
1145
+ const client = args.createClient(toCreateClientConfig(resolvedGlobal));
1146
+ const start = Date.now();
1147
+ const result = await args.definition.invoke(client, request, { debugRawResponse: resolvedGlobal.raw });
1148
+ const durationMs = Date.now() - start;
1149
+ spinner.stop(`Completed ${args.definition.path.join(" ")}.`);
1150
+ writeSuccessEnvelope({
1151
+ io: args.io,
1152
+ mode: resolvedGlobal.output,
1153
+ envelope: createEndpointEnvelope({
1154
+ endpoint: args.definition.id,
1155
+ request,
1156
+ result,
1157
+ command: args.definition.path.join(" "),
1158
+ output: resolvedGlobal.output,
1159
+ durationMs,
1160
+ includeRaw: resolvedGlobal.raw
1161
+ })
1162
+ });
1163
+ } catch (error) {
1164
+ spinner.fail(`Failed ${args.definition.path.join(" ")}.`);
1165
+ const normalized = normalizeCliError(error);
1166
+ writeErrorEnvelope({
1167
+ io: args.io,
1168
+ mode: resolvedGlobal.output,
1169
+ error: normalized
1170
+ });
1171
+ throw createCliExitSignal(normalized.exitCode);
1172
+ }
1173
+ };
1174
+ const safeConfigAction = async (args) => {
1175
+ const resolved = resolveGlobalOptions({
1176
+ flags: { output: args.options.output },
1177
+ env: args.env,
1178
+ store: args.store,
1179
+ stdoutIsTty: args.io.stdout.isTTY === true
1180
+ });
1181
+ try {
1182
+ const payload = await args.task();
1183
+ writeArbitraryOutput({
1184
+ io: args.io,
1185
+ mode: resolved.output,
1186
+ payload
1187
+ });
1188
+ } catch (error) {
1189
+ const normalized = normalizeCliError(error);
1190
+ writeErrorEnvelope({
1191
+ io: args.io,
1192
+ mode: resolved.output,
1193
+ error: normalized
1194
+ });
1195
+ throw createCliExitSignal(normalized.exitCode);
1196
+ }
1197
+ };
1198
+ const configureConfigCommands = (args) => {
1199
+ const configCommand = args.program.command("config").description("Manage persisted CLI defaults.");
1200
+ configCommand.command("get <key>").description("Get a persisted config value.").option("--output <mode>", "Output mode (pretty|json|jsonl).", "pretty").action(async (...actionArgs) => {
1201
+ const command = actionArgs.at(-1);
1202
+ const options = command.opts();
1203
+ const [key] = command.processedArgs;
1204
+ await safeConfigAction({
1205
+ io: args.io,
1206
+ env: args.env,
1207
+ store: args.store,
1208
+ options,
1209
+ task: () => ({
1210
+ key,
1211
+ value: args.store.get(key)
1212
+ })
1213
+ });
1214
+ });
1215
+ configCommand.command("set <key> <value>").description("Persist a config value.").option("--json", "Parse value as JSON.").option("--output <mode>", "Output mode (pretty|json|jsonl).", "pretty").action(async (...actionArgs) => {
1216
+ const command = actionArgs.at(-1);
1217
+ const options = command.opts();
1218
+ const [key, value] = command.processedArgs;
1219
+ await safeConfigAction({
1220
+ io: args.io,
1221
+ env: args.env,
1222
+ store: args.store,
1223
+ options,
1224
+ task: () => {
1225
+ const parsed = options.json ? parseJsonInput(value) : value;
1226
+ args.store.set(key, parsed);
1227
+ return {
1228
+ key,
1229
+ value: args.store.get(key)
1230
+ };
1231
+ }
1232
+ });
1233
+ });
1234
+ configCommand.command("unset <key>").description("Delete a persisted config value.").option("--output <mode>", "Output mode (pretty|json|jsonl).", "pretty").action(async (...actionArgs) => {
1235
+ const command = actionArgs.at(-1);
1236
+ const options = command.opts();
1237
+ const [key] = command.processedArgs;
1238
+ await safeConfigAction({
1239
+ io: args.io,
1240
+ env: args.env,
1241
+ store: args.store,
1242
+ options,
1243
+ task: () => {
1244
+ args.store.delete(key);
1245
+ return {
1246
+ key,
1247
+ removed: true
1248
+ };
1249
+ }
1250
+ });
1251
+ });
1252
+ configCommand.command("list").description("List all persisted config values.").option("--output <mode>", "Output mode (pretty|json|jsonl).", "pretty").action(async (...actionArgs) => {
1253
+ const options = actionArgs.at(-1).opts();
1254
+ await safeConfigAction({
1255
+ io: args.io,
1256
+ env: args.env,
1257
+ store: args.store,
1258
+ options,
1259
+ task: () => args.store.all()
1260
+ });
1261
+ });
1262
+ configCommand.command("reset").description("Clear all persisted config values.").option("--output <mode>", "Output mode (pretty|json|jsonl).", "pretty").action(async (...actionArgs) => {
1263
+ const options = actionArgs.at(-1).opts();
1264
+ await safeConfigAction({
1265
+ io: args.io,
1266
+ env: args.env,
1267
+ store: args.store,
1268
+ options,
1269
+ task: () => {
1270
+ args.store.clear();
1271
+ return { reset: true };
1272
+ }
1273
+ });
1274
+ });
1275
+ };
1276
+ const configureEndpointCommands = (args) => {
1277
+ const groupCommands = /* @__PURE__ */ new Map();
1278
+ const getParentCommand = (path) => {
1279
+ if (path.length === 0) return args.program;
1280
+ let current = args.program;
1281
+ for (const segment of path) {
1282
+ const key = `${current.name()}:${segment}`;
1283
+ const existing = groupCommands.get(key);
1284
+ if (existing) {
1285
+ current = existing;
1286
+ continue;
1287
+ }
1288
+ const created = current.command(segment).description(`${segment} commands.`);
1289
+ groupCommands.set(key, created);
1290
+ current = created;
1291
+ }
1292
+ return current;
1293
+ };
1294
+ for (const definition of endpointManifest) {
1295
+ const parentPath = definition.path.slice(0, -1);
1296
+ const leaf = definition.path.at(-1);
1297
+ if (!leaf) continue;
1298
+ const command = getParentCommand(parentPath).command(leaf).description(definition.summary);
1299
+ for (const arg of definition.args) command.argument(arg.label, arg.description);
1300
+ for (const option of definition.options) addOptionDefinition(command, option);
1301
+ addGlobalOptions(command);
1302
+ command.action(async (...actionArgs) => {
1303
+ const invokedCommand = actionArgs.at(-1);
1304
+ const options = invokedCommand.opts();
1305
+ const positionals = invokedCommand.processedArgs;
1306
+ await executeEndpointCommand({
1307
+ definition,
1308
+ positionals,
1309
+ options,
1310
+ io: args.io,
1311
+ env: args.env,
1312
+ configStore: args.configStore,
1313
+ stdin: args.stdin,
1314
+ createClient: args.createClient
1315
+ });
1316
+ });
1317
+ }
1318
+ };
1319
+ const configureWizardCommand = (args) => {
1320
+ const wizardCommand = args.program.command("wizard").description("Run the interactive endpoint wizard.");
1321
+ addGlobalOptions(wizardCommand);
1322
+ wizardCommand.action(async (...actionArgs) => {
1323
+ const options = actionArgs.at(-1).opts();
1324
+ const selection = await runWizardPrompt();
1325
+ if (!selection) return;
1326
+ const definition = findEndpointDefinitionByPath(selection.endpointPath);
1327
+ if (!definition) throw new CliUsageError("Unknown wizard endpoint selection.", { endpointPath: selection.endpointPath });
1328
+ await executeEndpointCommand({
1329
+ definition,
1330
+ positionals: [],
1331
+ options,
1332
+ io: args.io,
1333
+ env: args.env,
1334
+ configStore: args.configStore,
1335
+ stdin: args.stdin,
1336
+ createClient: args.createClient,
1337
+ forcedRequest: selection.request,
1338
+ forcedOutput: selection.output,
1339
+ forcedRaw: selection.raw
1340
+ });
1341
+ });
1342
+ };
1343
+ const configureCompletionCommands = (program, io) => {
1344
+ program.command("completion <shell>").description("Generate shell completion script (bash|zsh|fish).").action((shell) => {
1345
+ const script = renderCompletionScript(shell, "trendsearch");
1346
+ if (!script) throw new CliUsageError("Unsupported shell for completion.", {
1347
+ shell,
1348
+ supported: [
1349
+ "bash",
1350
+ "zsh",
1351
+ "fish"
1352
+ ]
1353
+ });
1354
+ io.stdout.write(script);
1355
+ });
1356
+ const completionHook = new Command("__complete").description("Internal shell completion hook.").argument("[hasTrailingSpace]").argument("[words...]").action((...actionArgs) => {
1357
+ const [hasTrailingSpace, words] = actionArgs.at(-1).processedArgs;
1358
+ const suggestions = completeWords({
1359
+ words: words ?? [],
1360
+ hasTrailingSpace: hasTrailingSpace === "1"
1361
+ });
1362
+ if (suggestions.length > 0) io.stdout.write(`${suggestions.join("\n")}\n`);
1363
+ });
1364
+ program.addCommand(completionHook, { hidden: true });
1365
+ };
1366
+ const createProgram = (options) => {
1367
+ const createClient$1 = options.createClient ?? createClient;
1368
+ const program = new Command();
1369
+ program.name("trendsearch").description("Google Trends SDK CLI for stable + experimental endpoints.").version(options.env.npm_package_version ?? "0.0.0").showHelpAfterError();
1370
+ configureEndpointCommands({
1371
+ program,
1372
+ io: options.io,
1373
+ env: options.env,
1374
+ configStore: options.configStore,
1375
+ stdin: options.stdin,
1376
+ createClient: createClient$1
1377
+ });
1378
+ configureConfigCommands({
1379
+ program,
1380
+ io: options.io,
1381
+ env: options.env,
1382
+ store: options.configStore
1383
+ });
1384
+ configureWizardCommand({
1385
+ program,
1386
+ io: options.io,
1387
+ env: options.env,
1388
+ configStore: options.configStore,
1389
+ stdin: options.stdin,
1390
+ createClient: createClient$1
1391
+ });
1392
+ configureCompletionCommands(program, options.io);
1393
+ return program;
1394
+ };
1395
+
1396
+ //#endregion
1397
+ //#region src/cli/main.ts
1398
+ const defaultIo = {
1399
+ stdout: process.stdout,
1400
+ stderr: process.stderr
1401
+ };
1402
+ const toDefaultOutputMode = (io) => io.stdout.isTTY === true ? "pretty" : "json";
1403
+ const runCli = async (options = {}) => {
1404
+ const env = options.env ?? process.env;
1405
+ const io = options.io ?? defaultIo;
1406
+ const program = createProgram({
1407
+ io,
1408
+ env,
1409
+ configStore: options.configStore ?? createCliConfigStore({ cwd: env.TRENDSEARCH_CONFIG_DIR }),
1410
+ stdin: options.stdin ?? process.stdin,
1411
+ createClient: options.createClient
1412
+ });
1413
+ program.configureOutput({
1414
+ writeErr: (line) => {
1415
+ io.stderr.write(line);
1416
+ },
1417
+ writeOut: (line) => {
1418
+ io.stdout.write(line);
1419
+ }
1420
+ });
1421
+ program.exitOverride();
1422
+ try {
1423
+ await program.parseAsync(options.argv ?? process.argv);
1424
+ return EXIT_CODES.ok;
1425
+ } catch (error) {
1426
+ if (isCliExitSignal(error)) return error.exitCode;
1427
+ if (isCommanderGracefulExit(error)) return EXIT_CODES.ok;
1428
+ if (error instanceof CommanderError) return EXIT_CODES.usage;
1429
+ const normalized = normalizeCliError(error);
1430
+ writeErrorEnvelope({
1431
+ io,
1432
+ mode: toDefaultOutputMode(io),
1433
+ error: normalized
1434
+ });
1435
+ return normalized.exitCode;
1436
+ }
1437
+ };
1438
+ if (import.meta.main) {
1439
+ const exitCode = await runCli();
1440
+ process.exitCode = exitCode;
1441
+ }
1442
+
1443
+ //#endregion
1444
+ export { runCli };