@geolonia/geonicdb-cli 0.2.0 → 0.4.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/README.md +26 -0
- package/dist/index.js +333 -162
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -77,6 +77,7 @@ geonic entities list --help
|
|
|
77
77
|
| `-f, --format <fmt>` | Output format: `json`, `table`, `geojson` |
|
|
78
78
|
| `--no-color` | Disable color output |
|
|
79
79
|
| `-v, --verbose` | Verbose output |
|
|
80
|
+
| `--dry-run` | Print the equivalent `curl` command without executing |
|
|
80
81
|
|
|
81
82
|
Options are resolved in this order (first wins):
|
|
82
83
|
|
|
@@ -388,6 +389,31 @@ Specify the output format with `--format` or `geonic config set format <fmt>`.
|
|
|
388
389
|
|
|
389
390
|
Use `--key-values` on `entities list` and `entities get` to request simplified key-value format from the API.
|
|
390
391
|
|
|
392
|
+
## Dry Run
|
|
393
|
+
|
|
394
|
+
Use `--dry-run` on any command to print the equivalent `curl` command instead of executing the request. The output can be copied and run directly in a terminal.
|
|
395
|
+
|
|
396
|
+
```bash
|
|
397
|
+
$ geonic entities list --type Sensor --dry-run
|
|
398
|
+
curl \
|
|
399
|
+
-H 'Content-Type: application/ld+json' \
|
|
400
|
+
-H 'Accept: application/ld+json' \
|
|
401
|
+
-H 'Authorization: Bearer <token>' \
|
|
402
|
+
'http://localhost:3000/ngsi-ld/v1/entities?type=Sensor'
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
Works with all operations including POST with body:
|
|
406
|
+
|
|
407
|
+
```bash
|
|
408
|
+
$ geonic entities create '{"id":"Room1","type":"Room"}' --dry-run
|
|
409
|
+
curl \
|
|
410
|
+
-X POST \
|
|
411
|
+
-H 'Content-Type: application/ld+json' \
|
|
412
|
+
-H 'Accept: application/ld+json' \
|
|
413
|
+
-d '{"id":"Room1","type":"Room"}' \
|
|
414
|
+
'http://localhost:3000/ngsi-ld/v1/entities'
|
|
415
|
+
```
|
|
416
|
+
|
|
391
417
|
## Configuration
|
|
392
418
|
|
|
393
419
|
The CLI stores configuration in `~/.config/geonic/config.json`.
|
package/dist/index.js
CHANGED
|
@@ -584,15 +584,54 @@ function registerConfigCommand(program2) {
|
|
|
584
584
|
// src/commands/auth.ts
|
|
585
585
|
import { Command } from "commander";
|
|
586
586
|
|
|
587
|
+
// src/oauth.ts
|
|
588
|
+
async function clientCredentialsGrant(options) {
|
|
589
|
+
const url = new URL("/oauth/token", options.baseUrl).toString();
|
|
590
|
+
const credentials = Buffer.from(`${options.clientId}:${options.clientSecret}`).toString("base64");
|
|
591
|
+
const body = { grant_type: "client_credentials" };
|
|
592
|
+
if (options.scope) {
|
|
593
|
+
body.scope = options.scope;
|
|
594
|
+
}
|
|
595
|
+
const response = await fetch(url, {
|
|
596
|
+
method: "POST",
|
|
597
|
+
headers: {
|
|
598
|
+
"Content-Type": "application/json",
|
|
599
|
+
Authorization: `Basic ${credentials}`
|
|
600
|
+
},
|
|
601
|
+
body: JSON.stringify(body)
|
|
602
|
+
});
|
|
603
|
+
if (!response.ok) {
|
|
604
|
+
const text = await response.text();
|
|
605
|
+
let message;
|
|
606
|
+
try {
|
|
607
|
+
const err = JSON.parse(text);
|
|
608
|
+
message = err.error_description ?? err.error ?? text;
|
|
609
|
+
} catch {
|
|
610
|
+
message = text || `HTTP ${response.status}`;
|
|
611
|
+
}
|
|
612
|
+
throw new Error(`OAuth token request failed: ${message}`);
|
|
613
|
+
}
|
|
614
|
+
return await response.json();
|
|
615
|
+
}
|
|
616
|
+
|
|
587
617
|
// src/client.ts
|
|
618
|
+
var DryRunSignal = class extends Error {
|
|
619
|
+
constructor() {
|
|
620
|
+
super("dry-run");
|
|
621
|
+
this.name = "DryRunSignal";
|
|
622
|
+
}
|
|
623
|
+
};
|
|
588
624
|
var GdbClient = class _GdbClient {
|
|
589
625
|
baseUrl;
|
|
590
626
|
service;
|
|
591
627
|
token;
|
|
592
628
|
refreshToken;
|
|
593
629
|
apiKey;
|
|
630
|
+
clientId;
|
|
631
|
+
clientSecret;
|
|
594
632
|
onTokenRefresh;
|
|
595
633
|
verbose;
|
|
634
|
+
dryRun;
|
|
596
635
|
refreshPromise;
|
|
597
636
|
constructor(options) {
|
|
598
637
|
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
@@ -600,8 +639,11 @@ var GdbClient = class _GdbClient {
|
|
|
600
639
|
this.token = options.token;
|
|
601
640
|
this.refreshToken = options.refreshToken;
|
|
602
641
|
this.apiKey = options.apiKey;
|
|
642
|
+
this.clientId = options.clientId;
|
|
643
|
+
this.clientSecret = options.clientSecret;
|
|
603
644
|
this.onTokenRefresh = options.onTokenRefresh;
|
|
604
645
|
this.verbose = options.verbose ?? false;
|
|
646
|
+
this.dryRun = options.dryRun ?? false;
|
|
605
647
|
}
|
|
606
648
|
buildHeaders(extra) {
|
|
607
649
|
const headers = {};
|
|
@@ -682,8 +724,30 @@ var GdbClient = class _GdbClient {
|
|
|
682
724
|
});
|
|
683
725
|
process.stderr.write("\n");
|
|
684
726
|
}
|
|
727
|
+
static shellQuote(value) {
|
|
728
|
+
return `'${value.split("'").join(`'"'"'`)}'`;
|
|
729
|
+
}
|
|
730
|
+
static buildCurlCommand(method, url, headers, body) {
|
|
731
|
+
const parts = ["curl"];
|
|
732
|
+
if (method !== "GET") {
|
|
733
|
+
parts.push(`-X ${method}`);
|
|
734
|
+
}
|
|
735
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
736
|
+
parts.push(`-H ${_GdbClient.shellQuote(`${key}: ${value}`)}`);
|
|
737
|
+
}
|
|
738
|
+
if (body) {
|
|
739
|
+
parts.push(`-d ${_GdbClient.shellQuote(body)}`);
|
|
740
|
+
}
|
|
741
|
+
parts.push(_GdbClient.shellQuote(url));
|
|
742
|
+
return parts.join(" \\\n ");
|
|
743
|
+
}
|
|
744
|
+
handleDryRun(method, url, headers, body) {
|
|
745
|
+
if (!this.dryRun) return;
|
|
746
|
+
console.log(_GdbClient.buildCurlCommand(method, url, headers, body));
|
|
747
|
+
throw new DryRunSignal();
|
|
748
|
+
}
|
|
685
749
|
canRefresh() {
|
|
686
|
-
return !!this.refreshToken && !this.apiKey;
|
|
750
|
+
return (!!this.refreshToken || !!this.clientId && !!this.clientSecret) && !this.apiKey;
|
|
687
751
|
}
|
|
688
752
|
async performTokenRefresh() {
|
|
689
753
|
if (this.refreshPromise) return this.refreshPromise;
|
|
@@ -695,32 +759,50 @@ var GdbClient = class _GdbClient {
|
|
|
695
759
|
}
|
|
696
760
|
}
|
|
697
761
|
async doRefresh() {
|
|
698
|
-
if (
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
762
|
+
if (this.refreshToken) {
|
|
763
|
+
try {
|
|
764
|
+
const url = this.buildUrl("/auth/refresh");
|
|
765
|
+
const response = await fetch(url, {
|
|
766
|
+
method: "POST",
|
|
767
|
+
headers: { "Content-Type": "application/json" },
|
|
768
|
+
body: JSON.stringify({ refreshToken: this.refreshToken })
|
|
769
|
+
});
|
|
770
|
+
if (response.ok) {
|
|
771
|
+
const data = await response.json();
|
|
772
|
+
const newToken = data.accessToken ?? data.token;
|
|
773
|
+
const newRefreshToken = data.refreshToken;
|
|
774
|
+
if (newToken) {
|
|
775
|
+
this.token = newToken;
|
|
776
|
+
if (newRefreshToken) this.refreshToken = newRefreshToken;
|
|
777
|
+
this.onTokenRefresh?.(newToken, newRefreshToken);
|
|
778
|
+
return true;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
} catch {
|
|
782
|
+
}
|
|
717
783
|
}
|
|
784
|
+
if (this.clientId && this.clientSecret) {
|
|
785
|
+
try {
|
|
786
|
+
const result = await clientCredentialsGrant({
|
|
787
|
+
baseUrl: this.baseUrl,
|
|
788
|
+
clientId: this.clientId,
|
|
789
|
+
clientSecret: this.clientSecret
|
|
790
|
+
});
|
|
791
|
+
this.token = result.access_token;
|
|
792
|
+
this.onTokenRefresh?.(result.access_token);
|
|
793
|
+
return true;
|
|
794
|
+
} catch {
|
|
795
|
+
return false;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
return false;
|
|
718
799
|
}
|
|
719
800
|
async executeRequest(method, path, options) {
|
|
720
801
|
const url = this.buildUrl(`${this.getBasePath()}${path}`, options?.params);
|
|
721
802
|
const headers = this.buildHeaders(options?.headers);
|
|
722
803
|
const body = options?.body ? JSON.stringify(options.body) : void 0;
|
|
723
804
|
this.logRequest(method, url, headers, body);
|
|
805
|
+
this.handleDryRun(method, url, headers, body);
|
|
724
806
|
const response = await fetch(url, { method, headers, body });
|
|
725
807
|
this.logResponse(response);
|
|
726
808
|
const countHeader = response.headers.get("NGSILD-Results-Count");
|
|
@@ -745,6 +827,7 @@ var GdbClient = class _GdbClient {
|
|
|
745
827
|
const headers = this.buildHeaders(options?.headers);
|
|
746
828
|
const body = options?.body ? JSON.stringify(options.body) : void 0;
|
|
747
829
|
this.logRequest(method, url, headers, body);
|
|
830
|
+
this.handleDryRun(method, url, headers, body);
|
|
748
831
|
const response = await fetch(url, { method, headers, body });
|
|
749
832
|
this.logResponse(response);
|
|
750
833
|
let data;
|
|
@@ -826,7 +909,8 @@ function resolveOptions(cmd) {
|
|
|
826
909
|
color: opts.color,
|
|
827
910
|
verbose: opts.verbose,
|
|
828
911
|
profile: opts.profile,
|
|
829
|
-
apiKey: opts.apiKey ?? process.env.GDB_API_KEY ?? config.apiKey
|
|
912
|
+
apiKey: opts.apiKey ?? process.env.GDB_API_KEY ?? config.apiKey,
|
|
913
|
+
dryRun: opts.dryRun
|
|
830
914
|
};
|
|
831
915
|
}
|
|
832
916
|
function createClient(cmd) {
|
|
@@ -844,6 +928,8 @@ function createClient(cmd) {
|
|
|
844
928
|
service: opts.service,
|
|
845
929
|
token: opts.token,
|
|
846
930
|
refreshToken: usingCliToken ? void 0 : config.refreshToken,
|
|
931
|
+
clientId: usingCliToken ? void 0 : config.clientId,
|
|
932
|
+
clientSecret: usingCliToken ? void 0 : config.clientSecret,
|
|
847
933
|
apiKey: opts.apiKey,
|
|
848
934
|
onTokenRefresh: usingCliToken ? void 0 : (token, refreshToken) => {
|
|
849
935
|
const cfg = loadConfig(opts.profile);
|
|
@@ -851,7 +937,8 @@ function createClient(cmd) {
|
|
|
851
937
|
if (refreshToken) cfg.refreshToken = refreshToken;
|
|
852
938
|
saveConfig(cfg, opts.profile);
|
|
853
939
|
},
|
|
854
|
-
verbose: opts.verbose
|
|
940
|
+
verbose: opts.verbose,
|
|
941
|
+
dryRun: opts.dryRun
|
|
855
942
|
});
|
|
856
943
|
}
|
|
857
944
|
function getFormat(cmd) {
|
|
@@ -871,6 +958,9 @@ function withErrorHandler(fn) {
|
|
|
871
958
|
try {
|
|
872
959
|
await fn(...args);
|
|
873
960
|
} catch (err) {
|
|
961
|
+
if (err instanceof DryRunSignal) {
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
874
964
|
if (err instanceof GdbClientError && err.status === 401) {
|
|
875
965
|
printError("Authentication failed. Please run `geonic login` to re-authenticate.");
|
|
876
966
|
} else if (err instanceof Error) {
|
|
@@ -1020,35 +1110,217 @@ function formatDuration(ms) {
|
|
|
1020
1110
|
return parts.join(" ");
|
|
1021
1111
|
}
|
|
1022
1112
|
|
|
1023
|
-
// src/
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1113
|
+
// src/input.ts
|
|
1114
|
+
import JSON5 from "json5";
|
|
1115
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
1116
|
+
import { createInterface as createInterface2 } from "readline";
|
|
1117
|
+
async function parseJsonInput(input) {
|
|
1118
|
+
if (input !== void 0) {
|
|
1119
|
+
if (input === "-") return parseData(readFileSync2(0, "utf-8"));
|
|
1120
|
+
if (input.startsWith("@")) return parseData(readFileSync2(input.slice(1), "utf-8"));
|
|
1121
|
+
return parseData(input);
|
|
1031
1122
|
}
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1123
|
+
if (!process.stdin.isTTY) {
|
|
1124
|
+
return parseData(readFileSync2(0, "utf-8"));
|
|
1125
|
+
}
|
|
1126
|
+
return readInteractiveJson();
|
|
1127
|
+
}
|
|
1128
|
+
function parseData(text) {
|
|
1129
|
+
return JSON5.parse(text.trim());
|
|
1130
|
+
}
|
|
1131
|
+
async function readInteractiveJson() {
|
|
1132
|
+
const rl = createInterface2({
|
|
1133
|
+
input: process.stdin,
|
|
1134
|
+
output: process.stderr,
|
|
1135
|
+
prompt: "json> "
|
|
1039
1136
|
});
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1137
|
+
process.stderr.write("Enter JSON (auto-submits when braces close, Ctrl+C to cancel):\n");
|
|
1138
|
+
rl.prompt();
|
|
1139
|
+
const lines = [];
|
|
1140
|
+
let depth = 0;
|
|
1141
|
+
let started = false;
|
|
1142
|
+
let inBlockComment = false;
|
|
1143
|
+
let inString = false;
|
|
1144
|
+
let stringChar = "";
|
|
1145
|
+
let cancelled = false;
|
|
1146
|
+
return new Promise((resolve, reject) => {
|
|
1147
|
+
rl.on("SIGINT", () => {
|
|
1148
|
+
cancelled = true;
|
|
1149
|
+
rl.close();
|
|
1150
|
+
});
|
|
1151
|
+
rl.on("line", (line) => {
|
|
1152
|
+
lines.push(line);
|
|
1153
|
+
const result = trackDepth(line, depth, started, inBlockComment, inString, stringChar);
|
|
1154
|
+
depth = result.depth;
|
|
1155
|
+
started = result.started;
|
|
1156
|
+
inBlockComment = result.inBlockComment;
|
|
1157
|
+
inString = result.inString;
|
|
1158
|
+
stringChar = result.stringChar;
|
|
1159
|
+
if (started && depth <= 0 && !inBlockComment && !inString) {
|
|
1160
|
+
rl.close();
|
|
1161
|
+
try {
|
|
1162
|
+
resolve(parseData(lines.join("\n")));
|
|
1163
|
+
} catch (err) {
|
|
1164
|
+
reject(err);
|
|
1165
|
+
}
|
|
1166
|
+
} else {
|
|
1167
|
+
rl.setPrompt("... ");
|
|
1168
|
+
rl.prompt();
|
|
1169
|
+
}
|
|
1170
|
+
});
|
|
1171
|
+
rl.on("close", () => {
|
|
1172
|
+
if (cancelled) {
|
|
1173
|
+
reject(new Error("Input cancelled."));
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
if (lines.length > 0 && (!started || depth > 0 || inBlockComment || inString)) {
|
|
1177
|
+
try {
|
|
1178
|
+
resolve(parseData(lines.join("\n")));
|
|
1179
|
+
} catch (err) {
|
|
1180
|
+
reject(err);
|
|
1181
|
+
}
|
|
1182
|
+
} else if (lines.length === 0) {
|
|
1183
|
+
reject(new Error("No input provided."));
|
|
1184
|
+
}
|
|
1185
|
+
});
|
|
1186
|
+
});
|
|
1187
|
+
}
|
|
1188
|
+
function trackDepth(line, depth, started, inBlockComment, inString, stringChar) {
|
|
1189
|
+
for (let i = 0; i < line.length; i++) {
|
|
1190
|
+
const ch = line[i];
|
|
1191
|
+
const next = i + 1 < line.length ? line[i + 1] : "";
|
|
1192
|
+
if (inBlockComment) {
|
|
1193
|
+
if (ch === "*" && next === "/") {
|
|
1194
|
+
inBlockComment = false;
|
|
1195
|
+
i++;
|
|
1196
|
+
}
|
|
1197
|
+
continue;
|
|
1198
|
+
}
|
|
1199
|
+
if (inString) {
|
|
1200
|
+
if (ch === "\\" && i + 1 < line.length) {
|
|
1201
|
+
i++;
|
|
1202
|
+
} else if (ch === stringChar) {
|
|
1203
|
+
inString = false;
|
|
1204
|
+
}
|
|
1205
|
+
continue;
|
|
1206
|
+
}
|
|
1207
|
+
if (ch === "/" && next === "/") break;
|
|
1208
|
+
if (ch === "/" && next === "*") {
|
|
1209
|
+
inBlockComment = true;
|
|
1210
|
+
i++;
|
|
1211
|
+
continue;
|
|
1212
|
+
}
|
|
1213
|
+
if (ch === '"' || ch === "'") {
|
|
1214
|
+
inString = true;
|
|
1215
|
+
stringChar = ch;
|
|
1216
|
+
} else if (ch === "{" || ch === "[") {
|
|
1217
|
+
depth++;
|
|
1218
|
+
started = true;
|
|
1219
|
+
} else if (ch === "}" || ch === "]") {
|
|
1220
|
+
depth--;
|
|
1048
1221
|
}
|
|
1049
|
-
throw new Error(`OAuth token request failed: ${message}`);
|
|
1050
1222
|
}
|
|
1051
|
-
return
|
|
1223
|
+
return { depth, started, inBlockComment, inString, stringChar };
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
// src/commands/me-oauth-clients.ts
|
|
1227
|
+
function addMeOAuthClientsSubcommand(me) {
|
|
1228
|
+
const oauthClients = me.command("oauth-clients").description("Manage your OAuth clients");
|
|
1229
|
+
const list = oauthClients.command("list").description("List your OAuth clients").action(
|
|
1230
|
+
withErrorHandler(async (_opts, cmd) => {
|
|
1231
|
+
const client = createClient(cmd);
|
|
1232
|
+
const format = getFormat(cmd);
|
|
1233
|
+
const response = await client.rawRequest("GET", "/me/oauth-clients");
|
|
1234
|
+
outputResponse(response, format);
|
|
1235
|
+
})
|
|
1236
|
+
);
|
|
1237
|
+
addExamples(list, [
|
|
1238
|
+
{
|
|
1239
|
+
description: "List your OAuth clients",
|
|
1240
|
+
command: "geonic me oauth-clients list"
|
|
1241
|
+
}
|
|
1242
|
+
]);
|
|
1243
|
+
const create = oauthClients.command("create [json]").description("Create a new OAuth client").option("--name <name>", "Client name").option("--scopes <scopes>", "Allowed scopes (comma-separated)").option("--save", "Save credentials to config for automatic re-authentication").action(
|
|
1244
|
+
withErrorHandler(async (json, _opts, cmd) => {
|
|
1245
|
+
const opts = cmd.opts();
|
|
1246
|
+
let body;
|
|
1247
|
+
if (json) {
|
|
1248
|
+
body = await parseJsonInput(json);
|
|
1249
|
+
} else if (opts.name || opts.scopes) {
|
|
1250
|
+
const payload = {};
|
|
1251
|
+
if (opts.name) payload.clientName = opts.name;
|
|
1252
|
+
if (opts.scopes) payload.allowedScopes = opts.scopes.split(",").map((s) => s.trim());
|
|
1253
|
+
body = payload;
|
|
1254
|
+
} else {
|
|
1255
|
+
body = await parseJsonInput();
|
|
1256
|
+
}
|
|
1257
|
+
const client = createClient(cmd);
|
|
1258
|
+
const format = getFormat(cmd);
|
|
1259
|
+
const response = await client.rawRequest("POST", "/me/oauth-clients", { body });
|
|
1260
|
+
const data = response.data;
|
|
1261
|
+
if (opts.save) {
|
|
1262
|
+
const globalOpts = resolveOptions(cmd);
|
|
1263
|
+
const clientId = data.clientId;
|
|
1264
|
+
const clientSecret = data.clientSecret;
|
|
1265
|
+
if (!clientId || !clientSecret) {
|
|
1266
|
+
printError("Response missing clientId or clientSecret. Cannot save credentials.");
|
|
1267
|
+
outputResponse(response, format);
|
|
1268
|
+
printSuccess("OAuth client created.");
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
const baseUrl = validateUrl(globalOpts.url);
|
|
1272
|
+
const tokenResult = await clientCredentialsGrant({
|
|
1273
|
+
baseUrl,
|
|
1274
|
+
clientId,
|
|
1275
|
+
clientSecret,
|
|
1276
|
+
scope: data.allowedScopes?.join(" ")
|
|
1277
|
+
});
|
|
1278
|
+
const config = loadConfig(globalOpts.profile);
|
|
1279
|
+
config.clientId = clientId;
|
|
1280
|
+
config.clientSecret = clientSecret;
|
|
1281
|
+
config.token = tokenResult.access_token;
|
|
1282
|
+
delete config.refreshToken;
|
|
1283
|
+
saveConfig(config, globalOpts.profile);
|
|
1284
|
+
printInfo("Client credentials saved to config. Auto-reauth enabled.");
|
|
1285
|
+
} else {
|
|
1286
|
+
printWarning(
|
|
1287
|
+
"Save the clientSecret now \u2014 it will not be shown again."
|
|
1288
|
+
);
|
|
1289
|
+
}
|
|
1290
|
+
outputResponse(response, format);
|
|
1291
|
+
printSuccess("OAuth client created.");
|
|
1292
|
+
})
|
|
1293
|
+
);
|
|
1294
|
+
addExamples(create, [
|
|
1295
|
+
{
|
|
1296
|
+
description: "Create an OAuth client with flags",
|
|
1297
|
+
command: "geonic me oauth-clients create --name my-ci-bot --scopes read:entities,write:entities"
|
|
1298
|
+
},
|
|
1299
|
+
{
|
|
1300
|
+
description: "Create and save credentials for auto-reauth",
|
|
1301
|
+
command: "geonic me oauth-clients create --name my-ci-bot --save"
|
|
1302
|
+
},
|
|
1303
|
+
{
|
|
1304
|
+
description: "Create an OAuth client from JSON",
|
|
1305
|
+
command: `geonic me oauth-clients create '{"clientName":"my-bot","allowedScopes":["read:entities"]}'`
|
|
1306
|
+
}
|
|
1307
|
+
]);
|
|
1308
|
+
const del = oauthClients.command("delete <id>").description("Delete an OAuth client").action(
|
|
1309
|
+
withErrorHandler(async (id, _opts, cmd) => {
|
|
1310
|
+
const client = createClient(cmd);
|
|
1311
|
+
await client.rawRequest(
|
|
1312
|
+
"DELETE",
|
|
1313
|
+
`/me/oauth-clients/${encodeURIComponent(String(id))}`
|
|
1314
|
+
);
|
|
1315
|
+
printSuccess("OAuth client deleted.");
|
|
1316
|
+
})
|
|
1317
|
+
);
|
|
1318
|
+
addExamples(del, [
|
|
1319
|
+
{
|
|
1320
|
+
description: "Delete an OAuth client",
|
|
1321
|
+
command: "geonic me oauth-clients delete <client-id>"
|
|
1322
|
+
}
|
|
1323
|
+
]);
|
|
1052
1324
|
}
|
|
1053
1325
|
|
|
1054
1326
|
// src/commands/auth.ts
|
|
@@ -1208,13 +1480,25 @@ function registerAuthCommands(program2) {
|
|
|
1208
1480
|
}
|
|
1209
1481
|
]);
|
|
1210
1482
|
auth.addCommand(logout);
|
|
1211
|
-
const me = program2.command("me").description("Display current authenticated user")
|
|
1483
|
+
const me = program2.command("me").description("Display current authenticated user and manage user resources");
|
|
1484
|
+
const meInfo = me.command("info", { isDefault: true, hidden: true }).description("Display current authenticated user").action(createMeAction());
|
|
1212
1485
|
addExamples(me, [
|
|
1486
|
+
{
|
|
1487
|
+
description: "Show current user info",
|
|
1488
|
+
command: "geonic me"
|
|
1489
|
+
},
|
|
1490
|
+
{
|
|
1491
|
+
description: "List your OAuth clients",
|
|
1492
|
+
command: "geonic me oauth-clients list"
|
|
1493
|
+
}
|
|
1494
|
+
]);
|
|
1495
|
+
addExamples(meInfo, [
|
|
1213
1496
|
{
|
|
1214
1497
|
description: "Show current user info",
|
|
1215
1498
|
command: "geonic me"
|
|
1216
1499
|
}
|
|
1217
1500
|
]);
|
|
1501
|
+
addMeOAuthClientsSubcommand(me);
|
|
1218
1502
|
program2.addCommand(createLoginCommand(), { hidden: true });
|
|
1219
1503
|
program2.addCommand(createLogoutCommand(), { hidden: true });
|
|
1220
1504
|
const hiddenWhoami = new Command("whoami").description("Display current authenticated user").action(createMeAction());
|
|
@@ -1310,119 +1594,6 @@ function registerProfileCommands(program2) {
|
|
|
1310
1594
|
]);
|
|
1311
1595
|
}
|
|
1312
1596
|
|
|
1313
|
-
// src/input.ts
|
|
1314
|
-
import JSON5 from "json5";
|
|
1315
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
1316
|
-
import { createInterface as createInterface2 } from "readline";
|
|
1317
|
-
async function parseJsonInput(input) {
|
|
1318
|
-
if (input !== void 0) {
|
|
1319
|
-
if (input === "-") return parseData(readFileSync2(0, "utf-8"));
|
|
1320
|
-
if (input.startsWith("@")) return parseData(readFileSync2(input.slice(1), "utf-8"));
|
|
1321
|
-
return parseData(input);
|
|
1322
|
-
}
|
|
1323
|
-
if (!process.stdin.isTTY) {
|
|
1324
|
-
return parseData(readFileSync2(0, "utf-8"));
|
|
1325
|
-
}
|
|
1326
|
-
return readInteractiveJson();
|
|
1327
|
-
}
|
|
1328
|
-
function parseData(text) {
|
|
1329
|
-
return JSON5.parse(text.trim());
|
|
1330
|
-
}
|
|
1331
|
-
async function readInteractiveJson() {
|
|
1332
|
-
const rl = createInterface2({
|
|
1333
|
-
input: process.stdin,
|
|
1334
|
-
output: process.stderr,
|
|
1335
|
-
prompt: "json> "
|
|
1336
|
-
});
|
|
1337
|
-
process.stderr.write("Enter JSON (auto-submits when braces close, Ctrl+C to cancel):\n");
|
|
1338
|
-
rl.prompt();
|
|
1339
|
-
const lines = [];
|
|
1340
|
-
let depth = 0;
|
|
1341
|
-
let started = false;
|
|
1342
|
-
let inBlockComment = false;
|
|
1343
|
-
let inString = false;
|
|
1344
|
-
let stringChar = "";
|
|
1345
|
-
let cancelled = false;
|
|
1346
|
-
return new Promise((resolve, reject) => {
|
|
1347
|
-
rl.on("SIGINT", () => {
|
|
1348
|
-
cancelled = true;
|
|
1349
|
-
rl.close();
|
|
1350
|
-
});
|
|
1351
|
-
rl.on("line", (line) => {
|
|
1352
|
-
lines.push(line);
|
|
1353
|
-
const result = trackDepth(line, depth, started, inBlockComment, inString, stringChar);
|
|
1354
|
-
depth = result.depth;
|
|
1355
|
-
started = result.started;
|
|
1356
|
-
inBlockComment = result.inBlockComment;
|
|
1357
|
-
inString = result.inString;
|
|
1358
|
-
stringChar = result.stringChar;
|
|
1359
|
-
if (started && depth <= 0 && !inBlockComment && !inString) {
|
|
1360
|
-
rl.close();
|
|
1361
|
-
try {
|
|
1362
|
-
resolve(parseData(lines.join("\n")));
|
|
1363
|
-
} catch (err) {
|
|
1364
|
-
reject(err);
|
|
1365
|
-
}
|
|
1366
|
-
} else {
|
|
1367
|
-
rl.setPrompt("... ");
|
|
1368
|
-
rl.prompt();
|
|
1369
|
-
}
|
|
1370
|
-
});
|
|
1371
|
-
rl.on("close", () => {
|
|
1372
|
-
if (cancelled) {
|
|
1373
|
-
reject(new Error("Input cancelled."));
|
|
1374
|
-
return;
|
|
1375
|
-
}
|
|
1376
|
-
if (lines.length > 0 && (!started || depth > 0 || inBlockComment || inString)) {
|
|
1377
|
-
try {
|
|
1378
|
-
resolve(parseData(lines.join("\n")));
|
|
1379
|
-
} catch (err) {
|
|
1380
|
-
reject(err);
|
|
1381
|
-
}
|
|
1382
|
-
} else if (lines.length === 0) {
|
|
1383
|
-
reject(new Error("No input provided."));
|
|
1384
|
-
}
|
|
1385
|
-
});
|
|
1386
|
-
});
|
|
1387
|
-
}
|
|
1388
|
-
function trackDepth(line, depth, started, inBlockComment, inString, stringChar) {
|
|
1389
|
-
for (let i = 0; i < line.length; i++) {
|
|
1390
|
-
const ch = line[i];
|
|
1391
|
-
const next = i + 1 < line.length ? line[i + 1] : "";
|
|
1392
|
-
if (inBlockComment) {
|
|
1393
|
-
if (ch === "*" && next === "/") {
|
|
1394
|
-
inBlockComment = false;
|
|
1395
|
-
i++;
|
|
1396
|
-
}
|
|
1397
|
-
continue;
|
|
1398
|
-
}
|
|
1399
|
-
if (inString) {
|
|
1400
|
-
if (ch === "\\" && i + 1 < line.length) {
|
|
1401
|
-
i++;
|
|
1402
|
-
} else if (ch === stringChar) {
|
|
1403
|
-
inString = false;
|
|
1404
|
-
}
|
|
1405
|
-
continue;
|
|
1406
|
-
}
|
|
1407
|
-
if (ch === "/" && next === "/") break;
|
|
1408
|
-
if (ch === "/" && next === "*") {
|
|
1409
|
-
inBlockComment = true;
|
|
1410
|
-
i++;
|
|
1411
|
-
continue;
|
|
1412
|
-
}
|
|
1413
|
-
if (ch === '"' || ch === "'") {
|
|
1414
|
-
inString = true;
|
|
1415
|
-
stringChar = ch;
|
|
1416
|
-
} else if (ch === "{" || ch === "[") {
|
|
1417
|
-
depth++;
|
|
1418
|
-
started = true;
|
|
1419
|
-
} else if (ch === "}" || ch === "]") {
|
|
1420
|
-
depth--;
|
|
1421
|
-
}
|
|
1422
|
-
}
|
|
1423
|
-
return { depth, started, inBlockComment, inString, stringChar };
|
|
1424
|
-
}
|
|
1425
|
-
|
|
1426
1597
|
// src/commands/attrs.ts
|
|
1427
1598
|
function addAttrsSubcommands(attrs) {
|
|
1428
1599
|
const list = attrs.command("list").description("List all attributes of an entity").argument("<entityId>", "Entity ID").action(
|
|
@@ -3379,7 +3550,7 @@ function createProgram() {
|
|
|
3379
3550
|
const require2 = createRequire3(import.meta.url);
|
|
3380
3551
|
const pkg = require2("../package.json");
|
|
3381
3552
|
const program2 = new Command2();
|
|
3382
|
-
program2.name("geonic").description(pkg.description).option("-u, --url <url>", "Base URL of the GeonicDB server").option("-s, --service <name>", "NGSILD-Tenant header").option("--token <token>", "Authentication token").option("-p, --profile <name>", "Use a named profile").option("--api-key <key>", "API key for authentication").option("-f, --format <fmt>", "Output format: json, table, geojson").option("--no-color", "Disable color output").option("-v, --verbose", "Verbose output");
|
|
3553
|
+
program2.name("geonic").description(pkg.description).option("-u, --url <url>", "Base URL of the GeonicDB server").option("-s, --service <name>", "NGSILD-Tenant header").option("--token <token>", "Authentication token").option("-p, --profile <name>", "Use a named profile").option("--api-key <key>", "API key for authentication").option("-f, --format <fmt>", "Output format: json, table, geojson").option("--no-color", "Disable color output").option("-v, --verbose", "Verbose output").option("--dry-run", "Print the equivalent curl command without executing");
|
|
3383
3554
|
registerHelpCommand(program2);
|
|
3384
3555
|
registerConfigCommand(program2);
|
|
3385
3556
|
registerAuthCommands(program2);
|