@geolonia/geonicdb-cli 0.9.0 → 0.10.0

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/index.js CHANGED
@@ -25,6 +25,7 @@ function migrateV1ToV2(data) {
25
25
  const knownKeys = [
26
26
  "url",
27
27
  "service",
28
+ "tenantId",
28
29
  "token",
29
30
  "refreshToken",
30
31
  "format",
@@ -592,7 +593,7 @@ function registerConfigCommand(program2) {
592
593
  command: "geonic config get url --profile staging"
593
594
  }
594
595
  ]);
595
- const list = config.command("list").description("List all config values").action((...args) => {
596
+ const list = config.command("list").description("List all config values for the active (or specified) profile").action((...args) => {
596
597
  const cmd = args[args.length - 1];
597
598
  const profile = cmd.optsWithGlobals().profile;
598
599
  const all = loadConfig(profile);
@@ -606,9 +607,13 @@ function registerConfigCommand(program2) {
606
607
  {
607
608
  description: "List all configuration values",
608
609
  command: "geonic config list"
610
+ },
611
+ {
612
+ description: "List config for a specific profile",
613
+ command: "geonic config list --profile staging"
609
614
  }
610
615
  ]);
611
- const del = config.command("delete").description("Delete a config value").argument("<key>", "Configuration key").action((...args) => {
616
+ const del = config.command("delete").description("Remove a config key from the active (or specified) profile").argument("<key>", "Configuration key").action((...args) => {
612
617
  const cmd = args[args.length - 1];
613
618
  const key = args[0];
614
619
  const profile = cmd.optsWithGlobals().profile;
@@ -617,8 +622,16 @@ function registerConfigCommand(program2) {
617
622
  });
618
623
  addExamples(del, [
619
624
  {
620
- description: "Delete a config value",
625
+ description: "Remove the saved server URL",
621
626
  command: "geonic config delete url"
627
+ },
628
+ {
629
+ description: "Clear the saved authentication token",
630
+ command: "geonic config delete token"
631
+ },
632
+ {
633
+ description: "Remove API key from a specific profile",
634
+ command: "geonic config delete apiKey --profile staging"
622
635
  }
623
636
  ]);
624
637
  }
@@ -673,6 +686,7 @@ var GdbClient = class _GdbClient {
673
686
  clientId;
674
687
  clientSecret;
675
688
  onTokenRefresh;
689
+ onBeforeRefresh;
676
690
  verbose;
677
691
  dryRun;
678
692
  refreshPromise;
@@ -685,6 +699,7 @@ var GdbClient = class _GdbClient {
685
699
  this.clientId = options.clientId;
686
700
  this.clientSecret = options.clientSecret;
687
701
  this.onTokenRefresh = options.onTokenRefresh;
702
+ this.onBeforeRefresh = options.onBeforeRefresh;
688
703
  this.verbose = options.verbose ?? false;
689
704
  this.dryRun = options.dryRun ?? false;
690
705
  }
@@ -813,6 +828,17 @@ var GdbClient = class _GdbClient {
813
828
  }
814
829
  }
815
830
  async doRefresh() {
831
+ if (this.onBeforeRefresh) {
832
+ const latest = this.onBeforeRefresh();
833
+ if (latest.token && latest.token !== this.token) {
834
+ this.token = latest.token;
835
+ if (latest.refreshToken) this.refreshToken = latest.refreshToken;
836
+ return true;
837
+ }
838
+ if (latest.refreshToken) {
839
+ this.refreshToken = latest.refreshToken;
840
+ }
841
+ }
816
842
  if (this.refreshToken) {
817
843
  try {
818
844
  const url = this.buildUrl("/auth/refresh");
@@ -994,6 +1020,10 @@ function createClient(cmd) {
994
1020
  if (refreshToken) cfg.refreshToken = refreshToken;
995
1021
  saveConfig(cfg, opts.profile);
996
1022
  },
1023
+ onBeforeRefresh: usingCliToken ? void 0 : () => {
1024
+ const cfg = loadConfig(opts.profile);
1025
+ return { token: cfg.token, refreshToken: cfg.refreshToken };
1026
+ },
997
1027
  verbose: opts.verbose,
998
1028
  dryRun: opts.dryRun
999
1029
  });
@@ -1141,7 +1171,8 @@ async function promptTenantSelection(tenants, currentTenantId) {
1141
1171
  const t = tenants[i];
1142
1172
  const current = t.tenantId === currentTenantId ? " \u2190 current" : "";
1143
1173
  const marker = t.tenantId === currentTenantId ? " *" : " ";
1144
- console.log(`${marker} ${i + 1}) ${t.tenantId} (${t.role})${current}`);
1174
+ const label = t.name ? `${t.name} (${t.tenantId})` : t.tenantId;
1175
+ console.log(`${marker} ${i + 1}) ${label} [${t.role}]${current}`);
1145
1176
  }
1146
1177
  for (; ; ) {
1147
1178
  const answer = await rl.question("\nSelect tenant number (Enter to keep current): ");
@@ -1328,6 +1359,10 @@ function addMeOAuthClientsSubcommand(me) {
1328
1359
  {
1329
1360
  description: "List your OAuth clients",
1330
1361
  command: "geonic me oauth-clients list"
1362
+ },
1363
+ {
1364
+ description: "List in table format for a quick overview",
1365
+ command: "geonic me oauth-clients list --format table"
1331
1366
  }
1332
1367
  ]);
1333
1368
  const create = oauthClients.command("create [json]").description("Create a new OAuth client").option("--name <name>", "Client name").option("--policy <policyId>", "Policy ID to attach").option("--save", "Save credentials to config for automatic re-authentication").action(
@@ -1448,7 +1483,7 @@ function addMeOAuthClientsSubcommand(me) {
1448
1483
  command: "geonic me oauth-clients update <client-id> --inactive"
1449
1484
  }
1450
1485
  ]);
1451
- const del = oauthClients.command("delete <id>").description("Delete an OAuth client").action(
1486
+ const del = oauthClients.command("delete <id>").description("Delete an OAuth client and revoke its credentials").action(
1452
1487
  withErrorHandler(async (id, _opts, cmd) => {
1453
1488
  const client = createClient(cmd);
1454
1489
  await client.rawRequest(
@@ -1460,11 +1495,15 @@ function addMeOAuthClientsSubcommand(me) {
1460
1495
  );
1461
1496
  addExamples(del, [
1462
1497
  {
1463
- description: "Delete an OAuth client",
1498
+ description: "Delete an OAuth client by ID",
1464
1499
  command: "geonic me oauth-clients delete <client-id>"
1500
+ },
1501
+ {
1502
+ description: "Revoke a compromised client",
1503
+ command: "geonic me oauth-clients delete abc123-def456"
1465
1504
  }
1466
1505
  ]);
1467
- const regenerateSecret = oauthClients.command("regenerate-secret <clientId>").description("Regenerate the client secret of an OAuth client").action(
1506
+ const regenerateSecret = oauthClients.command("regenerate-secret <clientId>").description("Regenerate the client secret \u2014 the old secret is immediately invalidated").action(
1468
1507
  withErrorHandler(async (clientId, _opts, cmd) => {
1469
1508
  const client = createClient(cmd);
1470
1509
  const format = getFormat(cmd);
@@ -1481,6 +1520,10 @@ function addMeOAuthClientsSubcommand(me) {
1481
1520
  {
1482
1521
  description: "Regenerate client secret",
1483
1522
  command: "geonic me oauth-clients regenerate-secret <client-id>"
1523
+ },
1524
+ {
1525
+ description: "Rotate secret after a security incident",
1526
+ command: "geonic me oauth-clients regenerate-secret abc123-def456"
1484
1527
  }
1485
1528
  ]);
1486
1529
  }
@@ -1541,6 +1584,10 @@ function addMeApiKeysSubcommand(me) {
1541
1584
  {
1542
1585
  description: "List your API keys",
1543
1586
  command: "geonic me api-keys list"
1587
+ },
1588
+ {
1589
+ description: "List in table format for a quick overview",
1590
+ command: "geonic me api-keys list --format table"
1544
1591
  }
1545
1592
  ]);
1546
1593
  const create = apiKeys.command("create [json]").description("Create a new API key").option("--name <name>", "Key name").option("--policy <policyId>", "Policy ID to attach").option("--origins <origins>", "Allowed origins (comma-separated)").option("--rate-limit <n>", "Rate limit per minute").option("--dpop-required", "Require DPoP token binding").option("--save", "Save the API key to config for automatic use").action(
@@ -1721,7 +1768,7 @@ function addMeApiKeysSubcommand(me) {
1721
1768
  command: `geonic me api-keys update <key-id> '{"name":"new-name","rateLimit":{"perMinute":60}}'`
1722
1769
  }
1723
1770
  ]);
1724
- const del = apiKeys.command("delete <keyId>").description("Delete an API key").action(
1771
+ const del = apiKeys.command("delete <keyId>").description("Delete an API key \u2014 immediately revokes access for any client using it").action(
1725
1772
  withErrorHandler(async (keyId, _opts, cmd) => {
1726
1773
  const client = createClient(cmd);
1727
1774
  await client.rawRequest(
@@ -1733,8 +1780,12 @@ function addMeApiKeysSubcommand(me) {
1733
1780
  );
1734
1781
  addExamples(del, [
1735
1782
  {
1736
- description: "Delete an API key",
1783
+ description: "Delete an API key by ID",
1737
1784
  command: "geonic me api-keys delete <key-id>"
1785
+ },
1786
+ {
1787
+ description: "Revoke a leaked or unused key",
1788
+ command: "geonic me api-keys delete abc123-def456"
1738
1789
  }
1739
1790
  ]);
1740
1791
  }
@@ -1754,9 +1805,13 @@ function addMePoliciesSubcommand(me) {
1754
1805
  {
1755
1806
  description: "List your personal policies",
1756
1807
  command: "geonic me policies list"
1808
+ },
1809
+ {
1810
+ description: "List in table format for a quick overview",
1811
+ command: "geonic me policies list --format table"
1757
1812
  }
1758
1813
  ]);
1759
- const get = policies.command("get <policyId>").description("Get a personal policy by ID").action(
1814
+ const get = policies.command("get <policyId>").description("Get a personal policy by ID to inspect its XACML rules and target resources").action(
1760
1815
  withErrorHandler(async (policyId, _opts, cmd) => {
1761
1816
  const client = createClient(cmd);
1762
1817
  const format = getFormat(cmd);
@@ -1771,9 +1826,13 @@ function addMePoliciesSubcommand(me) {
1771
1826
  {
1772
1827
  description: "Get a personal policy by ID",
1773
1828
  command: "geonic me policies get <policy-id>"
1829
+ },
1830
+ {
1831
+ description: "Inspect policy rules and permitted actions",
1832
+ command: "geonic me policies get my-readonly --format table"
1774
1833
  }
1775
1834
  ]);
1776
- const create = policies.command("create [json]").description(
1835
+ const create = policies.command("create [json]").summary("Create a personal XACML policy").description(
1777
1836
  `Create a personal XACML policy
1778
1837
 
1779
1838
  Constraints (enforced server-side):
@@ -1864,7 +1923,7 @@ Example \u2014 GET-only policy for /v2/**:
1864
1923
  command: "geonic me policies update <policy-id> @patch.json"
1865
1924
  }
1866
1925
  ]);
1867
- const del = policies.command("delete <policyId>").description("Delete a personal policy").action(
1926
+ const del = policies.command("delete <policyId>").description("Delete a personal policy \u2014 any API key or OAuth client bound to it loses access granted by this policy").action(
1868
1927
  withErrorHandler(async (policyId, _opts, cmd) => {
1869
1928
  const client = createClient(cmd);
1870
1929
  await client.rawRequest(
@@ -1876,8 +1935,12 @@ Example \u2014 GET-only policy for /v2/**:
1876
1935
  );
1877
1936
  addExamples(del, [
1878
1937
  {
1879
- description: "Delete a personal policy",
1938
+ description: "Delete a personal policy by ID",
1880
1939
  command: "geonic me policies delete <policy-id>"
1940
+ },
1941
+ {
1942
+ description: "Remove a policy (unbind from API keys/OAuth clients first)",
1943
+ command: "geonic me policies delete my-readonly"
1881
1944
  }
1882
1945
  ]);
1883
1946
  }
@@ -1935,8 +1998,10 @@ function createLoginCommand() {
1935
1998
  const password = await promptPassword();
1936
1999
  const client = createClient(cmd);
1937
2000
  const body = { email, password };
1938
- if (loginOpts.tenantId) {
1939
- body.tenantId = loginOpts.tenantId;
2001
+ const requestTenantId = loginOpts.tenantId;
2002
+ const serviceFlag = globalOpts.service;
2003
+ if (requestTenantId) {
2004
+ body.tenantId = requestTenantId;
1940
2005
  }
1941
2006
  const response = await client.rawRequest("POST", "/auth/login", {
1942
2007
  body,
@@ -1951,11 +2016,27 @@ function createLoginCommand() {
1951
2016
  }
1952
2017
  const availableTenants = data.availableTenants;
1953
2018
  let finalTenantId = data.tenantId;
1954
- if (availableTenants && availableTenants.length > 1 && !loginOpts.tenantId) {
1955
- const selectedTenantId = await promptTenantSelection(availableTenants, finalTenantId);
1956
- if (selectedTenantId && selectedTenantId !== finalTenantId) {
2019
+ if (availableTenants && availableTenants.length > 1 && !requestTenantId) {
2020
+ let resolvedTenantId;
2021
+ if (serviceFlag) {
2022
+ const match = availableTenants.find(
2023
+ (t) => t.name === serviceFlag || t.tenantId === serviceFlag
2024
+ );
2025
+ if (match) {
2026
+ resolvedTenantId = match.tenantId;
2027
+ } else {
2028
+ printError(
2029
+ `Tenant "${serviceFlag}" not found. Available: ${availableTenants.map((t) => t.name ?? t.tenantId).join(", ")}`
2030
+ );
2031
+ process.exit(1);
2032
+ }
2033
+ }
2034
+ if (!resolvedTenantId) {
2035
+ resolvedTenantId = await promptTenantSelection(availableTenants, finalTenantId);
2036
+ }
2037
+ if (resolvedTenantId && resolvedTenantId !== finalTenantId) {
1957
2038
  const reloginResponse = await client.rawRequest("POST", "/auth/login", {
1958
- body: { email, password, tenantId: selectedTenantId },
2039
+ body: { email, password, tenantId: resolvedTenantId },
1959
2040
  skipTenantHeader: true
1960
2041
  });
1961
2042
  const reloginData = reloginResponse.data;
@@ -1966,7 +2047,7 @@ function createLoginCommand() {
1966
2047
  }
1967
2048
  token = newToken;
1968
2049
  refreshToken = reloginData.refreshToken;
1969
- finalTenantId = selectedTenantId;
2050
+ finalTenantId = resolvedTenantId;
1970
2051
  }
1971
2052
  }
1972
2053
  const config = loadConfig(globalOpts.profile);
@@ -1978,16 +2059,28 @@ function createLoginCommand() {
1978
2059
  }
1979
2060
  if (finalTenantId) {
1980
2061
  config.service = finalTenantId;
2062
+ config.tenantId = finalTenantId;
1981
2063
  } else {
1982
2064
  delete config.service;
2065
+ delete config.tenantId;
2066
+ }
2067
+ if (availableTenants && availableTenants.length > 0) {
2068
+ config.availableTenants = availableTenants.map((t) => ({
2069
+ tenantId: t.tenantId,
2070
+ ...t.name ? { name: t.name } : {},
2071
+ role: t.role
2072
+ }));
2073
+ } else {
2074
+ delete config.availableTenants;
1983
2075
  }
1984
2076
  saveConfig(config, globalOpts.profile);
1985
- printSuccess("Login successful. Token saved to config.");
2077
+ const tenantLabel = finalTenantId ? ` (tenant: ${availableTenants?.find((t) => t.tenantId === finalTenantId)?.name ?? finalTenantId})` : "";
2078
+ printSuccess(`Login successful${tenantLabel}. Token saved to config.`);
1986
2079
  })
1987
2080
  );
1988
2081
  }
1989
2082
  function createLogoutCommand() {
1990
- return new Command("logout").description("Clear saved authentication token").action(
2083
+ return new Command("logout").description("Clear saved authentication token and notify the server").action(
1991
2084
  withErrorHandler(async (...args) => {
1992
2085
  const cmd = args[args.length - 1];
1993
2086
  const globalOpts = resolveOptions(cmd);
@@ -2060,7 +2153,7 @@ async function fetchNonce(baseUrl, apiKey) {
2060
2153
  return await response.json();
2061
2154
  }
2062
2155
  function createNonceCommand() {
2063
- return new Command("nonce").description("Get a nonce and PoW challenge for API key authentication").option("--api-key <key>", "API key to get nonce for").action(
2156
+ return new Command("nonce").description("Request a nonce and Proof-of-Work challenge (step 1 of API key to JWT exchange)").option("--api-key <key>", "API key to get nonce for").action(
2064
2157
  withErrorHandler(async (...args) => {
2065
2158
  const cmd = args[args.length - 1];
2066
2159
  const nonceOpts = cmd.opts();
@@ -2102,7 +2195,7 @@ function solvePoW(challenge, difficulty) {
2102
2195
  throw new Error(`PoW could not be solved within ${MAX_POW_ITERATIONS} iterations`);
2103
2196
  }
2104
2197
  function createTokenExchangeCommand() {
2105
- return new Command("token-exchange").description("Exchange API key for a session JWT via nonce + PoW").option("--api-key <key>", "API key to exchange").option("--save", "Save the obtained token to profile config").action(
2198
+ return new Command("token-exchange").description("Exchange an API key for a session JWT (fetches nonce, solves PoW, returns token)").option("--api-key <key>", "API key to exchange").option("--save", "Save the obtained token to profile config").action(
2106
2199
  withErrorHandler(async (...args) => {
2107
2200
  const cmd = args[args.length - 1];
2108
2201
  const exchangeOpts = cmd.opts();
@@ -2166,8 +2259,12 @@ function registerAuthCommands(program2) {
2166
2259
  command: "geonic auth login --client-credentials --client-id MY_ID --client-secret MY_SECRET"
2167
2260
  },
2168
2261
  {
2169
- description: "Login to a specific tenant",
2262
+ description: "Login to a specific tenant by ID",
2170
2263
  command: "geonic auth login --tenant-id my-tenant"
2264
+ },
2265
+ {
2266
+ description: "Login to a tenant by name",
2267
+ command: "geonic auth login -s demo_smartcity"
2171
2268
  }
2172
2269
  ]);
2173
2270
  auth.addCommand(login);
@@ -2176,6 +2273,10 @@ function registerAuthCommands(program2) {
2176
2273
  {
2177
2274
  description: "Clear saved authentication token",
2178
2275
  command: "geonic auth logout"
2276
+ },
2277
+ {
2278
+ description: "Logout from a specific profile",
2279
+ command: "geonic auth logout --profile staging"
2179
2280
  }
2180
2281
  ]);
2181
2282
  auth.addCommand(logout);
@@ -2184,14 +2285,26 @@ function registerAuthCommands(program2) {
2184
2285
  {
2185
2286
  description: "Get a nonce for API key authentication",
2186
2287
  command: "geonic auth nonce --api-key gdb_abcdef..."
2288
+ },
2289
+ {
2290
+ description: "Use a pre-configured API key",
2291
+ command: "geonic auth nonce"
2187
2292
  }
2188
2293
  ]);
2189
2294
  auth.addCommand(nonce);
2190
2295
  const tokenExchange = createTokenExchangeCommand();
2191
2296
  addExamples(tokenExchange, [
2192
2297
  {
2193
- description: "Exchange API key for a JWT and save it",
2298
+ description: "Exchange API key for a JWT and save it to the active profile",
2194
2299
  command: "geonic auth token-exchange --api-key gdb_abcdef... --save"
2300
+ },
2301
+ {
2302
+ description: "Exchange and print the JWT without saving",
2303
+ command: "geonic auth token-exchange --api-key gdb_abcdef..."
2304
+ },
2305
+ {
2306
+ description: "Use a pre-configured API key and save the resulting token",
2307
+ command: "geonic auth token-exchange --save"
2195
2308
  }
2196
2309
  ]);
2197
2310
  auth.addCommand(tokenExchange);
@@ -2228,8 +2341,8 @@ function registerAuthCommands(program2) {
2228
2341
 
2229
2342
  // src/commands/profile.ts
2230
2343
  function registerProfileCommands(program2) {
2231
- const profile = program2.command("profile").description("Manage connection profiles");
2232
- const list = profile.command("list").description("List all profiles").action(() => {
2344
+ const profile = program2.command("profile").description("Manage connection profiles (each profile stores its own URL, token, and tenant)");
2345
+ const list = profile.command("list").description("List all profiles (active profile marked with *)").action(() => {
2233
2346
  const profiles = listProfiles();
2234
2347
  for (const p of profiles) {
2235
2348
  const marker = p.active ? " *" : "";
@@ -2242,22 +2355,57 @@ function registerProfileCommands(program2) {
2242
2355
  command: "geonic profile list"
2243
2356
  }
2244
2357
  ]);
2245
- const use = profile.command("use <name>").description("Switch active profile").action((name) => {
2358
+ const use = profile.command("use <name>").description("Switch active profile (auto-refreshes expired tokens)").action(async (name) => {
2246
2359
  try {
2247
2360
  setCurrentProfile(name);
2248
- printSuccess(`Switched to profile "${name}".`);
2249
2361
  } catch (err) {
2250
2362
  printError(err.message);
2251
2363
  process.exit(1);
2252
2364
  }
2365
+ const config = loadConfig(name);
2366
+ const tenantLabel = config.tenantId ? ` (tenant: ${config.availableTenants?.find((t) => t.tenantId === config.tenantId)?.name ?? config.tenantId})` : "";
2367
+ if (config.token && config.refreshToken && config.url) {
2368
+ const status = getTokenStatus(config.token);
2369
+ if (status.isExpired || status.isExpiringSoon) {
2370
+ try {
2371
+ const baseUrl = validateUrl(config.url);
2372
+ const url = new URL("/auth/refresh", baseUrl).toString();
2373
+ const response = await fetch(url, {
2374
+ method: "POST",
2375
+ headers: { "Content-Type": "application/json" },
2376
+ body: JSON.stringify({ refreshToken: config.refreshToken })
2377
+ });
2378
+ if (response.ok) {
2379
+ const data = await response.json();
2380
+ const newToken = data.accessToken ?? data.token;
2381
+ const newRefreshToken = data.refreshToken;
2382
+ if (newToken) {
2383
+ config.token = newToken;
2384
+ if (newRefreshToken) config.refreshToken = newRefreshToken;
2385
+ saveConfig(config, name);
2386
+ printSuccess(`Switched to profile "${name}"${tenantLabel}. Token refreshed.`);
2387
+ return;
2388
+ }
2389
+ }
2390
+ printWarning("Token refresh failed. You may need to re-login.");
2391
+ } catch {
2392
+ printWarning("Token refresh failed. You may need to re-login.");
2393
+ }
2394
+ }
2395
+ }
2396
+ printSuccess(`Switched to profile "${name}"${tenantLabel}.`);
2253
2397
  });
2254
2398
  addExamples(use, [
2255
2399
  {
2256
2400
  description: "Switch to staging profile",
2257
2401
  command: "geonic profile use staging"
2402
+ },
2403
+ {
2404
+ description: "Switch to production profile",
2405
+ command: "geonic profile use production"
2258
2406
  }
2259
2407
  ]);
2260
- const profileCreate = profile.command("create <name>").description("Create a new profile").action((name) => {
2408
+ const profileCreate = profile.command("create <name>").description("Create a new named profile for a separate environment or tenant").action((name) => {
2261
2409
  try {
2262
2410
  createProfile(name);
2263
2411
  printSuccess(`Profile "${name}" created.`);
@@ -2270,9 +2418,13 @@ function registerProfileCommands(program2) {
2270
2418
  {
2271
2419
  description: "Create a new profile for staging",
2272
2420
  command: "geonic profile create staging"
2421
+ },
2422
+ {
2423
+ description: "Create a profile for a different tenant",
2424
+ command: "geonic profile create tenant-b"
2273
2425
  }
2274
2426
  ]);
2275
- const del = profile.command("delete <name>").description("Delete a profile").action((name) => {
2427
+ const del = profile.command("delete <name>").description("Delete a profile and its stored configuration").action((name) => {
2276
2428
  try {
2277
2429
  deleteProfile(name);
2278
2430
  printSuccess(`Profile "${name}" deleted.`);
@@ -2283,11 +2435,11 @@ function registerProfileCommands(program2) {
2283
2435
  });
2284
2436
  addExamples(del, [
2285
2437
  {
2286
- description: "Delete a profile",
2438
+ description: "Delete the staging profile",
2287
2439
  command: "geonic profile delete staging"
2288
2440
  }
2289
2441
  ]);
2290
- const show = profile.command("show [name]").description("Show profile settings").action((name) => {
2442
+ const show = profile.command("show [name]").description("Show profile settings (URL, tenant, token status, etc.)").action((name) => {
2291
2443
  const profileName = name ?? getCurrentProfile();
2292
2444
  const config = loadConfig(profileName);
2293
2445
  const entries = Object.entries(config).filter(([, v]) => v !== void 0);
@@ -2298,6 +2450,13 @@ function registerProfileCommands(program2) {
2298
2450
  for (const [key, value] of entries) {
2299
2451
  if ((key === "token" || key === "refreshToken" || key === "apiKey") && typeof value === "string") {
2300
2452
  console.log(`${key}: ***`);
2453
+ } else if (key === "availableTenants" && Array.isArray(value)) {
2454
+ console.log(`${key}:`);
2455
+ for (const t of value) {
2456
+ const label = t.name ? `${t.name} (${t.tenantId})` : t.tenantId;
2457
+ const current = t.tenantId === config.tenantId ? " \u2190 current" : "";
2458
+ console.log(` - ${label} [${t.role}]${current}`);
2459
+ }
2301
2460
  } else {
2302
2461
  console.log(`${key}: ${value}`);
2303
2462
  }
@@ -2317,7 +2476,7 @@ function registerProfileCommands(program2) {
2317
2476
 
2318
2477
  // src/commands/attrs.ts
2319
2478
  function addAttrsSubcommands(attrs) {
2320
- const list = attrs.command("list").description("List all attributes of an entity").argument("<entityId>", "Entity ID").action(
2479
+ const list = attrs.command("list").description("List all attributes of an entity, returning each attribute's type, value, and metadata").argument("<entityId>", "Entity ID").action(
2321
2480
  withErrorHandler(
2322
2481
  async (entityId, _opts, cmd) => {
2323
2482
  const client = createClient(cmd);
@@ -2333,9 +2492,17 @@ function addAttrsSubcommands(attrs) {
2333
2492
  {
2334
2493
  description: "List all attributes of an entity",
2335
2494
  command: "geonic entities attrs list urn:ngsi-ld:Sensor:001"
2495
+ },
2496
+ {
2497
+ description: "List attributes in table format",
2498
+ command: "geonic entities attrs list urn:ngsi-ld:Sensor:001 --format table"
2499
+ },
2500
+ {
2501
+ description: "List attributes with keyValues output",
2502
+ command: "geonic entities attrs list urn:ngsi-ld:Building:store01 --format json"
2336
2503
  }
2337
2504
  ]);
2338
- const get = attrs.command("get").description("Get a specific attribute of an entity").argument("<entityId>", "Entity ID").argument("<attrName>", "Attribute name").action(
2505
+ const get = attrs.command("get").description("Get the value and metadata of a single attribute on an entity").argument("<entityId>", "Entity ID").argument("<attrName>", "Attribute name").action(
2339
2506
  withErrorHandler(
2340
2507
  async (entityId, attrName, _opts, cmd) => {
2341
2508
  const client = createClient(cmd);
@@ -2349,11 +2516,19 @@ function addAttrsSubcommands(attrs) {
2349
2516
  );
2350
2517
  addExamples(get, [
2351
2518
  {
2352
- description: "Get a specific attribute",
2519
+ description: "Get the temperature attribute of a sensor",
2353
2520
  command: "geonic entities attrs get urn:ngsi-ld:Sensor:001 temperature"
2521
+ },
2522
+ {
2523
+ description: "Get a Relationship attribute to see what it links to",
2524
+ command: "geonic entities attrs get urn:ngsi-ld:Building:store01 owner"
2525
+ },
2526
+ {
2527
+ description: "Get an attribute in table format",
2528
+ command: "geonic entities attrs get urn:ngsi-ld:Sensor:001 location --format table"
2354
2529
  }
2355
2530
  ]);
2356
- const add = attrs.command("add").description(
2531
+ const add = attrs.command("add").summary("Add attributes to an entity").description(
2357
2532
  'Add attributes to an entity\n\nJSON payload example:\n {"humidity": {"type": "Property", "value": 60}}'
2358
2533
  ).argument("<entityId>", "Entity ID").argument("[json]", "JSON payload (inline, @file, - for stdin, or omit for interactive/pipe)").action(
2359
2534
  withErrorHandler(
@@ -2382,7 +2557,7 @@ function addAttrsSubcommands(attrs) {
2382
2557
  command: "cat attrs.json | geonic entities attrs add urn:ngsi-ld:Sensor:001"
2383
2558
  }
2384
2559
  ]);
2385
- const attrUpdate = attrs.command("update").description(
2560
+ const attrUpdate = attrs.command("update").summary("Update a specific attribute of an entity").description(
2386
2561
  'Update a specific attribute of an entity\n\nJSON payload example:\n {"type": "Property", "value": 25}'
2387
2562
  ).argument("<entityId>", "Entity ID").argument("<attrName>", "Attribute name").argument("[json]", "JSON payload (inline, @file, - for stdin, or omit for interactive/pipe)").action(
2388
2563
  withErrorHandler(
@@ -2407,7 +2582,7 @@ function addAttrsSubcommands(attrs) {
2407
2582
  command: "geonic entities attrs update urn:ngsi-ld:Sensor:001 temperature @attr.json"
2408
2583
  }
2409
2584
  ]);
2410
- const del = attrs.command("delete").description("Delete a specific attribute from an entity").argument("<entityId>", "Entity ID").argument("<attrName>", "Attribute name").action(
2585
+ const del = attrs.command("delete").description("Remove an attribute from an entity permanently").argument("<entityId>", "Entity ID").argument("<attrName>", "Attribute name").action(
2411
2586
  withErrorHandler(
2412
2587
  async (entityId, attrName, _opts, cmd) => {
2413
2588
  const client = createClient(cmd);
@@ -2420,8 +2595,12 @@ function addAttrsSubcommands(attrs) {
2420
2595
  );
2421
2596
  addExamples(del, [
2422
2597
  {
2423
- description: "Delete a specific attribute",
2598
+ description: "Remove the temperature attribute from a sensor",
2424
2599
  command: "geonic entities attrs delete urn:ngsi-ld:Sensor:001 temperature"
2600
+ },
2601
+ {
2602
+ description: "Remove a deprecated attribute from a building entity",
2603
+ command: "geonic entities attrs delete urn:ngsi-ld:Building:store01 legacyCode"
2425
2604
  }
2426
2605
  ]);
2427
2606
  }
@@ -2547,7 +2726,7 @@ function registerEntitiesCommand(program2) {
2547
2726
  command: "geonic entities get urn:ngsi-ld:Sensor:001 --key-values"
2548
2727
  }
2549
2728
  ]);
2550
- const create = entities.command("create").description(
2729
+ const create = entities.command("create").summary("Create a new entity").description(
2551
2730
  'Create a new entity\n\nJSON payload example:\n {\n "id": "urn:ngsi-ld:Sensor:001",\n "type": "Sensor",\n "temperature": {"type": "Property", "value": 25}\n }'
2552
2731
  ).argument("[json]", "JSON payload (inline, @file, - for stdin, or omit for interactive/pipe)").action(
2553
2732
  withErrorHandler(async (json, _opts, cmd) => {
@@ -2579,7 +2758,7 @@ function registerEntitiesCommand(program2) {
2579
2758
  command: "geonic entities create"
2580
2759
  }
2581
2760
  ]);
2582
- const update = entities.command("update").description(
2761
+ const update = entities.command("update").summary("Update attributes of an entity (PATCH)").description(
2583
2762
  'Update attributes of an entity (PATCH)\n\nJSON payload: only specified attributes are modified.\n e.g. {"temperature": {"type": "Property", "value": 30}}'
2584
2763
  ).argument("<id>", "Entity ID").argument("[json]", "JSON payload (inline, @file, - for stdin, or omit for interactive/pipe)").action(
2585
2764
  withErrorHandler(
@@ -2608,7 +2787,7 @@ function registerEntitiesCommand(program2) {
2608
2787
  command: "cat attrs.json | geonic entities update urn:ngsi-ld:Sensor:001"
2609
2788
  }
2610
2789
  ]);
2611
- const replace = entities.command("replace").description(
2790
+ const replace = entities.command("replace").summary("Replace all attributes of an entity (PUT)").description(
2612
2791
  'Replace all attributes of an entity (PUT)\n\nJSON payload: all existing attributes are replaced.\n e.g. {"temperature": {"type": "Property", "value": 20}}'
2613
2792
  ).argument("<id>", "Entity ID").argument("[json]", "JSON payload (inline, @file, - for stdin, or omit for interactive/pipe)").action(
2614
2793
  withErrorHandler(
@@ -2655,7 +2834,7 @@ function registerEntitiesCommand(program2) {
2655
2834
  command: "cat entities.json | geonic entities upsert"
2656
2835
  }
2657
2836
  ]);
2658
- const del = entities.command("delete").description("Delete an entity by ID").argument("<id>", "Entity ID").action(
2837
+ const del = entities.command("delete").description("Permanently delete an entity and all its attributes").argument("<id>", "Entity ID").action(
2659
2838
  withErrorHandler(async (id, _opts, cmd) => {
2660
2839
  const client = createClient(cmd);
2661
2840
  await client.delete(`/entities/${encodeURIComponent(id)}`);
@@ -2666,6 +2845,10 @@ function registerEntitiesCommand(program2) {
2666
2845
  {
2667
2846
  description: "Delete an entity by ID",
2668
2847
  command: "geonic entities delete urn:ngsi-ld:Sensor:001"
2848
+ },
2849
+ {
2850
+ description: "Delete with explicit service tenant",
2851
+ command: "geonic entities delete urn:ngsi-ld:Sensor:001 --service my-tenant"
2669
2852
  }
2670
2853
  ]);
2671
2854
  registerAttrsSubcommand(entities);
@@ -2674,7 +2857,7 @@ function registerEntitiesCommand(program2) {
2674
2857
  // src/commands/batch.ts
2675
2858
  function registerBatchCommand(program2) {
2676
2859
  const batch = program2.command("entityOperations").alias("batch").description("Perform batch operations on entities");
2677
- const create = batch.command("create [json]").description(
2860
+ const create = batch.command("create [json]").summary("Batch create entities").description(
2678
2861
  'Batch create entities\n\nJSON payload: an array of NGSI-LD entities.\n e.g. [{"id": "urn:ngsi-ld:Sensor:001", "type": "Sensor"}, ...]'
2679
2862
  ).action(
2680
2863
  withErrorHandler(async (json, _opts, cmd) => {
@@ -2699,7 +2882,7 @@ function registerBatchCommand(program2) {
2699
2882
  command: "cat entities.json | geonic batch create"
2700
2883
  }
2701
2884
  ]);
2702
- const upsert = batch.command("upsert [json]").description(
2885
+ const upsert = batch.command("upsert [json]").summary("Batch upsert entities").description(
2703
2886
  "Batch upsert entities\n\nJSON payload: an array of NGSI-LD entities.\nCreates entities that don't exist, updates those that do."
2704
2887
  ).action(
2705
2888
  withErrorHandler(async (json, _opts, cmd) => {
@@ -2724,7 +2907,7 @@ function registerBatchCommand(program2) {
2724
2907
  command: "cat entities.json | geonic batch upsert"
2725
2908
  }
2726
2909
  ]);
2727
- const update = batch.command("update [json]").description(
2910
+ const update = batch.command("update [json]").summary("Batch update entity attributes").description(
2728
2911
  "Batch update entity attributes\n\nJSON payload: an array of NGSI-LD entities with attributes to update.\nEach entity must include id and type; only specified attributes are modified."
2729
2912
  ).action(
2730
2913
  withErrorHandler(async (json, _opts, cmd) => {
@@ -2745,7 +2928,7 @@ function registerBatchCommand(program2) {
2745
2928
  command: "cat updates.json | geonic batch update"
2746
2929
  }
2747
2930
  ]);
2748
- const del = batch.command("delete [json]").description(
2931
+ const del = batch.command("delete [json]").summary("Batch delete entities by ID").description(
2749
2932
  'Batch delete entities by ID\n\nJSON payload: an array of entity ID strings.\n e.g. ["urn:ngsi-ld:Sensor:001","urn:ngsi-ld:Sensor:002"]'
2750
2933
  ).action(
2751
2934
  withErrorHandler(async (json, _opts, cmd) => {
@@ -2770,7 +2953,7 @@ function registerBatchCommand(program2) {
2770
2953
  command: "cat entity-ids.json | geonic batch delete"
2771
2954
  }
2772
2955
  ]);
2773
- const query = batch.command("query [json]").description(
2956
+ const query = batch.command("query [json]").summary("Query entities by posting a query payload").description(
2774
2957
  'Query entities by posting a query payload\n\nJSON payload example:\n {\n "entities": [{"type": "Sensor"}],\n "attrs": ["temperature"],\n "q": "temperature>30"\n }'
2775
2958
  ).action(
2776
2959
  withErrorHandler(async (json, _opts, cmd) => {
@@ -2795,7 +2978,7 @@ function registerBatchCommand(program2) {
2795
2978
  command: "cat query.json | geonic batch query"
2796
2979
  }
2797
2980
  ]);
2798
- const merge = batch.command("merge [json]").description(
2981
+ const merge = batch.command("merge [json]").summary("Batch merge-patch entities").description(
2799
2982
  "Batch merge-patch entities\n\nJSON payload: an array of NGSI-LD entities.\nEach entity must include id and type; attributes are merge-patched."
2800
2983
  ).action(
2801
2984
  withErrorHandler(async (json, _opts, cmd) => {
@@ -2848,7 +3031,7 @@ function registerSubscriptionsCommand(program2) {
2848
3031
  command: "geonic subscriptions list --count"
2849
3032
  }
2850
3033
  ]);
2851
- const get = subscriptions.command("get <id>").description("Get a subscription by ID").action(
3034
+ const get = subscriptions.command("get <id>").description("Get a subscription by ID to inspect its notification config, watched attributes, and status").action(
2852
3035
  withErrorHandler(async (id, _opts, cmd) => {
2853
3036
  const client = createClient(cmd);
2854
3037
  const format = getFormat(cmd);
@@ -2860,11 +3043,15 @@ function registerSubscriptionsCommand(program2) {
2860
3043
  );
2861
3044
  addExamples(get, [
2862
3045
  {
2863
- description: "Get subscription by ID",
3046
+ description: "Get subscription details by ID",
2864
3047
  command: "geonic subscriptions get urn:ngsi-ld:Subscription:001"
3048
+ },
3049
+ {
3050
+ description: "Inspect notification endpoint and status in table format",
3051
+ command: "geonic subscriptions get urn:ngsi-ld:Subscription:001 --format table"
2865
3052
  }
2866
3053
  ]);
2867
- const create = subscriptions.command("create [json]").description(
3054
+ const create = subscriptions.command("create [json]").summary("Create a subscription").description(
2868
3055
  'Create a subscription\n\nJSON payload example:\n {\n "type": "Subscription",\n "entities": [{"type": "Sensor"}],\n "watchedAttributes": ["temperature"],\n "notification": {"endpoint": {"uri": "http://localhost:3000/notify"}}\n }'
2869
3056
  ).action(
2870
3057
  withErrorHandler(async (json, _opts, cmd) => {
@@ -2894,7 +3081,7 @@ function registerSubscriptionsCommand(program2) {
2894
3081
  command: "geonic subscriptions create"
2895
3082
  }
2896
3083
  ]);
2897
- const update = subscriptions.command("update <id> [json]").description(
3084
+ const update = subscriptions.command("update <id> [json]").summary("Update a subscription").description(
2898
3085
  'Update a subscription\n\nJSON payload: only specified fields are updated.\n e.g. {"description": "Updated subscription"}'
2899
3086
  ).action(
2900
3087
  withErrorHandler(
@@ -2925,7 +3112,7 @@ function registerSubscriptionsCommand(program2) {
2925
3112
  command: "cat sub.json | geonic subscriptions update urn:ngsi-ld:Subscription:001"
2926
3113
  }
2927
3114
  ]);
2928
- const del = subscriptions.command("delete <id>").description("Delete a subscription").action(
3115
+ const del = subscriptions.command("delete <id>").description("Delete a subscription and stop its notifications").action(
2929
3116
  withErrorHandler(async (id, _opts, cmd) => {
2930
3117
  const client = createClient(cmd);
2931
3118
  await client.delete(
@@ -2936,8 +3123,12 @@ function registerSubscriptionsCommand(program2) {
2936
3123
  );
2937
3124
  addExamples(del, [
2938
3125
  {
2939
- description: "Delete a subscription",
3126
+ description: "Delete a subscription by ID",
2940
3127
  command: "geonic subscriptions delete urn:ngsi-ld:Subscription:001"
3128
+ },
3129
+ {
3130
+ description: "Stop notifications by removing the subscription (using alias)",
3131
+ command: "geonic sub delete urn:ngsi-ld:Subscription:001"
2941
3132
  }
2942
3133
  ]);
2943
3134
  }
@@ -2968,7 +3159,7 @@ function registerRegistrationsCommand(program2) {
2968
3159
  command: "geonic registrations list --limit 10"
2969
3160
  }
2970
3161
  ]);
2971
- const get = registrations.command("get <id>").description("Get a registration by ID").action(
3162
+ const get = registrations.command("get <id>").description("Get a registration by ID to inspect its federation endpoint and entity routing").action(
2972
3163
  withErrorHandler(async (id, _opts, cmd) => {
2973
3164
  const client = createClient(cmd);
2974
3165
  const format = getFormat(cmd);
@@ -2980,11 +3171,15 @@ function registerRegistrationsCommand(program2) {
2980
3171
  );
2981
3172
  addExamples(get, [
2982
3173
  {
2983
- description: "Get registration by ID",
3174
+ description: "Get registration details by ID",
2984
3175
  command: "geonic registrations get urn:ngsi-ld:ContextSourceRegistration:001"
3176
+ },
3177
+ {
3178
+ description: "Inspect federation config in table format",
3179
+ command: "geonic registrations get urn:ngsi-ld:ContextSourceRegistration:001 --format table"
2985
3180
  }
2986
3181
  ]);
2987
- const create = registrations.command("create [json]").description(
3182
+ const create = registrations.command("create [json]").summary("Create a registration").description(
2988
3183
  'Create a registration\n\nJSON payload example:\n {\n "type": "ContextSourceRegistration",\n "information": [{"entities": [{"type": "Room"}]}],\n "endpoint": "http://localhost:4000/source"\n }'
2989
3184
  ).action(
2990
3185
  withErrorHandler(async (json, _opts, cmd) => {
@@ -3039,7 +3234,7 @@ function registerRegistrationsCommand(program2) {
3039
3234
  command: "cat registration.json | geonic registrations update urn:ngsi-ld:ContextSourceRegistration:001"
3040
3235
  }
3041
3236
  ]);
3042
- const del = registrations.command("delete <id>").description("Delete a registration").action(
3237
+ const del = registrations.command("delete <id>").description("Delete a registration and remove its forwarding rule").action(
3043
3238
  withErrorHandler(async (id, _opts, cmd) => {
3044
3239
  const client = createClient(cmd);
3045
3240
  await client.delete(
@@ -3050,16 +3245,20 @@ function registerRegistrationsCommand(program2) {
3050
3245
  );
3051
3246
  addExamples(del, [
3052
3247
  {
3053
- description: "Delete a registration",
3248
+ description: "Delete a registration by ID",
3054
3249
  command: "geonic registrations delete urn:ngsi-ld:ContextSourceRegistration:001"
3250
+ },
3251
+ {
3252
+ description: "Remove forwarding rule (using alias)",
3253
+ command: "geonic reg delete urn:ngsi-ld:ContextSourceRegistration:001"
3055
3254
  }
3056
3255
  ]);
3057
3256
  }
3058
3257
 
3059
3258
  // src/commands/types.ts
3060
3259
  function registerTypesCommand(program2) {
3061
- const types = program2.command("types").description("Browse entity types");
3062
- const list = types.command("list").description("List available entity types").action(
3260
+ const types = program2.command("types").description("Discover what entity types exist in the broker and inspect their structure");
3261
+ const list = types.command("list").description("List all entity types currently stored in the broker").action(
3063
3262
  withErrorHandler(async (_opts, cmd) => {
3064
3263
  const client = createClient(cmd);
3065
3264
  const format = getFormat(cmd);
@@ -3069,11 +3268,15 @@ function registerTypesCommand(program2) {
3069
3268
  );
3070
3269
  addExamples(list, [
3071
3270
  {
3072
- description: "List all entity types",
3271
+ description: "List all entity types in the broker",
3073
3272
  command: "geonic types list"
3273
+ },
3274
+ {
3275
+ description: "List entity types in table format for a quick overview",
3276
+ command: "geonic types list --format table"
3074
3277
  }
3075
3278
  ]);
3076
- const get = types.command("get <typeName>").description("Get details for an entity type").action(
3279
+ const get = types.command("get <typeName>").description("Show attribute names and types for a given entity type").action(
3077
3280
  withErrorHandler(
3078
3281
  async (typeName, _opts, cmd) => {
3079
3282
  const client = createClient(cmd);
@@ -3087,8 +3290,16 @@ function registerTypesCommand(program2) {
3087
3290
  );
3088
3291
  addExamples(get, [
3089
3292
  {
3090
- description: "Get details for a specific type",
3293
+ description: "Inspect the Sensor type to see its attributes",
3091
3294
  command: "geonic types get Sensor"
3295
+ },
3296
+ {
3297
+ description: "Inspect a Building type in table format",
3298
+ command: "geonic types get Building --format table"
3299
+ },
3300
+ {
3301
+ description: "Inspect a fully-qualified NGSI-LD type",
3302
+ command: "geonic types get https://uri.fiware.org/ns/data-models#AirQualityObserved"
3092
3303
  }
3093
3304
  ]);
3094
3305
  }
@@ -3214,7 +3425,7 @@ function registerTemporalCommand(program2) {
3214
3425
  command: "geonic temporal entities get urn:ngsi-ld:Sensor:001 --last-n 10"
3215
3426
  }
3216
3427
  ]);
3217
- const create = entities.command("create [json]").description(
3428
+ const create = entities.command("create [json]").summary("Create a temporal entity").description(
3218
3429
  "Create a temporal entity\n\nJSON payload: an NGSI-LD entity with temporal attribute instances.\nEach attribute value is an array of {value, observedAt} objects."
3219
3430
  ).action(createCreateAction());
3220
3431
  addExamples(create, [
@@ -3231,11 +3442,15 @@ function registerTemporalCommand(program2) {
3231
3442
  command: "geonic temporal entities create"
3232
3443
  }
3233
3444
  ]);
3234
- const del = entities.command("delete <id>").description("Delete a temporal entity by ID").action(createDeleteAction());
3445
+ const del = entities.command("delete <id>").description("Delete a temporal entity and all its historical attribute data").action(createDeleteAction());
3235
3446
  addExamples(del, [
3236
3447
  {
3237
3448
  description: "Delete temporal data for an entity",
3238
3449
  command: "geonic temporal entities delete urn:ngsi-ld:Sensor:001"
3450
+ },
3451
+ {
3452
+ description: "Remove all historical records for a specific entity",
3453
+ command: "geonic temporal entities delete urn:ngsi-ld:WeatherStation:tokyo-01"
3239
3454
  }
3240
3455
  ]);
3241
3456
  const opsQuery = addQueryOptions(
@@ -3271,8 +3486,8 @@ function registerTemporalCommand(program2) {
3271
3486
 
3272
3487
  // src/commands/snapshots.ts
3273
3488
  function registerSnapshotsCommand(program2) {
3274
- const snapshots = program2.command("snapshots").description("Manage snapshots");
3275
- const list = snapshots.command("list").description("List snapshots").option("--limit <n>", "Maximum number of snapshots to return", parseInt).option("--offset <n>", "Skip first N snapshots", parseInt).action(
3489
+ const snapshots = program2.command("snapshots").description("Manage point-in-time snapshots of entity data for backup and cloning");
3490
+ const list = snapshots.command("list").description("List all available snapshots with their IDs, timestamps, and status").option("--limit <n>", "Maximum number of snapshots to return", parseInt).option("--offset <n>", "Skip first N snapshots", parseInt).action(
3276
3491
  withErrorHandler(async (_opts, cmd) => {
3277
3492
  const client = createClient(cmd);
3278
3493
  const format = getFormat(cmd);
@@ -3290,11 +3505,19 @@ function registerSnapshotsCommand(program2) {
3290
3505
  command: "geonic snapshots list"
3291
3506
  },
3292
3507
  {
3293
- description: "List with a limit",
3508
+ description: "List the 10 most recent snapshots",
3294
3509
  command: "geonic snapshots list --limit 10"
3510
+ },
3511
+ {
3512
+ description: "Paginate through snapshots",
3513
+ command: "geonic snapshots list --limit 5 --offset 10"
3514
+ },
3515
+ {
3516
+ description: "List snapshots in table format",
3517
+ command: "geonic snapshots list --format table"
3295
3518
  }
3296
3519
  ]);
3297
- const get = snapshots.command("get <id>").description("Get a snapshot by ID").action(
3520
+ const get = snapshots.command("get <id>").description("Retrieve details of a specific snapshot including its status and metadata").action(
3298
3521
  withErrorHandler(async (id, _opts, cmd) => {
3299
3522
  const client = createClient(cmd);
3300
3523
  const format = getFormat(cmd);
@@ -3306,11 +3529,15 @@ function registerSnapshotsCommand(program2) {
3306
3529
  );
3307
3530
  addExamples(get, [
3308
3531
  {
3309
- description: "Get a specific snapshot",
3310
- command: "geonic snapshots get <snapshot-id>"
3532
+ description: "Get details of a snapshot by its ID",
3533
+ command: "geonic snapshots get abc123"
3534
+ },
3535
+ {
3536
+ description: "Get snapshot details in table format",
3537
+ command: "geonic snapshots get abc123 --format table"
3311
3538
  }
3312
3539
  ]);
3313
- const create = snapshots.command("create").description("Create a new snapshot").action(
3540
+ const create = snapshots.command("create").description("Create a point-in-time snapshot of all current entity data").action(
3314
3541
  withErrorHandler(async (_opts, cmd) => {
3315
3542
  const client = createClient(cmd);
3316
3543
  await client.post("/snapshots");
@@ -3319,11 +3546,15 @@ function registerSnapshotsCommand(program2) {
3319
3546
  );
3320
3547
  addExamples(create, [
3321
3548
  {
3322
- description: "Create a new snapshot",
3549
+ description: "Create a snapshot of the current entity data",
3323
3550
  command: "geonic snapshots create"
3551
+ },
3552
+ {
3553
+ description: "Create a snapshot before performing a bulk update",
3554
+ command: "geonic snapshots create && geonic batch upsert @bulk-update.json"
3324
3555
  }
3325
3556
  ]);
3326
- const del = snapshots.command("delete <id>").description("Delete a snapshot by ID").action(
3557
+ const del = snapshots.command("delete <id>").description("Permanently delete a snapshot to free storage").action(
3327
3558
  withErrorHandler(async (id, _opts, cmd) => {
3328
3559
  const client = createClient(cmd);
3329
3560
  await client.delete(
@@ -3334,11 +3565,15 @@ function registerSnapshotsCommand(program2) {
3334
3565
  );
3335
3566
  addExamples(del, [
3336
3567
  {
3337
- description: "Delete a snapshot",
3338
- command: "geonic snapshots delete <snapshot-id>"
3568
+ description: "Delete a snapshot by its ID",
3569
+ command: "geonic snapshots delete abc123"
3570
+ },
3571
+ {
3572
+ description: "Delete an old snapshot to reclaim storage",
3573
+ command: "geonic snapshots delete old-backup-id"
3339
3574
  }
3340
3575
  ]);
3341
- const clone = snapshots.command("clone <id>").description("Clone a snapshot by ID").action(
3576
+ const clone = snapshots.command("clone <id>").description("Clone a snapshot to create a duplicate for testing or migration").action(
3342
3577
  withErrorHandler(async (id, _opts, cmd) => {
3343
3578
  const client = createClient(cmd);
3344
3579
  const format = getFormat(cmd);
@@ -3354,8 +3589,12 @@ function registerSnapshotsCommand(program2) {
3354
3589
  );
3355
3590
  addExamples(clone, [
3356
3591
  {
3357
- description: "Clone a snapshot",
3358
- command: "geonic snapshots clone <snapshot-id>"
3592
+ description: "Clone a snapshot for use in a test environment",
3593
+ command: "geonic snapshots clone abc123"
3594
+ },
3595
+ {
3596
+ description: "Clone a production snapshot to a staging profile",
3597
+ command: "geonic snapshots clone abc123 --profile staging"
3359
3598
  }
3360
3599
  ]);
3361
3600
  }
@@ -3363,7 +3602,7 @@ function registerSnapshotsCommand(program2) {
3363
3602
  // src/commands/admin/tenants.ts
3364
3603
  function registerTenantsCommand(parent) {
3365
3604
  const tenants = parent.command("tenants").description("Manage tenants");
3366
- const list = tenants.command("list").description("List all tenants").action(
3605
+ const list = tenants.command("list").description("List all tenants in the system, including their status and configuration").action(
3367
3606
  withErrorHandler(async (_opts, cmd) => {
3368
3607
  const client = createClient(cmd);
3369
3608
  const format = getFormat(cmd);
@@ -3375,9 +3614,13 @@ function registerTenantsCommand(parent) {
3375
3614
  {
3376
3615
  description: "List all tenants",
3377
3616
  command: "geonic admin tenants list"
3617
+ },
3618
+ {
3619
+ description: "List tenants in table format",
3620
+ command: "geonic admin tenants list --format table"
3378
3621
  }
3379
3622
  ]);
3380
- const get = tenants.command("get <id>").description("Get a tenant by ID").action(
3623
+ const get = tenants.command("get <id>").description("Get a tenant's details \u2014 name, description, status, and creation date").action(
3381
3624
  withErrorHandler(async (id, _opts, cmd) => {
3382
3625
  const client = createClient(cmd);
3383
3626
  const format = getFormat(cmd);
@@ -3390,11 +3633,15 @@ function registerTenantsCommand(parent) {
3390
3633
  );
3391
3634
  addExamples(get, [
3392
3635
  {
3393
- description: "Get a tenant by ID",
3636
+ description: "Get tenant details by ID",
3394
3637
  command: "geonic admin tenants get <tenant-id>"
3638
+ },
3639
+ {
3640
+ description: "Get tenant details in table format",
3641
+ command: "geonic admin tenants get <tenant-id> --format table"
3395
3642
  }
3396
3643
  ]);
3397
- const create = tenants.command("create [json]").description(
3644
+ const create = tenants.command("create [json]").summary("Create a new tenant").description(
3398
3645
  'Create a new tenant\n\nJSON payload example:\n {\n "name": "production",\n "description": "Production environment tenant"\n }'
3399
3646
  ).action(
3400
3647
  withErrorHandler(async (json, _opts, cmd) => {
@@ -3430,7 +3677,7 @@ function registerTenantsCommand(parent) {
3430
3677
  command: "geonic admin tenants create"
3431
3678
  }
3432
3679
  ]);
3433
- const update = tenants.command("update <id> [json]").description(
3680
+ const update = tenants.command("update <id> [json]").summary("Update a tenant").description(
3434
3681
  'Update a tenant\n\nJSON payload: only specified fields are updated.\n e.g. {"name": "new-name", "description": "Updated description"}'
3435
3682
  ).action(
3436
3683
  withErrorHandler(
@@ -3470,7 +3717,7 @@ function registerTenantsCommand(parent) {
3470
3717
  command: "geonic admin tenants update <tenant-id>"
3471
3718
  }
3472
3719
  ]);
3473
- const del = tenants.command("delete <id>").description("Delete a tenant").action(
3720
+ const del = tenants.command("delete <id>").description("Permanently delete a tenant and all its associated data. This action cannot be undone").action(
3474
3721
  withErrorHandler(async (id, _opts, cmd) => {
3475
3722
  const client = createClient(cmd);
3476
3723
  await client.rawRequest(
@@ -3482,11 +3729,15 @@ function registerTenantsCommand(parent) {
3482
3729
  );
3483
3730
  addExamples(del, [
3484
3731
  {
3485
- description: "Delete a tenant",
3732
+ description: "Delete a tenant by ID",
3486
3733
  command: "geonic admin tenants delete <tenant-id>"
3734
+ },
3735
+ {
3736
+ description: "Delete with verbose output to confirm",
3737
+ command: "geonic admin tenants delete <tenant-id> --verbose"
3487
3738
  }
3488
3739
  ]);
3489
- const activate = tenants.command("activate <id>").description("Activate a tenant").action(
3740
+ const activate = tenants.command("activate <id>").description("Activate a tenant, restoring API access for all its users").action(
3490
3741
  withErrorHandler(async (id, _opts, cmd) => {
3491
3742
  const client = createClient(cmd);
3492
3743
  await client.rawRequest(
@@ -3498,11 +3749,11 @@ function registerTenantsCommand(parent) {
3498
3749
  );
3499
3750
  addExamples(activate, [
3500
3751
  {
3501
- description: "Activate a tenant",
3752
+ description: "Activate a deactivated tenant",
3502
3753
  command: "geonic admin tenants activate <tenant-id>"
3503
3754
  }
3504
3755
  ]);
3505
- const deactivate = tenants.command("deactivate <id>").description("Deactivate a tenant").action(
3756
+ const deactivate = tenants.command("deactivate <id>").description("Deactivate a tenant, blocking API access for all its users until reactivated").action(
3506
3757
  withErrorHandler(async (id, _opts, cmd) => {
3507
3758
  const client = createClient(cmd);
3508
3759
  await client.rawRequest(
@@ -3514,7 +3765,7 @@ function registerTenantsCommand(parent) {
3514
3765
  );
3515
3766
  addExamples(deactivate, [
3516
3767
  {
3517
- description: "Deactivate a tenant",
3768
+ description: "Deactivate a tenant to temporarily suspend access",
3518
3769
  command: "geonic admin tenants deactivate <tenant-id>"
3519
3770
  }
3520
3771
  ]);
@@ -3523,7 +3774,7 @@ function registerTenantsCommand(parent) {
3523
3774
  // src/commands/admin/users.ts
3524
3775
  function registerUsersCommand(parent) {
3525
3776
  const users = parent.command("users").description("Manage users");
3526
- const list = users.command("list").description("List all users").action(
3777
+ const list = users.command("list").description("List all users across tenants, showing email, role, and status").action(
3527
3778
  withErrorHandler(async (_opts, cmd) => {
3528
3779
  const client = createClient(cmd);
3529
3780
  const format = getFormat(cmd);
@@ -3535,9 +3786,17 @@ function registerUsersCommand(parent) {
3535
3786
  {
3536
3787
  description: "List all users",
3537
3788
  command: "geonic admin users list"
3789
+ },
3790
+ {
3791
+ description: "List users in table format",
3792
+ command: "geonic admin users list --format table"
3793
+ },
3794
+ {
3795
+ description: "List users for a specific tenant",
3796
+ command: "geonic admin users list --service <tenant-id>"
3538
3797
  }
3539
3798
  ]);
3540
- const get = users.command("get <id>").description("Get a user by ID").action(
3799
+ const get = users.command("get <id>").description("Get a user's details \u2014 email, role, tenant, status, and login history").action(
3541
3800
  withErrorHandler(async (id, _opts, cmd) => {
3542
3801
  const client = createClient(cmd);
3543
3802
  const format = getFormat(cmd);
@@ -3550,11 +3809,15 @@ function registerUsersCommand(parent) {
3550
3809
  );
3551
3810
  addExamples(get, [
3552
3811
  {
3553
- description: "Get a user by ID",
3812
+ description: "Inspect a user's account details",
3554
3813
  command: "geonic admin users get <user-id>"
3814
+ },
3815
+ {
3816
+ description: "Get user details in table format",
3817
+ command: "geonic admin users get <user-id> --format table"
3555
3818
  }
3556
3819
  ]);
3557
- const create = users.command("create [json]").description(
3820
+ const create = users.command("create [json]").summary("Create a new user").description(
3558
3821
  'Create a new user\n\nJSON payload example:\n {\n "email": "user@example.com",\n "password": "SecurePassword123!",\n "role": "tenant_admin",\n "tenantId": "<tenant-id>"\n }\n\nRoles: super_admin, tenant_admin, user\ntenantId is required for tenant_admin and user roles.'
3559
3822
  ).action(
3560
3823
  withErrorHandler(async (json, _opts, cmd) => {
@@ -3586,7 +3849,7 @@ function registerUsersCommand(parent) {
3586
3849
  command: "cat user.json | geonic admin users create"
3587
3850
  }
3588
3851
  ]);
3589
- const update = users.command("update <id> [json]").description(
3852
+ const update = users.command("update <id> [json]").summary("Update a user").description(
3590
3853
  'Update a user\n\nJSON payload: only specified fields are updated.\n e.g. {"role": "admin"}'
3591
3854
  ).action(
3592
3855
  withErrorHandler(
@@ -3618,7 +3881,7 @@ function registerUsersCommand(parent) {
3618
3881
  command: "cat user.json | geonic admin users update <user-id>"
3619
3882
  }
3620
3883
  ]);
3621
- const del = users.command("delete <id>").description("Delete a user").action(
3884
+ const del = users.command("delete <id>").description("Permanently delete a user account. This revokes all access and cannot be undone").action(
3622
3885
  withErrorHandler(async (id, _opts, cmd) => {
3623
3886
  const client = createClient(cmd);
3624
3887
  await client.rawRequest(
@@ -3630,11 +3893,15 @@ function registerUsersCommand(parent) {
3630
3893
  );
3631
3894
  addExamples(del, [
3632
3895
  {
3633
- description: "Delete a user",
3896
+ description: "Delete a user by ID",
3634
3897
  command: "geonic admin users delete <user-id>"
3898
+ },
3899
+ {
3900
+ description: "Delete with verbose output",
3901
+ command: "geonic admin users delete <user-id> --verbose"
3635
3902
  }
3636
3903
  ]);
3637
- const activate = users.command("activate <id>").description("Activate a user").action(
3904
+ const activate = users.command("activate <id>").description("Activate a user account, allowing them to log in and access the API").action(
3638
3905
  withErrorHandler(async (id, _opts, cmd) => {
3639
3906
  const client = createClient(cmd);
3640
3907
  await client.rawRequest(
@@ -3646,11 +3913,11 @@ function registerUsersCommand(parent) {
3646
3913
  );
3647
3914
  addExamples(activate, [
3648
3915
  {
3649
- description: "Activate a user",
3916
+ description: "Activate a deactivated user",
3650
3917
  command: "geonic admin users activate <user-id>"
3651
3918
  }
3652
3919
  ]);
3653
- const deactivate = users.command("deactivate <id>").description("Deactivate a user").action(
3920
+ const deactivate = users.command("deactivate <id>").description("Deactivate a user account, preventing login until reactivated").action(
3654
3921
  withErrorHandler(async (id, _opts, cmd) => {
3655
3922
  const client = createClient(cmd);
3656
3923
  await client.rawRequest(
@@ -3662,11 +3929,11 @@ function registerUsersCommand(parent) {
3662
3929
  );
3663
3930
  addExamples(deactivate, [
3664
3931
  {
3665
- description: "Deactivate a user",
3932
+ description: "Deactivate a user to suspend their access",
3666
3933
  command: "geonic admin users deactivate <user-id>"
3667
3934
  }
3668
3935
  ]);
3669
- const unlock = users.command("unlock <id>").description("Unlock a user").action(
3936
+ const unlock = users.command("unlock <id>").description("Unlock a user account that was locked due to repeated failed login attempts").action(
3670
3937
  withErrorHandler(async (id, _opts, cmd) => {
3671
3938
  const client = createClient(cmd);
3672
3939
  await client.rawRequest(
@@ -3686,8 +3953,8 @@ function registerUsersCommand(parent) {
3686
3953
 
3687
3954
  // src/commands/admin/policies.ts
3688
3955
  function registerPoliciesCommand(parent) {
3689
- const policies = parent.command("policies").description("Manage policies");
3690
- const list = policies.command("list").description("List all policies").action(
3956
+ const policies = parent.command("policies").description("Manage XACML access control policies");
3957
+ const list = policies.command("list").description("List all access control policies, showing their status and priority").action(
3691
3958
  withErrorHandler(async (_opts, cmd) => {
3692
3959
  const client = createClient(cmd);
3693
3960
  const format = getFormat(cmd);
@@ -3699,9 +3966,13 @@ function registerPoliciesCommand(parent) {
3699
3966
  {
3700
3967
  description: "List all policies",
3701
3968
  command: "geonic admin policies list"
3969
+ },
3970
+ {
3971
+ description: "List policies in table format for an overview",
3972
+ command: "geonic admin policies list --format table"
3702
3973
  }
3703
3974
  ]);
3704
- const get = policies.command("get <id>").description("Get a policy by ID").action(
3975
+ const get = policies.command("get <id>").description("Get a policy's full details \u2014 target rules, effect, priority, and status").action(
3705
3976
  withErrorHandler(async (id, _opts, cmd) => {
3706
3977
  const client = createClient(cmd);
3707
3978
  const format = getFormat(cmd);
@@ -3714,11 +3985,11 @@ function registerPoliciesCommand(parent) {
3714
3985
  );
3715
3986
  addExamples(get, [
3716
3987
  {
3717
- description: "Get a policy by ID",
3988
+ description: "Inspect a policy's rules and target configuration",
3718
3989
  command: "geonic admin policies get <policy-id>"
3719
3990
  }
3720
3991
  ]);
3721
- const create = policies.command("create [json]").description(
3992
+ const create = policies.command("create [json]").summary("Create a new policy").description(
3722
3993
  'Create a new policy\n\nJSON payload examples:\n\n Allow all entities:\n {\n "description": "Allow all entities",\n "rules": [{"ruleId": "allow-all", "effect": "Permit"}]\n }\n\n Allow GET access to a specific entity type:\n {\n "description": "Allow GET access to Landmark entities",\n "target": {\n "resources": [{"attributeId": "entityType", "matchValue": "Landmark"}],\n "actions": [{"attributeId": "method", "matchValue": "GET"}]\n },\n "rules": [{"ruleId": "permit-get", "effect": "Permit"}]\n }\n\nTarget fields:\n subjects \u2014 attributeId: role, userId, email, tenantId\n resources \u2014 attributeId: path, entityType, entityId, entityOwner, tenantService, servicePath\n actions \u2014 attributeId: method (GET, POST, PATCH, DELETE)\n\nEach element: {attributeId, matchValue, matchFunction?}\n matchFunction: "string-equal" (default) | "string-regexp" | "glob"\n\nPriority: smaller value = higher precedence (e.g. priority 10 overrides user default at 100).\n tenant_admin: minimum priority 10. user self-service (/me/policies): fixed at 100.\n\nDefault role policies (priority 100):\n user \u2192 /v2/** and /ngsi-ld/** all methods Permit; other data APIs GET only\n api_key \u2192 all Deny, anonymous \u2192 all Deny'
3723
3994
  ).action(
3724
3995
  withErrorHandler(async (json, _opts, cmd) => {
@@ -3758,7 +4029,7 @@ function registerPoliciesCommand(parent) {
3758
4029
  command: "cat policy.json | geonic admin policies create"
3759
4030
  }
3760
4031
  ]);
3761
- const update = policies.command("update <id> [json]").description(
4032
+ const update = policies.command("update <id> [json]").summary("Update a policy").description(
3762
4033
  'Update a policy\n\nJSON payload: only specified fields are updated.\n e.g. {"description": "Updated policy"}'
3763
4034
  ).action(
3764
4035
  withErrorHandler(
@@ -3790,7 +4061,7 @@ function registerPoliciesCommand(parent) {
3790
4061
  command: "cat policy.json | geonic admin policies update <policy-id>"
3791
4062
  }
3792
4063
  ]);
3793
- const del = policies.command("delete <id>").description("Delete a policy").action(
4064
+ const del = policies.command("delete <id>").description("Delete a policy. Users or API keys referencing this policy will lose the access it granted").action(
3794
4065
  withErrorHandler(async (id, _opts, cmd) => {
3795
4066
  const client = createClient(cmd);
3796
4067
  await client.rawRequest(
@@ -3802,11 +4073,11 @@ function registerPoliciesCommand(parent) {
3802
4073
  );
3803
4074
  addExamples(del, [
3804
4075
  {
3805
- description: "Delete a policy",
4076
+ description: "Delete a policy by ID",
3806
4077
  command: "geonic admin policies delete <policy-id>"
3807
4078
  }
3808
4079
  ]);
3809
- const activate = policies.command("activate <id>").description("Activate a policy").action(
4080
+ const activate = policies.command("activate <id>").description("Activate a policy so its access control rules are enforced").action(
3810
4081
  withErrorHandler(async (id, _opts, cmd) => {
3811
4082
  const client = createClient(cmd);
3812
4083
  await client.rawRequest(
@@ -3818,11 +4089,11 @@ function registerPoliciesCommand(parent) {
3818
4089
  );
3819
4090
  addExamples(activate, [
3820
4091
  {
3821
- description: "Activate a policy",
4092
+ description: "Enable a policy to start enforcing its rules",
3822
4093
  command: "geonic admin policies activate <policy-id>"
3823
4094
  }
3824
4095
  ]);
3825
- const deactivate = policies.command("deactivate <id>").description("Deactivate a policy").action(
4096
+ const deactivate = policies.command("deactivate <id>").description("Deactivate a policy, suspending its rules without deleting it").action(
3826
4097
  withErrorHandler(async (id, _opts, cmd) => {
3827
4098
  const client = createClient(cmd);
3828
4099
  await client.rawRequest(
@@ -3834,7 +4105,7 @@ function registerPoliciesCommand(parent) {
3834
4105
  );
3835
4106
  addExamples(deactivate, [
3836
4107
  {
3837
- description: "Deactivate a policy",
4108
+ description: "Temporarily disable a policy without deleting it",
3838
4109
  command: "geonic admin policies deactivate <policy-id>"
3839
4110
  }
3840
4111
  ]);
@@ -3843,7 +4114,7 @@ function registerPoliciesCommand(parent) {
3843
4114
  // src/commands/admin/oauth-clients.ts
3844
4115
  function registerOAuthClientsCommand(parent) {
3845
4116
  const oauthClients = parent.command("oauth-clients").description("Manage OAuth clients");
3846
- const list = oauthClients.command("list").description("List all OAuth clients").action(
4117
+ const list = oauthClients.command("list").description("List all registered OAuth clients and their configurations").action(
3847
4118
  withErrorHandler(async (_opts, cmd) => {
3848
4119
  const client = createClient(cmd);
3849
4120
  const format = getFormat(cmd);
@@ -3855,9 +4126,13 @@ function registerOAuthClientsCommand(parent) {
3855
4126
  {
3856
4127
  description: "List all OAuth clients",
3857
4128
  command: "geonic admin oauth-clients list"
4129
+ },
4130
+ {
4131
+ description: "List OAuth clients in table format",
4132
+ command: "geonic admin oauth-clients list --format table"
3858
4133
  }
3859
4134
  ]);
3860
- const get = oauthClients.command("get <id>").description("Get an OAuth client by ID").action(
4135
+ const get = oauthClients.command("get <id>").description("Get an OAuth client's details \u2014 name, client ID, policy, and redirect URIs").action(
3861
4136
  withErrorHandler(async (id, _opts, cmd) => {
3862
4137
  const client = createClient(cmd);
3863
4138
  const format = getFormat(cmd);
@@ -3870,11 +4145,11 @@ function registerOAuthClientsCommand(parent) {
3870
4145
  );
3871
4146
  addExamples(get, [
3872
4147
  {
3873
- description: "Get an OAuth client by ID",
4148
+ description: "Inspect an OAuth client's configuration",
3874
4149
  command: "geonic admin oauth-clients get <client-id>"
3875
4150
  }
3876
4151
  ]);
3877
- const create = oauthClients.command("create [json]").description(
4152
+ const create = oauthClients.command("create [json]").summary("Create a new OAuth client").description(
3878
4153
  'Create a new OAuth client\n\nJSON payload example:\n {\n "name": "my-app",\n "policyId": "<policy-id>"\n }'
3879
4154
  ).action(
3880
4155
  withErrorHandler(async (json, _opts, cmd) => {
@@ -3902,7 +4177,7 @@ function registerOAuthClientsCommand(parent) {
3902
4177
  command: "cat client.json | geonic admin oauth-clients create"
3903
4178
  }
3904
4179
  ]);
3905
- const update = oauthClients.command("update <id> [json]").description(
4180
+ const update = oauthClients.command("update <id> [json]").summary("Update an OAuth client").description(
3906
4181
  'Update an OAuth client\n\nJSON payload: only specified fields are updated.\n e.g. {"description": "Updated client"}'
3907
4182
  ).action(
3908
4183
  withErrorHandler(
@@ -3934,7 +4209,7 @@ function registerOAuthClientsCommand(parent) {
3934
4209
  command: "cat client.json | geonic admin oauth-clients update <client-id>"
3935
4210
  }
3936
4211
  ]);
3937
- const del = oauthClients.command("delete <id>").description("Delete an OAuth client").action(
4212
+ const del = oauthClients.command("delete <id>").description("Delete an OAuth client. Existing tokens issued by this client will be invalidated").action(
3938
4213
  withErrorHandler(async (id, _opts, cmd) => {
3939
4214
  const client = createClient(cmd);
3940
4215
  await client.rawRequest(
@@ -3946,14 +4221,14 @@ function registerOAuthClientsCommand(parent) {
3946
4221
  );
3947
4222
  addExamples(del, [
3948
4223
  {
3949
- description: "Delete an OAuth client",
4224
+ description: "Delete an OAuth client by ID",
3950
4225
  command: "geonic admin oauth-clients delete <client-id>"
3951
4226
  }
3952
4227
  ]);
3953
4228
  }
3954
4229
  function registerCaddeCommand(parent) {
3955
- const cadde = parent.command("cadde").description("Manage CADDE configuration");
3956
- const caddeGet = cadde.command("get").description("Get CADDE configuration").action(
4230
+ const cadde = parent.command("cadde").description("Manage CADDE (data exchange) configuration for cross-platform data sharing");
4231
+ const caddeGet = cadde.command("get").description("Get the current CADDE data exchange configuration (provider, endpoint, etc.)").action(
3957
4232
  withErrorHandler(async (_opts, cmd) => {
3958
4233
  const client = createClient(cmd);
3959
4234
  const format = getFormat(cmd);
@@ -3963,11 +4238,15 @@ function registerCaddeCommand(parent) {
3963
4238
  );
3964
4239
  addExamples(caddeGet, [
3965
4240
  {
3966
- description: "Get CADDE configuration",
4241
+ description: "View current CADDE configuration",
3967
4242
  command: "geonic admin cadde get"
4243
+ },
4244
+ {
4245
+ description: "View CADDE configuration in table format",
4246
+ command: "geonic admin cadde get --format table"
3968
4247
  }
3969
4248
  ]);
3970
- const caddeSet = cadde.command("set [json]").description(
4249
+ const caddeSet = cadde.command("set [json]").summary("Set CADDE configuration").description(
3971
4250
  'Set CADDE configuration\n\nJSON payload example:\n {\n "provider": "my-provider",\n "endpoint": "http://localhost:6000"\n }'
3972
4251
  ).action(
3973
4252
  withErrorHandler(async (json, _opts, cmd) => {
@@ -3995,7 +4274,7 @@ function registerCaddeCommand(parent) {
3995
4274
  command: "cat cadde-config.json | geonic admin cadde set"
3996
4275
  }
3997
4276
  ]);
3998
- const caddeDelete = cadde.command("delete").description("Delete CADDE configuration").action(
4277
+ const caddeDelete = cadde.command("delete").description("Remove the CADDE data exchange configuration, disabling cross-platform data sharing").action(
3999
4278
  withErrorHandler(async (_opts, cmd) => {
4000
4279
  const client = createClient(cmd);
4001
4280
  await client.rawRequest("DELETE", "/admin/cadde");
@@ -4004,7 +4283,7 @@ function registerCaddeCommand(parent) {
4004
4283
  );
4005
4284
  addExamples(caddeDelete, [
4006
4285
  {
4007
- description: "Delete CADDE configuration",
4286
+ description: "Remove CADDE configuration",
4008
4287
  command: "geonic admin cadde delete"
4009
4288
  }
4010
4289
  ]);
@@ -4090,7 +4369,7 @@ function showKeyResult2(data, save, cmd) {
4090
4369
  }
4091
4370
  function registerApiKeysCommand(parent) {
4092
4371
  const apiKeys = parent.command("api-keys").description("Manage API keys");
4093
- const list = apiKeys.command("list").description("List all API keys").option("--tenant-id <id>", "Filter by tenant ID").action(
4372
+ const list = apiKeys.command("list").description("List all API keys, showing name, tenant, policy, and status (key values are masked)").option("--tenant-id <id>", "Filter by tenant ID").action(
4094
4373
  withErrorHandler(async (_opts, cmd) => {
4095
4374
  const opts = cmd.opts();
4096
4375
  const client = createClient(cmd);
@@ -4110,12 +4389,16 @@ function registerApiKeysCommand(parent) {
4110
4389
  description: "List all API keys",
4111
4390
  command: "geonic admin api-keys list"
4112
4391
  },
4392
+ {
4393
+ description: "List API keys in table format",
4394
+ command: "geonic admin api-keys list --format table"
4395
+ },
4113
4396
  {
4114
4397
  description: "List API keys for a specific tenant",
4115
4398
  command: "geonic admin api-keys list --tenant-id <tenant-id>"
4116
4399
  }
4117
4400
  ]);
4118
- const get = apiKeys.command("get <keyId>").description("Get an API key by ID").action(
4401
+ const get = apiKeys.command("get <keyId>").description("Get an API key's metadata \u2014 name, policy, allowed origins, and rate limit (key value is masked)").action(
4119
4402
  withErrorHandler(async (keyId, _opts, cmd) => {
4120
4403
  const client = createClient(cmd);
4121
4404
  const format = getFormat(cmd);
@@ -4129,7 +4412,7 @@ function registerApiKeysCommand(parent) {
4129
4412
  );
4130
4413
  addExamples(get, [
4131
4414
  {
4132
- description: "Get an API key by ID",
4415
+ description: "Inspect an API key's configuration",
4133
4416
  command: "geonic admin api-keys get <key-id>"
4134
4417
  }
4135
4418
  ]);
@@ -4256,7 +4539,7 @@ function registerApiKeysCommand(parent) {
4256
4539
  command: "geonic admin api-keys update <key-id> @key.json"
4257
4540
  }
4258
4541
  ]);
4259
- const del = apiKeys.command("delete <keyId>").description("Delete an API key").action(
4542
+ const del = apiKeys.command("delete <keyId>").description("Delete an API key. Any requests using this key will be immediately rejected").action(
4260
4543
  withErrorHandler(async (keyId, _opts, cmd) => {
4261
4544
  const client = createClient(cmd);
4262
4545
  await client.rawRequest(
@@ -4268,7 +4551,7 @@ function registerApiKeysCommand(parent) {
4268
4551
  );
4269
4552
  addExamples(del, [
4270
4553
  {
4271
- description: "Delete an API key",
4554
+ description: "Delete an API key by ID",
4272
4555
  command: "geonic admin api-keys delete <key-id>"
4273
4556
  }
4274
4557
  ]);
@@ -4287,8 +4570,8 @@ function registerAdminCommand(program2) {
4287
4570
 
4288
4571
  // src/commands/rules.ts
4289
4572
  function registerRulesCommand(program2) {
4290
- const rules = program2.command("rules").description("Manage rule engine");
4291
- const list = rules.command("list").description("List all rules").action(
4573
+ const rules = program2.command("rules").description("Manage ReactiveCore rules that trigger actions based on entity changes");
4574
+ const list = rules.command("list").description("List all configured rules and their current status").action(
4292
4575
  withErrorHandler(async (_opts, cmd) => {
4293
4576
  const client = createClient(cmd);
4294
4577
  const format = getFormat(cmd);
@@ -4298,11 +4581,15 @@ function registerRulesCommand(program2) {
4298
4581
  );
4299
4582
  addExamples(list, [
4300
4583
  {
4301
- description: "List all rules",
4584
+ description: "List all rules as JSON",
4302
4585
  command: "geonic rules list"
4586
+ },
4587
+ {
4588
+ description: "List rules in table format to review status at a glance",
4589
+ command: "geonic rules list --format table"
4303
4590
  }
4304
4591
  ]);
4305
- const get = rules.command("get <id>").description("Get a rule by ID").action(
4592
+ const get = rules.command("get <id>").description("Get a rule's full definition including conditions, actions, and status").action(
4306
4593
  withErrorHandler(async (id, _opts, cmd) => {
4307
4594
  const client = createClient(cmd);
4308
4595
  const format = getFormat(cmd);
@@ -4315,11 +4602,15 @@ function registerRulesCommand(program2) {
4315
4602
  );
4316
4603
  addExamples(get, [
4317
4604
  {
4318
- description: "Get a specific rule",
4605
+ description: "Inspect a rule's conditions and actions",
4319
4606
  command: "geonic rules get <rule-id>"
4607
+ },
4608
+ {
4609
+ description: "Get a rule and check if it is active",
4610
+ command: "geonic rules get urn:ngsi-ld:Rule:high-temp-alert"
4320
4611
  }
4321
4612
  ]);
4322
- const create = rules.command("create [json]").description(
4613
+ const create = rules.command("create [json]").summary("Create a new rule").description(
4323
4614
  'Create a new rule\n\nJSON payload example:\n {\n "name": "high-temp-alert",\n "description": "Alert on high temperature",\n "conditions": [{"type": "celExpression", "expression": "entity.temperature > 30"}],\n "actions": [{"type": "webhook", "url": "http://localhost:5000/alert", "method": "POST"}]\n }'
4324
4615
  ).action(
4325
4616
  withErrorHandler(async (json, _opts, cmd) => {
@@ -4345,7 +4636,7 @@ function registerRulesCommand(program2) {
4345
4636
  command: "cat rule.json | geonic rules create"
4346
4637
  }
4347
4638
  ]);
4348
- const update = rules.command("update <id> [json]").description(
4639
+ const update = rules.command("update <id> [json]").summary("Update a rule").description(
4349
4640
  'Update a rule\n\nJSON payload: only specified fields are updated.\n e.g. {"description": "Updated rule"}'
4350
4641
  ).action(
4351
4642
  withErrorHandler(
@@ -4377,7 +4668,7 @@ function registerRulesCommand(program2) {
4377
4668
  command: "cat rule.json | geonic rules update <rule-id>"
4378
4669
  }
4379
4670
  ]);
4380
- const del = rules.command("delete <id>").description("Delete a rule").action(
4671
+ const del = rules.command("delete <id>").description("Permanently delete a rule and stop its processing").action(
4381
4672
  withErrorHandler(async (id, _opts, cmd) => {
4382
4673
  const client = createClient(cmd);
4383
4674
  await client.rawRequest(
@@ -4389,11 +4680,15 @@ function registerRulesCommand(program2) {
4389
4680
  );
4390
4681
  addExamples(del, [
4391
4682
  {
4392
- description: "Delete a rule",
4683
+ description: "Delete a rule by ID",
4393
4684
  command: "geonic rules delete <rule-id>"
4685
+ },
4686
+ {
4687
+ description: "Remove an obsolete alert rule",
4688
+ command: "geonic rules delete urn:ngsi-ld:Rule:old-alert"
4394
4689
  }
4395
4690
  ]);
4396
- const activate = rules.command("activate <id>").description("Activate a rule").action(
4691
+ const activate = rules.command("activate <id>").description("Enable a rule so it begins evaluating conditions and firing actions").action(
4397
4692
  withErrorHandler(async (id, _opts, cmd) => {
4398
4693
  const client = createClient(cmd);
4399
4694
  await client.rawRequest(
@@ -4405,11 +4700,15 @@ function registerRulesCommand(program2) {
4405
4700
  );
4406
4701
  addExamples(activate, [
4407
4702
  {
4408
- description: "Activate a rule",
4703
+ description: "Start processing a rule",
4409
4704
  command: "geonic rules activate <rule-id>"
4705
+ },
4706
+ {
4707
+ description: "Re-enable a previously deactivated rule",
4708
+ command: "geonic rules activate urn:ngsi-ld:Rule:high-temp-alert"
4410
4709
  }
4411
4710
  ]);
4412
- const deactivate = rules.command("deactivate <id>").description("Deactivate a rule").action(
4711
+ const deactivate = rules.command("deactivate <id>").description("Disable a rule without deleting it, pausing condition evaluation and actions").action(
4413
4712
  withErrorHandler(async (id, _opts, cmd) => {
4414
4713
  const client = createClient(cmd);
4415
4714
  await client.rawRequest(
@@ -4421,16 +4720,20 @@ function registerRulesCommand(program2) {
4421
4720
  );
4422
4721
  addExamples(deactivate, [
4423
4722
  {
4424
- description: "Deactivate a rule",
4723
+ description: "Temporarily pause a rule during maintenance",
4425
4724
  command: "geonic rules deactivate <rule-id>"
4725
+ },
4726
+ {
4727
+ description: "Disable a noisy alert rule without removing it",
4728
+ command: "geonic rules deactivate urn:ngsi-ld:Rule:high-temp-alert"
4426
4729
  }
4427
4730
  ]);
4428
4731
  }
4429
4732
 
4430
4733
  // src/commands/models.ts
4431
4734
  function registerModelsCommand(program2) {
4432
- const models = program2.command("custom-data-models").alias("models").description("Manage custom data models");
4433
- const list = models.command("list").description("List all models").action(
4735
+ const models = program2.command("custom-data-models").alias("models").description("Manage custom data models that define entity type schemas and property constraints");
4736
+ const list = models.command("list").description("List all registered data models for the current tenant").action(
4434
4737
  withErrorHandler(async (_opts, cmd) => {
4435
4738
  const client = createClient(cmd);
4436
4739
  const format = getFormat(cmd);
@@ -4440,11 +4743,15 @@ function registerModelsCommand(program2) {
4440
4743
  );
4441
4744
  addExamples(list, [
4442
4745
  {
4443
- description: "List all models",
4746
+ description: "List all data models as JSON",
4444
4747
  command: "geonic models list"
4748
+ },
4749
+ {
4750
+ description: "Browse available data models in table format",
4751
+ command: "geonic models list --format table"
4445
4752
  }
4446
4753
  ]);
4447
- const get = models.command("get <id>").description("Get a model by ID").action(
4754
+ const get = models.command("get <id>").description("Get a data model's full schema including property definitions and constraints").action(
4448
4755
  withErrorHandler(async (id, _opts, cmd) => {
4449
4756
  const client = createClient(cmd);
4450
4757
  const format = getFormat(cmd);
@@ -4457,11 +4764,15 @@ function registerModelsCommand(program2) {
4457
4764
  );
4458
4765
  addExamples(get, [
4459
4766
  {
4460
- description: "Get a specific model",
4767
+ description: "Inspect a model's property definitions",
4461
4768
  command: "geonic models get <model-id>"
4769
+ },
4770
+ {
4771
+ description: "View the schema for a Sensor data model",
4772
+ command: "geonic models get urn:ngsi-ld:DataModel:Sensor"
4462
4773
  }
4463
4774
  ]);
4464
- const create = models.command("create [json]").description(
4775
+ const create = models.command("create [json]").summary("Create a new model").description(
4465
4776
  'Create a new model\n\nJSON payload example:\n {\n "type": "Sensor",\n "domain": "iot",\n "description": "IoT Sensor",\n "propertyDetails": {\n "temperature": {"ngsiType": "Property", "valueType": "Number", "example": 25}\n }\n }'
4466
4777
  ).action(
4467
4778
  withErrorHandler(async (json, _opts, cmd) => {
@@ -4487,7 +4798,7 @@ function registerModelsCommand(program2) {
4487
4798
  command: "cat model.json | geonic models create"
4488
4799
  }
4489
4800
  ]);
4490
- const update = models.command("update <id> [json]").description(
4801
+ const update = models.command("update <id> [json]").summary("Update a model").description(
4491
4802
  'Update a model\n\nJSON payload: only specified fields are updated.\n e.g. {"description": "Updated model"}'
4492
4803
  ).action(
4493
4804
  withErrorHandler(
@@ -4519,7 +4830,7 @@ function registerModelsCommand(program2) {
4519
4830
  command: "cat model.json | geonic models update <model-id>"
4520
4831
  }
4521
4832
  ]);
4522
- const del = models.command("delete <id>").description("Delete a model").action(
4833
+ const del = models.command("delete <id>").description("Delete a data model definition (does not affect existing entities)").action(
4523
4834
  withErrorHandler(async (id, _opts, cmd) => {
4524
4835
  const client = createClient(cmd);
4525
4836
  await client.rawRequest(
@@ -4531,16 +4842,20 @@ function registerModelsCommand(program2) {
4531
4842
  );
4532
4843
  addExamples(del, [
4533
4844
  {
4534
- description: "Delete a model",
4845
+ description: "Delete a data model by ID",
4535
4846
  command: "geonic models delete <model-id>"
4847
+ },
4848
+ {
4849
+ description: "Remove a deprecated model definition",
4850
+ command: "geonic models delete urn:ngsi-ld:DataModel:LegacySensor"
4536
4851
  }
4537
4852
  ]);
4538
4853
  }
4539
4854
 
4540
4855
  // src/commands/catalog.ts
4541
4856
  function registerCatalogCommand(program2) {
4542
- const catalog = program2.command("catalog").description("Browse DCAT-AP catalog");
4543
- const get = catalog.command("get").description("Get the catalog").action(
4857
+ const catalog = program2.command("catalog").description("Browse the DCAT-AP data catalog for discovering and previewing datasets");
4858
+ const get = catalog.command("get").description("Get the DCAT-AP catalog metadata including title, publisher, and dataset count").action(
4544
4859
  withErrorHandler(async (_opts, cmd) => {
4545
4860
  const client = createClient(cmd);
4546
4861
  const format = getFormat(cmd);
@@ -4550,12 +4865,16 @@ function registerCatalogCommand(program2) {
4550
4865
  );
4551
4866
  addExamples(get, [
4552
4867
  {
4553
- description: "Get the DCAT-AP catalog",
4868
+ description: "View catalog metadata (title, publisher, datasets summary)",
4554
4869
  command: "geonic catalog get"
4870
+ },
4871
+ {
4872
+ description: "Get catalog metadata in table format",
4873
+ command: "geonic catalog get --format table"
4555
4874
  }
4556
4875
  ]);
4557
- const datasets = catalog.command("datasets").description("Manage catalog datasets");
4558
- const datasetsList = datasets.command("list").description("List all datasets").action(
4876
+ const datasets = catalog.command("datasets").description("List, inspect, and preview datasets published in the catalog");
4877
+ const datasetsList = datasets.command("list").description("List all datasets published in the catalog").action(
4559
4878
  withErrorHandler(async (_opts, cmd) => {
4560
4879
  const client = createClient(cmd);
4561
4880
  const format = getFormat(cmd);
@@ -4565,11 +4884,15 @@ function registerCatalogCommand(program2) {
4565
4884
  );
4566
4885
  addExamples(datasetsList, [
4567
4886
  {
4568
- description: "List all catalog datasets",
4887
+ description: "List all catalog datasets as JSON",
4569
4888
  command: "geonic catalog datasets list"
4889
+ },
4890
+ {
4891
+ description: "Browse datasets in table format",
4892
+ command: "geonic catalog datasets list --format table"
4570
4893
  }
4571
4894
  ]);
4572
- const datasetsGet = datasets.command("get <id>").description("Get a dataset by ID").action(
4895
+ const datasetsGet = datasets.command("get <id>").description("Get a dataset's metadata including description, distributions, and license").action(
4573
4896
  withErrorHandler(async (id, _opts, cmd) => {
4574
4897
  const client = createClient(cmd);
4575
4898
  const format = getFormat(cmd);
@@ -4582,11 +4905,15 @@ function registerCatalogCommand(program2) {
4582
4905
  );
4583
4906
  addExamples(datasetsGet, [
4584
4907
  {
4585
- description: "Get a specific dataset",
4908
+ description: "Inspect a dataset's metadata and distributions",
4586
4909
  command: "geonic catalog datasets get <dataset-id>"
4910
+ },
4911
+ {
4912
+ description: "View dataset details including license and publisher",
4913
+ command: "geonic catalog datasets get urn:ngsi-ld:Dataset:weather-stations"
4587
4914
  }
4588
4915
  ]);
4589
- const datasetsSample = datasets.command("sample <id>").description("Get sample data for a dataset").action(
4916
+ const datasetsSample = datasets.command("sample <id>").description("Preview sample entities from a dataset to understand its structure and content").action(
4590
4917
  withErrorHandler(async (id, _opts, cmd) => {
4591
4918
  const client = createClient(cmd);
4592
4919
  const format = getFormat(cmd);
@@ -4599,8 +4926,12 @@ function registerCatalogCommand(program2) {
4599
4926
  );
4600
4927
  addExamples(datasetsSample, [
4601
4928
  {
4602
- description: "Get sample data for a dataset",
4929
+ description: "Preview sample entities from a dataset",
4603
4930
  command: "geonic catalog datasets sample <dataset-id>"
4931
+ },
4932
+ {
4933
+ description: "Preview data in table format to quickly assess content",
4934
+ command: "geonic catalog datasets sample <dataset-id> --format table"
4604
4935
  }
4605
4936
  ]);
4606
4937
  }
@@ -4608,7 +4939,7 @@ function registerCatalogCommand(program2) {
4608
4939
  // src/commands/health.ts
4609
4940
  import { createRequire } from "module";
4610
4941
  function registerHealthCommand(program2) {
4611
- const health = program2.command("health").description("Check the health status of the server").action(
4942
+ const health = program2.command("health").description("Check Context Broker connectivity and health status").action(
4612
4943
  withErrorHandler(async (_opts, cmd) => {
4613
4944
  const client = createClient(cmd);
4614
4945
  const format = getFormat(cmd);
@@ -4620,11 +4951,19 @@ function registerHealthCommand(program2) {
4620
4951
  {
4621
4952
  description: "Check server health",
4622
4953
  command: "geonic health"
4954
+ },
4955
+ {
4956
+ description: "Check health with table output",
4957
+ command: "geonic health --format table"
4958
+ },
4959
+ {
4960
+ description: "Check health of a specific server",
4961
+ command: "geonic health --url https://api.example.com"
4623
4962
  }
4624
4963
  ]);
4625
4964
  }
4626
4965
  function registerVersionCommand(program2) {
4627
- const version = program2.command("version").description("Display CLI and server version information").action(
4966
+ const version = program2.command("version").description("Display both CLI and server version information").action(
4628
4967
  withErrorHandler(async (_opts, cmd) => {
4629
4968
  const require2 = createRequire(import.meta.url);
4630
4969
  const pkg = require2("../package.json");
@@ -4641,11 +4980,16 @@ function registerVersionCommand(program2) {
4641
4980
  {
4642
4981
  description: "Show CLI and server version",
4643
4982
  command: "geonic version"
4983
+ },
4984
+ {
4985
+ description: "Show version as JSON",
4986
+ command: "geonic version --format json"
4644
4987
  }
4645
4988
  ]);
4646
4989
  }
4647
4990
 
4648
4991
  // src/commands/cli.ts
4992
+ import { execSync } from "child_process";
4649
4993
  import { createRequire as createRequire2 } from "module";
4650
4994
  function findOption(cmd, program2, flag) {
4651
4995
  return cmd.options.find((o) => o.long === flag || o.short === flag) || program2.options.find((o) => o.long === flag || o.short === flag);
@@ -4811,15 +5155,43 @@ function registerCliCommand(program2) {
4811
5155
  command: `echo 'eval "$(geonic cli completions zsh)"' >> ~/.zshrc`
4812
5156
  }
4813
5157
  ]);
4814
- const version = cli.command("version").description("Display the CLI version").action(() => {
5158
+ const version = cli.command("version").description("Display the currently installed CLI version").action(() => {
4815
5159
  const require2 = createRequire2(import.meta.url);
4816
5160
  const pkg = require2("../package.json");
4817
5161
  console.log(pkg.version);
4818
5162
  });
4819
5163
  addExamples(version, [
4820
5164
  {
4821
- description: "Show CLI version",
5165
+ description: "Show the installed version",
4822
5166
  command: "geonic cli version"
5167
+ },
5168
+ {
5169
+ description: "Use in scripts to check the installed version",
5170
+ command: 'echo "geonic $(geonic cli version)"'
5171
+ }
5172
+ ]);
5173
+ const update = cli.command("update").description("Update the CLI to the latest version via npm (requires global install)").action(
5174
+ withErrorHandler(async (...args) => {
5175
+ const cmd = args[args.length - 1];
5176
+ const opts = resolveOptions(cmd);
5177
+ const updateCommand = "npm update -g @geolonia/geonicdb-cli";
5178
+ if (opts.dryRun) {
5179
+ printInfo(`[dry-run] ${updateCommand}`);
5180
+ return;
5181
+ }
5182
+ printInfo("Updating @geolonia/geonicdb-cli...");
5183
+ execSync(updateCommand, { stdio: "inherit" });
5184
+ printSuccess("Update complete.");
5185
+ })
5186
+ );
5187
+ addExamples(update, [
5188
+ {
5189
+ description: "Update CLI to the latest version",
5190
+ command: "geonic cli update"
5191
+ },
5192
+ {
5193
+ description: "Preview the update command without executing it",
5194
+ command: "geonic cli update --dry-run"
4823
5195
  }
4824
5196
  ]);
4825
5197
  }