@geolonia/geonicdb-cli 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -240,14 +240,34 @@ function collectKeys(items) {
240
240
  sorted.push(...Array.from(keySet).sort());
241
241
  return sorted;
242
242
  }
243
+ function formatGeoJSON(geo) {
244
+ const geoType = String(geo.type);
245
+ const coords = geo.coordinates;
246
+ if (geoType === "Point" && Array.isArray(coords) && coords.length >= 2) {
247
+ return `Point(${Number(coords[0]).toFixed(2)}, ${Number(coords[1]).toFixed(2)})`;
248
+ }
249
+ if (Array.isArray(coords)) {
250
+ const count = geoType === "Polygon" ? Array.isArray(coords[0]) ? coords[0].length : 0 : coords.length;
251
+ return `${geoType}(${count} coords)`;
252
+ }
253
+ return `${geoType}(...)`;
254
+ }
255
+ function isGeoJSON(v) {
256
+ if (typeof v !== "object" || v === null) return false;
257
+ const o = v;
258
+ return typeof o.type === "string" && "coordinates" in o;
259
+ }
243
260
  function cellValue(val) {
244
261
  if (val === void 0 || val === null) return "";
245
- if (typeof val === "object") {
246
- const obj = val;
247
- if ("value" in obj) return String(obj.value);
248
- return JSON.stringify(val);
262
+ if (typeof val !== "object") return String(val);
263
+ const obj = val;
264
+ if (isGeoJSON(obj)) return formatGeoJSON(obj);
265
+ if ("value" in obj) {
266
+ const v = obj.value;
267
+ if (isGeoJSON(v)) return formatGeoJSON(v);
268
+ return String(v);
249
269
  }
250
- return String(val);
270
+ return JSON.stringify(val);
251
271
  }
252
272
  function toGeoJSON(data) {
253
273
  if (Array.isArray(data)) {
@@ -584,6 +604,36 @@ function registerConfigCommand(program2) {
584
604
  // src/commands/auth.ts
585
605
  import { Command } from "commander";
586
606
 
607
+ // src/oauth.ts
608
+ async function clientCredentialsGrant(options) {
609
+ const url = new URL("/oauth/token", options.baseUrl).toString();
610
+ const credentials = Buffer.from(`${options.clientId}:${options.clientSecret}`).toString("base64");
611
+ const body = { grant_type: "client_credentials" };
612
+ if (options.scope) {
613
+ body.scope = options.scope;
614
+ }
615
+ const response = await fetch(url, {
616
+ method: "POST",
617
+ headers: {
618
+ "Content-Type": "application/json",
619
+ Authorization: `Basic ${credentials}`
620
+ },
621
+ body: JSON.stringify(body)
622
+ });
623
+ if (!response.ok) {
624
+ const text = await response.text();
625
+ let message;
626
+ try {
627
+ const err = JSON.parse(text);
628
+ message = err.error_description ?? err.error ?? text;
629
+ } catch {
630
+ message = text || `HTTP ${response.status}`;
631
+ }
632
+ throw new Error(`OAuth token request failed: ${message}`);
633
+ }
634
+ return await response.json();
635
+ }
636
+
587
637
  // src/client.ts
588
638
  var DryRunSignal = class extends Error {
589
639
  constructor() {
@@ -597,6 +647,8 @@ var GdbClient = class _GdbClient {
597
647
  token;
598
648
  refreshToken;
599
649
  apiKey;
650
+ clientId;
651
+ clientSecret;
600
652
  onTokenRefresh;
601
653
  verbose;
602
654
  dryRun;
@@ -607,6 +659,8 @@ var GdbClient = class _GdbClient {
607
659
  this.token = options.token;
608
660
  this.refreshToken = options.refreshToken;
609
661
  this.apiKey = options.apiKey;
662
+ this.clientId = options.clientId;
663
+ this.clientSecret = options.clientSecret;
610
664
  this.onTokenRefresh = options.onTokenRefresh;
611
665
  this.verbose = options.verbose ?? false;
612
666
  this.dryRun = options.dryRun ?? false;
@@ -713,7 +767,7 @@ var GdbClient = class _GdbClient {
713
767
  throw new DryRunSignal();
714
768
  }
715
769
  canRefresh() {
716
- return !!this.refreshToken && !this.apiKey;
770
+ return (!!this.refreshToken || !!this.clientId && !!this.clientSecret) && !this.apiKey;
717
771
  }
718
772
  async performTokenRefresh() {
719
773
  if (this.refreshPromise) return this.refreshPromise;
@@ -725,26 +779,43 @@ var GdbClient = class _GdbClient {
725
779
  }
726
780
  }
727
781
  async doRefresh() {
728
- if (!this.refreshToken) return false;
729
- try {
730
- const url = this.buildUrl("/auth/refresh");
731
- const response = await fetch(url, {
732
- method: "POST",
733
- headers: { "Content-Type": "application/json" },
734
- body: JSON.stringify({ refreshToken: this.refreshToken })
735
- });
736
- if (!response.ok) return false;
737
- const data = await response.json();
738
- const newToken = data.accessToken ?? data.token;
739
- const newRefreshToken = data.refreshToken;
740
- if (!newToken) return false;
741
- this.token = newToken;
742
- if (newRefreshToken) this.refreshToken = newRefreshToken;
743
- this.onTokenRefresh?.(newToken, newRefreshToken);
744
- return true;
745
- } catch {
746
- return false;
782
+ if (this.refreshToken) {
783
+ try {
784
+ const url = this.buildUrl("/auth/refresh");
785
+ const response = await fetch(url, {
786
+ method: "POST",
787
+ headers: { "Content-Type": "application/json" },
788
+ body: JSON.stringify({ refreshToken: this.refreshToken })
789
+ });
790
+ if (response.ok) {
791
+ const data = await response.json();
792
+ const newToken = data.accessToken ?? data.token;
793
+ const newRefreshToken = data.refreshToken;
794
+ if (newToken) {
795
+ this.token = newToken;
796
+ if (newRefreshToken) this.refreshToken = newRefreshToken;
797
+ this.onTokenRefresh?.(newToken, newRefreshToken);
798
+ return true;
799
+ }
800
+ }
801
+ } catch {
802
+ }
803
+ }
804
+ if (this.clientId && this.clientSecret) {
805
+ try {
806
+ const result = await clientCredentialsGrant({
807
+ baseUrl: this.baseUrl,
808
+ clientId: this.clientId,
809
+ clientSecret: this.clientSecret
810
+ });
811
+ this.token = result.access_token;
812
+ this.onTokenRefresh?.(result.access_token);
813
+ return true;
814
+ } catch {
815
+ return false;
816
+ }
747
817
  }
818
+ return false;
748
819
  }
749
820
  async executeRequest(method, path, options) {
750
821
  const url = this.buildUrl(`${this.getBasePath()}${path}`, options?.params);
@@ -877,6 +948,8 @@ function createClient(cmd) {
877
948
  service: opts.service,
878
949
  token: opts.token,
879
950
  refreshToken: usingCliToken ? void 0 : config.refreshToken,
951
+ clientId: usingCliToken ? void 0 : config.clientId,
952
+ clientSecret: usingCliToken ? void 0 : config.clientSecret,
880
953
  apiKey: opts.apiKey,
881
954
  onTokenRefresh: usingCliToken ? void 0 : (token, refreshToken) => {
882
955
  const cfg = loadConfig(opts.profile);
@@ -1057,35 +1130,217 @@ function formatDuration(ms) {
1057
1130
  return parts.join(" ");
1058
1131
  }
1059
1132
 
1060
- // src/oauth.ts
1061
- async function clientCredentialsGrant(options) {
1062
- const url = new URL("/oauth/token", options.baseUrl).toString();
1063
- const credentials = Buffer.from(`${options.clientId}:${options.clientSecret}`).toString("base64");
1064
- const params = new URLSearchParams();
1065
- params.set("grant_type", "client_credentials");
1066
- if (options.scope) {
1067
- params.set("scope", options.scope);
1133
+ // src/input.ts
1134
+ import JSON5 from "json5";
1135
+ import { readFileSync as readFileSync2 } from "fs";
1136
+ import { createInterface as createInterface2 } from "readline";
1137
+ async function parseJsonInput(input) {
1138
+ if (input !== void 0) {
1139
+ if (input === "-") return parseData(readFileSync2(0, "utf-8"));
1140
+ if (input.startsWith("@")) return parseData(readFileSync2(input.slice(1), "utf-8"));
1141
+ return parseData(input);
1068
1142
  }
1069
- const response = await fetch(url, {
1070
- method: "POST",
1071
- headers: {
1072
- "Content-Type": "application/x-www-form-urlencoded",
1073
- Authorization: `Basic ${credentials}`
1074
- },
1075
- body: params.toString()
1143
+ if (!process.stdin.isTTY) {
1144
+ return parseData(readFileSync2(0, "utf-8"));
1145
+ }
1146
+ return readInteractiveJson();
1147
+ }
1148
+ function parseData(text) {
1149
+ return JSON5.parse(text.trim());
1150
+ }
1151
+ async function readInteractiveJson() {
1152
+ const rl = createInterface2({
1153
+ input: process.stdin,
1154
+ output: process.stderr,
1155
+ prompt: "json> "
1076
1156
  });
1077
- if (!response.ok) {
1078
- const text = await response.text();
1079
- let message;
1080
- try {
1081
- const err = JSON.parse(text);
1082
- message = err.error_description ?? err.error ?? text;
1083
- } catch {
1084
- message = text || `HTTP ${response.status}`;
1157
+ process.stderr.write("Enter JSON (auto-submits when braces close, Ctrl+C to cancel):\n");
1158
+ rl.prompt();
1159
+ const lines = [];
1160
+ let depth = 0;
1161
+ let started = false;
1162
+ let inBlockComment = false;
1163
+ let inString = false;
1164
+ let stringChar = "";
1165
+ let cancelled = false;
1166
+ return new Promise((resolve, reject) => {
1167
+ rl.on("SIGINT", () => {
1168
+ cancelled = true;
1169
+ rl.close();
1170
+ });
1171
+ rl.on("line", (line) => {
1172
+ lines.push(line);
1173
+ const result = trackDepth(line, depth, started, inBlockComment, inString, stringChar);
1174
+ depth = result.depth;
1175
+ started = result.started;
1176
+ inBlockComment = result.inBlockComment;
1177
+ inString = result.inString;
1178
+ stringChar = result.stringChar;
1179
+ if (started && depth <= 0 && !inBlockComment && !inString) {
1180
+ rl.close();
1181
+ try {
1182
+ resolve(parseData(lines.join("\n")));
1183
+ } catch (err) {
1184
+ reject(err);
1185
+ }
1186
+ } else {
1187
+ rl.setPrompt("... ");
1188
+ rl.prompt();
1189
+ }
1190
+ });
1191
+ rl.on("close", () => {
1192
+ if (cancelled) {
1193
+ reject(new Error("Input cancelled."));
1194
+ return;
1195
+ }
1196
+ if (lines.length > 0 && (!started || depth > 0 || inBlockComment || inString)) {
1197
+ try {
1198
+ resolve(parseData(lines.join("\n")));
1199
+ } catch (err) {
1200
+ reject(err);
1201
+ }
1202
+ } else if (lines.length === 0) {
1203
+ reject(new Error("No input provided."));
1204
+ }
1205
+ });
1206
+ });
1207
+ }
1208
+ function trackDepth(line, depth, started, inBlockComment, inString, stringChar) {
1209
+ for (let i = 0; i < line.length; i++) {
1210
+ const ch = line[i];
1211
+ const next = i + 1 < line.length ? line[i + 1] : "";
1212
+ if (inBlockComment) {
1213
+ if (ch === "*" && next === "/") {
1214
+ inBlockComment = false;
1215
+ i++;
1216
+ }
1217
+ continue;
1218
+ }
1219
+ if (inString) {
1220
+ if (ch === "\\" && i + 1 < line.length) {
1221
+ i++;
1222
+ } else if (ch === stringChar) {
1223
+ inString = false;
1224
+ }
1225
+ continue;
1226
+ }
1227
+ if (ch === "/" && next === "/") break;
1228
+ if (ch === "/" && next === "*") {
1229
+ inBlockComment = true;
1230
+ i++;
1231
+ continue;
1232
+ }
1233
+ if (ch === '"' || ch === "'") {
1234
+ inString = true;
1235
+ stringChar = ch;
1236
+ } else if (ch === "{" || ch === "[") {
1237
+ depth++;
1238
+ started = true;
1239
+ } else if (ch === "}" || ch === "]") {
1240
+ depth--;
1085
1241
  }
1086
- throw new Error(`OAuth token request failed: ${message}`);
1087
1242
  }
1088
- return await response.json();
1243
+ return { depth, started, inBlockComment, inString, stringChar };
1244
+ }
1245
+
1246
+ // src/commands/me-oauth-clients.ts
1247
+ function addMeOAuthClientsSubcommand(me) {
1248
+ const oauthClients = me.command("oauth-clients").description("Manage your OAuth clients");
1249
+ const list = oauthClients.command("list").description("List your OAuth clients").action(
1250
+ withErrorHandler(async (_opts, cmd) => {
1251
+ const client = createClient(cmd);
1252
+ const format = getFormat(cmd);
1253
+ const response = await client.rawRequest("GET", "/me/oauth-clients");
1254
+ outputResponse(response, format);
1255
+ })
1256
+ );
1257
+ addExamples(list, [
1258
+ {
1259
+ description: "List your OAuth clients",
1260
+ command: "geonic me oauth-clients list"
1261
+ }
1262
+ ]);
1263
+ const create = oauthClients.command("create [json]").description("Create a new OAuth client").option("--name <name>", "Client name").option("--scopes <scopes>", "Allowed scopes (comma-separated)").option("--save", "Save credentials to config for automatic re-authentication").action(
1264
+ withErrorHandler(async (json, _opts, cmd) => {
1265
+ const opts = cmd.opts();
1266
+ let body;
1267
+ if (json) {
1268
+ body = await parseJsonInput(json);
1269
+ } else if (opts.name || opts.scopes) {
1270
+ const payload = {};
1271
+ if (opts.name) payload.clientName = opts.name;
1272
+ if (opts.scopes) payload.allowedScopes = opts.scopes.split(",").map((s) => s.trim());
1273
+ body = payload;
1274
+ } else {
1275
+ body = await parseJsonInput();
1276
+ }
1277
+ const client = createClient(cmd);
1278
+ const format = getFormat(cmd);
1279
+ const response = await client.rawRequest("POST", "/me/oauth-clients", { body });
1280
+ const data = response.data;
1281
+ if (opts.save) {
1282
+ const globalOpts = resolveOptions(cmd);
1283
+ const clientId = data.clientId;
1284
+ const clientSecret = data.clientSecret;
1285
+ if (!clientId || !clientSecret) {
1286
+ printError("Response missing clientId or clientSecret. Cannot save credentials.");
1287
+ outputResponse(response, format);
1288
+ printSuccess("OAuth client created.");
1289
+ return;
1290
+ }
1291
+ const baseUrl = validateUrl(globalOpts.url);
1292
+ const tokenResult = await clientCredentialsGrant({
1293
+ baseUrl,
1294
+ clientId,
1295
+ clientSecret,
1296
+ scope: data.allowedScopes?.join(" ")
1297
+ });
1298
+ const config = loadConfig(globalOpts.profile);
1299
+ config.clientId = clientId;
1300
+ config.clientSecret = clientSecret;
1301
+ config.token = tokenResult.access_token;
1302
+ delete config.refreshToken;
1303
+ saveConfig(config, globalOpts.profile);
1304
+ printInfo("Client credentials saved to config. Auto-reauth enabled.");
1305
+ } else {
1306
+ printWarning(
1307
+ "Save the clientSecret now \u2014 it will not be shown again."
1308
+ );
1309
+ }
1310
+ outputResponse(response, format);
1311
+ printSuccess("OAuth client created.");
1312
+ })
1313
+ );
1314
+ addExamples(create, [
1315
+ {
1316
+ description: "Create an OAuth client with flags",
1317
+ command: "geonic me oauth-clients create --name my-ci-bot --scopes read:entities,write:entities"
1318
+ },
1319
+ {
1320
+ description: "Create and save credentials for auto-reauth",
1321
+ command: "geonic me oauth-clients create --name my-ci-bot --save"
1322
+ },
1323
+ {
1324
+ description: "Create an OAuth client from JSON",
1325
+ command: `geonic me oauth-clients create '{"clientName":"my-bot","allowedScopes":["read:entities"]}'`
1326
+ }
1327
+ ]);
1328
+ const del = oauthClients.command("delete <id>").description("Delete an OAuth client").action(
1329
+ withErrorHandler(async (id, _opts, cmd) => {
1330
+ const client = createClient(cmd);
1331
+ await client.rawRequest(
1332
+ "DELETE",
1333
+ `/me/oauth-clients/${encodeURIComponent(String(id))}`
1334
+ );
1335
+ printSuccess("OAuth client deleted.");
1336
+ })
1337
+ );
1338
+ addExamples(del, [
1339
+ {
1340
+ description: "Delete an OAuth client",
1341
+ command: "geonic me oauth-clients delete <client-id>"
1342
+ }
1343
+ ]);
1089
1344
  }
1090
1345
 
1091
1346
  // src/commands/auth.ts
@@ -1245,13 +1500,25 @@ function registerAuthCommands(program2) {
1245
1500
  }
1246
1501
  ]);
1247
1502
  auth.addCommand(logout);
1248
- const me = program2.command("me").description("Display current authenticated user").action(createMeAction());
1503
+ const me = program2.command("me").description("Display current authenticated user and manage user resources");
1504
+ const meInfo = me.command("info", { isDefault: true, hidden: true }).description("Display current authenticated user").action(createMeAction());
1249
1505
  addExamples(me, [
1506
+ {
1507
+ description: "Show current user info",
1508
+ command: "geonic me"
1509
+ },
1510
+ {
1511
+ description: "List your OAuth clients",
1512
+ command: "geonic me oauth-clients list"
1513
+ }
1514
+ ]);
1515
+ addExamples(meInfo, [
1250
1516
  {
1251
1517
  description: "Show current user info",
1252
1518
  command: "geonic me"
1253
1519
  }
1254
1520
  ]);
1521
+ addMeOAuthClientsSubcommand(me);
1255
1522
  program2.addCommand(createLoginCommand(), { hidden: true });
1256
1523
  program2.addCommand(createLogoutCommand(), { hidden: true });
1257
1524
  const hiddenWhoami = new Command("whoami").description("Display current authenticated user").action(createMeAction());
@@ -1347,119 +1614,6 @@ function registerProfileCommands(program2) {
1347
1614
  ]);
1348
1615
  }
1349
1616
 
1350
- // src/input.ts
1351
- import JSON5 from "json5";
1352
- import { readFileSync as readFileSync2 } from "fs";
1353
- import { createInterface as createInterface2 } from "readline";
1354
- async function parseJsonInput(input) {
1355
- if (input !== void 0) {
1356
- if (input === "-") return parseData(readFileSync2(0, "utf-8"));
1357
- if (input.startsWith("@")) return parseData(readFileSync2(input.slice(1), "utf-8"));
1358
- return parseData(input);
1359
- }
1360
- if (!process.stdin.isTTY) {
1361
- return parseData(readFileSync2(0, "utf-8"));
1362
- }
1363
- return readInteractiveJson();
1364
- }
1365
- function parseData(text) {
1366
- return JSON5.parse(text.trim());
1367
- }
1368
- async function readInteractiveJson() {
1369
- const rl = createInterface2({
1370
- input: process.stdin,
1371
- output: process.stderr,
1372
- prompt: "json> "
1373
- });
1374
- process.stderr.write("Enter JSON (auto-submits when braces close, Ctrl+C to cancel):\n");
1375
- rl.prompt();
1376
- const lines = [];
1377
- let depth = 0;
1378
- let started = false;
1379
- let inBlockComment = false;
1380
- let inString = false;
1381
- let stringChar = "";
1382
- let cancelled = false;
1383
- return new Promise((resolve, reject) => {
1384
- rl.on("SIGINT", () => {
1385
- cancelled = true;
1386
- rl.close();
1387
- });
1388
- rl.on("line", (line) => {
1389
- lines.push(line);
1390
- const result = trackDepth(line, depth, started, inBlockComment, inString, stringChar);
1391
- depth = result.depth;
1392
- started = result.started;
1393
- inBlockComment = result.inBlockComment;
1394
- inString = result.inString;
1395
- stringChar = result.stringChar;
1396
- if (started && depth <= 0 && !inBlockComment && !inString) {
1397
- rl.close();
1398
- try {
1399
- resolve(parseData(lines.join("\n")));
1400
- } catch (err) {
1401
- reject(err);
1402
- }
1403
- } else {
1404
- rl.setPrompt("... ");
1405
- rl.prompt();
1406
- }
1407
- });
1408
- rl.on("close", () => {
1409
- if (cancelled) {
1410
- reject(new Error("Input cancelled."));
1411
- return;
1412
- }
1413
- if (lines.length > 0 && (!started || depth > 0 || inBlockComment || inString)) {
1414
- try {
1415
- resolve(parseData(lines.join("\n")));
1416
- } catch (err) {
1417
- reject(err);
1418
- }
1419
- } else if (lines.length === 0) {
1420
- reject(new Error("No input provided."));
1421
- }
1422
- });
1423
- });
1424
- }
1425
- function trackDepth(line, depth, started, inBlockComment, inString, stringChar) {
1426
- for (let i = 0; i < line.length; i++) {
1427
- const ch = line[i];
1428
- const next = i + 1 < line.length ? line[i + 1] : "";
1429
- if (inBlockComment) {
1430
- if (ch === "*" && next === "/") {
1431
- inBlockComment = false;
1432
- i++;
1433
- }
1434
- continue;
1435
- }
1436
- if (inString) {
1437
- if (ch === "\\" && i + 1 < line.length) {
1438
- i++;
1439
- } else if (ch === stringChar) {
1440
- inString = false;
1441
- }
1442
- continue;
1443
- }
1444
- if (ch === "/" && next === "/") break;
1445
- if (ch === "/" && next === "*") {
1446
- inBlockComment = true;
1447
- i++;
1448
- continue;
1449
- }
1450
- if (ch === '"' || ch === "'") {
1451
- inString = true;
1452
- stringChar = ch;
1453
- } else if (ch === "{" || ch === "[") {
1454
- depth++;
1455
- started = true;
1456
- } else if (ch === "}" || ch === "]") {
1457
- depth--;
1458
- }
1459
- }
1460
- return { depth, started, inBlockComment, inString, stringChar };
1461
- }
1462
-
1463
1617
  // src/commands/attrs.ts
1464
1618
  function addAttrsSubcommands(attrs) {
1465
1619
  const list = attrs.command("list").description("List all attributes of an entity").argument("<entityId>", "Entity ID").action(
@@ -1562,7 +1716,7 @@ function registerAttrsSubcommand(entitiesCmd) {
1562
1716
  // src/commands/entities.ts
1563
1717
  function registerEntitiesCommand(program2) {
1564
1718
  const entities = program2.command("entities").description("Manage context entities");
1565
- const list = entities.command("list").description("List entities with optional filters").option("--type <type>", "Filter by entity type").option("--id-pattern <pat>", "Filter by entity ID pattern (regex)").option("--query <q>", "NGSI query expression").option("--attrs <a,b>", "Comma-separated list of attributes to include").option("--georel <rel>", "Geo-relationship (e.g. near;maxDistance:1000)").option("--geometry <geo>", "Geometry type for geo-query (e.g. point)").option("--coords <coords>", "Coordinates for geo-query").option("--spatial-id <zfxy>", "Spatial ID filter (ZFXY tile)").option("--limit <n>", "Maximum number of entities to return", parseInt).option("--offset <n>", "Skip first N entities", parseInt).option("--order-by <field>", "Order results by field").option("--count", "Include total count in response").option("--key-values", "Request simplified key-value format").action(
1719
+ const list = entities.command("list").description("List entities with optional filters").option("--type <type>", "Filter by entity type").option("--id-pattern <pat>", "Filter by entity ID pattern (regex)").option("--query <q>", "NGSI query expression").option("--attrs <a,b>", "Comma-separated list of attributes to include").option("--georel <rel>", "Geo-relationship (e.g. near;maxDistance:1000)").option("--geometry <geo>", "Geometry type for geo-query (e.g. point)").option("--coords <coords>", "Coordinates for geo-query").option("--spatial-id <zfxy>", "Spatial ID filter (ZFXY tile)").option("--limit <n>", "Maximum number of entities to return", parseInt).option("--offset <n>", "Skip first N entities", parseInt).option("--order-by <field>", "Order results by field").option("--count", "Include total count in response").option("--count-only", "Only show the total count without listing entities").option("--key-values", "Request simplified key-value format").action(
1566
1720
  withErrorHandler(async (opts, cmd) => {
1567
1721
  const client = createClient(cmd);
1568
1722
  const format = getFormat(cmd);
@@ -1578,10 +1732,15 @@ function registerEntitiesCommand(program2) {
1578
1732
  if (opts.limit !== void 0) params.limit = String(opts.limit);
1579
1733
  if (opts.offset !== void 0) params.offset = String(opts.offset);
1580
1734
  if (opts.orderBy) params.orderBy = String(opts.orderBy);
1581
- if (opts.count) params.count = "true";
1735
+ if (opts.count || opts.countOnly) params.count = "true";
1736
+ if (opts.countOnly) params.limit = "0";
1582
1737
  if (opts.keyValues) params.options = "keyValues";
1583
1738
  const response = await client.get("/entities", params);
1584
- outputResponse(response, format, !!opts.count);
1739
+ if (opts.countOnly) {
1740
+ printCount(response.count ?? 0);
1741
+ } else {
1742
+ outputResponse(response, format, !!opts.count);
1743
+ }
1585
1744
  })
1586
1745
  );
1587
1746
  addExamples(list, [
@@ -1640,6 +1799,10 @@ function registerEntitiesCommand(program2) {
1640
1799
  {
1641
1800
  description: "Get total count with results",
1642
1801
  command: "geonic entities list --type Sensor --count"
1802
+ },
1803
+ {
1804
+ description: "Get only the total count (no entity data)",
1805
+ command: "geonic entities list --type Sensor --count-only"
1643
1806
  }
1644
1807
  ]);
1645
1808
  const get = entities.command("get").description("Get a single entity by ID").argument("<id>", "Entity ID").option("--key-values", "Request simplified key-value format").action(