@vigilbase/cli 0.2.3 → 0.2.5
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/CLOUDFLARE.md +2 -2
- package/COMMANDS.md +111 -44
- package/LOCAL.md +99 -38
- package/ONE.md +2 -2
- package/README.md +215 -67
- package/dist/auth/acl.d.ts +6 -0
- package/dist/auth/acl.js +45 -0
- package/dist/auth/device-auth.js +1 -0
- package/dist/auth/provider.js +4 -4
- package/dist/auth/refresh.d.ts +5 -4
- package/dist/auth/refresh.js +82 -41
- package/dist/auth/store.d.ts +25 -0
- package/dist/auth/store.js +91 -11
- package/dist/bin.js +13 -1
- package/dist/cli.js +205 -81
- package/dist/commands/account.js +11 -7
- package/dist/commands/auth.d.ts +6 -0
- package/dist/commands/auth.js +75 -24
- package/dist/commands/billing.js +2 -2
- package/dist/commands/cloudflare.d.ts +1 -1
- package/dist/commands/cloudflare.js +31 -17
- package/dist/commands/helpers.js +3 -3
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.js +2 -1
- package/dist/commands/legacy-validation.d.ts +20 -0
- package/dist/commands/legacy-validation.js +77 -3
- package/dist/commands/local.js +186 -65
- package/dist/commands/one.js +16 -13
- package/dist/commands/public-tools.js +52 -27
- package/dist/commands/runs.js +5 -5
- package/dist/commands/security.d.ts +1 -1
- package/dist/commands/security.js +41 -37
- package/dist/commands/watchdog.js +5 -5
- package/dist/lib/banner.d.ts +5 -11
- package/dist/lib/banner.js +11 -31
- package/dist/lib/client.js +19 -1
- package/dist/lib/cloudflare-client.d.ts +2 -2
- package/dist/lib/config.d.ts +16 -0
- package/dist/lib/config.js +77 -19
- package/dist/lib/format.d.ts +1 -1
- package/dist/lib/format.js +75 -24
- package/dist/lib/help.js +57 -6
- package/dist/lib/network.d.ts +12 -3
- package/dist/lib/network.js +73 -9
- package/dist/lib/one-client.js +9 -2
- package/dist/lib/one-error-hints.d.ts +1 -0
- package/dist/lib/one-error-hints.js +10 -0
- package/dist/lib/open-url.js +2 -1
- package/dist/lib/platform.d.ts +2 -0
- package/dist/lib/platform.js +5 -0
- package/dist/lib/progress.d.ts +18 -3
- package/dist/lib/progress.js +72 -10
- package/dist/lib/system-trust.d.ts +2 -0
- package/dist/lib/system-trust.js +6 -0
- package/dist/lib/tui.d.ts +1 -1
- package/dist/lib/tui.js +5 -7
- package/dist/local/cloudflare-checkup/cf/child-env.js +2 -4
- package/dist/local/cloudflare-checkup/cf/install.d.ts +4 -2
- package/dist/local/cloudflare-checkup/cf/install.js +55 -24
- package/dist/local/cloudflare-checkup/cf/locate.d.ts +2 -0
- package/dist/local/cloudflare-checkup/cf/locate.js +10 -1
- package/dist/local/cloudflare-checkup/cf/run-cf.d.ts +18 -3
- package/dist/local/cloudflare-checkup/cf/run-cf.js +75 -17
- package/dist/local/cloudflare-checkup/contract.d.ts +16 -9
- package/dist/local/cloudflare-checkup/contract.js +80 -14
- package/dist/local/cloudflare-checkup/errors.d.ts +34 -1
- package/dist/local/cloudflare-checkup/errors.js +36 -5
- package/dist/local/cloudflare-checkup/output.d.ts +6 -0
- package/dist/local/cloudflare-checkup/output.js +9 -0
- package/dist/local/cloudflare-checkup/snapshot-file.d.ts +6 -2
- package/dist/local/cloudflare-checkup/snapshot-file.js +25 -11
- package/dist/local/cloudflare-checkup/submit-client.js +7 -1
- package/dist/types.d.ts +2 -1
- package/esm-only.cjs +3 -0
- package/package.json +7 -4
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { toolNameSchema, validateArguments } from './legacy-validation.js';
|
|
1
|
+
import { InvalidArgumentError, targetValue, toolNameValue, validateArguments } from './legacy-validation.js';
|
|
3
2
|
import { NetworkError } from '../lib/network.js';
|
|
4
3
|
import { PUBLIC_TOOLS_PATH, VigilbaseApiError } from '../lib/client.js';
|
|
5
4
|
import { getOneAccessToken, getOptionalOneAccessToken, hasUsableSignIn, readCredentials } from '../auth/index.js';
|
|
6
|
-
import { SignInUnsupportedError } from '../auth/store.js';
|
|
5
|
+
import { CredentialsPathError, NotSignedInError, SignInUnsupportedError } from '../auth/store.js';
|
|
7
6
|
import { formatExecuteResult, formatToolDescription, formatToolsList, isSuccessfulScan, } from '../lib/format.js';
|
|
8
7
|
import { isOneAuthRequiredTool, isOneExecutableTool, ONE_AUTH_REQUIRED_TOOLS, resolveToolDefinition, VigilbaseOneClient, } from '../lib/one-client.js';
|
|
9
8
|
import { EXIT, ok, outputJson, withSpinner, } from './helpers.js';
|
|
10
|
-
|
|
9
|
+
function unknownTool(toolName) {
|
|
10
|
+
return new InvalidArgumentError(`Unknown tool "${toolName}". Run \`vigilbase list\` for the allowlist.`);
|
|
11
|
+
}
|
|
11
12
|
/**
|
|
12
13
|
* For --via one and auth-required tools only: any sign-in problem fails. Specific errors keep their
|
|
13
14
|
* message; those that don't already point to `vigilbase login` get it as their hint.
|
|
@@ -18,14 +19,15 @@ async function requireOneSignIn(config) {
|
|
|
18
19
|
accessToken = await getOneAccessToken(config);
|
|
19
20
|
}
|
|
20
21
|
catch (error) {
|
|
21
|
-
// Windows cannot sign in at all, so pointing to
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
// Windows cannot sign in at all, and login stops on an unsafe credentials path too, so pointing to it would mislead.
|
|
23
|
+
const isLoginBlocked = error instanceof SignInUnsupportedError || error instanceof CredentialsPathError;
|
|
24
|
+
if (error instanceof Error && !isLoginBlocked && !error.message.includes('`vigilbase login`')) {
|
|
25
|
+
Object.assign(error, { hint: new NotSignedInError().message });
|
|
24
26
|
}
|
|
25
27
|
throw error;
|
|
26
28
|
}
|
|
27
29
|
if (!accessToken)
|
|
28
|
-
throw new
|
|
30
|
+
throw new NotSignedInError();
|
|
29
31
|
return accessToken;
|
|
30
32
|
}
|
|
31
33
|
/** Public tools work without login. Auth-required tools need Vigilbase One. */
|
|
@@ -39,6 +41,7 @@ export const handleList = async (ctx) => {
|
|
|
39
41
|
...catalog,
|
|
40
42
|
authenticated,
|
|
41
43
|
authRequiredTools: [...ONE_AUTH_REQUIRED_TOOLS],
|
|
44
|
+
// Signed out, nothing runs through One.
|
|
42
45
|
oneExecutableTools: authenticated
|
|
43
46
|
? [
|
|
44
47
|
...new Set([
|
|
@@ -46,7 +49,7 @@ export const handleList = async (ctx) => {
|
|
|
46
49
|
...ONE_AUTH_REQUIRED_TOOLS,
|
|
47
50
|
]),
|
|
48
51
|
]
|
|
49
|
-
:
|
|
52
|
+
: [],
|
|
50
53
|
});
|
|
51
54
|
}
|
|
52
55
|
else {
|
|
@@ -78,17 +81,23 @@ export const handleRun = async (ctx) => {
|
|
|
78
81
|
}
|
|
79
82
|
if (!toolName || !target)
|
|
80
83
|
throw new Error(`Usage: ${usage}`);
|
|
81
|
-
toolName =
|
|
82
|
-
target =
|
|
84
|
+
toolName = toolNameValue(toolName);
|
|
85
|
+
target = targetValue(target);
|
|
86
|
+
// Known tools are those One can run: the public ones and the One-only ones.
|
|
87
|
+
if (!isOneExecutableTool(toolName))
|
|
88
|
+
throw unknownTool(toolName);
|
|
83
89
|
const via = ctx.values.via;
|
|
84
90
|
if (ctx.values.isPublic && via === 'one') {
|
|
85
91
|
throw new Error('--public cannot be combined with --via one.');
|
|
86
92
|
}
|
|
87
|
-
if (via && via !== 'one' && via !== 'public') {
|
|
88
|
-
throw new
|
|
93
|
+
if (via !== undefined && via !== 'one' && via !== 'public') {
|
|
94
|
+
throw new InvalidArgumentError('--via must be one of: public, one.');
|
|
89
95
|
}
|
|
90
96
|
const forcePublic = Boolean(ctx.values.isPublic) || via === 'public';
|
|
91
97
|
const forceOne = via === 'one';
|
|
98
|
+
if (forcePublic && isOneAuthRequiredTool(toolName)) {
|
|
99
|
+
throw new Error(`${toolName} runs only through Vigilbase One: drop ${ctx.values.isPublic ? '--public' : '--via public'} and run \`vigilbase login\`.`);
|
|
100
|
+
}
|
|
92
101
|
// Public tools stay free unless forced onto One. Auth-required tools need login.
|
|
93
102
|
// The public route is anonymous and never reads the credential store.
|
|
94
103
|
const useOne = !forcePublic && (forceOne || isOneAuthRequiredTool(toolName));
|
|
@@ -123,15 +132,14 @@ export const handleRun = async (ctx) => {
|
|
|
123
132
|
};
|
|
124
133
|
export const handleDescribe = async (ctx) => {
|
|
125
134
|
validateArguments(ctx, 1, ['isNoSkill'], 'vigilbase describe <tool>');
|
|
126
|
-
|
|
135
|
+
toolNameValue(ctx.rest[0]);
|
|
127
136
|
const toolName = ctx.rest[0];
|
|
128
137
|
if (!toolName)
|
|
129
138
|
throw new Error('Usage: vigilbase describe <tool>');
|
|
130
139
|
const catalog = await withSpinner(ctx, 'Loading tool catalog', () => ctx.publicClient.listTools());
|
|
131
140
|
const tool = resolveToolDefinition(toolName, catalog.tools, ctx.config.oneBaseUrl);
|
|
132
|
-
if (!tool)
|
|
133
|
-
throw
|
|
134
|
-
}
|
|
141
|
+
if (!tool)
|
|
142
|
+
throw unknownTool(toolName);
|
|
135
143
|
let skillMarkdown;
|
|
136
144
|
if (!ctx.values.isNoSkill) {
|
|
137
145
|
try {
|
|
@@ -146,18 +154,18 @@ export const handleDescribe = async (ctx) => {
|
|
|
146
154
|
};
|
|
147
155
|
export const handleSkill = async (ctx) => {
|
|
148
156
|
validateArguments(ctx, 1, [], 'vigilbase skill <tool>');
|
|
149
|
-
|
|
157
|
+
toolNameValue(ctx.rest[0]);
|
|
150
158
|
const toolName = ctx.rest[0];
|
|
151
159
|
if (!toolName)
|
|
152
160
|
throw new Error('Usage: vigilbase skill <tool>');
|
|
153
161
|
const catalog = await withSpinner(ctx, 'Loading tool catalog', () => ctx.publicClient.listTools());
|
|
154
162
|
const definition = resolveToolDefinition(toolName, catalog.tools, ctx.config.oneBaseUrl);
|
|
155
|
-
if (!definition)
|
|
156
|
-
throw
|
|
157
|
-
}
|
|
163
|
+
if (!definition)
|
|
164
|
+
throw unknownTool(toolName);
|
|
158
165
|
const skillMarkdown = await withSpinner(ctx, 'Fetching skill docs', () => ctx.publicClient.fetchSkillMarkdown(definition.skillUrl));
|
|
159
166
|
if (ctx.mode === 'json') {
|
|
160
167
|
outputJson(ctx, {
|
|
168
|
+
success: true,
|
|
161
169
|
toolName: definition.toolName,
|
|
162
170
|
skillUrl: definition.skillUrl,
|
|
163
171
|
content: skillMarkdown,
|
|
@@ -168,18 +176,35 @@ export const handleSkill = async (ctx) => {
|
|
|
168
176
|
}
|
|
169
177
|
return ok();
|
|
170
178
|
};
|
|
179
|
+
/** The badge route's domain rule (apps/com/app/api/badge/[domain]/route.ts). */
|
|
180
|
+
const BADGE_DOMAIN = /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/;
|
|
181
|
+
/** The hostname the badge route accepts: a URL gives its hostname; case, a trailing dot and www. don't matter. */
|
|
182
|
+
function badgeDomain(value) {
|
|
183
|
+
let domain = value.trim();
|
|
184
|
+
try {
|
|
185
|
+
const url = new URL(domain);
|
|
186
|
+
if (url.protocol === 'https:' || url.protocol === 'http:')
|
|
187
|
+
domain = url.hostname;
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
// Not a URL: a bare domain.
|
|
191
|
+
}
|
|
192
|
+
domain = domain.toLowerCase().replace(/\.$/, '').replace(/^www\./, '');
|
|
193
|
+
if (domain.length > 253 || !BADGE_DOMAIN.test(domain)) {
|
|
194
|
+
throw new InvalidArgumentError('Enter a domain such as vigilbase.com. Usage: vigilbase badge <domain>');
|
|
195
|
+
}
|
|
196
|
+
return domain;
|
|
197
|
+
}
|
|
171
198
|
export const handleBadge = async (ctx) => {
|
|
172
199
|
validateArguments(ctx, 1, ['style'], 'vigilbase badge <domain>');
|
|
173
|
-
const domain = ctx.rest[0];
|
|
174
|
-
if (!domain)
|
|
175
|
-
throw new Error('Usage: vigilbase badge <domain>');
|
|
200
|
+
const domain = badgeDomain(ctx.rest[0] ?? '');
|
|
176
201
|
const styleRaw = ctx.values.style ?? 'default';
|
|
177
202
|
if (styleRaw !== 'default' && styleRaw !== 'compact' && styleRaw !== 'mini') {
|
|
178
|
-
throw new
|
|
203
|
+
throw new InvalidArgumentError('--style must be one of: default, compact, mini.');
|
|
179
204
|
}
|
|
180
205
|
const url = ctx.publicClient.badgeUrl(domain, styleRaw);
|
|
181
206
|
if (ctx.mode === 'json') {
|
|
182
|
-
outputJson(ctx, { domain, style: styleRaw, url });
|
|
207
|
+
outputJson(ctx, { success: true, domain, style: styleRaw, url });
|
|
183
208
|
}
|
|
184
209
|
else {
|
|
185
210
|
ctx.writer.stdout(url);
|
|
@@ -246,7 +271,7 @@ export const handleDoctor = async (ctx) => {
|
|
|
246
271
|
const payload = {
|
|
247
272
|
ok: isOk,
|
|
248
273
|
success: isOk,
|
|
249
|
-
...(firstFailure ? { error: firstFailure.error, status: firstFailure.status } : {}),
|
|
274
|
+
...(firstFailure ? { error: firstFailure.error, status: firstFailure.status, ...(firstFailure.code ? { code: firstFailure.code } : {}) } : {}),
|
|
250
275
|
publicBaseUrl: ctx.config.publicBaseUrl,
|
|
251
276
|
oneBaseUrl: ctx.config.oneBaseUrl,
|
|
252
277
|
clientId: ctx.config.clientId,
|
package/dist/commands/runs.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { limitValue,
|
|
2
|
+
import { limitValue, parseArgument, toolNameValue, unknownSubcommand, validateArguments } from './legacy-validation.js';
|
|
3
3
|
import { asArray, asRecord, ok, outputJson, requireOneClient, withSpinner, } from './helpers.js';
|
|
4
4
|
export const handleRuns = async (ctx) => {
|
|
5
5
|
const subcommand = (ctx.rest[0] ?? 'list').toLowerCase();
|
|
@@ -7,16 +7,16 @@ export const handleRuns = async (ctx) => {
|
|
|
7
7
|
if (subcommand === 'list' || subcommand === 'ls') {
|
|
8
8
|
validateArguments(ctx, ctx.rest[0] === undefined ? 0 : 1, ['tool', 'limit']);
|
|
9
9
|
limit = limitValue(ctx.values.limit, 50, 200);
|
|
10
|
-
toolName =
|
|
10
|
+
toolName = ctx.values.tool === undefined ? undefined : toolNameValue(ctx.values.tool);
|
|
11
11
|
}
|
|
12
12
|
else if (subcommand === 'get' || subcommand === 'show') {
|
|
13
13
|
validateArguments(ctx, 2, [], `vigilbase runs ${subcommand} <UUID>`);
|
|
14
|
-
z.string().uuid()
|
|
14
|
+
parseArgument(z.string().uuid(), ctx.rest[1], 'A valid UUID is required. Copy the full run ID from `vigilbase runs list`.');
|
|
15
15
|
}
|
|
16
16
|
else if (z.string().uuid().safeParse(subcommand).success)
|
|
17
17
|
validateArguments(ctx, 1);
|
|
18
18
|
else
|
|
19
|
-
throw
|
|
19
|
+
throw unknownSubcommand('runs', ctx.rest[0] ?? '');
|
|
20
20
|
const one = await requireOneClient(ctx);
|
|
21
21
|
if (subcommand === 'list' || subcommand === 'ls') {
|
|
22
22
|
const payload = await withSpinner(ctx, 'Loading tool runs', () => one.listToolRuns({
|
|
@@ -36,7 +36,7 @@ export const handleRuns = async (ctx) => {
|
|
|
36
36
|
const run = asRecord(raw);
|
|
37
37
|
if (!run)
|
|
38
38
|
continue;
|
|
39
|
-
ctx.writer.stdout(`${String(run.id)
|
|
39
|
+
ctx.writer.stdout(`${String(run.id)} ${String(run.tool_name).padEnd(18)} ${String(run.status).padEnd(10)} ${String(run.grade ?? '-')} ${String(run.target)}`);
|
|
40
40
|
}
|
|
41
41
|
return ok();
|
|
42
42
|
}
|
|
@@ -2,4 +2,4 @@ import { type SecurityRequest } from '../lib/security-client.js';
|
|
|
2
2
|
import { type CommandContext, type CommandHandler } from './helpers.js';
|
|
3
3
|
export declare function buildSecurityRequest(ctx: CommandContext): SecurityRequest;
|
|
4
4
|
export declare const handleSecurity: CommandHandler;
|
|
5
|
-
export declare const SECURITY_HELP = "vigilbase security \u2014 managed security in Vigilbase One\n\n [overview]\n incidents [list] --status --severity --attack-category\n --order-by --order-direction
|
|
5
|
+
export declare const SECURITY_HELP = "vigilbase security \u2014 managed security in Vigilbase One\n\n [overview]\n incidents [list] --status --severity --attack-category\n --order-by --order-direction\n --limit --offset\n incidents show <id>\n incidents timeline <id> --limit --before\n incidents fabric <id> --dataset --min-weight --limit\n incidents share-links <id>\n incidents update <id> <status> --confirm [--note]\n alerts [list] --limit --asset-id --start\n alerts show <id>\n logs --start --end --limit --cursor --sort\n --asset-id --provider --dataset\n --search --frs-min\n audit --limit --cursor\n --action-prefix --outcome\n assets\n assets <allowlist|blocklist|remediation|autonomy|datasets> <asset-id>\n assets graph <asset-id> --dataset --hours --min-weight --limit\n fabric default-asset\n detections --asset-id <asset-id>\n providers\n connections [list]\n connections show <id>\n connections <available-assets|remediation> <id>\n connections <logpush-status|worker-stats> <connection-id> <job-id>\n analytics [overview] --metrics + time-window flags\n analytics <security-analysis|bot-activity|top-attacker-ips>\n analytics <bot-asns|dns-security|page-shield>\n analytics provider-events --provider --dataset --start --end\n [--asset-id --search --limit]\n zero-trust <device-health|app-tests|access|sessions|device-posture>\n zero-trust gateway <dns|http|network>\n cloudflare-account [inventory]\n cloudflare-account <audit|ingestion|posture>\n notifications [list] --limit\n notifications <settings|preferences|slack-channels>\n reports [list] --limit --offset\n reports show <id>\n reports settings\n intel quota\n intel <ip|asn|cidr|ja3|ja4> <query>\n settings <lifecycle-emails|broadcast-emails|applications>\n\nTime-window flags: --hours (1\u2013720, default 24) or --start/--end ISO timestamps;\noptional --asset-id. Apply to analytics (except provider-events), zero-trust,\nand cloudflare-account audit/ingestion/posture. Inventory has no window flags.\nLimits: list incidents/reports/audit 1\u2013100 (default 50); alerts/notifications\n1\u2013100 (default 100); timeline 1\u2013200 (default 100); logs 1\u20131000 (default 100);\nprovider-events 1\u2013500 (default 200); graph 1\u20131000 (default 200).\nOffsets: 0\u20131000000. Graph min-weight: 1\u20131000000 (default 1).\nAll: --json, --timeout <ms>.\nInternal View-as: --customer-id <One customer UUID>.\nAll operations read data except confirmed incident status updates.\nUpdate status: acknowledged, mitigating, resolved, false_positive.\nSee SECURITY.md for endpoints, supported values, aliases and pagination.\n";
|
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { SecurityClient } from '../lib/security-client.js';
|
|
3
3
|
import { assertCommandFlags, asArray, asRecord, ok, outputJson, requireOneClient, withSpinner } from './helpers.js';
|
|
4
|
-
import { missingArguments } from './legacy-validation.js';
|
|
4
|
+
import { InvalidArgumentError, inRange, isoDateTime, missingArguments, oneOf, parseArgument, textValue, unknownSubcommand } from './legacy-validation.js';
|
|
5
5
|
const statuses = ['open', 'active', 'acknowledged', 'investigating', 'mitigating', 'mitigated', 'resolved', 'false_positive'];
|
|
6
6
|
const updateStatuses = ['acknowledged', 'mitigating', 'resolved', 'false_positive'];
|
|
7
7
|
function textFlag(ctx, flag) {
|
|
8
8
|
const value = ctx.values[flag];
|
|
9
|
-
|
|
10
|
-
return undefined;
|
|
11
|
-
return z.string().trim().min(1, `--${flag} must not be empty.`).max(2048).parse(value);
|
|
9
|
+
return value === undefined ? undefined : textValue(`--${flag}`, String(value), 2048);
|
|
12
10
|
}
|
|
13
11
|
function integer(ctx, flag, max, fallback) {
|
|
14
12
|
const value = textFlag(ctx, flag);
|
|
15
13
|
if (value === undefined)
|
|
16
14
|
return fallback;
|
|
17
15
|
if (!/^\d+$/.test(value))
|
|
18
|
-
throw new
|
|
19
|
-
return
|
|
16
|
+
throw new InvalidArgumentError(`--${flag} must be an integer.`);
|
|
17
|
+
return inRange(`--${flag}`, Number(value), ['limit', 'hours', 'min-weight'].includes(flag) ? 1 : 0, max);
|
|
20
18
|
}
|
|
21
19
|
function allowed(ctx, flags) {
|
|
22
20
|
assertCommandFlags(ctx, [...flags, 'customer-id']);
|
|
@@ -28,6 +26,10 @@ function arity(ctx, length, usage) {
|
|
|
28
26
|
if (ctx.rest.length > length)
|
|
29
27
|
throw new Error('Unexpected security arguments. Run `vigilbase security --help` for usage.');
|
|
30
28
|
}
|
|
29
|
+
/** The entry of a command table named by user input; never an inherited Object.prototype member. */
|
|
30
|
+
function own(table, name) {
|
|
31
|
+
return name !== undefined && Object.hasOwn(table, name) ? table[name] : undefined;
|
|
32
|
+
}
|
|
31
33
|
function uuid(value) {
|
|
32
34
|
const parsed = z.string().uuid().safeParse(value);
|
|
33
35
|
if (!parsed.success)
|
|
@@ -44,8 +46,8 @@ function extendedSecurityRequest(ctx, scope) {
|
|
|
44
46
|
if (Boolean(start) !== Boolean(end))
|
|
45
47
|
throw new Error('--start and --end must be supplied together as ISO timestamps.');
|
|
46
48
|
if (start && end) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
isoDateTime('--start', start);
|
|
50
|
+
isoDateTime('--end', end);
|
|
49
51
|
if (Date.parse(start) >= Date.parse(end))
|
|
50
52
|
throw new Error('--start must be before --end.');
|
|
51
53
|
if (ctx.values.hours !== undefined)
|
|
@@ -79,9 +81,9 @@ function extendedSecurityRequest(ctx, scope) {
|
|
|
79
81
|
throw new Error('--search must be at most 200 characters for provider events.');
|
|
80
82
|
return request('/analytics/provider-events', 'providerEvents', { provider_id: provider, dataset_id: dataset, start: query.start, end: query.end, asset_ids: query.asset_id, search, limit: integer(ctx, 'limit', 500, 200) });
|
|
81
83
|
}
|
|
82
|
-
const view = views
|
|
84
|
+
const view = own(views, operation);
|
|
83
85
|
if (!view)
|
|
84
|
-
throw
|
|
86
|
+
throw unknownSubcommand('security analytics', operation);
|
|
85
87
|
const query = window(operation === 'overview' ? ['metrics'] : []);
|
|
86
88
|
if (operation === 'overview') {
|
|
87
89
|
const metrics = textFlag(ctx, 'metrics') ?? 'hourly,by_domain,by_dataset';
|
|
@@ -94,26 +96,28 @@ function extendedSecurityRequest(ctx, scope) {
|
|
|
94
96
|
}
|
|
95
97
|
if (group === 'zero-trust') {
|
|
96
98
|
const views = { 'device-health': 'deviceHealth', 'app-tests': 'appTests', gateway: 'gateway', access: 'access', sessions: 'sessions', 'device-posture': 'devicePosture' };
|
|
97
|
-
|
|
99
|
+
const view = own(views, sub);
|
|
100
|
+
if (!sub || !view)
|
|
98
101
|
throw new Error('Choose a zero-trust command: device-health, app-tests, gateway, access, sessions, device-posture.');
|
|
99
102
|
arity(ctx, sub === 'gateway' ? 3 : 2, 'zero-trust gateway <dns|http|network>');
|
|
100
103
|
const query = window();
|
|
101
104
|
if (sub === 'gateway')
|
|
102
|
-
query.surface =
|
|
103
|
-
return request(`/zero-trust/${sub}`,
|
|
105
|
+
query.surface = oneOf('Gateway surface', ['dns', 'http', 'network'], id);
|
|
106
|
+
return request(`/zero-trust/${sub}`, view, query);
|
|
104
107
|
}
|
|
105
108
|
if (group === 'cloudflare-account') {
|
|
106
109
|
const operation = sub ?? 'inventory';
|
|
107
110
|
const views = { inventory: 'accountInventory', audit: 'accountAudit', ingestion: 'accountIngestion', posture: 'accountPosture' };
|
|
108
|
-
|
|
109
|
-
|
|
111
|
+
const view = own(views, operation);
|
|
112
|
+
if (!view)
|
|
113
|
+
throw unknownSubcommand('security cloudflare-account', operation);
|
|
110
114
|
arity(ctx, sub === undefined ? 1 : 2);
|
|
111
115
|
let query = {};
|
|
112
116
|
if (operation === 'inventory')
|
|
113
117
|
allowed(ctx, []);
|
|
114
118
|
else
|
|
115
119
|
query = window();
|
|
116
|
-
return request(`/cloudflare-account/${operation}`,
|
|
120
|
+
return request(`/cloudflare-account/${operation}`, view, query);
|
|
117
121
|
}
|
|
118
122
|
if (group === 'providers') {
|
|
119
123
|
arity(ctx, 1);
|
|
@@ -123,9 +127,9 @@ function extendedSecurityRequest(ctx, scope) {
|
|
|
123
127
|
if (group === 'notifications') {
|
|
124
128
|
const operation = sub ?? 'list';
|
|
125
129
|
const routes = { list: ['/notifications', 'notifications'], settings: ['/notifications/settings', 'notificationSettings'], preferences: ['/notifications/preferences', 'preferences'], 'slack-channels': ['/notifications/slack/channels', 'slackChannels'] };
|
|
126
|
-
const route = routes
|
|
130
|
+
const route = own(routes, operation);
|
|
127
131
|
if (!route)
|
|
128
|
-
throw
|
|
132
|
+
throw unknownSubcommand('security notifications', operation);
|
|
129
133
|
arity(ctx, sub === undefined ? 1 : 2);
|
|
130
134
|
allowed(ctx, operation === 'list' ? ['limit'] : []);
|
|
131
135
|
return request(...route, operation === 'list' ? { limit: integer(ctx, 'limit', 100, 100) } : {});
|
|
@@ -136,9 +140,9 @@ function extendedSecurityRequest(ctx, scope) {
|
|
|
136
140
|
remediation: (assetId) => [`/assets/${assetId}/remediation-settings`, 'assetRemediation'], autonomy: (assetId) => [`/assets/${assetId}/vigil-autonomy`, 'autonomy'],
|
|
137
141
|
datasets: (assetId) => [`/fabric/assets/${assetId}/datasets`, 'datasets'], graph: (assetId) => [`/fabric/assets/${assetId}/graph`, 'graph'],
|
|
138
142
|
};
|
|
139
|
-
const route =
|
|
143
|
+
const route = own(routes, sub);
|
|
140
144
|
if (!route)
|
|
141
|
-
throw
|
|
145
|
+
throw unknownSubcommand('security assets', sub);
|
|
142
146
|
arity(ctx, 3, `assets ${sub} <asset-id>`);
|
|
143
147
|
if (sub !== 'graph')
|
|
144
148
|
allowed(ctx, []);
|
|
@@ -174,12 +178,12 @@ function extendedSecurityRequest(ctx, scope) {
|
|
|
174
178
|
return request(`/alerts/${uuid(id)}`, 'alert');
|
|
175
179
|
}
|
|
176
180
|
if (sub !== undefined && sub !== 'list')
|
|
177
|
-
throw
|
|
181
|
+
throw unknownSubcommand('security alerts', sub);
|
|
178
182
|
arity(ctx, sub === undefined ? 1 : 2);
|
|
179
183
|
allowed(ctx, ['limit', 'asset-id', 'start']);
|
|
180
184
|
const since = textFlag(ctx, 'start');
|
|
181
185
|
if (since)
|
|
182
|
-
|
|
186
|
+
isoDateTime('--start', since);
|
|
183
187
|
return request('/alerts', 'alerts', { limit: integer(ctx, 'limit', 100, 100), asset_id: ctx.values['asset-id'] !== undefined ? uuid(ctx.values['asset-id']) : undefined, since });
|
|
184
188
|
}
|
|
185
189
|
if (group === 'incidents' && (sub === 'fabric' || sub === 'share-links')) {
|
|
@@ -190,7 +194,7 @@ function extendedSecurityRequest(ctx, scope) {
|
|
|
190
194
|
}
|
|
191
195
|
if (group === 'settings') {
|
|
192
196
|
const routes = { 'lifecycle-emails': ['/settings/lifecycle-emails', 'lifecycleEmails'], 'broadcast-emails': ['/settings/broadcast-emails', 'broadcastEmails'], applications: ['/integrations/grants', 'applications'] };
|
|
193
|
-
const route =
|
|
197
|
+
const route = own(routes, sub);
|
|
194
198
|
if (!route)
|
|
195
199
|
throw new Error('Choose settings lifecycle-emails, broadcast-emails, or applications.');
|
|
196
200
|
arity(ctx, 2);
|
|
@@ -228,13 +232,13 @@ export function buildSecurityRequest(ctx) {
|
|
|
228
232
|
arity(ctx, 4, 'incidents update <id> <status>');
|
|
229
233
|
allowed(ctx, ['note', 'isConfirm']);
|
|
230
234
|
const id = uuid(ctx.rest[2]);
|
|
231
|
-
const status =
|
|
235
|
+
const status = oneOf('Status', updateStatuses, ctx.rest[3]);
|
|
232
236
|
if (!ctx.values.isConfirm)
|
|
233
237
|
throw new Error('Updating an incident requires --confirm. Your One permissions still apply.');
|
|
234
238
|
return { ...request(`/incidents/${id}`, 'update'), body: { status, ...(ctx.values.note !== undefined ? { note: textFlag(ctx, 'note') } : {}) } };
|
|
235
239
|
}
|
|
236
240
|
if (command !== 'list' && command !== 'ls')
|
|
237
|
-
throw
|
|
241
|
+
throw unknownSubcommand(`security ${group}`, command);
|
|
238
242
|
arity(ctx, ctx.rest[1] === undefined ? 1 : 2);
|
|
239
243
|
if (group === 'connections') {
|
|
240
244
|
allowed(ctx, []);
|
|
@@ -247,11 +251,11 @@ export function buildSecurityRequest(ctx) {
|
|
|
247
251
|
allowed(ctx, ['limit', 'offset', 'status', 'severity', 'attack-category', 'order-by', 'order-direction']);
|
|
248
252
|
return request('/incidents', 'incidents', {
|
|
249
253
|
limit: integer(ctx, 'limit', 100, 50), offset: integer(ctx, 'offset', 1_000_000),
|
|
250
|
-
status:
|
|
251
|
-
severity:
|
|
254
|
+
status: oneOf('--status', statuses, textFlag(ctx, 'status')),
|
|
255
|
+
severity: oneOf('--severity', ['critical', 'high', 'medium', 'low'], textFlag(ctx, 'severity')),
|
|
252
256
|
attack_category: textFlag(ctx, 'attack-category'),
|
|
253
|
-
order_by:
|
|
254
|
-
order_direction:
|
|
257
|
+
order_by: oneOf('--order-by', ['last_alert_at', 'first_alert_at', 'created_at', 'severity_rank', 'combined_score', 'alert_count'], textFlag(ctx, 'order-by')),
|
|
258
|
+
order_direction: oneOf('--order-direction', ['asc', 'desc'], textFlag(ctx, 'order-direction')),
|
|
255
259
|
});
|
|
256
260
|
}
|
|
257
261
|
if (group === 'assets') {
|
|
@@ -274,8 +278,8 @@ export function buildSecurityRequest(ctx) {
|
|
|
274
278
|
if (Boolean(start) !== Boolean(end))
|
|
275
279
|
throw new Error('--start and --end must be supplied together as ISO timestamps.');
|
|
276
280
|
if (start && end) {
|
|
277
|
-
|
|
278
|
-
|
|
281
|
+
isoDateTime('--start', start);
|
|
282
|
+
isoDateTime('--end', end);
|
|
279
283
|
if (Date.parse(start) >= Date.parse(end))
|
|
280
284
|
throw new Error('--start must be before --end.');
|
|
281
285
|
}
|
|
@@ -283,7 +287,7 @@ export function buildSecurityRequest(ctx) {
|
|
|
283
287
|
limit: integer(ctx, 'limit', 1000, 100), asset_id: ctx.values['asset-id'] !== undefined ? uuid(ctx.values['asset-id']) : undefined,
|
|
284
288
|
provider: textFlag(ctx, 'provider'), dataset: textFlag(ctx, 'dataset'), search: textFlag(ctx, 'search'),
|
|
285
289
|
start_time: start, end_time: end, cursor: textFlag(ctx, 'cursor'),
|
|
286
|
-
sort:
|
|
290
|
+
sort: oneOf('--sort', ['ts', 'frs', 'frs_desc'], textFlag(ctx, 'sort')),
|
|
287
291
|
frs_min: integer(ctx, 'frs-min', 100),
|
|
288
292
|
});
|
|
289
293
|
}
|
|
@@ -292,7 +296,7 @@ export function buildSecurityRequest(ctx) {
|
|
|
292
296
|
allowed(ctx, ['limit', 'cursor', 'action-prefix', 'outcome']);
|
|
293
297
|
return request('/vigilbase-one/audit-events', 'audit', {
|
|
294
298
|
limit: integer(ctx, 'limit', 100, 50), cursor: textFlag(ctx, 'cursor'), action_prefix: textFlag(ctx, 'action-prefix'),
|
|
295
|
-
outcome:
|
|
299
|
+
outcome: oneOf('--outcome', ['success', 'failure', 'denied'], textFlag(ctx, 'outcome')),
|
|
296
300
|
});
|
|
297
301
|
}
|
|
298
302
|
if (group === 'intel') {
|
|
@@ -302,11 +306,11 @@ export function buildSecurityRequest(ctx) {
|
|
|
302
306
|
return request('/intel/quota', 'quota');
|
|
303
307
|
}
|
|
304
308
|
arity(ctx, 3, 'intel <ip|asn|cidr|ja3|ja4> <query>');
|
|
305
|
-
const type =
|
|
306
|
-
const q = z.string().trim().min(1).max(512)
|
|
309
|
+
const type = oneOf('Lookup type', ['ip', 'asn', 'cidr', 'ja3', 'ja4'], command);
|
|
310
|
+
const q = parseArgument(z.string().trim().min(1).max(512), ctx.rest[2], 'The intel query must be 1-512 characters.');
|
|
307
311
|
return request('/intel/lookup', 'intel', { q, type });
|
|
308
312
|
}
|
|
309
|
-
throw
|
|
313
|
+
throw unknownSubcommand('security', group);
|
|
310
314
|
}
|
|
311
315
|
function cell(value) {
|
|
312
316
|
// Security evidence may contain attacker-controlled text; keep each terminal row literal.
|
|
@@ -399,4 +403,4 @@ export const handleSecurity = async (ctx) => {
|
|
|
399
403
|
display(ctx, request, payload);
|
|
400
404
|
return ok();
|
|
401
405
|
};
|
|
402
|
-
export const SECURITY_HELP = "vigilbase security \u2014 managed security in Vigilbase One\n\n [overview]\n incidents [list] --status --severity --attack-category\n --order-by --order-direction
|
|
406
|
+
export const SECURITY_HELP = "vigilbase security \u2014 managed security in Vigilbase One\n\n [overview]\n incidents [list] --status --severity --attack-category\n --order-by --order-direction\n --limit --offset\n incidents show <id>\n incidents timeline <id> --limit --before\n incidents fabric <id> --dataset --min-weight --limit\n incidents share-links <id>\n incidents update <id> <status> --confirm [--note]\n alerts [list] --limit --asset-id --start\n alerts show <id>\n logs --start --end --limit --cursor --sort\n --asset-id --provider --dataset\n --search --frs-min\n audit --limit --cursor\n --action-prefix --outcome\n assets\n assets <allowlist|blocklist|remediation|autonomy|datasets> <asset-id>\n assets graph <asset-id> --dataset --hours --min-weight --limit\n fabric default-asset\n detections --asset-id <asset-id>\n providers\n connections [list]\n connections show <id>\n connections <available-assets|remediation> <id>\n connections <logpush-status|worker-stats> <connection-id> <job-id>\n analytics [overview] --metrics + time-window flags\n analytics <security-analysis|bot-activity|top-attacker-ips>\n analytics <bot-asns|dns-security|page-shield>\n analytics provider-events --provider --dataset --start --end\n [--asset-id --search --limit]\n zero-trust <device-health|app-tests|access|sessions|device-posture>\n zero-trust gateway <dns|http|network>\n cloudflare-account [inventory]\n cloudflare-account <audit|ingestion|posture>\n notifications [list] --limit\n notifications <settings|preferences|slack-channels>\n reports [list] --limit --offset\n reports show <id>\n reports settings\n intel quota\n intel <ip|asn|cidr|ja3|ja4> <query>\n settings <lifecycle-emails|broadcast-emails|applications>\n\nTime-window flags: --hours (1\u2013720, default 24) or --start/--end ISO timestamps;\noptional --asset-id. Apply to analytics (except provider-events), zero-trust,\nand cloudflare-account audit/ingestion/posture. Inventory has no window flags.\nLimits: list incidents/reports/audit 1\u2013100 (default 50); alerts/notifications\n1\u2013100 (default 100); timeline 1\u2013200 (default 100); logs 1\u20131000 (default 100);\nprovider-events 1\u2013500 (default 200); graph 1\u20131000 (default 200).\nOffsets: 0\u20131000000. Graph min-weight: 1\u20131000000 (default 1).\nAll: --json, --timeout <ms>.\nInternal View-as: --customer-id <One customer UUID>.\nAll operations read data except confirmed incident status updates.\nUpdate status: acknowledged, mitigating, resolved, false_positive.\nSee SECURITY.md for endpoints, supported values, aliases and pagination.\n";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { limitValue, validateArguments } from './legacy-validation.js';
|
|
2
|
+
import { limitValue, parseArgument, unknownSubcommand, validateArguments } from './legacy-validation.js';
|
|
3
3
|
import { asArray, asRecord, ok, outputJson, requireOneClient, withSpinner, } from './helpers.js';
|
|
4
4
|
export const handleWatchdog = async (ctx) => {
|
|
5
5
|
const subcommand = (ctx.rest[0] ?? 'list').toLowerCase();
|
|
@@ -7,12 +7,12 @@ export const handleWatchdog = async (ctx) => {
|
|
|
7
7
|
if (subcommand === 'scans' || subcommand === 'scan') {
|
|
8
8
|
validateArguments(ctx, 1, ['target-id', 'limit']);
|
|
9
9
|
limit = limitValue(ctx.values.limit, 20, 100);
|
|
10
|
-
targetId = z.string().uuid().optional()
|
|
10
|
+
targetId = parseArgument(z.string().uuid().optional(), ctx.values['target-id'], '--target-id must be a UUID. Copy the full ID from its list command.');
|
|
11
11
|
}
|
|
12
12
|
else if (['list', 'targets', 'status'].includes(subcommand))
|
|
13
13
|
validateArguments(ctx, ctx.rest[0] === undefined ? 0 : 1);
|
|
14
14
|
else
|
|
15
|
-
throw
|
|
15
|
+
throw unknownSubcommand('watchdog', ctx.rest[0] ?? '');
|
|
16
16
|
const one = await requireOneClient(ctx);
|
|
17
17
|
if (subcommand === 'list' || subcommand === 'targets' || subcommand === 'status') {
|
|
18
18
|
const payload = await withSpinner(ctx, 'Loading watchdog targets', () => one.getWatchdogTargets());
|
|
@@ -29,7 +29,7 @@ export const handleWatchdog = async (ctx) => {
|
|
|
29
29
|
const target = asRecord(raw);
|
|
30
30
|
if (!target)
|
|
31
31
|
continue;
|
|
32
|
-
ctx.writer.stdout(`${String(target.domain).padEnd(28)} ${String(target.uptimeStatus || '-')}${typeof target.uptime24h === 'number'
|
|
32
|
+
ctx.writer.stdout(`${String(target.id ?? '-')} ${String(target.domain).padEnd(28)} ${String(target.uptimeStatus || '-')}${typeof target.uptime24h === 'number'
|
|
33
33
|
? ` 24h=${Math.round(target.uptime24h * 100) / 100}`
|
|
34
34
|
: ''}`);
|
|
35
35
|
}
|
|
@@ -53,7 +53,7 @@ export const handleWatchdog = async (ctx) => {
|
|
|
53
53
|
const scan = asRecord(raw);
|
|
54
54
|
if (!scan)
|
|
55
55
|
continue;
|
|
56
|
-
ctx.writer.stdout(`${String(scan.id)
|
|
56
|
+
ctx.writer.stdout(`${String(scan.id)} ${String(scan.status || '-').padEnd(10)} ${String(scan.scanType || '-')} ${String(scan.createdAt || '')}`);
|
|
57
57
|
}
|
|
58
58
|
return ok();
|
|
59
59
|
}
|
package/dist/lib/banner.d.ts
CHANGED
|
@@ -2,27 +2,21 @@
|
|
|
2
2
|
* Compact Vigilbase flare mark + wordmark for human CLI surfaces.
|
|
3
3
|
* Static strings only — no I/O, no deps, skipped entirely in --json mode.
|
|
4
4
|
*/
|
|
5
|
-
/**
|
|
6
|
-
* Compact square Vigilbase flare (inverted V).
|
|
7
|
-
* Left wing (full block) = terminal default fg; right wing (medium shade) = brand yellow.
|
|
8
|
-
*/
|
|
9
|
-
/** Compact flare used on help/version (keeps the first viewport short). */
|
|
5
|
+
/** Compact square Vigilbase flare (inverted V), used on help/version (keeps the first viewport short). */
|
|
10
6
|
export declare const VIGILBASE_ASCII_MARK: string;
|
|
11
7
|
export type BannerOptions = {
|
|
12
8
|
/** Include version line under the mark. */
|
|
13
9
|
version?: string;
|
|
14
10
|
/** One-line tagline. */
|
|
15
11
|
tagline?: string;
|
|
16
|
-
/** Force ANSI on/off. Default: auto from TTY + env. */
|
|
17
|
-
useColor?: boolean;
|
|
18
12
|
};
|
|
13
|
+
/** Whether the checkup terminal UI (tui.ts) may colour this stream. */
|
|
19
14
|
export declare function shouldUseColor(stream: {
|
|
20
15
|
isTTY?: boolean;
|
|
21
16
|
} | undefined, env?: NodeJS.ProcessEnv): boolean;
|
|
22
17
|
export declare function shouldShowBanner(mode?: 'text' | 'json', env?: NodeJS.ProcessEnv): boolean;
|
|
23
18
|
/**
|
|
24
|
-
* Banner composition (flare + wordmark + tagline).
|
|
19
|
+
* Banner composition (flare + wordmark + tagline). Plain text: the writer strips terminal
|
|
20
|
+
* sequences from every line, so the banner carries no colour.
|
|
25
21
|
*/
|
|
26
|
-
export declare function renderBanner(options?: BannerOptions
|
|
27
|
-
isTTY?: boolean;
|
|
28
|
-
} | undefined, env?: NodeJS.ProcessEnv): string;
|
|
22
|
+
export declare function renderBanner(options?: BannerOptions): string;
|
package/dist/lib/banner.js
CHANGED
|
@@ -2,11 +2,8 @@
|
|
|
2
2
|
* Compact Vigilbase flare mark + wordmark for human CLI surfaces.
|
|
3
3
|
* Static strings only — no I/O, no deps, skipped entirely in --json mode.
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
* Left wing (full block) = terminal default fg; right wing (medium shade) = brand yellow.
|
|
8
|
-
*/
|
|
9
|
-
/** Compact flare used on help/version (keeps the first viewport short). */
|
|
5
|
+
import { isCi } from './platform.js';
|
|
6
|
+
/** Compact square Vigilbase flare (inverted V), used on help/version (keeps the first viewport short). */
|
|
10
7
|
export const VIGILBASE_ASCII_MARK = [
|
|
11
8
|
'\u2588\u2588\u2588\u2588\u2588 \u2592\u2592\u2592',
|
|
12
9
|
' \u2588\u2588\u2588\u2588 \u2592\u2592\u2592\u2592',
|
|
@@ -14,13 +11,7 @@ export const VIGILBASE_ASCII_MARK = [
|
|
|
14
11
|
' \u2588\u2592\u2592\u2592\u2592 ',
|
|
15
12
|
' \u2592\u2592\u2592 ',
|
|
16
13
|
].join('\n');
|
|
17
|
-
|
|
18
|
-
reset: '\u001b[0m',
|
|
19
|
-
dim: '\u001b[2m',
|
|
20
|
-
bold: '\u001b[1m',
|
|
21
|
-
/** Brand yellow (#f9e75e / #FFE74D family) — 256-color approximation. */
|
|
22
|
-
brand: '\u001b[38;5;221m',
|
|
23
|
-
};
|
|
14
|
+
/** Whether the checkup terminal UI (tui.ts) may colour this stream. */
|
|
24
15
|
export function shouldUseColor(stream, env = process.env) {
|
|
25
16
|
if (env.NO_COLOR != null && env.NO_COLOR !== '')
|
|
26
17
|
return false;
|
|
@@ -28,7 +19,7 @@ export function shouldUseColor(stream, env = process.env) {
|
|
|
28
19
|
return false;
|
|
29
20
|
if (env.FORCE_COLOR && env.FORCE_COLOR !== '0')
|
|
30
21
|
return true;
|
|
31
|
-
if (env
|
|
22
|
+
if (isCi(env))
|
|
32
23
|
return false;
|
|
33
24
|
return Boolean(stream?.isTTY);
|
|
34
25
|
}
|
|
@@ -39,29 +30,18 @@ export function shouldShowBanner(mode = 'text', env = process.env) {
|
|
|
39
30
|
return false;
|
|
40
31
|
return true;
|
|
41
32
|
}
|
|
42
|
-
function paintMarkLine(line, useColor) {
|
|
43
|
-
if (!useColor)
|
|
44
|
-
return line;
|
|
45
|
-
// Left wing stays terminal default fg; right wing is brand yellow.
|
|
46
|
-
return line.replace(/\u2592+/g, (run) => `${ANSI.brand}${run}${ANSI.reset}`);
|
|
47
|
-
}
|
|
48
33
|
/**
|
|
49
|
-
* Banner composition (flare + wordmark + tagline).
|
|
34
|
+
* Banner composition (flare + wordmark + tagline). Plain text: the writer strips terminal
|
|
35
|
+
* sequences from every line, so the banner carries no colour.
|
|
50
36
|
*/
|
|
51
|
-
export function renderBanner(options = {}
|
|
52
|
-
const useColor = options.useColor ?? shouldUseColor(stream, env);
|
|
37
|
+
export function renderBanner(options = {}) {
|
|
53
38
|
const tagline = options.tagline ?? 'one \u00b7 tools';
|
|
54
39
|
const version = options.version ? ` \u00b7 v${options.version}` : '';
|
|
55
|
-
|
|
56
|
-
const brand = (text) => useColor ? `${ANSI.brand}${ANSI.bold}${text}${ANSI.reset}` : text;
|
|
57
|
-
const dim = (text) => useColor ? `${ANSI.dim}${text}${ANSI.reset}` : text;
|
|
58
|
-
const painted = mark.map((line) => paintMarkLine(line, useColor));
|
|
59
|
-
const lines = painted.map((line, index) => {
|
|
40
|
+
return VIGILBASE_ASCII_MARK.split('\n').map((line, index) => {
|
|
60
41
|
if (index === 0)
|
|
61
|
-
return `${line}
|
|
42
|
+
return `${line} vigilbase`;
|
|
62
43
|
if (index === 1)
|
|
63
|
-
return `${line} ${
|
|
44
|
+
return `${line} ${tagline}${version}`;
|
|
64
45
|
return line;
|
|
65
|
-
});
|
|
66
|
-
return lines.join('\n');
|
|
46
|
+
}).join('\n');
|
|
67
47
|
}
|