@countrystatecity/cli 0.1.4 → 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 +27 -1
  2. package/dist/index.js +194 -109
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -55,18 +55,40 @@ csc search countries
55
55
  csc search countries --filter "united"
56
56
  csc search countries --json
57
57
 
58
+ # List all states globally
59
+ csc search states
60
+
58
61
  # List states for a country
59
62
  csc search states --country IN
60
63
  csc search states -c US --filter "new"
61
64
 
65
+ # List all cities globally
66
+ csc search cities
67
+
62
68
  # List all cities for a country
63
69
  csc search cities --country IN
64
- csc search cities --country IN --json
65
70
 
66
71
  # List cities for a specific state
67
72
  csc search cities --country IN --state MH
68
73
  csc search cities -c US -s CA --json
69
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
+
70
92
  # Global search (matches country names)
71
93
  csc search india
72
94
  ```
@@ -83,6 +105,10 @@ csc get country # Interactive — prompts to pick a country (TTY only)
83
105
  csc get state IN MH
84
106
  csc get state IN MH --json
85
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
86
112
  ```
87
113
 
88
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,40 +436,26 @@ 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
- search3.command("states").description("List states for a country").option("-c, --country <iso2>", "Country ISO2 code").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
- };
459
- let code = options.country?.toUpperCase();
460
- if (!code) {
461
- if (isTTY()) {
462
- const countrySpinner = await createSpinner("Loading countries...", flags);
463
- const { data: allCountries } = await get("/countries");
464
- countrySpinner.stop();
465
- code = await promptCountry(allCountries);
466
- } else {
467
- process.stderr.write(chalk5.red("Country code required. Use --country IN\n"));
468
- process.exit(1);
469
- return;
470
- }
471
- }
472
- const spinner = await createSpinner(`Fetching states for ${code}...`, flags);
473
- const { data, usage } = await get(`/countries/${code}/states`);
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) => {
454
+ const flags = resolveFlags(cmd);
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);
474
459
  spinner.stop();
475
460
  let states = data;
476
461
  if (options.filter) {
@@ -480,72 +465,142 @@ function registerSearchCommands(program2) {
480
465
  if (flags.json) {
481
466
  printJson(states);
482
467
  } else {
483
- const rows = states.map((s) => [
484
- String(s.id),
485
- s.name,
486
- s.iso2 || "",
487
- s.type || ""
488
- ]);
489
- printTable(["ID", "Name", "ISO2", "Type"], rows);
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
+ );
490
474
  }
491
475
  printUsageFooter(usage, flags);
492
476
  });
493
- 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(
494
- async (options, cmd) => {
495
- const globalOpts = cmd.optsWithGlobals();
496
- const flags = {
497
- json: globalOpts.json ?? false,
498
- quiet: globalOpts.quiet ?? false,
499
- noFooter: globalOpts.footer === false
500
- };
501
- let countryCode = options.country?.toUpperCase();
502
- if (!countryCode) {
503
- if (isTTY()) {
504
- const countrySpinner = await createSpinner("Loading countries...", flags);
505
- const { data: allCountries } = await get("/countries");
506
- countrySpinner.stop();
507
- countryCode = await promptCountry(allCountries);
508
- } else {
509
- process.stderr.write(chalk5.red("Country code required. Use --country IN\n"));
510
- process.exit(1);
511
- return;
512
- }
513
- }
514
- const stateCode = options.state?.toUpperCase();
515
- let endpoint;
516
- let spinnerText;
517
- if (stateCode) {
518
- endpoint = `/countries/${countryCode}/states/${stateCode}/cities`;
519
- spinnerText = `Fetching cities for ${countryCode}/${stateCode}...`;
520
- } else {
521
- endpoint = `/countries/${countryCode}/cities`;
522
- spinnerText = `Fetching all cities for ${countryCode}...`;
523
- }
524
- const spinner = await createSpinner(spinnerText, flags);
525
- const { data, usage } = await get(endpoint);
526
- spinner.stop();
527
- let cities = data;
528
- if (options.filter) {
529
- const term = options.filter.toLowerCase();
530
- cities = cities.filter((c) => c.name.toLowerCase().includes(term));
531
- }
532
- if (flags.json) {
533
- printJson(cities);
534
- } else {
535
- const rows = cities.map((c) => [String(c.id), c.name]);
536
- printTable(["ID", "Name"], rows);
537
- }
538
- printUsageFooter(usage, flags);
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();
481
+ let endpoint;
482
+ let spinnerText;
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}...`;
489
+ } else {
490
+ endpoint = "/cities";
491
+ spinnerText = "Fetching all cities...";
539
492
  }
540
- );
493
+ const spinner = await createSpinner(spinnerText, flags);
494
+ const { data, usage } = await get(endpoint);
495
+ spinner.stop();
496
+ let cities = data;
497
+ if (options.filter) {
498
+ const term = options.filter.toLowerCase();
499
+ cities = cities.filter((c) => c.name.toLowerCase().includes(term));
500
+ }
501
+ if (flags.json) {
502
+ printJson(cities);
503
+ } else {
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
+ );
511
+ }
512
+ printUsageFooter(usage, flags);
513
+ });
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));
523
+ }
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
+ });
541
601
  search3.argument("[query]", "Search term to match country names").action(async (query, options, cmd) => {
542
602
  if (!query) return;
543
- const globalOpts = cmd.optsWithGlobals();
544
- const flags = {
545
- json: globalOpts.json ?? false,
546
- quiet: globalOpts.quiet ?? false,
547
- noFooter: globalOpts.footer === false
548
- };
603
+ const flags = resolveFlags(cmd);
549
604
  const spinner = await createSpinner("Searching...", flags);
550
605
  const { data, usage } = await get("/countries");
551
606
  spinner.stop();
@@ -556,15 +611,17 @@ function registerSearchCommands(program2) {
556
611
  } else if (matches.length === 0) {
557
612
  console.log(chalk5.yellow(`No countries matching "${query}".`));
558
613
  } else {
559
- const rows = matches.map((c) => [
560
- c.iso2,
561
- c.iso3,
562
- c.name,
563
- c.capital || "",
564
- c.phonecode ? `+${c.phonecode.replace(/^\+/, "")}` : "",
565
- c.currency || ""
566
- ]);
567
- 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
+ );
568
625
  }
569
626
  if (!flags.json) {
570
627
  process.stderr.write(
@@ -692,6 +749,34 @@ function registerGetCommands(program2) {
692
749
  }
693
750
  printUsageFooter(usage, flags);
694
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
+ });
695
780
  }
696
781
 
697
782
  // src/commands/usage.ts
@@ -1320,7 +1405,7 @@ async function promptCountry2(countries) {
1320
1405
  }
1321
1406
  });
1322
1407
  }
1323
- async function promptState3(states) {
1408
+ async function promptState2(states) {
1324
1409
  return search2({
1325
1410
  message: "Select a state",
1326
1411
  source: (input) => {
@@ -1388,7 +1473,7 @@ async function runExploreSession(flags) {
1388
1473
  stderr(`No states found for ${countryIso}.`);
1389
1474
  return latestUsage;
1390
1475
  }
1391
- const stateIso = await promptState3(states);
1476
+ const stateIso = await promptState2(states);
1392
1477
  const selectedState = states.find((s) => s.iso2 === stateIso);
1393
1478
  const stateName = selectedState?.name ?? stateIso;
1394
1479
  let running = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@countrystatecity/cli",
3
- "version": "0.1.4",
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",