@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.
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Parse an optimistic-lock `--version` value. Accepts whole numbers only:
3
+ * `parseInt` would silently turn "7junk" or "7.5" into 7 and send the wrong version.
4
+ */
5
+ export declare function parseVersion(raw: string): number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/bigconsole-cli",
3
- "version": "2026.1029.1",
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,27 +14,23 @@
14
14
  "README.md"
15
15
  ],
16
16
  "scripts": {
17
- "build": "tsc && tsc-alias",
18
- "build:watch": "tsc -w",
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": "bash scripts/build-limits.sh vitest run",
22
- "test:coverage": "vitest --coverage",
23
- "test:ui": "vitest --ui",
24
- "lint": "eslint src --fix",
25
- "lint:sanity": "if [ \"${CI:-}\" = \"true\" ]; then bun run lint; else bun run lint -- --cache --cache-location .eslintcache; fi",
26
- "lint:check": "eslint src",
27
- "format": "prettier --write \"src/**/*.{ts,js,json,md}\"",
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": "bunx better-npm-audit audit",
28
+ "audit": "bun audit --audit-level=high",
34
29
  "start": "bun dist/cli.js",
35
- "publish:alpha": "bun run build && bunx npm@latest publish --tag alpha",
36
- "publish:prod": "bun run build && bunx npm@latest publish",
37
- "schema:pull": "bash scripts/schema-pull.sh"
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"
38
34
  },
39
35
  "keywords": [
40
36
  "algoshred",
@@ -66,37 +62,22 @@
66
62
  "cross-fetch": "^4.1.0",
67
63
  "graphql": "^17.0.2",
68
64
  "graphql-request": "^7.4.0",
69
- "inquirer": "^14.1.0",
70
65
  "js-yaml": "^5.4.1",
71
- "ora": "^9.4.1",
72
- "table": "^6.9.0",
73
66
  "update-notifier": "^7.3.1"
74
67
  },
75
68
  "devDependencies": {
76
- "@eslint/js": "^10.0.1",
69
+ "@burdenoff/toolchain": "2026.1107.14",
77
70
  "@playwright/test": "^1.62.1",
78
- "@types/inquirer": "^9.0.10",
79
- "@types/node": "^26.3.0",
71
+ "@types/bun": "^1.4.4",
80
72
  "@types/update-notifier": "^6.0.8",
81
- "@typescript-eslint/eslint-plugin": "^8.68.0",
82
- "@typescript-eslint/parser": "^8.68.0",
83
- "@vitest/coverage-v8": "^4.1.11",
84
- "@vitest/ui": "^4.1.11",
85
- "eslint": "^10.9.1",
86
- "eslint-config-prettier": "^10.1.8",
87
- "eslint-plugin-prettier": "^5.5.6",
88
- "globals": "^17.11.0",
89
- "prettier": "^3.9.6",
90
- "rimraf": "^6.1.3",
91
- "tsc-alias": "^1.9.2",
92
- "tsx": "^4.23.12",
93
- "typescript": "^5.9.3",
94
- "vitest": "^4.1.11",
73
+ "eslint": "^9.39.4",
74
+ "knip": "^6.44.1",
75
+ "typescript": "6.0.3",
95
76
  "yaml": "^2.8.3"
96
77
  },
97
78
  "engines": {
98
79
  "node": ">=24.11.0",
99
- "bun": ">=1.3.10"
80
+ "bun": ">=1.4.4"
100
81
  },
101
82
  "overrides": {
102
83
  "lodash": ">=4.17.21",
@@ -105,7 +86,7 @@
105
86
  "fast-xml-parser": ">=4.5.5",
106
87
  "@xmldom/xmldom": ">=0.8.12",
107
88
  "swiper": ">=12.1.2",
108
- "brace-expansion": "5.0.9",
89
+ "brace-expansion": "5.0.12",
109
90
  "follow-redirects": ">=1.15.12",
110
91
  "smol-toml": ">=1.6.1",
111
92
  "axios": ">=1.15.0",
@@ -119,5 +100,25 @@
119
100
  "vite": "8.2.2",
120
101
  "fast-uri": "4.1.3",
121
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
+ }
122
123
  }
123
124
  }
@@ -1,244 +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
- 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
- }
16
- /**
17
- * Start the device code flow by requesting a device code from the OIDC issuer.
18
- */
19
- export async function startDeviceCodeFlow(config) {
20
- const url = `${config.oidcIssuer}/oidc/device/code`;
21
- const body = new URLSearchParams({
22
- client_id: config.clientId,
23
- scope: 'openid profile email offline_access',
24
- });
25
- const response = await fetch(url, {
26
- method: 'POST',
27
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
28
- body: body.toString(),
29
- });
30
- if (!response.ok) {
31
- throw await httpFailure('Device code request failed', response);
32
- }
33
- const data = (await response.json());
34
- return {
35
- deviceCode: data['device_code'],
36
- userCode: data['user_code'],
37
- verificationUri: data['verification_uri'],
38
- verificationUriComplete: data['verification_uri_complete'] ?? null,
39
- expiresIn: data['expires_in'],
40
- interval: data['interval'] ?? 5,
41
- };
42
- }
43
- /**
44
- * Poll the token endpoint until the user approves or the code expires.
45
- */
46
- export async function pollForToken(deviceCode, interval, config) {
47
- const url = `${config.oidcIssuer}/oauth/device/token`;
48
- let pollInterval = interval;
49
- while (true) {
50
- await new Promise((resolve) => setTimeout(resolve, pollInterval * 1000));
51
- const response = await fetch(url, {
52
- method: 'POST',
53
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
54
- body: new URLSearchParams({
55
- grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
56
- device_code: deviceCode,
57
- client_id: config.clientId,
58
- }).toString(),
59
- });
60
- // Guard against non-JSON responses (e.g. a 502 HTML error page from a
61
- // gateway during a restart). Parsing those as JSON throws SyntaxError,
62
- // aborts the whole poll loop, and forces the user to re-run
63
- // `bigconsole auth login` from scratch even though the transient error
64
- // would have cleared on the next tick.
65
- const raw = await response.text();
66
- let data;
67
- try {
68
- data = JSON.parse(raw);
69
- }
70
- catch {
71
- if (response.status >= 500) {
72
- // Treat it like authorization_pending — keep polling.
73
- continue;
74
- }
75
- throw new Error(`Token endpoint returned non-JSON response (status ${response.status}): ${summarizeHttpBody(raw)}`);
76
- }
77
- if (response.ok && data['access_token']) {
78
- return {
79
- accessToken: data['access_token'],
80
- refreshToken: data['refresh_token'] ?? '',
81
- expiresIn: data['expires_in'] ?? 3600,
82
- };
83
- }
84
- const errorCode = data['error'];
85
- switch (errorCode) {
86
- case 'authorization_pending':
87
- continue;
88
- case 'slow_down':
89
- pollInterval += 5;
90
- continue;
91
- case 'expired_token':
92
- throw new Error('Device code has expired. Please try again.');
93
- case 'access_denied':
94
- throw new Error('Authorization was denied by the user.');
95
- default:
96
- throw new Error(`Device code polling failed: ${serverMessage(data['error_description'] ?? errorCode)}`);
97
- }
98
- }
99
- }
100
- /**
101
- * Authenticate using client credentials (app authentication).
102
- * Calls the AuthenticateApp mutation on the global gateway.
103
- */
104
- export async function clientCredentialsFlow(globalEndpoint, clientId, clientSecret, scopes) {
105
- const query = `
106
- mutation AuthenticateApp($input: AuthenticateAppInput!) {
107
- authenticateApp(input: $input) {
108
- accessToken tokenType expiresIn scopes
109
- }
110
- }
111
- `;
112
- const response = await fetch(globalEndpoint, {
113
- method: 'POST',
114
- headers: { 'Content-Type': 'application/json' },
115
- body: JSON.stringify({
116
- query,
117
- variables: { input: { clientId, clientSecret, scopes } },
118
- }),
119
- });
120
- if (!response.ok) {
121
- throw await httpFailure('App authentication failed', response);
122
- }
123
- const data = (await response.json());
124
- if (data.errors?.length) {
125
- throw new Error(`App authentication failed: ${serverMessage(data.errors[0]?.message)}`);
126
- }
127
- if (!data.data?.authenticateApp?.accessToken) {
128
- throw new Error('App authentication failed: no access token returned');
129
- }
130
- return data.data.authenticateApp;
131
- }
132
- /**
133
- * Issue a workspace token by calling the workspace gateway with an auth token.
134
- * Only sends Authorization header (no X-Workspace-Authorization).
135
- */
136
- export async function issueWorkspaceToken(workspaceEndpoint, authToken, workspaceId, organizationId) {
137
- const query = `
138
- mutation IssueWorkspaceToken($input: IssueWorkspaceTokenInput!) {
139
- issueWorkspaceToken(input: $input) {
140
- success token expiresAt scopes
141
- error { code message }
142
- }
143
- }
144
- `;
145
- const response = await fetch(workspaceEndpoint, {
146
- method: 'POST',
147
- headers: {
148
- 'Content-Type': 'application/json',
149
- Authorization: `Bearer ${authToken}`,
150
- },
151
- body: JSON.stringify({
152
- query,
153
- variables: { input: { workspaceId, organizationId } },
154
- }),
155
- });
156
- if (!response.ok) {
157
- throw await httpFailure('Workspace token request failed', response);
158
- }
159
- const data = (await response.json());
160
- if (data.errors?.length) {
161
- throw new Error(`Workspace token failed: ${serverMessage(data.errors[0]?.message)}`);
162
- }
163
- const result = data.data?.issueWorkspaceToken;
164
- if (!result?.success || !result.token) {
165
- throw new Error(`Workspace token failed: ${serverMessage(result?.error?.message)}`);
166
- }
167
- return { token: result.token, expiresAt: result.expiresAt, scopes: result.scopes ?? [] };
168
- }
169
- /**
170
- * Fetch user's workspaces from the global gateway.
171
- */
172
- export async function fetchWorkspaces(globalEndpoint, authToken) {
173
- const query = `
174
- query MyWorkspaces($limit: Int) {
175
- myWorkspaces(limit: $limit) {
176
- items { id name slug tenantId organizationId }
177
- }
178
- }
179
- `;
180
- const response = await fetch(globalEndpoint, {
181
- method: 'POST',
182
- headers: {
183
- 'Content-Type': 'application/json',
184
- Authorization: `Bearer ${authToken}`,
185
- },
186
- body: JSON.stringify({ query, variables: { limit: 50 } }),
187
- });
188
- if (!response.ok) {
189
- throw await httpFailure('Failed to fetch workspaces', response);
190
- }
191
- const data = (await response.json());
192
- if (data.errors?.length) {
193
- throw new Error(`Failed to fetch workspaces: ${serverMessage(data.errors[0]?.message)}`);
194
- }
195
- return data.data?.myWorkspaces?.items ?? [];
196
- }
197
- /**
198
- * Validate that a string is a well-formed HTTP(S) URL.
199
- * Prevents command injection by rejecting anything that isn't a valid URL.
200
- */
201
- function isValidHttpUrl(urlString) {
202
- try {
203
- const parsed = new URL(urlString);
204
- return parsed.protocol === 'http:' || parsed.protocol === 'https:';
205
- }
206
- catch {
207
- return false;
208
- }
209
- }
210
- /**
211
- * Attempt to open a URL in the user's default browser.
212
- * Uses execFile instead of exec to avoid shell interpretation and command injection.
213
- */
214
- export async function openBrowser(url) {
215
- try {
216
- if (!isValidHttpUrl(url)) {
217
- return false;
218
- }
219
- const { execFile } = await import('child_process');
220
- const platform = process.platform;
221
- let command;
222
- let args;
223
- if (platform === 'darwin') {
224
- command = 'open';
225
- args = [url];
226
- }
227
- else if (platform === 'win32') {
228
- command = 'cmd';
229
- args = ['/c', 'start', '', url];
230
- }
231
- else {
232
- command = 'xdg-open';
233
- args = [url];
234
- }
235
- return new Promise((resolve) => {
236
- execFile(command, args, (error) => {
237
- resolve(!error);
238
- });
239
- });
240
- }
241
- catch {
242
- return false;
243
- }
244
- }
@@ -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
- }