@countrystatecity/cli 0.1.5 → 0.1.7

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 +4 -1
  2. package/dist/index.js +98 -101
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -64,12 +64,15 @@ csc search states -c US --filter "new"
64
64
 
65
65
  # List all cities for a country
66
66
  csc search cities --country IN
67
- csc search cities --country IN --json
68
67
 
69
68
  # List cities for a specific state
70
69
  csc search cities --country IN --state MH
71
70
  csc search cities -c US -s CA --json
72
71
 
72
+ # List all world regions (requires Starter plan+)
73
+ csc search regions
74
+ csc search regions --filter "asia"
75
+
73
76
  # Global search (matches country names)
74
77
  csc search india
75
78
  ```
package/dist/index.js CHANGED
@@ -74,6 +74,11 @@ async function get(path) {
74
74
  console.error(chalk.dim("Run `csc auth login` to set your key."));
75
75
  process.exit(1);
76
76
  }
77
+ if (status === 403) {
78
+ console.error(chalk.red("Access denied \u2014 this endpoint requires a higher plan."));
79
+ console.error(chalk.dim("Run `csc upgrade` to view available plans."));
80
+ process.exit(1);
81
+ }
77
82
  if (status === 429) {
78
83
  console.error(chalk.red("Daily limit reached."));
79
84
  console.error(chalk.yellow("Run `csc upgrade` to increase your limits."));
@@ -417,15 +422,14 @@ function printDetail(label, value) {
417
422
  }
418
423
 
419
424
  // src/commands/search.ts
425
+ function resolveFlags(cmd) {
426
+ const g = cmd.optsWithGlobals();
427
+ return { json: g.json ?? false, quiet: g.quiet ?? false, noFooter: g.footer === false };
428
+ }
420
429
  function registerSearchCommands(program2) {
421
- const search3 = program2.command("search").description("Search countries, states, and cities");
430
+ const search3 = program2.command("search").description("Search countries, states, cities, and more");
422
431
  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
- };
432
+ const flags = resolveFlags(cmd);
429
433
  const spinner = await createSpinner("Fetching countries...", flags);
430
434
  const { data, usage } = await get("/countries");
431
435
  spinner.stop();
@@ -437,112 +441,103 @@ function registerSearchCommands(program2) {
437
441
  if (flags.json) {
438
442
  printJson(countries);
439
443
  } 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);
444
+ printTable(
445
+ ["ISO2", "ISO3", "Name", "Capital", "Phone", "Currency"],
446
+ countries.map((c) => [
447
+ c.iso2,
448
+ c.iso3,
449
+ c.name,
450
+ c.capital || "",
451
+ c.phonecode ? `+${c.phonecode.replace(/^\+/, "")}` : "",
452
+ c.currency || ""
453
+ ])
454
+ );
449
455
  }
450
456
  printUsageFooter(usage, flags);
451
457
  });
452
458
  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
- };
459
+ const flags = resolveFlags(cmd);
459
460
  const code = options.country?.toUpperCase();
461
+ const endpoint = code ? `/countries/${code}/states` : "/states";
462
+ const spinner = await createSpinner(code ? `Fetching states for ${code}...` : "Fetching all states...", flags);
463
+ const { data, usage } = await get(endpoint);
464
+ spinner.stop();
465
+ let states = data;
466
+ if (options.filter) {
467
+ const term = options.filter.toLowerCase();
468
+ states = states.filter((s) => s.name.toLowerCase().includes(term));
469
+ }
470
+ if (flags.json) {
471
+ printJson(states);
472
+ } else {
473
+ printTable(
474
+ code ? ["ID", "Name", "ISO2", "Type"] : ["ID", "Name", "ISO2", "Type", "Country"],
475
+ states.map(
476
+ (s) => code ? [String(s.id), s.name, s.iso2 || "", s.type || ""] : [String(s.id), s.name, s.iso2 || "", s.type || "", s.country_code || ""]
477
+ )
478
+ );
479
+ }
480
+ printUsageFooter(usage, flags);
481
+ });
482
+ 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) => {
483
+ const flags = resolveFlags(cmd);
484
+ const countryCode = options.country?.toUpperCase();
485
+ const stateCode = options.state?.toUpperCase();
460
486
  let endpoint;
461
487
  let spinnerText;
462
- if (code) {
463
- endpoint = `/countries/${code}/states`;
464
- spinnerText = `Fetching states for ${code}...`;
488
+ if (countryCode && stateCode) {
489
+ endpoint = `/countries/${countryCode}/states/${stateCode}/cities`;
490
+ spinnerText = `Fetching cities for ${countryCode}/${stateCode}...`;
491
+ } else if (countryCode) {
492
+ endpoint = `/countries/${countryCode}/cities`;
493
+ spinnerText = `Fetching all cities for ${countryCode}...`;
465
494
  } else {
466
- endpoint = "/states";
467
- spinnerText = "Fetching all states...";
495
+ process.stderr.write(chalk5.red("Country code required. Use --country IN\n"));
496
+ process.stderr.write(chalk5.dim("Use --state MH to filter by state.\n"));
497
+ process.exit(1);
498
+ return;
468
499
  }
469
500
  const spinner = await createSpinner(spinnerText, flags);
470
501
  const { data, usage } = await get(endpoint);
471
502
  spinner.stop();
472
- let states = data;
503
+ let cities = data;
473
504
  if (options.filter) {
474
505
  const term = options.filter.toLowerCase();
475
- states = states.filter((s) => s.name.toLowerCase().includes(term));
506
+ cities = cities.filter((c) => c.name.toLowerCase().includes(term));
476
507
  }
477
508
  if (flags.json) {
478
- printJson(states);
509
+ printJson(cities);
479
510
  } 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);
511
+ const hasExtra = !countryCode;
512
+ printTable(
513
+ hasExtra ? ["ID", "Name", "State", "Country"] : ["ID", "Name"],
514
+ cities.map(
515
+ (c) => hasExtra ? [String(c.id), c.name, c.state_code || "", c.country_code || ""] : [String(c.id), c.name]
516
+ )
517
+ );
487
518
  }
488
519
  printUsageFooter(usage, flags);
489
520
  });
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);
521
+ search3.command("regions").description("List all world regions").option("--filter <text>", "Filter by name").action(async (options, cmd) => {
522
+ const flags = resolveFlags(cmd);
523
+ const spinner = await createSpinner("Fetching regions...", flags);
524
+ const { data, usage } = await get("/regions");
525
+ spinner.stop();
526
+ let regions = data;
527
+ if (options.filter) {
528
+ const term = options.filter.toLowerCase();
529
+ regions = regions.filter((r) => r.name.toLowerCase().includes(term));
536
530
  }
537
- );
531
+ if (flags.json) {
532
+ printJson(regions);
533
+ } else {
534
+ printTable(["ID", "Name"], regions.map((r) => [String(r.id), r.name]));
535
+ }
536
+ printUsageFooter(usage, flags);
537
+ });
538
538
  search3.argument("[query]", "Search term to match country names").action(async (query, options, cmd) => {
539
539
  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
- };
540
+ const flags = resolveFlags(cmd);
546
541
  const spinner = await createSpinner("Searching...", flags);
547
542
  const { data, usage } = await get("/countries");
548
543
  spinner.stop();
@@ -553,15 +548,17 @@ function registerSearchCommands(program2) {
553
548
  } else if (matches.length === 0) {
554
549
  console.log(chalk5.yellow(`No countries matching "${query}".`));
555
550
  } 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);
551
+ printTable(
552
+ ["ISO2", "ISO3", "Name", "Capital", "Phone", "Currency"],
553
+ matches.map((c) => [
554
+ c.iso2,
555
+ c.iso3,
556
+ c.name,
557
+ c.capital || "",
558
+ c.phonecode ? `+${c.phonecode.replace(/^\+/, "")}` : "",
559
+ c.currency || ""
560
+ ])
561
+ );
565
562
  }
566
563
  if (!flags.json) {
567
564
  process.stderr.write(
@@ -1317,7 +1314,7 @@ async function promptCountry2(countries) {
1317
1314
  }
1318
1315
  });
1319
1316
  }
1320
- async function promptState3(states) {
1317
+ async function promptState2(states) {
1321
1318
  return search2({
1322
1319
  message: "Select a state",
1323
1320
  source: (input) => {
@@ -1385,7 +1382,7 @@ async function runExploreSession(flags) {
1385
1382
  stderr(`No states found for ${countryIso}.`);
1386
1383
  return latestUsage;
1387
1384
  }
1388
- const stateIso = await promptState3(states);
1385
+ const stateIso = await promptState2(states);
1389
1386
  const selectedState = states.find((s) => s.iso2 === stateIso);
1390
1387
  const stateName = selectedState?.name ?? stateIso;
1391
1388
  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.7",
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",