@burdenoff/bigconsole-cli 2026.802.1 → 2026.1029.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -11
- package/dist/auth/device-code.js +20 -15
- package/dist/cli.js +5 -2
- package/dist/commands/auth.js +6 -5
- package/dist/commands/base.d.ts +11 -0
- package/dist/commands/base.js +22 -2
- package/dist/commands/dashboard.js +7 -4
- package/dist/commands/drilldown.js +9 -1
- package/dist/commands/prompt.js +1 -1
- package/dist/graphql/client.d.ts +12 -0
- package/dist/graphql/client.js +15 -35
- package/dist/graphql/operations.d.ts +13 -12
- package/dist/graphql/operations.js +44 -18
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/output.d.ts +29 -7
- package/dist/utils/output.js +113 -62
- package/dist/utils/sanitize.d.ts +79 -0
- package/dist/utils/sanitize.js +347 -0
- package/package.json +39 -32
package/README.md
CHANGED
|
@@ -19,12 +19,6 @@ A powerful command-line interface for managing bigconsole operations with GraphQ
|
|
|
19
19
|
npm install -g @burdenoff/bigconsole-cli
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
### Global Installation (Alpha/Pre-release)
|
|
23
|
-
|
|
24
|
-
```bash
|
|
25
|
-
npm install -g @burdenoff/alpha-bigconsole-cli
|
|
26
|
-
```
|
|
27
|
-
|
|
28
22
|
### Local Installation
|
|
29
23
|
|
|
30
24
|
```bash
|
|
@@ -138,9 +132,25 @@ All commands support different output formats:
|
|
|
138
132
|
```bash
|
|
139
133
|
bigconsole users list --format json
|
|
140
134
|
bigconsole users list --format table
|
|
141
|
-
bigconsole users list --format yaml
|
|
135
|
+
bigconsole users list --format yaml # `yml` is accepted as an alias
|
|
142
136
|
```
|
|
143
137
|
|
|
138
|
+
- **stdout carries only command data.** Status lines (`✓`, `ℹ`, `!`, `✗`) are
|
|
139
|
+
written to stderr, so `--format json|yaml` output can be piped straight into
|
|
140
|
+
`jq` / `yq`.
|
|
141
|
+
- **JSON** is standard `JSON.stringify` output; C1 control characters, line and
|
|
142
|
+
paragraph separators and bidirectional formatting characters are written as
|
|
143
|
+
`\uXXXX` escapes (the parsed data is unchanged).
|
|
144
|
+
- **YAML** is produced by js-yaml with the YAML 1.2 JSON schema. Strings that a
|
|
145
|
+
YAML 1.1 or 1.2 parser could read as another type (`yes`, `~`, `0x1F`,
|
|
146
|
+
`2026-01-01`, ...) are quoted, so the output parses back to the same data.
|
|
147
|
+
- **Tables** are for people: control sequences are removed, line breaks and tabs
|
|
148
|
+
are shown as spaces, and bidirectional formatting characters are shown as
|
|
149
|
+
`\uXXXX`.
|
|
150
|
+
- **Errors** are a single bounded line. Failed requests report the HTTP status,
|
|
151
|
+
the server's first GraphQL error and its code; request variables, headers and
|
|
152
|
+
response bodies are never printed, and credential-shaped values are masked.
|
|
153
|
+
|
|
144
154
|
## Global Options
|
|
145
155
|
|
|
146
156
|
- `--endpoint <endpoint>`: Override the API endpoint for a single command
|
|
@@ -199,12 +209,30 @@ The CLI stores configuration in `~/.config/bigconsole-cli/config.json`. This inc
|
|
|
199
209
|
- `accessToken`: Authentication token
|
|
200
210
|
- `refreshToken`: Refresh token for authentication
|
|
201
211
|
|
|
202
|
-
##
|
|
212
|
+
## Releasing
|
|
213
|
+
|
|
214
|
+
`main` is the only branch. The **Publish Production** workflow
|
|
215
|
+
(`.github/workflows/publish-production.yml`) publishes it to npmjs:
|
|
216
|
+
|
|
217
|
+
1. Bump `version` in `package.json` (CalVer `YYYY.MDD.PATCH`, e.g. `2026.1029.1`)
|
|
218
|
+
and add a `CHANGELOG.md` entry.
|
|
219
|
+
2. Push to `main`. The `build` job lints, type-checks, tests, builds and packs
|
|
220
|
+
the CLI; the `publish` job (environment `Production`, org secret `NPM_TOKEN`)
|
|
221
|
+
publishes the tarball with dist-tags `latest` and `dev`.
|
|
222
|
+
3. A version that is already on the registry is skipped, so a push without a
|
|
223
|
+
version bump publishes nothing. The workflow can be re-run from `main`
|
|
224
|
+
(workflow_dispatch).
|
|
225
|
+
|
|
226
|
+
The former `@burdenoff/alpha-bigconsole-cli` channel and its `alpha` branch are
|
|
227
|
+
retired.
|
|
203
228
|
|
|
204
|
-
|
|
229
|
+
### Schema contract
|
|
205
230
|
|
|
206
|
-
|
|
207
|
-
|
|
231
|
+
`tests/unit/graphql/schema-contract.test.ts` validates every workspace operation
|
|
232
|
+
in `src/graphql/operations.ts` against `graphql/bigconsole-subgraph.graphql`,
|
|
233
|
+
the SDL that wspace-bigconsole-svc publishes to Hive. When the service schema
|
|
234
|
+
changes, run `bun run schema:pull` (needs `gh` with read access to the service
|
|
235
|
+
repo) and fix any operation the test rejects.
|
|
208
236
|
|
|
209
237
|
## Testing
|
|
210
238
|
|
package/dist/auth/device-code.js
CHANGED
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
* Used for CLI authentication (similar to `gh auth login`).
|
|
4
4
|
*/
|
|
5
5
|
import fetch from 'cross-fetch';
|
|
6
|
+
import { MAX_HTTP_BODY_EXCERPT_LENGTH, summarizeHttpBody, toDisplayLine, } from '../utils/sanitize.js';
|
|
7
|
+
/** Bounded, printable form of a server-supplied error message. */
|
|
8
|
+
function serverMessage(message) {
|
|
9
|
+
return toDisplayLine(message, MAX_HTTP_BODY_EXCERPT_LENGTH) || 'Unknown error';
|
|
10
|
+
}
|
|
11
|
+
/** Error for a non-2xx response; only a short, sanitized body excerpt is kept. */
|
|
12
|
+
async function httpFailure(prefix, response) {
|
|
13
|
+
const body = await response.text().catch(() => '');
|
|
14
|
+
return new Error(`${prefix} (${response.status}): ${summarizeHttpBody(body)}`);
|
|
15
|
+
}
|
|
6
16
|
/**
|
|
7
17
|
* Start the device code flow by requesting a device code from the OIDC issuer.
|
|
8
18
|
*/
|
|
@@ -18,8 +28,7 @@ export async function startDeviceCodeFlow(config) {
|
|
|
18
28
|
body: body.toString(),
|
|
19
29
|
});
|
|
20
30
|
if (!response.ok) {
|
|
21
|
-
|
|
22
|
-
throw new Error(`Device code request failed (${response.status}): ${text}`);
|
|
31
|
+
throw await httpFailure('Device code request failed', response);
|
|
23
32
|
}
|
|
24
33
|
const data = (await response.json());
|
|
25
34
|
return {
|
|
@@ -63,7 +72,7 @@ export async function pollForToken(deviceCode, interval, config) {
|
|
|
63
72
|
// Treat it like authorization_pending — keep polling.
|
|
64
73
|
continue;
|
|
65
74
|
}
|
|
66
|
-
throw new Error(`Token endpoint returned non-JSON response (status ${response.status}): ${raw
|
|
75
|
+
throw new Error(`Token endpoint returned non-JSON response (status ${response.status}): ${summarizeHttpBody(raw)}`);
|
|
67
76
|
}
|
|
68
77
|
if (response.ok && data['access_token']) {
|
|
69
78
|
return {
|
|
@@ -84,7 +93,7 @@ export async function pollForToken(deviceCode, interval, config) {
|
|
|
84
93
|
case 'access_denied':
|
|
85
94
|
throw new Error('Authorization was denied by the user.');
|
|
86
95
|
default:
|
|
87
|
-
throw new Error(`Device code polling failed: ${data['error_description'] ?? errorCode
|
|
96
|
+
throw new Error(`Device code polling failed: ${serverMessage(data['error_description'] ?? errorCode)}`);
|
|
88
97
|
}
|
|
89
98
|
}
|
|
90
99
|
}
|
|
@@ -109,12 +118,11 @@ export async function clientCredentialsFlow(globalEndpoint, clientId, clientSecr
|
|
|
109
118
|
}),
|
|
110
119
|
});
|
|
111
120
|
if (!response.ok) {
|
|
112
|
-
|
|
113
|
-
throw new Error(`App authentication failed (${response.status}): ${text}`);
|
|
121
|
+
throw await httpFailure('App authentication failed', response);
|
|
114
122
|
}
|
|
115
123
|
const data = (await response.json());
|
|
116
124
|
if (data.errors?.length) {
|
|
117
|
-
throw new Error(`App authentication failed: ${data.errors[0]
|
|
125
|
+
throw new Error(`App authentication failed: ${serverMessage(data.errors[0]?.message)}`);
|
|
118
126
|
}
|
|
119
127
|
if (!data.data?.authenticateApp?.accessToken) {
|
|
120
128
|
throw new Error('App authentication failed: no access token returned');
|
|
@@ -146,17 +154,15 @@ export async function issueWorkspaceToken(workspaceEndpoint, authToken, workspac
|
|
|
146
154
|
}),
|
|
147
155
|
});
|
|
148
156
|
if (!response.ok) {
|
|
149
|
-
|
|
150
|
-
throw new Error(`Workspace token request failed (${response.status}): ${text}`);
|
|
157
|
+
throw await httpFailure('Workspace token request failed', response);
|
|
151
158
|
}
|
|
152
159
|
const data = (await response.json());
|
|
153
160
|
if (data.errors?.length) {
|
|
154
|
-
throw new Error(`Workspace token failed: ${data.errors[0]
|
|
161
|
+
throw new Error(`Workspace token failed: ${serverMessage(data.errors[0]?.message)}`);
|
|
155
162
|
}
|
|
156
163
|
const result = data.data?.issueWorkspaceToken;
|
|
157
164
|
if (!result?.success || !result.token) {
|
|
158
|
-
|
|
159
|
-
throw new Error(`Workspace token failed: ${errMsg}`);
|
|
165
|
+
throw new Error(`Workspace token failed: ${serverMessage(result?.error?.message)}`);
|
|
160
166
|
}
|
|
161
167
|
return { token: result.token, expiresAt: result.expiresAt, scopes: result.scopes ?? [] };
|
|
162
168
|
}
|
|
@@ -180,12 +186,11 @@ export async function fetchWorkspaces(globalEndpoint, authToken) {
|
|
|
180
186
|
body: JSON.stringify({ query, variables: { limit: 50 } }),
|
|
181
187
|
});
|
|
182
188
|
if (!response.ok) {
|
|
183
|
-
|
|
184
|
-
throw new Error(`Failed to fetch workspaces (${response.status}): ${text}`);
|
|
189
|
+
throw await httpFailure('Failed to fetch workspaces', response);
|
|
185
190
|
}
|
|
186
191
|
const data = (await response.json());
|
|
187
192
|
if (data.errors?.length) {
|
|
188
|
-
throw new Error(`Failed to fetch workspaces: ${data.errors[0]
|
|
193
|
+
throw new Error(`Failed to fetch workspaces: ${serverMessage(data.errors[0]?.message)}`);
|
|
189
194
|
}
|
|
190
195
|
return data.data?.myWorkspaces?.items ?? [];
|
|
191
196
|
}
|
package/dist/cli.js
CHANGED
|
@@ -9,7 +9,9 @@ import updateNotifier from 'update-notifier';
|
|
|
9
9
|
import { readFileSync } from 'fs';
|
|
10
10
|
import { join, dirname } from 'path';
|
|
11
11
|
import { fileURLToPath } from 'url';
|
|
12
|
+
import { sanitizeForTerminal } from './utils/sanitize.js';
|
|
12
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';
|
|
13
15
|
const __filename = fileURLToPath(import.meta.url);
|
|
14
16
|
const __dirname = dirname(__filename);
|
|
15
17
|
let pkg = {
|
|
@@ -60,6 +62,7 @@ program
|
|
|
60
62
|
.option('-f, --format <format>', 'Output format (json, table, yaml)')
|
|
61
63
|
.option('--verbose', 'Enable verbose logging')
|
|
62
64
|
.option('--no-color', 'Disable colored output');
|
|
65
|
+
program.hook('preAction', (_program, actionCommand) => inheritGlobalFormat(actionCommand));
|
|
63
66
|
registerAuthCommands(program);
|
|
64
67
|
registerCLIConfigCommands(program);
|
|
65
68
|
registerWorkspaceCommands(program);
|
|
@@ -116,8 +119,8 @@ ${chalk.bold('Configuration:')} ${chalk.dim('~/.config/bigconsole-cli/')}
|
|
|
116
119
|
${chalk.bold('Docs:')} ${chalk.blue('https://docs.bigconsole.com/cli')}
|
|
117
120
|
`);
|
|
118
121
|
program.on('command:*', () => {
|
|
119
|
-
console.error(chalk.red(`Invalid command: ${program.args.join(' ')}`));
|
|
120
|
-
console.
|
|
122
|
+
console.error(chalk.red(`Invalid command: ${sanitizeForTerminal(program.args.join(' '))}`));
|
|
123
|
+
console.error(chalk.dim('See --help for a list of available commands.'));
|
|
121
124
|
process.exit(1);
|
|
122
125
|
});
|
|
123
126
|
program.parse();
|
package/dist/commands/auth.js
CHANGED
|
@@ -7,6 +7,7 @@ import { startDeviceCodeFlow, pollForToken, openBrowser, clientCredentialsFlow,
|
|
|
7
7
|
import { globalRequest, resetClients } from '../graphql/client.js';
|
|
8
8
|
import { SIGN_OUT, REFRESH_TOKEN } from '../graphql/operations.js';
|
|
9
9
|
import { printSuccess, printError, printInfo, printWarning, formatDate } from '../utils/output.js';
|
|
10
|
+
import { sanitizeForTerminal, toSafeErrorMessage } from '../utils/sanitize.js';
|
|
10
11
|
import { handleCommandError, formatAndOutput } from './base.js';
|
|
11
12
|
export function registerAuthCommands(program) {
|
|
12
13
|
const auth = program.command('auth').description('Authentication commands');
|
|
@@ -185,8 +186,8 @@ async function loginWithDeviceCode() {
|
|
|
185
186
|
printInfo('Starting device code authentication...');
|
|
186
187
|
const dc = await startDeviceCodeFlow(oidcConfig);
|
|
187
188
|
console.log('');
|
|
188
|
-
console.log(` Open: ${dc.verificationUri}`);
|
|
189
|
-
console.log(` Code: ${dc.userCode}`);
|
|
189
|
+
console.log(` Open: ${sanitizeForTerminal(dc.verificationUri)}`);
|
|
190
|
+
console.log(` Code: ${sanitizeForTerminal(dc.userCode)}`);
|
|
190
191
|
console.log('');
|
|
191
192
|
const opened = await openBrowser(dc.verificationUriComplete ?? dc.verificationUri);
|
|
192
193
|
if (opened)
|
|
@@ -214,7 +215,7 @@ async function loginWithDeviceCode() {
|
|
|
214
215
|
let selected = workspaces[0];
|
|
215
216
|
if (workspaces.length > 1) {
|
|
216
217
|
console.log('\nAvailable workspaces:');
|
|
217
|
-
workspaces.forEach((ws, i) => console.log(` ${i + 1}. ${ws.name} (${ws.slug})`));
|
|
218
|
+
workspaces.forEach((ws, i) => console.log(` ${i + 1}. ${sanitizeForTerminal(ws.name)} (${sanitizeForTerminal(ws.slug)})`));
|
|
218
219
|
const readline = await import('readline');
|
|
219
220
|
// Re-prompt up to 3 times on invalid input rather than silently
|
|
220
221
|
// defaulting to workspaces[0] — users who mistype expect to see
|
|
@@ -265,7 +266,7 @@ async function loginWithDeviceCode() {
|
|
|
265
266
|
});
|
|
266
267
|
resetClients();
|
|
267
268
|
printSuccess('Authentication successful');
|
|
268
|
-
printWarning(`Workspace token not obtained: ${
|
|
269
|
+
printWarning(`Workspace token not obtained: ${toSafeErrorMessage(wsError)}`);
|
|
269
270
|
printInfo('Run `bigconsole auth login` again once a workspace is available.');
|
|
270
271
|
}
|
|
271
272
|
}
|
|
@@ -331,7 +332,7 @@ async function loginWithClientCredentials(workspaceId) {
|
|
|
331
332
|
printInfo(`Workspace token issued for: ${ws.name}`);
|
|
332
333
|
}
|
|
333
334
|
catch (err) {
|
|
334
|
-
printError(`Workspace token issuance failed: ${
|
|
335
|
+
printError(`Workspace token issuance failed: ${toSafeErrorMessage(err)}`);
|
|
335
336
|
process.exit(1);
|
|
336
337
|
}
|
|
337
338
|
}
|
package/dist/commands/base.d.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Shared command utilities.
|
|
3
3
|
* Provides auth wrappers, format helpers, and error handling for all commands.
|
|
4
4
|
*/
|
|
5
|
+
import type { Command } from 'commander';
|
|
5
6
|
import type { RequestDocument, Variables } from 'graphql-request';
|
|
6
7
|
/**
|
|
7
8
|
* Load a JSON-valued command input from either --file or --json, mutually
|
|
@@ -33,8 +34,18 @@ export declare function execWorkspace<T>(document: RequestDocument, variables?:
|
|
|
33
34
|
export declare function execGlobal<T>(document: RequestDocument, variables?: Variables): Promise<T>;
|
|
34
35
|
/**
|
|
35
36
|
* Handle command errors consistently, printing user-friendly messages.
|
|
37
|
+
* The message is reduced to a single safe line (see `toSafeErrorMessage`):
|
|
38
|
+
* request variables, headers and raw response bodies are never printed.
|
|
36
39
|
*/
|
|
37
40
|
export declare function handleCommandError(error: unknown): never;
|
|
41
|
+
/**
|
|
42
|
+
* Commander recognises program-level options anywhere on the command line, so
|
|
43
|
+
* `-f/--format` written after a subcommand is stored on the program rather
|
|
44
|
+
* than on the subcommand. Copy it to the command about to run when that
|
|
45
|
+
* command declares its own format option and did not receive a value.
|
|
46
|
+
* Registered as a `preAction` hook in the CLI entry point.
|
|
47
|
+
*/
|
|
48
|
+
export declare function inheritGlobalFormat(actionCommand: Command): void;
|
|
38
49
|
/**
|
|
39
50
|
* Get format and output data for a command.
|
|
40
51
|
* Standard pattern used by all list/get commands.
|
package/dist/commands/base.js
CHANGED
|
@@ -6,6 +6,7 @@ import { readFileSync } from 'fs';
|
|
|
6
6
|
import { readAuth } from '../auth/store.js';
|
|
7
7
|
import { workspaceRequest, globalRequest } from '../graphql/client.js';
|
|
8
8
|
import { printError, getFormat, outputData } from '../utils/output.js';
|
|
9
|
+
import { toSafeErrorMessage } from '../utils/sanitize.js';
|
|
9
10
|
/**
|
|
10
11
|
* Load a JSON-valued command input from either --file or --json, mutually
|
|
11
12
|
* exclusive. Returns undefined if neither is provided. Exits with a clear
|
|
@@ -84,12 +85,31 @@ export async function execGlobal(document, variables) {
|
|
|
84
85
|
}
|
|
85
86
|
/**
|
|
86
87
|
* Handle command errors consistently, printing user-friendly messages.
|
|
88
|
+
* The message is reduced to a single safe line (see `toSafeErrorMessage`):
|
|
89
|
+
* request variables, headers and raw response bodies are never printed.
|
|
87
90
|
*/
|
|
88
91
|
export function handleCommandError(error) {
|
|
89
|
-
|
|
90
|
-
printError(message);
|
|
92
|
+
printError(toSafeErrorMessage(error));
|
|
91
93
|
process.exit(1);
|
|
92
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Commander recognises program-level options anywhere on the command line, so
|
|
97
|
+
* `-f/--format` written after a subcommand is stored on the program rather
|
|
98
|
+
* than on the subcommand. Copy it to the command about to run when that
|
|
99
|
+
* command declares its own format option and did not receive a value.
|
|
100
|
+
* Registered as a `preAction` hook in the CLI entry point.
|
|
101
|
+
*/
|
|
102
|
+
export function inheritGlobalFormat(actionCommand) {
|
|
103
|
+
const declaresFormat = actionCommand.options.some((o) => o.attributeName() === 'format');
|
|
104
|
+
if (!declaresFormat)
|
|
105
|
+
return;
|
|
106
|
+
const current = actionCommand.getOptionValue('format');
|
|
107
|
+
if (current !== undefined)
|
|
108
|
+
return;
|
|
109
|
+
const { format } = actionCommand.optsWithGlobals();
|
|
110
|
+
if (format !== undefined)
|
|
111
|
+
actionCommand.setOptionValue('format', format);
|
|
112
|
+
}
|
|
93
113
|
/**
|
|
94
114
|
* Get format and output data for a command.
|
|
95
115
|
* Standard pattern used by all list/get commands.
|
|
@@ -5,6 +5,7 @@ import { readFileSync, writeFileSync } from 'fs';
|
|
|
5
5
|
import * as ops from '../graphql/operations.js';
|
|
6
6
|
import { execWorkspace, formatAndOutput, handleCommandError, loadJsonInput } from './base.js';
|
|
7
7
|
import { printSuccess, printError, formatDate, formatStatus, truncate } from '../utils/output.js';
|
|
8
|
+
import { sanitizeForTerminal } from '../utils/sanitize.js';
|
|
8
9
|
export function registerDashboardCommands(program) {
|
|
9
10
|
const dash = program.command('dashboard').description('Dashboard operations');
|
|
10
11
|
dash
|
|
@@ -187,7 +188,6 @@ export function registerDashboardCommands(program) {
|
|
|
187
188
|
.description('Export a dashboard definition as JSON (stdout or --out file)')
|
|
188
189
|
.argument('<id>')
|
|
189
190
|
.option('--out <path>', 'Write JSON to file instead of stdout')
|
|
190
|
-
.option('--include-comments', 'Include dashboard comments')
|
|
191
191
|
.option('--include-data-refs', 'Include data source references', true)
|
|
192
192
|
.option('--include-webhook-mappings', 'Include webhook mappings')
|
|
193
193
|
.option('--pretty', 'Pretty-print JSON', true)
|
|
@@ -200,7 +200,6 @@ export function registerDashboardCommands(program) {
|
|
|
200
200
|
};
|
|
201
201
|
const d = await execWorkspace(ops.EXPORT_DASHBOARD, {
|
|
202
202
|
id,
|
|
203
|
-
includeComments: o.includeComments ?? false,
|
|
204
203
|
options,
|
|
205
204
|
});
|
|
206
205
|
if (o.out) {
|
|
@@ -208,8 +207,12 @@ export function registerDashboardCommands(program) {
|
|
|
208
207
|
printSuccess(`Exported to ${o.out}`);
|
|
209
208
|
}
|
|
210
209
|
else {
|
|
211
|
-
|
|
212
|
-
|
|
210
|
+
// Byte-exact when piped or redirected; made inert for a terminal.
|
|
211
|
+
const body = process.stdout.isTTY
|
|
212
|
+
? sanitizeForTerminal(d.exportDashboard, { preserveNewlines: true })
|
|
213
|
+
: d.exportDashboard;
|
|
214
|
+
process.stdout.write(body);
|
|
215
|
+
if (!body.endsWith('\n'))
|
|
213
216
|
process.stdout.write('\n');
|
|
214
217
|
}
|
|
215
218
|
}
|
|
@@ -37,7 +37,15 @@ export function registerDrilldownCommands(program) {
|
|
|
37
37
|
.requiredOption('--widget <id>', 'Widget ID')
|
|
38
38
|
.action(async (o) => {
|
|
39
39
|
try {
|
|
40
|
-
|
|
40
|
+
// deleteWidgetDrilldown takes the drilldown's own id; resolve it from the widget.
|
|
41
|
+
const lookup = await execWorkspace(ops.GET_WIDGET_DRILLDOWN, {
|
|
42
|
+
id: o.widget,
|
|
43
|
+
});
|
|
44
|
+
const drilldownId = lookup.getWidget?.drilldown?.id;
|
|
45
|
+
if (!drilldownId) {
|
|
46
|
+
throw new Error(`Widget ${o.widget} has no drilldown configuration`);
|
|
47
|
+
}
|
|
48
|
+
await execWorkspace(ops.DELETE_WIDGET_DRILLDOWN, { id: drilldownId });
|
|
41
49
|
printSuccess(`Removed drilldown from widget ${o.widget}`);
|
|
42
50
|
}
|
|
43
51
|
catch (e) {
|
package/dist/commands/prompt.js
CHANGED
|
@@ -74,7 +74,7 @@ export function registerPromptCommands(program) {
|
|
|
74
74
|
.argument('<id>')
|
|
75
75
|
.action(async (id) => {
|
|
76
76
|
try {
|
|
77
|
-
const d = await execWorkspace(ops.CANCEL_PROMPT_GENERATION, { id });
|
|
77
|
+
const d = await execWorkspace(ops.CANCEL_PROMPT_GENERATION, { generationId: id });
|
|
78
78
|
printSuccess(`Cancelled generation ${id} (status: ${d.cancelPromptGeneration.status})`);
|
|
79
79
|
}
|
|
80
80
|
catch (e) {
|
package/dist/graphql/client.d.ts
CHANGED
|
@@ -4,6 +4,18 @@
|
|
|
4
4
|
* Auto-injects auth headers, auto-refreshes both platform and workspace tokens.
|
|
5
5
|
*/
|
|
6
6
|
import { type RequestDocument, type Variables } from 'graphql-request';
|
|
7
|
+
import { type SafeErrorDescription } from '../utils/sanitize.js';
|
|
8
|
+
/**
|
|
9
|
+
* Error thrown for a failed GraphQL request. Its message is built only from
|
|
10
|
+
* the HTTP status, the server's first GraphQL error and its error code, so it
|
|
11
|
+
* is safe to print. The underlying client error is deliberately not attached
|
|
12
|
+
* (no `cause`): it carries the request variables and the raw response body.
|
|
13
|
+
*/
|
|
14
|
+
export declare class BigConsoleRequestError extends Error {
|
|
15
|
+
readonly status: number | undefined;
|
|
16
|
+
readonly code: string | undefined;
|
|
17
|
+
constructor(description: SafeErrorDescription);
|
|
18
|
+
}
|
|
7
19
|
export declare function ensureFreshTokens(): Promise<void>;
|
|
8
20
|
/**
|
|
9
21
|
* Execute a GraphQL request against the workspace gateway.
|
package/dist/graphql/client.js
CHANGED
|
@@ -8,6 +8,7 @@ import { readConfig } from '../config/index.js';
|
|
|
8
8
|
import { readAuth, writeAuth, isTokenExpiringSoon, isWorkspaceTokenExpiringSoon, } from '../auth/store.js';
|
|
9
9
|
import { issueWorkspaceToken, clientCredentialsFlow } from '../auth/device-code.js';
|
|
10
10
|
import { printError } from '../utils/output.js';
|
|
11
|
+
import { describeRequestError, toSafeErrorMessage, } from '../utils/sanitize.js';
|
|
11
12
|
const DEFAULT_TIMEOUT = 30000;
|
|
12
13
|
const MAX_RETRIES = 3;
|
|
13
14
|
const INITIAL_RETRY_DELAY_MS = 1000;
|
|
@@ -25,39 +26,18 @@ function isNetworkOrServerError(error) {
|
|
|
25
26
|
return false;
|
|
26
27
|
}
|
|
27
28
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* usable string so callers can fall through to the next source.
|
|
29
|
+
* Error thrown for a failed GraphQL request. Its message is built only from
|
|
30
|
+
* the HTTP status, the server's first GraphQL error and its error code, so it
|
|
31
|
+
* is safe to print. The underlying client error is deliberately not attached
|
|
32
|
+
* (no `cause`): it carries the request variables and the raw response body.
|
|
33
33
|
*/
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const MAX = 500;
|
|
41
|
-
return firstLine.length > MAX ? `${firstLine.slice(0, MAX)}…` : firstLine;
|
|
42
|
-
}
|
|
43
|
-
function extractErrorMessage(error) {
|
|
44
|
-
if (error && typeof error === 'object' && 'response' in error) {
|
|
45
|
-
const resp = error;
|
|
46
|
-
const firstError = resp.response?.errors?.[0];
|
|
47
|
-
// Federated subgraph errors land under extensions.originalError.message;
|
|
48
|
-
// the top-level message is usually "Unexpected error." and useless.
|
|
49
|
-
const original = sanitizeBackendMessage(firstError?.extensions?.originalError?.message);
|
|
50
|
-
if (original)
|
|
51
|
-
return original;
|
|
52
|
-
const wrapper = sanitizeBackendMessage(firstError?.message);
|
|
53
|
-
if (wrapper)
|
|
54
|
-
return wrapper;
|
|
55
|
-
if (resp.message)
|
|
56
|
-
return resp.message;
|
|
34
|
+
export class BigConsoleRequestError extends Error {
|
|
35
|
+
constructor(description) {
|
|
36
|
+
super(description.message);
|
|
37
|
+
this.name = 'BigConsoleRequestError';
|
|
38
|
+
this.status = description.status;
|
|
39
|
+
this.code = description.code;
|
|
57
40
|
}
|
|
58
|
-
if (error instanceof Error)
|
|
59
|
-
return error.message;
|
|
60
|
-
return String(error);
|
|
61
41
|
}
|
|
62
42
|
let workspaceClient = null;
|
|
63
43
|
let globalClient = null;
|
|
@@ -109,7 +89,7 @@ async function refreshPlatformToken() {
|
|
|
109
89
|
globalClient = null;
|
|
110
90
|
}
|
|
111
91
|
catch (error) {
|
|
112
|
-
printError(`App token refresh failed: ${
|
|
92
|
+
printError(`App token refresh failed: ${toSafeErrorMessage(error)}`);
|
|
113
93
|
}
|
|
114
94
|
return;
|
|
115
95
|
}
|
|
@@ -143,7 +123,7 @@ async function refreshPlatformToken() {
|
|
|
143
123
|
globalClient = null;
|
|
144
124
|
}
|
|
145
125
|
catch (error) {
|
|
146
|
-
printError(`Token refresh failed: ${
|
|
126
|
+
printError(`Token refresh failed: ${toSafeErrorMessage(error)}`);
|
|
147
127
|
}
|
|
148
128
|
}
|
|
149
129
|
/**
|
|
@@ -164,7 +144,7 @@ async function refreshWorkspaceToken() {
|
|
|
164
144
|
workspaceClient = null;
|
|
165
145
|
}
|
|
166
146
|
catch (error) {
|
|
167
|
-
printError(`Workspace token refresh failed: ${
|
|
147
|
+
printError(`Workspace token refresh failed: ${toSafeErrorMessage(error)}`);
|
|
168
148
|
}
|
|
169
149
|
}
|
|
170
150
|
export async function ensureFreshTokens() {
|
|
@@ -231,7 +211,7 @@ async function executeWithRetry(client, document, variables) {
|
|
|
231
211
|
break;
|
|
232
212
|
}
|
|
233
213
|
}
|
|
234
|
-
throw new
|
|
214
|
+
throw new BigConsoleRequestError(describeRequestError(lastError));
|
|
235
215
|
}
|
|
236
216
|
/**
|
|
237
217
|
* Execute a GraphQL request against the workspace gateway.
|
|
@@ -6,17 +6,17 @@
|
|
|
6
6
|
export declare const SIGN_OUT = "\n mutation SignOut {\n signOut\n }\n";
|
|
7
7
|
export declare const REFRESH_TOKEN = "\n mutation RefreshToken($token: String!) {\n refreshToken(token: $token) {\n accessToken\n refreshToken\n expiresIn\n }\n }\n";
|
|
8
8
|
export declare const MY_WORKSPACES = "\n query MyWorkspaces($limit: Int) {\n myWorkspaces(limit: $limit) {\n items { id name slug tenantId organizationId }\n }\n }\n";
|
|
9
|
-
export declare const LIST_DASHBOARDS = "\n query ListDashboards(\n $first: Int\n $after: String\n $search: String\n $visibility:
|
|
9
|
+
export declare const LIST_DASHBOARDS = "\n query ListDashboards(\n $first: Int\n $after: String\n $search: String\n $visibility: BigConsoleDashboardVisibility\n $category: BigConsoleDashboardCategory\n $includeArchived: Boolean\n $sortBy: BigConsoleDashboardSortField\n $sortOrder: SortOrder\n ) {\n listDashboards(\n first: $first\n after: $after\n search: $search\n visibility: $visibility\n category: $category\n includeArchived: $includeArchived\n sortBy: $sortBy\n sortOrder: $sortOrder\n ) {\n edges {\n node {\n id\n name\n description\n status\n visibility\n category\n ownerId\n createdAt\n updatedAt\n }\n }\n pageInfo { hasNextPage endCursor }\n totalCount\n }\n }\n";
|
|
10
10
|
export declare const GET_DASHBOARD = "\n query GetDashboard($id: ID!) {\n getDashboard(id: $id) {\n id\n name\n description\n status\n visibility\n category\n ownerId\n definitionVersion\n createdAt\n updatedAt\n }\n }\n";
|
|
11
11
|
export declare const ARCHIVE_DASHBOARD = "\n mutation ArchiveDashboard($id: ID!) {\n archiveDashboard(id: $id) {\n id\n name\n status\n }\n }\n";
|
|
12
12
|
export declare const DELETE_DASHBOARD = "\n mutation DeleteDashboard($id: ID!) {\n deleteBigConsoleDashboard(id: $id)\n }\n";
|
|
13
|
-
export declare const LIST_WIDGETS = "\n query ListWidgets($pageId: ID!, $first: Int, $after: String, $type:
|
|
13
|
+
export declare const LIST_WIDGETS = "\n query ListWidgets($pageId: ID!, $first: Int, $after: String, $type: BigConsoleWidgetType) {\n listWidgets(pageId: $pageId, first: $first, after: $after, type: $type) {\n edges {\n node {\n id\n pageId\n dashboardId\n type\n title\n dataSinkId\n lastRefreshedAt\n createdAt\n updatedAt\n }\n }\n pageInfo { hasNextPage endCursor }\n totalCount\n }\n }\n";
|
|
14
14
|
export declare const GET_WIDGET = "\n query GetWidget($id: ID!) {\n getWidget(id: $id) {\n id\n pageId\n dashboardId\n type\n title\n config\n dataSinkId\n datasetId\n lastRefreshedAt\n cacheTTL\n createdAt\n updatedAt\n }\n }\n";
|
|
15
15
|
export declare const DELETE_WIDGET = "\n mutation DeleteWidget($id: ID!) {\n deleteBigConsoleWidget(id: $id)\n }\n";
|
|
16
16
|
export declare const LIST_DATASINKS = "\n query ListDataSinks(\n $first: Int\n $after: String\n $search: String\n $status: DataSinkStatus\n $setupState: DataSinkSetupState\n $isActive: Boolean\n ) {\n listDataSinks(\n first: $first\n after: $after\n search: $search\n status: $status\n setupState: $setupState\n isActive: $isActive\n ) {\n edges {\n node {\n id\n name\n key\n description\n status\n setupState\n isActive\n createdAt\n updatedAt\n }\n }\n pageInfo { hasNextPage endCursor }\n totalCount\n }\n }\n";
|
|
17
17
|
export declare const GET_DATASINK = "\n query GetDataSink($id: ID, $key: String) {\n getDataSink(id: $id, key: $key) {\n id\n name\n key\n description\n status\n setupState\n isActive\n createdAt\n updatedAt\n }\n }\n";
|
|
18
|
-
export declare const GET_DATASINK_DATA = "\n query GetDataSinkData($id: ID, $key: String, $dataPath: String, $forceRefresh: Boolean) {\n getDataSinkData(id: $id, key: $key, dataPath: $dataPath, forceRefresh: $forceRefresh)\n }\n";
|
|
19
|
-
export declare const DELETE_DATASINK = "\n mutation DeleteDataSink($id: ID!) {\n deleteDataSink(id: $id)\n }\n";
|
|
18
|
+
export declare const GET_DATASINK_DATA = "\n query GetDataSinkData($id: ID, $key: String, $dataPath: String, $forceRefresh: Boolean) {\n getDataSinkData(id: $id, key: $key, dataPath: $dataPath, forceRefresh: $forceRefresh) {\n raw\n extracted\n cached\n fetchedAt\n version\n totalCount\n hasMore\n limit\n offset\n }\n }\n";
|
|
19
|
+
export declare const DELETE_DATASINK = "\n mutation DeleteDataSink($id: ID!) {\n deleteDataSink(id: $id) {\n id\n name\n }\n }\n";
|
|
20
20
|
export declare const LIST_PARSERS = "\n query ListParsers($first: Int, $after: String, $filter: ParserFilterInput) {\n listParsers(first: $first, after: $after, filter: $filter) {\n edges {\n node {\n id\n name\n description\n type\n status\n inputSinkKey\n outputKey\n outputWidgetId\n ttl\n version\n createdAt\n updatedAt\n }\n }\n pageInfo { hasNextPage endCursor }\n totalCount\n }\n }\n";
|
|
21
21
|
export declare const GET_PARSER = "\n query GetParser($id: ID!) {\n parser(id: $id) {\n id\n name\n description\n type\n status\n expression\n inputSinkKey\n outputKey\n outputWidgetId\n ttl\n version\n createdAt\n updatedAt\n }\n }\n";
|
|
22
22
|
export declare const DELETE_PARSER = "\n mutation DeleteParser($id: ID!) {\n deleteParser(id: $id)\n }\n";
|
|
@@ -25,8 +25,8 @@ export declare const GET_PIPELINE = "\n query GetPipeline($id: ID!) {\n pipe
|
|
|
25
25
|
export declare const DELETE_PIPELINE = "\n mutation DeletePipeline($id: ID!) {\n deletePipeline(id: $id)\n }\n";
|
|
26
26
|
export declare const CREATE_DASHBOARD = "\n mutation CreateDashboard($input: BigConsoleCreateDashboardInput!) {\n createBigConsoleDashboard(input: $input) {\n id\n name\n status\n visibility\n category\n createdAt\n }\n }\n";
|
|
27
27
|
export declare const UPDATE_DASHBOARD = "\n mutation UpdateDashboard($input: BigConsoleUpdateDashboardInput!) {\n updateBigConsoleDashboard(input: $input) {\n id\n name\n status\n visibility\n category\n updatedAt\n }\n }\n";
|
|
28
|
-
export declare const IMPORT_DASHBOARD = "\n mutation ImportDashboard($input:
|
|
29
|
-
export declare const EXPORT_DASHBOARD = "\n query ExportDashboard($id: ID!, $
|
|
28
|
+
export declare const IMPORT_DASHBOARD = "\n mutation ImportDashboard($input: BigConsoleImportDashboardInput!) {\n importDashboard(input: $input) {\n id\n name\n status\n visibility\n category\n createdAt\n }\n }\n";
|
|
29
|
+
export declare const EXPORT_DASHBOARD = "\n query ExportDashboard($id: ID!, $options: BigConsoleExportDashboardOptions) {\n exportDashboard(id: $id, options: $options)\n }\n";
|
|
30
30
|
export declare const CREATE_WIDGET = "\n mutation CreateWidget($input: BigConsoleCreateWidgetInput!) {\n createBigConsoleWidget(input: $input) {\n id\n pageId\n type\n title\n createdAt\n }\n }\n";
|
|
31
31
|
export declare const UPDATE_WIDGET = "\n mutation UpdateWidget($input: BigConsoleUpdateWidgetInput!) {\n updateBigConsoleWidget(input: $input) {\n id\n pageId\n type\n title\n updatedAt\n }\n }\n";
|
|
32
32
|
export declare const CREATE_DATASINK = "\n mutation CreateDataSink($input: CreateDataSinkInput!) {\n createDataSink(input: $input) {\n id\n name\n key\n status\n setupState\n isActive\n createdAt\n }\n }\n";
|
|
@@ -35,14 +35,15 @@ export declare const CREATE_PARSER = "\n mutation CreateParser($input: CreatePa
|
|
|
35
35
|
export declare const UPDATE_PARSER = "\n mutation UpdateParser($id: ID!, $version: Int!, $input: UpdateParserInput!) {\n updateParser(id: $id, version: $version, input: $input) {\n id\n name\n type\n status\n version\n updatedAt\n }\n }\n";
|
|
36
36
|
export declare const CREATE_PIPELINE = "\n mutation CreatePipeline($input: CreatePipelineInput!) {\n createPipeline(input: $input) {\n id\n name\n status\n version\n createdAt\n }\n }\n";
|
|
37
37
|
export declare const UPDATE_PIPELINE = "\n mutation UpdatePipeline($id: ID!, $version: Int!, $input: UpdatePipelineInput!) {\n updatePipeline(id: $id, version: $version, input: $input) {\n id\n name\n status\n version\n updatedAt\n }\n }\n";
|
|
38
|
-
export declare const GET_WIDGET_ACTIONS = "\n query GetWidgetActions($widgetId: ID!) {\n getWidgetActions(widgetId: $widgetId) {\n id\n widgetId\n type\n label\n
|
|
39
|
-
export declare const DELETE_WIDGET_DRILLDOWN = "\n mutation DeleteWidgetDrilldown($
|
|
40
|
-
export declare const
|
|
41
|
-
export declare const
|
|
38
|
+
export declare const GET_WIDGET_ACTIONS = "\n query GetWidgetActions($widgetId: ID!) {\n getWidgetActions(widgetId: $widgetId) {\n id\n widgetId\n type\n name\n label\n order\n disabled\n hidden\n createdAt\n }\n }\n";
|
|
39
|
+
export declare const DELETE_WIDGET_DRILLDOWN = "\n mutation DeleteWidgetDrilldown($id: ID!) {\n deleteWidgetDrilldown(id: $id)\n }\n";
|
|
40
|
+
export declare const GET_WIDGET_DRILLDOWN = "\n query GetWidgetDrilldown($id: ID!) {\n getWidget(id: $id) {\n id\n drilldown {\n id\n }\n }\n }\n";
|
|
41
|
+
export declare const LIST_DASHBOARD_SHARES = "\n query ListDashboardShares(\n $dashboardId: ID!\n $first: Int\n $after: String\n $isActive: Boolean\n $permission: BigConsoleDashboardSharePermission\n $recipientType: BigConsoleDashboardShareRecipientType\n ) {\n listDashboardShares(\n dashboardId: $dashboardId\n first: $first\n after: $after\n isActive: $isActive\n permission: $permission\n recipientType: $recipientType\n ) {\n edges {\n node {\n id\n dashboardId\n permission\n isActive\n expiresAt\n createdAt\n }\n }\n pageInfo { hasNextPage endCursor }\n totalCount\n }\n }\n";
|
|
42
|
+
export declare const LIST_TEMPLATES = "\n query ListTemplates(\n $first: Int\n $after: String\n $category: BigConsoleDashboardCategory\n $search: String\n $isFeatured: Boolean\n ) {\n listTemplates(\n first: $first\n after: $after\n category: $category\n search: $search\n isFeatured: $isFeatured\n ) {\n edges {\n node {\n id\n name\n description\n category\n isFeatured\n status\n createdAt\n }\n }\n pageInfo { hasNextPage endCursor }\n totalCount\n }\n }\n";
|
|
42
43
|
export declare const GET_TEMPLATE = "\n query GetTemplate($id: ID!) {\n getTemplate(id: $id) {\n id\n name\n description\n category\n isFeatured\n status\n createdAt\n }\n }\n";
|
|
43
|
-
export declare const GENERATE_DASHBOARD_FROM_PROMPT = "\n mutation GenerateDashboardFromPrompt($input:
|
|
44
|
+
export declare const GENERATE_DASHBOARD_FROM_PROMPT = "\n mutation GenerateDashboardFromPrompt($input: BigConsoleGenerateDashboardFromPromptInput!) {\n generateDashboardFromPrompt(input: $input) {\n id\n status\n prompt\n createdAt\n }\n }\n";
|
|
44
45
|
export declare const LIST_PROMPT_GENERATIONS = "\n query ListPromptGenerations($first: Int, $after: String, $status: PromptGenerationStatus) {\n listPromptGenerations(first: $first, after: $after, status: $status) {\n edges {\n node {\n id\n status\n prompt\n createdAt\n }\n }\n pageInfo { hasNextPage endCursor }\n totalCount\n }\n }\n";
|
|
45
46
|
export declare const GET_PROMPT_GENERATION = "\n query GetPromptGeneration($id: ID!) {\n getPromptGeneration(id: $id) {\n id\n status\n prompt\n createdAt\n }\n }\n";
|
|
46
|
-
export declare const CANCEL_PROMPT_GENERATION = "\n mutation CancelPromptGeneration($
|
|
47
|
+
export declare const CANCEL_PROMPT_GENERATION = "\n mutation CancelPromptGeneration($generationId: ID!) {\n cancelPromptGeneration(generationId: $generationId) {\n id\n status\n }\n }\n";
|
|
47
48
|
export declare const LIST_WORKFLOWS = "\n query ListWorkflows($filter: FluidGridsWorkflowFilterInput) {\n workflows(filter: $filter) {\n id\n name\n key\n description\n status\n executionCount\n linkedDataSinkCount\n lastExecutedAt\n createdAt\n updatedAt\n }\n }\n";
|
|
48
49
|
export declare const GET_WORKFLOW = "\n query GetWorkflow($id: ID!) {\n workflow(id: $id) {\n id\n name\n key\n description\n status\n executionCount\n linkedDataSinkCount\n externalWorkflowId\n lastExecutionId\n lastExecutedAt\n webhookUrl\n createdAt\n updatedAt\n }\n }\n";
|