@burdenoff/bigconsole-cli 2026.1029.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/dist/cli.js CHANGED
@@ -1,68 +1,2921 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * BigConsole CLI — dashboard, widget, parser, and workflow management from the terminal.
4
- */
5
- import { Command } from 'commander';
6
- import chalk from 'chalk';
7
- import boxen from 'boxen';
8
- import updateNotifier from 'update-notifier';
9
- import { readFileSync } from 'fs';
10
- import { join, dirname } from 'path';
11
- import { fileURLToPath } from 'url';
12
- import { sanitizeForTerminal } from './utils/sanitize.js';
13
- import { registerAuthCommands, registerCLIConfigCommands, registerWorkspaceCommands, registerDashboardCommands, registerWidgetCommands, registerDataSinkCommands, registerParserCommands, registerPipelineCommands, registerWorkflowCommands, registerDrilldownCommands, registerShareCommands, registerTemplateCommands, registerPromptCommands, } from './commands/index.js';
14
- import { inheritGlobalFormat } from './commands/base.js';
15
- const __filename = fileURLToPath(import.meta.url);
16
- const __dirname = dirname(__filename);
17
- let pkg = {
18
- name: '@burdenoff/bigconsole-cli',
19
- version: '0.0.0-dev',
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
20
271
  };
21
- try {
22
- const packagePath = join(__dirname, '../package.json');
23
- pkg = JSON.parse(readFileSync(packagePath, 'utf8'));
272
+ function ensureConfigDir() {
273
+ if (!existsSync(CONFIG_DIR)) {
274
+ mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
275
+ }
24
276
  }
25
- catch {
26
- // package.json not found in dev mode — use defaults
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;
27
292
  }
28
- try {
29
- const notifier = updateNotifier({
30
- pkg,
31
- updateCheckInterval: 1000 * 60 * 60 * 24,
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()
32
451
  });
33
- // Suppress the update banner when the caller asked for machine-readable
34
- // output (`--format json|yaml`) or when stdout isn't a TTY. Printing a
35
- // human-readable box on stdout corrupts JSON/YAML piped to jq, yq,
36
- // shell vars, etc. Also send the banner to stderr (not stdout) so
37
- // it still doesn't mingle with data even in formats we miss.
38
- const argv = process.argv.slice(2);
39
- const fmtIndex = argv.findIndex((a) => a === '-f' || a === '--format');
40
- const fmt = fmtIndex !== -1 ? argv[fmtIndex + 1]?.toLowerCase() : undefined;
41
- const isMachineOutput = fmt === 'json' || fmt === 'yaml' || fmt === 'yml';
42
- const isTty = Boolean(process.stdout.isTTY);
43
- if (notifier.update && !isMachineOutput && isTty) {
44
- const message = `Update available: ${chalk.dim(notifier.update.current)} → ${chalk.green(notifier.update.latest)}\nRun ${chalk.cyan(`bun add -g ${pkg.name}`)} to update`;
45
- process.stderr.write(boxen(message, {
46
- padding: 1,
47
- margin: 1,
48
- align: 'center',
49
- borderColor: 'yellow',
50
- borderStyle: 'round',
51
- }) + '\n');
52
- }
53
- }
54
- catch {
55
- // Update check is best-effort; never block CLI startup on a network failure.
56
- }
57
- const program = new Command();
58
- program
59
- .name('bigconsole')
60
- .description('BigConsole CLI — dashboards, widgets, parsers, and workflows from the terminal')
61
- .version(pkg.version, '-v, --version')
62
- .option('-f, --format <format>', 'Output format (json, table, yaml)')
63
- .option('--verbose', 'Enable verbose logging')
64
- .option('--no-color', 'Disable colored output');
65
- program.hook('preAction', (_program, actionCommand) => inheritGlobalFormat(actionCommand));
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));
66
2919
  registerAuthCommands(program);
67
2920
  registerCLIConfigCommands(program);
68
2921
  registerWorkspaceCommands(program);
@@ -76,54 +2929,57 @@ registerDrilldownCommands(program);
76
2929
  registerShareCommands(program);
77
2930
  registerTemplateCommands(program);
78
2931
  registerPromptCommands(program);
79
- program.addHelpText('after', `
80
- ${chalk.bold('Examples:')}
81
- ${chalk.dim('# Authentication')}
82
- $ bigconsole auth login ${chalk.dim('# OAuth2 Device Code login')}
83
- $ bigconsole auth status ${chalk.dim('# Show auth state')}
84
-
85
- ${chalk.dim('# Workspace selection')}
86
- $ bigconsole workspace list ${chalk.dim('# List accessible workspaces')}
87
- $ bigconsole workspace set <id> ${chalk.dim('# Switch active workspace')}
88
-
89
- ${chalk.dim('# Core domain')}
90
- $ bigconsole dashboard list --search sales ${chalk.dim('# List dashboards')}
91
- $ bigconsole dashboard get <id> ${chalk.dim('# Inspect a dashboard')}
92
- $ bigconsole widget list --page <pageId> ${chalk.dim('# Widgets on a page')}
93
- $ bigconsole datasink list ${chalk.dim('# List data sinks')}
94
- $ bigconsole datasink data --key orders --force-refresh ${chalk.dim('# Fetch live payload')}
95
- $ bigconsole parser list ${chalk.dim('# List parsers')}
96
- $ bigconsole pipeline list ${chalk.dim('# List pipelines')}
97
- $ bigconsole workflow list ${chalk.dim('# List workflows')}
98
-
99
- ${chalk.dim('# CLI config')}
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")}
100
2953
  $ bigconsole cli-config show
101
2954
  $ bigconsole cli-config set workspaceEndpoint https://graphqlworkspaces.burdenoff.com/workspaces/graphql
102
2955
 
103
- ${chalk.bold('Command groups:')}
104
- ${chalk.cyan('auth')} login, logout, status, token, refresh
105
- ${chalk.cyan('workspace')} list, current, set
106
- ${chalk.cyan('dashboard')} list, get, create, update, archive, delete, import, export
107
- ${chalk.cyan('widget')} list, get, create, update, delete
108
- ${chalk.cyan('datasink')} list, get, data, create, update, delete
109
- ${chalk.cyan('parser')} list, get, create, update, delete
110
- ${chalk.cyan('pipeline')} list, get, create, update, delete
111
- ${chalk.cyan('workflow')} list, get
112
- ${chalk.cyan('drilldown')} list, delete
113
- ${chalk.cyan('share')} list
114
- ${chalk.cyan('template')} list, get
115
- ${chalk.cyan('prompt')} generate, list, get, cancel
116
- ${chalk.cyan('cli-config')} show, get, set, path, reset
117
-
118
- ${chalk.bold('Configuration:')} ${chalk.dim('~/.config/bigconsole-cli/')}
119
- ${chalk.bold('Docs:')} ${chalk.blue('https://docs.bigconsole.com/cli')}
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")}
120
2976
  `);
121
- program.on('command:*', () => {
122
- console.error(chalk.red(`Invalid command: ${sanitizeForTerminal(program.args.join(' '))}`));
123
- console.error(chalk.dim('See --help for a list of available commands.'));
124
- process.exit(1);
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);
125
2981
  });
126
2982
  program.parse();
127
2983
  if (!process.argv.slice(2).length) {
128
- program.outputHelp();
2984
+ program.outputHelp();
129
2985
  }