@geolonia/geonicdb-cli 0.6.0 → 0.6.2

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
@@ -848,6 +848,9 @@ var GdbClient = class _GdbClient {
848
848
  async executeRawRequest(method, path, options) {
849
849
  const url = this.buildUrl(path, options?.params);
850
850
  const headers = this.buildHeaders(options?.headers);
851
+ if (options?.skipTenantHeader) {
852
+ delete headers["NGSILD-Tenant"];
853
+ }
851
854
  const body = options?.body ? JSON.stringify(options.body) : void 0;
852
855
  this.logRequest(method, url, headers, body);
853
856
  this.handleDryRun(method, url, headers, body);
@@ -985,7 +988,7 @@ function withErrorHandler(fn) {
985
988
  return;
986
989
  }
987
990
  if (err instanceof GdbClientError && err.status === 401) {
988
- printError("Authentication failed. Please run `geonic login` to re-authenticate.");
991
+ printError("Authentication failed. Please re-authenticate (e.g., `geonic auth login` or check your API key).");
989
992
  } else if (err instanceof GdbClientError && err.status === 403) {
990
993
  const detail = (err.ngsiError?.detail ?? err.ngsiError?.description ?? "").toLowerCase();
991
994
  if (detail.includes("entity type") || detail.includes("allowedentitytypes")) {
@@ -1097,6 +1100,30 @@ async function promptPassword() {
1097
1100
  stdin.on("error", onError);
1098
1101
  });
1099
1102
  }
1103
+ async function promptTenantSelection(tenants, currentTenantId) {
1104
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
1105
+ try {
1106
+ console.log("\nAvailable tenants:");
1107
+ for (let i = 0; i < tenants.length; i++) {
1108
+ const t = tenants[i];
1109
+ const current = t.tenantId === currentTenantId ? " \u2190 current" : "";
1110
+ const marker = t.tenantId === currentTenantId ? " *" : " ";
1111
+ console.log(`${marker} ${i + 1}) ${t.tenantId} (${t.role})${current}`);
1112
+ }
1113
+ for (; ; ) {
1114
+ const answer = await rl.question("\nSelect tenant number (Enter to keep current): ");
1115
+ const trimmed = answer.trim();
1116
+ if (!trimmed) return void 0;
1117
+ const index = parseInt(trimmed, 10) - 1;
1118
+ if (index >= 0 && index < tenants.length) {
1119
+ return tenants[index].tenantId;
1120
+ }
1121
+ console.log(`Invalid selection. Please enter a number between 1 and ${tenants.length}.`);
1122
+ }
1123
+ } finally {
1124
+ rl.close();
1125
+ }
1126
+ }
1100
1127
 
1101
1128
  // src/token.ts
1102
1129
  function decodeJwtPayload(token) {
@@ -1511,32 +1538,59 @@ function createLoginCommand() {
1511
1538
  printSuccess("Login successful (OAuth Client Credentials). Token saved to config.");
1512
1539
  return;
1513
1540
  }
1514
- let email = process.env.GDB_EMAIL;
1515
- let password = process.env.GDB_PASSWORD;
1516
- if (!email || !password) {
1517
- if (isInteractive()) {
1518
- if (!email) email = await promptEmail();
1519
- if (!password) password = await promptPassword();
1520
- } else {
1521
- printError(
1522
- "Set GDB_EMAIL and GDB_PASSWORD environment variables, or run in a terminal for interactive login."
1523
- );
1524
- process.exit(1);
1525
- }
1541
+ if (!globalOpts.url) {
1542
+ printError("No URL configured. Use `geonic config set url <url>` or pass --url.");
1543
+ process.exit(1);
1526
1544
  }
1545
+ try {
1546
+ validateUrl(globalOpts.url);
1547
+ } catch (err) {
1548
+ printError(err.message);
1549
+ process.exit(1);
1550
+ }
1551
+ if (!isInteractive()) {
1552
+ printError(
1553
+ "Interactive terminal required. Run `geonic auth login` in a terminal with TTY."
1554
+ );
1555
+ process.exit(1);
1556
+ }
1557
+ const email = await promptEmail();
1558
+ const password = await promptPassword();
1527
1559
  const client = createClient(cmd);
1528
1560
  const body = { email, password };
1529
1561
  if (loginOpts.tenantId) {
1530
1562
  body.tenantId = loginOpts.tenantId;
1531
1563
  }
1532
- const response = await client.rawRequest("POST", "/auth/login", { body });
1564
+ const response = await client.rawRequest("POST", "/auth/login", {
1565
+ body,
1566
+ skipTenantHeader: true
1567
+ });
1533
1568
  const data = response.data;
1534
- const token = data.accessToken ?? data.token;
1535
- const refreshToken = data.refreshToken;
1569
+ let token = data.accessToken ?? data.token;
1570
+ let refreshToken = data.refreshToken;
1536
1571
  if (!token) {
1537
1572
  printError("No token received from server.");
1538
1573
  process.exit(1);
1539
1574
  }
1575
+ const availableTenants = data.availableTenants;
1576
+ const currentTenantId = data.tenantId;
1577
+ if (availableTenants && availableTenants.length > 1 && !loginOpts.tenantId) {
1578
+ const selectedTenantId = await promptTenantSelection(availableTenants, currentTenantId);
1579
+ if (selectedTenantId && selectedTenantId !== currentTenantId) {
1580
+ const reloginResponse = await client.rawRequest("POST", "/auth/login", {
1581
+ body: { email, password, tenantId: selectedTenantId },
1582
+ skipTenantHeader: true
1583
+ });
1584
+ const reloginData = reloginResponse.data;
1585
+ const newToken = reloginData.accessToken ?? reloginData.token;
1586
+ if (!newToken) {
1587
+ printError("Re-login failed: no token received for selected tenant.");
1588
+ process.exit(1);
1589
+ }
1590
+ token = newToken;
1591
+ refreshToken = reloginData.refreshToken;
1592
+ }
1593
+ }
1540
1594
  const config = loadConfig(globalOpts.profile);
1541
1595
  config.token = token;
1542
1596
  if (refreshToken) {
@@ -1728,10 +1782,6 @@ function registerAuthCommands(program2) {
1728
1782
  description: "Login with OAuth client credentials",
1729
1783
  command: "geonic auth login --client-credentials --client-id MY_ID --client-secret MY_SECRET"
1730
1784
  },
1731
- {
1732
- description: "Login with environment variables",
1733
- command: "GDB_EMAIL=user@example.com GDB_PASSWORD=pass geonic auth login"
1734
- },
1735
1785
  {
1736
1786
  description: "Login to a specific tenant",
1737
1787
  command: "geonic auth login --tenant-id my-tenant"
@@ -1919,7 +1969,9 @@ function addAttrsSubcommands(attrs) {
1919
1969
  command: "geonic entities attrs get urn:ngsi-ld:Sensor:001 temperature"
1920
1970
  }
1921
1971
  ]);
1922
- const add = attrs.command("add").description("Add attributes to an entity").argument("<entityId>", "Entity ID").argument("[json]", "JSON payload (inline, @file, - for stdin, or omit for interactive/pipe)").action(
1972
+ const add = attrs.command("add").description(
1973
+ 'Add attributes to an entity\n\nJSON payload example:\n {"humidity": {"type": "Property", "value": 60}}'
1974
+ ).argument("<entityId>", "Entity ID").argument("[json]", "JSON payload (inline, @file, - for stdin, or omit for interactive/pipe)").action(
1923
1975
  withErrorHandler(
1924
1976
  async (entityId, json, _opts, cmd) => {
1925
1977
  const client = createClient(cmd);
@@ -1933,12 +1985,22 @@ function addAttrsSubcommands(attrs) {
1933
1985
  )
1934
1986
  );
1935
1987
  addExamples(add, [
1988
+ {
1989
+ description: "Add an attribute with inline JSON",
1990
+ command: `geonic entities attrs add urn:ngsi-ld:Sensor:001 '{"humidity":{"type":"Property","value":60}}'`
1991
+ },
1936
1992
  {
1937
1993
  description: "Add attributes from a file",
1938
1994
  command: "geonic entities attrs add urn:ngsi-ld:Sensor:001 @attrs.json"
1995
+ },
1996
+ {
1997
+ description: "Add from stdin pipe",
1998
+ command: "cat attrs.json | geonic entities attrs add urn:ngsi-ld:Sensor:001"
1939
1999
  }
1940
2000
  ]);
1941
- const attrUpdate = attrs.command("update").description("Update a specific attribute of an entity").argument("<entityId>", "Entity ID").argument("<attrName>", "Attribute name").argument("[json]", "JSON payload (inline, @file, - for stdin, or omit for interactive/pipe)").action(
2001
+ const attrUpdate = attrs.command("update").description(
2002
+ 'Update a specific attribute of an entity\n\nJSON payload example:\n {"type": "Property", "value": 25}'
2003
+ ).argument("<entityId>", "Entity ID").argument("<attrName>", "Attribute name").argument("[json]", "JSON payload (inline, @file, - for stdin, or omit for interactive/pipe)").action(
1942
2004
  withErrorHandler(
1943
2005
  async (entityId, attrName, json, _opts, cmd) => {
1944
2006
  const client = createClient(cmd);
@@ -1953,8 +2015,12 @@ function addAttrsSubcommands(attrs) {
1953
2015
  );
1954
2016
  addExamples(attrUpdate, [
1955
2017
  {
1956
- description: "Update a specific attribute",
1957
- command: `geonic entities attrs update urn:ngsi-ld:Sensor:001 temperature '{"value":25}'`
2018
+ description: "Update a Property value",
2019
+ command: `geonic entities attrs update urn:ngsi-ld:Sensor:001 temperature '{"type":"Property","value":25}'`
2020
+ },
2021
+ {
2022
+ description: "Update from a file",
2023
+ command: "geonic entities attrs update urn:ngsi-ld:Sensor:001 temperature @attr.json"
1958
2024
  }
1959
2025
  ]);
1960
2026
  const del = attrs.command("delete").description("Delete a specific attribute from an entity").argument("<entityId>", "Entity ID").argument("<attrName>", "Attribute name").action(
@@ -2097,7 +2163,9 @@ function registerEntitiesCommand(program2) {
2097
2163
  command: "geonic entities get urn:ngsi-ld:Sensor:001 --key-values"
2098
2164
  }
2099
2165
  ]);
2100
- const create = entities.command("create").description("Create a new entity").argument("[json]", "JSON payload (inline, @file, - for stdin, or omit for interactive/pipe)").action(
2166
+ const create = entities.command("create").description(
2167
+ '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 }'
2168
+ ).argument("[json]", "JSON payload (inline, @file, - for stdin, or omit for interactive/pipe)").action(
2101
2169
  withErrorHandler(async (json, _opts, cmd) => {
2102
2170
  const client = createClient(cmd);
2103
2171
  const data = await parseJsonInput(json);
@@ -2107,9 +2175,13 @@ function registerEntitiesCommand(program2) {
2107
2175
  );
2108
2176
  addExamples(create, [
2109
2177
  {
2110
- description: "Create from inline JSON",
2178
+ description: "Create from inline JSON (minimal)",
2111
2179
  command: `geonic entities create '{"id":"urn:ngsi-ld:Sensor:001","type":"Sensor"}'`
2112
2180
  },
2181
+ {
2182
+ description: "Create with attributes",
2183
+ command: `geonic entities create '{"id":"urn:ngsi-ld:Sensor:001","type":"Sensor","temperature":{"type":"Property","value":25},"location":{"type":"GeoProperty","value":{"type":"Point","coordinates":[139.77,35.68]}}}'`
2184
+ },
2113
2185
  {
2114
2186
  description: "Create from a file",
2115
2187
  command: "geonic entities create @entity.json"
@@ -2117,9 +2189,15 @@ function registerEntitiesCommand(program2) {
2117
2189
  {
2118
2190
  description: "Create from stdin pipe",
2119
2191
  command: "cat entity.json | geonic entities create"
2192
+ },
2193
+ {
2194
+ description: "Interactive mode (omit JSON argument)",
2195
+ command: "geonic entities create"
2120
2196
  }
2121
2197
  ]);
2122
- const update = entities.command("update").description("Update attributes of an entity (PATCH)").argument("<id>", "Entity ID").argument("[json]", "JSON payload (inline, @file, - for stdin, or omit for interactive/pipe)").action(
2198
+ const update = entities.command("update").description(
2199
+ 'Update attributes of an entity (PATCH)\n\nJSON payload: only specified attributes are modified.\n e.g. {"temperature": {"type": "Property", "value": 30}}'
2200
+ ).argument("<id>", "Entity ID").argument("[json]", "JSON payload (inline, @file, - for stdin, or omit for interactive/pipe)").action(
2123
2201
  withErrorHandler(
2124
2202
  async (id, json, _opts, cmd) => {
2125
2203
  const client = createClient(cmd);
@@ -2134,15 +2212,21 @@ function registerEntitiesCommand(program2) {
2134
2212
  );
2135
2213
  addExamples(update, [
2136
2214
  {
2137
- description: "Update entity attributes from inline JSON",
2138
- command: `geonic entities update urn:ngsi-ld:Sensor:001 '{"temperature":{"value":25}}'`
2215
+ description: "Update a Property attribute",
2216
+ command: `geonic entities update urn:ngsi-ld:Sensor:001 '{"temperature":{"type":"Property","value":30}}'`
2139
2217
  },
2140
2218
  {
2141
- description: "Update entity attributes from a file",
2219
+ description: "Update from a file",
2142
2220
  command: "geonic entities update urn:ngsi-ld:Sensor:001 @attrs.json"
2221
+ },
2222
+ {
2223
+ description: "Update from stdin pipe",
2224
+ command: "cat attrs.json | geonic entities update urn:ngsi-ld:Sensor:001"
2143
2225
  }
2144
2226
  ]);
2145
- const replace = entities.command("replace").description("Replace all attributes of an entity (PUT)").argument("<id>", "Entity ID").argument("[json]", "JSON payload (inline, @file, - for stdin, or omit for interactive/pipe)").action(
2227
+ const replace = entities.command("replace").description(
2228
+ 'Replace all attributes of an entity (PUT)\n\nJSON payload: all existing attributes are replaced.\n e.g. {"temperature": {"type": "Property", "value": 20}}'
2229
+ ).argument("<id>", "Entity ID").argument("[json]", "JSON payload (inline, @file, - for stdin, or omit for interactive/pipe)").action(
2146
2230
  withErrorHandler(
2147
2231
  async (id, json, _opts, cmd) => {
2148
2232
  const client = createClient(cmd);
@@ -2157,8 +2241,16 @@ function registerEntitiesCommand(program2) {
2157
2241
  );
2158
2242
  addExamples(replace, [
2159
2243
  {
2160
- description: "Replace all attributes from a file",
2244
+ description: "Replace all attributes with inline JSON",
2245
+ command: `geonic entities replace urn:ngsi-ld:Sensor:001 '{"temperature":{"type":"Property","value":20}}'`
2246
+ },
2247
+ {
2248
+ description: "Replace from a file",
2161
2249
  command: "geonic entities replace urn:ngsi-ld:Sensor:001 @attrs.json"
2250
+ },
2251
+ {
2252
+ description: "Replace from stdin pipe",
2253
+ command: "cat attrs.json | geonic entities replace urn:ngsi-ld:Sensor:001"
2162
2254
  }
2163
2255
  ]);
2164
2256
  const upsert = entities.command("upsert").description("Create or update entities").argument("[json]", "JSON payload (inline, @file, - for stdin, or omit for interactive/pipe)").action(
@@ -2198,7 +2290,9 @@ function registerEntitiesCommand(program2) {
2198
2290
  // src/commands/batch.ts
2199
2291
  function registerBatchCommand(program2) {
2200
2292
  const batch = program2.command("entityOperations").alias("batch").description("Perform batch operations on entities");
2201
- const create = batch.command("create [json]").description("Batch create entities").action(
2293
+ const create = batch.command("create [json]").description(
2294
+ 'Batch create entities\n\nJSON payload: an array of NGSI-LD entities.\n e.g. [{"id": "urn:ngsi-ld:Sensor:001", "type": "Sensor"}, ...]'
2295
+ ).action(
2202
2296
  withErrorHandler(async (json, _opts, cmd) => {
2203
2297
  const client = createClient(cmd);
2204
2298
  const format = getFormat(cmd);
@@ -2208,16 +2302,22 @@ function registerBatchCommand(program2) {
2208
2302
  })
2209
2303
  );
2210
2304
  addExamples(create, [
2305
+ {
2306
+ description: "Batch create with inline JSON",
2307
+ command: `geonic batch create '[{"id":"urn:ngsi-ld:Sensor:001","type":"Sensor"},{"id":"urn:ngsi-ld:Sensor:002","type":"Sensor"}]'`
2308
+ },
2211
2309
  {
2212
2310
  description: "Batch create from a file",
2213
2311
  command: "geonic batch create @entities.json"
2214
2312
  },
2215
2313
  {
2216
- description: "Batch create from stdin",
2314
+ description: "Batch create from stdin pipe",
2217
2315
  command: "cat entities.json | geonic batch create"
2218
2316
  }
2219
2317
  ]);
2220
- const upsert = batch.command("upsert [json]").description("Batch upsert entities").action(
2318
+ const upsert = batch.command("upsert [json]").description(
2319
+ "Batch upsert entities\n\nJSON payload: an array of NGSI-LD entities.\nCreates entities that don't exist, updates those that do."
2320
+ ).action(
2221
2321
  withErrorHandler(async (json, _opts, cmd) => {
2222
2322
  const client = createClient(cmd);
2223
2323
  const format = getFormat(cmd);
@@ -2227,16 +2327,22 @@ function registerBatchCommand(program2) {
2227
2327
  })
2228
2328
  );
2229
2329
  addExamples(upsert, [
2330
+ {
2331
+ description: "Batch upsert with inline JSON",
2332
+ command: `geonic batch upsert '[{"id":"urn:ngsi-ld:Sensor:001","type":"Sensor","temperature":{"type":"Property","value":25}}]'`
2333
+ },
2230
2334
  {
2231
2335
  description: "Batch upsert from a file",
2232
2336
  command: "geonic batch upsert @entities.json"
2233
2337
  },
2234
2338
  {
2235
- description: "Batch upsert from stdin",
2339
+ description: "Batch upsert from stdin pipe",
2236
2340
  command: "cat entities.json | geonic batch upsert"
2237
2341
  }
2238
2342
  ]);
2239
- const update = batch.command("update [json]").description("Batch update entity attributes").action(
2343
+ const update = batch.command("update [json]").description(
2344
+ "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."
2345
+ ).action(
2240
2346
  withErrorHandler(async (json, _opts, cmd) => {
2241
2347
  const client = createClient(cmd);
2242
2348
  const format = getFormat(cmd);
@@ -2255,7 +2361,9 @@ function registerBatchCommand(program2) {
2255
2361
  command: "cat updates.json | geonic batch update"
2256
2362
  }
2257
2363
  ]);
2258
- const del = batch.command("delete [json]").description("Batch delete entities by ID").action(
2364
+ const del = batch.command("delete [json]").description(
2365
+ '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"]'
2366
+ ).action(
2259
2367
  withErrorHandler(async (json, _opts, cmd) => {
2260
2368
  const client = createClient(cmd);
2261
2369
  const format = getFormat(cmd);
@@ -2265,16 +2373,22 @@ function registerBatchCommand(program2) {
2265
2373
  })
2266
2374
  );
2267
2375
  addExamples(del, [
2376
+ {
2377
+ description: "Batch delete with inline JSON",
2378
+ command: `geonic batch delete '["urn:ngsi-ld:Sensor:001","urn:ngsi-ld:Sensor:002"]'`
2379
+ },
2268
2380
  {
2269
2381
  description: "Batch delete from a file",
2270
2382
  command: "geonic batch delete @entity-ids.json"
2271
2383
  },
2272
2384
  {
2273
- description: "Batch delete from stdin",
2385
+ description: "Batch delete from stdin pipe",
2274
2386
  command: "cat entity-ids.json | geonic batch delete"
2275
2387
  }
2276
2388
  ]);
2277
- const query = batch.command("query [json]").description("Query entities by posting a query payload").action(
2389
+ const query = batch.command("query [json]").description(
2390
+ 'Query entities by posting a query payload\n\nJSON payload example:\n {\n "entities": [{"type": "Sensor"}],\n "attrs": ["temperature"],\n "q": "temperature>30"\n }'
2391
+ ).action(
2278
2392
  withErrorHandler(async (json, _opts, cmd) => {
2279
2393
  const client = createClient(cmd);
2280
2394
  const format = getFormat(cmd);
@@ -2285,15 +2399,21 @@ function registerBatchCommand(program2) {
2285
2399
  );
2286
2400
  addExamples(query, [
2287
2401
  {
2288
- description: "Query entities from a file",
2402
+ description: "Query with inline JSON",
2403
+ command: `geonic batch query '{"entities":[{"type":"Sensor"}],"attrs":["temperature"]}'`
2404
+ },
2405
+ {
2406
+ description: "Query from a file",
2289
2407
  command: "geonic batch query @query.json"
2290
2408
  },
2291
2409
  {
2292
- description: "Query entities from stdin",
2410
+ description: "Query from stdin pipe",
2293
2411
  command: "cat query.json | geonic batch query"
2294
2412
  }
2295
2413
  ]);
2296
- const merge = batch.command("merge [json]").description("Batch merge-patch entities").action(
2414
+ const merge = batch.command("merge [json]").description(
2415
+ "Batch merge-patch entities\n\nJSON payload: an array of NGSI-LD entities.\nEach entity must include id and type; attributes are merge-patched."
2416
+ ).action(
2297
2417
  withErrorHandler(async (json, _opts, cmd) => {
2298
2418
  const client = createClient(cmd);
2299
2419
  const format = getFormat(cmd);
@@ -2360,7 +2480,9 @@ function registerSubscriptionsCommand(program2) {
2360
2480
  command: "geonic subscriptions get urn:ngsi-ld:Subscription:001"
2361
2481
  }
2362
2482
  ]);
2363
- const create = subscriptions.command("create [json]").description("Create a subscription").action(
2483
+ const create = subscriptions.command("create [json]").description(
2484
+ '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 }'
2485
+ ).action(
2364
2486
  withErrorHandler(async (json, _opts, cmd) => {
2365
2487
  const client = createClient(cmd);
2366
2488
  const format = getFormat(cmd);
@@ -2371,16 +2493,26 @@ function registerSubscriptionsCommand(program2) {
2371
2493
  })
2372
2494
  );
2373
2495
  addExamples(create, [
2496
+ {
2497
+ description: "Create with inline JSON",
2498
+ command: `geonic subscriptions create '{"type":"Subscription","entities":[{"type":"Sensor"}],"watchedAttributes":["temperature"],"notification":{"endpoint":{"uri":"http://localhost:3000/notify"}}}'`
2499
+ },
2374
2500
  {
2375
2501
  description: "Create from a JSON file",
2376
2502
  command: "geonic subscriptions create @subscription.json"
2377
2503
  },
2378
2504
  {
2379
- description: "Create from stdin",
2505
+ description: "Create from stdin pipe",
2380
2506
  command: "cat subscription.json | geonic subscriptions create"
2507
+ },
2508
+ {
2509
+ description: "Interactive mode",
2510
+ command: "geonic subscriptions create"
2381
2511
  }
2382
2512
  ]);
2383
- const update = subscriptions.command("update <id> [json]").description("Update a subscription").action(
2513
+ const update = subscriptions.command("update <id> [json]").description(
2514
+ 'Update a subscription\n\nJSON payload: only specified fields are updated.\n e.g. {"description": "Updated subscription"}'
2515
+ ).action(
2384
2516
  withErrorHandler(
2385
2517
  async (id, json, _opts, cmd) => {
2386
2518
  const client = createClient(cmd);
@@ -2397,8 +2529,16 @@ function registerSubscriptionsCommand(program2) {
2397
2529
  );
2398
2530
  addExamples(update, [
2399
2531
  {
2400
- description: "Update a subscription from a file",
2532
+ description: "Update description",
2533
+ command: `geonic subscriptions update urn:ngsi-ld:Subscription:001 '{"description":"Updated subscription"}'`
2534
+ },
2535
+ {
2536
+ description: "Update from a file",
2401
2537
  command: "geonic subscriptions update urn:ngsi-ld:Subscription:001 @sub.json"
2538
+ },
2539
+ {
2540
+ description: "Update from stdin pipe",
2541
+ command: "cat sub.json | geonic subscriptions update urn:ngsi-ld:Subscription:001"
2402
2542
  }
2403
2543
  ]);
2404
2544
  const del = subscriptions.command("delete <id>").description("Delete a subscription").action(
@@ -2460,7 +2600,9 @@ function registerRegistrationsCommand(program2) {
2460
2600
  command: "geonic registrations get urn:ngsi-ld:ContextSourceRegistration:001"
2461
2601
  }
2462
2602
  ]);
2463
- const create = registrations.command("create [json]").description("Create a registration").action(
2603
+ const create = registrations.command("create [json]").description(
2604
+ 'Create a registration\n\nJSON payload example:\n {\n "type": "ContextSourceRegistration",\n "information": [{"entities": [{"type": "Room"}]}],\n "endpoint": "http://localhost:4000/source"\n }'
2605
+ ).action(
2464
2606
  withErrorHandler(async (json, _opts, cmd) => {
2465
2607
  const client = createClient(cmd);
2466
2608
  const format = getFormat(cmd);
@@ -2472,8 +2614,16 @@ function registerRegistrationsCommand(program2) {
2472
2614
  );
2473
2615
  addExamples(create, [
2474
2616
  {
2475
- description: "Create a registration from a file",
2617
+ description: "Create with inline JSON",
2618
+ command: `geonic registrations create '{"type":"ContextSourceRegistration","information":[{"entities":[{"type":"Room"}]}],"endpoint":"http://localhost:4000/source"}'`
2619
+ },
2620
+ {
2621
+ description: "Create from a file",
2476
2622
  command: "geonic registrations create @registration.json"
2623
+ },
2624
+ {
2625
+ description: "Create from stdin pipe",
2626
+ command: "cat registration.json | geonic registrations create"
2477
2627
  }
2478
2628
  ]);
2479
2629
  const regUpdate = registrations.command("update <id> [json]").description("Update a registration").action(
@@ -2493,8 +2643,16 @@ function registerRegistrationsCommand(program2) {
2493
2643
  );
2494
2644
  addExamples(regUpdate, [
2495
2645
  {
2496
- description: "Update a registration from a file",
2646
+ description: "Update endpoint",
2647
+ command: `geonic registrations update urn:ngsi-ld:ContextSourceRegistration:001 '{"endpoint":"http://localhost:5000/source"}'`
2648
+ },
2649
+ {
2650
+ description: "Update from a file",
2497
2651
  command: "geonic registrations update urn:ngsi-ld:ContextSourceRegistration:001 @registration.json"
2652
+ },
2653
+ {
2654
+ description: "Update from stdin pipe",
2655
+ command: "cat registration.json | geonic registrations update urn:ngsi-ld:ContextSourceRegistration:001"
2498
2656
  }
2499
2657
  ]);
2500
2658
  const del = registrations.command("delete <id>").description("Delete a registration").action(
@@ -2672,11 +2830,21 @@ function registerTemporalCommand(program2) {
2672
2830
  command: "geonic temporal entities get urn:ngsi-ld:Sensor:001 --last-n 10"
2673
2831
  }
2674
2832
  ]);
2675
- const create = entities.command("create [json]").description("Create a temporal entity").action(createCreateAction());
2833
+ const create = entities.command("create [json]").description(
2834
+ "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."
2835
+ ).action(createCreateAction());
2676
2836
  addExamples(create, [
2677
2837
  {
2678
- description: "Create temporal entity from a file",
2838
+ description: "Create from a file",
2679
2839
  command: "geonic temporal entities create @temporal-entity.json"
2840
+ },
2841
+ {
2842
+ description: "Create from stdin pipe",
2843
+ command: "cat temporal-entity.json | geonic temporal entities create"
2844
+ },
2845
+ {
2846
+ description: "Interactive mode",
2847
+ command: "geonic temporal entities create"
2680
2848
  }
2681
2849
  ]);
2682
2850
  const del = entities.command("delete <id>").description("Delete a temporal entity by ID").action(createDeleteAction());
@@ -2691,6 +2859,10 @@ function registerTemporalCommand(program2) {
2691
2859
  );
2692
2860
  opsQuery.action(createQueryAction());
2693
2861
  addExamples(opsQuery, [
2862
+ {
2863
+ description: "Query with inline JSON",
2864
+ command: `geonic temporal entityOperations query '{"entities":[{"type":"Sensor"}],"attrs":["temperature"]}'`
2865
+ },
2694
2866
  {
2695
2867
  description: "Query with aggregation (hourly count)",
2696
2868
  command: "geonic temporal entityOperations query @query.json --aggr-methods totalCount --aggr-period PT1H"
@@ -2838,7 +3010,9 @@ function registerTenantsCommand(parent) {
2838
3010
  command: "geonic admin tenants get <tenant-id>"
2839
3011
  }
2840
3012
  ]);
2841
- const create = tenants.command("create [json]").description("Create a new tenant").action(
3013
+ const create = tenants.command("create [json]").description(
3014
+ 'Create a new tenant\n\nJSON payload example:\n {\n "name": "production",\n "description": "Production environment tenant"\n }'
3015
+ ).action(
2842
3016
  withErrorHandler(async (json, _opts, cmd) => {
2843
3017
  const body = await parseJsonInput(json);
2844
3018
  const client = createClient(cmd);
@@ -2852,11 +3026,29 @@ function registerTenantsCommand(parent) {
2852
3026
  );
2853
3027
  addExamples(create, [
2854
3028
  {
2855
- description: "Create a tenant from a JSON file",
3029
+ description: "Create with inline JSON",
3030
+ command: `geonic admin tenants create '{"name":"my-tenant","description":"My first tenant"}'`
3031
+ },
3032
+ {
3033
+ description: "Minimal (name only)",
3034
+ command: `geonic admin tenants create '{"name":"production"}'`
3035
+ },
3036
+ {
3037
+ description: "Create from a JSON file",
2856
3038
  command: "geonic admin tenants create @tenant.json"
3039
+ },
3040
+ {
3041
+ description: "Create from stdin pipe",
3042
+ command: "cat tenant.json | geonic admin tenants create"
3043
+ },
3044
+ {
3045
+ description: "Interactive mode (omit JSON argument)",
3046
+ command: "geonic admin tenants create"
2857
3047
  }
2858
3048
  ]);
2859
- const update = tenants.command("update <id> [json]").description("Update a tenant").action(
3049
+ const update = tenants.command("update <id> [json]").description(
3050
+ 'Update a tenant\n\nJSON payload: only specified fields are updated.\n e.g. {"name": "new-name", "description": "Updated description"}'
3051
+ ).action(
2860
3052
  withErrorHandler(
2861
3053
  async (id, json, _opts, cmd) => {
2862
3054
  const body = await parseJsonInput(json);
@@ -2874,8 +3066,24 @@ function registerTenantsCommand(parent) {
2874
3066
  );
2875
3067
  addExamples(update, [
2876
3068
  {
2877
- description: "Update a tenant from a JSON file",
2878
- command: "geonic admin tenants update <tenant-id> @tenant.json"
3069
+ description: "Update description with inline JSON",
3070
+ command: `geonic admin tenants update <tenant-id> '{"description":"Updated description"}'`
3071
+ },
3072
+ {
3073
+ description: "Rename a tenant",
3074
+ command: `geonic admin tenants update <tenant-id> '{"name":"new-name"}'`
3075
+ },
3076
+ {
3077
+ description: "Update from a JSON file",
3078
+ command: "geonic admin tenants update <tenant-id> @patch.json"
3079
+ },
3080
+ {
3081
+ description: "Update from stdin pipe",
3082
+ command: "cat patch.json | geonic admin tenants update <tenant-id>"
3083
+ },
3084
+ {
3085
+ description: "Interactive mode",
3086
+ command: "geonic admin tenants update <tenant-id>"
2879
3087
  }
2880
3088
  ]);
2881
3089
  const del = tenants.command("delete <id>").description("Delete a tenant").action(
@@ -2962,7 +3170,9 @@ function registerUsersCommand(parent) {
2962
3170
  command: "geonic admin users get <user-id>"
2963
3171
  }
2964
3172
  ]);
2965
- const create = users.command("create [json]").description("Create a new user").action(
3173
+ const create = users.command("create [json]").description(
3174
+ 'Create a new user\n\nJSON payload example:\n {\n "email": "user@example.com",\n "password": "SecurePassword123!",\n "role": "super_admin"\n }'
3175
+ ).action(
2966
3176
  withErrorHandler(async (json, _opts, cmd) => {
2967
3177
  const body = await parseJsonInput(json);
2968
3178
  const client = createClient(cmd);
@@ -2976,11 +3186,21 @@ function registerUsersCommand(parent) {
2976
3186
  );
2977
3187
  addExamples(create, [
2978
3188
  {
2979
- description: "Create a user from a JSON file",
3189
+ description: "Create with inline JSON",
3190
+ command: `geonic admin users create '{"email":"user@example.com","password":"SecurePassword123!","role":"super_admin"}'`
3191
+ },
3192
+ {
3193
+ description: "Create from a JSON file",
2980
3194
  command: "geonic admin users create @user.json"
3195
+ },
3196
+ {
3197
+ description: "Create from stdin pipe",
3198
+ command: "cat user.json | geonic admin users create"
2981
3199
  }
2982
3200
  ]);
2983
- const update = users.command("update <id> [json]").description("Update a user").action(
3201
+ const update = users.command("update <id> [json]").description(
3202
+ 'Update a user\n\nJSON payload: only specified fields are updated.\n e.g. {"role": "admin"}'
3203
+ ).action(
2984
3204
  withErrorHandler(
2985
3205
  async (id, json, _opts, cmd) => {
2986
3206
  const body = await parseJsonInput(json);
@@ -2998,8 +3218,16 @@ function registerUsersCommand(parent) {
2998
3218
  );
2999
3219
  addExamples(update, [
3000
3220
  {
3001
- description: "Update a user from a JSON file",
3221
+ description: "Update role with inline JSON",
3222
+ command: `geonic admin users update <user-id> '{"role":"admin"}'`
3223
+ },
3224
+ {
3225
+ description: "Update from a JSON file",
3002
3226
  command: "geonic admin users update <user-id> @user.json"
3227
+ },
3228
+ {
3229
+ description: "Update from stdin pipe",
3230
+ command: "cat user.json | geonic admin users update <user-id>"
3003
3231
  }
3004
3232
  ]);
3005
3233
  const del = users.command("delete <id>").description("Delete a user").action(
@@ -3102,7 +3330,9 @@ function registerPoliciesCommand(parent) {
3102
3330
  command: "geonic admin policies get <policy-id>"
3103
3331
  }
3104
3332
  ]);
3105
- const create = policies.command("create [json]").description("Create a new policy").action(
3333
+ const create = policies.command("create [json]").description(
3334
+ 'Create a new policy\n\nJSON payload example:\n {\n "description": "Allow all entities",\n "rules": [{"ruleId": "allow-all", "effect": "Permit"}]\n }'
3335
+ ).action(
3106
3336
  withErrorHandler(async (json, _opts, cmd) => {
3107
3337
  const body = await parseJsonInput(json);
3108
3338
  const client = createClient(cmd);
@@ -3116,11 +3346,21 @@ function registerPoliciesCommand(parent) {
3116
3346
  );
3117
3347
  addExamples(create, [
3118
3348
  {
3119
- description: "Create a policy from a JSON file",
3349
+ description: "Create with inline JSON",
3350
+ command: `geonic admin policies create '{"description":"Allow all entities","rules":[{"ruleId":"allow-all","effect":"Permit"}]}'`
3351
+ },
3352
+ {
3353
+ description: "Create from a JSON file",
3120
3354
  command: "geonic admin policies create @policy.json"
3355
+ },
3356
+ {
3357
+ description: "Create from stdin pipe",
3358
+ command: "cat policy.json | geonic admin policies create"
3121
3359
  }
3122
3360
  ]);
3123
- const update = policies.command("update <id> [json]").description("Update a policy").action(
3361
+ const update = policies.command("update <id> [json]").description(
3362
+ 'Update a policy\n\nJSON payload: only specified fields are updated.\n e.g. {"description": "Updated policy"}'
3363
+ ).action(
3124
3364
  withErrorHandler(
3125
3365
  async (id, json, _opts, cmd) => {
3126
3366
  const body = await parseJsonInput(json);
@@ -3138,8 +3378,16 @@ function registerPoliciesCommand(parent) {
3138
3378
  );
3139
3379
  addExamples(update, [
3140
3380
  {
3141
- description: "Update a policy from a JSON file",
3381
+ description: "Update description",
3382
+ command: `geonic admin policies update <policy-id> '{"description":"Updated policy"}'`
3383
+ },
3384
+ {
3385
+ description: "Update from a JSON file",
3142
3386
  command: "geonic admin policies update <policy-id> @policy.json"
3387
+ },
3388
+ {
3389
+ description: "Update from stdin pipe",
3390
+ command: "cat policy.json | geonic admin policies update <policy-id>"
3143
3391
  }
3144
3392
  ]);
3145
3393
  const del = policies.command("delete <id>").description("Delete a policy").action(
@@ -3226,7 +3474,9 @@ function registerOAuthClientsCommand(parent) {
3226
3474
  command: "geonic admin oauth-clients get <client-id>"
3227
3475
  }
3228
3476
  ]);
3229
- const create = oauthClients.command("create [json]").description("Create a new OAuth client").action(
3477
+ const create = oauthClients.command("create [json]").description(
3478
+ 'Create a new OAuth client\n\nJSON payload example:\n {\n "clientName": "my-app",\n "allowedScopes": ["read:entities", "write:entities"]\n }'
3479
+ ).action(
3230
3480
  withErrorHandler(async (json, _opts, cmd) => {
3231
3481
  const body = await parseJsonInput(json);
3232
3482
  const client = createClient(cmd);
@@ -3240,11 +3490,21 @@ function registerOAuthClientsCommand(parent) {
3240
3490
  );
3241
3491
  addExamples(create, [
3242
3492
  {
3243
- description: "Create an OAuth client from a JSON file",
3493
+ description: "Create with inline JSON",
3494
+ command: `geonic admin oauth-clients create '{"clientName":"my-app","allowedScopes":["read:entities","write:entities"]}'`
3495
+ },
3496
+ {
3497
+ description: "Create from a JSON file",
3244
3498
  command: "geonic admin oauth-clients create @client.json"
3499
+ },
3500
+ {
3501
+ description: "Create from stdin pipe",
3502
+ command: "cat client.json | geonic admin oauth-clients create"
3245
3503
  }
3246
3504
  ]);
3247
- const update = oauthClients.command("update <id> [json]").description("Update an OAuth client").action(
3505
+ const update = oauthClients.command("update <id> [json]").description(
3506
+ 'Update an OAuth client\n\nJSON payload: only specified fields are updated.\n e.g. {"description": "Updated client"}'
3507
+ ).action(
3248
3508
  withErrorHandler(
3249
3509
  async (id, json, _opts, cmd) => {
3250
3510
  const body = await parseJsonInput(json);
@@ -3262,8 +3522,16 @@ function registerOAuthClientsCommand(parent) {
3262
3522
  );
3263
3523
  addExamples(update, [
3264
3524
  {
3265
- description: "Update an OAuth client from a JSON file",
3525
+ description: "Update description",
3526
+ command: `geonic admin oauth-clients update <client-id> '{"description":"Updated client"}'`
3527
+ },
3528
+ {
3529
+ description: "Update from a JSON file",
3266
3530
  command: "geonic admin oauth-clients update <client-id> @client.json"
3531
+ },
3532
+ {
3533
+ description: "Update from stdin pipe",
3534
+ command: "cat client.json | geonic admin oauth-clients update <client-id>"
3267
3535
  }
3268
3536
  ]);
3269
3537
  const del = oauthClients.command("delete <id>").description("Delete an OAuth client").action(
@@ -3299,7 +3567,9 @@ function registerCaddeCommand(parent) {
3299
3567
  command: "geonic admin cadde get"
3300
3568
  }
3301
3569
  ]);
3302
- const caddeSet = cadde.command("set [json]").description("Set CADDE configuration").action(
3570
+ const caddeSet = cadde.command("set [json]").description(
3571
+ 'Set CADDE configuration\n\nJSON payload example:\n {\n "provider": "my-provider",\n "endpoint": "http://localhost:6000"\n }'
3572
+ ).action(
3303
3573
  withErrorHandler(async (json, _opts, cmd) => {
3304
3574
  const body = await parseJsonInput(json);
3305
3575
  const client = createClient(cmd);
@@ -3313,8 +3583,16 @@ function registerCaddeCommand(parent) {
3313
3583
  );
3314
3584
  addExamples(caddeSet, [
3315
3585
  {
3316
- description: "Set CADDE configuration from a JSON file",
3586
+ description: "Set with inline JSON",
3587
+ command: `geonic admin cadde set '{"provider":"my-provider","endpoint":"http://localhost:6000"}'`
3588
+ },
3589
+ {
3590
+ description: "Set from a JSON file",
3317
3591
  command: "geonic admin cadde set @cadde-config.json"
3592
+ },
3593
+ {
3594
+ description: "Set from stdin pipe",
3595
+ command: "cat cadde-config.json | geonic admin cadde set"
3318
3596
  }
3319
3597
  ]);
3320
3598
  const caddeDelete = cadde.command("delete").description("Delete CADDE configuration").action(
@@ -3574,7 +3852,9 @@ function registerRulesCommand(program2) {
3574
3852
  command: "geonic rules get <rule-id>"
3575
3853
  }
3576
3854
  ]);
3577
- const create = rules.command("create [json]").description("Create a new rule").action(
3855
+ const create = rules.command("create [json]").description(
3856
+ '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 }'
3857
+ ).action(
3578
3858
  withErrorHandler(async (json, _opts, cmd) => {
3579
3859
  const body = await parseJsonInput(json);
3580
3860
  const client = createClient(cmd);
@@ -3586,11 +3866,21 @@ function registerRulesCommand(program2) {
3586
3866
  );
3587
3867
  addExamples(create, [
3588
3868
  {
3589
- description: "Create a rule from a file",
3869
+ description: "Create with inline JSON",
3870
+ command: `geonic rules create '{"name":"high-temp-alert","conditions":[{"type":"celExpression","expression":"entity.temperature > 30"}],"actions":[{"type":"webhook","url":"http://localhost:5000/alert","method":"POST"}]}'`
3871
+ },
3872
+ {
3873
+ description: "Create from a file",
3590
3874
  command: "geonic rules create @rule.json"
3875
+ },
3876
+ {
3877
+ description: "Create from stdin pipe",
3878
+ command: "cat rule.json | geonic rules create"
3591
3879
  }
3592
3880
  ]);
3593
- const update = rules.command("update <id> [json]").description("Update a rule").action(
3881
+ const update = rules.command("update <id> [json]").description(
3882
+ 'Update a rule\n\nJSON payload: only specified fields are updated.\n e.g. {"description": "Updated rule"}'
3883
+ ).action(
3594
3884
  withErrorHandler(
3595
3885
  async (id, json, _opts, cmd) => {
3596
3886
  const body = await parseJsonInput(json);
@@ -3608,8 +3898,16 @@ function registerRulesCommand(program2) {
3608
3898
  );
3609
3899
  addExamples(update, [
3610
3900
  {
3611
- description: "Update a rule from a file",
3901
+ description: "Update description",
3902
+ command: `geonic rules update <rule-id> '{"description":"Updated rule"}'`
3903
+ },
3904
+ {
3905
+ description: "Update from a file",
3612
3906
  command: "geonic rules update <rule-id> @rule.json"
3907
+ },
3908
+ {
3909
+ description: "Update from stdin pipe",
3910
+ command: "cat rule.json | geonic rules update <rule-id>"
3613
3911
  }
3614
3912
  ]);
3615
3913
  const del = rules.command("delete <id>").description("Delete a rule").action(
@@ -3696,7 +3994,9 @@ function registerModelsCommand(program2) {
3696
3994
  command: "geonic models get <model-id>"
3697
3995
  }
3698
3996
  ]);
3699
- const create = models.command("create [json]").description("Create a new model").action(
3997
+ const create = models.command("create [json]").description(
3998
+ '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 }'
3999
+ ).action(
3700
4000
  withErrorHandler(async (json, _opts, cmd) => {
3701
4001
  const body = await parseJsonInput(json);
3702
4002
  const client = createClient(cmd);
@@ -3708,11 +4008,21 @@ function registerModelsCommand(program2) {
3708
4008
  );
3709
4009
  addExamples(create, [
3710
4010
  {
3711
- description: "Create a model from a file",
4011
+ description: "Create with inline JSON",
4012
+ command: `geonic models create '{"type":"Sensor","domain":"iot","description":"IoT Sensor","propertyDetails":{"temperature":{"ngsiType":"Property","valueType":"Number","example":25}}}'`
4013
+ },
4014
+ {
4015
+ description: "Create from a file",
3712
4016
  command: "geonic models create @model.json"
4017
+ },
4018
+ {
4019
+ description: "Create from stdin pipe",
4020
+ command: "cat model.json | geonic models create"
3713
4021
  }
3714
4022
  ]);
3715
- const update = models.command("update <id> [json]").description("Update a model").action(
4023
+ const update = models.command("update <id> [json]").description(
4024
+ 'Update a model\n\nJSON payload: only specified fields are updated.\n e.g. {"description": "Updated model"}'
4025
+ ).action(
3716
4026
  withErrorHandler(
3717
4027
  async (id, json, _opts, cmd) => {
3718
4028
  const body = await parseJsonInput(json);
@@ -3730,8 +4040,16 @@ function registerModelsCommand(program2) {
3730
4040
  );
3731
4041
  addExamples(update, [
3732
4042
  {
3733
- description: "Update a model from a file",
4043
+ description: "Update description",
4044
+ command: `geonic models update <model-id> '{"description":"Updated description"}'`
4045
+ },
4046
+ {
4047
+ description: "Update from a file",
3734
4048
  command: "geonic models update <model-id> @model.json"
4049
+ },
4050
+ {
4051
+ description: "Update from stdin pipe",
4052
+ command: "cat model.json | geonic models update <model-id>"
3735
4053
  }
3736
4054
  ]);
3737
4055
  const del = models.command("delete <id>").description("Delete a model").action(