@burdenoff/bigconsole-cli 2026.802.1 → 2026.1110.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -184
- package/dist/auth/store.d.ts +0 -4
- package/dist/cli.js +2959 -100
- package/dist/commands/base.d.ts +11 -4
- package/dist/graphql/client.d.ts +12 -0
- package/dist/graphql/operations.d.ts +14 -13
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2895 -6
- package/dist/utils/output.d.ts +29 -7
- package/dist/utils/parse.d.ts +5 -0
- package/dist/utils/sanitize.d.ts +79 -0
- package/package.json +59 -51
- package/dist/auth/device-code.js +0 -239
- package/dist/auth/store.js +0 -90
- package/dist/commands/auth.js +0 -352
- package/dist/commands/base.js +0 -100
- package/dist/commands/cli-config.js +0 -79
- package/dist/commands/dashboard.js +0 -220
- package/dist/commands/datasink.js +0 -175
- package/dist/commands/drilldown.js +0 -47
- package/dist/commands/index.js +0 -13
- package/dist/commands/parser.js +0 -181
- package/dist/commands/pipeline.js +0 -142
- package/dist/commands/prompt.js +0 -84
- package/dist/commands/share.js +0 -50
- package/dist/commands/template.js +0 -62
- package/dist/commands/widget.js +0 -164
- package/dist/commands/workflow.js +0 -54
- package/dist/commands/workspace.js +0 -95
- package/dist/config/index.js +0 -151
- package/dist/graphql/client.js +0 -274
- package/dist/graphql/operations.js +0 -654
- package/dist/types/index.d.ts +0 -30
- package/dist/types/index.js +0 -7
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/index.js +0 -1
- package/dist/utils/output.js +0 -186
package/dist/cli.js
CHANGED
|
@@ -1,65 +1,2921 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import boxen from "boxen";
|
|
7
|
+
import updateNotifier from "update-notifier";
|
|
8
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
9
|
+
import { join as join3, dirname } from "path";
|
|
10
|
+
import { fileURLToPath } from "url";
|
|
11
|
+
|
|
12
|
+
// src/utils/sanitize.ts
|
|
13
|
+
var MAX_ERROR_MESSAGE_LENGTH = 500;
|
|
14
|
+
var MAX_HTTP_BODY_EXCERPT_LENGTH = 200;
|
|
15
|
+
var MAX_SCAN_LENGTH = 4000;
|
|
16
|
+
var REDACTED = "[REDACTED]";
|
|
17
|
+
var ELLIPSIS = "…";
|
|
18
|
+
var ESC = 27;
|
|
19
|
+
var BEL = 7;
|
|
20
|
+
var TAB = 9;
|
|
21
|
+
var LF = 10;
|
|
22
|
+
var CR = 13;
|
|
23
|
+
var DEL = 127;
|
|
24
|
+
var NEL = 133;
|
|
25
|
+
var C1_CSI = 155;
|
|
26
|
+
var C1_ST = 156;
|
|
27
|
+
var LINE_SEPARATOR = 8232;
|
|
28
|
+
var PARAGRAPH_SEPARATOR = 8233;
|
|
29
|
+
var C1_STRING_INTRODUCERS = new Set([144, 152, 157, 158, 159]);
|
|
30
|
+
var ESC_STRING_INTRODUCERS = new Set(["P", "X", "]", "^", "_"].map((c) => c.charCodeAt(0)));
|
|
31
|
+
var BIDI_CONTROLS = /[\u202a-\u202e\u2066-\u2069]/g;
|
|
32
|
+
function toUnicodeEscape(char) {
|
|
33
|
+
return `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`;
|
|
34
|
+
}
|
|
35
|
+
function escapeBidiControls(text) {
|
|
36
|
+
return text.replace(BIDI_CONTROLS, toUnicodeEscape);
|
|
37
|
+
}
|
|
38
|
+
function isInRange(code, low, high) {
|
|
39
|
+
return code >= low && code <= high;
|
|
40
|
+
}
|
|
41
|
+
function skipControlSequence(text, index) {
|
|
42
|
+
let i = index;
|
|
43
|
+
while (i < text.length && isInRange(text.charCodeAt(i), 48, 63))
|
|
44
|
+
i++;
|
|
45
|
+
while (i < text.length && isInRange(text.charCodeAt(i), 32, 47))
|
|
46
|
+
i++;
|
|
47
|
+
if (i < text.length && isInRange(text.charCodeAt(i), 64, 126))
|
|
48
|
+
i++;
|
|
49
|
+
return i;
|
|
50
|
+
}
|
|
51
|
+
function skipControlString(text, index) {
|
|
52
|
+
let i = index;
|
|
53
|
+
while (i < text.length) {
|
|
54
|
+
const code = text.charCodeAt(i);
|
|
55
|
+
if (code === BEL || code === C1_ST)
|
|
56
|
+
return i + 1;
|
|
57
|
+
if (code === ESC)
|
|
58
|
+
return text.charCodeAt(i + 1) === 92 ? i + 2 : i;
|
|
59
|
+
i++;
|
|
60
|
+
}
|
|
61
|
+
return i;
|
|
62
|
+
}
|
|
63
|
+
function skipEscapeSequence(text, index) {
|
|
64
|
+
const next = text.charCodeAt(index + 1);
|
|
65
|
+
if (Number.isNaN(next))
|
|
66
|
+
return index + 1;
|
|
67
|
+
if (next === 91)
|
|
68
|
+
return skipControlSequence(text, index + 2);
|
|
69
|
+
if (ESC_STRING_INTRODUCERS.has(next))
|
|
70
|
+
return skipControlString(text, index + 2);
|
|
71
|
+
let i = index + 1;
|
|
72
|
+
while (i < text.length && isInRange(text.charCodeAt(i), 32, 47))
|
|
73
|
+
i++;
|
|
74
|
+
if (i < text.length && isInRange(text.charCodeAt(i), 48, 126))
|
|
75
|
+
i++;
|
|
76
|
+
return i;
|
|
77
|
+
}
|
|
78
|
+
function sanitizeForTerminal(value, options = {}) {
|
|
79
|
+
const text = value === null || value === undefined ? "" : String(value);
|
|
80
|
+
const multiline = options.preserveNewlines === true;
|
|
81
|
+
let out = "";
|
|
82
|
+
let i = 0;
|
|
83
|
+
while (i < text.length) {
|
|
84
|
+
const code = text.charCodeAt(i);
|
|
85
|
+
if (code === ESC) {
|
|
86
|
+
i = skipEscapeSequence(text, i);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (code === C1_CSI) {
|
|
90
|
+
i = skipControlSequence(text, i + 1);
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (C1_STRING_INTRODUCERS.has(code)) {
|
|
94
|
+
i = skipControlString(text, i + 1);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (code === CR || code === LF) {
|
|
98
|
+
const crlf = code === CR && text.charCodeAt(i + 1) === LF;
|
|
99
|
+
out += multiline ? `
|
|
100
|
+
` : " ";
|
|
101
|
+
i += crlf ? 2 : 1;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (code === NEL || code === LINE_SEPARATOR || code === PARAGRAPH_SEPARATOR) {
|
|
105
|
+
out += multiline ? toUnicodeEscape(text.charAt(i)) : " ";
|
|
106
|
+
i++;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (code === TAB) {
|
|
110
|
+
out += multiline ? "\t" : " ";
|
|
111
|
+
i++;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (code < 32 || isInRange(code, DEL, 159)) {
|
|
115
|
+
i++;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
out += text.charAt(i);
|
|
119
|
+
i++;
|
|
120
|
+
}
|
|
121
|
+
return escapeBidiControls(out);
|
|
122
|
+
}
|
|
123
|
+
var SECRET_KEY_HINT = /pass(?:word|wd|phrase)?|secret|token|api[-_]?key|private[-_]?key|access[-_]?key|credential|authori[sz]ation|cookie|session[-_]?id/i;
|
|
124
|
+
var KEY_VALUE_OPENING = /\b([A-Za-z_$][\w$-]{0,127})\\*["']?\s*[:=]\s*/g;
|
|
125
|
+
var QUOTE_OPENING = /^(\\*)(["'])/;
|
|
126
|
+
var UNQUOTED_VALUE = /^[^\s"'\\,;&)}\]]*/;
|
|
127
|
+
var AUTH_SCHEME = /^(?:Bearer|Basic|Digest|Token)\s+/i;
|
|
128
|
+
var BEARER_CREDENTIAL = /\bBearer\s+[A-Za-z0-9\-._~+/]+=*/g;
|
|
129
|
+
var JWT_LIKE = /\beyJ[\w-]{4,}\.[\w-]{4,}\.[\w-]*/g;
|
|
130
|
+
var URL_USERINFO = /\b([a-z][a-z0-9+.-]{1,31}:\/\/)[^\s/?#@"'\\]+@/gi;
|
|
131
|
+
function backslashRun(text, index, floor) {
|
|
132
|
+
let run = 0;
|
|
133
|
+
while (index - run - 1 >= floor && text.charAt(index - run - 1) === "\\")
|
|
134
|
+
run++;
|
|
135
|
+
return run;
|
|
136
|
+
}
|
|
137
|
+
function maskValue(text, start) {
|
|
138
|
+
if (text.startsWith(REDACTED, start)) {
|
|
139
|
+
return { replacement: REDACTED, end: start + REDACTED.length };
|
|
140
|
+
}
|
|
141
|
+
const quote = QUOTE_OPENING.exec(text.slice(start, start + 64));
|
|
142
|
+
if (quote) {
|
|
143
|
+
const opener = quote[0];
|
|
144
|
+
const depth = (quote[1] ?? "").length;
|
|
145
|
+
const quoteChar = quote[2] ?? '"';
|
|
146
|
+
let search = start + opener.length;
|
|
147
|
+
while (search < text.length) {
|
|
148
|
+
const candidate = text.indexOf(quoteChar, search);
|
|
149
|
+
if (candidate === -1)
|
|
150
|
+
break;
|
|
151
|
+
if (backslashRun(text, candidate, start + opener.length) === depth) {
|
|
152
|
+
return { replacement: `${opener}${REDACTED}${opener}`, end: candidate + 1 };
|
|
153
|
+
}
|
|
154
|
+
search = candidate + 1;
|
|
155
|
+
}
|
|
156
|
+
return { replacement: `${opener}${REDACTED}`, end: text.length };
|
|
157
|
+
}
|
|
158
|
+
const rest = text.slice(start);
|
|
159
|
+
const scheme = AUTH_SCHEME.exec(rest);
|
|
160
|
+
const offset = scheme ? scheme[0].length : 0;
|
|
161
|
+
const run = UNQUOTED_VALUE.exec(rest.slice(offset));
|
|
162
|
+
const length = offset + (run ? run[0].length : 0);
|
|
163
|
+
return { replacement: length > 0 ? REDACTED : "", end: start + length };
|
|
164
|
+
}
|
|
165
|
+
function redactKeyValues(text) {
|
|
166
|
+
let out = "";
|
|
167
|
+
let copiedUpTo = 0;
|
|
168
|
+
KEY_VALUE_OPENING.lastIndex = 0;
|
|
169
|
+
let match;
|
|
170
|
+
while ((match = KEY_VALUE_OPENING.exec(text)) !== null) {
|
|
171
|
+
if (!SECRET_KEY_HINT.test(match[1] ?? ""))
|
|
172
|
+
continue;
|
|
173
|
+
const start = match.index + match[0].length;
|
|
174
|
+
const { replacement, end } = maskValue(text, start);
|
|
175
|
+
if (end === start)
|
|
176
|
+
continue;
|
|
177
|
+
out += text.slice(copiedUpTo, start) + replacement;
|
|
178
|
+
copiedUpTo = end;
|
|
179
|
+
KEY_VALUE_OPENING.lastIndex = end;
|
|
180
|
+
}
|
|
181
|
+
return out + text.slice(copiedUpTo);
|
|
182
|
+
}
|
|
183
|
+
function redactSecrets(text) {
|
|
184
|
+
return redactKeyValues(text).replace(BEARER_CREDENTIAL, `Bearer ${REDACTED}`).replace(JWT_LIKE, REDACTED).replace(URL_USERINFO, `$1${REDACTED}@`);
|
|
185
|
+
}
|
|
186
|
+
var LINE_BREAK = /\r\n|[\r\n\u0085\u2028\u2029]/;
|
|
187
|
+
function toDisplayLine(value, maxLength = MAX_ERROR_MESSAGE_LENGTH) {
|
|
188
|
+
if (value === null || value === undefined)
|
|
189
|
+
return "";
|
|
190
|
+
const firstLine = String(value).split(LINE_BREAK, 1)[0] ?? "";
|
|
191
|
+
const scanned = firstLine.slice(0, MAX_SCAN_LENGTH);
|
|
192
|
+
const line = redactSecrets(sanitizeForTerminal(scanned)).trim();
|
|
193
|
+
return line.length > maxLength ? `${line.slice(0, maxLength)}${ELLIPSIS}` : line;
|
|
194
|
+
}
|
|
195
|
+
function summarizeHttpBody(body) {
|
|
196
|
+
return toDisplayLine(body, MAX_HTTP_BODY_EXCERPT_LENGTH) || "empty response body";
|
|
197
|
+
}
|
|
198
|
+
var ERROR_CODE = /^[A-Z][A-Z0-9_]{1,63}$/;
|
|
199
|
+
function asRecord(value) {
|
|
200
|
+
return typeof value === "object" && value !== null ? value : undefined;
|
|
201
|
+
}
|
|
202
|
+
function stringField(record, key) {
|
|
203
|
+
const value = record?.[key];
|
|
204
|
+
return typeof value === "string" ? value : undefined;
|
|
205
|
+
}
|
|
206
|
+
function isAbortError(error) {
|
|
207
|
+
const name = stringField(asRecord(error), "name");
|
|
208
|
+
return name === "AbortError" || name === "TimeoutError";
|
|
209
|
+
}
|
|
210
|
+
function describeResponseError(response) {
|
|
211
|
+
const status = typeof response.status === "number" ? response.status : undefined;
|
|
212
|
+
const errors = Array.isArray(response.errors) ? response.errors : [];
|
|
213
|
+
const first = asRecord(errors[0]);
|
|
214
|
+
const extensions = asRecord(first?.extensions);
|
|
215
|
+
const original = toDisplayLine(stringField(asRecord(extensions?.originalError), "message"));
|
|
216
|
+
const wrapper = toDisplayLine(stringField(first, "message"));
|
|
217
|
+
const rawCode = stringField(extensions, "code");
|
|
218
|
+
const code = rawCode !== undefined && ERROR_CODE.test(rawCode) ? rawCode : undefined;
|
|
219
|
+
const base = original || wrapper || (status !== undefined ? `Request failed (HTTP ${status})` : "Request failed");
|
|
220
|
+
return {
|
|
221
|
+
message: code ? `${base} [${code}]` : base,
|
|
222
|
+
...status !== undefined ? { status } : {},
|
|
223
|
+
...code ? { code } : {}
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
function networkErrorCode(error) {
|
|
227
|
+
const code = stringField(asRecord(asRecord(error)?.cause), "code");
|
|
228
|
+
return code !== undefined && ERROR_CODE.test(code) ? code : undefined;
|
|
229
|
+
}
|
|
230
|
+
function describeRequestError(error) {
|
|
231
|
+
const response = asRecord(asRecord(error)?.response);
|
|
232
|
+
if (response)
|
|
233
|
+
return describeResponseError(response);
|
|
234
|
+
if (isAbortError(error))
|
|
235
|
+
return { message: "Request timed out" };
|
|
236
|
+
if (error instanceof TypeError) {
|
|
237
|
+
const detail = toDisplayLine(error.message, MAX_HTTP_BODY_EXCERPT_LENGTH);
|
|
238
|
+
const code = networkErrorCode(error);
|
|
239
|
+
return {
|
|
240
|
+
message: `Network error${detail ? `: ${detail}` : ""}${code ? ` (${code})` : ""}`
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
return { message: toSafeErrorMessage(error) };
|
|
244
|
+
}
|
|
245
|
+
function toSafeErrorMessage(error) {
|
|
246
|
+
const response = asRecord(asRecord(error)?.response);
|
|
247
|
+
if (response)
|
|
248
|
+
return describeResponseError(response).message;
|
|
249
|
+
if (isAbortError(error))
|
|
250
|
+
return "Request timed out";
|
|
251
|
+
if (error instanceof Error || typeof error === "string") {
|
|
252
|
+
const text = typeof error === "string" ? error : error.message;
|
|
253
|
+
return toDisplayLine(text) || "Unexpected error";
|
|
254
|
+
}
|
|
255
|
+
return "Unexpected error";
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// src/config/index.ts
|
|
259
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync } from "fs";
|
|
260
|
+
import { join } from "path";
|
|
261
|
+
import { homedir } from "os";
|
|
262
|
+
var CONFIG_DIR = join(homedir(), ".config", "bigconsole-cli");
|
|
263
|
+
var CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
264
|
+
var DEFAULT_CLIENT_ID = "burdenoff_cli_bigconsole";
|
|
265
|
+
var PROD_OIDC_ISSUER = "https://graphql.burdenoff.com/global";
|
|
266
|
+
var PROD_WORKSPACE_ENDPOINT = "https://graphqlworkspaces.burdenoff.com/workspaces/graphql";
|
|
267
|
+
var PROD_GLOBAL_ENDPOINT = "https://graphql.burdenoff.com/global/graphql";
|
|
268
|
+
var DEFAULT_CONFIG = {
|
|
269
|
+
workspaceEndpoint: PROD_WORKSPACE_ENDPOINT,
|
|
270
|
+
globalEndpoint: PROD_GLOBAL_ENDPOINT
|
|
18
271
|
};
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
272
|
+
function ensureConfigDir() {
|
|
273
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
274
|
+
mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
|
|
275
|
+
}
|
|
22
276
|
}
|
|
23
|
-
|
|
24
|
-
|
|
277
|
+
function getEnvOverrides() {
|
|
278
|
+
const overrides = {};
|
|
279
|
+
if (process.env.BIGCONSOLE_WORKSPACE_ENDPOINT) {
|
|
280
|
+
overrides.workspaceEndpoint = process.env.BIGCONSOLE_WORKSPACE_ENDPOINT;
|
|
281
|
+
}
|
|
282
|
+
if (process.env.BIGCONSOLE_GLOBAL_ENDPOINT) {
|
|
283
|
+
overrides.globalEndpoint = process.env.BIGCONSOLE_GLOBAL_ENDPOINT;
|
|
284
|
+
}
|
|
285
|
+
if (process.env.BIGCONSOLE_OIDC_ISSUER) {
|
|
286
|
+
overrides.oidcIssuer = process.env.BIGCONSOLE_OIDC_ISSUER;
|
|
287
|
+
}
|
|
288
|
+
if (process.env.BIGCONSOLE_CLIENT_ID) {
|
|
289
|
+
overrides.clientId = process.env.BIGCONSOLE_CLIENT_ID;
|
|
290
|
+
}
|
|
291
|
+
return overrides;
|
|
25
292
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
293
|
+
function readConfig() {
|
|
294
|
+
try {
|
|
295
|
+
const envOverrides = getEnvOverrides();
|
|
296
|
+
if (!existsSync(CONFIG_FILE))
|
|
297
|
+
return { ...DEFAULT_CONFIG, ...envOverrides };
|
|
298
|
+
const raw = readFileSync(CONFIG_FILE, "utf8");
|
|
299
|
+
const stored = JSON.parse(raw);
|
|
300
|
+
return { ...DEFAULT_CONFIG, ...envOverrides, ...stored };
|
|
301
|
+
} catch {
|
|
302
|
+
return { ...DEFAULT_CONFIG };
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
function writeConfig(config) {
|
|
306
|
+
ensureConfigDir();
|
|
307
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { encoding: "utf8" });
|
|
308
|
+
try {
|
|
309
|
+
chmodSync(CONFIG_FILE, 384);
|
|
310
|
+
} catch {}
|
|
311
|
+
}
|
|
312
|
+
function getConfigValue(key) {
|
|
313
|
+
const config = readConfig();
|
|
314
|
+
return config[key];
|
|
315
|
+
}
|
|
316
|
+
function setConfigValue(key, value) {
|
|
317
|
+
const config = readConfig();
|
|
318
|
+
config[key] = value;
|
|
319
|
+
writeConfig(config);
|
|
320
|
+
}
|
|
321
|
+
function getConfigPath() {
|
|
322
|
+
return CONFIG_FILE;
|
|
323
|
+
}
|
|
324
|
+
function getConfigDir() {
|
|
325
|
+
return CONFIG_DIR;
|
|
326
|
+
}
|
|
327
|
+
function resolveOIDCConfig() {
|
|
328
|
+
const config = readConfig();
|
|
329
|
+
const clientId = config.clientId || DEFAULT_CLIENT_ID;
|
|
330
|
+
let oidcIssuer = config.oidcIssuer;
|
|
331
|
+
if (!oidcIssuer) {
|
|
332
|
+
const globalEndpoint = config.globalEndpoint;
|
|
333
|
+
const isProdEndpoint = globalEndpoint.includes("graphql.burdenoff.com");
|
|
334
|
+
if (isProdEndpoint) {
|
|
335
|
+
oidcIssuer = PROD_OIDC_ISSUER;
|
|
336
|
+
} else {
|
|
337
|
+
oidcIssuer = globalEndpoint.replace(/\/graphql$/, "");
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return { oidcIssuer, clientId };
|
|
341
|
+
}
|
|
342
|
+
var KNOWN_CONFIG_KEYS = [
|
|
343
|
+
"workspaceEndpoint",
|
|
344
|
+
"globalEndpoint",
|
|
345
|
+
"oidcIssuer",
|
|
346
|
+
"clientId",
|
|
347
|
+
"defaultFormat",
|
|
348
|
+
"verbose",
|
|
349
|
+
"authServiceEndpoint"
|
|
350
|
+
];
|
|
351
|
+
|
|
352
|
+
// src/auth/store.ts
|
|
353
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2, chmodSync as chmodSync2, unlinkSync } from "fs";
|
|
354
|
+
import { join as join2 } from "path";
|
|
355
|
+
import { homedir as homedir2 } from "os";
|
|
356
|
+
var CONFIG_DIR2 = join2(homedir2(), ".config", "bigconsole-cli");
|
|
357
|
+
var AUTH_FILE = join2(CONFIG_DIR2, "auth.json");
|
|
358
|
+
function ensureConfigDir2() {
|
|
359
|
+
if (!existsSync2(CONFIG_DIR2)) {
|
|
360
|
+
mkdirSync2(CONFIG_DIR2, { recursive: true, mode: 448 });
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function readAuth() {
|
|
364
|
+
try {
|
|
365
|
+
if (!existsSync2(AUTH_FILE))
|
|
366
|
+
return null;
|
|
367
|
+
const raw = readFileSync2(AUTH_FILE, "utf8");
|
|
368
|
+
return JSON.parse(raw);
|
|
369
|
+
} catch {
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
function writeAuth(auth) {
|
|
374
|
+
ensureConfigDir2();
|
|
375
|
+
writeFileSync2(AUTH_FILE, JSON.stringify(auth, null, 2), { encoding: "utf8", mode: 384 });
|
|
376
|
+
try {
|
|
377
|
+
chmodSync2(AUTH_FILE, 384);
|
|
378
|
+
} catch {}
|
|
379
|
+
}
|
|
380
|
+
function clearAuth() {
|
|
381
|
+
try {
|
|
382
|
+
if (existsSync2(AUTH_FILE)) {
|
|
383
|
+
unlinkSync(AUTH_FILE);
|
|
384
|
+
}
|
|
385
|
+
} catch {}
|
|
386
|
+
}
|
|
387
|
+
function isTokenExpired(auth) {
|
|
388
|
+
if (!auth.expiresAt)
|
|
389
|
+
return false;
|
|
390
|
+
return Date.now() >= auth.expiresAt;
|
|
391
|
+
}
|
|
392
|
+
function isTokenExpiringSoon(auth) {
|
|
393
|
+
if (!auth.expiresAt)
|
|
394
|
+
return false;
|
|
395
|
+
const BUFFER_MS = 5 * 60 * 1000;
|
|
396
|
+
return Date.now() + BUFFER_MS >= auth.expiresAt;
|
|
397
|
+
}
|
|
398
|
+
function isWorkspaceTokenExpiringSoon(auth) {
|
|
399
|
+
if (!auth.workspaceTokenExpiresAt)
|
|
400
|
+
return false;
|
|
401
|
+
const BUFFER_MS = 5 * 60 * 1000;
|
|
402
|
+
return Date.now() + BUFFER_MS >= auth.workspaceTokenExpiresAt;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// src/auth/device-code.ts
|
|
406
|
+
import fetch from "cross-fetch";
|
|
407
|
+
function serverMessage(message) {
|
|
408
|
+
return toDisplayLine(message, MAX_HTTP_BODY_EXCERPT_LENGTH) || "Unknown error";
|
|
409
|
+
}
|
|
410
|
+
async function httpFailure(prefix, response) {
|
|
411
|
+
const body = await response.text().catch(() => "");
|
|
412
|
+
return new Error(`${prefix} (${response.status}): ${summarizeHttpBody(body)}`);
|
|
413
|
+
}
|
|
414
|
+
async function startDeviceCodeFlow(config) {
|
|
415
|
+
const url = `${config.oidcIssuer}/oidc/device/code`;
|
|
416
|
+
const body = new URLSearchParams({
|
|
417
|
+
client_id: config.clientId,
|
|
418
|
+
scope: "openid profile email offline_access"
|
|
419
|
+
});
|
|
420
|
+
const response = await fetch(url, {
|
|
421
|
+
method: "POST",
|
|
422
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
423
|
+
body: body.toString()
|
|
424
|
+
});
|
|
425
|
+
if (!response.ok) {
|
|
426
|
+
throw await httpFailure("Device code request failed", response);
|
|
427
|
+
}
|
|
428
|
+
const data = await response.json();
|
|
429
|
+
return {
|
|
430
|
+
deviceCode: data["device_code"],
|
|
431
|
+
userCode: data["user_code"],
|
|
432
|
+
verificationUri: data["verification_uri"],
|
|
433
|
+
verificationUriComplete: data["verification_uri_complete"] ?? null,
|
|
434
|
+
expiresIn: data["expires_in"],
|
|
435
|
+
interval: data["interval"] ?? 5
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
async function pollForToken(deviceCode, interval, config) {
|
|
439
|
+
const url = `${config.oidcIssuer}/oauth/device/token`;
|
|
440
|
+
let pollInterval = interval;
|
|
441
|
+
while (true) {
|
|
442
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval * 1000));
|
|
443
|
+
const response = await fetch(url, {
|
|
444
|
+
method: "POST",
|
|
445
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
446
|
+
body: new URLSearchParams({
|
|
447
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
448
|
+
device_code: deviceCode,
|
|
449
|
+
client_id: config.clientId
|
|
450
|
+
}).toString()
|
|
30
451
|
});
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
452
|
+
const raw = await response.text();
|
|
453
|
+
let data;
|
|
454
|
+
try {
|
|
455
|
+
data = JSON.parse(raw);
|
|
456
|
+
} catch {
|
|
457
|
+
if (response.status >= 500) {
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
throw new Error(`Token endpoint returned non-JSON response (status ${response.status}): ${summarizeHttpBody(raw)}`);
|
|
461
|
+
}
|
|
462
|
+
if (response.ok && data["access_token"]) {
|
|
463
|
+
return {
|
|
464
|
+
accessToken: data["access_token"],
|
|
465
|
+
refreshToken: data["refresh_token"] ?? "",
|
|
466
|
+
expiresIn: data["expires_in"] ?? 3600
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
const errorCode = data["error"];
|
|
470
|
+
switch (errorCode) {
|
|
471
|
+
case "authorization_pending":
|
|
472
|
+
continue;
|
|
473
|
+
case "slow_down":
|
|
474
|
+
pollInterval += 5;
|
|
475
|
+
continue;
|
|
476
|
+
case "expired_token":
|
|
477
|
+
throw new Error("Device code has expired. Please try again.");
|
|
478
|
+
case "access_denied":
|
|
479
|
+
throw new Error("Authorization was denied by the user.");
|
|
480
|
+
default:
|
|
481
|
+
throw new Error(`Device code polling failed: ${serverMessage(data["error_description"] ?? errorCode)}`);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
async function clientCredentialsFlow(globalEndpoint, clientId, clientSecret, scopes) {
|
|
486
|
+
const query = `
|
|
487
|
+
mutation AuthenticateApp($input: AuthenticateAppInput!) {
|
|
488
|
+
authenticateApp(input: $input) {
|
|
489
|
+
accessToken tokenType expiresIn scopes
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
`;
|
|
493
|
+
const response = await fetch(globalEndpoint, {
|
|
494
|
+
method: "POST",
|
|
495
|
+
headers: { "Content-Type": "application/json" },
|
|
496
|
+
body: JSON.stringify({
|
|
497
|
+
query,
|
|
498
|
+
variables: { input: { clientId, clientSecret, scopes } }
|
|
499
|
+
})
|
|
500
|
+
});
|
|
501
|
+
if (!response.ok) {
|
|
502
|
+
throw await httpFailure("App authentication failed", response);
|
|
503
|
+
}
|
|
504
|
+
const data = await response.json();
|
|
505
|
+
if (data.errors?.length) {
|
|
506
|
+
throw new Error(`App authentication failed: ${serverMessage(data.errors[0]?.message)}`);
|
|
507
|
+
}
|
|
508
|
+
if (!data.data?.authenticateApp?.accessToken) {
|
|
509
|
+
throw new Error("App authentication failed: no access token returned");
|
|
510
|
+
}
|
|
511
|
+
return data.data.authenticateApp;
|
|
512
|
+
}
|
|
513
|
+
async function issueWorkspaceToken(workspaceEndpoint, authToken, workspaceId, organizationId) {
|
|
514
|
+
const query = `
|
|
515
|
+
mutation IssueWorkspaceToken($input: IssueWorkspaceTokenInput!) {
|
|
516
|
+
issueWorkspaceToken(input: $input) {
|
|
517
|
+
success token expiresAt scopes
|
|
518
|
+
error { code message }
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
`;
|
|
522
|
+
const response = await fetch(workspaceEndpoint, {
|
|
523
|
+
method: "POST",
|
|
524
|
+
headers: {
|
|
525
|
+
"Content-Type": "application/json",
|
|
526
|
+
Authorization: `Bearer ${authToken}`
|
|
527
|
+
},
|
|
528
|
+
body: JSON.stringify({
|
|
529
|
+
query,
|
|
530
|
+
variables: { input: { workspaceId, organizationId } }
|
|
531
|
+
})
|
|
532
|
+
});
|
|
533
|
+
if (!response.ok) {
|
|
534
|
+
throw await httpFailure("Workspace token request failed", response);
|
|
535
|
+
}
|
|
536
|
+
const data = await response.json();
|
|
537
|
+
if (data.errors?.length) {
|
|
538
|
+
throw new Error(`Workspace token failed: ${serverMessage(data.errors[0]?.message)}`);
|
|
539
|
+
}
|
|
540
|
+
const result = data.data?.issueWorkspaceToken;
|
|
541
|
+
if (!result?.success || !result.token) {
|
|
542
|
+
throw new Error(`Workspace token failed: ${serverMessage(result?.error?.message)}`);
|
|
543
|
+
}
|
|
544
|
+
return { token: result.token, expiresAt: result.expiresAt, scopes: result.scopes ?? [] };
|
|
545
|
+
}
|
|
546
|
+
async function fetchWorkspaces(globalEndpoint, authToken) {
|
|
547
|
+
const query = `
|
|
548
|
+
query MyWorkspaces($limit: Int) {
|
|
549
|
+
myWorkspaces(limit: $limit) {
|
|
550
|
+
items { id name slug tenantId organizationId }
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
`;
|
|
554
|
+
const response = await fetch(globalEndpoint, {
|
|
555
|
+
method: "POST",
|
|
556
|
+
headers: {
|
|
557
|
+
"Content-Type": "application/json",
|
|
558
|
+
Authorization: `Bearer ${authToken}`
|
|
559
|
+
},
|
|
560
|
+
body: JSON.stringify({ query, variables: { limit: 50 } })
|
|
561
|
+
});
|
|
562
|
+
if (!response.ok) {
|
|
563
|
+
throw await httpFailure("Failed to fetch workspaces", response);
|
|
564
|
+
}
|
|
565
|
+
const data = await response.json();
|
|
566
|
+
if (data.errors?.length) {
|
|
567
|
+
throw new Error(`Failed to fetch workspaces: ${serverMessage(data.errors[0]?.message)}`);
|
|
568
|
+
}
|
|
569
|
+
return data.data?.myWorkspaces?.items ?? [];
|
|
570
|
+
}
|
|
571
|
+
function isValidHttpUrl(urlString) {
|
|
572
|
+
try {
|
|
573
|
+
const parsed = new URL(urlString);
|
|
574
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
575
|
+
} catch {
|
|
576
|
+
return false;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
async function openBrowser(url) {
|
|
580
|
+
try {
|
|
581
|
+
if (!isValidHttpUrl(url)) {
|
|
582
|
+
return false;
|
|
583
|
+
}
|
|
584
|
+
const { execFile } = await import("child_process");
|
|
585
|
+
const platform = process.platform;
|
|
586
|
+
let command;
|
|
587
|
+
let args;
|
|
588
|
+
if (platform === "darwin") {
|
|
589
|
+
command = "open";
|
|
590
|
+
args = [url];
|
|
591
|
+
} else if (platform === "win32") {
|
|
592
|
+
command = "cmd";
|
|
593
|
+
args = ["/c", "start", "", url];
|
|
594
|
+
} else {
|
|
595
|
+
command = "xdg-open";
|
|
596
|
+
args = [url];
|
|
597
|
+
}
|
|
598
|
+
return new Promise((resolve) => {
|
|
599
|
+
execFile(command, args, (error) => {
|
|
600
|
+
resolve(!error);
|
|
601
|
+
});
|
|
602
|
+
});
|
|
603
|
+
} catch {
|
|
604
|
+
return false;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// src/graphql/client.ts
|
|
609
|
+
import { GraphQLClient } from "graphql-request";
|
|
610
|
+
|
|
611
|
+
// src/utils/output.ts
|
|
612
|
+
import {
|
|
613
|
+
DEFAULT_SCALAR_STYLE_RULES,
|
|
614
|
+
JSON_SCHEMA,
|
|
615
|
+
SCALAR_STYLE,
|
|
616
|
+
dump,
|
|
617
|
+
strTag
|
|
618
|
+
} from "js-yaml";
|
|
619
|
+
var MAX_COLUMN_WIDTH = 50;
|
|
620
|
+
function isInteractive() {
|
|
621
|
+
return Boolean(process.stdout.isTTY && process.stdin.isTTY);
|
|
622
|
+
}
|
|
623
|
+
function getFormat(options) {
|
|
624
|
+
if (options.format) {
|
|
625
|
+
const f = options.format.toLowerCase();
|
|
626
|
+
if (f === "json" || f === "table" || f === "yaml")
|
|
627
|
+
return f;
|
|
628
|
+
if (f === "yml")
|
|
629
|
+
return "yaml";
|
|
630
|
+
}
|
|
631
|
+
return isInteractive() ? "table" : "json";
|
|
632
|
+
}
|
|
633
|
+
var JSON_DISPLAY_ESCAPES = /[\u007f-\u009f\u2028\u2029\u202a-\u202e\u2066-\u2069]/g;
|
|
634
|
+
function toJson(data) {
|
|
635
|
+
const json = JSON.stringify(data, null, 2) ?? "null";
|
|
636
|
+
return json.replace(JSON_DISPLAY_ESCAPES, toUnicodeEscape);
|
|
637
|
+
}
|
|
638
|
+
function printJson(data) {
|
|
639
|
+
console.log(toJson(data));
|
|
640
|
+
}
|
|
641
|
+
function printTable(headers, rows) {
|
|
642
|
+
if (headers.length === 0)
|
|
643
|
+
return;
|
|
644
|
+
const header = headers.map((h) => sanitizeForTerminal(h));
|
|
645
|
+
const body = rows.map((row) => headers.map((_, i) => sanitizeForTerminal(row[i])));
|
|
646
|
+
const colWidths = header.map((h, i) => Math.min(body.reduce((max, row) => Math.max(max, (row[i] ?? "").length), h.length), MAX_COLUMN_WIDTH));
|
|
647
|
+
const separator = colWidths.map((w) => "-".repeat(w + 2)).join("+");
|
|
648
|
+
const formatRow = (cells) => cells.map((cell, i) => ` ${cell.padEnd(colWidths[i] ?? 0)} `).join("|");
|
|
649
|
+
console.log(formatRow(header));
|
|
650
|
+
console.log(separator);
|
|
651
|
+
for (const row of body) {
|
|
652
|
+
console.log(formatRow(row));
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
function allowsStyle(mask, style) {
|
|
656
|
+
return (mask & 1 << style) !== 0;
|
|
657
|
+
}
|
|
658
|
+
var BIDI_CONTROL = /[\u202a-\u202e\u2066-\u2069]/;
|
|
659
|
+
var UNAMBIGUOUS_PLAIN = /^\p{L}[\p{L}\p{N} _./@+-]*$/u;
|
|
660
|
+
var NON_STRING_WORDS = /^(?:y|n|yes|no|on|off|true|false|null)$/i;
|
|
661
|
+
var doubleQuoteBidiControls = (layout) => {
|
|
662
|
+
if (BIDI_CONTROL.test(layout.node.value))
|
|
663
|
+
layout.style = SCALAR_STYLE.DOUBLE_QUOTED;
|
|
664
|
+
};
|
|
665
|
+
var quoteAmbiguousStrings = (layout) => {
|
|
666
|
+
if (layout.style !== SCALAR_STYLE.PLAIN || layout.node.tag !== strTag.tagName)
|
|
667
|
+
return;
|
|
668
|
+
const value = layout.node.value;
|
|
669
|
+
if (UNAMBIGUOUS_PLAIN.test(value) && !NON_STRING_WORDS.test(value))
|
|
670
|
+
return;
|
|
671
|
+
layout.style = allowsStyle(layout.allowedStylesMask, SCALAR_STYLE.SINGLE_QUOTED) ? SCALAR_STYLE.SINGLE_QUOTED : SCALAR_STYLE.DOUBLE_QUOTED;
|
|
672
|
+
};
|
|
673
|
+
var YAML_STYLE_RULES = [
|
|
674
|
+
doubleQuoteBidiControls,
|
|
675
|
+
...Object.values(DEFAULT_SCALAR_STYLE_RULES),
|
|
676
|
+
quoteAmbiguousStrings
|
|
677
|
+
];
|
|
678
|
+
function toYaml(data) {
|
|
679
|
+
const yaml = dump(data ?? null, {
|
|
680
|
+
schema: JSON_SCHEMA,
|
|
681
|
+
noRefs: true,
|
|
682
|
+
skipInvalid: true,
|
|
683
|
+
lineWidth: -1,
|
|
684
|
+
scalarStyleRules: YAML_STYLE_RULES
|
|
685
|
+
});
|
|
686
|
+
return escapeBidiControls(yaml).trimEnd();
|
|
687
|
+
}
|
|
688
|
+
function printYaml(data) {
|
|
689
|
+
console.log(toYaml(data));
|
|
690
|
+
}
|
|
691
|
+
function printSuccess(message) {
|
|
692
|
+
console.error(`✓ ${sanitizeForTerminal(message)}`);
|
|
693
|
+
}
|
|
694
|
+
function printError(message) {
|
|
695
|
+
console.error(`✗ ${redactSecrets(sanitizeForTerminal(message))}`);
|
|
696
|
+
}
|
|
697
|
+
function printWarning(message) {
|
|
698
|
+
console.warn(`! ${redactSecrets(sanitizeForTerminal(message))}`);
|
|
699
|
+
}
|
|
700
|
+
function printInfo(message) {
|
|
701
|
+
console.error(`ℹ ${sanitizeForTerminal(message)}`);
|
|
702
|
+
}
|
|
703
|
+
function formatDate(date) {
|
|
704
|
+
if (!date)
|
|
705
|
+
return "-";
|
|
706
|
+
try {
|
|
707
|
+
return new Date(date).toLocaleString();
|
|
708
|
+
} catch {
|
|
709
|
+
return date;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
function formatStatus(status) {
|
|
713
|
+
if (!status)
|
|
714
|
+
return "-";
|
|
715
|
+
const upper = status.toUpperCase();
|
|
716
|
+
switch (upper) {
|
|
717
|
+
case "ACTIVE":
|
|
718
|
+
case "RUNNING":
|
|
719
|
+
case "HEALTHY":
|
|
720
|
+
return `✓ ${status}`;
|
|
721
|
+
case "INACTIVE":
|
|
722
|
+
case "STOPPED":
|
|
723
|
+
case "PENDING":
|
|
724
|
+
return `○ ${status}`;
|
|
725
|
+
case "ERROR":
|
|
726
|
+
return `✗ ${status}`;
|
|
727
|
+
case "ARCHIVED":
|
|
728
|
+
return `→ ${status}`;
|
|
729
|
+
case "STARTING":
|
|
730
|
+
return `▶ ${status}`;
|
|
731
|
+
case "TERMINATED":
|
|
732
|
+
return `■ ${status}`;
|
|
733
|
+
default:
|
|
734
|
+
return status;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
function outputData(format, data, tableConfig) {
|
|
738
|
+
switch (format) {
|
|
739
|
+
case "json":
|
|
740
|
+
printJson(data);
|
|
741
|
+
break;
|
|
742
|
+
case "yaml":
|
|
743
|
+
printYaml(data);
|
|
744
|
+
break;
|
|
745
|
+
case "table":
|
|
746
|
+
if (tableConfig) {
|
|
747
|
+
printTable(tableConfig.headers, tableConfig.rows);
|
|
748
|
+
} else {
|
|
749
|
+
printJson(data);
|
|
750
|
+
}
|
|
751
|
+
break;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
function truncate(str, maxLen) {
|
|
755
|
+
if (!str)
|
|
756
|
+
return "-";
|
|
757
|
+
const clean = sanitizeForTerminal(str);
|
|
758
|
+
if (clean.length <= maxLen)
|
|
759
|
+
return clean;
|
|
760
|
+
return clean.substring(0, maxLen - 3) + "...";
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// src/graphql/client.ts
|
|
764
|
+
var DEFAULT_TIMEOUT = 30000;
|
|
765
|
+
var MAX_RETRIES = 3;
|
|
766
|
+
var INITIAL_RETRY_DELAY_MS = 1000;
|
|
767
|
+
function sleep(ms) {
|
|
768
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
769
|
+
}
|
|
770
|
+
function isNetworkOrServerError(error) {
|
|
771
|
+
if (error instanceof TypeError)
|
|
772
|
+
return true;
|
|
773
|
+
if (error && typeof error === "object" && "response" in error) {
|
|
774
|
+
const resp = error.response;
|
|
775
|
+
if (resp && typeof resp.status === "number" && resp.status >= 500)
|
|
776
|
+
return true;
|
|
777
|
+
}
|
|
778
|
+
return false;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
class BigConsoleRequestError extends Error {
|
|
782
|
+
status;
|
|
783
|
+
code;
|
|
784
|
+
constructor(description) {
|
|
785
|
+
super(description.message);
|
|
786
|
+
this.name = "BigConsoleRequestError";
|
|
787
|
+
this.status = description.status;
|
|
788
|
+
this.code = description.code;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
var workspaceClient = null;
|
|
792
|
+
var globalClient = null;
|
|
793
|
+
function getHeaders(auth) {
|
|
794
|
+
const headers = {};
|
|
795
|
+
if (auth?.accessToken) {
|
|
796
|
+
headers["Authorization"] = `Bearer ${auth.accessToken}`;
|
|
797
|
+
}
|
|
798
|
+
return headers;
|
|
799
|
+
}
|
|
800
|
+
function getWorkspaceHeaders(auth) {
|
|
801
|
+
const headers = getHeaders(auth);
|
|
802
|
+
if (auth?.workspaceToken) {
|
|
803
|
+
headers["X-Workspace-Authorization"] = `Bearer ${auth.workspaceToken}`;
|
|
804
|
+
}
|
|
805
|
+
if (auth?.organizationId) {
|
|
806
|
+
headers["x-org-id"] = auth.organizationId;
|
|
807
|
+
}
|
|
808
|
+
return headers;
|
|
809
|
+
}
|
|
810
|
+
function initClients() {
|
|
811
|
+
const config = readConfig();
|
|
812
|
+
const auth = readAuth();
|
|
813
|
+
workspaceClient = new GraphQLClient(config.workspaceEndpoint);
|
|
814
|
+
workspaceClient.setHeaders(getWorkspaceHeaders(auth));
|
|
815
|
+
globalClient = new GraphQLClient(config.globalEndpoint);
|
|
816
|
+
globalClient.setHeaders(getHeaders(auth));
|
|
817
|
+
return { workspace: workspaceClient, global: globalClient };
|
|
818
|
+
}
|
|
819
|
+
async function refreshPlatformToken() {
|
|
820
|
+
const auth = readAuth();
|
|
821
|
+
if (!auth)
|
|
822
|
+
return;
|
|
823
|
+
if (auth.authMethod === "client_credentials" && auth.clientId && auth.clientSecret) {
|
|
824
|
+
const config = readConfig();
|
|
825
|
+
try {
|
|
826
|
+
const result = await clientCredentialsFlow(config.globalEndpoint, auth.clientId, auth.clientSecret);
|
|
827
|
+
writeAuth({
|
|
828
|
+
...auth,
|
|
829
|
+
accessToken: result.accessToken,
|
|
830
|
+
expiresAt: Date.now() + result.expiresIn * 1000
|
|
831
|
+
});
|
|
832
|
+
workspaceClient = null;
|
|
833
|
+
globalClient = null;
|
|
834
|
+
} catch (error) {
|
|
835
|
+
printError(`App token refresh failed: ${toSafeErrorMessage(error)}`);
|
|
836
|
+
}
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
if (!auth.refreshToken)
|
|
840
|
+
return;
|
|
841
|
+
const config = readConfig();
|
|
842
|
+
const client = new GraphQLClient(config.globalEndpoint);
|
|
843
|
+
client.setHeaders({ Authorization: `Bearer ${auth.accessToken}` });
|
|
844
|
+
const REFRESH_MUTATION = `
|
|
845
|
+
mutation RefreshToken($token: String!) {
|
|
846
|
+
refreshToken(token: $token) {
|
|
847
|
+
accessToken
|
|
848
|
+
refreshToken
|
|
849
|
+
expiresIn
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
`;
|
|
853
|
+
try {
|
|
854
|
+
const data = await client.request(REFRESH_MUTATION, {
|
|
855
|
+
token: auth.refreshToken
|
|
856
|
+
});
|
|
857
|
+
const result = data.refreshToken;
|
|
858
|
+
const expiresAt = Date.now() + result.expiresIn * 1000;
|
|
859
|
+
writeAuth({
|
|
860
|
+
...auth,
|
|
861
|
+
accessToken: result.accessToken,
|
|
862
|
+
refreshToken: result.refreshToken,
|
|
863
|
+
expiresAt
|
|
864
|
+
});
|
|
865
|
+
workspaceClient = null;
|
|
866
|
+
globalClient = null;
|
|
867
|
+
} catch (error) {
|
|
868
|
+
printError(`Token refresh failed: ${toSafeErrorMessage(error)}`);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
async function refreshWorkspaceToken() {
|
|
872
|
+
const auth = readAuth();
|
|
873
|
+
if (!auth?.accessToken || !auth.workspaceId || !auth.organizationId)
|
|
874
|
+
return;
|
|
875
|
+
const config = readConfig();
|
|
876
|
+
try {
|
|
877
|
+
const result = await issueWorkspaceToken(config.workspaceEndpoint, auth.accessToken, auth.workspaceId, auth.organizationId);
|
|
878
|
+
writeAuth({
|
|
879
|
+
...auth,
|
|
880
|
+
workspaceToken: result.token,
|
|
881
|
+
workspaceTokenExpiresAt: new Date(result.expiresAt).getTime()
|
|
882
|
+
});
|
|
883
|
+
workspaceClient = null;
|
|
884
|
+
} catch (error) {
|
|
885
|
+
printError(`Workspace token refresh failed: ${toSafeErrorMessage(error)}`);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
async function ensureFreshTokens() {
|
|
889
|
+
const auth = readAuth();
|
|
890
|
+
if (!auth)
|
|
891
|
+
return;
|
|
892
|
+
if (isTokenExpiringSoon(auth)) {
|
|
893
|
+
await refreshPlatformToken();
|
|
894
|
+
}
|
|
895
|
+
const freshAuth = readAuth();
|
|
896
|
+
if (freshAuth && freshAuth.workspaceToken && isWorkspaceTokenExpiringSoon(freshAuth)) {
|
|
897
|
+
await refreshWorkspaceToken();
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
function isRetryableDocument(document) {
|
|
901
|
+
const text = typeof document === "string" ? document : typeof document?.loc?.source?.body === "string" ? document.loc.source.body : "";
|
|
902
|
+
const m = text.match(/\b(query|mutation|subscription)\b/i);
|
|
903
|
+
return !m || m[1].toLowerCase() === "query";
|
|
904
|
+
}
|
|
905
|
+
async function executeWithRetry(client, document, variables) {
|
|
906
|
+
const retryable = isRetryableDocument(document);
|
|
907
|
+
const maxAttempts = retryable ? MAX_RETRIES : 1;
|
|
908
|
+
let lastError;
|
|
909
|
+
for (let attempt = 0;attempt < maxAttempts; attempt++) {
|
|
910
|
+
try {
|
|
911
|
+
const controller = new AbortController;
|
|
912
|
+
const timer = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT);
|
|
913
|
+
try {
|
|
914
|
+
return await client.request({
|
|
915
|
+
document,
|
|
916
|
+
variables,
|
|
917
|
+
signal: controller.signal
|
|
918
|
+
});
|
|
919
|
+
} finally {
|
|
920
|
+
clearTimeout(timer);
|
|
921
|
+
}
|
|
922
|
+
} catch (error) {
|
|
923
|
+
lastError = error;
|
|
924
|
+
if (error && typeof error === "object" && "response" in error) {
|
|
925
|
+
const resp = error.response;
|
|
926
|
+
if (resp && typeof resp.status === "number" && resp.status >= 400 && resp.status < 500) {
|
|
927
|
+
break;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
if (retryable && isNetworkOrServerError(error) && attempt < maxAttempts - 1) {
|
|
931
|
+
const delay = INITIAL_RETRY_DELAY_MS * Math.pow(2, attempt);
|
|
932
|
+
await sleep(delay);
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
935
|
+
break;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
throw new BigConsoleRequestError(describeRequestError(lastError));
|
|
939
|
+
}
|
|
940
|
+
async function workspaceRequest(document, variables) {
|
|
941
|
+
await ensureFreshTokens();
|
|
942
|
+
const { workspace } = workspaceClient ? { workspace: workspaceClient } : initClients();
|
|
943
|
+
const auth = readAuth();
|
|
944
|
+
workspace.setHeaders(getWorkspaceHeaders(auth));
|
|
945
|
+
return executeWithRetry(workspace, document, variables);
|
|
946
|
+
}
|
|
947
|
+
async function globalRequest(document, variables) {
|
|
948
|
+
const { global: client } = globalClient ? { global: globalClient } : initClients();
|
|
949
|
+
const auth = readAuth();
|
|
950
|
+
client.setHeaders(getHeaders(auth));
|
|
951
|
+
return executeWithRetry(client, document, variables);
|
|
952
|
+
}
|
|
953
|
+
function resetClients() {
|
|
954
|
+
workspaceClient = null;
|
|
955
|
+
globalClient = null;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
// src/graphql/operations.ts
|
|
959
|
+
var SIGN_OUT = `
|
|
960
|
+
mutation SignOut {
|
|
961
|
+
signOut
|
|
962
|
+
}
|
|
963
|
+
`;
|
|
964
|
+
var REFRESH_TOKEN = `
|
|
965
|
+
mutation RefreshToken($token: String!) {
|
|
966
|
+
refreshToken(token: $token) {
|
|
967
|
+
accessToken
|
|
968
|
+
refreshToken
|
|
969
|
+
expiresIn
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
`;
|
|
973
|
+
var LIST_DASHBOARDS = `
|
|
974
|
+
query ListDashboards(
|
|
975
|
+
$first: Int
|
|
976
|
+
$after: String
|
|
977
|
+
$search: String
|
|
978
|
+
$visibility: BigConsoleDashboardVisibility
|
|
979
|
+
$category: BigConsoleDashboardCategory
|
|
980
|
+
$includeArchived: Boolean
|
|
981
|
+
$sortBy: BigConsoleDashboardSortField
|
|
982
|
+
$sortOrder: SortOrder
|
|
983
|
+
) {
|
|
984
|
+
listDashboards(
|
|
985
|
+
first: $first
|
|
986
|
+
after: $after
|
|
987
|
+
search: $search
|
|
988
|
+
visibility: $visibility
|
|
989
|
+
category: $category
|
|
990
|
+
includeArchived: $includeArchived
|
|
991
|
+
sortBy: $sortBy
|
|
992
|
+
sortOrder: $sortOrder
|
|
993
|
+
) {
|
|
994
|
+
edges {
|
|
995
|
+
node {
|
|
996
|
+
id
|
|
997
|
+
name
|
|
998
|
+
description
|
|
999
|
+
status
|
|
1000
|
+
visibility
|
|
1001
|
+
category
|
|
1002
|
+
ownerId
|
|
1003
|
+
createdAt
|
|
1004
|
+
updatedAt
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
pageInfo { hasNextPage endCursor }
|
|
1008
|
+
totalCount
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
`;
|
|
1012
|
+
var GET_DASHBOARD = `
|
|
1013
|
+
query GetDashboard($id: ID!) {
|
|
1014
|
+
getDashboard(id: $id) {
|
|
1015
|
+
id
|
|
1016
|
+
name
|
|
1017
|
+
description
|
|
1018
|
+
status
|
|
1019
|
+
visibility
|
|
1020
|
+
category
|
|
1021
|
+
ownerId
|
|
1022
|
+
definitionVersion
|
|
1023
|
+
createdAt
|
|
1024
|
+
updatedAt
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
`;
|
|
1028
|
+
var ARCHIVE_DASHBOARD = `
|
|
1029
|
+
mutation ArchiveDashboard($id: ID!) {
|
|
1030
|
+
archiveDashboard(id: $id) {
|
|
1031
|
+
id
|
|
1032
|
+
name
|
|
1033
|
+
status
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
`;
|
|
1037
|
+
var DELETE_DASHBOARD = `
|
|
1038
|
+
mutation DeleteDashboard($id: ID!) {
|
|
1039
|
+
deleteBigConsoleDashboard(id: $id)
|
|
1040
|
+
}
|
|
1041
|
+
`;
|
|
1042
|
+
var LIST_WIDGETS = `
|
|
1043
|
+
query ListWidgets($pageId: ID!, $first: Int, $after: String, $type: BigConsoleWidgetType) {
|
|
1044
|
+
listWidgets(pageId: $pageId, first: $first, after: $after, type: $type) {
|
|
1045
|
+
edges {
|
|
1046
|
+
node {
|
|
1047
|
+
id
|
|
1048
|
+
pageId
|
|
1049
|
+
dashboardId
|
|
1050
|
+
type
|
|
1051
|
+
title
|
|
1052
|
+
dataSinkId
|
|
1053
|
+
lastRefreshedAt
|
|
1054
|
+
createdAt
|
|
1055
|
+
updatedAt
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
pageInfo { hasNextPage endCursor }
|
|
1059
|
+
totalCount
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
`;
|
|
1063
|
+
var GET_WIDGET = `
|
|
1064
|
+
query GetWidget($id: ID!) {
|
|
1065
|
+
getWidget(id: $id) {
|
|
1066
|
+
id
|
|
1067
|
+
pageId
|
|
1068
|
+
dashboardId
|
|
1069
|
+
type
|
|
1070
|
+
title
|
|
1071
|
+
config
|
|
1072
|
+
dataSinkId
|
|
1073
|
+
datasetId
|
|
1074
|
+
lastRefreshedAt
|
|
1075
|
+
cacheTTL
|
|
1076
|
+
createdAt
|
|
1077
|
+
updatedAt
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
`;
|
|
1081
|
+
var DELETE_WIDGET = `
|
|
1082
|
+
mutation DeleteWidget($id: ID!) {
|
|
1083
|
+
deleteBigConsoleWidget(id: $id)
|
|
1084
|
+
}
|
|
1085
|
+
`;
|
|
1086
|
+
var LIST_DATASINKS = `
|
|
1087
|
+
query ListDataSinks(
|
|
1088
|
+
$first: Int
|
|
1089
|
+
$after: String
|
|
1090
|
+
$search: String
|
|
1091
|
+
$status: DataSinkStatus
|
|
1092
|
+
$setupState: DataSinkSetupState
|
|
1093
|
+
$isActive: Boolean
|
|
1094
|
+
) {
|
|
1095
|
+
listDataSinks(
|
|
1096
|
+
first: $first
|
|
1097
|
+
after: $after
|
|
1098
|
+
search: $search
|
|
1099
|
+
status: $status
|
|
1100
|
+
setupState: $setupState
|
|
1101
|
+
isActive: $isActive
|
|
1102
|
+
) {
|
|
1103
|
+
edges {
|
|
1104
|
+
node {
|
|
1105
|
+
id
|
|
1106
|
+
name
|
|
1107
|
+
key
|
|
1108
|
+
description
|
|
1109
|
+
status
|
|
1110
|
+
setupState
|
|
1111
|
+
isActive
|
|
1112
|
+
createdAt
|
|
1113
|
+
updatedAt
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
pageInfo { hasNextPage endCursor }
|
|
1117
|
+
totalCount
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
`;
|
|
1121
|
+
var GET_DATASINK = `
|
|
1122
|
+
query GetDataSink($id: ID, $key: String) {
|
|
1123
|
+
getDataSink(id: $id, key: $key) {
|
|
1124
|
+
id
|
|
1125
|
+
name
|
|
1126
|
+
key
|
|
1127
|
+
description
|
|
1128
|
+
status
|
|
1129
|
+
setupState
|
|
1130
|
+
isActive
|
|
1131
|
+
version
|
|
1132
|
+
createdAt
|
|
1133
|
+
updatedAt
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
`;
|
|
1137
|
+
var GET_DATASINK_DATA = `
|
|
1138
|
+
query GetDataSinkData($id: ID, $key: String, $dataPath: String, $forceRefresh: Boolean) {
|
|
1139
|
+
getDataSinkData(id: $id, key: $key, dataPath: $dataPath, forceRefresh: $forceRefresh) {
|
|
1140
|
+
raw
|
|
1141
|
+
extracted
|
|
1142
|
+
cached
|
|
1143
|
+
fetchedAt
|
|
1144
|
+
version
|
|
1145
|
+
totalCount
|
|
1146
|
+
hasMore
|
|
1147
|
+
limit
|
|
1148
|
+
offset
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
`;
|
|
1152
|
+
var DELETE_DATASINK = `
|
|
1153
|
+
mutation DeleteDataSink($id: ID!) {
|
|
1154
|
+
deleteDataSink(id: $id) {
|
|
1155
|
+
id
|
|
1156
|
+
name
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
`;
|
|
1160
|
+
var LIST_PARSERS = `
|
|
1161
|
+
query ListParsers($first: Int, $after: String, $filter: ParserFilterInput) {
|
|
1162
|
+
listParsers(first: $first, after: $after, filter: $filter) {
|
|
1163
|
+
edges {
|
|
1164
|
+
node {
|
|
1165
|
+
id
|
|
1166
|
+
name
|
|
1167
|
+
description
|
|
1168
|
+
type
|
|
1169
|
+
status
|
|
1170
|
+
inputSinkKey
|
|
1171
|
+
outputKey
|
|
1172
|
+
outputWidgetId
|
|
1173
|
+
ttl
|
|
1174
|
+
version
|
|
1175
|
+
createdAt
|
|
1176
|
+
updatedAt
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
pageInfo { hasNextPage endCursor }
|
|
1180
|
+
totalCount
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
`;
|
|
1184
|
+
var GET_PARSER = `
|
|
1185
|
+
query GetParser($id: ID!) {
|
|
1186
|
+
parser(id: $id) {
|
|
1187
|
+
id
|
|
1188
|
+
name
|
|
1189
|
+
description
|
|
1190
|
+
type
|
|
1191
|
+
status
|
|
1192
|
+
expression
|
|
1193
|
+
inputSinkKey
|
|
1194
|
+
outputKey
|
|
1195
|
+
outputWidgetId
|
|
1196
|
+
ttl
|
|
1197
|
+
version
|
|
1198
|
+
createdAt
|
|
1199
|
+
updatedAt
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
`;
|
|
1203
|
+
var DELETE_PARSER = `
|
|
1204
|
+
mutation DeleteParser($id: ID!) {
|
|
1205
|
+
deleteParser(id: $id)
|
|
1206
|
+
}
|
|
1207
|
+
`;
|
|
1208
|
+
var LIST_PIPELINES = `
|
|
1209
|
+
query ListPipelines($first: Int, $after: String, $filter: PipelineFilterInput) {
|
|
1210
|
+
listPipelines(first: $first, after: $after, filter: $filter) {
|
|
1211
|
+
edges {
|
|
1212
|
+
node {
|
|
1213
|
+
id
|
|
1214
|
+
name
|
|
1215
|
+
description
|
|
1216
|
+
status
|
|
1217
|
+
version
|
|
1218
|
+
lastExecutedAt
|
|
1219
|
+
createdAt
|
|
1220
|
+
updatedAt
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
pageInfo { hasNextPage endCursor }
|
|
1224
|
+
totalCount
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
`;
|
|
1228
|
+
var GET_PIPELINE = `
|
|
1229
|
+
query GetPipeline($id: ID!) {
|
|
1230
|
+
pipeline(id: $id) {
|
|
1231
|
+
id
|
|
1232
|
+
name
|
|
1233
|
+
description
|
|
1234
|
+
status
|
|
1235
|
+
version
|
|
1236
|
+
lastExecutedAt
|
|
1237
|
+
stages {
|
|
1238
|
+
id
|
|
1239
|
+
position
|
|
1240
|
+
inputKey
|
|
1241
|
+
outputKey
|
|
1242
|
+
enabled
|
|
1243
|
+
parserName
|
|
1244
|
+
parserType
|
|
1245
|
+
}
|
|
1246
|
+
createdAt
|
|
1247
|
+
updatedAt
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
`;
|
|
1251
|
+
var DELETE_PIPELINE = `
|
|
1252
|
+
mutation DeletePipeline($id: ID!) {
|
|
1253
|
+
deletePipeline(id: $id)
|
|
1254
|
+
}
|
|
1255
|
+
`;
|
|
1256
|
+
var CREATE_DASHBOARD = `
|
|
1257
|
+
mutation CreateDashboard($input: BigConsoleCreateDashboardInput!) {
|
|
1258
|
+
createBigConsoleDashboard(input: $input) {
|
|
1259
|
+
id
|
|
1260
|
+
name
|
|
1261
|
+
status
|
|
1262
|
+
visibility
|
|
1263
|
+
category
|
|
1264
|
+
createdAt
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
`;
|
|
1268
|
+
var UPDATE_DASHBOARD = `
|
|
1269
|
+
mutation UpdateDashboard($input: BigConsoleUpdateDashboardInput!) {
|
|
1270
|
+
updateBigConsoleDashboard(input: $input) {
|
|
1271
|
+
id
|
|
1272
|
+
name
|
|
1273
|
+
status
|
|
1274
|
+
visibility
|
|
1275
|
+
category
|
|
1276
|
+
updatedAt
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
`;
|
|
1280
|
+
var IMPORT_DASHBOARD = `
|
|
1281
|
+
mutation ImportDashboard($input: BigConsoleImportDashboardInput!) {
|
|
1282
|
+
importDashboard(input: $input) {
|
|
1283
|
+
id
|
|
1284
|
+
name
|
|
1285
|
+
status
|
|
1286
|
+
visibility
|
|
1287
|
+
category
|
|
1288
|
+
createdAt
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
`;
|
|
1292
|
+
var EXPORT_DASHBOARD = `
|
|
1293
|
+
query ExportDashboard($id: ID!, $options: BigConsoleExportDashboardOptions) {
|
|
1294
|
+
exportDashboard(id: $id, options: $options)
|
|
1295
|
+
}
|
|
1296
|
+
`;
|
|
1297
|
+
var CREATE_WIDGET = `
|
|
1298
|
+
mutation CreateWidget($input: BigConsoleCreateWidgetInput!) {
|
|
1299
|
+
createBigConsoleWidget(input: $input) {
|
|
1300
|
+
id
|
|
1301
|
+
pageId
|
|
1302
|
+
type
|
|
1303
|
+
title
|
|
1304
|
+
createdAt
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
`;
|
|
1308
|
+
var UPDATE_WIDGET = `
|
|
1309
|
+
mutation UpdateWidget($input: BigConsoleUpdateWidgetInput!) {
|
|
1310
|
+
updateBigConsoleWidget(input: $input) {
|
|
1311
|
+
id
|
|
1312
|
+
pageId
|
|
1313
|
+
type
|
|
1314
|
+
title
|
|
1315
|
+
updatedAt
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
`;
|
|
1319
|
+
var CREATE_DATASINK = `
|
|
1320
|
+
mutation CreateDataSink($input: CreateDataSinkInput!) {
|
|
1321
|
+
createDataSink(input: $input) {
|
|
1322
|
+
id
|
|
1323
|
+
name
|
|
1324
|
+
key
|
|
1325
|
+
status
|
|
1326
|
+
setupState
|
|
1327
|
+
isActive
|
|
1328
|
+
createdAt
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
`;
|
|
1332
|
+
var UPDATE_DATASINK = `
|
|
1333
|
+
mutation UpdateDataSink($input: UpdateDataSinkInput!) {
|
|
1334
|
+
updateDataSink(input: $input) {
|
|
1335
|
+
id
|
|
1336
|
+
name
|
|
1337
|
+
key
|
|
1338
|
+
status
|
|
1339
|
+
isActive
|
|
1340
|
+
updatedAt
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
`;
|
|
1344
|
+
var CREATE_PARSER = `
|
|
1345
|
+
mutation CreateParser($input: CreateParserInput!) {
|
|
1346
|
+
createParser(input: $input) {
|
|
1347
|
+
id
|
|
1348
|
+
name
|
|
1349
|
+
type
|
|
1350
|
+
status
|
|
1351
|
+
inputSinkKey
|
|
1352
|
+
outputKey
|
|
1353
|
+
version
|
|
1354
|
+
createdAt
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
`;
|
|
1358
|
+
var UPDATE_PARSER = `
|
|
1359
|
+
mutation UpdateParser($id: ID!, $version: Int!, $input: UpdateParserInput!) {
|
|
1360
|
+
updateParser(id: $id, version: $version, input: $input) {
|
|
1361
|
+
id
|
|
1362
|
+
name
|
|
1363
|
+
type
|
|
1364
|
+
status
|
|
1365
|
+
version
|
|
1366
|
+
updatedAt
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
`;
|
|
1370
|
+
var CREATE_PIPELINE = `
|
|
1371
|
+
mutation CreatePipeline($input: CreatePipelineInput!) {
|
|
1372
|
+
createPipeline(input: $input) {
|
|
1373
|
+
id
|
|
1374
|
+
name
|
|
1375
|
+
status
|
|
1376
|
+
version
|
|
1377
|
+
createdAt
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
`;
|
|
1381
|
+
var UPDATE_PIPELINE = `
|
|
1382
|
+
mutation UpdatePipeline($id: ID!, $version: Int!, $input: UpdatePipelineInput!) {
|
|
1383
|
+
updatePipeline(id: $id, version: $version, input: $input) {
|
|
1384
|
+
id
|
|
1385
|
+
name
|
|
1386
|
+
status
|
|
1387
|
+
version
|
|
1388
|
+
updatedAt
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
`;
|
|
1392
|
+
var GET_WIDGET_ACTIONS = `
|
|
1393
|
+
query GetWidgetActions($widgetId: ID!) {
|
|
1394
|
+
getWidgetActions(widgetId: $widgetId) {
|
|
1395
|
+
id
|
|
1396
|
+
widgetId
|
|
1397
|
+
type
|
|
1398
|
+
name
|
|
1399
|
+
label
|
|
1400
|
+
order
|
|
1401
|
+
disabled
|
|
1402
|
+
hidden
|
|
1403
|
+
createdAt
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
`;
|
|
1407
|
+
var DELETE_WIDGET_DRILLDOWN = `
|
|
1408
|
+
mutation DeleteWidgetDrilldown($id: ID!) {
|
|
1409
|
+
deleteWidgetDrilldown(id: $id)
|
|
1410
|
+
}
|
|
1411
|
+
`;
|
|
1412
|
+
var GET_WIDGET_DRILLDOWN = `
|
|
1413
|
+
query GetWidgetDrilldown($id: ID!) {
|
|
1414
|
+
getWidget(id: $id) {
|
|
1415
|
+
id
|
|
1416
|
+
drilldown {
|
|
1417
|
+
id
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
`;
|
|
1422
|
+
var LIST_DASHBOARD_SHARES = `
|
|
1423
|
+
query ListDashboardShares(
|
|
1424
|
+
$dashboardId: ID!
|
|
1425
|
+
$first: Int
|
|
1426
|
+
$after: String
|
|
1427
|
+
$isActive: Boolean
|
|
1428
|
+
$permission: BigConsoleDashboardSharePermission
|
|
1429
|
+
$recipientType: BigConsoleDashboardShareRecipientType
|
|
1430
|
+
) {
|
|
1431
|
+
listDashboardShares(
|
|
1432
|
+
dashboardId: $dashboardId
|
|
1433
|
+
first: $first
|
|
1434
|
+
after: $after
|
|
1435
|
+
isActive: $isActive
|
|
1436
|
+
permission: $permission
|
|
1437
|
+
recipientType: $recipientType
|
|
1438
|
+
) {
|
|
1439
|
+
edges {
|
|
1440
|
+
node {
|
|
1441
|
+
id
|
|
1442
|
+
dashboardId
|
|
1443
|
+
permission
|
|
1444
|
+
isActive
|
|
1445
|
+
expiresAt
|
|
1446
|
+
createdAt
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
pageInfo { hasNextPage endCursor }
|
|
1450
|
+
totalCount
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
`;
|
|
1454
|
+
var LIST_TEMPLATES = `
|
|
1455
|
+
query ListTemplates(
|
|
1456
|
+
$first: Int
|
|
1457
|
+
$after: String
|
|
1458
|
+
$category: BigConsoleDashboardCategory
|
|
1459
|
+
$search: String
|
|
1460
|
+
$isFeatured: Boolean
|
|
1461
|
+
) {
|
|
1462
|
+
listTemplates(
|
|
1463
|
+
first: $first
|
|
1464
|
+
after: $after
|
|
1465
|
+
category: $category
|
|
1466
|
+
search: $search
|
|
1467
|
+
isFeatured: $isFeatured
|
|
1468
|
+
) {
|
|
1469
|
+
edges {
|
|
1470
|
+
node {
|
|
1471
|
+
id
|
|
1472
|
+
name
|
|
1473
|
+
description
|
|
1474
|
+
category
|
|
1475
|
+
isFeatured
|
|
1476
|
+
status
|
|
1477
|
+
createdAt
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
pageInfo { hasNextPage endCursor }
|
|
1481
|
+
totalCount
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
`;
|
|
1485
|
+
var GET_TEMPLATE = `
|
|
1486
|
+
query GetTemplate($id: ID!) {
|
|
1487
|
+
getTemplate(id: $id) {
|
|
1488
|
+
id
|
|
1489
|
+
name
|
|
1490
|
+
description
|
|
1491
|
+
category
|
|
1492
|
+
isFeatured
|
|
1493
|
+
status
|
|
1494
|
+
createdAt
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
`;
|
|
1498
|
+
var GENERATE_DASHBOARD_FROM_PROMPT = `
|
|
1499
|
+
mutation GenerateDashboardFromPrompt($input: BigConsoleGenerateDashboardFromPromptInput!) {
|
|
1500
|
+
generateDashboardFromPrompt(input: $input) {
|
|
1501
|
+
id
|
|
1502
|
+
status
|
|
1503
|
+
prompt
|
|
1504
|
+
createdAt
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
`;
|
|
1508
|
+
var LIST_PROMPT_GENERATIONS = `
|
|
1509
|
+
query ListPromptGenerations($first: Int, $after: String, $status: PromptGenerationStatus) {
|
|
1510
|
+
listPromptGenerations(first: $first, after: $after, status: $status) {
|
|
1511
|
+
edges {
|
|
1512
|
+
node {
|
|
1513
|
+
id
|
|
1514
|
+
status
|
|
1515
|
+
prompt
|
|
1516
|
+
createdAt
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
pageInfo { hasNextPage endCursor }
|
|
1520
|
+
totalCount
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
`;
|
|
1524
|
+
var GET_PROMPT_GENERATION = `
|
|
1525
|
+
query GetPromptGeneration($id: ID!) {
|
|
1526
|
+
getPromptGeneration(id: $id) {
|
|
1527
|
+
id
|
|
1528
|
+
status
|
|
1529
|
+
prompt
|
|
1530
|
+
createdAt
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
`;
|
|
1534
|
+
var CANCEL_PROMPT_GENERATION = `
|
|
1535
|
+
mutation CancelPromptGeneration($generationId: ID!) {
|
|
1536
|
+
cancelPromptGeneration(generationId: $generationId) {
|
|
1537
|
+
id
|
|
1538
|
+
status
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
`;
|
|
1542
|
+
var LIST_WORKFLOWS = `
|
|
1543
|
+
query ListWorkflows($filter: FluidGridsWorkflowFilterInput) {
|
|
1544
|
+
workflows(filter: $filter) {
|
|
1545
|
+
id
|
|
1546
|
+
name
|
|
1547
|
+
key
|
|
1548
|
+
description
|
|
1549
|
+
status
|
|
1550
|
+
executionCount
|
|
1551
|
+
linkedDataSinkCount
|
|
1552
|
+
lastExecutedAt
|
|
1553
|
+
createdAt
|
|
1554
|
+
updatedAt
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
`;
|
|
1558
|
+
var GET_WORKFLOW = `
|
|
1559
|
+
query GetWorkflow($id: ID!) {
|
|
1560
|
+
workflow(id: $id) {
|
|
1561
|
+
id
|
|
1562
|
+
name
|
|
1563
|
+
key
|
|
1564
|
+
description
|
|
1565
|
+
status
|
|
1566
|
+
executionCount
|
|
1567
|
+
linkedDataSinkCount
|
|
1568
|
+
externalWorkflowId
|
|
1569
|
+
lastExecutionId
|
|
1570
|
+
lastExecutedAt
|
|
1571
|
+
webhookUrl
|
|
1572
|
+
createdAt
|
|
1573
|
+
updatedAt
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
`;
|
|
1577
|
+
|
|
1578
|
+
// src/commands/base.ts
|
|
1579
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
1580
|
+
function loadJsonInput(file, inline) {
|
|
1581
|
+
if (!file && !inline)
|
|
1582
|
+
return;
|
|
1583
|
+
if (file && inline) {
|
|
1584
|
+
printError("Provide --file OR --json, not both");
|
|
1585
|
+
process.exit(1);
|
|
1586
|
+
}
|
|
1587
|
+
try {
|
|
1588
|
+
const raw = file ? readFileSync3(file, "utf8") : inline;
|
|
1589
|
+
return JSON.parse(raw);
|
|
1590
|
+
} catch (err) {
|
|
1591
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1592
|
+
printError(`Failed to parse JSON input: ${msg}`);
|
|
1593
|
+
process.exit(1);
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
function loadTextInput(file, inline) {
|
|
1597
|
+
if (!file && !inline)
|
|
1598
|
+
return;
|
|
1599
|
+
if (file && inline) {
|
|
1600
|
+
printError("Provide --file OR inline value, not both");
|
|
1601
|
+
process.exit(1);
|
|
1602
|
+
}
|
|
1603
|
+
try {
|
|
1604
|
+
return file ? readFileSync3(file, "utf8") : inline;
|
|
1605
|
+
} catch (err) {
|
|
1606
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1607
|
+
printError(`Failed to read file: ${msg}`);
|
|
1608
|
+
process.exit(1);
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
function requireAuth() {
|
|
1612
|
+
const auth = readAuth();
|
|
1613
|
+
if (!auth?.accessToken) {
|
|
1614
|
+
printError("Authentication required. Run: bigconsole auth login");
|
|
1615
|
+
process.exit(1);
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
async function execWorkspace(document, variables) {
|
|
1619
|
+
requireAuth();
|
|
1620
|
+
return workspaceRequest(document, variables);
|
|
1621
|
+
}
|
|
1622
|
+
function handleCommandError(error) {
|
|
1623
|
+
printError(toSafeErrorMessage(error));
|
|
1624
|
+
process.exit(1);
|
|
1625
|
+
}
|
|
1626
|
+
function inheritGlobalFormat(actionCommand) {
|
|
1627
|
+
const declaresFormat = actionCommand.options.some((o) => o.attributeName() === "format");
|
|
1628
|
+
if (!declaresFormat)
|
|
1629
|
+
return;
|
|
1630
|
+
const current = actionCommand.getOptionValue("format");
|
|
1631
|
+
if (current !== undefined)
|
|
1632
|
+
return;
|
|
1633
|
+
const { format } = actionCommand.optsWithGlobals();
|
|
1634
|
+
if (format !== undefined)
|
|
1635
|
+
actionCommand.setOptionValue("format", format);
|
|
1636
|
+
}
|
|
1637
|
+
function formatAndOutput(options, data, tableConfig) {
|
|
1638
|
+
const format = getFormat(options);
|
|
1639
|
+
outputData(format, data, tableConfig);
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
// src/commands/auth.ts
|
|
1643
|
+
function registerAuthCommands(program) {
|
|
1644
|
+
const auth = program.command("auth").description("Authentication commands");
|
|
1645
|
+
auth.command("login").description("Authenticate with BigConsole").option("--client-credentials", "Authenticate as an app with client ID and secret").option("--workspace-id <id>", "Workspace ID (for app auth)").action(async (options) => {
|
|
1646
|
+
try {
|
|
1647
|
+
if (options.clientCredentials) {
|
|
1648
|
+
await loginWithClientCredentials(options.workspaceId);
|
|
1649
|
+
} else {
|
|
1650
|
+
await loginWithDeviceCode();
|
|
1651
|
+
}
|
|
1652
|
+
} catch (error) {
|
|
1653
|
+
handleCommandError(error);
|
|
1654
|
+
}
|
|
1655
|
+
});
|
|
1656
|
+
auth.command("logout").description("Revoke tokens and clear stored auth").action(async () => {
|
|
1657
|
+
try {
|
|
1658
|
+
const authData = readAuth();
|
|
1659
|
+
if (authData?.accessToken) {
|
|
1660
|
+
try {
|
|
1661
|
+
await globalRequest(SIGN_OUT);
|
|
1662
|
+
} catch {
|
|
1663
|
+
printWarning("Server-side logout failed, clearing local tokens");
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
clearAuth();
|
|
1667
|
+
resetClients();
|
|
1668
|
+
printSuccess("Logged out successfully");
|
|
1669
|
+
} catch (error) {
|
|
1670
|
+
handleCommandError(error);
|
|
1671
|
+
}
|
|
1672
|
+
});
|
|
1673
|
+
auth.command("status").description("Show current auth state").option("-f, --format <format>", "Output format (json, table)").action((options) => {
|
|
1674
|
+
const authData = readAuth();
|
|
1675
|
+
const config = readConfig();
|
|
1676
|
+
const authenticated = Boolean(authData?.accessToken);
|
|
1677
|
+
const expired = authData?.accessToken ? isTokenExpired(authData) : false;
|
|
1678
|
+
const expiresAt = authData?.expiresAt ? formatDate(new Date(authData.expiresAt).toISOString()) : "-";
|
|
1679
|
+
const method = authData?.authMethod === "client_credentials" ? "Client Credentials" : "OAuth2 Device Code";
|
|
1680
|
+
const wsTokenExpires = authData?.workspaceTokenExpiresAt ? formatDate(new Date(authData.workspaceTokenExpiresAt).toISOString()) : "-";
|
|
1681
|
+
formatAndOutput(options, {
|
|
1682
|
+
authenticated,
|
|
1683
|
+
method,
|
|
1684
|
+
expired,
|
|
1685
|
+
expiresAt,
|
|
1686
|
+
workspaceId: authData?.workspaceId ?? "-",
|
|
1687
|
+
organizationId: authData?.organizationId ?? "-",
|
|
1688
|
+
workspaceTokenExpiresAt: wsTokenExpires,
|
|
1689
|
+
workspaceEndpoint: config.workspaceEndpoint,
|
|
1690
|
+
globalEndpoint: config.globalEndpoint
|
|
1691
|
+
}, {
|
|
1692
|
+
headers: ["Key", "Value"],
|
|
1693
|
+
rows: [
|
|
1694
|
+
["Authenticated", String(authenticated)],
|
|
1695
|
+
["Method", method],
|
|
1696
|
+
["Expired", String(expired)],
|
|
1697
|
+
["Expires At", expiresAt],
|
|
1698
|
+
["Workspace ID", authData?.workspaceId ?? "-"],
|
|
1699
|
+
["Organization ID", authData?.organizationId ?? "-"],
|
|
1700
|
+
["Workspace Token Expires", wsTokenExpires],
|
|
1701
|
+
["Workspace Endpoint", config.workspaceEndpoint],
|
|
1702
|
+
["Global Endpoint", config.globalEndpoint]
|
|
1703
|
+
]
|
|
1704
|
+
});
|
|
1705
|
+
});
|
|
1706
|
+
auth.command("token").description("Print access token to stdout").option("--workspace", "Print workspace token instead").action((options) => {
|
|
1707
|
+
const authData = readAuth();
|
|
1708
|
+
if (options.workspace) {
|
|
1709
|
+
if (!authData?.workspaceToken) {
|
|
1710
|
+
printError("No workspace token. Log in and select a workspace: bigconsole auth login");
|
|
1711
|
+
process.exit(1);
|
|
1712
|
+
}
|
|
1713
|
+
process.stdout.write(authData.workspaceToken);
|
|
1714
|
+
return;
|
|
1715
|
+
}
|
|
1716
|
+
if (!authData?.accessToken) {
|
|
1717
|
+
printError("Not authenticated. Run: bigconsole auth login");
|
|
1718
|
+
process.exit(1);
|
|
1719
|
+
}
|
|
1720
|
+
process.stdout.write(authData.accessToken);
|
|
1721
|
+
});
|
|
1722
|
+
auth.command("refresh").description("Force token refresh").action(async () => {
|
|
1723
|
+
try {
|
|
1724
|
+
const authData = readAuth();
|
|
1725
|
+
if (!authData?.refreshToken && authData?.authMethod !== "client_credentials") {
|
|
1726
|
+
printError("No refresh token available. Run: bigconsole auth login");
|
|
1727
|
+
process.exit(1);
|
|
1728
|
+
}
|
|
1729
|
+
if (authData?.authMethod === "client_credentials" && authData.clientId && authData.clientSecret) {
|
|
1730
|
+
const config = readConfig();
|
|
1731
|
+
const result = await clientCredentialsFlow(config.globalEndpoint, authData.clientId, authData.clientSecret);
|
|
1732
|
+
writeAuth({
|
|
1733
|
+
...authData,
|
|
1734
|
+
accessToken: result.accessToken,
|
|
1735
|
+
expiresAt: Date.now() + result.expiresIn * 1000
|
|
1736
|
+
});
|
|
1737
|
+
resetClients();
|
|
1738
|
+
printSuccess("App token refreshed successfully");
|
|
1739
|
+
if (authData.workspaceId && authData.organizationId) {
|
|
1740
|
+
await refreshWorkspaceTokenForAuth();
|
|
1741
|
+
}
|
|
1742
|
+
return;
|
|
1743
|
+
}
|
|
1744
|
+
const data = await globalRequest(REFRESH_TOKEN, {
|
|
1745
|
+
token: authData.refreshToken
|
|
1746
|
+
});
|
|
1747
|
+
const r = data.refreshToken;
|
|
1748
|
+
writeAuth({
|
|
1749
|
+
...authData,
|
|
1750
|
+
accessToken: r.accessToken,
|
|
1751
|
+
refreshToken: r.refreshToken,
|
|
1752
|
+
expiresAt: Date.now() + r.expiresIn * 1000
|
|
1753
|
+
});
|
|
1754
|
+
resetClients();
|
|
1755
|
+
printSuccess("Token refreshed successfully");
|
|
1756
|
+
if (authData.workspaceId && authData.organizationId) {
|
|
1757
|
+
await refreshWorkspaceTokenForAuth();
|
|
1758
|
+
}
|
|
1759
|
+
} catch (error) {
|
|
1760
|
+
handleCommandError(error);
|
|
1761
|
+
}
|
|
1762
|
+
});
|
|
1763
|
+
}
|
|
1764
|
+
async function refreshWorkspaceTokenForAuth() {
|
|
1765
|
+
const authData = readAuth();
|
|
1766
|
+
if (!authData?.accessToken || !authData.workspaceId || !authData.organizationId)
|
|
1767
|
+
return;
|
|
1768
|
+
const config = readConfig();
|
|
1769
|
+
try {
|
|
1770
|
+
const wsResult = await issueWorkspaceToken(config.workspaceEndpoint, authData.accessToken, authData.workspaceId, authData.organizationId);
|
|
1771
|
+
writeAuth({
|
|
1772
|
+
...authData,
|
|
1773
|
+
workspaceToken: wsResult.token,
|
|
1774
|
+
workspaceTokenExpiresAt: new Date(wsResult.expiresAt).getTime()
|
|
1775
|
+
});
|
|
1776
|
+
resetClients();
|
|
1777
|
+
printSuccess("Workspace token refreshed");
|
|
1778
|
+
} catch {
|
|
1779
|
+
printWarning("Workspace token refresh failed — workspace operations may require re-login");
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
async function loginWithDeviceCode() {
|
|
1783
|
+
const config = readConfig();
|
|
1784
|
+
const { oidcIssuer, clientId } = resolveOIDCConfig();
|
|
1785
|
+
const oidcConfig = { ...config, oidcIssuer, clientId };
|
|
1786
|
+
printInfo("Starting device code authentication...");
|
|
1787
|
+
const dc = await startDeviceCodeFlow(oidcConfig);
|
|
1788
|
+
console.log("");
|
|
1789
|
+
console.log(` Open: ${sanitizeForTerminal(dc.verificationUri)}`);
|
|
1790
|
+
console.log(` Code: ${sanitizeForTerminal(dc.userCode)}`);
|
|
1791
|
+
console.log("");
|
|
1792
|
+
const opened = await openBrowser(dc.verificationUriComplete ?? dc.verificationUri);
|
|
1793
|
+
if (opened)
|
|
1794
|
+
printInfo("Browser opened");
|
|
1795
|
+
else
|
|
1796
|
+
printWarning("Could not open browser — open the URL manually");
|
|
1797
|
+
printInfo("Waiting for authorization...");
|
|
1798
|
+
const result = await pollForToken(dc.deviceCode, dc.interval, oidcConfig);
|
|
1799
|
+
const accessToken = result.accessToken;
|
|
1800
|
+
const expiresAt = Date.now() + result.expiresIn * 1000;
|
|
1801
|
+
printInfo("Fetching workspaces...");
|
|
1802
|
+
try {
|
|
1803
|
+
const workspaces = await fetchWorkspaces(config.globalEndpoint, accessToken);
|
|
1804
|
+
if (workspaces.length === 0) {
|
|
1805
|
+
writeAuth({
|
|
1806
|
+
accessToken,
|
|
1807
|
+
refreshToken: result.refreshToken,
|
|
1808
|
+
expiresAt,
|
|
1809
|
+
authMethod: "device_code"
|
|
1810
|
+
});
|
|
1811
|
+
resetClients();
|
|
1812
|
+
printSuccess("Authentication successful (no workspaces found)");
|
|
1813
|
+
return;
|
|
1814
|
+
}
|
|
1815
|
+
let selected = workspaces[0];
|
|
1816
|
+
if (workspaces.length > 1) {
|
|
1817
|
+
console.log(`
|
|
1818
|
+
Available workspaces:`);
|
|
1819
|
+
workspaces.forEach((ws, i) => console.log(` ${i + 1}. ${sanitizeForTerminal(ws.name)} (${sanitizeForTerminal(ws.slug)})`));
|
|
1820
|
+
const readline = await import("readline");
|
|
1821
|
+
for (let attempt = 0;attempt < 3; attempt++) {
|
|
1822
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1823
|
+
const answer = await new Promise((resolve) => {
|
|
1824
|
+
rl.question(`
|
|
1825
|
+
Select workspace [1-${workspaces.length}]: `, (a) => {
|
|
1826
|
+
rl.close();
|
|
1827
|
+
resolve(a.trim());
|
|
1828
|
+
});
|
|
1829
|
+
});
|
|
1830
|
+
const idx = parseInt(answer, 10) - 1;
|
|
1831
|
+
if (!Number.isNaN(idx) && idx >= 0 && idx < workspaces.length) {
|
|
1832
|
+
selected = workspaces[idx];
|
|
1833
|
+
break;
|
|
1834
|
+
}
|
|
1835
|
+
if (attempt < 2) {
|
|
1836
|
+
printError(`Invalid selection. Enter a number between 1 and ${workspaces.length}.`);
|
|
1837
|
+
} else {
|
|
1838
|
+
printError("Too many invalid attempts — aborting login.");
|
|
1839
|
+
process.exit(1);
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
printInfo(`Using workspace: ${selected.name} (${selected.id})`);
|
|
1844
|
+
const wsResult = await issueWorkspaceToken(config.workspaceEndpoint, accessToken, selected.id, selected.organizationId);
|
|
1845
|
+
writeAuth({
|
|
1846
|
+
accessToken,
|
|
1847
|
+
refreshToken: result.refreshToken,
|
|
1848
|
+
expiresAt,
|
|
1849
|
+
workspaceToken: wsResult.token,
|
|
1850
|
+
workspaceTokenExpiresAt: new Date(wsResult.expiresAt).getTime(),
|
|
1851
|
+
workspaceId: selected.id,
|
|
1852
|
+
organizationId: selected.organizationId,
|
|
1853
|
+
authMethod: "device_code"
|
|
1854
|
+
});
|
|
1855
|
+
resetClients();
|
|
1856
|
+
printSuccess("Authentication successful");
|
|
1857
|
+
} catch (wsError) {
|
|
1858
|
+
writeAuth({
|
|
1859
|
+
accessToken,
|
|
1860
|
+
refreshToken: result.refreshToken,
|
|
1861
|
+
expiresAt,
|
|
1862
|
+
authMethod: "device_code"
|
|
1863
|
+
});
|
|
1864
|
+
resetClients();
|
|
1865
|
+
printSuccess("Authentication successful");
|
|
1866
|
+
printWarning(`Workspace token not obtained: ${toSafeErrorMessage(wsError)}`);
|
|
1867
|
+
printInfo("Run `bigconsole auth login` again once a workspace is available.");
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
async function loginWithClientCredentials(workspaceId) {
|
|
1871
|
+
const config = readConfig();
|
|
1872
|
+
const readline = await import("readline");
|
|
1873
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1874
|
+
const clientId = await new Promise((resolve) => {
|
|
1875
|
+
rl.question("Client ID: ", (a) => resolve(a.trim()));
|
|
1876
|
+
});
|
|
1877
|
+
const out = rl;
|
|
1878
|
+
out._writeToOutput = function maskedWrite(stringToWrite) {
|
|
1879
|
+
if (stringToWrite === `\r
|
|
1880
|
+
` || stringToWrite === `
|
|
1881
|
+
`) {
|
|
1882
|
+
out.output.write(stringToWrite);
|
|
1883
|
+
return;
|
|
1884
|
+
}
|
|
1885
|
+
out.output.write("*");
|
|
1886
|
+
};
|
|
1887
|
+
const clientSecret = await new Promise((resolve) => {
|
|
1888
|
+
rl.question("Client Secret: ", (a) => {
|
|
1889
|
+
rl.close();
|
|
1890
|
+
resolve(a.trim());
|
|
1891
|
+
});
|
|
1892
|
+
});
|
|
1893
|
+
process.stdout.write(`
|
|
1894
|
+
`);
|
|
1895
|
+
if (!clientId || !clientSecret) {
|
|
1896
|
+
printError("Client ID and secret cannot be empty");
|
|
1897
|
+
process.exit(1);
|
|
1898
|
+
}
|
|
1899
|
+
printInfo("Authenticating app...");
|
|
1900
|
+
const result = await clientCredentialsFlow(config.globalEndpoint, clientId, clientSecret);
|
|
1901
|
+
const accessToken = result.accessToken;
|
|
1902
|
+
const expiresAt = Date.now() + result.expiresIn * 1000;
|
|
1903
|
+
let wsToken;
|
|
1904
|
+
let wsTokenExpiresAt;
|
|
1905
|
+
const wsId = workspaceId;
|
|
1906
|
+
let orgId;
|
|
1907
|
+
if (wsId) {
|
|
1908
|
+
try {
|
|
1909
|
+
const workspaces = await fetchWorkspaces(config.globalEndpoint, accessToken);
|
|
1910
|
+
const ws = workspaces.find((w) => w.id === wsId);
|
|
1911
|
+
if (!ws) {
|
|
1912
|
+
printError(`Workspace ${wsId} not found or not accessible to this app.`);
|
|
1913
|
+
process.exit(1);
|
|
1914
|
+
}
|
|
1915
|
+
orgId = ws.organizationId;
|
|
1916
|
+
const wsResult = await issueWorkspaceToken(config.workspaceEndpoint, accessToken, wsId, orgId);
|
|
1917
|
+
wsToken = wsResult.token;
|
|
1918
|
+
wsTokenExpiresAt = new Date(wsResult.expiresAt).getTime();
|
|
1919
|
+
printInfo(`Workspace token issued for: ${ws.name}`);
|
|
1920
|
+
} catch (err) {
|
|
1921
|
+
printError(`Workspace token issuance failed: ${toSafeErrorMessage(err)}`);
|
|
1922
|
+
process.exit(1);
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
writeAuth({
|
|
1926
|
+
accessToken,
|
|
1927
|
+
refreshToken: "",
|
|
1928
|
+
expiresAt,
|
|
1929
|
+
workspaceToken: wsToken,
|
|
1930
|
+
workspaceTokenExpiresAt: wsTokenExpiresAt,
|
|
1931
|
+
workspaceId: wsId,
|
|
1932
|
+
organizationId: orgId,
|
|
1933
|
+
authMethod: "client_credentials",
|
|
1934
|
+
clientId,
|
|
1935
|
+
clientSecret
|
|
1936
|
+
});
|
|
1937
|
+
resetClients();
|
|
1938
|
+
printSuccess("App authentication successful");
|
|
1939
|
+
}
|
|
1940
|
+
// src/commands/cli-config.ts
|
|
1941
|
+
function registerCLIConfigCommands(program) {
|
|
1942
|
+
const cli = program.command("cli-config").description("CLI configuration (endpoints, client ID)");
|
|
1943
|
+
cli.command("show").description("Show current CLI configuration").option("-f, --format <format>", "Output format (json, table, yaml)").action((options) => {
|
|
1944
|
+
const config = readConfig();
|
|
1945
|
+
formatAndOutput(options, config, {
|
|
1946
|
+
headers: ["Key", "Value"],
|
|
1947
|
+
rows: Object.entries(config).map(([k, v]) => [k, v == null ? "-" : String(v)])
|
|
1948
|
+
});
|
|
1949
|
+
});
|
|
1950
|
+
cli.command("path").description("Show CLI config file path").action(() => {
|
|
1951
|
+
console.log(getConfigPath());
|
|
1952
|
+
});
|
|
1953
|
+
cli.command("get <key>").description("Get a single config value").action((key) => {
|
|
1954
|
+
try {
|
|
1955
|
+
if (!KNOWN_CONFIG_KEYS.includes(key)) {
|
|
1956
|
+
printError(`Unknown config key: ${key}. Known keys: ${KNOWN_CONFIG_KEYS.join(", ")}`);
|
|
1957
|
+
process.exit(1);
|
|
1958
|
+
}
|
|
1959
|
+
const config = readConfig();
|
|
1960
|
+
const value = config[key];
|
|
1961
|
+
if (value === undefined || value === null) {
|
|
1962
|
+
console.log("-");
|
|
1963
|
+
} else {
|
|
1964
|
+
console.log(String(value));
|
|
1965
|
+
}
|
|
1966
|
+
} catch (error) {
|
|
1967
|
+
handleCommandError(error);
|
|
1968
|
+
}
|
|
1969
|
+
});
|
|
1970
|
+
cli.command("set <key> <value>").description("Set a config value").action((key, value) => {
|
|
1971
|
+
try {
|
|
1972
|
+
if (!KNOWN_CONFIG_KEYS.includes(key)) {
|
|
1973
|
+
printError(`Unknown config key: ${key}. Known keys: ${KNOWN_CONFIG_KEYS.join(", ")}`);
|
|
1974
|
+
process.exit(1);
|
|
1975
|
+
}
|
|
1976
|
+
const config = readConfig();
|
|
1977
|
+
config[key] = value;
|
|
1978
|
+
writeConfig(config);
|
|
1979
|
+
printSuccess(`Set ${key} = ${value} (last updated ${formatDate(new Date().toISOString())})`);
|
|
1980
|
+
} catch (error) {
|
|
1981
|
+
handleCommandError(error);
|
|
1982
|
+
}
|
|
1983
|
+
});
|
|
1984
|
+
cli.command("reset").description("Reset to default CLI configuration").action(() => {
|
|
1985
|
+
try {
|
|
1986
|
+
writeConfig({ ...DEFAULT_CONFIG });
|
|
1987
|
+
printSuccess("CLI configuration reset to defaults");
|
|
1988
|
+
} catch (error) {
|
|
1989
|
+
handleCommandError(error);
|
|
1990
|
+
}
|
|
1991
|
+
});
|
|
1992
|
+
}
|
|
1993
|
+
// src/commands/workspace.ts
|
|
1994
|
+
function registerWorkspaceCommands(program) {
|
|
1995
|
+
const ws = program.command("workspace").description("Workspace selection and info");
|
|
1996
|
+
ws.command("list").description("List workspaces the current user can access").option("-f, --format <format>", "Output format (json, table, yaml)").action(async (o) => {
|
|
1997
|
+
try {
|
|
1998
|
+
requireAuth();
|
|
1999
|
+
await ensureFreshTokens();
|
|
2000
|
+
const auth = readAuth();
|
|
2001
|
+
const config = readConfig();
|
|
2002
|
+
const workspaces = await fetchWorkspaces(config.globalEndpoint, auth.accessToken);
|
|
2003
|
+
formatAndOutput(o, workspaces, {
|
|
2004
|
+
headers: ["ID", "Name", "Slug", "Org ID", "Tenant ID", "Current"],
|
|
2005
|
+
rows: workspaces.map((w) => [
|
|
2006
|
+
truncate(w.id, 14),
|
|
2007
|
+
w.name,
|
|
2008
|
+
w.slug,
|
|
2009
|
+
truncate(w.organizationId, 14),
|
|
2010
|
+
truncate(w.tenantId, 14),
|
|
2011
|
+
auth.workspaceId === w.id ? "*" : ""
|
|
2012
|
+
])
|
|
2013
|
+
});
|
|
2014
|
+
} catch (e) {
|
|
2015
|
+
handleCommandError(e);
|
|
2016
|
+
}
|
|
2017
|
+
});
|
|
2018
|
+
ws.command("current").description("Show the currently-selected workspace").option("-f, --format <format>", "Output format").action((o) => {
|
|
2019
|
+
const auth = readAuth();
|
|
2020
|
+
if (!auth?.workspaceId) {
|
|
2021
|
+
printError("No workspace selected. Run `bigconsole workspace set <id>` or re-login.");
|
|
2022
|
+
process.exit(1);
|
|
2023
|
+
}
|
|
2024
|
+
formatAndOutput(o, {
|
|
2025
|
+
workspaceId: auth.workspaceId,
|
|
2026
|
+
organizationId: auth.organizationId ?? "-",
|
|
2027
|
+
workspaceTokenExpiresAt: auth.workspaceTokenExpiresAt ? new Date(auth.workspaceTokenExpiresAt).toISOString() : "-"
|
|
2028
|
+
});
|
|
2029
|
+
});
|
|
2030
|
+
ws.command("set").description("Select a workspace and issue a workspace token").argument("<id>", "Workspace ID").action(async (id) => {
|
|
2031
|
+
try {
|
|
2032
|
+
requireAuth();
|
|
2033
|
+
await ensureFreshTokens();
|
|
2034
|
+
const auth = readAuth();
|
|
2035
|
+
const config = readConfig();
|
|
2036
|
+
const workspaces = await fetchWorkspaces(config.globalEndpoint, auth.accessToken);
|
|
2037
|
+
const ws = workspaces.find((w) => w.id === id);
|
|
2038
|
+
if (!ws) {
|
|
2039
|
+
printError(`Workspace ${id} not found or not accessible. Run \`bigconsole workspace list\`.`);
|
|
2040
|
+
process.exit(1);
|
|
2041
|
+
}
|
|
2042
|
+
const result = await issueWorkspaceToken(config.workspaceEndpoint, auth.accessToken, ws.id, ws.organizationId);
|
|
2043
|
+
writeAuth({
|
|
2044
|
+
...auth,
|
|
2045
|
+
workspaceId: ws.id,
|
|
2046
|
+
organizationId: ws.organizationId,
|
|
2047
|
+
workspaceToken: result.token,
|
|
2048
|
+
workspaceTokenExpiresAt: new Date(result.expiresAt).getTime()
|
|
2049
|
+
});
|
|
2050
|
+
resetClients();
|
|
2051
|
+
printSuccess(`Switched to workspace: ${ws.name} (${ws.id})`);
|
|
2052
|
+
} catch (e) {
|
|
2053
|
+
handleCommandError(e);
|
|
2054
|
+
}
|
|
2055
|
+
});
|
|
2056
|
+
}
|
|
2057
|
+
// src/commands/dashboard.ts
|
|
2058
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
2059
|
+
function registerDashboardCommands(program) {
|
|
2060
|
+
const dash = program.command("dashboard").description("Dashboard operations");
|
|
2061
|
+
dash.command("list").description("List dashboards in the current workspace").option("--search <term>", "Search by name").option("--category <c>", "Filter by category").option("--visibility <v>", "PRIVATE | WORKSPACE | PUBLIC").option("--include-archived", "Include archived dashboards").option("--first <n>", "Page size", (v) => parseInt(v, 10), 25).option("--after <cursor>", "Cursor for next page").option("-f, --format <format>", "Output format").action(async (o) => {
|
|
2062
|
+
try {
|
|
2063
|
+
const d = await execWorkspace(LIST_DASHBOARDS, {
|
|
2064
|
+
first: o.first ?? 25,
|
|
2065
|
+
after: o.after,
|
|
2066
|
+
search: o.search,
|
|
2067
|
+
category: o.category ? o.category.toUpperCase() : undefined,
|
|
2068
|
+
visibility: o.visibility ? o.visibility.toUpperCase() : undefined,
|
|
2069
|
+
includeArchived: o.includeArchived ?? false
|
|
2070
|
+
});
|
|
2071
|
+
const items = d.listDashboards.edges.map((e) => e.node);
|
|
2072
|
+
formatAndOutput(o, items, {
|
|
2073
|
+
headers: ["ID", "Name", "Status", "Visibility", "Category", "Updated"],
|
|
2074
|
+
rows: items.map((x) => [
|
|
2075
|
+
truncate(x.id, 14),
|
|
2076
|
+
truncate(x.name, 30),
|
|
2077
|
+
formatStatus(x.status),
|
|
2078
|
+
x.visibility,
|
|
2079
|
+
x.category,
|
|
2080
|
+
formatDate(x.updatedAt)
|
|
2081
|
+
])
|
|
2082
|
+
});
|
|
2083
|
+
} catch (e) {
|
|
2084
|
+
handleCommandError(e);
|
|
2085
|
+
}
|
|
2086
|
+
});
|
|
2087
|
+
dash.command("get").description("Get a dashboard by ID").argument("<id>").option("-f, --format <format>", "Output format").action(async (id, o) => {
|
|
2088
|
+
try {
|
|
2089
|
+
const d = await execWorkspace(GET_DASHBOARD, {
|
|
2090
|
+
id
|
|
2091
|
+
});
|
|
2092
|
+
if (!d.getDashboard) {
|
|
2093
|
+
printError("Dashboard not found");
|
|
2094
|
+
process.exit(1);
|
|
2095
|
+
}
|
|
2096
|
+
formatAndOutput(o, d.getDashboard);
|
|
2097
|
+
} catch (e) {
|
|
2098
|
+
handleCommandError(e);
|
|
2099
|
+
}
|
|
2100
|
+
});
|
|
2101
|
+
dash.command("archive").description("Archive a dashboard (soft-delete, can be restored)").argument("<id>").action(async (id) => {
|
|
2102
|
+
try {
|
|
2103
|
+
const d = await execWorkspace(ARCHIVE_DASHBOARD, {
|
|
2104
|
+
id
|
|
2105
|
+
});
|
|
2106
|
+
printSuccess(`Archived: ${d.archiveDashboard.name} (${d.archiveDashboard.id})`);
|
|
2107
|
+
} catch (e) {
|
|
2108
|
+
handleCommandError(e);
|
|
2109
|
+
}
|
|
2110
|
+
});
|
|
2111
|
+
dash.command("delete").description("Permanently delete a dashboard").argument("<id>").action(async (id) => {
|
|
2112
|
+
try {
|
|
2113
|
+
await execWorkspace(DELETE_DASHBOARD, { id });
|
|
2114
|
+
printSuccess(`Deleted dashboard ${id}`);
|
|
2115
|
+
} catch (e) {
|
|
2116
|
+
handleCommandError(e);
|
|
2117
|
+
}
|
|
2118
|
+
});
|
|
2119
|
+
dash.command("create").description("Create a dashboard").requiredOption("--name <name>", "Dashboard name").requiredOption("--category <cat>", "Category (CUSTOM | EXECUTIVE | FINANCIAL | HR | MARKETING | OPERATIONS | SALES)").option("--description <d>", "Description").option("--visibility <v>", "PRIVATE | WORKSPACE | PUBLIC", "PRIVATE").option("--layout <l>", "GRID | FLEX | TABS", "GRID").option("--no-default-page", "Skip creating the default page").option("--metadata-file <path>", "JSON file with custom metadata").option("--metadata-json <json>", "Inline JSON for custom metadata").option("-f, --format <format>", "Output format").action(async (o) => {
|
|
2120
|
+
try {
|
|
2121
|
+
const metadata = loadJsonInput(o.metadataFile, o.metadataJson);
|
|
2122
|
+
const input = {
|
|
2123
|
+
name: o.name,
|
|
2124
|
+
category: o.category.toUpperCase(),
|
|
2125
|
+
description: o.description,
|
|
2126
|
+
visibility: (o.visibility ?? "PRIVATE").toUpperCase(),
|
|
2127
|
+
layout: (o.layout ?? "GRID").toUpperCase(),
|
|
2128
|
+
createDefaultPage: o.defaultPage !== false,
|
|
2129
|
+
metadata
|
|
2130
|
+
};
|
|
2131
|
+
const d = await execWorkspace(CREATE_DASHBOARD, { input });
|
|
2132
|
+
printSuccess(`Created dashboard: ${d.createBigConsoleDashboard.name} (${d.createBigConsoleDashboard.id})`);
|
|
2133
|
+
formatAndOutput(o, d.createBigConsoleDashboard);
|
|
2134
|
+
} catch (e) {
|
|
2135
|
+
handleCommandError(e);
|
|
2136
|
+
}
|
|
2137
|
+
});
|
|
2138
|
+
dash.command("update").description("Update a dashboard").argument("<id>").option("--name <name>").option("--description <d>").option("--category <cat>").option("--visibility <v>", "PRIVATE | WORKSPACE | PUBLIC").option("--layout <l>", "GRID | FLEX | TABS").option("-f, --format <format>", "Output format").action(async (id, o) => {
|
|
2139
|
+
try {
|
|
2140
|
+
const input = { id };
|
|
2141
|
+
if (o.name)
|
|
2142
|
+
input.name = o.name;
|
|
2143
|
+
if (o.description !== undefined)
|
|
2144
|
+
input.description = o.description;
|
|
2145
|
+
if (o.category)
|
|
2146
|
+
input.category = o.category.toUpperCase();
|
|
2147
|
+
if (o.visibility)
|
|
2148
|
+
input.visibility = o.visibility.toUpperCase();
|
|
2149
|
+
if (o.layout)
|
|
2150
|
+
input.layout = o.layout.toUpperCase();
|
|
2151
|
+
const d = await execWorkspace(UPDATE_DASHBOARD, { input });
|
|
2152
|
+
printSuccess(`Updated dashboard ${id}`);
|
|
2153
|
+
formatAndOutput(o, d.updateBigConsoleDashboard);
|
|
2154
|
+
} catch (e) {
|
|
2155
|
+
handleCommandError(e);
|
|
2156
|
+
}
|
|
2157
|
+
});
|
|
2158
|
+
dash.command("import").description("Import a dashboard from a JSON file (dashboard definition)").requiredOption("--file <path>", "Dashboard JSON file produced by `dashboard export`").option("--name <name>", "Override dashboard name").option("--visibility <v>", "Override visibility: PRIVATE | WORKSPACE | PUBLIC").option("--conflict <strategy>", "Conflict resolution: RENAME | REPLACE | FAIL", "RENAME").action(async (o) => {
|
|
2159
|
+
try {
|
|
2160
|
+
const json = readFileSync4(o.file, "utf8");
|
|
2161
|
+
const input = {
|
|
2162
|
+
json,
|
|
2163
|
+
conflictResolution: (o.conflict ?? "RENAME").toUpperCase()
|
|
2164
|
+
};
|
|
2165
|
+
if (o.name)
|
|
2166
|
+
input.name = o.name;
|
|
2167
|
+
if (o.visibility)
|
|
2168
|
+
input.visibility = o.visibility.toUpperCase();
|
|
2169
|
+
const d = await execWorkspace(IMPORT_DASHBOARD, {
|
|
2170
|
+
input
|
|
2171
|
+
});
|
|
2172
|
+
printSuccess(`Imported dashboard: ${d.importDashboard.name} (${d.importDashboard.id})`);
|
|
2173
|
+
} catch (e) {
|
|
2174
|
+
handleCommandError(e);
|
|
2175
|
+
}
|
|
2176
|
+
});
|
|
2177
|
+
dash.command("export").description("Export a dashboard definition as JSON (stdout or --out file)").argument("<id>").option("--out <path>", "Write JSON to file instead of stdout").option("--include-data-refs", "Include data source references", true).option("--include-webhook-mappings", "Include webhook mappings").option("--pretty", "Pretty-print JSON", true).action(async (id, o) => {
|
|
2178
|
+
try {
|
|
2179
|
+
const options = {
|
|
2180
|
+
includeDataSourceRefs: o.includeDataRefs !== false,
|
|
2181
|
+
includeWebhookMappings: o.includeWebhookMappings ?? false,
|
|
2182
|
+
prettyPrint: o.pretty !== false
|
|
2183
|
+
};
|
|
2184
|
+
const d = await execWorkspace(EXPORT_DASHBOARD, {
|
|
2185
|
+
id,
|
|
2186
|
+
options
|
|
2187
|
+
});
|
|
2188
|
+
if (o.out) {
|
|
2189
|
+
writeFileSync3(o.out, d.exportDashboard, "utf8");
|
|
2190
|
+
printSuccess(`Exported to ${o.out}`);
|
|
2191
|
+
} else {
|
|
2192
|
+
const body = process.stdout.isTTY ? sanitizeForTerminal(d.exportDashboard, { preserveNewlines: true }) : d.exportDashboard;
|
|
2193
|
+
process.stdout.write(body);
|
|
2194
|
+
if (!body.endsWith(`
|
|
2195
|
+
`))
|
|
2196
|
+
process.stdout.write(`
|
|
2197
|
+
`);
|
|
2198
|
+
}
|
|
2199
|
+
} catch (e) {
|
|
2200
|
+
handleCommandError(e);
|
|
2201
|
+
}
|
|
2202
|
+
});
|
|
2203
|
+
}
|
|
2204
|
+
// src/commands/widget.ts
|
|
2205
|
+
function registerWidgetCommands(program) {
|
|
2206
|
+
const w = program.command("widget").description("Widget operations");
|
|
2207
|
+
w.command("list").description("List widgets on a dashboard page").requiredOption("--page <pageId>", "Dashboard page ID").option("--type <t>", "Filter by widget type (METRIC_CARD, CHART, TABLE, ...)").option("--first <n>", "Page size", (v) => parseInt(v, 10), 50).option("--after <cursor>", "Cursor for next page").option("-f, --format <format>", "Output format").action(async (o) => {
|
|
2208
|
+
try {
|
|
2209
|
+
const d = await execWorkspace(LIST_WIDGETS, {
|
|
2210
|
+
pageId: o.page,
|
|
2211
|
+
first: o.first ?? 50,
|
|
2212
|
+
after: o.after,
|
|
2213
|
+
type: o.type ? o.type.toUpperCase() : undefined
|
|
2214
|
+
});
|
|
2215
|
+
const items = d.listWidgets.edges.map((e) => e.node);
|
|
2216
|
+
formatAndOutput(o, items, {
|
|
2217
|
+
headers: ["ID", "Title", "Type", "DataSink", "Last Refreshed", "Updated"],
|
|
2218
|
+
rows: items.map((x) => [
|
|
2219
|
+
truncate(x.id, 14),
|
|
2220
|
+
truncate(x.title ?? "-", 24),
|
|
2221
|
+
x.type,
|
|
2222
|
+
x.dataSinkId ? truncate(x.dataSinkId, 14) : "-",
|
|
2223
|
+
formatDate(x.lastRefreshedAt),
|
|
2224
|
+
formatDate(x.updatedAt)
|
|
2225
|
+
])
|
|
2226
|
+
});
|
|
2227
|
+
} catch (e) {
|
|
2228
|
+
handleCommandError(e);
|
|
2229
|
+
}
|
|
2230
|
+
});
|
|
2231
|
+
w.command("get").description("Get a widget by ID (includes full config JSON)").argument("<id>").option("-f, --format <format>", "Output format").action(async (id, o) => {
|
|
2232
|
+
try {
|
|
2233
|
+
const d = await execWorkspace(GET_WIDGET, { id });
|
|
2234
|
+
if (!d.getWidget) {
|
|
2235
|
+
printError("Widget not found");
|
|
2236
|
+
process.exit(1);
|
|
2237
|
+
}
|
|
2238
|
+
formatAndOutput(o, d.getWidget);
|
|
2239
|
+
} catch (e) {
|
|
2240
|
+
handleCommandError(e);
|
|
2241
|
+
}
|
|
2242
|
+
});
|
|
2243
|
+
w.command("delete").description("Delete a widget").argument("<id>").action(async (id) => {
|
|
2244
|
+
try {
|
|
2245
|
+
await execWorkspace(DELETE_WIDGET, { id });
|
|
2246
|
+
printSuccess(`Deleted widget ${id}`);
|
|
2247
|
+
} catch (e) {
|
|
2248
|
+
handleCommandError(e);
|
|
2249
|
+
}
|
|
2250
|
+
});
|
|
2251
|
+
w.command("create").description("Create a widget (config is JSON — use --config-file for complex layouts)").requiredOption("--page <id>", "Parent page ID").requiredOption("--type <t>", "Widget type (METRIC_CARD, CHART, TABLE, ...)").option("--title <t>", "Widget title").option("--config-file <path>", "JSON file with widget config").option("--config-json <json>", "Inline JSON config").option("--position-file <path>", "JSON file with position {x,y,width,height}").option("--position-json <json>", "Inline JSON position").option("--datasink <id>", "Bound DataSink ID").option("--parser <id>", "Bound Parser ID").option("--dataset <id>", "Dataset ID within DataSink").option("--cache-ttl <seconds>", "Cache TTL override", (v) => parseInt(v, 10)).option("-f, --format <format>", "Output format").action(async (o) => {
|
|
2252
|
+
try {
|
|
2253
|
+
const config = loadJsonInput(o.configFile, o.configJson);
|
|
2254
|
+
const position = loadJsonInput(o.positionFile, o.positionJson);
|
|
2255
|
+
if (!config) {
|
|
2256
|
+
printError("Widget config is required — provide --config-file or --config-json");
|
|
2257
|
+
process.exit(1);
|
|
2258
|
+
}
|
|
2259
|
+
if (!position) {
|
|
2260
|
+
printError("Position is required — provide --position-file or --position-json");
|
|
2261
|
+
process.exit(1);
|
|
2262
|
+
}
|
|
2263
|
+
const input = {
|
|
2264
|
+
pageId: o.page,
|
|
2265
|
+
type: o.type.toUpperCase(),
|
|
2266
|
+
title: o.title ?? "",
|
|
2267
|
+
config,
|
|
2268
|
+
position
|
|
2269
|
+
};
|
|
2270
|
+
if (o.datasink)
|
|
2271
|
+
input.dataSinkId = o.datasink;
|
|
2272
|
+
if (o.parser)
|
|
2273
|
+
input.parserId = o.parser;
|
|
2274
|
+
if (o.dataset)
|
|
2275
|
+
input.datasetId = o.dataset;
|
|
2276
|
+
if (o.cacheTtl !== undefined)
|
|
2277
|
+
input.cacheTTL = o.cacheTtl;
|
|
2278
|
+
const d = await execWorkspace(CREATE_WIDGET, {
|
|
2279
|
+
input
|
|
2280
|
+
});
|
|
2281
|
+
printSuccess(`Created widget ${d.createBigConsoleWidget.id}`);
|
|
2282
|
+
formatAndOutput(o, d.createBigConsoleWidget);
|
|
2283
|
+
} catch (e) {
|
|
2284
|
+
handleCommandError(e);
|
|
2285
|
+
}
|
|
2286
|
+
});
|
|
2287
|
+
w.command("update").description("Update a widget").argument("<id>").option("--config-file <path>", "Replace widget config from JSON file").option("--config-json <json>", "Replace widget config inline").option("--position-file <path>").option("--position-json <json>").option("--datasink <id>").option("--parser <id>").option("--dataset <id>").option("--cache-ttl <seconds>", "", (v) => parseInt(v, 10)).option("-f, --format <format>", "Output format").action(async (id, o) => {
|
|
2288
|
+
try {
|
|
2289
|
+
const config = loadJsonInput(o.configFile, o.configJson);
|
|
2290
|
+
const position = loadJsonInput(o.positionFile, o.positionJson);
|
|
2291
|
+
const input = { id };
|
|
2292
|
+
if (config !== undefined)
|
|
2293
|
+
input.config = config;
|
|
2294
|
+
if (position !== undefined)
|
|
2295
|
+
input.position = position;
|
|
2296
|
+
if (o.datasink)
|
|
2297
|
+
input.dataSinkId = o.datasink;
|
|
2298
|
+
if (o.parser)
|
|
2299
|
+
input.parserId = o.parser;
|
|
2300
|
+
if (o.dataset)
|
|
2301
|
+
input.datasetId = o.dataset;
|
|
2302
|
+
if (o.cacheTtl !== undefined)
|
|
2303
|
+
input.cacheTTL = o.cacheTtl;
|
|
2304
|
+
const d = await execWorkspace(UPDATE_WIDGET, {
|
|
2305
|
+
input
|
|
2306
|
+
});
|
|
2307
|
+
printSuccess(`Updated widget ${id}`);
|
|
2308
|
+
formatAndOutput(o, d.updateBigConsoleWidget);
|
|
2309
|
+
} catch (e) {
|
|
2310
|
+
handleCommandError(e);
|
|
2311
|
+
}
|
|
2312
|
+
});
|
|
2313
|
+
}
|
|
2314
|
+
// src/utils/parse.ts
|
|
2315
|
+
import { InvalidArgumentError } from "commander";
|
|
2316
|
+
function parseVersion(raw) {
|
|
2317
|
+
if (!/^\d+$/.test(raw)) {
|
|
2318
|
+
throw new InvalidArgumentError("must be a whole number, for example 3");
|
|
2319
|
+
}
|
|
2320
|
+
return Number(raw);
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
// src/commands/datasink.ts
|
|
2324
|
+
function registerDataSinkCommands(program) {
|
|
2325
|
+
const ds = program.command("datasink").description("DataSink operations");
|
|
2326
|
+
ds.command("list").description("List DataSinks in the current workspace").option("--search <term>", "Search by name").option("--status <s>", "Filter by status").option("--setup-state <s>", "Filter by setup state").option("--active <bool>", "Filter by isActive (true/false)").option("--first <n>", "Page size", (v) => parseInt(v, 10), 25).option("--after <cursor>", "Cursor for next page").option("-f, --format <format>", "Output format").action(async (o) => {
|
|
2327
|
+
try {
|
|
2328
|
+
const d = await execWorkspace(LIST_DATASINKS, {
|
|
2329
|
+
first: o.first ?? 25,
|
|
2330
|
+
after: o.after,
|
|
2331
|
+
search: o.search,
|
|
2332
|
+
status: o.status ? o.status.toUpperCase() : undefined,
|
|
2333
|
+
setupState: o.setupState ? o.setupState.toUpperCase() : undefined,
|
|
2334
|
+
isActive: o.active === undefined ? undefined : o.active.toLowerCase() === "true" || o.active === "1"
|
|
2335
|
+
});
|
|
2336
|
+
const items = d.listDataSinks.edges.map((e) => e.node);
|
|
2337
|
+
formatAndOutput(o, items, {
|
|
2338
|
+
headers: ["ID", "Name", "Key", "Status", "Setup", "Active", "Updated"],
|
|
2339
|
+
rows: items.map((x) => [
|
|
2340
|
+
truncate(x.id, 14),
|
|
2341
|
+
truncate(x.name ?? "-", 24),
|
|
2342
|
+
truncate(x.key, 20),
|
|
2343
|
+
formatStatus(x.status),
|
|
2344
|
+
x.setupState,
|
|
2345
|
+
x.isActive ? "yes" : "no",
|
|
2346
|
+
formatDate(x.updatedAt)
|
|
2347
|
+
])
|
|
2348
|
+
});
|
|
2349
|
+
} catch (e) {
|
|
2350
|
+
handleCommandError(e);
|
|
2351
|
+
}
|
|
2352
|
+
});
|
|
2353
|
+
ds.command("get").description("Get a DataSink by ID or key").option("--id <id>", "DataSink ID").option("--key <key>", "DataSink key").option("-f, --format <format>", "Output format").action(async (o) => {
|
|
2354
|
+
try {
|
|
2355
|
+
if (!o.id && !o.key) {
|
|
2356
|
+
printError("Provide --id or --key");
|
|
2357
|
+
process.exit(1);
|
|
2358
|
+
}
|
|
2359
|
+
const d = await execWorkspace(GET_DATASINK, {
|
|
2360
|
+
id: o.id,
|
|
2361
|
+
key: o.key
|
|
2362
|
+
});
|
|
2363
|
+
if (!d.getDataSink) {
|
|
2364
|
+
printError("DataSink not found");
|
|
2365
|
+
process.exit(1);
|
|
2366
|
+
}
|
|
2367
|
+
formatAndOutput(o, d.getDataSink);
|
|
2368
|
+
} catch (e) {
|
|
2369
|
+
handleCommandError(e);
|
|
2370
|
+
}
|
|
2371
|
+
});
|
|
2372
|
+
ds.command("data").description("Fetch the current data payload of a DataSink").option("--id <id>", "DataSink ID").option("--key <key>", "DataSink key").option("--path <p>", "JSONPath-like filter (e.g. items[0].value)").option("--force-refresh", "Bypass cache").option("-f, --format <format>", "Output format (json recommended)").action(async (o) => {
|
|
2373
|
+
try {
|
|
2374
|
+
if (!o.id && !o.key) {
|
|
2375
|
+
printError("Provide --id or --key");
|
|
2376
|
+
process.exit(1);
|
|
2377
|
+
}
|
|
2378
|
+
const d = await execWorkspace(GET_DATASINK_DATA, {
|
|
2379
|
+
id: o.id,
|
|
2380
|
+
key: o.key,
|
|
2381
|
+
dataPath: o.path,
|
|
2382
|
+
forceRefresh: o.forceRefresh ?? false
|
|
2383
|
+
});
|
|
2384
|
+
formatAndOutput({ format: o.format ?? "json" }, d.getDataSinkData);
|
|
2385
|
+
} catch (e) {
|
|
2386
|
+
handleCommandError(e);
|
|
2387
|
+
}
|
|
2388
|
+
});
|
|
2389
|
+
ds.command("delete").description("Delete a DataSink").argument("<id>").action(async (id) => {
|
|
2390
|
+
try {
|
|
2391
|
+
await execWorkspace(DELETE_DATASINK, { id });
|
|
2392
|
+
printSuccess(`Deleted DataSink ${id}`);
|
|
2393
|
+
} catch (e) {
|
|
2394
|
+
handleCommandError(e);
|
|
2395
|
+
}
|
|
2396
|
+
});
|
|
2397
|
+
ds.command("create").description("Create a DataSink").requiredOption("--key <key>", "Unique key within workspace (e.g. sales_metrics_q4)").option("--name <name>", "Display name").option("--description <d>", "Description").option("--data-file <path>", "JSON file with initial payload").option("--data-json <json>", "Inline JSON for initial payload").option("--config-file <path>", "JSON file with DataSink config").option("--config-json <json>", "Inline JSON for DataSink config").option("-f, --format <format>", "Output format").action(async (o) => {
|
|
2398
|
+
try {
|
|
2399
|
+
const data = loadJsonInput(o.dataFile, o.dataJson);
|
|
2400
|
+
const config = loadJsonInput(o.configFile, o.configJson);
|
|
2401
|
+
const input = { key: o.key };
|
|
2402
|
+
if (o.name)
|
|
2403
|
+
input.name = o.name;
|
|
2404
|
+
if (o.description)
|
|
2405
|
+
input.description = o.description;
|
|
2406
|
+
if (data !== undefined)
|
|
2407
|
+
input.data = data;
|
|
2408
|
+
if (config !== undefined)
|
|
2409
|
+
input.config = config;
|
|
2410
|
+
const d = await execWorkspace(CREATE_DATASINK, {
|
|
2411
|
+
input
|
|
2412
|
+
});
|
|
2413
|
+
printSuccess(`Created DataSink: ${d.createDataSink.key} (${d.createDataSink.id})`);
|
|
2414
|
+
formatAndOutput(o, d.createDataSink);
|
|
2415
|
+
} catch (e) {
|
|
2416
|
+
handleCommandError(e);
|
|
2417
|
+
}
|
|
2418
|
+
});
|
|
2419
|
+
ds.command("update").description("Update a DataSink").argument("<id>").option("--name <name>").option("--description <d>").option("--active <bool>", "true / false").option("--version <n>", "Current DataSink version (optimistic lock); fetched automatically when omitted", parseVersion).option("--data-file <path>", "JSON file to replace data payload").option("--data-json <json>", "Inline JSON to replace data payload").option("-f, --format <format>", "Output format").action(async (id, o) => {
|
|
2420
|
+
try {
|
|
2421
|
+
const data = loadJsonInput(o.dataFile, o.dataJson);
|
|
2422
|
+
let version = o.version;
|
|
2423
|
+
if (version === undefined) {
|
|
2424
|
+
const current = await execWorkspace(GET_DATASINK, { id });
|
|
2425
|
+
if (!current.getDataSink || typeof current.getDataSink.version !== "number") {
|
|
2426
|
+
printError(`DataSink not found or has no version: ${id}`);
|
|
2427
|
+
process.exit(1);
|
|
2428
|
+
}
|
|
2429
|
+
version = current.getDataSink.version;
|
|
2430
|
+
}
|
|
2431
|
+
const input = { id, version };
|
|
2432
|
+
if (o.name)
|
|
2433
|
+
input.name = o.name;
|
|
2434
|
+
if (o.description !== undefined)
|
|
2435
|
+
input.description = o.description;
|
|
2436
|
+
if (o.active !== undefined) {
|
|
2437
|
+
input.isActive = o.active.toLowerCase() === "true" || o.active === "1";
|
|
2438
|
+
}
|
|
2439
|
+
if (data !== undefined)
|
|
2440
|
+
input.data = data;
|
|
2441
|
+
const d = await execWorkspace(UPDATE_DATASINK, {
|
|
2442
|
+
input
|
|
2443
|
+
});
|
|
2444
|
+
printSuccess(`Updated DataSink ${id}`);
|
|
2445
|
+
formatAndOutput(o, d.updateDataSink);
|
|
2446
|
+
} catch (e) {
|
|
2447
|
+
handleCommandError(e);
|
|
2448
|
+
}
|
|
2449
|
+
});
|
|
2450
|
+
}
|
|
2451
|
+
// src/commands/parser.ts
|
|
2452
|
+
function registerParserCommands(program) {
|
|
2453
|
+
const p = program.command("parser").description("Parser operations");
|
|
2454
|
+
p.command("list").description("List parsers in the current workspace (scoped to a console)").requiredOption("--console <id>", "Console (dashboard) ID parsers belong to").option("--status <s>", "Filter by status (active, inactive)").option("--type <t>", "Filter by parser type (jq, jsonata, spark)").option("--input <sinkKey>", "Filter by input sink key").option("--first <n>", "Page size", (v) => parseInt(v, 10), 25).option("--after <cursor>", "Cursor for next page").option("-f, --format <format>", "Output format").action(async (o) => {
|
|
2455
|
+
try {
|
|
2456
|
+
const VALID_STATUSES = new Set(["active", "inactive"]);
|
|
2457
|
+
const VALID_TYPES = new Set(["jq", "jsonata", "spark"]);
|
|
2458
|
+
const filter = { consoleId: o.console };
|
|
2459
|
+
if (o.status) {
|
|
2460
|
+
const s = o.status.toLowerCase();
|
|
2461
|
+
if (!VALID_STATUSES.has(s)) {
|
|
2462
|
+
printError(`Invalid --status "${o.status}". Valid: ${[...VALID_STATUSES].join(", ")}`);
|
|
2463
|
+
process.exit(1);
|
|
2464
|
+
}
|
|
2465
|
+
filter.status = s;
|
|
2466
|
+
}
|
|
2467
|
+
if (o.type) {
|
|
2468
|
+
const t = o.type.toLowerCase();
|
|
2469
|
+
if (!VALID_TYPES.has(t)) {
|
|
2470
|
+
printError(`Invalid --type "${o.type}". Valid: ${[...VALID_TYPES].join(", ")}`);
|
|
2471
|
+
process.exit(1);
|
|
2472
|
+
}
|
|
2473
|
+
filter.type = t;
|
|
2474
|
+
}
|
|
2475
|
+
if (o.input)
|
|
2476
|
+
filter.inputSinkKey = o.input;
|
|
2477
|
+
const d = await execWorkspace(LIST_PARSERS, { first: o.first ?? 25, after: o.after, filter });
|
|
2478
|
+
const items = d.listParsers.edges.map((e) => e.node);
|
|
2479
|
+
formatAndOutput(o, items, {
|
|
2480
|
+
headers: ["ID", "Name", "Type", "Status", "Input", "Output", "Updated"],
|
|
2481
|
+
rows: items.map((x) => [
|
|
2482
|
+
truncate(x.id, 14),
|
|
2483
|
+
truncate(x.name, 24),
|
|
2484
|
+
x.type,
|
|
2485
|
+
formatStatus(x.status),
|
|
2486
|
+
truncate(x.inputSinkKey, 18),
|
|
2487
|
+
truncate(x.outputKey, 18),
|
|
2488
|
+
formatDate(x.updatedAt)
|
|
2489
|
+
])
|
|
2490
|
+
});
|
|
2491
|
+
} catch (e) {
|
|
2492
|
+
handleCommandError(e);
|
|
2493
|
+
}
|
|
2494
|
+
});
|
|
2495
|
+
p.command("get").description("Get a parser by ID (includes the expression body)").argument("<id>").option("-f, --format <format>", "Output format").action(async (id, o) => {
|
|
2496
|
+
try {
|
|
2497
|
+
const d = await execWorkspace(GET_PARSER, { id });
|
|
2498
|
+
if (!d.parser) {
|
|
2499
|
+
printError("Parser not found");
|
|
2500
|
+
process.exit(1);
|
|
2501
|
+
}
|
|
2502
|
+
formatAndOutput(o, d.parser);
|
|
2503
|
+
} catch (e) {
|
|
2504
|
+
handleCommandError(e);
|
|
2505
|
+
}
|
|
2506
|
+
});
|
|
2507
|
+
p.command("delete").description("Delete a parser").argument("<id>").action(async (id) => {
|
|
2508
|
+
try {
|
|
2509
|
+
await execWorkspace(DELETE_PARSER, { id });
|
|
2510
|
+
printSuccess(`Deleted parser ${id}`);
|
|
2511
|
+
} catch (e) {
|
|
2512
|
+
handleCommandError(e);
|
|
2513
|
+
}
|
|
2514
|
+
});
|
|
2515
|
+
p.command("create").description("Create a parser").requiredOption("--name <name>").requiredOption("--console <id>", "Parent dashboard/console ID").requiredOption("--input-key <key>", "DataSink key this parser reads from").requiredOption("--output-key <key>", "Key for parser output (used by widgets)").requiredOption("--type <t>", "Parser type (jq | jsonata | spark)").option("--expression <expr>", "Parser expression (inline)").option("--expression-file <path>", "Parser expression from file").option("--description <d>").option("--output-widget <id>", "Target widget ID (v2.1)").option("--ttl <seconds>", "Cache TTL", (v) => parseInt(v, 10)).option("-f, --format <format>", "Output format").action(async (o) => {
|
|
2516
|
+
try {
|
|
2517
|
+
const expression = loadTextInput(o.expressionFile, o.expression);
|
|
2518
|
+
if (!expression) {
|
|
2519
|
+
printError("Provide --expression or --expression-file");
|
|
2520
|
+
process.exit(1);
|
|
2521
|
+
}
|
|
2522
|
+
const input = {
|
|
2523
|
+
name: o.name,
|
|
2524
|
+
consoleId: o.console,
|
|
2525
|
+
inputSinkKey: o.inputKey,
|
|
2526
|
+
outputKey: o.outputKey,
|
|
2527
|
+
type: o.type.toLowerCase(),
|
|
2528
|
+
expression
|
|
2529
|
+
};
|
|
2530
|
+
if (o.description)
|
|
2531
|
+
input.description = o.description;
|
|
2532
|
+
if (o.outputWidget)
|
|
2533
|
+
input.outputWidgetId = o.outputWidget;
|
|
2534
|
+
if (o.ttl !== undefined)
|
|
2535
|
+
input.ttl = o.ttl;
|
|
2536
|
+
const d = await execWorkspace(CREATE_PARSER, {
|
|
2537
|
+
input
|
|
2538
|
+
});
|
|
2539
|
+
printSuccess(`Created parser: ${d.createParser.name} (${d.createParser.id})`);
|
|
2540
|
+
formatAndOutput(o, d.createParser);
|
|
2541
|
+
} catch (e) {
|
|
2542
|
+
handleCommandError(e);
|
|
2543
|
+
}
|
|
2544
|
+
});
|
|
2545
|
+
p.command("update").description("Update a parser (requires current version for optimistic concurrency)").argument("<id>").requiredOption("--version <n>", "Current parser version", parseVersion).option("--name <n>").option("--description <d>").option("--input-key <k>").option("--output-key <k>").option("--output-widget <id>").option("--status <s>", "Parser status (active | inactive)").option("--expression <expr>").option("--expression-file <path>").option("--ttl <seconds>", "", (v) => parseInt(v, 10)).option("-f, --format <format>", "Output format").action(async (id, o) => {
|
|
2546
|
+
try {
|
|
2547
|
+
const expression = loadTextInput(o.expressionFile, o.expression);
|
|
2548
|
+
const input = {};
|
|
2549
|
+
if (o.name)
|
|
2550
|
+
input.name = o.name;
|
|
2551
|
+
if (o.description !== undefined)
|
|
2552
|
+
input.description = o.description;
|
|
2553
|
+
if (o.inputKey)
|
|
2554
|
+
input.inputSinkKey = o.inputKey;
|
|
2555
|
+
if (o.outputKey)
|
|
2556
|
+
input.outputKey = o.outputKey;
|
|
2557
|
+
if (o.outputWidget)
|
|
2558
|
+
input.outputWidgetId = o.outputWidget;
|
|
2559
|
+
if (o.status)
|
|
2560
|
+
input.status = o.status.toLowerCase();
|
|
2561
|
+
if (expression)
|
|
2562
|
+
input.expression = expression;
|
|
2563
|
+
if (o.ttl !== undefined)
|
|
2564
|
+
input.ttl = o.ttl;
|
|
2565
|
+
const d = await execWorkspace(UPDATE_PARSER, {
|
|
2566
|
+
id,
|
|
2567
|
+
version: o.version,
|
|
2568
|
+
input
|
|
2569
|
+
});
|
|
2570
|
+
printSuccess(`Updated parser ${id} (now v${d.updateParser.version})`);
|
|
2571
|
+
formatAndOutput(o, d.updateParser);
|
|
2572
|
+
} catch (e) {
|
|
2573
|
+
handleCommandError(e);
|
|
2574
|
+
}
|
|
2575
|
+
});
|
|
2576
|
+
}
|
|
2577
|
+
// src/commands/pipeline.ts
|
|
2578
|
+
function registerPipelineCommands(program) {
|
|
2579
|
+
const p = program.command("pipeline").description("Pipeline operations");
|
|
2580
|
+
p.command("list").description("List pipelines in the current workspace (scoped to a console)").requiredOption("--console <id>", "Console (dashboard) ID pipelines belong to").option("--status <s>", "Filter by status (active, draft, error, inactive)").option("--first <n>", "Page size", (v) => parseInt(v, 10), 25).option("--after <cursor>", "Cursor for next page").option("-f, --format <format>", "Output format").action(async (o) => {
|
|
2581
|
+
try {
|
|
2582
|
+
const VALID_STATUSES = new Set(["active", "draft", "error", "inactive"]);
|
|
2583
|
+
const filter = { consoleId: o.console };
|
|
2584
|
+
if (o.status) {
|
|
2585
|
+
const s = o.status.toLowerCase();
|
|
2586
|
+
if (!VALID_STATUSES.has(s)) {
|
|
2587
|
+
printError(`Invalid --status "${o.status}". Valid: ${[...VALID_STATUSES].join(", ")}`);
|
|
2588
|
+
process.exit(1);
|
|
2589
|
+
}
|
|
2590
|
+
filter.status = s;
|
|
2591
|
+
}
|
|
2592
|
+
const d = await execWorkspace(LIST_PIPELINES, { first: o.first ?? 25, after: o.after, filter });
|
|
2593
|
+
const items = d.listPipelines.edges.map((e) => e.node);
|
|
2594
|
+
formatAndOutput(o, items, {
|
|
2595
|
+
headers: ["ID", "Name", "Status", "Version", "Last Executed", "Updated"],
|
|
2596
|
+
rows: items.map((x) => [
|
|
2597
|
+
truncate(x.id, 14),
|
|
2598
|
+
truncate(x.name, 28),
|
|
2599
|
+
formatStatus(x.status),
|
|
2600
|
+
String(x.version),
|
|
2601
|
+
formatDate(x.lastExecutedAt),
|
|
2602
|
+
formatDate(x.updatedAt)
|
|
2603
|
+
])
|
|
2604
|
+
});
|
|
2605
|
+
} catch (e) {
|
|
2606
|
+
handleCommandError(e);
|
|
2607
|
+
}
|
|
2608
|
+
});
|
|
2609
|
+
p.command("get").description("Get a pipeline by ID (includes stages)").argument("<id>").option("-f, --format <format>", "Output format").action(async (id, o) => {
|
|
2610
|
+
try {
|
|
2611
|
+
const d = await execWorkspace(GET_PIPELINE, { id });
|
|
2612
|
+
if (!d.pipeline) {
|
|
2613
|
+
printError("Pipeline not found");
|
|
2614
|
+
process.exit(1);
|
|
2615
|
+
}
|
|
2616
|
+
formatAndOutput(o, d.pipeline);
|
|
2617
|
+
} catch (e) {
|
|
2618
|
+
handleCommandError(e);
|
|
2619
|
+
}
|
|
2620
|
+
});
|
|
2621
|
+
p.command("delete").description("Delete a pipeline").argument("<id>").action(async (id) => {
|
|
2622
|
+
try {
|
|
2623
|
+
await execWorkspace(DELETE_PIPELINE, { id });
|
|
2624
|
+
printSuccess(`Deleted pipeline ${id}`);
|
|
2625
|
+
} catch (e) {
|
|
2626
|
+
handleCommandError(e);
|
|
2627
|
+
}
|
|
2628
|
+
});
|
|
2629
|
+
p.command("create").description("Create a pipeline").requiredOption("--name <name>").requiredOption("--console <id>", "Parent dashboard/console ID").option("--description <d>").option("--status <s>", "Initial status (draft | active | inactive | error)").option("--stages-file <path>", "JSON file with array of CreatePipelineStageInput").option("--stages-json <json>", "Inline JSON array for stages").option("-f, --format <format>", "Output format").action(async (o) => {
|
|
2630
|
+
try {
|
|
2631
|
+
const stages = loadJsonInput(o.stagesFile, o.stagesJson);
|
|
2632
|
+
const input = {
|
|
2633
|
+
name: o.name,
|
|
2634
|
+
consoleId: o.console
|
|
2635
|
+
};
|
|
2636
|
+
if (o.description)
|
|
2637
|
+
input.description = o.description;
|
|
2638
|
+
if (o.status)
|
|
2639
|
+
input.status = o.status.toLowerCase();
|
|
2640
|
+
if (stages)
|
|
2641
|
+
input.stages = stages;
|
|
2642
|
+
const d = await execWorkspace(CREATE_PIPELINE, {
|
|
2643
|
+
input
|
|
2644
|
+
});
|
|
2645
|
+
printSuccess(`Created pipeline: ${d.createPipeline.name} (${d.createPipeline.id})`);
|
|
2646
|
+
formatAndOutput(o, d.createPipeline);
|
|
2647
|
+
} catch (e) {
|
|
2648
|
+
handleCommandError(e);
|
|
2649
|
+
}
|
|
2650
|
+
});
|
|
2651
|
+
p.command("update").description("Update a pipeline (requires current version)").argument("<id>").requiredOption("--version <n>", "Current pipeline version", parseVersion).option("--name <n>").option("--description <d>").option("--status <s>", "Pipeline status (draft | active | inactive | error)").option("--stages-file <path>", "JSON file with stages array to replace existing stages").option("--stages-json <json>", "Inline JSON stages array").option("-f, --format <format>", "Output format").action(async (id, o) => {
|
|
2652
|
+
try {
|
|
2653
|
+
const stages = loadJsonInput(o.stagesFile, o.stagesJson);
|
|
2654
|
+
const input = {};
|
|
2655
|
+
if (o.name)
|
|
2656
|
+
input.name = o.name;
|
|
2657
|
+
if (o.description !== undefined)
|
|
2658
|
+
input.description = o.description;
|
|
2659
|
+
if (o.status)
|
|
2660
|
+
input.status = o.status.toLowerCase();
|
|
2661
|
+
if (stages)
|
|
2662
|
+
input.stages = stages;
|
|
2663
|
+
const d = await execWorkspace(UPDATE_PIPELINE, {
|
|
2664
|
+
id,
|
|
2665
|
+
version: o.version,
|
|
2666
|
+
input
|
|
2667
|
+
});
|
|
2668
|
+
printSuccess(`Updated pipeline ${id} (now v${d.updatePipeline.version})`);
|
|
2669
|
+
formatAndOutput(o, d.updatePipeline);
|
|
2670
|
+
} catch (e) {
|
|
2671
|
+
handleCommandError(e);
|
|
2672
|
+
}
|
|
2673
|
+
});
|
|
2674
|
+
}
|
|
2675
|
+
// src/commands/workflow.ts
|
|
2676
|
+
function registerWorkflowCommands(program) {
|
|
2677
|
+
const wf = program.command("workflow").description("Workflow operations");
|
|
2678
|
+
wf.command("list").description("List workflows linked to BigConsole").option("-f, --format <format>", "Output format").action(async (o) => {
|
|
2679
|
+
try {
|
|
2680
|
+
const d = await execWorkspace(LIST_WORKFLOWS, {});
|
|
2681
|
+
const items = d.workflows;
|
|
2682
|
+
formatAndOutput(o, items, {
|
|
2683
|
+
headers: ["ID", "Name", "Key", "Status", "Execs", "Sinks", "Last Run"],
|
|
2684
|
+
rows: items.map((x) => [
|
|
2685
|
+
truncate(x.id, 14),
|
|
2686
|
+
truncate(x.name, 24),
|
|
2687
|
+
truncate(x.key, 18),
|
|
2688
|
+
formatStatus(x.status),
|
|
2689
|
+
String(x.executionCount),
|
|
2690
|
+
String(x.linkedDataSinkCount),
|
|
2691
|
+
formatDate(x.lastExecutedAt)
|
|
2692
|
+
])
|
|
2693
|
+
});
|
|
2694
|
+
} catch (e) {
|
|
2695
|
+
handleCommandError(e);
|
|
2696
|
+
}
|
|
2697
|
+
});
|
|
2698
|
+
wf.command("get").description("Get a workflow by ID").argument("<id>").option("-f, --format <format>", "Output format").action(async (id, o) => {
|
|
2699
|
+
try {
|
|
2700
|
+
const d = await execWorkspace(GET_WORKFLOW, { id });
|
|
2701
|
+
if (!d.workflow) {
|
|
2702
|
+
printError("Workflow not found");
|
|
2703
|
+
process.exit(1);
|
|
2704
|
+
}
|
|
2705
|
+
formatAndOutput(o, d.workflow);
|
|
2706
|
+
} catch (e) {
|
|
2707
|
+
handleCommandError(e);
|
|
2708
|
+
}
|
|
2709
|
+
});
|
|
2710
|
+
}
|
|
2711
|
+
// src/commands/drilldown.ts
|
|
2712
|
+
function registerDrilldownCommands(program) {
|
|
2713
|
+
const dd = program.command("drilldown").description("Widget drilldown actions");
|
|
2714
|
+
dd.command("list").description("List widget actions (includes drilldowns)").requiredOption("--widget <id>", "Widget ID").option("-f, --format <format>", "Output format").action(async (o) => {
|
|
2715
|
+
try {
|
|
2716
|
+
const d = await execWorkspace(GET_WIDGET_ACTIONS, { widgetId: o.widget });
|
|
2717
|
+
formatAndOutput(o, d.getWidgetActions, {
|
|
2718
|
+
headers: ["ID", "Widget", "Type", "Label", "Created"],
|
|
2719
|
+
rows: d.getWidgetActions.map((a) => [
|
|
2720
|
+
truncate(a.id, 14),
|
|
2721
|
+
truncate(a.widgetId, 14),
|
|
2722
|
+
a.type,
|
|
2723
|
+
truncate(a.label ?? "-", 24),
|
|
2724
|
+
formatDate(a.createdAt)
|
|
2725
|
+
])
|
|
2726
|
+
});
|
|
2727
|
+
} catch (e) {
|
|
2728
|
+
handleCommandError(e);
|
|
2729
|
+
}
|
|
2730
|
+
});
|
|
2731
|
+
dd.command("delete").description("Delete the drilldown configuration from a widget").requiredOption("--widget <id>", "Widget ID").action(async (o) => {
|
|
2732
|
+
try {
|
|
2733
|
+
const lookup = await execWorkspace(GET_WIDGET_DRILLDOWN, {
|
|
2734
|
+
id: o.widget
|
|
2735
|
+
});
|
|
2736
|
+
const drilldownId = lookup.getWidget?.drilldown?.id;
|
|
2737
|
+
if (!drilldownId) {
|
|
2738
|
+
throw new Error(`Widget ${o.widget} has no drilldown configuration`);
|
|
2739
|
+
}
|
|
2740
|
+
await execWorkspace(DELETE_WIDGET_DRILLDOWN, { id: drilldownId });
|
|
2741
|
+
printSuccess(`Removed drilldown from widget ${o.widget}`);
|
|
2742
|
+
} catch (e) {
|
|
2743
|
+
handleCommandError(e);
|
|
2744
|
+
}
|
|
2745
|
+
});
|
|
2746
|
+
}
|
|
2747
|
+
// src/commands/share.ts
|
|
2748
|
+
function registerShareCommands(program) {
|
|
2749
|
+
const sh = program.command("share").description("Dashboard share management");
|
|
2750
|
+
sh.command("list").description("List shares for a dashboard").requiredOption("--dashboard <id>", "Dashboard ID").option("--active <bool>", "Filter by active status").option("--permission <p>", "Filter by permission (VIEW, EDIT, ...)").option("--recipient-type <t>", "USER | GROUP").option("--first <n>", "Page size", (v) => parseInt(v, 10), 25).option("--after <cursor>").option("-f, --format <format>", "Output format").action(async (o) => {
|
|
2751
|
+
try {
|
|
2752
|
+
const d = await execWorkspace(LIST_DASHBOARD_SHARES, {
|
|
2753
|
+
dashboardId: o.dashboard,
|
|
2754
|
+
first: o.first ?? 25,
|
|
2755
|
+
after: o.after,
|
|
2756
|
+
isActive: o.active === undefined ? undefined : o.active.toLowerCase() === "true" || o.active === "1",
|
|
2757
|
+
permission: o.permission ? o.permission.toUpperCase() : undefined,
|
|
2758
|
+
recipientType: o.recipientType ? o.recipientType.toUpperCase() : undefined
|
|
2759
|
+
});
|
|
2760
|
+
const items = d.listDashboardShares.edges.map((e) => e.node);
|
|
2761
|
+
formatAndOutput(o, items, {
|
|
2762
|
+
headers: ["ID", "Dashboard", "Permission", "Active", "Expires", "Created"],
|
|
2763
|
+
rows: items.map((x) => [
|
|
2764
|
+
truncate(x.id, 14),
|
|
2765
|
+
truncate(x.dashboardId, 14),
|
|
2766
|
+
x.permission,
|
|
2767
|
+
x.isActive ? "yes" : "no",
|
|
2768
|
+
formatDate(x.expiresAt),
|
|
2769
|
+
formatDate(x.createdAt)
|
|
2770
|
+
])
|
|
2771
|
+
});
|
|
2772
|
+
} catch (e) {
|
|
2773
|
+
handleCommandError(e);
|
|
2774
|
+
}
|
|
2775
|
+
});
|
|
2776
|
+
}
|
|
2777
|
+
// src/commands/template.ts
|
|
2778
|
+
function registerTemplateCommands(program) {
|
|
2779
|
+
const t = program.command("template").description("Dashboard templates");
|
|
2780
|
+
t.command("list").description("List dashboard templates").option("--search <term>").option("--category <c>").option("--featured", "Only featured templates").option("--first <n>", "Page size", (v) => parseInt(v, 10), 25).option("--after <cursor>").option("-f, --format <format>", "Output format").action(async (o) => {
|
|
2781
|
+
try {
|
|
2782
|
+
const d = await execWorkspace(LIST_TEMPLATES, {
|
|
2783
|
+
first: o.first ?? 25,
|
|
2784
|
+
after: o.after,
|
|
2785
|
+
category: o.category ? o.category.toUpperCase() : undefined,
|
|
2786
|
+
search: o.search,
|
|
2787
|
+
isFeatured: o.featured ?? undefined
|
|
2788
|
+
});
|
|
2789
|
+
const items = d.listTemplates.edges.map((e) => e.node);
|
|
2790
|
+
formatAndOutput(o, items, {
|
|
2791
|
+
headers: ["ID", "Name", "Category", "Featured", "Status", "Created"],
|
|
2792
|
+
rows: items.map((x) => [
|
|
2793
|
+
truncate(x.id, 14),
|
|
2794
|
+
truncate(x.name, 30),
|
|
2795
|
+
x.category,
|
|
2796
|
+
x.isFeatured ? "*" : "",
|
|
2797
|
+
x.status,
|
|
2798
|
+
formatDate(x.createdAt)
|
|
2799
|
+
])
|
|
2800
|
+
});
|
|
2801
|
+
} catch (e) {
|
|
2802
|
+
handleCommandError(e);
|
|
2803
|
+
}
|
|
2804
|
+
});
|
|
2805
|
+
t.command("get").description("Get a template by ID").argument("<id>").option("-f, --format <format>", "Output format").action(async (id, o) => {
|
|
2806
|
+
try {
|
|
2807
|
+
const d = await execWorkspace(GET_TEMPLATE, {
|
|
2808
|
+
id
|
|
2809
|
+
});
|
|
2810
|
+
if (!d.getTemplate) {
|
|
2811
|
+
printError("Template not found");
|
|
2812
|
+
process.exit(1);
|
|
2813
|
+
}
|
|
2814
|
+
formatAndOutput(o, d.getTemplate);
|
|
2815
|
+
} catch (e) {
|
|
2816
|
+
handleCommandError(e);
|
|
2817
|
+
}
|
|
2818
|
+
});
|
|
2819
|
+
}
|
|
2820
|
+
// src/commands/prompt.ts
|
|
2821
|
+
function registerPromptCommands(program) {
|
|
2822
|
+
const p = program.command("prompt").description("AI-driven dashboard generation");
|
|
2823
|
+
p.command("generate").description("Start a new dashboard generation from a natural-language prompt").requiredOption("--prompt <text>", "The natural-language prompt").option("--config-file <path>", "Optional JSON with generation config").option("--config-json <json>", "Optional inline JSON with generation config").option("-f, --format <format>", "Output format").action(async (o) => {
|
|
2824
|
+
try {
|
|
2825
|
+
const extra = loadJsonInput(o.configFile, o.configJson) ?? {};
|
|
2826
|
+
const input = { prompt: o.prompt, ...extra };
|
|
2827
|
+
const d = await execWorkspace(GENERATE_DASHBOARD_FROM_PROMPT, { input });
|
|
2828
|
+
printSuccess(`Generation queued: ${d.generateDashboardFromPrompt.id}`);
|
|
2829
|
+
formatAndOutput(o, d.generateDashboardFromPrompt);
|
|
2830
|
+
} catch (e) {
|
|
2831
|
+
handleCommandError(e);
|
|
2832
|
+
}
|
|
2833
|
+
});
|
|
2834
|
+
p.command("list").description("List prompt generations").option("--status <s>", "Filter by status").option("--first <n>", "Page size", (v) => parseInt(v, 10), 25).option("--after <cursor>").option("-f, --format <format>", "Output format").action(async (o) => {
|
|
2835
|
+
try {
|
|
2836
|
+
const d = await execWorkspace(LIST_PROMPT_GENERATIONS, {
|
|
2837
|
+
first: o.first ?? 25,
|
|
2838
|
+
after: o.after,
|
|
2839
|
+
status: o.status ? o.status.toUpperCase() : undefined
|
|
2840
|
+
});
|
|
2841
|
+
const items = d.listPromptGenerations.edges.map((e) => e.node);
|
|
2842
|
+
formatAndOutput(o, items, {
|
|
2843
|
+
headers: ["ID", "Status", "Prompt", "Created"],
|
|
2844
|
+
rows: items.map((x) => [
|
|
2845
|
+
truncate(x.id, 14),
|
|
2846
|
+
formatStatus(x.status),
|
|
2847
|
+
truncate(x.prompt, 50),
|
|
2848
|
+
formatDate(x.createdAt)
|
|
2849
|
+
])
|
|
2850
|
+
});
|
|
2851
|
+
} catch (e) {
|
|
2852
|
+
handleCommandError(e);
|
|
2853
|
+
}
|
|
2854
|
+
});
|
|
2855
|
+
p.command("get").description("Get a prompt generation by ID").argument("<id>").option("-f, --format <format>", "Output format").action(async (id, o) => {
|
|
2856
|
+
try {
|
|
2857
|
+
const d = await execWorkspace(GET_PROMPT_GENERATION, { id });
|
|
2858
|
+
if (!d.getPromptGeneration) {
|
|
2859
|
+
printError("Prompt generation not found");
|
|
2860
|
+
process.exit(1);
|
|
2861
|
+
}
|
|
2862
|
+
formatAndOutput(o, d.getPromptGeneration);
|
|
2863
|
+
} catch (e) {
|
|
2864
|
+
handleCommandError(e);
|
|
2865
|
+
}
|
|
2866
|
+
});
|
|
2867
|
+
p.command("cancel").description("Cancel an in-flight prompt generation").argument("<id>").action(async (id) => {
|
|
2868
|
+
try {
|
|
2869
|
+
const d = await execWorkspace(CANCEL_PROMPT_GENERATION, { generationId: id });
|
|
2870
|
+
printSuccess(`Cancelled generation ${id} (status: ${d.cancelPromptGeneration.status})`);
|
|
2871
|
+
} catch (e) {
|
|
2872
|
+
handleCommandError(e);
|
|
2873
|
+
}
|
|
2874
|
+
});
|
|
2875
|
+
}
|
|
2876
|
+
// src/cli.ts
|
|
2877
|
+
var __filename2 = fileURLToPath(import.meta.url);
|
|
2878
|
+
var __dirname2 = dirname(__filename2);
|
|
2879
|
+
var pkg = {
|
|
2880
|
+
name: "@burdenoff/bigconsole-cli",
|
|
2881
|
+
version: "0.0.0-dev"
|
|
2882
|
+
};
|
|
2883
|
+
try {
|
|
2884
|
+
const packagePath = join3(__dirname2, "../package.json");
|
|
2885
|
+
pkg = JSON.parse(readFileSync5(packagePath, "utf8"));
|
|
2886
|
+
} catch {}
|
|
2887
|
+
try {
|
|
2888
|
+
const notifier = updateNotifier({
|
|
2889
|
+
pkg,
|
|
2890
|
+
updateCheckInterval: 1000 * 60 * 60 * 24
|
|
2891
|
+
});
|
|
2892
|
+
const argv = process.argv.slice(2);
|
|
2893
|
+
const fmtIndex = argv.findIndex((a) => a === "-f" || a === "--format");
|
|
2894
|
+
const fmt = fmtIndex !== -1 ? argv[fmtIndex + 1]?.toLowerCase() : undefined;
|
|
2895
|
+
const isMachineOutput = fmt === "json" || fmt === "yaml" || fmt === "yml";
|
|
2896
|
+
const isTty = Boolean(process.stdout.isTTY);
|
|
2897
|
+
if (notifier.update && !isMachineOutput && isTty) {
|
|
2898
|
+
const message = `Update available: ${chalk.dim(notifier.update.current)} → ${chalk.green(notifier.update.latest)}
|
|
2899
|
+
Run ${chalk.cyan(`bun add -g ${pkg.name}`)} to update`;
|
|
2900
|
+
process.stderr.write(boxen(message, {
|
|
2901
|
+
padding: 1,
|
|
2902
|
+
margin: 1,
|
|
2903
|
+
align: "center",
|
|
2904
|
+
borderColor: "yellow",
|
|
2905
|
+
borderStyle: "round"
|
|
2906
|
+
}) + `
|
|
2907
|
+
`);
|
|
2908
|
+
}
|
|
2909
|
+
} catch {}
|
|
2910
|
+
var firstArg = process.argv[2];
|
|
2911
|
+
if (firstArg === "-v" || firstArg === "--version") {
|
|
2912
|
+
process.stdout.write(`${pkg.version}
|
|
2913
|
+
`);
|
|
2914
|
+
process.exit(0);
|
|
2915
|
+
}
|
|
2916
|
+
var program = new Command;
|
|
2917
|
+
program.name("bigconsole").description("BigConsole CLI — dashboards, widgets, parsers, and workflows from the terminal").option("-f, --format <format>", "Output format (json, table, yaml)").option("--verbose", "Enable verbose logging").option("--no-color", "Disable colored output");
|
|
2918
|
+
program.hook("preAction", (_program, actionCommand) => inheritGlobalFormat(actionCommand));
|
|
63
2919
|
registerAuthCommands(program);
|
|
64
2920
|
registerCLIConfigCommands(program);
|
|
65
2921
|
registerWorkspaceCommands(program);
|
|
@@ -73,54 +2929,57 @@ registerDrilldownCommands(program);
|
|
|
73
2929
|
registerShareCommands(program);
|
|
74
2930
|
registerTemplateCommands(program);
|
|
75
2931
|
registerPromptCommands(program);
|
|
76
|
-
program.addHelpText(
|
|
77
|
-
${chalk.bold(
|
|
78
|
-
${chalk.dim(
|
|
79
|
-
$ bigconsole auth login ${chalk.dim(
|
|
80
|
-
$ bigconsole auth status ${chalk.dim(
|
|
81
|
-
|
|
82
|
-
${chalk.dim(
|
|
83
|
-
$ bigconsole workspace list ${chalk.dim(
|
|
84
|
-
$ bigconsole workspace set <id> ${chalk.dim(
|
|
85
|
-
|
|
86
|
-
${chalk.dim(
|
|
87
|
-
$ bigconsole dashboard list --search sales ${chalk.dim(
|
|
88
|
-
$ bigconsole dashboard get <id> ${chalk.dim(
|
|
89
|
-
$ bigconsole widget list --page <pageId> ${chalk.dim(
|
|
90
|
-
$ bigconsole datasink list ${chalk.dim(
|
|
91
|
-
$ bigconsole datasink data --key orders --force-refresh ${chalk.dim(
|
|
92
|
-
$ bigconsole parser list
|
|
93
|
-
$ bigconsole pipeline list
|
|
94
|
-
$ bigconsole workflow list ${chalk.dim(
|
|
95
|
-
|
|
96
|
-
${chalk.dim(
|
|
2932
|
+
program.addHelpText("after", `
|
|
2933
|
+
${chalk.bold("Examples:")}
|
|
2934
|
+
${chalk.dim("# Authentication")}
|
|
2935
|
+
$ bigconsole auth login ${chalk.dim("# OAuth2 Device Code login")}
|
|
2936
|
+
$ bigconsole auth status ${chalk.dim("# Show auth state")}
|
|
2937
|
+
|
|
2938
|
+
${chalk.dim("# Workspace selection")}
|
|
2939
|
+
$ bigconsole workspace list ${chalk.dim("# List accessible workspaces")}
|
|
2940
|
+
$ bigconsole workspace set <id> ${chalk.dim("# Switch active workspace")}
|
|
2941
|
+
|
|
2942
|
+
${chalk.dim("# Core domain")}
|
|
2943
|
+
$ bigconsole dashboard list --search sales ${chalk.dim("# List dashboards")}
|
|
2944
|
+
$ bigconsole dashboard get <id> ${chalk.dim("# Inspect a dashboard")}
|
|
2945
|
+
$ bigconsole widget list --page <pageId> ${chalk.dim("# Widgets on a page")}
|
|
2946
|
+
$ bigconsole datasink list ${chalk.dim("# List data sinks")}
|
|
2947
|
+
$ bigconsole datasink data --key orders --force-refresh ${chalk.dim("# Fetch live payload")}
|
|
2948
|
+
$ bigconsole parser list --console <dashboardId> ${chalk.dim("# List parsers")}
|
|
2949
|
+
$ bigconsole pipeline list --console <dashboardId> ${chalk.dim("# List pipelines")}
|
|
2950
|
+
$ bigconsole workflow list ${chalk.dim("# List workflows")}
|
|
2951
|
+
|
|
2952
|
+
${chalk.dim("# CLI config")}
|
|
97
2953
|
$ bigconsole cli-config show
|
|
98
2954
|
$ bigconsole cli-config set workspaceEndpoint https://graphqlworkspaces.burdenoff.com/workspaces/graphql
|
|
99
2955
|
|
|
100
|
-
${chalk.bold(
|
|
101
|
-
${chalk.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
${chalk.cyan(
|
|
105
|
-
${chalk.cyan(
|
|
106
|
-
${chalk.cyan(
|
|
107
|
-
${chalk.cyan(
|
|
108
|
-
${chalk.cyan(
|
|
109
|
-
${chalk.cyan(
|
|
110
|
-
${chalk.cyan(
|
|
111
|
-
${chalk.cyan(
|
|
112
|
-
${chalk.cyan(
|
|
113
|
-
${chalk.cyan(
|
|
114
|
-
|
|
115
|
-
${chalk.
|
|
116
|
-
${chalk.
|
|
2956
|
+
${chalk.bold("Version:")}
|
|
2957
|
+
$ bigconsole -v | --version ${chalk.dim("# Print the CLI version")}
|
|
2958
|
+
|
|
2959
|
+
${chalk.bold("Command groups:")}
|
|
2960
|
+
${chalk.cyan("auth")} login, logout, status, token, refresh
|
|
2961
|
+
${chalk.cyan("workspace")} list, current, set
|
|
2962
|
+
${chalk.cyan("dashboard")} list, get, create, update, archive, delete, import, export
|
|
2963
|
+
${chalk.cyan("widget")} list, get, create, update, delete
|
|
2964
|
+
${chalk.cyan("datasink")} list, get, data, create, update, delete
|
|
2965
|
+
${chalk.cyan("parser")} list, get, create, update, delete
|
|
2966
|
+
${chalk.cyan("pipeline")} list, get, create, update, delete
|
|
2967
|
+
${chalk.cyan("workflow")} list, get
|
|
2968
|
+
${chalk.cyan("drilldown")} list, delete
|
|
2969
|
+
${chalk.cyan("share")} list
|
|
2970
|
+
${chalk.cyan("template")} list, get
|
|
2971
|
+
${chalk.cyan("prompt")} generate, list, get, cancel
|
|
2972
|
+
${chalk.cyan("cli-config")} show, get, set, path, reset
|
|
2973
|
+
|
|
2974
|
+
${chalk.bold("Configuration:")} ${chalk.dim("~/.config/bigconsole-cli/")}
|
|
2975
|
+
${chalk.bold("Docs:")} ${chalk.blue("https://docs.bigconsole.com/cli")}
|
|
117
2976
|
`);
|
|
118
|
-
program.on(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
2977
|
+
program.on("command:*", () => {
|
|
2978
|
+
console.error(chalk.red(`Invalid command: ${sanitizeForTerminal(program.args.join(" "))}`));
|
|
2979
|
+
console.error(chalk.dim("See --help for a list of available commands."));
|
|
2980
|
+
process.exit(1);
|
|
122
2981
|
});
|
|
123
2982
|
program.parse();
|
|
124
2983
|
if (!process.argv.slice(2).length) {
|
|
125
|
-
|
|
2984
|
+
program.outputHelp();
|
|
126
2985
|
}
|