@countrystatecity/cli 0.1.5 → 0.1.6

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 (3) hide show
  1. package/README.md +24 -1
  2. package/dist/index.js +189 -101
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -62,14 +62,33 @@ csc search states
62
62
  csc search states --country IN
63
63
  csc search states -c US --filter "new"
64
64
 
65
+ # List all cities globally
66
+ csc search cities
67
+
65
68
  # List all cities for a country
66
69
  csc search cities --country IN
67
- csc search cities --country IN --json
68
70
 
69
71
  # List cities for a specific state
70
72
  csc search cities --country IN --state MH
71
73
  csc search cities -c US -s CA --json
72
74
 
75
+ # List all world regions
76
+ csc search regions
77
+ csc search regions --filter "asia"
78
+
79
+ # List all currencies
80
+ csc search currencies
81
+ csc search currencies --filter "dollar"
82
+
83
+ # List all timezones
84
+ csc search timezones
85
+ csc search timezones --country IN
86
+ csc search timezones --filter "kolkata"
87
+
88
+ # List all country phone codes
89
+ csc search phonecodes
90
+ csc search phonecodes --filter "india"
91
+
73
92
  # Global search (matches country names)
74
93
  csc search india
75
94
  ```
@@ -86,6 +105,10 @@ csc get country # Interactive — prompts to pick a country (TTY only)
86
105
  csc get state IN MH
87
106
  csc get state IN MH --json
88
107
  csc get state # Interactive — prompts for country then state (TTY only)
108
+
109
+ # Detailed city info by ID
110
+ csc get city IN MH 57589
111
+ csc get city IN MH 57589 --json
89
112
  ```
90
113
 
91
114
  ### Usage & Billing
package/dist/index.js CHANGED
@@ -417,15 +417,14 @@ function printDetail(label, value) {
417
417
  }
418
418
 
419
419
  // src/commands/search.ts
420
+ function resolveFlags(cmd) {
421
+ const g = cmd.optsWithGlobals();
422
+ return { json: g.json ?? false, quiet: g.quiet ?? false, noFooter: g.footer === false };
423
+ }
420
424
  function registerSearchCommands(program2) {
421
- const search3 = program2.command("search").description("Search countries, states, and cities");
425
+ const search3 = program2.command("search").description("Search countries, states, cities, and more");
422
426
  search3.command("countries").description("List all countries").option("--filter <text>", "Filter by name").action(async (options, cmd) => {
423
- const globalOpts = cmd.optsWithGlobals();
424
- const flags = {
425
- json: globalOpts.json ?? false,
426
- quiet: globalOpts.quiet ?? false,
427
- noFooter: globalOpts.footer === false
428
- };
427
+ const flags = resolveFlags(cmd);
429
428
  const spinner = await createSpinner("Fetching countries...", flags);
430
429
  const { data, usage } = await get("/countries");
431
430
  spinner.stop();
@@ -437,112 +436,171 @@ function registerSearchCommands(program2) {
437
436
  if (flags.json) {
438
437
  printJson(countries);
439
438
  } else {
440
- const rows = countries.map((c) => [
441
- c.iso2,
442
- c.iso3,
443
- c.name,
444
- c.capital || "",
445
- c.phonecode ? `+${c.phonecode.replace(/^\+/, "")}` : "",
446
- c.currency || ""
447
- ]);
448
- printTable(["ISO2", "ISO3", "Name", "Capital", "Phone", "Currency"], rows);
439
+ printTable(
440
+ ["ISO2", "ISO3", "Name", "Capital", "Phone", "Currency"],
441
+ countries.map((c) => [
442
+ c.iso2,
443
+ c.iso3,
444
+ c.name,
445
+ c.capital || "",
446
+ c.phonecode ? `+${c.phonecode.replace(/^\+/, "")}` : "",
447
+ c.currency || ""
448
+ ])
449
+ );
449
450
  }
450
451
  printUsageFooter(usage, flags);
451
452
  });
452
453
  search3.command("states").description("List states for a country, or all states globally").option("-c, --country <iso2>", "Country ISO2 code (omit to get all states globally)").option("--filter <text>", "Filter by name").action(async (options, cmd) => {
453
- const globalOpts = cmd.optsWithGlobals();
454
- const flags = {
455
- json: globalOpts.json ?? false,
456
- quiet: globalOpts.quiet ?? false,
457
- noFooter: globalOpts.footer === false
458
- };
454
+ const flags = resolveFlags(cmd);
459
455
  const code = options.country?.toUpperCase();
456
+ const endpoint = code ? `/countries/${code}/states` : "/states";
457
+ const spinner = await createSpinner(code ? `Fetching states for ${code}...` : "Fetching all states...", flags);
458
+ const { data, usage } = await get(endpoint);
459
+ spinner.stop();
460
+ let states = data;
461
+ if (options.filter) {
462
+ const term = options.filter.toLowerCase();
463
+ states = states.filter((s) => s.name.toLowerCase().includes(term));
464
+ }
465
+ if (flags.json) {
466
+ printJson(states);
467
+ } else {
468
+ printTable(
469
+ code ? ["ID", "Name", "ISO2", "Type"] : ["ID", "Name", "ISO2", "Type", "Country"],
470
+ states.map(
471
+ (s) => code ? [String(s.id), s.name, s.iso2 || "", s.type || ""] : [String(s.id), s.name, s.iso2 || "", s.type || "", s.country_code || ""]
472
+ )
473
+ );
474
+ }
475
+ printUsageFooter(usage, flags);
476
+ });
477
+ search3.command("cities").description("List cities globally, for a country, or for a state").option("-c, --country <iso2>", "Country ISO2 code").option("-s, --state <iso2>", "State ISO2 code").option("--filter <text>", "Filter by name").action(async (options, cmd) => {
478
+ const flags = resolveFlags(cmd);
479
+ const countryCode = options.country?.toUpperCase();
480
+ const stateCode = options.state?.toUpperCase();
460
481
  let endpoint;
461
482
  let spinnerText;
462
- if (code) {
463
- endpoint = `/countries/${code}/states`;
464
- spinnerText = `Fetching states for ${code}...`;
483
+ if (countryCode && stateCode) {
484
+ endpoint = `/countries/${countryCode}/states/${stateCode}/cities`;
485
+ spinnerText = `Fetching cities for ${countryCode}/${stateCode}...`;
486
+ } else if (countryCode) {
487
+ endpoint = `/countries/${countryCode}/cities`;
488
+ spinnerText = `Fetching all cities for ${countryCode}...`;
465
489
  } else {
466
- endpoint = "/states";
467
- spinnerText = "Fetching all states...";
490
+ endpoint = "/cities";
491
+ spinnerText = "Fetching all cities...";
468
492
  }
469
493
  const spinner = await createSpinner(spinnerText, flags);
470
494
  const { data, usage } = await get(endpoint);
471
495
  spinner.stop();
472
- let states = data;
496
+ let cities = data;
473
497
  if (options.filter) {
474
498
  const term = options.filter.toLowerCase();
475
- states = states.filter((s) => s.name.toLowerCase().includes(term));
499
+ cities = cities.filter((c) => c.name.toLowerCase().includes(term));
476
500
  }
477
501
  if (flags.json) {
478
- printJson(states);
502
+ printJson(cities);
479
503
  } else {
480
- const rows = states.map((s) => [
481
- String(s.id),
482
- s.name,
483
- s.iso2 || "",
484
- s.type || ""
485
- ]);
486
- printTable(["ID", "Name", "ISO2", "Type"], rows);
504
+ const hasExtra = !countryCode;
505
+ printTable(
506
+ hasExtra ? ["ID", "Name", "State", "Country"] : ["ID", "Name"],
507
+ cities.map(
508
+ (c) => hasExtra ? [String(c.id), c.name, c.state_code || "", c.country_code || ""] : [String(c.id), c.name]
509
+ )
510
+ );
487
511
  }
488
512
  printUsageFooter(usage, flags);
489
513
  });
490
- search3.command("cities").description("List cities for a country or state").option("-c, --country <iso2>", "Country ISO2 code").option("-s, --state <iso2>", "State ISO2 code (omit to get all cities in the country)").option("--filter <text>", "Filter by name").action(
491
- async (options, cmd) => {
492
- const globalOpts = cmd.optsWithGlobals();
493
- const flags = {
494
- json: globalOpts.json ?? false,
495
- quiet: globalOpts.quiet ?? false,
496
- noFooter: globalOpts.footer === false
497
- };
498
- let countryCode = options.country?.toUpperCase();
499
- if (!countryCode) {
500
- if (isTTY()) {
501
- const countrySpinner = await createSpinner("Loading countries...", flags);
502
- const { data: allCountries } = await get("/countries");
503
- countrySpinner.stop();
504
- countryCode = await promptCountry(allCountries);
505
- } else {
506
- process.stderr.write(chalk5.red("Country code required. Use --country IN\n"));
507
- process.exit(1);
508
- return;
509
- }
510
- }
511
- const stateCode = options.state?.toUpperCase();
512
- let endpoint;
513
- let spinnerText;
514
- if (stateCode) {
515
- endpoint = `/countries/${countryCode}/states/${stateCode}/cities`;
516
- spinnerText = `Fetching cities for ${countryCode}/${stateCode}...`;
517
- } else {
518
- endpoint = `/countries/${countryCode}/cities`;
519
- spinnerText = `Fetching all cities for ${countryCode}...`;
520
- }
521
- const spinner = await createSpinner(spinnerText, flags);
522
- const { data, usage } = await get(endpoint);
523
- spinner.stop();
524
- let cities = data;
525
- if (options.filter) {
526
- const term = options.filter.toLowerCase();
527
- cities = cities.filter((c) => c.name.toLowerCase().includes(term));
528
- }
529
- if (flags.json) {
530
- printJson(cities);
531
- } else {
532
- const rows = cities.map((c) => [String(c.id), c.name]);
533
- printTable(["ID", "Name"], rows);
534
- }
535
- printUsageFooter(usage, flags);
514
+ search3.command("regions").description("List all world regions").option("--filter <text>", "Filter by name").action(async (options, cmd) => {
515
+ const flags = resolveFlags(cmd);
516
+ const spinner = await createSpinner("Fetching regions...", flags);
517
+ const { data, usage } = await get("/regions");
518
+ spinner.stop();
519
+ let regions = data;
520
+ if (options.filter) {
521
+ const term = options.filter.toLowerCase();
522
+ regions = regions.filter((r) => r.name.toLowerCase().includes(term));
536
523
  }
537
- );
524
+ if (flags.json) {
525
+ printJson(regions);
526
+ } else {
527
+ printTable(["ID", "Name"], regions.map((r) => [String(r.id), r.name]));
528
+ }
529
+ printUsageFooter(usage, flags);
530
+ });
531
+ search3.command("currencies").description("List all currencies").option("--filter <text>", "Filter by name or code").action(async (options, cmd) => {
532
+ const flags = resolveFlags(cmd);
533
+ const spinner = await createSpinner("Fetching currencies...", flags);
534
+ const { data, usage } = await get("/currencies");
535
+ spinner.stop();
536
+ let currencies = data;
537
+ if (options.filter) {
538
+ const term = options.filter.toLowerCase();
539
+ currencies = currencies.filter(
540
+ (c) => c.name.toLowerCase().includes(term) || (c.code || "").toLowerCase().includes(term)
541
+ );
542
+ }
543
+ if (flags.json) {
544
+ printJson(currencies);
545
+ } else {
546
+ printTable(
547
+ ["ID", "Name", "Symbol", "Code"],
548
+ currencies.map((c) => [String(c.id), c.name, c.symbol || "", c.code || ""])
549
+ );
550
+ }
551
+ printUsageFooter(usage, flags);
552
+ });
553
+ search3.command("timezones").description("List all timezones").option("-c, --country <iso2>", "Filter by country ISO2 code").option("--filter <text>", "Filter by zone name or abbreviation").action(async (options, cmd) => {
554
+ const flags = resolveFlags(cmd);
555
+ const spinner = await createSpinner("Fetching timezones...", flags);
556
+ const { data, usage } = await get("/timezones");
557
+ spinner.stop();
558
+ let timezones = data;
559
+ if (options.country) {
560
+ const code = options.country.toUpperCase();
561
+ timezones = timezones.filter((t) => (t.countryCode || "").toUpperCase() === code);
562
+ }
563
+ if (options.filter) {
564
+ const term = options.filter.toLowerCase();
565
+ timezones = timezones.filter(
566
+ (t) => t.zoneName.toLowerCase().includes(term) || t.abbreviation.toLowerCase().includes(term)
567
+ );
568
+ }
569
+ if (flags.json) {
570
+ printJson(timezones);
571
+ } else {
572
+ printTable(
573
+ ["Zone Name", "Abbreviation", "UTC Offset", "TZ Name"],
574
+ timezones.map((t) => [t.zoneName, t.abbreviation, t.gmtOffsetName || String(t.gmtOffset), t.tzName || ""])
575
+ );
576
+ }
577
+ printUsageFooter(usage, flags);
578
+ });
579
+ search3.command("phonecodes").description("List all country phone codes").option("--filter <text>", "Filter by country name or code").action(async (options, cmd) => {
580
+ const flags = resolveFlags(cmd);
581
+ const spinner = await createSpinner("Fetching phone codes...", flags);
582
+ const { data, usage } = await get("/phone-codes");
583
+ spinner.stop();
584
+ let phonecodes = data;
585
+ if (options.filter) {
586
+ const term = options.filter.toLowerCase();
587
+ phonecodes = phonecodes.filter(
588
+ (p) => p.name.toLowerCase().includes(term) || (p.iso2 || "").toLowerCase().includes(term)
589
+ );
590
+ }
591
+ if (flags.json) {
592
+ printJson(phonecodes);
593
+ } else {
594
+ printTable(
595
+ ["ISO2", "Name", "Phone Code"],
596
+ phonecodes.map((p) => [p.iso2 || "", p.name, `+${p.phonecode.replace(/^\+/, "")}`])
597
+ );
598
+ }
599
+ printUsageFooter(usage, flags);
600
+ });
538
601
  search3.argument("[query]", "Search term to match country names").action(async (query, options, cmd) => {
539
602
  if (!query) return;
540
- const globalOpts = cmd.optsWithGlobals();
541
- const flags = {
542
- json: globalOpts.json ?? false,
543
- quiet: globalOpts.quiet ?? false,
544
- noFooter: globalOpts.footer === false
545
- };
603
+ const flags = resolveFlags(cmd);
546
604
  const spinner = await createSpinner("Searching...", flags);
547
605
  const { data, usage } = await get("/countries");
548
606
  spinner.stop();
@@ -553,15 +611,17 @@ function registerSearchCommands(program2) {
553
611
  } else if (matches.length === 0) {
554
612
  console.log(chalk5.yellow(`No countries matching "${query}".`));
555
613
  } else {
556
- const rows = matches.map((c) => [
557
- c.iso2,
558
- c.iso3,
559
- c.name,
560
- c.capital || "",
561
- c.phonecode ? `+${c.phonecode.replace(/^\+/, "")}` : "",
562
- c.currency || ""
563
- ]);
564
- printTable(["ISO2", "ISO3", "Name", "Capital", "Phone", "Currency"], rows);
614
+ printTable(
615
+ ["ISO2", "ISO3", "Name", "Capital", "Phone", "Currency"],
616
+ matches.map((c) => [
617
+ c.iso2,
618
+ c.iso3,
619
+ c.name,
620
+ c.capital || "",
621
+ c.phonecode ? `+${c.phonecode.replace(/^\+/, "")}` : "",
622
+ c.currency || ""
623
+ ])
624
+ );
565
625
  }
566
626
  if (!flags.json) {
567
627
  process.stderr.write(
@@ -689,6 +749,34 @@ function registerGetCommands(program2) {
689
749
  }
690
750
  printUsageFooter(usage, flags);
691
751
  });
752
+ getCmd.command("city <country_iso2> <state_iso2> <city_id>").description("Get detailed city information by ID").action(async (countryIso2, stateIso2, cityId, options, cmd) => {
753
+ const globalOpts = cmd.optsWithGlobals();
754
+ const flags = {
755
+ json: globalOpts.json ?? false,
756
+ quiet: globalOpts.quiet ?? false,
757
+ noFooter: globalOpts.footer === false
758
+ };
759
+ const countryCode = countryIso2.toUpperCase();
760
+ const stateCode = stateIso2.toUpperCase();
761
+ const spinner = await createSpinner(`Fetching city ${cityId}...`, flags);
762
+ const { data, usage } = await get(
763
+ `/countries/${countryCode}/states/${stateCode}/cities/${cityId}`
764
+ );
765
+ spinner.stop();
766
+ if (flags.json) {
767
+ printJson(data);
768
+ } else {
769
+ const city = data;
770
+ printDetail("City:", city.name);
771
+ printDetail("ID:", String(city.id));
772
+ printDetail("State:", city.state_code || stateCode);
773
+ printDetail("Country:", city.country_code || countryCode);
774
+ if (city.latitude && city.longitude) {
775
+ printDetail("Coordinates:", `${formatCoord(city.latitude)}, ${formatCoord(city.longitude)}`);
776
+ }
777
+ }
778
+ printUsageFooter(usage, flags);
779
+ });
692
780
  }
693
781
 
694
782
  // src/commands/usage.ts
@@ -1317,7 +1405,7 @@ async function promptCountry2(countries) {
1317
1405
  }
1318
1406
  });
1319
1407
  }
1320
- async function promptState3(states) {
1408
+ async function promptState2(states) {
1321
1409
  return search2({
1322
1410
  message: "Select a state",
1323
1411
  source: (input) => {
@@ -1385,7 +1473,7 @@ async function runExploreSession(flags) {
1385
1473
  stderr(`No states found for ${countryIso}.`);
1386
1474
  return latestUsage;
1387
1475
  }
1388
- const stateIso = await promptState3(states);
1476
+ const stateIso = await promptState2(states);
1389
1477
  const selectedState = states.find((s) => s.iso2 === stateIso);
1390
1478
  const stateName = selectedState?.name ?? stateIso;
1391
1479
  let running = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@countrystatecity/cli",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Official CLI for the Country State City API - search, explore, and generate code from geographic data",
5
5
  "files": [
6
6
  "dist",