arendi-cli 0.1.0 → 0.1.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.
Files changed (2) hide show
  1. package/dist/index.js +411 -11
  2. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -46,7 +46,9 @@ var optionsSchema = z.object({
46
46
  apiKey: z.string().min(1, "LLM provider API key is required"),
47
47
  tavilyApiKey: z.string().min(1, "Tavily API key is required"),
48
48
  /** Optional ISO 3166-1 alpha-2 code for Google Trends (e.g. "US", "ID"). Auto-detected from `country` if omitted. */
49
- geo: z.string().length(2).optional()
49
+ geo: z.string().length(2).optional(),
50
+ /** Optional ISO 639-1 language code (e.g. "id", "es", "fr"). Auto-detected from `country` if omitted. Used for local-language source discovery and Google Trends `hl` parameter. */
51
+ language: z.string().optional()
50
52
  });
51
53
  var tavilyResultSchema = z.object({
52
54
  title: z.string(),
@@ -211,6 +213,364 @@ import { createHash } from "crypto";
211
213
 
212
214
  // ../../packages/arendi/dist/trends.js
213
215
  import googleTrends from "google-trends-api";
216
+
217
+ // ../../packages/arendi/dist/languages.js
218
+ var COUNTRY_LANGUAGE_MAP = {
219
+ afghanistan: "fa",
220
+ albania: "sq",
221
+ algeria: "ar",
222
+ argentina: "es",
223
+ austria: "de",
224
+ bangladesh: "bn",
225
+ belgium: "nl",
226
+ bolivia: "es",
227
+ brazil: "pt",
228
+ cambodia: "km",
229
+ cameroon: "fr",
230
+ chile: "es",
231
+ china: "zh",
232
+ colombia: "es",
233
+ "costa rica": "es",
234
+ croatia: "hr",
235
+ "czech republic": "cs",
236
+ czechia: "cs",
237
+ denmark: "da",
238
+ "dominican republic": "es",
239
+ ecuador: "es",
240
+ egypt: "ar",
241
+ "el salvador": "es",
242
+ ethiopia: "am",
243
+ finland: "fi",
244
+ france: "fr",
245
+ germany: "de",
246
+ greece: "el",
247
+ guatemala: "es",
248
+ honduras: "es",
249
+ "hong kong": "zh",
250
+ hungary: "hu",
251
+ india: "hi",
252
+ indonesia: "id",
253
+ iran: "fa",
254
+ iraq: "ar",
255
+ israel: "he",
256
+ italy: "it",
257
+ japan: "ja",
258
+ jordan: "ar",
259
+ kazakhstan: "kk",
260
+ kuwait: "ar",
261
+ laos: "lo",
262
+ lebanon: "ar",
263
+ libya: "ar",
264
+ malaysia: "ms",
265
+ mexico: "es",
266
+ morocco: "ar",
267
+ myanmar: "my",
268
+ nepal: "ne",
269
+ netherlands: "nl",
270
+ nicaragua: "es",
271
+ norway: "no",
272
+ oman: "ar",
273
+ pakistan: "ur",
274
+ panama: "es",
275
+ paraguay: "es",
276
+ peru: "es",
277
+ philippines: "tl",
278
+ poland: "pl",
279
+ portugal: "pt",
280
+ qatar: "ar",
281
+ romania: "ro",
282
+ russia: "ru",
283
+ "saudi arabia": "ar",
284
+ senegal: "fr",
285
+ serbia: "sr",
286
+ spain: "es",
287
+ "sri lanka": "si",
288
+ sweden: "sv",
289
+ switzerland: "de",
290
+ taiwan: "zh",
291
+ tanzania: "sw",
292
+ thailand: "th",
293
+ tunisia: "ar",
294
+ turkey: "tr",
295
+ turkiye: "tr",
296
+ ukraine: "uk",
297
+ "united arab emirates": "ar",
298
+ uae: "ar",
299
+ uruguay: "es",
300
+ uzbekistan: "uz",
301
+ venezuela: "es",
302
+ vietnam: "vi"
303
+ };
304
+ var LANGUAGE_INFO = {
305
+ // --- Southeast Asia -------------------------------------------------------
306
+ id: {
307
+ name: "Indonesian",
308
+ nativeName: "Bahasa Indonesia",
309
+ discoveryQueries: [
310
+ "situs berita bisnis lokal terbaik di {location}",
311
+ "media online ekonomi startup {location}",
312
+ "koran digital bisnis {location} terpopuler"
313
+ ]
314
+ },
315
+ ms: {
316
+ name: "Malay",
317
+ nativeName: "Bahasa Melayu",
318
+ discoveryQueries: [
319
+ "laman web berita perniagaan tempatan terbaik di {location}",
320
+ "media dalam talian ekonomi startup {location}",
321
+ "akhbar digital perniagaan {location} paling popular"
322
+ ]
323
+ },
324
+ th: {
325
+ name: "Thai",
326
+ nativeName: "\u0E20\u0E32\u0E29\u0E32\u0E44\u0E17\u0E22",
327
+ discoveryQueries: [
328
+ "\u0E40\u0E27\u0E47\u0E1A\u0E44\u0E0B\u0E15\u0E4C\u0E02\u0E48\u0E32\u0E27\u0E18\u0E38\u0E23\u0E01\u0E34\u0E08\u0E17\u0E49\u0E2D\u0E07\u0E16\u0E34\u0E48\u0E19\u0E17\u0E35\u0E48\u0E14\u0E35\u0E17\u0E35\u0E48\u0E2A\u0E38\u0E14\u0E43\u0E19 {location}",
329
+ "\u0E2A\u0E37\u0E48\u0E2D\u0E14\u0E34\u0E08\u0E34\u0E17\u0E31\u0E25 \u0E40\u0E28\u0E23\u0E29\u0E10\u0E01\u0E34\u0E08 \u0E2A\u0E15\u0E32\u0E23\u0E4C\u0E17\u0E2D\u0E31\u0E1E {location}",
330
+ "\u0E2B\u0E19\u0E31\u0E07\u0E2A\u0E37\u0E2D\u0E1E\u0E34\u0E21\u0E1E\u0E4C\u0E2D\u0E2D\u0E19\u0E44\u0E25\u0E19\u0E4C\u0E18\u0E38\u0E23\u0E01\u0E34\u0E08 {location} \u0E22\u0E2D\u0E14\u0E19\u0E34\u0E22\u0E21"
331
+ ]
332
+ },
333
+ vi: {
334
+ name: "Vietnamese",
335
+ nativeName: "Ti\u1EBFng Vi\u1EC7t",
336
+ discoveryQueries: [
337
+ "trang web tin t\u1EE9c kinh doanh \u0111\u1ECBa ph\u01B0\u01A1ng t\u1ED1t nh\u1EA5t t\u1EA1i {location}",
338
+ "truy\u1EC1n th\xF4ng s\u1ED1 kinh t\u1EBF startup {location}",
339
+ "b\xE1o \u0111i\u1EC7n t\u1EED kinh doanh {location} ph\u1ED5 bi\u1EBFn nh\u1EA5t"
340
+ ]
341
+ },
342
+ tl: {
343
+ name: "Filipino",
344
+ nativeName: "Tagalog",
345
+ discoveryQueries: [
346
+ "pinakamahusay na lokal na website ng balita sa negosyo sa {location}",
347
+ "digital media ekonomiya startup {location}",
348
+ "pinakasikat na online na pahayagan ng negosyo {location}"
349
+ ]
350
+ },
351
+ // --- East Asia ------------------------------------------------------------
352
+ ja: {
353
+ name: "Japanese",
354
+ nativeName: "\u65E5\u672C\u8A9E",
355
+ discoveryQueries: [
356
+ "{location} \u30D3\u30B8\u30CD\u30B9\u30CB\u30E5\u30FC\u30B9\u30B5\u30A4\u30C8 \u304A\u3059\u3059\u3081",
357
+ "{location} \u7D4C\u6E08 \u30B9\u30BF\u30FC\u30C8\u30A2\u30C3\u30D7 \u30E1\u30C7\u30A3\u30A2",
358
+ "{location} \u30D3\u30B8\u30CD\u30B9 \u30AA\u30F3\u30E9\u30A4\u30F3\u65B0\u805E \u4EBA\u6C17"
359
+ ]
360
+ },
361
+ ko: {
362
+ name: "Korean",
363
+ nativeName: "\uD55C\uAD6D\uC5B4",
364
+ discoveryQueries: [
365
+ "{location} \uBE44\uC988\uB2C8\uC2A4 \uB274\uC2A4 \uC0AC\uC774\uD2B8 \uCD94\uCC9C",
366
+ "{location} \uACBD\uC81C \uC2A4\uD0C0\uD2B8\uC5C5 \uBBF8\uB514\uC5B4",
367
+ "{location} \uBE44\uC988\uB2C8\uC2A4 \uC628\uB77C\uC778 \uC2E0\uBB38 \uC778\uAE30"
368
+ ]
369
+ },
370
+ zh: {
371
+ name: "Chinese",
372
+ nativeName: "\u4E2D\u6587",
373
+ discoveryQueries: [
374
+ "{location} \u5546\u4E1A\u65B0\u95FB\u7F51\u7AD9 \u63A8\u8350",
375
+ "{location} \u7ECF\u6D4E \u521B\u4E1A \u5A92\u4F53",
376
+ "{location} \u5546\u4E1A \u5728\u7EBF\u62A5\u7EB8 \u70ED\u95E8"
377
+ ]
378
+ },
379
+ // --- South Asia -----------------------------------------------------------
380
+ hi: {
381
+ name: "Hindi",
382
+ nativeName: "\u0939\u093F\u0928\u094D\u0926\u0940",
383
+ discoveryQueries: [
384
+ "{location} \u092E\u0947\u0902 \u0938\u0930\u094D\u0935\u0936\u094D\u0930\u0947\u0937\u094D\u0920 \u0938\u094D\u0925\u093E\u0928\u0940\u092F \u0935\u094D\u092F\u093E\u092A\u093E\u0930 \u0938\u092E\u093E\u091A\u093E\u0930 \u0935\u0947\u092C\u0938\u093E\u0907\u091F",
385
+ "{location} \u0905\u0930\u094D\u0925\u0935\u094D\u092F\u0935\u0938\u094D\u0925\u093E \u0938\u094D\u091F\u093E\u0930\u094D\u091F\u0905\u092A \u0921\u093F\u091C\u093F\u091F\u0932 \u092E\u0940\u0921\u093F\u092F\u093E",
386
+ "{location} \u0935\u094D\u092F\u093E\u092A\u093E\u0930 \u0911\u0928\u0932\u093E\u0907\u0928 \u0938\u092E\u093E\u091A\u093E\u0930 \u092A\u0924\u094D\u0930 \u0932\u094B\u0915\u092A\u094D\u0930\u093F\u092F"
387
+ ]
388
+ },
389
+ bn: {
390
+ name: "Bengali",
391
+ nativeName: "\u09AC\u09BE\u0982\u09B2\u09BE",
392
+ discoveryQueries: [
393
+ "{location} \u09B8\u09C7\u09B0\u09BE \u09B8\u09CD\u09A5\u09BE\u09A8\u09C0\u09AF\u09BC \u09AC\u09CD\u09AF\u09AC\u09B8\u09BE \u09B8\u0982\u09AC\u09BE\u09A6 \u0993\u09AF\u09BC\u09C7\u09AC\u09B8\u09BE\u0987\u099F",
394
+ "{location} \u0985\u09B0\u09CD\u09A5\u09A8\u09C0\u09A4\u09BF \u09B8\u09CD\u099F\u09BE\u09B0\u09CD\u099F\u0986\u09AA \u09A1\u09BF\u099C\u09BF\u099F\u09BE\u09B2 \u09AE\u09BF\u09A1\u09BF\u09AF\u09BC\u09BE",
395
+ "{location} \u09AC\u09CD\u09AF\u09AC\u09B8\u09BE \u0985\u09A8\u09B2\u09BE\u0987\u09A8 \u09B8\u0982\u09AC\u09BE\u09A6\u09AA\u09A4\u09CD\u09B0 \u099C\u09A8\u09AA\u09CD\u09B0\u09BF\u09AF\u09BC"
396
+ ]
397
+ },
398
+ ur: {
399
+ name: "Urdu",
400
+ nativeName: "\u0627\u0631\u062F\u0648",
401
+ discoveryQueries: [
402
+ "{location} \u0628\u06C1\u062A\u0631\u06CC\u0646 \u0645\u0642\u0627\u0645\u06CC \u06A9\u0627\u0631\u0648\u0628\u0627\u0631\u06CC \u062E\u0628\u0631\u0648\u06BA \u06A9\u06CC \u0648\u06CC\u0628 \u0633\u0627\u0626\u0679\u0633",
403
+ "{location} \u0645\u0639\u06CC\u0634\u062A \u0627\u0633\u0679\u0627\u0631\u0679 \u0627\u067E \u0688\u06CC\u062C\u06CC\u0679\u0644 \u0645\u06CC\u0688\u06CC\u0627",
404
+ "{location} \u06A9\u0627\u0631\u0648\u0628\u0627\u0631 \u0622\u0646 \u0644\u0627\u0626\u0646 \u0627\u062E\u0628\u0627\u0631\u0627\u062A \u0645\u0642\u0628\u0648\u0644"
405
+ ]
406
+ },
407
+ // --- Western Europe -------------------------------------------------------
408
+ es: {
409
+ name: "Spanish",
410
+ nativeName: "Espa\xF1ol",
411
+ discoveryQueries: [
412
+ "mejores sitios de noticias de negocios locales en {location}",
413
+ "medios digitales econom\xEDa startups {location}",
414
+ "peri\xF3dicos digitales de negocios {location} m\xE1s populares"
415
+ ]
416
+ },
417
+ pt: {
418
+ name: "Portuguese",
419
+ nativeName: "Portugu\xEAs",
420
+ discoveryQueries: [
421
+ "melhores sites de not\xEDcias de neg\xF3cios em {location}",
422
+ "m\xEDdia digital economia startups {location}",
423
+ "jornais digitais de neg\xF3cios {location} mais populares"
424
+ ]
425
+ },
426
+ fr: {
427
+ name: "French",
428
+ nativeName: "Fran\xE7ais",
429
+ discoveryQueries: [
430
+ "meilleurs sites d'actualit\xE9s business \xE0 {location}",
431
+ "m\xE9dias num\xE9riques \xE9conomie startups {location}",
432
+ "journaux en ligne business {location} les plus populaires"
433
+ ]
434
+ },
435
+ de: {
436
+ name: "German",
437
+ nativeName: "Deutsch",
438
+ discoveryQueries: [
439
+ "beste lokale Wirtschaftsnachrichten Webseiten in {location}",
440
+ "digitale Medien Wirtschaft Startups {location}",
441
+ "beliebteste Online-Wirtschaftszeitungen {location}"
442
+ ]
443
+ },
444
+ it: {
445
+ name: "Italian",
446
+ nativeName: "Italiano",
447
+ discoveryQueries: [
448
+ "migliori siti di notizie business locali a {location}",
449
+ "media digitali economia startup {location}",
450
+ "giornali online business {location} pi\xF9 popolari"
451
+ ]
452
+ },
453
+ nl: {
454
+ name: "Dutch",
455
+ nativeName: "Nederlands",
456
+ discoveryQueries: [
457
+ "beste lokale zakennieuws websites in {location}",
458
+ "digitale media economie startups {location}",
459
+ "populairste online zakenkranten {location}"
460
+ ]
461
+ },
462
+ sv: {
463
+ name: "Swedish",
464
+ nativeName: "Svenska",
465
+ discoveryQueries: [
466
+ "b\xE4sta lokala aff\xE4rsnyheter webbplatser i {location}",
467
+ "digitala medier ekonomi startups {location}",
468
+ "popul\xE4raste online aff\xE4rstidningar {location}"
469
+ ]
470
+ },
471
+ // --- Eastern Europe -------------------------------------------------------
472
+ pl: {
473
+ name: "Polish",
474
+ nativeName: "Polski",
475
+ discoveryQueries: [
476
+ "najlepsze lokalne strony z wiadomo\u015Bciami biznesowymi w {location}",
477
+ "media cyfrowe gospodarka startupy {location}",
478
+ "najpopularniejsze gazety biznesowe online {location}"
479
+ ]
480
+ },
481
+ ro: {
482
+ name: "Romanian",
483
+ nativeName: "Rom\xE2n\u0103",
484
+ discoveryQueries: [
485
+ "cele mai bune site-uri de \u0219tiri de afaceri locale din {location}",
486
+ "media digital\u0103 economie startup-uri {location}",
487
+ "cele mai populare ziare online de afaceri {location}"
488
+ ]
489
+ },
490
+ ru: {
491
+ name: "Russian",
492
+ nativeName: "\u0420\u0443\u0441\u0441\u043A\u0438\u0439",
493
+ discoveryQueries: [
494
+ "\u043B\u0443\u0447\u0448\u0438\u0435 \u0441\u0430\u0439\u0442\u044B \u0434\u0435\u043B\u043E\u0432\u044B\u0445 \u043D\u043E\u0432\u043E\u0441\u0442\u0435\u0439 \u0432 {location}",
495
+ "\u0446\u0438\u0444\u0440\u043E\u0432\u044B\u0435 \u043C\u0435\u0434\u0438\u0430 \u044D\u043A\u043E\u043D\u043E\u043C\u0438\u043A\u0430 \u0441\u0442\u0430\u0440\u0442\u0430\u043F\u044B {location}",
496
+ "\u0441\u0430\u043C\u044B\u0435 \u043F\u043E\u043F\u0443\u043B\u044F\u0440\u043D\u044B\u0435 \u043E\u043D\u043B\u0430\u0439\u043D \u0434\u0435\u043B\u043E\u0432\u044B\u0435 \u0433\u0430\u0437\u0435\u0442\u044B {location}"
497
+ ]
498
+ },
499
+ uk: {
500
+ name: "Ukrainian",
501
+ nativeName: "\u0423\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0430",
502
+ discoveryQueries: [
503
+ "\u043D\u0430\u0439\u043A\u0440\u0430\u0449\u0456 \u0441\u0430\u0439\u0442\u0438 \u0434\u0456\u043B\u043E\u0432\u0438\u0445 \u043D\u043E\u0432\u0438\u043D \u0432 {location}",
504
+ "\u0446\u0438\u0444\u0440\u043E\u0432\u0456 \u043C\u0435\u0434\u0456\u0430 \u0435\u043A\u043E\u043D\u043E\u043C\u0456\u043A\u0430 \u0441\u0442\u0430\u0440\u0442\u0430\u043F\u0438 {location}",
505
+ "\u043D\u0430\u0439\u043F\u043E\u043F\u0443\u043B\u044F\u0440\u043D\u0456\u0448\u0456 \u043E\u043D\u043B\u0430\u0439\u043D \u0434\u0456\u043B\u043E\u0432\u0456 \u0433\u0430\u0437\u0435\u0442\u0438 {location}"
506
+ ]
507
+ },
508
+ // --- Middle East / North Africa -------------------------------------------
509
+ ar: {
510
+ name: "Arabic",
511
+ nativeName: "\u0627\u0644\u0639\u0631\u0628\u064A\u0629",
512
+ discoveryQueries: [
513
+ "\u0623\u0641\u0636\u0644 \u0645\u0648\u0627\u0642\u0639 \u0623\u062E\u0628\u0627\u0631 \u0627\u0644\u0623\u0639\u0645\u0627\u0644 \u0627\u0644\u0645\u062D\u0644\u064A\u0629 \u0641\u064A {location}",
514
+ "\u0648\u0633\u0627\u0626\u0644 \u0625\u0639\u0644\u0627\u0645 \u0631\u0642\u0645\u064A\u0629 \u0627\u0642\u062A\u0635\u0627\u062F \u0634\u0631\u0643\u0627\u062A \u0646\u0627\u0634\u0626\u0629 {location}",
515
+ "\u0635\u062D\u0641 \u0631\u0642\u0645\u064A\u0629 \u0623\u0639\u0645\u0627\u0644 {location} \u0627\u0644\u0623\u0643\u062B\u0631 \u0634\u0639\u0628\u064A\u0629"
516
+ ]
517
+ },
518
+ he: {
519
+ name: "Hebrew",
520
+ nativeName: "\u05E2\u05D1\u05E8\u05D9\u05EA",
521
+ discoveryQueries: [
522
+ "\u05D0\u05EA\u05E8\u05D9 \u05D7\u05D3\u05E9\u05D5\u05EA \u05E2\u05E1\u05E7\u05D9\u05D5\u05EA \u05DE\u05E7\u05D5\u05DE\u05D9\u05D5\u05EA \u05D4\u05D8\u05D5\u05D1\u05D9\u05DD \u05D1\u05D9\u05D5\u05EA\u05E8 \u05D1{location}",
523
+ "\u05DE\u05D3\u05D9\u05D4 \u05D3\u05D9\u05D2\u05D9\u05D8\u05DC\u05D9\u05EA \u05DB\u05DC\u05DB\u05DC\u05D4 \u05E1\u05D8\u05D0\u05E8\u05D8\u05D0\u05E4\u05D9\u05DD {location}",
524
+ "\u05E2\u05D9\u05EA\u05D5\u05E0\u05D9\u05DD \u05D3\u05D9\u05D2\u05D9\u05D8\u05DC\u05D9\u05D9\u05DD \u05E2\u05E1\u05E7\u05D9\u05D9\u05DD {location} \u05D4\u05E4\u05D5\u05E4\u05D5\u05DC\u05E8\u05D9\u05D9\u05DD \u05D1\u05D9\u05D5\u05EA\u05E8"
525
+ ]
526
+ },
527
+ fa: {
528
+ name: "Persian",
529
+ nativeName: "\u0641\u0627\u0631\u0633\u06CC",
530
+ discoveryQueries: [
531
+ "\u0628\u0647\u062A\u0631\u06CC\u0646 \u0633\u0627\u06CC\u062A\u200C\u0647\u0627\u06CC \u062E\u0628\u0631\u06CC \u06A9\u0633\u0628\u200C\u0648\u06A9\u0627\u0631 \u0645\u062D\u0644\u06CC \u062F\u0631 {location}",
532
+ "\u0631\u0633\u0627\u0646\u0647 \u062F\u06CC\u062C\u06CC\u062A\u0627\u0644 \u0627\u0642\u062A\u0635\u0627\u062F \u0627\u0633\u062A\u0627\u0631\u062A\u0627\u067E {location}",
533
+ "\u0645\u062D\u0628\u0648\u0628\u200C\u062A\u0631\u06CC\u0646 \u0631\u0648\u0632\u0646\u0627\u0645\u0647\u200C\u0647\u0627\u06CC \u0622\u0646\u0644\u0627\u06CC\u0646 \u06A9\u0633\u0628\u200C\u0648\u06A9\u0627\u0631 {location}"
534
+ ]
535
+ },
536
+ // --- Turkey ---------------------------------------------------------------
537
+ tr: {
538
+ name: "Turkish",
539
+ nativeName: "T\xFCrk\xE7e",
540
+ discoveryQueries: [
541
+ "{location} en iyi yerel i\u015F haberleri web siteleri",
542
+ "{location} dijital medya ekonomi startup",
543
+ "{location} en pop\xFCler online i\u015F gazeteleri"
544
+ ]
545
+ }
546
+ };
547
+ function getLanguageForCountry(country) {
548
+ const code = COUNTRY_LANGUAGE_MAP[country.trim().toLowerCase()];
549
+ if (!code)
550
+ return null;
551
+ return code;
552
+ }
553
+ function resolveLanguage(options) {
554
+ if (options.language) {
555
+ const code = options.language.trim().toLowerCase();
556
+ return code === "en" ? null : code;
557
+ }
558
+ if (options.country) {
559
+ return getLanguageForCountry(options.country);
560
+ }
561
+ return null;
562
+ }
563
+ function getLocalizedDiscoveryQueries(location, languageCode) {
564
+ const info = LANGUAGE_INFO[languageCode];
565
+ if (!info)
566
+ return [];
567
+ return info.discoveryQueries.map((q) => q.replace("{location}", location));
568
+ }
569
+ function getLanguageName(code) {
570
+ return LANGUAGE_INFO[code]?.name ?? null;
571
+ }
572
+
573
+ // ../../packages/arendi/dist/trends.js
214
574
  var COUNTRY_GEO_MAP = {
215
575
  afghanistan: "AF",
216
576
  albania: "AL",
@@ -325,9 +685,17 @@ function countryToGeo(country) {
325
685
  }
326
686
  return COUNTRY_GEO_MAP[trimmed.toLowerCase()] ?? null;
327
687
  }
328
- async function fetchDailyTrends(geo) {
688
+ function looksLikeHtml(raw) {
689
+ const trimmed = raw.trimStart();
690
+ return trimmed.startsWith("<!") || trimmed.startsWith("<html");
691
+ }
692
+ async function fetchDailyTrends(geo, hl) {
329
693
  try {
330
- const raw = await googleTrends.dailyTrends({ geo });
694
+ const raw = await googleTrends.dailyTrends({ geo, hl });
695
+ if (looksLikeHtml(raw)) {
696
+ console.warn(`Warning: Google Trends returned an HTML page instead of data for geo "${geo}" (daily trends). This usually means the geo is unsupported or Google is rate-limiting requests. Skipping.`);
697
+ return [];
698
+ }
331
699
  const parsed = JSON.parse(raw);
332
700
  const days = parsed?.["default"];
333
701
  const trendingDays = days?.["trendingSearchesDays"] ?? [];
@@ -350,7 +718,7 @@ async function fetchDailyTrends(geo) {
350
718
  return [];
351
719
  }
352
720
  }
353
- async function fetchRelatedQueries(keywords, geo) {
721
+ async function fetchRelatedQueries(keywords, geo, hl) {
354
722
  const ninetyDaysAgo = /* @__PURE__ */ new Date();
355
723
  ninetyDaysAgo.setDate(ninetyDaysAgo.getDate() - 90);
356
724
  const results = await Promise.all(keywords.map(async (keyword) => {
@@ -358,8 +726,13 @@ async function fetchRelatedQueries(keywords, geo) {
358
726
  const raw = await googleTrends.relatedQueries({
359
727
  keyword,
360
728
  startTime: ninetyDaysAgo,
361
- geo: geo ?? ""
729
+ geo: geo ?? "",
730
+ hl
362
731
  });
732
+ if (looksLikeHtml(raw)) {
733
+ console.warn(`Warning: Google Trends returned an HTML page for related queries "${keyword}" (geo "${geo ?? "global"}"). Skipping.`);
734
+ return null;
735
+ }
363
736
  const parsed = JSON.parse(raw);
364
737
  const rankedList = parsed?.["default"]?.["rankedList"];
365
738
  if (!rankedList || rankedList.length === 0) {
@@ -383,7 +756,7 @@ async function fetchRelatedQueries(keywords, geo) {
383
756
  }));
384
757
  return results.filter((r) => r !== null);
385
758
  }
386
- async function fetchRelatedTopics(keywords, geo) {
759
+ async function fetchRelatedTopics(keywords, geo, hl) {
387
760
  const ninetyDaysAgo = /* @__PURE__ */ new Date();
388
761
  ninetyDaysAgo.setDate(ninetyDaysAgo.getDate() - 90);
389
762
  const results = await Promise.all(keywords.map(async (keyword) => {
@@ -391,8 +764,13 @@ async function fetchRelatedTopics(keywords, geo) {
391
764
  const raw = await googleTrends.relatedTopics({
392
765
  keyword,
393
766
  startTime: ninetyDaysAgo,
394
- geo: geo ?? ""
767
+ geo: geo ?? "",
768
+ hl
395
769
  });
770
+ if (looksLikeHtml(raw)) {
771
+ console.warn(`Warning: Google Trends returned an HTML page for related topics "${keyword}" (geo "${geo ?? "global"}"). Skipping.`);
772
+ return null;
773
+ }
396
774
  const parsed = JSON.parse(raw);
397
775
  const rankedList = parsed?.["default"]?.["rankedList"];
398
776
  if (!rankedList || rankedList.length === 0) {
@@ -430,10 +808,11 @@ async function trendsResearch(options) {
430
808
  if (options.categories?.length && keywords.length === 0) {
431
809
  keywords.push(...options.categories);
432
810
  }
811
+ const hl = resolveLanguage(options) ?? void 0;
433
812
  const [dailyTrends, relatedQueries, relatedTopics] = await Promise.all([
434
- geo ? fetchDailyTrends(geo) : Promise.resolve([]),
435
- keywords.length > 0 ? fetchRelatedQueries(keywords, geo ?? void 0) : Promise.resolve([]),
436
- keywords.length > 0 ? fetchRelatedTopics(keywords, geo ?? void 0) : Promise.resolve([])
813
+ geo ? fetchDailyTrends(geo, hl) : Promise.resolve([]),
814
+ keywords.length > 0 ? fetchRelatedQueries(keywords, geo ?? void 0, hl) : Promise.resolve([]),
815
+ keywords.length > 0 ? fetchRelatedTopics(keywords, geo ?? void 0, hl) : Promise.resolve([])
437
816
  ]);
438
817
  return { dailyTrends, relatedQueries, relatedTopics };
439
818
  }
@@ -593,6 +972,15 @@ async function discoverLocalSources(options, cacheStats) {
593
972
  `popular local media sources ${location} economy startups`,
594
973
  `${location} local online newspapers business publications`
595
974
  ];
975
+ const languageCode = resolveLanguage(options);
976
+ if (languageCode) {
977
+ const localizedQueries = getLocalizedDiscoveryQueries(location, languageCode);
978
+ discoveryQueries.push(...localizedQueries);
979
+ const langName = getLanguageName(languageCode);
980
+ if (langName) {
981
+ discoveryQueries.push(`${location} local news media websites in ${langName} language`);
982
+ }
983
+ }
596
984
  const allResults = await Promise.all(discoveryQueries.map((q) => tavilySearch(q, options.tavilyApiKey, {
597
985
  cache: options.cache,
598
986
  cacheStats
@@ -631,6 +1019,13 @@ function buildLocalSearchQueries(options) {
631
1019
  if (kw) {
632
1020
  queries.push([kw, `market demand`, location, year].filter(Boolean).join(" "));
633
1021
  }
1022
+ const languageCode = resolveLanguage(options);
1023
+ if (languageCode) {
1024
+ const langName = getLanguageName(languageCode);
1025
+ if (langName) {
1026
+ queries.push([`${langName} language`, cats, `business trends`, location, year].filter(Boolean).join(" "));
1027
+ }
1028
+ }
634
1029
  return queries;
635
1030
  }
636
1031
  function buildGlobalSearchQueries(options) {
@@ -908,7 +1303,8 @@ async function generateIdeas(rawOptions, callbacks) {
908
1303
  limit: options.limit,
909
1304
  provider: options.provider,
910
1305
  model: options.model,
911
- geo: options.geo
1306
+ geo: options.geo,
1307
+ language: options.language
912
1308
  },
913
1309
  sourceDiscovery: {
914
1310
  domains: localDomains,
@@ -1260,6 +1656,9 @@ program.name("arendi").description(
1260
1656
  ).option(
1261
1657
  "--geo <code>",
1262
1658
  "ISO 3166-1 alpha-2 country code for Google Trends (e.g., US, ID). Auto-detected from --country if omitted."
1659
+ ).option(
1660
+ "--language <code>",
1661
+ "ISO 639-1 language code for local-language results (e.g., id, es, fr). Auto-detected from --country if omitted."
1263
1662
  ).option(
1264
1663
  "--db <path>",
1265
1664
  "Path to the SQLite database for caching and run history",
@@ -1351,6 +1750,7 @@ program.action(async (opts) => {
1351
1750
  apiKey,
1352
1751
  tavilyApiKey,
1353
1752
  geo: opts["geo"],
1753
+ language: opts["language"],
1354
1754
  cache,
1355
1755
  store
1356
1756
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arendi-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "CLI for AI-powered business idea generation with web research",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -25,6 +25,7 @@
25
25
  "build": "tsup",
26
26
  "dev": "tsx src/index.ts",
27
27
  "dev:id": "tsx src/index.ts --country Indonesia --categories health,finance --format json --output ideas.json",
28
+ "dev:jakarta": "tsx src/index.ts --country Indonesia --city Jakarta --categories entertainment,lifestyle --format json --output ideas.json",
28
29
  "prepublishOnly": "pnpm run build",
29
30
  "test": "vitest run",
30
31
  "test:coverage": "vitest run --coverage"