@geolonia/geonicdb-cli 0.8.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 +704 -206
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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",
|
|
@@ -186,6 +187,15 @@ function printInfo(message) {
|
|
|
186
187
|
function printWarning(message) {
|
|
187
188
|
console.error(chalk.yellow(message));
|
|
188
189
|
}
|
|
190
|
+
function printApiKeyBox(key) {
|
|
191
|
+
const border = "\u2500".repeat(key.length + 4);
|
|
192
|
+
console.error("");
|
|
193
|
+
console.error(chalk.green(` \u250C${border}\u2510`));
|
|
194
|
+
console.error(chalk.green(` \u2502 ${chalk.bold(key)} \u2502`));
|
|
195
|
+
console.error(chalk.green(` \u2514${border}\u2518`));
|
|
196
|
+
console.error("");
|
|
197
|
+
console.error(chalk.yellow("\u26A0 \u3053\u306E API \u30AD\u30FC\u5024\u3092\u5B89\u5168\u306B\u4FDD\u5B58\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u4E8C\u5EA6\u3068\u8868\u793A\u3055\u308C\u307E\u305B\u3093\u3002"));
|
|
198
|
+
}
|
|
189
199
|
function printCount(count) {
|
|
190
200
|
console.log(chalk.dim(`Count: ${count}`));
|
|
191
201
|
}
|
|
@@ -583,7 +593,7 @@ function registerConfigCommand(program2) {
|
|
|
583
593
|
command: "geonic config get url --profile staging"
|
|
584
594
|
}
|
|
585
595
|
]);
|
|
586
|
-
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) => {
|
|
587
597
|
const cmd = args[args.length - 1];
|
|
588
598
|
const profile = cmd.optsWithGlobals().profile;
|
|
589
599
|
const all = loadConfig(profile);
|
|
@@ -597,9 +607,13 @@ function registerConfigCommand(program2) {
|
|
|
597
607
|
{
|
|
598
608
|
description: "List all configuration values",
|
|
599
609
|
command: "geonic config list"
|
|
610
|
+
},
|
|
611
|
+
{
|
|
612
|
+
description: "List config for a specific profile",
|
|
613
|
+
command: "geonic config list --profile staging"
|
|
600
614
|
}
|
|
601
615
|
]);
|
|
602
|
-
const del = config.command("delete").description("
|
|
616
|
+
const del = config.command("delete").description("Remove a config key from the active (or specified) profile").argument("<key>", "Configuration key").action((...args) => {
|
|
603
617
|
const cmd = args[args.length - 1];
|
|
604
618
|
const key = args[0];
|
|
605
619
|
const profile = cmd.optsWithGlobals().profile;
|
|
@@ -608,8 +622,16 @@ function registerConfigCommand(program2) {
|
|
|
608
622
|
});
|
|
609
623
|
addExamples(del, [
|
|
610
624
|
{
|
|
611
|
-
description: "
|
|
625
|
+
description: "Remove the saved server URL",
|
|
612
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"
|
|
613
635
|
}
|
|
614
636
|
]);
|
|
615
637
|
}
|
|
@@ -664,6 +686,7 @@ var GdbClient = class _GdbClient {
|
|
|
664
686
|
clientId;
|
|
665
687
|
clientSecret;
|
|
666
688
|
onTokenRefresh;
|
|
689
|
+
onBeforeRefresh;
|
|
667
690
|
verbose;
|
|
668
691
|
dryRun;
|
|
669
692
|
refreshPromise;
|
|
@@ -676,6 +699,7 @@ var GdbClient = class _GdbClient {
|
|
|
676
699
|
this.clientId = options.clientId;
|
|
677
700
|
this.clientSecret = options.clientSecret;
|
|
678
701
|
this.onTokenRefresh = options.onTokenRefresh;
|
|
702
|
+
this.onBeforeRefresh = options.onBeforeRefresh;
|
|
679
703
|
this.verbose = options.verbose ?? false;
|
|
680
704
|
this.dryRun = options.dryRun ?? false;
|
|
681
705
|
}
|
|
@@ -804,6 +828,17 @@ var GdbClient = class _GdbClient {
|
|
|
804
828
|
}
|
|
805
829
|
}
|
|
806
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
|
+
}
|
|
807
842
|
if (this.refreshToken) {
|
|
808
843
|
try {
|
|
809
844
|
const url = this.buildUrl("/auth/refresh");
|
|
@@ -985,6 +1020,10 @@ function createClient(cmd) {
|
|
|
985
1020
|
if (refreshToken) cfg.refreshToken = refreshToken;
|
|
986
1021
|
saveConfig(cfg, opts.profile);
|
|
987
1022
|
},
|
|
1023
|
+
onBeforeRefresh: usingCliToken ? void 0 : () => {
|
|
1024
|
+
const cfg = loadConfig(opts.profile);
|
|
1025
|
+
return { token: cfg.token, refreshToken: cfg.refreshToken };
|
|
1026
|
+
},
|
|
988
1027
|
verbose: opts.verbose,
|
|
989
1028
|
dryRun: opts.dryRun
|
|
990
1029
|
});
|
|
@@ -1132,7 +1171,8 @@ async function promptTenantSelection(tenants, currentTenantId) {
|
|
|
1132
1171
|
const t = tenants[i];
|
|
1133
1172
|
const current = t.tenantId === currentTenantId ? " \u2190 current" : "";
|
|
1134
1173
|
const marker = t.tenantId === currentTenantId ? " *" : " ";
|
|
1135
|
-
|
|
1174
|
+
const label = t.name ? `${t.name} (${t.tenantId})` : t.tenantId;
|
|
1175
|
+
console.log(`${marker} ${i + 1}) ${label} [${t.role}]${current}`);
|
|
1136
1176
|
}
|
|
1137
1177
|
for (; ; ) {
|
|
1138
1178
|
const answer = await rl.question("\nSelect tenant number (Enter to keep current): ");
|
|
@@ -1319,6 +1359,10 @@ function addMeOAuthClientsSubcommand(me) {
|
|
|
1319
1359
|
{
|
|
1320
1360
|
description: "List your OAuth clients",
|
|
1321
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"
|
|
1322
1366
|
}
|
|
1323
1367
|
]);
|
|
1324
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(
|
|
@@ -1439,7 +1483,7 @@ function addMeOAuthClientsSubcommand(me) {
|
|
|
1439
1483
|
command: "geonic me oauth-clients update <client-id> --inactive"
|
|
1440
1484
|
}
|
|
1441
1485
|
]);
|
|
1442
|
-
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(
|
|
1443
1487
|
withErrorHandler(async (id, _opts, cmd) => {
|
|
1444
1488
|
const client = createClient(cmd);
|
|
1445
1489
|
await client.rawRequest(
|
|
@@ -1451,11 +1495,15 @@ function addMeOAuthClientsSubcommand(me) {
|
|
|
1451
1495
|
);
|
|
1452
1496
|
addExamples(del, [
|
|
1453
1497
|
{
|
|
1454
|
-
description: "Delete an OAuth client",
|
|
1498
|
+
description: "Delete an OAuth client by ID",
|
|
1455
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"
|
|
1456
1504
|
}
|
|
1457
1505
|
]);
|
|
1458
|
-
const regenerateSecret = oauthClients.command("regenerate-secret <clientId>").description("Regenerate the client secret
|
|
1506
|
+
const regenerateSecret = oauthClients.command("regenerate-secret <clientId>").description("Regenerate the client secret \u2014 the old secret is immediately invalidated").action(
|
|
1459
1507
|
withErrorHandler(async (clientId, _opts, cmd) => {
|
|
1460
1508
|
const client = createClient(cmd);
|
|
1461
1509
|
const format = getFormat(cmd);
|
|
@@ -1472,11 +1520,54 @@ function addMeOAuthClientsSubcommand(me) {
|
|
|
1472
1520
|
{
|
|
1473
1521
|
description: "Regenerate client secret",
|
|
1474
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"
|
|
1475
1527
|
}
|
|
1476
1528
|
]);
|
|
1477
1529
|
}
|
|
1478
1530
|
|
|
1479
1531
|
// src/commands/me-api-keys.ts
|
|
1532
|
+
function cleanApiKeyData(data) {
|
|
1533
|
+
if (Array.isArray(data)) return data.map(cleanApiKeyData);
|
|
1534
|
+
if (typeof data !== "object" || data === null) return data;
|
|
1535
|
+
const obj = { ...data };
|
|
1536
|
+
if (obj.key === "******") delete obj.key;
|
|
1537
|
+
return obj;
|
|
1538
|
+
}
|
|
1539
|
+
function handleSaveKey(data, cmd) {
|
|
1540
|
+
const globalOpts = resolveOptions(cmd);
|
|
1541
|
+
const key = data.key;
|
|
1542
|
+
if (!key) {
|
|
1543
|
+
printError("Response missing key. API key was created, but it could not be saved.");
|
|
1544
|
+
process.exitCode = 1;
|
|
1545
|
+
return false;
|
|
1546
|
+
}
|
|
1547
|
+
try {
|
|
1548
|
+
const config = loadConfig(globalOpts.profile);
|
|
1549
|
+
config.apiKey = key;
|
|
1550
|
+
saveConfig(config, globalOpts.profile);
|
|
1551
|
+
console.error("API key saved to config. X-Api-Key header will be sent automatically.");
|
|
1552
|
+
return true;
|
|
1553
|
+
} catch (err) {
|
|
1554
|
+
printError(`Failed to save API key to config: ${err instanceof Error ? err.message : String(err)}`);
|
|
1555
|
+
printApiKeyBox(key);
|
|
1556
|
+
process.exitCode = 1;
|
|
1557
|
+
return false;
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
function showKeyResult(data, save, cmd) {
|
|
1561
|
+
const key = data.key;
|
|
1562
|
+
if (!key) {
|
|
1563
|
+
printError("Response missing key. The new API key value was not returned.");
|
|
1564
|
+
process.exitCode = 1;
|
|
1565
|
+
return false;
|
|
1566
|
+
}
|
|
1567
|
+
if (save) return handleSaveKey(data, cmd);
|
|
1568
|
+
printApiKeyBox(key);
|
|
1569
|
+
return true;
|
|
1570
|
+
}
|
|
1480
1571
|
function addMeApiKeysSubcommand(me) {
|
|
1481
1572
|
const apiKeys = me.command("api-keys").description("Manage your API keys");
|
|
1482
1573
|
const list = apiKeys.command("list").description("List your API keys").action(
|
|
@@ -1484,13 +1575,19 @@ function addMeApiKeysSubcommand(me) {
|
|
|
1484
1575
|
const client = createClient(cmd);
|
|
1485
1576
|
const format = getFormat(cmd);
|
|
1486
1577
|
const response = await client.rawRequest("GET", "/me/api-keys");
|
|
1578
|
+
response.data = cleanApiKeyData(response.data);
|
|
1487
1579
|
outputResponse(response, format);
|
|
1580
|
+
console.error("\u203B API \u30AD\u30FC\u5024\u306F\u4F5C\u6210\u6642 (create) \u307E\u305F\u306F\u30EA\u30D5\u30EC\u30C3\u30B7\u30E5\u6642 (refresh) \u306B\u306E\u307F\u8868\u793A\u3055\u308C\u307E\u3059\u3002");
|
|
1488
1581
|
})
|
|
1489
1582
|
);
|
|
1490
1583
|
addExamples(list, [
|
|
1491
1584
|
{
|
|
1492
1585
|
description: "List your API keys",
|
|
1493
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"
|
|
1494
1591
|
}
|
|
1495
1592
|
]);
|
|
1496
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(
|
|
@@ -1540,24 +1637,9 @@ function addMeApiKeysSubcommand(me) {
|
|
|
1540
1637
|
const format = getFormat(cmd);
|
|
1541
1638
|
const response = await client.rawRequest("POST", "/me/api-keys", { body });
|
|
1542
1639
|
const data = response.data;
|
|
1543
|
-
|
|
1544
|
-
const globalOpts = resolveOptions(cmd);
|
|
1545
|
-
const key = data.key;
|
|
1546
|
-
if (!key) {
|
|
1547
|
-
printError("Response missing key. API key was created, but it could not be saved.");
|
|
1548
|
-
outputResponse(response, format);
|
|
1549
|
-
process.exitCode = 1;
|
|
1550
|
-
return;
|
|
1551
|
-
}
|
|
1552
|
-
const config = loadConfig(globalOpts.profile);
|
|
1553
|
-
config.apiKey = key;
|
|
1554
|
-
saveConfig(config, globalOpts.profile);
|
|
1555
|
-
console.error("API key saved to config. X-Api-Key header will be sent automatically.");
|
|
1556
|
-
} else {
|
|
1557
|
-
printWarning("Save the API key now \u2014 it will not be shown again. Use --save to store it automatically.");
|
|
1558
|
-
}
|
|
1640
|
+
const ok = showKeyResult(data, !!opts.save, cmd);
|
|
1559
1641
|
outputResponse(response, format);
|
|
1560
|
-
console.error("API key created.");
|
|
1642
|
+
if (ok) console.error("API key created.");
|
|
1561
1643
|
})
|
|
1562
1644
|
);
|
|
1563
1645
|
addNotes(create, [
|
|
@@ -1586,6 +1668,35 @@ function addMeApiKeysSubcommand(me) {
|
|
|
1586
1668
|
command: "geonic me api-keys create --name my-app --dpop-required"
|
|
1587
1669
|
}
|
|
1588
1670
|
]);
|
|
1671
|
+
const refresh = apiKeys.command("refresh <keyId>").description("Refresh (rotate) an API key \u2014 generates a new key value").option("--save", "Save the new API key to config for automatic use").action(
|
|
1672
|
+
withErrorHandler(async (keyId, _opts, cmd) => {
|
|
1673
|
+
const opts = cmd.opts();
|
|
1674
|
+
const client = createClient(cmd);
|
|
1675
|
+
const format = getFormat(cmd);
|
|
1676
|
+
const response = await client.rawRequest(
|
|
1677
|
+
"POST",
|
|
1678
|
+
`/me/api-keys/${encodeURIComponent(String(keyId))}/refresh`
|
|
1679
|
+
);
|
|
1680
|
+
const data = response.data;
|
|
1681
|
+
const ok = showKeyResult(data, !!opts.save, cmd);
|
|
1682
|
+
outputResponse(response, format);
|
|
1683
|
+
if (ok) console.error("API key refreshed.");
|
|
1684
|
+
})
|
|
1685
|
+
);
|
|
1686
|
+
addNotes(refresh, [
|
|
1687
|
+
"Refreshing generates a new key value while keeping keyId, name, and policy settings.",
|
|
1688
|
+
"The previous key value is immediately invalidated."
|
|
1689
|
+
]);
|
|
1690
|
+
addExamples(refresh, [
|
|
1691
|
+
{
|
|
1692
|
+
description: "Refresh an API key",
|
|
1693
|
+
command: "geonic me api-keys refresh <key-id>"
|
|
1694
|
+
},
|
|
1695
|
+
{
|
|
1696
|
+
description: "Refresh and save new key to config",
|
|
1697
|
+
command: "geonic me api-keys refresh <key-id> --save"
|
|
1698
|
+
}
|
|
1699
|
+
]);
|
|
1589
1700
|
const update = apiKeys.command("update <keyId> [json]").description("Update an API key").option("--name <name>", "Key name").option("--policy-id <policyId>", "Policy ID to attach (use 'null' to unbind)").option("--origins <origins>", "Allowed origins (comma-separated)").option("--rate-limit <n>", "Rate limit (requests per minute)").option("--dpop-required", "Require DPoP token binding").option("--no-dpop-required", "Disable DPoP requirement").option("--active", "Activate the API key").option("--inactive", "Deactivate the API key").action(
|
|
1590
1701
|
withErrorHandler(async (keyId, json, _opts, cmd) => {
|
|
1591
1702
|
const opts = cmd.opts();
|
|
@@ -1657,7 +1768,7 @@ function addMeApiKeysSubcommand(me) {
|
|
|
1657
1768
|
command: `geonic me api-keys update <key-id> '{"name":"new-name","rateLimit":{"perMinute":60}}'`
|
|
1658
1769
|
}
|
|
1659
1770
|
]);
|
|
1660
|
-
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(
|
|
1661
1772
|
withErrorHandler(async (keyId, _opts, cmd) => {
|
|
1662
1773
|
const client = createClient(cmd);
|
|
1663
1774
|
await client.rawRequest(
|
|
@@ -1669,8 +1780,12 @@ function addMeApiKeysSubcommand(me) {
|
|
|
1669
1780
|
);
|
|
1670
1781
|
addExamples(del, [
|
|
1671
1782
|
{
|
|
1672
|
-
description: "Delete an API key",
|
|
1783
|
+
description: "Delete an API key by ID",
|
|
1673
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"
|
|
1674
1789
|
}
|
|
1675
1790
|
]);
|
|
1676
1791
|
}
|
|
@@ -1690,9 +1805,13 @@ function addMePoliciesSubcommand(me) {
|
|
|
1690
1805
|
{
|
|
1691
1806
|
description: "List your personal policies",
|
|
1692
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"
|
|
1693
1812
|
}
|
|
1694
1813
|
]);
|
|
1695
|
-
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(
|
|
1696
1815
|
withErrorHandler(async (policyId, _opts, cmd) => {
|
|
1697
1816
|
const client = createClient(cmd);
|
|
1698
1817
|
const format = getFormat(cmd);
|
|
@@ -1707,9 +1826,13 @@ function addMePoliciesSubcommand(me) {
|
|
|
1707
1826
|
{
|
|
1708
1827
|
description: "Get a personal policy by ID",
|
|
1709
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"
|
|
1710
1833
|
}
|
|
1711
1834
|
]);
|
|
1712
|
-
const create = policies.command("create [json]").description(
|
|
1835
|
+
const create = policies.command("create [json]").summary("Create a personal XACML policy").description(
|
|
1713
1836
|
`Create a personal XACML policy
|
|
1714
1837
|
|
|
1715
1838
|
Constraints (enforced server-side):
|
|
@@ -1800,7 +1923,7 @@ Example \u2014 GET-only policy for /v2/**:
|
|
|
1800
1923
|
command: "geonic me policies update <policy-id> @patch.json"
|
|
1801
1924
|
}
|
|
1802
1925
|
]);
|
|
1803
|
-
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(
|
|
1804
1927
|
withErrorHandler(async (policyId, _opts, cmd) => {
|
|
1805
1928
|
const client = createClient(cmd);
|
|
1806
1929
|
await client.rawRequest(
|
|
@@ -1812,8 +1935,12 @@ Example \u2014 GET-only policy for /v2/**:
|
|
|
1812
1935
|
);
|
|
1813
1936
|
addExamples(del, [
|
|
1814
1937
|
{
|
|
1815
|
-
description: "Delete a personal policy",
|
|
1938
|
+
description: "Delete a personal policy by ID",
|
|
1816
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"
|
|
1817
1944
|
}
|
|
1818
1945
|
]);
|
|
1819
1946
|
}
|
|
@@ -1871,8 +1998,10 @@ function createLoginCommand() {
|
|
|
1871
1998
|
const password = await promptPassword();
|
|
1872
1999
|
const client = createClient(cmd);
|
|
1873
2000
|
const body = { email, password };
|
|
1874
|
-
|
|
1875
|
-
|
|
2001
|
+
const requestTenantId = loginOpts.tenantId;
|
|
2002
|
+
const serviceFlag = globalOpts.service;
|
|
2003
|
+
if (requestTenantId) {
|
|
2004
|
+
body.tenantId = requestTenantId;
|
|
1876
2005
|
}
|
|
1877
2006
|
const response = await client.rawRequest("POST", "/auth/login", {
|
|
1878
2007
|
body,
|
|
@@ -1886,12 +2015,28 @@ function createLoginCommand() {
|
|
|
1886
2015
|
process.exit(1);
|
|
1887
2016
|
}
|
|
1888
2017
|
const availableTenants = data.availableTenants;
|
|
1889
|
-
|
|
1890
|
-
if (availableTenants && availableTenants.length > 1 && !
|
|
1891
|
-
|
|
1892
|
-
if (
|
|
2018
|
+
let finalTenantId = data.tenantId;
|
|
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) {
|
|
1893
2038
|
const reloginResponse = await client.rawRequest("POST", "/auth/login", {
|
|
1894
|
-
body: { email, password, tenantId:
|
|
2039
|
+
body: { email, password, tenantId: resolvedTenantId },
|
|
1895
2040
|
skipTenantHeader: true
|
|
1896
2041
|
});
|
|
1897
2042
|
const reloginData = reloginResponse.data;
|
|
@@ -1902,6 +2047,7 @@ function createLoginCommand() {
|
|
|
1902
2047
|
}
|
|
1903
2048
|
token = newToken;
|
|
1904
2049
|
refreshToken = reloginData.refreshToken;
|
|
2050
|
+
finalTenantId = resolvedTenantId;
|
|
1905
2051
|
}
|
|
1906
2052
|
}
|
|
1907
2053
|
const config = loadConfig(globalOpts.profile);
|
|
@@ -1911,13 +2057,30 @@ function createLoginCommand() {
|
|
|
1911
2057
|
} else {
|
|
1912
2058
|
delete config.refreshToken;
|
|
1913
2059
|
}
|
|
2060
|
+
if (finalTenantId) {
|
|
2061
|
+
config.service = finalTenantId;
|
|
2062
|
+
config.tenantId = finalTenantId;
|
|
2063
|
+
} else {
|
|
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;
|
|
2075
|
+
}
|
|
1914
2076
|
saveConfig(config, globalOpts.profile);
|
|
1915
|
-
|
|
2077
|
+
const tenantLabel = finalTenantId ? ` (tenant: ${availableTenants?.find((t) => t.tenantId === finalTenantId)?.name ?? finalTenantId})` : "";
|
|
2078
|
+
printSuccess(`Login successful${tenantLabel}. Token saved to config.`);
|
|
1916
2079
|
})
|
|
1917
2080
|
);
|
|
1918
2081
|
}
|
|
1919
2082
|
function createLogoutCommand() {
|
|
1920
|
-
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(
|
|
1921
2084
|
withErrorHandler(async (...args) => {
|
|
1922
2085
|
const cmd = args[args.length - 1];
|
|
1923
2086
|
const globalOpts = resolveOptions(cmd);
|
|
@@ -1990,7 +2153,7 @@ async function fetchNonce(baseUrl, apiKey) {
|
|
|
1990
2153
|
return await response.json();
|
|
1991
2154
|
}
|
|
1992
2155
|
function createNonceCommand() {
|
|
1993
|
-
return new Command("nonce").description("
|
|
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(
|
|
1994
2157
|
withErrorHandler(async (...args) => {
|
|
1995
2158
|
const cmd = args[args.length - 1];
|
|
1996
2159
|
const nonceOpts = cmd.opts();
|
|
@@ -2032,7 +2195,7 @@ function solvePoW(challenge, difficulty) {
|
|
|
2032
2195
|
throw new Error(`PoW could not be solved within ${MAX_POW_ITERATIONS} iterations`);
|
|
2033
2196
|
}
|
|
2034
2197
|
function createTokenExchangeCommand() {
|
|
2035
|
-
return new Command("token-exchange").description("Exchange API key for a session JWT
|
|
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(
|
|
2036
2199
|
withErrorHandler(async (...args) => {
|
|
2037
2200
|
const cmd = args[args.length - 1];
|
|
2038
2201
|
const exchangeOpts = cmd.opts();
|
|
@@ -2096,8 +2259,12 @@ function registerAuthCommands(program2) {
|
|
|
2096
2259
|
command: "geonic auth login --client-credentials --client-id MY_ID --client-secret MY_SECRET"
|
|
2097
2260
|
},
|
|
2098
2261
|
{
|
|
2099
|
-
description: "Login to a specific tenant",
|
|
2262
|
+
description: "Login to a specific tenant by ID",
|
|
2100
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"
|
|
2101
2268
|
}
|
|
2102
2269
|
]);
|
|
2103
2270
|
auth.addCommand(login);
|
|
@@ -2106,6 +2273,10 @@ function registerAuthCommands(program2) {
|
|
|
2106
2273
|
{
|
|
2107
2274
|
description: "Clear saved authentication token",
|
|
2108
2275
|
command: "geonic auth logout"
|
|
2276
|
+
},
|
|
2277
|
+
{
|
|
2278
|
+
description: "Logout from a specific profile",
|
|
2279
|
+
command: "geonic auth logout --profile staging"
|
|
2109
2280
|
}
|
|
2110
2281
|
]);
|
|
2111
2282
|
auth.addCommand(logout);
|
|
@@ -2114,14 +2285,26 @@ function registerAuthCommands(program2) {
|
|
|
2114
2285
|
{
|
|
2115
2286
|
description: "Get a nonce for API key authentication",
|
|
2116
2287
|
command: "geonic auth nonce --api-key gdb_abcdef..."
|
|
2288
|
+
},
|
|
2289
|
+
{
|
|
2290
|
+
description: "Use a pre-configured API key",
|
|
2291
|
+
command: "geonic auth nonce"
|
|
2117
2292
|
}
|
|
2118
2293
|
]);
|
|
2119
2294
|
auth.addCommand(nonce);
|
|
2120
2295
|
const tokenExchange = createTokenExchangeCommand();
|
|
2121
2296
|
addExamples(tokenExchange, [
|
|
2122
2297
|
{
|
|
2123
|
-
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",
|
|
2124
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"
|
|
2125
2308
|
}
|
|
2126
2309
|
]);
|
|
2127
2310
|
auth.addCommand(tokenExchange);
|
|
@@ -2158,8 +2341,8 @@ function registerAuthCommands(program2) {
|
|
|
2158
2341
|
|
|
2159
2342
|
// src/commands/profile.ts
|
|
2160
2343
|
function registerProfileCommands(program2) {
|
|
2161
|
-
const profile = program2.command("profile").description("Manage connection profiles");
|
|
2162
|
-
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(() => {
|
|
2163
2346
|
const profiles = listProfiles();
|
|
2164
2347
|
for (const p of profiles) {
|
|
2165
2348
|
const marker = p.active ? " *" : "";
|
|
@@ -2172,22 +2355,57 @@ function registerProfileCommands(program2) {
|
|
|
2172
2355
|
command: "geonic profile list"
|
|
2173
2356
|
}
|
|
2174
2357
|
]);
|
|
2175
|
-
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) => {
|
|
2176
2359
|
try {
|
|
2177
2360
|
setCurrentProfile(name);
|
|
2178
|
-
printSuccess(`Switched to profile "${name}".`);
|
|
2179
2361
|
} catch (err) {
|
|
2180
2362
|
printError(err.message);
|
|
2181
2363
|
process.exit(1);
|
|
2182
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}.`);
|
|
2183
2397
|
});
|
|
2184
2398
|
addExamples(use, [
|
|
2185
2399
|
{
|
|
2186
2400
|
description: "Switch to staging profile",
|
|
2187
2401
|
command: "geonic profile use staging"
|
|
2402
|
+
},
|
|
2403
|
+
{
|
|
2404
|
+
description: "Switch to production profile",
|
|
2405
|
+
command: "geonic profile use production"
|
|
2188
2406
|
}
|
|
2189
2407
|
]);
|
|
2190
|
-
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) => {
|
|
2191
2409
|
try {
|
|
2192
2410
|
createProfile(name);
|
|
2193
2411
|
printSuccess(`Profile "${name}" created.`);
|
|
@@ -2200,9 +2418,13 @@ function registerProfileCommands(program2) {
|
|
|
2200
2418
|
{
|
|
2201
2419
|
description: "Create a new profile for staging",
|
|
2202
2420
|
command: "geonic profile create staging"
|
|
2421
|
+
},
|
|
2422
|
+
{
|
|
2423
|
+
description: "Create a profile for a different tenant",
|
|
2424
|
+
command: "geonic profile create tenant-b"
|
|
2203
2425
|
}
|
|
2204
2426
|
]);
|
|
2205
|
-
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) => {
|
|
2206
2428
|
try {
|
|
2207
2429
|
deleteProfile(name);
|
|
2208
2430
|
printSuccess(`Profile "${name}" deleted.`);
|
|
@@ -2213,11 +2435,11 @@ function registerProfileCommands(program2) {
|
|
|
2213
2435
|
});
|
|
2214
2436
|
addExamples(del, [
|
|
2215
2437
|
{
|
|
2216
|
-
description: "Delete
|
|
2438
|
+
description: "Delete the staging profile",
|
|
2217
2439
|
command: "geonic profile delete staging"
|
|
2218
2440
|
}
|
|
2219
2441
|
]);
|
|
2220
|
-
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) => {
|
|
2221
2443
|
const profileName = name ?? getCurrentProfile();
|
|
2222
2444
|
const config = loadConfig(profileName);
|
|
2223
2445
|
const entries = Object.entries(config).filter(([, v]) => v !== void 0);
|
|
@@ -2228,6 +2450,13 @@ function registerProfileCommands(program2) {
|
|
|
2228
2450
|
for (const [key, value] of entries) {
|
|
2229
2451
|
if ((key === "token" || key === "refreshToken" || key === "apiKey") && typeof value === "string") {
|
|
2230
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
|
+
}
|
|
2231
2460
|
} else {
|
|
2232
2461
|
console.log(`${key}: ${value}`);
|
|
2233
2462
|
}
|
|
@@ -2247,7 +2476,7 @@ function registerProfileCommands(program2) {
|
|
|
2247
2476
|
|
|
2248
2477
|
// src/commands/attrs.ts
|
|
2249
2478
|
function addAttrsSubcommands(attrs) {
|
|
2250
|
-
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(
|
|
2251
2480
|
withErrorHandler(
|
|
2252
2481
|
async (entityId, _opts, cmd) => {
|
|
2253
2482
|
const client = createClient(cmd);
|
|
@@ -2263,9 +2492,17 @@ function addAttrsSubcommands(attrs) {
|
|
|
2263
2492
|
{
|
|
2264
2493
|
description: "List all attributes of an entity",
|
|
2265
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"
|
|
2266
2503
|
}
|
|
2267
2504
|
]);
|
|
2268
|
-
const get = attrs.command("get").description("Get a
|
|
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(
|
|
2269
2506
|
withErrorHandler(
|
|
2270
2507
|
async (entityId, attrName, _opts, cmd) => {
|
|
2271
2508
|
const client = createClient(cmd);
|
|
@@ -2279,11 +2516,19 @@ function addAttrsSubcommands(attrs) {
|
|
|
2279
2516
|
);
|
|
2280
2517
|
addExamples(get, [
|
|
2281
2518
|
{
|
|
2282
|
-
description: "Get
|
|
2519
|
+
description: "Get the temperature attribute of a sensor",
|
|
2283
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"
|
|
2284
2529
|
}
|
|
2285
2530
|
]);
|
|
2286
|
-
const add = attrs.command("add").description(
|
|
2531
|
+
const add = attrs.command("add").summary("Add attributes to an entity").description(
|
|
2287
2532
|
'Add attributes to an entity\n\nJSON payload example:\n {"humidity": {"type": "Property", "value": 60}}'
|
|
2288
2533
|
).argument("<entityId>", "Entity ID").argument("[json]", "JSON payload (inline, @file, - for stdin, or omit for interactive/pipe)").action(
|
|
2289
2534
|
withErrorHandler(
|
|
@@ -2312,7 +2557,7 @@ function addAttrsSubcommands(attrs) {
|
|
|
2312
2557
|
command: "cat attrs.json | geonic entities attrs add urn:ngsi-ld:Sensor:001"
|
|
2313
2558
|
}
|
|
2314
2559
|
]);
|
|
2315
|
-
const attrUpdate = attrs.command("update").description(
|
|
2560
|
+
const attrUpdate = attrs.command("update").summary("Update a specific attribute of an entity").description(
|
|
2316
2561
|
'Update a specific attribute of an entity\n\nJSON payload example:\n {"type": "Property", "value": 25}'
|
|
2317
2562
|
).argument("<entityId>", "Entity ID").argument("<attrName>", "Attribute name").argument("[json]", "JSON payload (inline, @file, - for stdin, or omit for interactive/pipe)").action(
|
|
2318
2563
|
withErrorHandler(
|
|
@@ -2337,7 +2582,7 @@ function addAttrsSubcommands(attrs) {
|
|
|
2337
2582
|
command: "geonic entities attrs update urn:ngsi-ld:Sensor:001 temperature @attr.json"
|
|
2338
2583
|
}
|
|
2339
2584
|
]);
|
|
2340
|
-
const del = attrs.command("delete").description("
|
|
2585
|
+
const del = attrs.command("delete").description("Remove an attribute from an entity permanently").argument("<entityId>", "Entity ID").argument("<attrName>", "Attribute name").action(
|
|
2341
2586
|
withErrorHandler(
|
|
2342
2587
|
async (entityId, attrName, _opts, cmd) => {
|
|
2343
2588
|
const client = createClient(cmd);
|
|
@@ -2350,8 +2595,12 @@ function addAttrsSubcommands(attrs) {
|
|
|
2350
2595
|
);
|
|
2351
2596
|
addExamples(del, [
|
|
2352
2597
|
{
|
|
2353
|
-
description: "
|
|
2598
|
+
description: "Remove the temperature attribute from a sensor",
|
|
2354
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"
|
|
2355
2604
|
}
|
|
2356
2605
|
]);
|
|
2357
2606
|
}
|
|
@@ -2477,7 +2726,7 @@ function registerEntitiesCommand(program2) {
|
|
|
2477
2726
|
command: "geonic entities get urn:ngsi-ld:Sensor:001 --key-values"
|
|
2478
2727
|
}
|
|
2479
2728
|
]);
|
|
2480
|
-
const create = entities.command("create").description(
|
|
2729
|
+
const create = entities.command("create").summary("Create a new entity").description(
|
|
2481
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 }'
|
|
2482
2731
|
).argument("[json]", "JSON payload (inline, @file, - for stdin, or omit for interactive/pipe)").action(
|
|
2483
2732
|
withErrorHandler(async (json, _opts, cmd) => {
|
|
@@ -2509,7 +2758,7 @@ function registerEntitiesCommand(program2) {
|
|
|
2509
2758
|
command: "geonic entities create"
|
|
2510
2759
|
}
|
|
2511
2760
|
]);
|
|
2512
|
-
const update = entities.command("update").description(
|
|
2761
|
+
const update = entities.command("update").summary("Update attributes of an entity (PATCH)").description(
|
|
2513
2762
|
'Update attributes of an entity (PATCH)\n\nJSON payload: only specified attributes are modified.\n e.g. {"temperature": {"type": "Property", "value": 30}}'
|
|
2514
2763
|
).argument("<id>", "Entity ID").argument("[json]", "JSON payload (inline, @file, - for stdin, or omit for interactive/pipe)").action(
|
|
2515
2764
|
withErrorHandler(
|
|
@@ -2538,7 +2787,7 @@ function registerEntitiesCommand(program2) {
|
|
|
2538
2787
|
command: "cat attrs.json | geonic entities update urn:ngsi-ld:Sensor:001"
|
|
2539
2788
|
}
|
|
2540
2789
|
]);
|
|
2541
|
-
const replace = entities.command("replace").description(
|
|
2790
|
+
const replace = entities.command("replace").summary("Replace all attributes of an entity (PUT)").description(
|
|
2542
2791
|
'Replace all attributes of an entity (PUT)\n\nJSON payload: all existing attributes are replaced.\n e.g. {"temperature": {"type": "Property", "value": 20}}'
|
|
2543
2792
|
).argument("<id>", "Entity ID").argument("[json]", "JSON payload (inline, @file, - for stdin, or omit for interactive/pipe)").action(
|
|
2544
2793
|
withErrorHandler(
|
|
@@ -2585,7 +2834,7 @@ function registerEntitiesCommand(program2) {
|
|
|
2585
2834
|
command: "cat entities.json | geonic entities upsert"
|
|
2586
2835
|
}
|
|
2587
2836
|
]);
|
|
2588
|
-
const del = entities.command("delete").description("
|
|
2837
|
+
const del = entities.command("delete").description("Permanently delete an entity and all its attributes").argument("<id>", "Entity ID").action(
|
|
2589
2838
|
withErrorHandler(async (id, _opts, cmd) => {
|
|
2590
2839
|
const client = createClient(cmd);
|
|
2591
2840
|
await client.delete(`/entities/${encodeURIComponent(id)}`);
|
|
@@ -2596,6 +2845,10 @@ function registerEntitiesCommand(program2) {
|
|
|
2596
2845
|
{
|
|
2597
2846
|
description: "Delete an entity by ID",
|
|
2598
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"
|
|
2599
2852
|
}
|
|
2600
2853
|
]);
|
|
2601
2854
|
registerAttrsSubcommand(entities);
|
|
@@ -2604,7 +2857,7 @@ function registerEntitiesCommand(program2) {
|
|
|
2604
2857
|
// src/commands/batch.ts
|
|
2605
2858
|
function registerBatchCommand(program2) {
|
|
2606
2859
|
const batch = program2.command("entityOperations").alias("batch").description("Perform batch operations on entities");
|
|
2607
|
-
const create = batch.command("create [json]").description(
|
|
2860
|
+
const create = batch.command("create [json]").summary("Batch create entities").description(
|
|
2608
2861
|
'Batch create entities\n\nJSON payload: an array of NGSI-LD entities.\n e.g. [{"id": "urn:ngsi-ld:Sensor:001", "type": "Sensor"}, ...]'
|
|
2609
2862
|
).action(
|
|
2610
2863
|
withErrorHandler(async (json, _opts, cmd) => {
|
|
@@ -2629,7 +2882,7 @@ function registerBatchCommand(program2) {
|
|
|
2629
2882
|
command: "cat entities.json | geonic batch create"
|
|
2630
2883
|
}
|
|
2631
2884
|
]);
|
|
2632
|
-
const upsert = batch.command("upsert [json]").description(
|
|
2885
|
+
const upsert = batch.command("upsert [json]").summary("Batch upsert entities").description(
|
|
2633
2886
|
"Batch upsert entities\n\nJSON payload: an array of NGSI-LD entities.\nCreates entities that don't exist, updates those that do."
|
|
2634
2887
|
).action(
|
|
2635
2888
|
withErrorHandler(async (json, _opts, cmd) => {
|
|
@@ -2654,7 +2907,7 @@ function registerBatchCommand(program2) {
|
|
|
2654
2907
|
command: "cat entities.json | geonic batch upsert"
|
|
2655
2908
|
}
|
|
2656
2909
|
]);
|
|
2657
|
-
const update = batch.command("update [json]").description(
|
|
2910
|
+
const update = batch.command("update [json]").summary("Batch update entity attributes").description(
|
|
2658
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."
|
|
2659
2912
|
).action(
|
|
2660
2913
|
withErrorHandler(async (json, _opts, cmd) => {
|
|
@@ -2675,7 +2928,7 @@ function registerBatchCommand(program2) {
|
|
|
2675
2928
|
command: "cat updates.json | geonic batch update"
|
|
2676
2929
|
}
|
|
2677
2930
|
]);
|
|
2678
|
-
const del = batch.command("delete [json]").description(
|
|
2931
|
+
const del = batch.command("delete [json]").summary("Batch delete entities by ID").description(
|
|
2679
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"]'
|
|
2680
2933
|
).action(
|
|
2681
2934
|
withErrorHandler(async (json, _opts, cmd) => {
|
|
@@ -2700,7 +2953,7 @@ function registerBatchCommand(program2) {
|
|
|
2700
2953
|
command: "cat entity-ids.json | geonic batch delete"
|
|
2701
2954
|
}
|
|
2702
2955
|
]);
|
|
2703
|
-
const query = batch.command("query [json]").description(
|
|
2956
|
+
const query = batch.command("query [json]").summary("Query entities by posting a query payload").description(
|
|
2704
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 }'
|
|
2705
2958
|
).action(
|
|
2706
2959
|
withErrorHandler(async (json, _opts, cmd) => {
|
|
@@ -2725,7 +2978,7 @@ function registerBatchCommand(program2) {
|
|
|
2725
2978
|
command: "cat query.json | geonic batch query"
|
|
2726
2979
|
}
|
|
2727
2980
|
]);
|
|
2728
|
-
const merge = batch.command("merge [json]").description(
|
|
2981
|
+
const merge = batch.command("merge [json]").summary("Batch merge-patch entities").description(
|
|
2729
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."
|
|
2730
2983
|
).action(
|
|
2731
2984
|
withErrorHandler(async (json, _opts, cmd) => {
|
|
@@ -2778,7 +3031,7 @@ function registerSubscriptionsCommand(program2) {
|
|
|
2778
3031
|
command: "geonic subscriptions list --count"
|
|
2779
3032
|
}
|
|
2780
3033
|
]);
|
|
2781
|
-
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(
|
|
2782
3035
|
withErrorHandler(async (id, _opts, cmd) => {
|
|
2783
3036
|
const client = createClient(cmd);
|
|
2784
3037
|
const format = getFormat(cmd);
|
|
@@ -2790,11 +3043,15 @@ function registerSubscriptionsCommand(program2) {
|
|
|
2790
3043
|
);
|
|
2791
3044
|
addExamples(get, [
|
|
2792
3045
|
{
|
|
2793
|
-
description: "Get subscription by ID",
|
|
3046
|
+
description: "Get subscription details by ID",
|
|
2794
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"
|
|
2795
3052
|
}
|
|
2796
3053
|
]);
|
|
2797
|
-
const create = subscriptions.command("create [json]").description(
|
|
3054
|
+
const create = subscriptions.command("create [json]").summary("Create a subscription").description(
|
|
2798
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 }'
|
|
2799
3056
|
).action(
|
|
2800
3057
|
withErrorHandler(async (json, _opts, cmd) => {
|
|
@@ -2824,7 +3081,7 @@ function registerSubscriptionsCommand(program2) {
|
|
|
2824
3081
|
command: "geonic subscriptions create"
|
|
2825
3082
|
}
|
|
2826
3083
|
]);
|
|
2827
|
-
const update = subscriptions.command("update <id> [json]").description(
|
|
3084
|
+
const update = subscriptions.command("update <id> [json]").summary("Update a subscription").description(
|
|
2828
3085
|
'Update a subscription\n\nJSON payload: only specified fields are updated.\n e.g. {"description": "Updated subscription"}'
|
|
2829
3086
|
).action(
|
|
2830
3087
|
withErrorHandler(
|
|
@@ -2855,7 +3112,7 @@ function registerSubscriptionsCommand(program2) {
|
|
|
2855
3112
|
command: "cat sub.json | geonic subscriptions update urn:ngsi-ld:Subscription:001"
|
|
2856
3113
|
}
|
|
2857
3114
|
]);
|
|
2858
|
-
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(
|
|
2859
3116
|
withErrorHandler(async (id, _opts, cmd) => {
|
|
2860
3117
|
const client = createClient(cmd);
|
|
2861
3118
|
await client.delete(
|
|
@@ -2866,8 +3123,12 @@ function registerSubscriptionsCommand(program2) {
|
|
|
2866
3123
|
);
|
|
2867
3124
|
addExamples(del, [
|
|
2868
3125
|
{
|
|
2869
|
-
description: "Delete a subscription",
|
|
3126
|
+
description: "Delete a subscription by ID",
|
|
2870
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"
|
|
2871
3132
|
}
|
|
2872
3133
|
]);
|
|
2873
3134
|
}
|
|
@@ -2898,7 +3159,7 @@ function registerRegistrationsCommand(program2) {
|
|
|
2898
3159
|
command: "geonic registrations list --limit 10"
|
|
2899
3160
|
}
|
|
2900
3161
|
]);
|
|
2901
|
-
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(
|
|
2902
3163
|
withErrorHandler(async (id, _opts, cmd) => {
|
|
2903
3164
|
const client = createClient(cmd);
|
|
2904
3165
|
const format = getFormat(cmd);
|
|
@@ -2910,11 +3171,15 @@ function registerRegistrationsCommand(program2) {
|
|
|
2910
3171
|
);
|
|
2911
3172
|
addExamples(get, [
|
|
2912
3173
|
{
|
|
2913
|
-
description: "Get registration by ID",
|
|
3174
|
+
description: "Get registration details by ID",
|
|
2914
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"
|
|
2915
3180
|
}
|
|
2916
3181
|
]);
|
|
2917
|
-
const create = registrations.command("create [json]").description(
|
|
3182
|
+
const create = registrations.command("create [json]").summary("Create a registration").description(
|
|
2918
3183
|
'Create a registration\n\nJSON payload example:\n {\n "type": "ContextSourceRegistration",\n "information": [{"entities": [{"type": "Room"}]}],\n "endpoint": "http://localhost:4000/source"\n }'
|
|
2919
3184
|
).action(
|
|
2920
3185
|
withErrorHandler(async (json, _opts, cmd) => {
|
|
@@ -2969,7 +3234,7 @@ function registerRegistrationsCommand(program2) {
|
|
|
2969
3234
|
command: "cat registration.json | geonic registrations update urn:ngsi-ld:ContextSourceRegistration:001"
|
|
2970
3235
|
}
|
|
2971
3236
|
]);
|
|
2972
|
-
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(
|
|
2973
3238
|
withErrorHandler(async (id, _opts, cmd) => {
|
|
2974
3239
|
const client = createClient(cmd);
|
|
2975
3240
|
await client.delete(
|
|
@@ -2980,16 +3245,20 @@ function registerRegistrationsCommand(program2) {
|
|
|
2980
3245
|
);
|
|
2981
3246
|
addExamples(del, [
|
|
2982
3247
|
{
|
|
2983
|
-
description: "Delete a registration",
|
|
3248
|
+
description: "Delete a registration by ID",
|
|
2984
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"
|
|
2985
3254
|
}
|
|
2986
3255
|
]);
|
|
2987
3256
|
}
|
|
2988
3257
|
|
|
2989
3258
|
// src/commands/types.ts
|
|
2990
3259
|
function registerTypesCommand(program2) {
|
|
2991
|
-
const types = program2.command("types").description("
|
|
2992
|
-
const list = types.command("list").description("List
|
|
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(
|
|
2993
3262
|
withErrorHandler(async (_opts, cmd) => {
|
|
2994
3263
|
const client = createClient(cmd);
|
|
2995
3264
|
const format = getFormat(cmd);
|
|
@@ -2999,11 +3268,15 @@ function registerTypesCommand(program2) {
|
|
|
2999
3268
|
);
|
|
3000
3269
|
addExamples(list, [
|
|
3001
3270
|
{
|
|
3002
|
-
description: "List all entity types",
|
|
3271
|
+
description: "List all entity types in the broker",
|
|
3003
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"
|
|
3004
3277
|
}
|
|
3005
3278
|
]);
|
|
3006
|
-
const get = types.command("get <typeName>").description("
|
|
3279
|
+
const get = types.command("get <typeName>").description("Show attribute names and types for a given entity type").action(
|
|
3007
3280
|
withErrorHandler(
|
|
3008
3281
|
async (typeName, _opts, cmd) => {
|
|
3009
3282
|
const client = createClient(cmd);
|
|
@@ -3017,8 +3290,16 @@ function registerTypesCommand(program2) {
|
|
|
3017
3290
|
);
|
|
3018
3291
|
addExamples(get, [
|
|
3019
3292
|
{
|
|
3020
|
-
description: "
|
|
3293
|
+
description: "Inspect the Sensor type to see its attributes",
|
|
3021
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"
|
|
3022
3303
|
}
|
|
3023
3304
|
]);
|
|
3024
3305
|
}
|
|
@@ -3144,7 +3425,7 @@ function registerTemporalCommand(program2) {
|
|
|
3144
3425
|
command: "geonic temporal entities get urn:ngsi-ld:Sensor:001 --last-n 10"
|
|
3145
3426
|
}
|
|
3146
3427
|
]);
|
|
3147
|
-
const create = entities.command("create [json]").description(
|
|
3428
|
+
const create = entities.command("create [json]").summary("Create a temporal entity").description(
|
|
3148
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."
|
|
3149
3430
|
).action(createCreateAction());
|
|
3150
3431
|
addExamples(create, [
|
|
@@ -3161,11 +3442,15 @@ function registerTemporalCommand(program2) {
|
|
|
3161
3442
|
command: "geonic temporal entities create"
|
|
3162
3443
|
}
|
|
3163
3444
|
]);
|
|
3164
|
-
const del = entities.command("delete <id>").description("Delete a temporal entity
|
|
3445
|
+
const del = entities.command("delete <id>").description("Delete a temporal entity and all its historical attribute data").action(createDeleteAction());
|
|
3165
3446
|
addExamples(del, [
|
|
3166
3447
|
{
|
|
3167
3448
|
description: "Delete temporal data for an entity",
|
|
3168
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"
|
|
3169
3454
|
}
|
|
3170
3455
|
]);
|
|
3171
3456
|
const opsQuery = addQueryOptions(
|
|
@@ -3201,8 +3486,8 @@ function registerTemporalCommand(program2) {
|
|
|
3201
3486
|
|
|
3202
3487
|
// src/commands/snapshots.ts
|
|
3203
3488
|
function registerSnapshotsCommand(program2) {
|
|
3204
|
-
const snapshots = program2.command("snapshots").description("Manage snapshots");
|
|
3205
|
-
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(
|
|
3206
3491
|
withErrorHandler(async (_opts, cmd) => {
|
|
3207
3492
|
const client = createClient(cmd);
|
|
3208
3493
|
const format = getFormat(cmd);
|
|
@@ -3220,11 +3505,19 @@ function registerSnapshotsCommand(program2) {
|
|
|
3220
3505
|
command: "geonic snapshots list"
|
|
3221
3506
|
},
|
|
3222
3507
|
{
|
|
3223
|
-
description: "List
|
|
3508
|
+
description: "List the 10 most recent snapshots",
|
|
3224
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"
|
|
3225
3518
|
}
|
|
3226
3519
|
]);
|
|
3227
|
-
const get = snapshots.command("get <id>").description("
|
|
3520
|
+
const get = snapshots.command("get <id>").description("Retrieve details of a specific snapshot including its status and metadata").action(
|
|
3228
3521
|
withErrorHandler(async (id, _opts, cmd) => {
|
|
3229
3522
|
const client = createClient(cmd);
|
|
3230
3523
|
const format = getFormat(cmd);
|
|
@@ -3236,11 +3529,15 @@ function registerSnapshotsCommand(program2) {
|
|
|
3236
3529
|
);
|
|
3237
3530
|
addExamples(get, [
|
|
3238
3531
|
{
|
|
3239
|
-
description: "Get a
|
|
3240
|
-
command: "geonic snapshots get
|
|
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"
|
|
3241
3538
|
}
|
|
3242
3539
|
]);
|
|
3243
|
-
const create = snapshots.command("create").description("Create a
|
|
3540
|
+
const create = snapshots.command("create").description("Create a point-in-time snapshot of all current entity data").action(
|
|
3244
3541
|
withErrorHandler(async (_opts, cmd) => {
|
|
3245
3542
|
const client = createClient(cmd);
|
|
3246
3543
|
await client.post("/snapshots");
|
|
@@ -3249,11 +3546,15 @@ function registerSnapshotsCommand(program2) {
|
|
|
3249
3546
|
);
|
|
3250
3547
|
addExamples(create, [
|
|
3251
3548
|
{
|
|
3252
|
-
description: "Create a
|
|
3549
|
+
description: "Create a snapshot of the current entity data",
|
|
3253
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"
|
|
3254
3555
|
}
|
|
3255
3556
|
]);
|
|
3256
|
-
const del = snapshots.command("delete <id>").description("
|
|
3557
|
+
const del = snapshots.command("delete <id>").description("Permanently delete a snapshot to free storage").action(
|
|
3257
3558
|
withErrorHandler(async (id, _opts, cmd) => {
|
|
3258
3559
|
const client = createClient(cmd);
|
|
3259
3560
|
await client.delete(
|
|
@@ -3264,11 +3565,15 @@ function registerSnapshotsCommand(program2) {
|
|
|
3264
3565
|
);
|
|
3265
3566
|
addExamples(del, [
|
|
3266
3567
|
{
|
|
3267
|
-
description: "Delete a snapshot",
|
|
3268
|
-
command: "geonic snapshots delete
|
|
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"
|
|
3269
3574
|
}
|
|
3270
3575
|
]);
|
|
3271
|
-
const clone = snapshots.command("clone <id>").description("Clone a snapshot
|
|
3576
|
+
const clone = snapshots.command("clone <id>").description("Clone a snapshot to create a duplicate for testing or migration").action(
|
|
3272
3577
|
withErrorHandler(async (id, _opts, cmd) => {
|
|
3273
3578
|
const client = createClient(cmd);
|
|
3274
3579
|
const format = getFormat(cmd);
|
|
@@ -3284,8 +3589,12 @@ function registerSnapshotsCommand(program2) {
|
|
|
3284
3589
|
);
|
|
3285
3590
|
addExamples(clone, [
|
|
3286
3591
|
{
|
|
3287
|
-
description: "Clone a snapshot",
|
|
3288
|
-
command: "geonic snapshots clone
|
|
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"
|
|
3289
3598
|
}
|
|
3290
3599
|
]);
|
|
3291
3600
|
}
|
|
@@ -3293,7 +3602,7 @@ function registerSnapshotsCommand(program2) {
|
|
|
3293
3602
|
// src/commands/admin/tenants.ts
|
|
3294
3603
|
function registerTenantsCommand(parent) {
|
|
3295
3604
|
const tenants = parent.command("tenants").description("Manage tenants");
|
|
3296
|
-
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(
|
|
3297
3606
|
withErrorHandler(async (_opts, cmd) => {
|
|
3298
3607
|
const client = createClient(cmd);
|
|
3299
3608
|
const format = getFormat(cmd);
|
|
@@ -3305,9 +3614,13 @@ function registerTenantsCommand(parent) {
|
|
|
3305
3614
|
{
|
|
3306
3615
|
description: "List all tenants",
|
|
3307
3616
|
command: "geonic admin tenants list"
|
|
3617
|
+
},
|
|
3618
|
+
{
|
|
3619
|
+
description: "List tenants in table format",
|
|
3620
|
+
command: "geonic admin tenants list --format table"
|
|
3308
3621
|
}
|
|
3309
3622
|
]);
|
|
3310
|
-
const get = tenants.command("get <id>").description("Get a tenant
|
|
3623
|
+
const get = tenants.command("get <id>").description("Get a tenant's details \u2014 name, description, status, and creation date").action(
|
|
3311
3624
|
withErrorHandler(async (id, _opts, cmd) => {
|
|
3312
3625
|
const client = createClient(cmd);
|
|
3313
3626
|
const format = getFormat(cmd);
|
|
@@ -3320,11 +3633,15 @@ function registerTenantsCommand(parent) {
|
|
|
3320
3633
|
);
|
|
3321
3634
|
addExamples(get, [
|
|
3322
3635
|
{
|
|
3323
|
-
description: "Get
|
|
3636
|
+
description: "Get tenant details by ID",
|
|
3324
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"
|
|
3325
3642
|
}
|
|
3326
3643
|
]);
|
|
3327
|
-
const create = tenants.command("create [json]").description(
|
|
3644
|
+
const create = tenants.command("create [json]").summary("Create a new tenant").description(
|
|
3328
3645
|
'Create a new tenant\n\nJSON payload example:\n {\n "name": "production",\n "description": "Production environment tenant"\n }'
|
|
3329
3646
|
).action(
|
|
3330
3647
|
withErrorHandler(async (json, _opts, cmd) => {
|
|
@@ -3360,7 +3677,7 @@ function registerTenantsCommand(parent) {
|
|
|
3360
3677
|
command: "geonic admin tenants create"
|
|
3361
3678
|
}
|
|
3362
3679
|
]);
|
|
3363
|
-
const update = tenants.command("update <id> [json]").description(
|
|
3680
|
+
const update = tenants.command("update <id> [json]").summary("Update a tenant").description(
|
|
3364
3681
|
'Update a tenant\n\nJSON payload: only specified fields are updated.\n e.g. {"name": "new-name", "description": "Updated description"}'
|
|
3365
3682
|
).action(
|
|
3366
3683
|
withErrorHandler(
|
|
@@ -3400,7 +3717,7 @@ function registerTenantsCommand(parent) {
|
|
|
3400
3717
|
command: "geonic admin tenants update <tenant-id>"
|
|
3401
3718
|
}
|
|
3402
3719
|
]);
|
|
3403
|
-
const del = tenants.command("delete <id>").description("
|
|
3720
|
+
const del = tenants.command("delete <id>").description("Permanently delete a tenant and all its associated data. This action cannot be undone").action(
|
|
3404
3721
|
withErrorHandler(async (id, _opts, cmd) => {
|
|
3405
3722
|
const client = createClient(cmd);
|
|
3406
3723
|
await client.rawRequest(
|
|
@@ -3412,11 +3729,15 @@ function registerTenantsCommand(parent) {
|
|
|
3412
3729
|
);
|
|
3413
3730
|
addExamples(del, [
|
|
3414
3731
|
{
|
|
3415
|
-
description: "Delete a tenant",
|
|
3732
|
+
description: "Delete a tenant by ID",
|
|
3416
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"
|
|
3417
3738
|
}
|
|
3418
3739
|
]);
|
|
3419
|
-
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(
|
|
3420
3741
|
withErrorHandler(async (id, _opts, cmd) => {
|
|
3421
3742
|
const client = createClient(cmd);
|
|
3422
3743
|
await client.rawRequest(
|
|
@@ -3428,11 +3749,11 @@ function registerTenantsCommand(parent) {
|
|
|
3428
3749
|
);
|
|
3429
3750
|
addExamples(activate, [
|
|
3430
3751
|
{
|
|
3431
|
-
description: "Activate a tenant",
|
|
3752
|
+
description: "Activate a deactivated tenant",
|
|
3432
3753
|
command: "geonic admin tenants activate <tenant-id>"
|
|
3433
3754
|
}
|
|
3434
3755
|
]);
|
|
3435
|
-
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(
|
|
3436
3757
|
withErrorHandler(async (id, _opts, cmd) => {
|
|
3437
3758
|
const client = createClient(cmd);
|
|
3438
3759
|
await client.rawRequest(
|
|
@@ -3444,7 +3765,7 @@ function registerTenantsCommand(parent) {
|
|
|
3444
3765
|
);
|
|
3445
3766
|
addExamples(deactivate, [
|
|
3446
3767
|
{
|
|
3447
|
-
description: "Deactivate a tenant",
|
|
3768
|
+
description: "Deactivate a tenant to temporarily suspend access",
|
|
3448
3769
|
command: "geonic admin tenants deactivate <tenant-id>"
|
|
3449
3770
|
}
|
|
3450
3771
|
]);
|
|
@@ -3453,7 +3774,7 @@ function registerTenantsCommand(parent) {
|
|
|
3453
3774
|
// src/commands/admin/users.ts
|
|
3454
3775
|
function registerUsersCommand(parent) {
|
|
3455
3776
|
const users = parent.command("users").description("Manage users");
|
|
3456
|
-
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(
|
|
3457
3778
|
withErrorHandler(async (_opts, cmd) => {
|
|
3458
3779
|
const client = createClient(cmd);
|
|
3459
3780
|
const format = getFormat(cmd);
|
|
@@ -3465,9 +3786,17 @@ function registerUsersCommand(parent) {
|
|
|
3465
3786
|
{
|
|
3466
3787
|
description: "List all users",
|
|
3467
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>"
|
|
3468
3797
|
}
|
|
3469
3798
|
]);
|
|
3470
|
-
const get = users.command("get <id>").description("Get a user
|
|
3799
|
+
const get = users.command("get <id>").description("Get a user's details \u2014 email, role, tenant, status, and login history").action(
|
|
3471
3800
|
withErrorHandler(async (id, _opts, cmd) => {
|
|
3472
3801
|
const client = createClient(cmd);
|
|
3473
3802
|
const format = getFormat(cmd);
|
|
@@ -3480,11 +3809,15 @@ function registerUsersCommand(parent) {
|
|
|
3480
3809
|
);
|
|
3481
3810
|
addExamples(get, [
|
|
3482
3811
|
{
|
|
3483
|
-
description: "
|
|
3812
|
+
description: "Inspect a user's account details",
|
|
3484
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"
|
|
3485
3818
|
}
|
|
3486
3819
|
]);
|
|
3487
|
-
const create = users.command("create [json]").description(
|
|
3820
|
+
const create = users.command("create [json]").summary("Create a new user").description(
|
|
3488
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.'
|
|
3489
3822
|
).action(
|
|
3490
3823
|
withErrorHandler(async (json, _opts, cmd) => {
|
|
@@ -3516,7 +3849,7 @@ function registerUsersCommand(parent) {
|
|
|
3516
3849
|
command: "cat user.json | geonic admin users create"
|
|
3517
3850
|
}
|
|
3518
3851
|
]);
|
|
3519
|
-
const update = users.command("update <id> [json]").description(
|
|
3852
|
+
const update = users.command("update <id> [json]").summary("Update a user").description(
|
|
3520
3853
|
'Update a user\n\nJSON payload: only specified fields are updated.\n e.g. {"role": "admin"}'
|
|
3521
3854
|
).action(
|
|
3522
3855
|
withErrorHandler(
|
|
@@ -3548,7 +3881,7 @@ function registerUsersCommand(parent) {
|
|
|
3548
3881
|
command: "cat user.json | geonic admin users update <user-id>"
|
|
3549
3882
|
}
|
|
3550
3883
|
]);
|
|
3551
|
-
const del = users.command("delete <id>").description("
|
|
3884
|
+
const del = users.command("delete <id>").description("Permanently delete a user account. This revokes all access and cannot be undone").action(
|
|
3552
3885
|
withErrorHandler(async (id, _opts, cmd) => {
|
|
3553
3886
|
const client = createClient(cmd);
|
|
3554
3887
|
await client.rawRequest(
|
|
@@ -3560,11 +3893,15 @@ function registerUsersCommand(parent) {
|
|
|
3560
3893
|
);
|
|
3561
3894
|
addExamples(del, [
|
|
3562
3895
|
{
|
|
3563
|
-
description: "Delete a user",
|
|
3896
|
+
description: "Delete a user by ID",
|
|
3564
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"
|
|
3565
3902
|
}
|
|
3566
3903
|
]);
|
|
3567
|
-
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(
|
|
3568
3905
|
withErrorHandler(async (id, _opts, cmd) => {
|
|
3569
3906
|
const client = createClient(cmd);
|
|
3570
3907
|
await client.rawRequest(
|
|
@@ -3576,11 +3913,11 @@ function registerUsersCommand(parent) {
|
|
|
3576
3913
|
);
|
|
3577
3914
|
addExamples(activate, [
|
|
3578
3915
|
{
|
|
3579
|
-
description: "Activate a user",
|
|
3916
|
+
description: "Activate a deactivated user",
|
|
3580
3917
|
command: "geonic admin users activate <user-id>"
|
|
3581
3918
|
}
|
|
3582
3919
|
]);
|
|
3583
|
-
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(
|
|
3584
3921
|
withErrorHandler(async (id, _opts, cmd) => {
|
|
3585
3922
|
const client = createClient(cmd);
|
|
3586
3923
|
await client.rawRequest(
|
|
@@ -3592,11 +3929,11 @@ function registerUsersCommand(parent) {
|
|
|
3592
3929
|
);
|
|
3593
3930
|
addExamples(deactivate, [
|
|
3594
3931
|
{
|
|
3595
|
-
description: "Deactivate a user",
|
|
3932
|
+
description: "Deactivate a user to suspend their access",
|
|
3596
3933
|
command: "geonic admin users deactivate <user-id>"
|
|
3597
3934
|
}
|
|
3598
3935
|
]);
|
|
3599
|
-
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(
|
|
3600
3937
|
withErrorHandler(async (id, _opts, cmd) => {
|
|
3601
3938
|
const client = createClient(cmd);
|
|
3602
3939
|
await client.rawRequest(
|
|
@@ -3616,8 +3953,8 @@ function registerUsersCommand(parent) {
|
|
|
3616
3953
|
|
|
3617
3954
|
// src/commands/admin/policies.ts
|
|
3618
3955
|
function registerPoliciesCommand(parent) {
|
|
3619
|
-
const policies = parent.command("policies").description("Manage policies");
|
|
3620
|
-
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(
|
|
3621
3958
|
withErrorHandler(async (_opts, cmd) => {
|
|
3622
3959
|
const client = createClient(cmd);
|
|
3623
3960
|
const format = getFormat(cmd);
|
|
@@ -3629,9 +3966,13 @@ function registerPoliciesCommand(parent) {
|
|
|
3629
3966
|
{
|
|
3630
3967
|
description: "List all policies",
|
|
3631
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"
|
|
3632
3973
|
}
|
|
3633
3974
|
]);
|
|
3634
|
-
const get = policies.command("get <id>").description("Get a policy
|
|
3975
|
+
const get = policies.command("get <id>").description("Get a policy's full details \u2014 target rules, effect, priority, and status").action(
|
|
3635
3976
|
withErrorHandler(async (id, _opts, cmd) => {
|
|
3636
3977
|
const client = createClient(cmd);
|
|
3637
3978
|
const format = getFormat(cmd);
|
|
@@ -3644,11 +3985,11 @@ function registerPoliciesCommand(parent) {
|
|
|
3644
3985
|
);
|
|
3645
3986
|
addExamples(get, [
|
|
3646
3987
|
{
|
|
3647
|
-
description: "
|
|
3988
|
+
description: "Inspect a policy's rules and target configuration",
|
|
3648
3989
|
command: "geonic admin policies get <policy-id>"
|
|
3649
3990
|
}
|
|
3650
3991
|
]);
|
|
3651
|
-
const create = policies.command("create [json]").description(
|
|
3992
|
+
const create = policies.command("create [json]").summary("Create a new policy").description(
|
|
3652
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'
|
|
3653
3994
|
).action(
|
|
3654
3995
|
withErrorHandler(async (json, _opts, cmd) => {
|
|
@@ -3688,7 +4029,7 @@ function registerPoliciesCommand(parent) {
|
|
|
3688
4029
|
command: "cat policy.json | geonic admin policies create"
|
|
3689
4030
|
}
|
|
3690
4031
|
]);
|
|
3691
|
-
const update = policies.command("update <id> [json]").description(
|
|
4032
|
+
const update = policies.command("update <id> [json]").summary("Update a policy").description(
|
|
3692
4033
|
'Update a policy\n\nJSON payload: only specified fields are updated.\n e.g. {"description": "Updated policy"}'
|
|
3693
4034
|
).action(
|
|
3694
4035
|
withErrorHandler(
|
|
@@ -3720,7 +4061,7 @@ function registerPoliciesCommand(parent) {
|
|
|
3720
4061
|
command: "cat policy.json | geonic admin policies update <policy-id>"
|
|
3721
4062
|
}
|
|
3722
4063
|
]);
|
|
3723
|
-
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(
|
|
3724
4065
|
withErrorHandler(async (id, _opts, cmd) => {
|
|
3725
4066
|
const client = createClient(cmd);
|
|
3726
4067
|
await client.rawRequest(
|
|
@@ -3732,11 +4073,11 @@ function registerPoliciesCommand(parent) {
|
|
|
3732
4073
|
);
|
|
3733
4074
|
addExamples(del, [
|
|
3734
4075
|
{
|
|
3735
|
-
description: "Delete a policy",
|
|
4076
|
+
description: "Delete a policy by ID",
|
|
3736
4077
|
command: "geonic admin policies delete <policy-id>"
|
|
3737
4078
|
}
|
|
3738
4079
|
]);
|
|
3739
|
-
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(
|
|
3740
4081
|
withErrorHandler(async (id, _opts, cmd) => {
|
|
3741
4082
|
const client = createClient(cmd);
|
|
3742
4083
|
await client.rawRequest(
|
|
@@ -3748,11 +4089,11 @@ function registerPoliciesCommand(parent) {
|
|
|
3748
4089
|
);
|
|
3749
4090
|
addExamples(activate, [
|
|
3750
4091
|
{
|
|
3751
|
-
description: "
|
|
4092
|
+
description: "Enable a policy to start enforcing its rules",
|
|
3752
4093
|
command: "geonic admin policies activate <policy-id>"
|
|
3753
4094
|
}
|
|
3754
4095
|
]);
|
|
3755
|
-
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(
|
|
3756
4097
|
withErrorHandler(async (id, _opts, cmd) => {
|
|
3757
4098
|
const client = createClient(cmd);
|
|
3758
4099
|
await client.rawRequest(
|
|
@@ -3764,7 +4105,7 @@ function registerPoliciesCommand(parent) {
|
|
|
3764
4105
|
);
|
|
3765
4106
|
addExamples(deactivate, [
|
|
3766
4107
|
{
|
|
3767
|
-
description: "
|
|
4108
|
+
description: "Temporarily disable a policy without deleting it",
|
|
3768
4109
|
command: "geonic admin policies deactivate <policy-id>"
|
|
3769
4110
|
}
|
|
3770
4111
|
]);
|
|
@@ -3773,7 +4114,7 @@ function registerPoliciesCommand(parent) {
|
|
|
3773
4114
|
// src/commands/admin/oauth-clients.ts
|
|
3774
4115
|
function registerOAuthClientsCommand(parent) {
|
|
3775
4116
|
const oauthClients = parent.command("oauth-clients").description("Manage OAuth clients");
|
|
3776
|
-
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(
|
|
3777
4118
|
withErrorHandler(async (_opts, cmd) => {
|
|
3778
4119
|
const client = createClient(cmd);
|
|
3779
4120
|
const format = getFormat(cmd);
|
|
@@ -3785,9 +4126,13 @@ function registerOAuthClientsCommand(parent) {
|
|
|
3785
4126
|
{
|
|
3786
4127
|
description: "List all OAuth clients",
|
|
3787
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"
|
|
3788
4133
|
}
|
|
3789
4134
|
]);
|
|
3790
|
-
const get = oauthClients.command("get <id>").description("Get an OAuth client
|
|
4135
|
+
const get = oauthClients.command("get <id>").description("Get an OAuth client's details \u2014 name, client ID, policy, and redirect URIs").action(
|
|
3791
4136
|
withErrorHandler(async (id, _opts, cmd) => {
|
|
3792
4137
|
const client = createClient(cmd);
|
|
3793
4138
|
const format = getFormat(cmd);
|
|
@@ -3800,11 +4145,11 @@ function registerOAuthClientsCommand(parent) {
|
|
|
3800
4145
|
);
|
|
3801
4146
|
addExamples(get, [
|
|
3802
4147
|
{
|
|
3803
|
-
description: "
|
|
4148
|
+
description: "Inspect an OAuth client's configuration",
|
|
3804
4149
|
command: "geonic admin oauth-clients get <client-id>"
|
|
3805
4150
|
}
|
|
3806
4151
|
]);
|
|
3807
|
-
const create = oauthClients.command("create [json]").description(
|
|
4152
|
+
const create = oauthClients.command("create [json]").summary("Create a new OAuth client").description(
|
|
3808
4153
|
'Create a new OAuth client\n\nJSON payload example:\n {\n "name": "my-app",\n "policyId": "<policy-id>"\n }'
|
|
3809
4154
|
).action(
|
|
3810
4155
|
withErrorHandler(async (json, _opts, cmd) => {
|
|
@@ -3832,7 +4177,7 @@ function registerOAuthClientsCommand(parent) {
|
|
|
3832
4177
|
command: "cat client.json | geonic admin oauth-clients create"
|
|
3833
4178
|
}
|
|
3834
4179
|
]);
|
|
3835
|
-
const update = oauthClients.command("update <id> [json]").description(
|
|
4180
|
+
const update = oauthClients.command("update <id> [json]").summary("Update an OAuth client").description(
|
|
3836
4181
|
'Update an OAuth client\n\nJSON payload: only specified fields are updated.\n e.g. {"description": "Updated client"}'
|
|
3837
4182
|
).action(
|
|
3838
4183
|
withErrorHandler(
|
|
@@ -3864,7 +4209,7 @@ function registerOAuthClientsCommand(parent) {
|
|
|
3864
4209
|
command: "cat client.json | geonic admin oauth-clients update <client-id>"
|
|
3865
4210
|
}
|
|
3866
4211
|
]);
|
|
3867
|
-
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(
|
|
3868
4213
|
withErrorHandler(async (id, _opts, cmd) => {
|
|
3869
4214
|
const client = createClient(cmd);
|
|
3870
4215
|
await client.rawRequest(
|
|
@@ -3876,14 +4221,14 @@ function registerOAuthClientsCommand(parent) {
|
|
|
3876
4221
|
);
|
|
3877
4222
|
addExamples(del, [
|
|
3878
4223
|
{
|
|
3879
|
-
description: "Delete an OAuth client",
|
|
4224
|
+
description: "Delete an OAuth client by ID",
|
|
3880
4225
|
command: "geonic admin oauth-clients delete <client-id>"
|
|
3881
4226
|
}
|
|
3882
4227
|
]);
|
|
3883
4228
|
}
|
|
3884
4229
|
function registerCaddeCommand(parent) {
|
|
3885
|
-
const cadde = parent.command("cadde").description("Manage CADDE configuration");
|
|
3886
|
-
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(
|
|
3887
4232
|
withErrorHandler(async (_opts, cmd) => {
|
|
3888
4233
|
const client = createClient(cmd);
|
|
3889
4234
|
const format = getFormat(cmd);
|
|
@@ -3893,11 +4238,15 @@ function registerCaddeCommand(parent) {
|
|
|
3893
4238
|
);
|
|
3894
4239
|
addExamples(caddeGet, [
|
|
3895
4240
|
{
|
|
3896
|
-
description: "
|
|
4241
|
+
description: "View current CADDE configuration",
|
|
3897
4242
|
command: "geonic admin cadde get"
|
|
4243
|
+
},
|
|
4244
|
+
{
|
|
4245
|
+
description: "View CADDE configuration in table format",
|
|
4246
|
+
command: "geonic admin cadde get --format table"
|
|
3898
4247
|
}
|
|
3899
4248
|
]);
|
|
3900
|
-
const caddeSet = cadde.command("set [json]").description(
|
|
4249
|
+
const caddeSet = cadde.command("set [json]").summary("Set CADDE configuration").description(
|
|
3901
4250
|
'Set CADDE configuration\n\nJSON payload example:\n {\n "provider": "my-provider",\n "endpoint": "http://localhost:6000"\n }'
|
|
3902
4251
|
).action(
|
|
3903
4252
|
withErrorHandler(async (json, _opts, cmd) => {
|
|
@@ -3925,7 +4274,7 @@ function registerCaddeCommand(parent) {
|
|
|
3925
4274
|
command: "cat cadde-config.json | geonic admin cadde set"
|
|
3926
4275
|
}
|
|
3927
4276
|
]);
|
|
3928
|
-
const caddeDelete = cadde.command("delete").description("
|
|
4277
|
+
const caddeDelete = cadde.command("delete").description("Remove the CADDE data exchange configuration, disabling cross-platform data sharing").action(
|
|
3929
4278
|
withErrorHandler(async (_opts, cmd) => {
|
|
3930
4279
|
const client = createClient(cmd);
|
|
3931
4280
|
await client.rawRequest("DELETE", "/admin/cadde");
|
|
@@ -3934,13 +4283,20 @@ function registerCaddeCommand(parent) {
|
|
|
3934
4283
|
);
|
|
3935
4284
|
addExamples(caddeDelete, [
|
|
3936
4285
|
{
|
|
3937
|
-
description: "
|
|
4286
|
+
description: "Remove CADDE configuration",
|
|
3938
4287
|
command: "geonic admin cadde delete"
|
|
3939
4288
|
}
|
|
3940
4289
|
]);
|
|
3941
4290
|
}
|
|
3942
4291
|
|
|
3943
4292
|
// src/commands/admin/api-keys.ts
|
|
4293
|
+
function cleanApiKeyData2(data) {
|
|
4294
|
+
if (Array.isArray(data)) return data.map(cleanApiKeyData2);
|
|
4295
|
+
if (typeof data !== "object" || data === null) return data;
|
|
4296
|
+
const obj = { ...data };
|
|
4297
|
+
if (obj.key === "******") delete obj.key;
|
|
4298
|
+
return obj;
|
|
4299
|
+
}
|
|
3944
4300
|
function validateOrigins(body, opts) {
|
|
3945
4301
|
if (opts.origins !== void 0) {
|
|
3946
4302
|
const origins = String(opts.origins).split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -3979,9 +4335,41 @@ function buildBodyFromFlags(opts) {
|
|
|
3979
4335
|
if (opts.tenantId) payload.tenantId = opts.tenantId;
|
|
3980
4336
|
return payload;
|
|
3981
4337
|
}
|
|
4338
|
+
function handleSaveKey2(data, cmd) {
|
|
4339
|
+
const globalOpts = resolveOptions(cmd);
|
|
4340
|
+
const key = data.key;
|
|
4341
|
+
if (!key) {
|
|
4342
|
+
printError("Response missing key. API key was created, but it could not be saved.");
|
|
4343
|
+
process.exitCode = 1;
|
|
4344
|
+
return false;
|
|
4345
|
+
}
|
|
4346
|
+
try {
|
|
4347
|
+
const config = loadConfig(globalOpts.profile);
|
|
4348
|
+
config.apiKey = key;
|
|
4349
|
+
saveConfig(config, globalOpts.profile);
|
|
4350
|
+
console.error("API key saved to config. X-Api-Key header will be sent automatically.");
|
|
4351
|
+
return true;
|
|
4352
|
+
} catch (err) {
|
|
4353
|
+
printError(`Failed to save API key to config: ${err instanceof Error ? err.message : String(err)}`);
|
|
4354
|
+
printApiKeyBox(key);
|
|
4355
|
+
process.exitCode = 1;
|
|
4356
|
+
return false;
|
|
4357
|
+
}
|
|
4358
|
+
}
|
|
4359
|
+
function showKeyResult2(data, save, cmd) {
|
|
4360
|
+
const key = data.key;
|
|
4361
|
+
if (!key) {
|
|
4362
|
+
printError("Response missing key. The new API key value was not returned.");
|
|
4363
|
+
process.exitCode = 1;
|
|
4364
|
+
return false;
|
|
4365
|
+
}
|
|
4366
|
+
if (save) return handleSaveKey2(data, cmd);
|
|
4367
|
+
printApiKeyBox(key);
|
|
4368
|
+
return true;
|
|
4369
|
+
}
|
|
3982
4370
|
function registerApiKeysCommand(parent) {
|
|
3983
4371
|
const apiKeys = parent.command("api-keys").description("Manage API keys");
|
|
3984
|
-
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(
|
|
3985
4373
|
withErrorHandler(async (_opts, cmd) => {
|
|
3986
4374
|
const opts = cmd.opts();
|
|
3987
4375
|
const client = createClient(cmd);
|
|
@@ -3991,7 +4379,9 @@ function registerApiKeysCommand(parent) {
|
|
|
3991
4379
|
const response = await client.rawRequest("GET", "/admin/api-keys", {
|
|
3992
4380
|
params
|
|
3993
4381
|
});
|
|
4382
|
+
response.data = cleanApiKeyData2(response.data);
|
|
3994
4383
|
outputResponse(response, format);
|
|
4384
|
+
console.error("\u203B API \u30AD\u30FC\u5024\u306F\u4F5C\u6210\u6642 (create) \u307E\u305F\u306F\u30EA\u30D5\u30EC\u30C3\u30B7\u30E5\u6642 (refresh) \u306B\u306E\u307F\u8868\u793A\u3055\u308C\u307E\u3059\u3002");
|
|
3995
4385
|
})
|
|
3996
4386
|
);
|
|
3997
4387
|
addExamples(list, [
|
|
@@ -3999,12 +4389,16 @@ function registerApiKeysCommand(parent) {
|
|
|
3999
4389
|
description: "List all API keys",
|
|
4000
4390
|
command: "geonic admin api-keys list"
|
|
4001
4391
|
},
|
|
4392
|
+
{
|
|
4393
|
+
description: "List API keys in table format",
|
|
4394
|
+
command: "geonic admin api-keys list --format table"
|
|
4395
|
+
},
|
|
4002
4396
|
{
|
|
4003
4397
|
description: "List API keys for a specific tenant",
|
|
4004
4398
|
command: "geonic admin api-keys list --tenant-id <tenant-id>"
|
|
4005
4399
|
}
|
|
4006
4400
|
]);
|
|
4007
|
-
const get = apiKeys.command("get <keyId>").description("Get an API key
|
|
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(
|
|
4008
4402
|
withErrorHandler(async (keyId, _opts, cmd) => {
|
|
4009
4403
|
const client = createClient(cmd);
|
|
4010
4404
|
const format = getFormat(cmd);
|
|
@@ -4012,12 +4406,13 @@ function registerApiKeysCommand(parent) {
|
|
|
4012
4406
|
"GET",
|
|
4013
4407
|
`/admin/api-keys/${encodeURIComponent(String(keyId))}`
|
|
4014
4408
|
);
|
|
4409
|
+
response.data = cleanApiKeyData2(response.data);
|
|
4015
4410
|
outputResponse(response, format);
|
|
4016
4411
|
})
|
|
4017
4412
|
);
|
|
4018
4413
|
addExamples(get, [
|
|
4019
4414
|
{
|
|
4020
|
-
description: "
|
|
4415
|
+
description: "Inspect an API key's configuration",
|
|
4021
4416
|
command: "geonic admin api-keys get <key-id>"
|
|
4022
4417
|
}
|
|
4023
4418
|
]);
|
|
@@ -4040,24 +4435,9 @@ function registerApiKeysCommand(parent) {
|
|
|
4040
4435
|
body
|
|
4041
4436
|
});
|
|
4042
4437
|
const data = response.data;
|
|
4043
|
-
|
|
4044
|
-
const globalOpts = resolveOptions(cmd);
|
|
4045
|
-
const key = data.key;
|
|
4046
|
-
if (!key) {
|
|
4047
|
-
printError("Response missing key. API key was created, but it could not be saved.");
|
|
4048
|
-
outputResponse(response, format);
|
|
4049
|
-
process.exitCode = 1;
|
|
4050
|
-
return;
|
|
4051
|
-
}
|
|
4052
|
-
const config = loadConfig(globalOpts.profile);
|
|
4053
|
-
config.apiKey = key;
|
|
4054
|
-
saveConfig(config, globalOpts.profile);
|
|
4055
|
-
console.error("API key saved to config. X-Api-Key header will be sent automatically.");
|
|
4056
|
-
} else {
|
|
4057
|
-
printWarning("Save the API key now \u2014 it will not be shown again. Use --save to store it automatically.");
|
|
4058
|
-
}
|
|
4438
|
+
const ok = showKeyResult2(data, !!opts.save, cmd);
|
|
4059
4439
|
outputResponse(response, format);
|
|
4060
|
-
console.error("API key created.");
|
|
4440
|
+
if (ok) console.error("API key created.");
|
|
4061
4441
|
})
|
|
4062
4442
|
);
|
|
4063
4443
|
addNotes(create, [
|
|
@@ -4078,6 +4458,35 @@ function registerApiKeysCommand(parent) {
|
|
|
4078
4458
|
command: "geonic admin api-keys create @key.json --save"
|
|
4079
4459
|
}
|
|
4080
4460
|
]);
|
|
4461
|
+
const refresh = apiKeys.command("refresh <keyId>").description("Refresh (rotate) an API key \u2014 generates a new key value").option("--save", "Save the new API key to profile config").action(
|
|
4462
|
+
withErrorHandler(async (keyId, _opts, cmd) => {
|
|
4463
|
+
const opts = cmd.opts();
|
|
4464
|
+
const client = createClient(cmd);
|
|
4465
|
+
const format = getFormat(cmd);
|
|
4466
|
+
const response = await client.rawRequest(
|
|
4467
|
+
"POST",
|
|
4468
|
+
`/admin/api-keys/${encodeURIComponent(String(keyId))}/refresh`
|
|
4469
|
+
);
|
|
4470
|
+
const data = response.data;
|
|
4471
|
+
const ok = showKeyResult2(data, !!opts.save, cmd);
|
|
4472
|
+
outputResponse(response, format);
|
|
4473
|
+
if (ok) console.error("API key refreshed.");
|
|
4474
|
+
})
|
|
4475
|
+
);
|
|
4476
|
+
addNotes(refresh, [
|
|
4477
|
+
"Refreshing generates a new key value while keeping keyId, name, and policy settings.",
|
|
4478
|
+
"The previous key value is immediately invalidated."
|
|
4479
|
+
]);
|
|
4480
|
+
addExamples(refresh, [
|
|
4481
|
+
{
|
|
4482
|
+
description: "Refresh an API key",
|
|
4483
|
+
command: "geonic admin api-keys refresh <key-id>"
|
|
4484
|
+
},
|
|
4485
|
+
{
|
|
4486
|
+
description: "Refresh and save new key to config",
|
|
4487
|
+
command: "geonic admin api-keys refresh <key-id> --save"
|
|
4488
|
+
}
|
|
4489
|
+
]);
|
|
4081
4490
|
const update = apiKeys.command("update <keyId> [json]").description("Update an API key").option("--name <name>", "Key name").option("--policy <policyId>", "Policy ID to attach").option("--origins <origins>", "Comma-separated origins").option("--rate-limit <n>", "Rate limit per minute").option("--dpop-required", "Require DPoP token binding").option("--no-dpop-required", "Disable DPoP token binding").action(
|
|
4082
4491
|
withErrorHandler(
|
|
4083
4492
|
async (keyId, json, _opts, cmd) => {
|
|
@@ -4130,7 +4539,7 @@ function registerApiKeysCommand(parent) {
|
|
|
4130
4539
|
command: "geonic admin api-keys update <key-id> @key.json"
|
|
4131
4540
|
}
|
|
4132
4541
|
]);
|
|
4133
|
-
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(
|
|
4134
4543
|
withErrorHandler(async (keyId, _opts, cmd) => {
|
|
4135
4544
|
const client = createClient(cmd);
|
|
4136
4545
|
await client.rawRequest(
|
|
@@ -4142,7 +4551,7 @@ function registerApiKeysCommand(parent) {
|
|
|
4142
4551
|
);
|
|
4143
4552
|
addExamples(del, [
|
|
4144
4553
|
{
|
|
4145
|
-
description: "Delete an API key",
|
|
4554
|
+
description: "Delete an API key by ID",
|
|
4146
4555
|
command: "geonic admin api-keys delete <key-id>"
|
|
4147
4556
|
}
|
|
4148
4557
|
]);
|
|
@@ -4161,8 +4570,8 @@ function registerAdminCommand(program2) {
|
|
|
4161
4570
|
|
|
4162
4571
|
// src/commands/rules.ts
|
|
4163
4572
|
function registerRulesCommand(program2) {
|
|
4164
|
-
const rules = program2.command("rules").description("Manage
|
|
4165
|
-
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(
|
|
4166
4575
|
withErrorHandler(async (_opts, cmd) => {
|
|
4167
4576
|
const client = createClient(cmd);
|
|
4168
4577
|
const format = getFormat(cmd);
|
|
@@ -4172,11 +4581,15 @@ function registerRulesCommand(program2) {
|
|
|
4172
4581
|
);
|
|
4173
4582
|
addExamples(list, [
|
|
4174
4583
|
{
|
|
4175
|
-
description: "List all rules",
|
|
4584
|
+
description: "List all rules as JSON",
|
|
4176
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"
|
|
4177
4590
|
}
|
|
4178
4591
|
]);
|
|
4179
|
-
const get = rules.command("get <id>").description("Get a rule
|
|
4592
|
+
const get = rules.command("get <id>").description("Get a rule's full definition including conditions, actions, and status").action(
|
|
4180
4593
|
withErrorHandler(async (id, _opts, cmd) => {
|
|
4181
4594
|
const client = createClient(cmd);
|
|
4182
4595
|
const format = getFormat(cmd);
|
|
@@ -4189,11 +4602,15 @@ function registerRulesCommand(program2) {
|
|
|
4189
4602
|
);
|
|
4190
4603
|
addExamples(get, [
|
|
4191
4604
|
{
|
|
4192
|
-
description: "
|
|
4605
|
+
description: "Inspect a rule's conditions and actions",
|
|
4193
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"
|
|
4194
4611
|
}
|
|
4195
4612
|
]);
|
|
4196
|
-
const create = rules.command("create [json]").description(
|
|
4613
|
+
const create = rules.command("create [json]").summary("Create a new rule").description(
|
|
4197
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 }'
|
|
4198
4615
|
).action(
|
|
4199
4616
|
withErrorHandler(async (json, _opts, cmd) => {
|
|
@@ -4219,7 +4636,7 @@ function registerRulesCommand(program2) {
|
|
|
4219
4636
|
command: "cat rule.json | geonic rules create"
|
|
4220
4637
|
}
|
|
4221
4638
|
]);
|
|
4222
|
-
const update = rules.command("update <id> [json]").description(
|
|
4639
|
+
const update = rules.command("update <id> [json]").summary("Update a rule").description(
|
|
4223
4640
|
'Update a rule\n\nJSON payload: only specified fields are updated.\n e.g. {"description": "Updated rule"}'
|
|
4224
4641
|
).action(
|
|
4225
4642
|
withErrorHandler(
|
|
@@ -4251,7 +4668,7 @@ function registerRulesCommand(program2) {
|
|
|
4251
4668
|
command: "cat rule.json | geonic rules update <rule-id>"
|
|
4252
4669
|
}
|
|
4253
4670
|
]);
|
|
4254
|
-
const del = rules.command("delete <id>").description("
|
|
4671
|
+
const del = rules.command("delete <id>").description("Permanently delete a rule and stop its processing").action(
|
|
4255
4672
|
withErrorHandler(async (id, _opts, cmd) => {
|
|
4256
4673
|
const client = createClient(cmd);
|
|
4257
4674
|
await client.rawRequest(
|
|
@@ -4263,11 +4680,15 @@ function registerRulesCommand(program2) {
|
|
|
4263
4680
|
);
|
|
4264
4681
|
addExamples(del, [
|
|
4265
4682
|
{
|
|
4266
|
-
description: "Delete a rule",
|
|
4683
|
+
description: "Delete a rule by ID",
|
|
4267
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"
|
|
4268
4689
|
}
|
|
4269
4690
|
]);
|
|
4270
|
-
const activate = rules.command("activate <id>").description("
|
|
4691
|
+
const activate = rules.command("activate <id>").description("Enable a rule so it begins evaluating conditions and firing actions").action(
|
|
4271
4692
|
withErrorHandler(async (id, _opts, cmd) => {
|
|
4272
4693
|
const client = createClient(cmd);
|
|
4273
4694
|
await client.rawRequest(
|
|
@@ -4279,11 +4700,15 @@ function registerRulesCommand(program2) {
|
|
|
4279
4700
|
);
|
|
4280
4701
|
addExamples(activate, [
|
|
4281
4702
|
{
|
|
4282
|
-
description: "
|
|
4703
|
+
description: "Start processing a rule",
|
|
4283
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"
|
|
4284
4709
|
}
|
|
4285
4710
|
]);
|
|
4286
|
-
const deactivate = rules.command("deactivate <id>").description("
|
|
4711
|
+
const deactivate = rules.command("deactivate <id>").description("Disable a rule without deleting it, pausing condition evaluation and actions").action(
|
|
4287
4712
|
withErrorHandler(async (id, _opts, cmd) => {
|
|
4288
4713
|
const client = createClient(cmd);
|
|
4289
4714
|
await client.rawRequest(
|
|
@@ -4295,16 +4720,20 @@ function registerRulesCommand(program2) {
|
|
|
4295
4720
|
);
|
|
4296
4721
|
addExamples(deactivate, [
|
|
4297
4722
|
{
|
|
4298
|
-
description: "
|
|
4723
|
+
description: "Temporarily pause a rule during maintenance",
|
|
4299
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"
|
|
4300
4729
|
}
|
|
4301
4730
|
]);
|
|
4302
4731
|
}
|
|
4303
4732
|
|
|
4304
4733
|
// src/commands/models.ts
|
|
4305
4734
|
function registerModelsCommand(program2) {
|
|
4306
|
-
const models = program2.command("custom-data-models").alias("models").description("Manage custom data models");
|
|
4307
|
-
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(
|
|
4308
4737
|
withErrorHandler(async (_opts, cmd) => {
|
|
4309
4738
|
const client = createClient(cmd);
|
|
4310
4739
|
const format = getFormat(cmd);
|
|
@@ -4314,11 +4743,15 @@ function registerModelsCommand(program2) {
|
|
|
4314
4743
|
);
|
|
4315
4744
|
addExamples(list, [
|
|
4316
4745
|
{
|
|
4317
|
-
description: "List all models",
|
|
4746
|
+
description: "List all data models as JSON",
|
|
4318
4747
|
command: "geonic models list"
|
|
4748
|
+
},
|
|
4749
|
+
{
|
|
4750
|
+
description: "Browse available data models in table format",
|
|
4751
|
+
command: "geonic models list --format table"
|
|
4319
4752
|
}
|
|
4320
4753
|
]);
|
|
4321
|
-
const get = models.command("get <id>").description("Get a model
|
|
4754
|
+
const get = models.command("get <id>").description("Get a data model's full schema including property definitions and constraints").action(
|
|
4322
4755
|
withErrorHandler(async (id, _opts, cmd) => {
|
|
4323
4756
|
const client = createClient(cmd);
|
|
4324
4757
|
const format = getFormat(cmd);
|
|
@@ -4331,11 +4764,15 @@ function registerModelsCommand(program2) {
|
|
|
4331
4764
|
);
|
|
4332
4765
|
addExamples(get, [
|
|
4333
4766
|
{
|
|
4334
|
-
description: "
|
|
4767
|
+
description: "Inspect a model's property definitions",
|
|
4335
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"
|
|
4336
4773
|
}
|
|
4337
4774
|
]);
|
|
4338
|
-
const create = models.command("create [json]").description(
|
|
4775
|
+
const create = models.command("create [json]").summary("Create a new model").description(
|
|
4339
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 }'
|
|
4340
4777
|
).action(
|
|
4341
4778
|
withErrorHandler(async (json, _opts, cmd) => {
|
|
@@ -4361,7 +4798,7 @@ function registerModelsCommand(program2) {
|
|
|
4361
4798
|
command: "cat model.json | geonic models create"
|
|
4362
4799
|
}
|
|
4363
4800
|
]);
|
|
4364
|
-
const update = models.command("update <id> [json]").description(
|
|
4801
|
+
const update = models.command("update <id> [json]").summary("Update a model").description(
|
|
4365
4802
|
'Update a model\n\nJSON payload: only specified fields are updated.\n e.g. {"description": "Updated model"}'
|
|
4366
4803
|
).action(
|
|
4367
4804
|
withErrorHandler(
|
|
@@ -4393,7 +4830,7 @@ function registerModelsCommand(program2) {
|
|
|
4393
4830
|
command: "cat model.json | geonic models update <model-id>"
|
|
4394
4831
|
}
|
|
4395
4832
|
]);
|
|
4396
|
-
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(
|
|
4397
4834
|
withErrorHandler(async (id, _opts, cmd) => {
|
|
4398
4835
|
const client = createClient(cmd);
|
|
4399
4836
|
await client.rawRequest(
|
|
@@ -4405,16 +4842,20 @@ function registerModelsCommand(program2) {
|
|
|
4405
4842
|
);
|
|
4406
4843
|
addExamples(del, [
|
|
4407
4844
|
{
|
|
4408
|
-
description: "Delete a model",
|
|
4845
|
+
description: "Delete a data model by ID",
|
|
4409
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"
|
|
4410
4851
|
}
|
|
4411
4852
|
]);
|
|
4412
4853
|
}
|
|
4413
4854
|
|
|
4414
4855
|
// src/commands/catalog.ts
|
|
4415
4856
|
function registerCatalogCommand(program2) {
|
|
4416
|
-
const catalog = program2.command("catalog").description("Browse DCAT-AP catalog");
|
|
4417
|
-
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(
|
|
4418
4859
|
withErrorHandler(async (_opts, cmd) => {
|
|
4419
4860
|
const client = createClient(cmd);
|
|
4420
4861
|
const format = getFormat(cmd);
|
|
@@ -4424,12 +4865,16 @@ function registerCatalogCommand(program2) {
|
|
|
4424
4865
|
);
|
|
4425
4866
|
addExamples(get, [
|
|
4426
4867
|
{
|
|
4427
|
-
description: "
|
|
4868
|
+
description: "View catalog metadata (title, publisher, datasets summary)",
|
|
4428
4869
|
command: "geonic catalog get"
|
|
4870
|
+
},
|
|
4871
|
+
{
|
|
4872
|
+
description: "Get catalog metadata in table format",
|
|
4873
|
+
command: "geonic catalog get --format table"
|
|
4429
4874
|
}
|
|
4430
4875
|
]);
|
|
4431
|
-
const datasets = catalog.command("datasets").description("
|
|
4432
|
-
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(
|
|
4433
4878
|
withErrorHandler(async (_opts, cmd) => {
|
|
4434
4879
|
const client = createClient(cmd);
|
|
4435
4880
|
const format = getFormat(cmd);
|
|
@@ -4439,11 +4884,15 @@ function registerCatalogCommand(program2) {
|
|
|
4439
4884
|
);
|
|
4440
4885
|
addExamples(datasetsList, [
|
|
4441
4886
|
{
|
|
4442
|
-
description: "List all catalog datasets",
|
|
4887
|
+
description: "List all catalog datasets as JSON",
|
|
4443
4888
|
command: "geonic catalog datasets list"
|
|
4889
|
+
},
|
|
4890
|
+
{
|
|
4891
|
+
description: "Browse datasets in table format",
|
|
4892
|
+
command: "geonic catalog datasets list --format table"
|
|
4444
4893
|
}
|
|
4445
4894
|
]);
|
|
4446
|
-
const datasetsGet = datasets.command("get <id>").description("Get a dataset
|
|
4895
|
+
const datasetsGet = datasets.command("get <id>").description("Get a dataset's metadata including description, distributions, and license").action(
|
|
4447
4896
|
withErrorHandler(async (id, _opts, cmd) => {
|
|
4448
4897
|
const client = createClient(cmd);
|
|
4449
4898
|
const format = getFormat(cmd);
|
|
@@ -4456,11 +4905,15 @@ function registerCatalogCommand(program2) {
|
|
|
4456
4905
|
);
|
|
4457
4906
|
addExamples(datasetsGet, [
|
|
4458
4907
|
{
|
|
4459
|
-
description: "
|
|
4908
|
+
description: "Inspect a dataset's metadata and distributions",
|
|
4460
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"
|
|
4461
4914
|
}
|
|
4462
4915
|
]);
|
|
4463
|
-
const datasetsSample = datasets.command("sample <id>").description("
|
|
4916
|
+
const datasetsSample = datasets.command("sample <id>").description("Preview sample entities from a dataset to understand its structure and content").action(
|
|
4464
4917
|
withErrorHandler(async (id, _opts, cmd) => {
|
|
4465
4918
|
const client = createClient(cmd);
|
|
4466
4919
|
const format = getFormat(cmd);
|
|
@@ -4473,8 +4926,12 @@ function registerCatalogCommand(program2) {
|
|
|
4473
4926
|
);
|
|
4474
4927
|
addExamples(datasetsSample, [
|
|
4475
4928
|
{
|
|
4476
|
-
description: "
|
|
4929
|
+
description: "Preview sample entities from a dataset",
|
|
4477
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"
|
|
4478
4935
|
}
|
|
4479
4936
|
]);
|
|
4480
4937
|
}
|
|
@@ -4482,7 +4939,7 @@ function registerCatalogCommand(program2) {
|
|
|
4482
4939
|
// src/commands/health.ts
|
|
4483
4940
|
import { createRequire } from "module";
|
|
4484
4941
|
function registerHealthCommand(program2) {
|
|
4485
|
-
const health = program2.command("health").description("Check
|
|
4942
|
+
const health = program2.command("health").description("Check Context Broker connectivity and health status").action(
|
|
4486
4943
|
withErrorHandler(async (_opts, cmd) => {
|
|
4487
4944
|
const client = createClient(cmd);
|
|
4488
4945
|
const format = getFormat(cmd);
|
|
@@ -4494,11 +4951,19 @@ function registerHealthCommand(program2) {
|
|
|
4494
4951
|
{
|
|
4495
4952
|
description: "Check server health",
|
|
4496
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"
|
|
4497
4962
|
}
|
|
4498
4963
|
]);
|
|
4499
4964
|
}
|
|
4500
4965
|
function registerVersionCommand(program2) {
|
|
4501
|
-
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(
|
|
4502
4967
|
withErrorHandler(async (_opts, cmd) => {
|
|
4503
4968
|
const require2 = createRequire(import.meta.url);
|
|
4504
4969
|
const pkg = require2("../package.json");
|
|
@@ -4515,11 +4980,16 @@ function registerVersionCommand(program2) {
|
|
|
4515
4980
|
{
|
|
4516
4981
|
description: "Show CLI and server version",
|
|
4517
4982
|
command: "geonic version"
|
|
4983
|
+
},
|
|
4984
|
+
{
|
|
4985
|
+
description: "Show version as JSON",
|
|
4986
|
+
command: "geonic version --format json"
|
|
4518
4987
|
}
|
|
4519
4988
|
]);
|
|
4520
4989
|
}
|
|
4521
4990
|
|
|
4522
4991
|
// src/commands/cli.ts
|
|
4992
|
+
import { execSync } from "child_process";
|
|
4523
4993
|
import { createRequire as createRequire2 } from "module";
|
|
4524
4994
|
function findOption(cmd, program2, flag) {
|
|
4525
4995
|
return cmd.options.find((o) => o.long === flag || o.short === flag) || program2.options.find((o) => o.long === flag || o.short === flag);
|
|
@@ -4685,15 +5155,43 @@ function registerCliCommand(program2) {
|
|
|
4685
5155
|
command: `echo 'eval "$(geonic cli completions zsh)"' >> ~/.zshrc`
|
|
4686
5156
|
}
|
|
4687
5157
|
]);
|
|
4688
|
-
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(() => {
|
|
4689
5159
|
const require2 = createRequire2(import.meta.url);
|
|
4690
5160
|
const pkg = require2("../package.json");
|
|
4691
5161
|
console.log(pkg.version);
|
|
4692
5162
|
});
|
|
4693
5163
|
addExamples(version, [
|
|
4694
5164
|
{
|
|
4695
|
-
description: "Show
|
|
5165
|
+
description: "Show the installed version",
|
|
4696
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"
|
|
4697
5195
|
}
|
|
4698
5196
|
]);
|
|
4699
5197
|
}
|