@burdenoff/bigconsole-cli 2026.802.1 → 2026.1110.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -184
- package/dist/auth/store.d.ts +0 -4
- package/dist/cli.js +2959 -100
- package/dist/commands/base.d.ts +11 -4
- package/dist/graphql/client.d.ts +12 -0
- package/dist/graphql/operations.d.ts +14 -13
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2895 -6
- package/dist/utils/output.d.ts +29 -7
- package/dist/utils/parse.d.ts +5 -0
- package/dist/utils/sanitize.d.ts +79 -0
- package/package.json +59 -51
- package/dist/auth/device-code.js +0 -239
- package/dist/auth/store.js +0 -90
- package/dist/commands/auth.js +0 -352
- package/dist/commands/base.js +0 -100
- package/dist/commands/cli-config.js +0 -79
- package/dist/commands/dashboard.js +0 -220
- package/dist/commands/datasink.js +0 -175
- package/dist/commands/drilldown.js +0 -47
- package/dist/commands/index.js +0 -13
- package/dist/commands/parser.js +0 -181
- package/dist/commands/pipeline.js +0 -142
- package/dist/commands/prompt.js +0 -84
- package/dist/commands/share.js +0 -50
- package/dist/commands/template.js +0 -62
- package/dist/commands/widget.js +0 -164
- package/dist/commands/workflow.js +0 -54
- package/dist/commands/workspace.js +0 -95
- package/dist/config/index.js +0 -151
- package/dist/graphql/client.js +0 -274
- package/dist/graphql/operations.js +0 -654
- package/dist/types/index.d.ts +0 -30
- package/dist/types/index.js +0 -7
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/index.js +0 -1
- package/dist/utils/output.js +0 -186
package/dist/utils/output.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Output utilities for CLI display.
|
|
3
3
|
* Provides consistent formatting for both interactive and non-interactive modes.
|
|
4
|
+
*
|
|
5
|
+
* stdout carries only command data (JSON, YAML, tables); status lines
|
|
6
|
+
* (success, info, warning, error) go to stderr so piping stays clean. Remote
|
|
7
|
+
* strings are made inert before they reach the terminal: tables and status
|
|
8
|
+
* lines pass through `sanitizeForTerminal`, while JSON and YAML stay exact
|
|
9
|
+
* data and only use escape forms their own syntax defines.
|
|
4
10
|
*/
|
|
5
11
|
export type OutputFormat = 'json' | 'table' | 'yaml';
|
|
6
12
|
/**
|
|
@@ -13,32 +19,47 @@ export declare function isInteractive(): boolean;
|
|
|
13
19
|
export declare function getFormat(options: {
|
|
14
20
|
format?: string;
|
|
15
21
|
}): OutputFormat;
|
|
22
|
+
/**
|
|
23
|
+
* Serialize data as pretty-printed JSON. Characters in
|
|
24
|
+
* {@link JSON_DISPLAY_ESCAPES} can only occur inside JSON strings, so writing
|
|
25
|
+
* them as `\uXXXX` escapes leaves the parsed value unchanged.
|
|
26
|
+
*/
|
|
27
|
+
export declare function toJson(data: unknown): string;
|
|
16
28
|
/**
|
|
17
29
|
* Print data as formatted JSON.
|
|
18
30
|
*/
|
|
19
31
|
export declare function printJson(data: unknown): void;
|
|
20
32
|
/**
|
|
21
|
-
* Print data as a simple ASCII table.
|
|
33
|
+
* Print data as a simple ASCII table. Headers and cells are sanitized for the
|
|
34
|
+
* terminal (single line, no control sequences) before widths are measured;
|
|
35
|
+
* columns are padded to at most {@link MAX_COLUMN_WIDTH} characters.
|
|
22
36
|
*/
|
|
23
37
|
export declare function printTable(headers: string[], rows: string[][]): void;
|
|
24
38
|
/**
|
|
25
|
-
*
|
|
39
|
+
* Serialize data as YAML with js-yaml, restricted to the JSON schema (no
|
|
40
|
+
* custom tags) and without anchors/aliases. Control characters are escaped by
|
|
41
|
+
* the dumper; bidirectional controls, which YAML treats as printable, are
|
|
42
|
+
* written as `\uXXXX` inside double-quoted scalars.
|
|
43
|
+
*/
|
|
44
|
+
export declare function toYaml(data: unknown): string;
|
|
45
|
+
/**
|
|
46
|
+
* Print data as YAML.
|
|
26
47
|
*/
|
|
27
48
|
export declare function printYaml(data: unknown): void;
|
|
28
49
|
/**
|
|
29
|
-
* Print a success message.
|
|
50
|
+
* Print a success message (stderr).
|
|
30
51
|
*/
|
|
31
52
|
export declare function printSuccess(message: string): void;
|
|
32
53
|
/**
|
|
33
|
-
* Print an error message to stderr.
|
|
54
|
+
* Print an error message to stderr. Credential-shaped values are masked.
|
|
34
55
|
*/
|
|
35
56
|
export declare function printError(message: string): void;
|
|
36
57
|
/**
|
|
37
|
-
* Print a warning message.
|
|
58
|
+
* Print a warning message to stderr. Credential-shaped values are masked.
|
|
38
59
|
*/
|
|
39
60
|
export declare function printWarning(message: string): void;
|
|
40
61
|
/**
|
|
41
|
-
* Print an info message.
|
|
62
|
+
* Print an info message (stderr).
|
|
42
63
|
*/
|
|
43
64
|
export declare function printInfo(message: string): void;
|
|
44
65
|
/**
|
|
@@ -57,6 +78,7 @@ export declare function outputData(format: OutputFormat, data: unknown, tableCon
|
|
|
57
78
|
rows: string[][];
|
|
58
79
|
}): void;
|
|
59
80
|
/**
|
|
60
|
-
*
|
|
81
|
+
* Sanitize a string for the terminal, then truncate it to a maximum length,
|
|
82
|
+
* appending an ellipsis if needed.
|
|
61
83
|
*/
|
|
62
84
|
export declare function truncate(str: string | null | undefined, maxLen: number): string;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text hygiene for everything the CLI prints.
|
|
3
|
+
*
|
|
4
|
+
* Remote data (resource names, server error messages, HTTP response bodies)
|
|
5
|
+
* ends up in terminals, CI logs and support captures. The helpers here:
|
|
6
|
+
* - keep terminal output inert: escape sequences and control characters are
|
|
7
|
+
* removed, and bidirectional formatting characters are made visible;
|
|
8
|
+
* - mask credential-shaped values (bearer tokens, JWTs, URL credentials and
|
|
9
|
+
* values stored under secret-like keys);
|
|
10
|
+
* - turn any thrown value into a short, single-line message that is built
|
|
11
|
+
* from an allowlist of fields and never includes request variables,
|
|
12
|
+
* request headers or raw response bodies.
|
|
13
|
+
*
|
|
14
|
+
* The module is side-effect free and imports nothing from the rest of the CLI,
|
|
15
|
+
* so every layer can use it (and unit-test module mocks never replace it).
|
|
16
|
+
*/
|
|
17
|
+
/** Longest error message shown to the user. */
|
|
18
|
+
export declare const MAX_ERROR_MESSAGE_LENGTH = 500;
|
|
19
|
+
/** Longest excerpt of an HTTP response body or server message kept in an error. */
|
|
20
|
+
export declare const MAX_HTTP_BODY_EXCERPT_LENGTH = 200;
|
|
21
|
+
export interface TerminalSanitizeOptions {
|
|
22
|
+
/**
|
|
23
|
+
* Keep line structure: CRLF and CR become LF, LF and TAB are kept, and
|
|
24
|
+
* NEL/LS/PS are shown as `\uXXXX`. By default (single-line mode) every line
|
|
25
|
+
* break and TAB becomes a single space.
|
|
26
|
+
*/
|
|
27
|
+
preserveNewlines?: boolean;
|
|
28
|
+
}
|
|
29
|
+
/** `\uXXXX` spelling of a single UTF-16 code unit (valid in JSON and YAML). */
|
|
30
|
+
export declare function toUnicodeEscape(char: string): string;
|
|
31
|
+
/** Make bidirectional formatting controls visible as `\uXXXX` text. */
|
|
32
|
+
export declare function escapeBidiControls(text: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* Make a value safe to print on a terminal.
|
|
35
|
+
*
|
|
36
|
+
* - ANSI/VT escape sequences (CSI, OSC, DCS, SOS, PM, APC and two-character
|
|
37
|
+
* ESC sequences, in 7-bit and 8-bit form) are removed entirely.
|
|
38
|
+
* - Other C0 controls, DEL and C1 controls are removed.
|
|
39
|
+
* - Line breaks and TAB become a space (see {@link TerminalSanitizeOptions}).
|
|
40
|
+
* - Bidirectional formatting controls are shown as `\uXXXX` text so the
|
|
41
|
+
* printed text reads in its stored order.
|
|
42
|
+
*
|
|
43
|
+
* `null`/`undefined` become an empty string. The function is idempotent.
|
|
44
|
+
*/
|
|
45
|
+
export declare function sanitizeForTerminal(value: unknown, options?: TerminalSanitizeOptions): string;
|
|
46
|
+
/**
|
|
47
|
+
* Mask credential-shaped values in free text: values under secret-like keys
|
|
48
|
+
* (`password`, `token`, `refreshToken`, `clientSecret`, `Authorization`, ...),
|
|
49
|
+
* `Bearer` credentials, JWT-shaped strings and credentials embedded in URLs.
|
|
50
|
+
*/
|
|
51
|
+
export declare function redactSecrets(text: string): string;
|
|
52
|
+
/**
|
|
53
|
+
* Reduce untrusted text to one printable, credential-masked line of at most
|
|
54
|
+
* `maxLength` characters. Returns an empty string when nothing is left.
|
|
55
|
+
*/
|
|
56
|
+
export declare function toDisplayLine(value: unknown, maxLength?: number): string;
|
|
57
|
+
/** Short, printable excerpt of an HTTP response body for an error message. */
|
|
58
|
+
export declare function summarizeHttpBody(body: unknown): string;
|
|
59
|
+
/** Safe, user-facing description of a failed request. */
|
|
60
|
+
export interface SafeErrorDescription {
|
|
61
|
+
message: string;
|
|
62
|
+
/** HTTP status of the response, when there was one. */
|
|
63
|
+
status?: number;
|
|
64
|
+
/** Stable GraphQL error code (`extensions.code`), when the server sent one. */
|
|
65
|
+
code?: string;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Describe a failure of a GraphQL request for display. Network-level
|
|
69
|
+
* `TypeError`s (fetch failures) are reported as network errors; aborted
|
|
70
|
+
* requests as timeouts.
|
|
71
|
+
*/
|
|
72
|
+
export declare function describeRequestError(error: unknown): SafeErrorDescription;
|
|
73
|
+
/**
|
|
74
|
+
* Convert any thrown value into a message that is safe to print: a single
|
|
75
|
+
* line, at most {@link MAX_ERROR_MESSAGE_LENGTH} characters, free of terminal
|
|
76
|
+
* control sequences and with credential-shaped values masked. Response-bearing
|
|
77
|
+
* errors are described from an allowlist of fields only.
|
|
78
|
+
*/
|
|
79
|
+
export declare function toSafeErrorMessage(error: unknown): string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@burdenoff/bigconsole-cli",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.1110.1",
|
|
4
4
|
"description": "Command-line interface for BigConsole platform operations and management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -14,26 +14,23 @@
|
|
|
14
14
|
"README.md"
|
|
15
15
|
],
|
|
16
16
|
"scripts": {
|
|
17
|
-
"build": "
|
|
18
|
-
"
|
|
19
|
-
"dev": "tsx watch src/cli.ts",
|
|
17
|
+
"build": "bun build src/cli.ts src/index.ts --outdir dist --target node --packages external --root src && tsc -p tsconfig.build.json",
|
|
18
|
+
"dev": "bun --watch src/cli.ts",
|
|
20
19
|
"pretest": "bun run build",
|
|
21
|
-
"test": "
|
|
22
|
-
"test:coverage": "
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"format:check": "prettier --check \"src/**/*.{ts,js,json,md}\"",
|
|
29
|
-
"type-check": "bash scripts/build-limits.sh tsc --noEmit",
|
|
30
|
-
"sanity": "bash scripts/build-limits.sh --gate bash -c 'bun run format && bun run lint:sanity && bun run type-check && bun run test && bun run build'",
|
|
31
|
-
"clean": "rimraf dist",
|
|
20
|
+
"test": "bun test --isolate",
|
|
21
|
+
"test:coverage": "bun test --coverage",
|
|
22
|
+
"lint": "eslint .",
|
|
23
|
+
"format": "bun fmt",
|
|
24
|
+
"format:check": "bun fmt --check",
|
|
25
|
+
"sanity": "boff sanity",
|
|
26
|
+
"clean": "rm -rf dist coverage junit.xml",
|
|
32
27
|
"prepublishOnly": "bun run clean && bun run build",
|
|
33
|
-
"audit": "
|
|
28
|
+
"audit": "bun audit --audit-level=high",
|
|
34
29
|
"start": "bun dist/cli.js",
|
|
35
|
-
"
|
|
36
|
-
"
|
|
30
|
+
"schema:pull": "bash scripts/schema-pull.sh",
|
|
31
|
+
"type:check": "boff gate -- bun check --no-pretty",
|
|
32
|
+
"sanity:force": "boff sanity --force",
|
|
33
|
+
"lint:fix": "eslint . --fix"
|
|
37
34
|
},
|
|
38
35
|
"keywords": [
|
|
39
36
|
"algoshred",
|
|
@@ -56,45 +53,31 @@
|
|
|
56
53
|
},
|
|
57
54
|
"homepage": "https://github.com/Algoshred/bigconsole-cli#readme",
|
|
58
55
|
"publishConfig": {
|
|
59
|
-
"access": "
|
|
56
|
+
"access": "public"
|
|
60
57
|
},
|
|
61
58
|
"dependencies": {
|
|
62
59
|
"boxen": "^8.0.1",
|
|
63
|
-
"chalk": "^
|
|
64
|
-
"commander": "^
|
|
60
|
+
"chalk": "^6.0.0",
|
|
61
|
+
"commander": "^15.0.0",
|
|
65
62
|
"cross-fetch": "^4.1.0",
|
|
66
|
-
"graphql": "^
|
|
67
|
-
"graphql-request": "^7.
|
|
68
|
-
"
|
|
69
|
-
"js-yaml": "^4.1.0",
|
|
70
|
-
"ora": "^8.2.0",
|
|
71
|
-
"table": "^6.9.0",
|
|
63
|
+
"graphql": "^17.0.2",
|
|
64
|
+
"graphql-request": "^7.4.0",
|
|
65
|
+
"js-yaml": "^5.4.1",
|
|
72
66
|
"update-notifier": "^7.3.1"
|
|
73
67
|
},
|
|
74
68
|
"devDependencies": {
|
|
75
|
-
"@
|
|
76
|
-
"@
|
|
77
|
-
"@types/
|
|
78
|
-
"@types/node": "^24.3.0",
|
|
69
|
+
"@burdenoff/toolchain": "2026.1107.14",
|
|
70
|
+
"@playwright/test": "^1.62.1",
|
|
71
|
+
"@types/bun": "^1.4.4",
|
|
79
72
|
"@types/update-notifier": "^6.0.8",
|
|
80
|
-
"
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
"
|
|
84
|
-
"eslint": "^9.33.0",
|
|
85
|
-
"eslint-config-prettier": "^10.1.8",
|
|
86
|
-
"eslint-plugin-prettier": "^5.5.4",
|
|
87
|
-
"globals": "^16.3.0",
|
|
88
|
-
"prettier": "^3.6.2",
|
|
89
|
-
"rimraf": "^6.0.1",
|
|
90
|
-
"tsc-alias": "^1.8.16",
|
|
91
|
-
"tsx": "^4.20.4",
|
|
92
|
-
"typescript": "^5.9.2",
|
|
93
|
-
"vitest": "^3.2.4"
|
|
73
|
+
"eslint": "^9.39.4",
|
|
74
|
+
"knip": "^6.44.1",
|
|
75
|
+
"typescript": "6.0.3",
|
|
76
|
+
"yaml": "^2.8.3"
|
|
94
77
|
},
|
|
95
78
|
"engines": {
|
|
96
79
|
"node": ">=24.11.0",
|
|
97
|
-
"bun": ">=1.
|
|
80
|
+
"bun": ">=1.4.4"
|
|
98
81
|
},
|
|
99
82
|
"overrides": {
|
|
100
83
|
"lodash": ">=4.17.21",
|
|
@@ -103,14 +86,39 @@
|
|
|
103
86
|
"fast-xml-parser": ">=4.5.5",
|
|
104
87
|
"@xmldom/xmldom": ">=0.8.12",
|
|
105
88
|
"swiper": ">=12.1.2",
|
|
106
|
-
"brace-expansion": "
|
|
89
|
+
"brace-expansion": "5.0.12",
|
|
107
90
|
"follow-redirects": ">=1.15.12",
|
|
108
91
|
"smol-toml": ">=1.6.1",
|
|
109
92
|
"axios": ">=1.15.0",
|
|
110
|
-
"flatted": "
|
|
111
|
-
"yaml": "
|
|
112
|
-
"picomatch": "
|
|
93
|
+
"flatted": "3.4.4",
|
|
94
|
+
"yaml": "$yaml",
|
|
95
|
+
"picomatch": "4.0.7",
|
|
113
96
|
"immutable": ">=3.8.3",
|
|
114
|
-
"minimatch": ">=3.1.3"
|
|
97
|
+
"minimatch": ">=3.1.3",
|
|
98
|
+
"nanoid": "6.0.1",
|
|
99
|
+
"postcss": "8.5.26",
|
|
100
|
+
"vite": "8.2.2",
|
|
101
|
+
"fast-uri": "4.1.3",
|
|
102
|
+
"esbuild": "0.28.2"
|
|
103
|
+
},
|
|
104
|
+
"packageManager": "bun@1.4.4",
|
|
105
|
+
"trustedDependencies": [],
|
|
106
|
+
"boff": {
|
|
107
|
+
"type": "cli",
|
|
108
|
+
"sanity": {
|
|
109
|
+
"steps": [
|
|
110
|
+
"format",
|
|
111
|
+
"lint",
|
|
112
|
+
"typecheck",
|
|
113
|
+
"build",
|
|
114
|
+
"test",
|
|
115
|
+
"knip"
|
|
116
|
+
]
|
|
117
|
+
},
|
|
118
|
+
"scripts": {
|
|
119
|
+
"unmanaged": [
|
|
120
|
+
"test"
|
|
121
|
+
]
|
|
122
|
+
}
|
|
115
123
|
}
|
|
116
124
|
}
|
package/dist/auth/device-code.js
DELETED
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OAuth2 Device Code flow implementation.
|
|
3
|
-
* Used for CLI authentication (similar to `gh auth login`).
|
|
4
|
-
*/
|
|
5
|
-
import fetch from 'cross-fetch';
|
|
6
|
-
/**
|
|
7
|
-
* Start the device code flow by requesting a device code from the OIDC issuer.
|
|
8
|
-
*/
|
|
9
|
-
export async function startDeviceCodeFlow(config) {
|
|
10
|
-
const url = `${config.oidcIssuer}/oidc/device/code`;
|
|
11
|
-
const body = new URLSearchParams({
|
|
12
|
-
client_id: config.clientId,
|
|
13
|
-
scope: 'openid profile email offline_access',
|
|
14
|
-
});
|
|
15
|
-
const response = await fetch(url, {
|
|
16
|
-
method: 'POST',
|
|
17
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
18
|
-
body: body.toString(),
|
|
19
|
-
});
|
|
20
|
-
if (!response.ok) {
|
|
21
|
-
const text = await response.text().catch(() => 'Unknown error');
|
|
22
|
-
throw new Error(`Device code request failed (${response.status}): ${text}`);
|
|
23
|
-
}
|
|
24
|
-
const data = (await response.json());
|
|
25
|
-
return {
|
|
26
|
-
deviceCode: data['device_code'],
|
|
27
|
-
userCode: data['user_code'],
|
|
28
|
-
verificationUri: data['verification_uri'],
|
|
29
|
-
verificationUriComplete: data['verification_uri_complete'] ?? null,
|
|
30
|
-
expiresIn: data['expires_in'],
|
|
31
|
-
interval: data['interval'] ?? 5,
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Poll the token endpoint until the user approves or the code expires.
|
|
36
|
-
*/
|
|
37
|
-
export async function pollForToken(deviceCode, interval, config) {
|
|
38
|
-
const url = `${config.oidcIssuer}/oauth/device/token`;
|
|
39
|
-
let pollInterval = interval;
|
|
40
|
-
while (true) {
|
|
41
|
-
await new Promise((resolve) => setTimeout(resolve, pollInterval * 1000));
|
|
42
|
-
const response = await fetch(url, {
|
|
43
|
-
method: 'POST',
|
|
44
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
45
|
-
body: new URLSearchParams({
|
|
46
|
-
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
|
|
47
|
-
device_code: deviceCode,
|
|
48
|
-
client_id: config.clientId,
|
|
49
|
-
}).toString(),
|
|
50
|
-
});
|
|
51
|
-
// Guard against non-JSON responses (e.g. a 502 HTML error page from a
|
|
52
|
-
// gateway during a restart). Parsing those as JSON throws SyntaxError,
|
|
53
|
-
// aborts the whole poll loop, and forces the user to re-run
|
|
54
|
-
// `bigconsole auth login` from scratch even though the transient error
|
|
55
|
-
// would have cleared on the next tick.
|
|
56
|
-
const raw = await response.text();
|
|
57
|
-
let data;
|
|
58
|
-
try {
|
|
59
|
-
data = JSON.parse(raw);
|
|
60
|
-
}
|
|
61
|
-
catch {
|
|
62
|
-
if (response.status >= 500) {
|
|
63
|
-
// Treat it like authorization_pending — keep polling.
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
throw new Error(`Token endpoint returned non-JSON response (status ${response.status}): ${raw.slice(0, 200)}`);
|
|
67
|
-
}
|
|
68
|
-
if (response.ok && data['access_token']) {
|
|
69
|
-
return {
|
|
70
|
-
accessToken: data['access_token'],
|
|
71
|
-
refreshToken: data['refresh_token'] ?? '',
|
|
72
|
-
expiresIn: data['expires_in'] ?? 3600,
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
const errorCode = data['error'];
|
|
76
|
-
switch (errorCode) {
|
|
77
|
-
case 'authorization_pending':
|
|
78
|
-
continue;
|
|
79
|
-
case 'slow_down':
|
|
80
|
-
pollInterval += 5;
|
|
81
|
-
continue;
|
|
82
|
-
case 'expired_token':
|
|
83
|
-
throw new Error('Device code has expired. Please try again.');
|
|
84
|
-
case 'access_denied':
|
|
85
|
-
throw new Error('Authorization was denied by the user.');
|
|
86
|
-
default:
|
|
87
|
-
throw new Error(`Device code polling failed: ${data['error_description'] ?? errorCode ?? 'Unknown error'}`);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Authenticate using client credentials (app authentication).
|
|
93
|
-
* Calls the AuthenticateApp mutation on the global gateway.
|
|
94
|
-
*/
|
|
95
|
-
export async function clientCredentialsFlow(globalEndpoint, clientId, clientSecret, scopes) {
|
|
96
|
-
const query = `
|
|
97
|
-
mutation AuthenticateApp($input: AuthenticateAppInput!) {
|
|
98
|
-
authenticateApp(input: $input) {
|
|
99
|
-
accessToken tokenType expiresIn scopes
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
`;
|
|
103
|
-
const response = await fetch(globalEndpoint, {
|
|
104
|
-
method: 'POST',
|
|
105
|
-
headers: { 'Content-Type': 'application/json' },
|
|
106
|
-
body: JSON.stringify({
|
|
107
|
-
query,
|
|
108
|
-
variables: { input: { clientId, clientSecret, scopes } },
|
|
109
|
-
}),
|
|
110
|
-
});
|
|
111
|
-
if (!response.ok) {
|
|
112
|
-
const text = await response.text().catch(() => 'Unknown error');
|
|
113
|
-
throw new Error(`App authentication failed (${response.status}): ${text}`);
|
|
114
|
-
}
|
|
115
|
-
const data = (await response.json());
|
|
116
|
-
if (data.errors?.length) {
|
|
117
|
-
throw new Error(`App authentication failed: ${data.errors[0].message}`);
|
|
118
|
-
}
|
|
119
|
-
if (!data.data?.authenticateApp?.accessToken) {
|
|
120
|
-
throw new Error('App authentication failed: no access token returned');
|
|
121
|
-
}
|
|
122
|
-
return data.data.authenticateApp;
|
|
123
|
-
}
|
|
124
|
-
/**
|
|
125
|
-
* Issue a workspace token by calling the workspace gateway with an auth token.
|
|
126
|
-
* Only sends Authorization header (no X-Workspace-Authorization).
|
|
127
|
-
*/
|
|
128
|
-
export async function issueWorkspaceToken(workspaceEndpoint, authToken, workspaceId, organizationId) {
|
|
129
|
-
const query = `
|
|
130
|
-
mutation IssueWorkspaceToken($input: IssueWorkspaceTokenInput!) {
|
|
131
|
-
issueWorkspaceToken(input: $input) {
|
|
132
|
-
success token expiresAt scopes
|
|
133
|
-
error { code message }
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
`;
|
|
137
|
-
const response = await fetch(workspaceEndpoint, {
|
|
138
|
-
method: 'POST',
|
|
139
|
-
headers: {
|
|
140
|
-
'Content-Type': 'application/json',
|
|
141
|
-
Authorization: `Bearer ${authToken}`,
|
|
142
|
-
},
|
|
143
|
-
body: JSON.stringify({
|
|
144
|
-
query,
|
|
145
|
-
variables: { input: { workspaceId, organizationId } },
|
|
146
|
-
}),
|
|
147
|
-
});
|
|
148
|
-
if (!response.ok) {
|
|
149
|
-
const text = await response.text().catch(() => 'Unknown error');
|
|
150
|
-
throw new Error(`Workspace token request failed (${response.status}): ${text}`);
|
|
151
|
-
}
|
|
152
|
-
const data = (await response.json());
|
|
153
|
-
if (data.errors?.length) {
|
|
154
|
-
throw new Error(`Workspace token failed: ${data.errors[0].message}`);
|
|
155
|
-
}
|
|
156
|
-
const result = data.data?.issueWorkspaceToken;
|
|
157
|
-
if (!result?.success || !result.token) {
|
|
158
|
-
const errMsg = result?.error?.message ?? 'Unknown error';
|
|
159
|
-
throw new Error(`Workspace token failed: ${errMsg}`);
|
|
160
|
-
}
|
|
161
|
-
return { token: result.token, expiresAt: result.expiresAt, scopes: result.scopes ?? [] };
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* Fetch user's workspaces from the global gateway.
|
|
165
|
-
*/
|
|
166
|
-
export async function fetchWorkspaces(globalEndpoint, authToken) {
|
|
167
|
-
const query = `
|
|
168
|
-
query MyWorkspaces($limit: Int) {
|
|
169
|
-
myWorkspaces(limit: $limit) {
|
|
170
|
-
items { id name slug tenantId organizationId }
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
`;
|
|
174
|
-
const response = await fetch(globalEndpoint, {
|
|
175
|
-
method: 'POST',
|
|
176
|
-
headers: {
|
|
177
|
-
'Content-Type': 'application/json',
|
|
178
|
-
Authorization: `Bearer ${authToken}`,
|
|
179
|
-
},
|
|
180
|
-
body: JSON.stringify({ query, variables: { limit: 50 } }),
|
|
181
|
-
});
|
|
182
|
-
if (!response.ok) {
|
|
183
|
-
const text = await response.text().catch(() => 'Unknown error');
|
|
184
|
-
throw new Error(`Failed to fetch workspaces (${response.status}): ${text}`);
|
|
185
|
-
}
|
|
186
|
-
const data = (await response.json());
|
|
187
|
-
if (data.errors?.length) {
|
|
188
|
-
throw new Error(`Failed to fetch workspaces: ${data.errors[0].message}`);
|
|
189
|
-
}
|
|
190
|
-
return data.data?.myWorkspaces?.items ?? [];
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* Validate that a string is a well-formed HTTP(S) URL.
|
|
194
|
-
* Prevents command injection by rejecting anything that isn't a valid URL.
|
|
195
|
-
*/
|
|
196
|
-
function isValidHttpUrl(urlString) {
|
|
197
|
-
try {
|
|
198
|
-
const parsed = new URL(urlString);
|
|
199
|
-
return parsed.protocol === 'http:' || parsed.protocol === 'https:';
|
|
200
|
-
}
|
|
201
|
-
catch {
|
|
202
|
-
return false;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
/**
|
|
206
|
-
* Attempt to open a URL in the user's default browser.
|
|
207
|
-
* Uses execFile instead of exec to avoid shell interpretation and command injection.
|
|
208
|
-
*/
|
|
209
|
-
export async function openBrowser(url) {
|
|
210
|
-
try {
|
|
211
|
-
if (!isValidHttpUrl(url)) {
|
|
212
|
-
return false;
|
|
213
|
-
}
|
|
214
|
-
const { execFile } = await import('child_process');
|
|
215
|
-
const platform = process.platform;
|
|
216
|
-
let command;
|
|
217
|
-
let args;
|
|
218
|
-
if (platform === 'darwin') {
|
|
219
|
-
command = 'open';
|
|
220
|
-
args = [url];
|
|
221
|
-
}
|
|
222
|
-
else if (platform === 'win32') {
|
|
223
|
-
command = 'cmd';
|
|
224
|
-
args = ['/c', 'start', '', url];
|
|
225
|
-
}
|
|
226
|
-
else {
|
|
227
|
-
command = 'xdg-open';
|
|
228
|
-
args = [url];
|
|
229
|
-
}
|
|
230
|
-
return new Promise((resolve) => {
|
|
231
|
-
execFile(command, args, (error) => {
|
|
232
|
-
resolve(!error);
|
|
233
|
-
});
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
catch {
|
|
237
|
-
return false;
|
|
238
|
-
}
|
|
239
|
-
}
|
package/dist/auth/store.js
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Auth token persistence.
|
|
3
|
-
* Stores tokens in ~/.config/bigconsole-cli/auth.json with restrictive permissions.
|
|
4
|
-
*/
|
|
5
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync, unlinkSync } from 'fs';
|
|
6
|
-
import { join } from 'path';
|
|
7
|
-
import { homedir } from 'os';
|
|
8
|
-
const CONFIG_DIR = join(homedir(), '.config', 'bigconsole-cli');
|
|
9
|
-
const AUTH_FILE = join(CONFIG_DIR, 'auth.json');
|
|
10
|
-
function ensureConfigDir() {
|
|
11
|
-
if (!existsSync(CONFIG_DIR)) {
|
|
12
|
-
mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Read stored authentication data.
|
|
17
|
-
* Returns null if no auth data exists.
|
|
18
|
-
*/
|
|
19
|
-
export function readAuth() {
|
|
20
|
-
try {
|
|
21
|
-
if (!existsSync(AUTH_FILE))
|
|
22
|
-
return null;
|
|
23
|
-
const raw = readFileSync(AUTH_FILE, 'utf8');
|
|
24
|
-
return JSON.parse(raw);
|
|
25
|
-
}
|
|
26
|
-
catch {
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Write authentication data to the store.
|
|
32
|
-
* File permissions are set to 0o600 (owner read/write only).
|
|
33
|
-
*/
|
|
34
|
-
export function writeAuth(auth) {
|
|
35
|
-
ensureConfigDir();
|
|
36
|
-
writeFileSync(AUTH_FILE, JSON.stringify(auth, null, 2), { encoding: 'utf8', mode: 0o600 });
|
|
37
|
-
try {
|
|
38
|
-
chmodSync(AUTH_FILE, 0o600);
|
|
39
|
-
}
|
|
40
|
-
catch {
|
|
41
|
-
// chmod may fail on some platforms (Windows), that's acceptable
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Clear stored authentication data.
|
|
46
|
-
*/
|
|
47
|
-
export function clearAuth() {
|
|
48
|
-
try {
|
|
49
|
-
if (existsSync(AUTH_FILE)) {
|
|
50
|
-
unlinkSync(AUTH_FILE);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
catch {
|
|
54
|
-
// Ignore errors on cleanup
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Check if the stored token has expired.
|
|
59
|
-
*/
|
|
60
|
-
export function isTokenExpired(auth) {
|
|
61
|
-
if (!auth.expiresAt)
|
|
62
|
-
return false;
|
|
63
|
-
return Date.now() >= auth.expiresAt;
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Check if the stored token is expiring within the next 5 minutes.
|
|
67
|
-
*/
|
|
68
|
-
export function isTokenExpiringSoon(auth) {
|
|
69
|
-
if (!auth.expiresAt)
|
|
70
|
-
return false;
|
|
71
|
-
const BUFFER_MS = 5 * 60 * 1000;
|
|
72
|
-
return Date.now() + BUFFER_MS >= auth.expiresAt;
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Check if the workspace token has expired.
|
|
76
|
-
*/
|
|
77
|
-
export function isWorkspaceTokenExpired(auth) {
|
|
78
|
-
if (!auth.workspaceTokenExpiresAt)
|
|
79
|
-
return false;
|
|
80
|
-
return Date.now() >= auth.workspaceTokenExpiresAt;
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Check if the workspace token is expiring within the next 5 minutes.
|
|
84
|
-
*/
|
|
85
|
-
export function isWorkspaceTokenExpiringSoon(auth) {
|
|
86
|
-
if (!auth.workspaceTokenExpiresAt)
|
|
87
|
-
return false;
|
|
88
|
-
const BUFFER_MS = 5 * 60 * 1000;
|
|
89
|
-
return Date.now() + BUFFER_MS >= auth.workspaceTokenExpiresAt;
|
|
90
|
-
}
|