@f5xc-salesdemos/xcsh 18.65.1 → 18.66.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/package.json +7 -7
- package/scripts/generate-api-spec-index.ts +37 -3
- package/src/internal-urls/build-info.generated.ts +9 -9
- package/src/modes/components/welcome-checks.ts +70 -1
- package/src/modes/interactive-mode.ts +67 -13
- package/src/tools/xcsh-api-renderer.ts +6 -3
- package/src/tui/output-block.ts +10 -9
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5xc-salesdemos/xcsh",
|
|
4
|
-
"version": "18.
|
|
4
|
+
"version": "18.66.1",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/f5xc-salesdemos/xcsh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -48,12 +48,12 @@
|
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@agentclientprotocol/sdk": "0.16.1",
|
|
50
50
|
"@mozilla/readability": "^0.6",
|
|
51
|
-
"@f5xc-salesdemos/xcsh-stats": "18.
|
|
52
|
-
"@f5xc-salesdemos/pi-agent-core": "18.
|
|
53
|
-
"@f5xc-salesdemos/pi-ai": "18.
|
|
54
|
-
"@f5xc-salesdemos/pi-natives": "18.
|
|
55
|
-
"@f5xc-salesdemos/pi-tui": "18.
|
|
56
|
-
"@f5xc-salesdemos/pi-utils": "18.
|
|
51
|
+
"@f5xc-salesdemos/xcsh-stats": "18.66.1",
|
|
52
|
+
"@f5xc-salesdemos/pi-agent-core": "18.66.1",
|
|
53
|
+
"@f5xc-salesdemos/pi-ai": "18.66.1",
|
|
54
|
+
"@f5xc-salesdemos/pi-natives": "18.66.1",
|
|
55
|
+
"@f5xc-salesdemos/pi-tui": "18.66.1",
|
|
56
|
+
"@f5xc-salesdemos/pi-utils": "18.66.1",
|
|
57
57
|
"@sinclair/typebox": "^0.34",
|
|
58
58
|
"@xterm/headless": "^6.0",
|
|
59
59
|
"ajv": "^8.18",
|
|
@@ -90,6 +90,35 @@ const REPO = "f5xc-salesdemos/api-specs-enriched";
|
|
|
90
90
|
const outputPath = path.resolve(import.meta.dir, "../src/internal-urls/api-spec-index.generated.ts");
|
|
91
91
|
const catalogOutputPath = path.resolve(import.meta.dir, "../src/internal-urls/api-catalog-index.generated.ts");
|
|
92
92
|
|
|
93
|
+
const MAX_RETRIES = 3;
|
|
94
|
+
const INITIAL_BACKOFF_MS = 2000;
|
|
95
|
+
|
|
96
|
+
async function fetchWithRetry(url: string, init?: RequestInit): Promise<Response> {
|
|
97
|
+
let lastError: Error | null = null;
|
|
98
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
99
|
+
if (attempt > 0) {
|
|
100
|
+
const delay = INITIAL_BACKOFF_MS * 2 ** (attempt - 1);
|
|
101
|
+
console.warn(` Retry ${attempt}/${MAX_RETRIES} after ${delay}ms...`);
|
|
102
|
+
await Bun.sleep(delay);
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const response = await fetch(url, init);
|
|
106
|
+
if (response.status === 403 || response.status === 429) {
|
|
107
|
+
const retryAfter = response.headers.get("retry-after");
|
|
108
|
+
const waitMs = retryAfter ? Number.parseInt(retryAfter, 10) * 1000 : INITIAL_BACKOFF_MS * 2 ** attempt;
|
|
109
|
+
console.warn(` Rate limited (${response.status}), waiting ${waitMs}ms...`);
|
|
110
|
+
await Bun.sleep(waitMs);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
return response;
|
|
114
|
+
} catch (err) {
|
|
115
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
116
|
+
console.warn(` Fetch failed: ${lastError.message}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
throw lastError ?? new Error(`Failed to fetch ${url} after ${MAX_RETRIES} retries`);
|
|
120
|
+
}
|
|
121
|
+
|
|
93
122
|
function githubHeaders(): Record<string, string> {
|
|
94
123
|
const headers: Record<string, string> = { Accept: "application/vnd.github+json" };
|
|
95
124
|
const token = process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN;
|
|
@@ -98,7 +127,7 @@ function githubHeaders(): Record<string, string> {
|
|
|
98
127
|
}
|
|
99
128
|
|
|
100
129
|
async function resolveLatestTag(): Promise<string> {
|
|
101
|
-
const response = await
|
|
130
|
+
const response = await fetchWithRetry(`https://api.github.com/repos/${REPO}/releases/latest`, {
|
|
102
131
|
headers: githubHeaders(),
|
|
103
132
|
});
|
|
104
133
|
if (!response.ok) {
|
|
@@ -119,7 +148,7 @@ async function downloadFromRelease(): Promise<string> {
|
|
|
119
148
|
const downloadUrl = `https://github.com/${REPO}/releases/download/${tag}/${zipName}`;
|
|
120
149
|
|
|
121
150
|
console.log(`Downloading API specs from ${downloadUrl}...`);
|
|
122
|
-
const response = await
|
|
151
|
+
const response = await fetchWithRetry(downloadUrl, { redirect: "follow" });
|
|
123
152
|
if (!response.ok) {
|
|
124
153
|
throw new Error(`Failed to download release: ${response.status} ${response.statusText}`);
|
|
125
154
|
}
|
|
@@ -172,7 +201,7 @@ async function downloadCatalog(specsDir: string): Promise<Record<string, unknown
|
|
|
172
201
|
const catalogUrl = `https://github.com/${REPO}/releases/download/${catalogTag}/api-catalog.json`;
|
|
173
202
|
console.log(`Downloading API catalog from ${catalogUrl}...`);
|
|
174
203
|
try {
|
|
175
|
-
const response = await
|
|
204
|
+
const response = await fetchWithRetry(catalogUrl, { redirect: "follow" });
|
|
176
205
|
if (!response.ok) {
|
|
177
206
|
console.warn(`api-catalog.json not found (${response.status}), skipping catalog generation`);
|
|
178
207
|
return null;
|
|
@@ -192,6 +221,11 @@ function serializeEnrichment(key: string, value: unknown): string | undefined {
|
|
|
192
221
|
|
|
193
222
|
let downloadedTmpDir: string | null = null;
|
|
194
223
|
|
|
224
|
+
if (fs.existsSync(outputPath) && fs.existsSync(catalogOutputPath) && process.env.CI) {
|
|
225
|
+
console.log("Generated spec files already exist in CI — skipping regeneration.");
|
|
226
|
+
process.exit(0);
|
|
227
|
+
}
|
|
228
|
+
|
|
195
229
|
const specsDir = await findSpecsDir();
|
|
196
230
|
console.log(`Reading specs from: ${specsDir}`);
|
|
197
231
|
|
|
@@ -17,17 +17,17 @@ export interface BuildInfo {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export const BUILD_INFO: BuildInfo = {
|
|
20
|
-
"version": "18.
|
|
21
|
-
"commit": "
|
|
22
|
-
"shortCommit": "
|
|
20
|
+
"version": "18.66.1",
|
|
21
|
+
"commit": "90d34ed7bee228bbc7e09126b5e9e71aee873861",
|
|
22
|
+
"shortCommit": "90d34ed",
|
|
23
23
|
"branch": "main",
|
|
24
|
-
"tag": "v18.
|
|
25
|
-
"commitDate": "2026-05-
|
|
26
|
-
"buildDate": "2026-05-
|
|
27
|
-
"dirty":
|
|
24
|
+
"tag": "v18.66.1",
|
|
25
|
+
"commitDate": "2026-05-18T06:44:43Z",
|
|
26
|
+
"buildDate": "2026-05-18T07:08:42.105Z",
|
|
27
|
+
"dirty": false,
|
|
28
28
|
"prNumber": "",
|
|
29
29
|
"repoUrl": "https://github.com/f5xc-salesdemos/xcsh",
|
|
30
30
|
"repoSlug": "f5xc-salesdemos/xcsh",
|
|
31
|
-
"commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/
|
|
32
|
-
"releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.
|
|
31
|
+
"commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/90d34ed7bee228bbc7e09126b5e9e71aee873861",
|
|
32
|
+
"releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.66.1"
|
|
33
33
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Model } from "@f5xc-salesdemos/pi-ai";
|
|
2
2
|
import { validateApiKeyAgainstModelsEndpoint } from "@f5xc-salesdemos/pi-ai/utils/oauth/api-key-validation";
|
|
3
|
-
import { $which, logger } from "@f5xc-salesdemos/pi-utils";
|
|
3
|
+
import { $which, getProjectDir, logger } from "@f5xc-salesdemos/pi-utils";
|
|
4
4
|
import { $ } from "bun";
|
|
5
5
|
import { loadProfile } from "../../internal-urls/user-profile";
|
|
6
6
|
import { type AuthStatus, ContextService } from "../../services/f5xc-context";
|
|
@@ -595,3 +595,72 @@ export async function checkProfileStatus(): Promise<WelcomeProfileStatus | undef
|
|
|
595
595
|
return { state: "missing" };
|
|
596
596
|
}
|
|
597
597
|
}
|
|
598
|
+
|
|
599
|
+
export interface FixableService {
|
|
600
|
+
name: string;
|
|
601
|
+
prompt: string;
|
|
602
|
+
command: string[];
|
|
603
|
+
recheck: () => Promise<ServiceStatus>;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
export function getFixableServices(statuses: {
|
|
607
|
+
aws: WelcomeAwsStatus | undefined;
|
|
608
|
+
azure: WelcomeAzureStatus | undefined;
|
|
609
|
+
gcloud: WelcomeGcloudStatus | undefined;
|
|
610
|
+
github: WelcomeGitHubStatus | undefined;
|
|
611
|
+
gitlab: WelcomeGitLabStatus | undefined;
|
|
612
|
+
salesforce: WelcomeSalesforceStatus | undefined;
|
|
613
|
+
}): FixableService[] {
|
|
614
|
+
const fixable: FixableService[] = [];
|
|
615
|
+
|
|
616
|
+
if (statuses.gitlab?.state === "auth_error") {
|
|
617
|
+
fixable.push({
|
|
618
|
+
name: "GitLab",
|
|
619
|
+
prompt: "GitLab not authenticated",
|
|
620
|
+
command: ["glab", "auth", "login"],
|
|
621
|
+
recheck: async () => mapGitLabStatus(await checkGitLabStatus(getProjectDir())),
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
if (statuses.github?.state === "auth_error") {
|
|
625
|
+
fixable.push({
|
|
626
|
+
name: "GitHub",
|
|
627
|
+
prompt: "GitHub not authenticated",
|
|
628
|
+
command: ["gh", "auth", "login"],
|
|
629
|
+
recheck: async () => mapGitHubStatus(await checkGitHubStatus()),
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
if (statuses.salesforce?.state === "session_expired") {
|
|
633
|
+
fixable.push({
|
|
634
|
+
name: "Salesforce",
|
|
635
|
+
prompt: "Salesforce session expired",
|
|
636
|
+
command: ["sf", "org", "login", "web"],
|
|
637
|
+
recheck: async () => mapSalesforceStatus(await checkSalesforceStatus(getProjectDir())),
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
if (statuses.azure?.state === "auth_error") {
|
|
641
|
+
fixable.push({
|
|
642
|
+
name: "Azure",
|
|
643
|
+
prompt: "Azure session expired",
|
|
644
|
+
command: ["az", "login", "--use-device-code"],
|
|
645
|
+
recheck: async () => mapAzureStatus(await checkAzureStatus()),
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
if (statuses.aws?.state === "sso_expired") {
|
|
649
|
+
fixable.push({
|
|
650
|
+
name: "AWS",
|
|
651
|
+
prompt: "AWS SSO session expired",
|
|
652
|
+
command: ["aws", "sso", "login"],
|
|
653
|
+
recheck: async () => mapAwsStatus(await checkAwsStatus()),
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
if (statuses.gcloud?.state === "token_expired") {
|
|
657
|
+
fixable.push({
|
|
658
|
+
name: "Google Cloud",
|
|
659
|
+
prompt: "Google Cloud token expired",
|
|
660
|
+
command: ["gcloud", "auth", "login"],
|
|
661
|
+
recheck: async () => mapGcloudStatus(await checkGcloudStatus()),
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
return fixable;
|
|
666
|
+
}
|
|
@@ -60,6 +60,8 @@ import {
|
|
|
60
60
|
checkGitHubStatus,
|
|
61
61
|
checkGitLabStatus,
|
|
62
62
|
checkSalesforceStatus,
|
|
63
|
+
type FixableService,
|
|
64
|
+
getFixableServices,
|
|
63
65
|
mapAwsStatus,
|
|
64
66
|
mapAzureStatus,
|
|
65
67
|
mapContextStatus,
|
|
@@ -68,6 +70,7 @@ import {
|
|
|
68
70
|
mapGitLabStatus,
|
|
69
71
|
mapSalesforceStatus,
|
|
70
72
|
runWelcomeChecks,
|
|
73
|
+
type ServiceStatus,
|
|
71
74
|
} from "./components/welcome-checks";
|
|
72
75
|
import { BtwController } from "./controllers/btw-controller";
|
|
73
76
|
import { CommandController } from "./controllers/command-controller";
|
|
@@ -358,20 +361,32 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
358
361
|
this.ui.addChild(new Spacer(1));
|
|
359
362
|
}
|
|
360
363
|
|
|
364
|
+
const services =
|
|
365
|
+
!startupQuiet && welcomeResult.model.state === "connected"
|
|
366
|
+
? [
|
|
367
|
+
mapContextStatus(welcomeResult.context ?? { state: "no_context" }),
|
|
368
|
+
mapGitLabStatus(gitlabStatus),
|
|
369
|
+
mapGitHubStatus(githubStatus),
|
|
370
|
+
mapSalesforceStatus(salesforceStatus),
|
|
371
|
+
mapAzureStatus(azureStatus),
|
|
372
|
+
mapAwsStatus(awsStatus),
|
|
373
|
+
mapGcloudStatus(gcloudStatus),
|
|
374
|
+
]
|
|
375
|
+
: [];
|
|
376
|
+
|
|
377
|
+
const fixableServices =
|
|
378
|
+
!startupQuiet && welcomeResult.model.state === "connected"
|
|
379
|
+
? getFixableServices({
|
|
380
|
+
aws: awsStatus,
|
|
381
|
+
azure: azureStatus,
|
|
382
|
+
gcloud: gcloudStatus,
|
|
383
|
+
github: githubStatus,
|
|
384
|
+
gitlab: gitlabStatus,
|
|
385
|
+
salesforce: salesforceStatus,
|
|
386
|
+
})
|
|
387
|
+
: [];
|
|
388
|
+
|
|
361
389
|
if (!startupQuiet) {
|
|
362
|
-
// Welcome box owns all startup notifications (model, services, update)
|
|
363
|
-
const services =
|
|
364
|
-
welcomeResult.model.state === "connected"
|
|
365
|
-
? [
|
|
366
|
-
mapContextStatus(welcomeResult.context ?? { state: "no_context" }),
|
|
367
|
-
mapGitLabStatus(gitlabStatus),
|
|
368
|
-
mapGitHubStatus(githubStatus),
|
|
369
|
-
mapSalesforceStatus(salesforceStatus),
|
|
370
|
-
mapAzureStatus(azureStatus),
|
|
371
|
-
mapAwsStatus(awsStatus),
|
|
372
|
-
mapGcloudStatus(gcloudStatus),
|
|
373
|
-
]
|
|
374
|
-
: [];
|
|
375
390
|
this.#welcomeComponent = new WelcomeComponent(
|
|
376
391
|
this.#version,
|
|
377
392
|
welcomeResult.model,
|
|
@@ -423,6 +438,11 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
423
438
|
// Initialize hooks with TUI-based UI context
|
|
424
439
|
await this.initHooksAndCustomTools();
|
|
425
440
|
|
|
441
|
+
// Offer to fix expired cloud credentials before entering the main loop
|
|
442
|
+
if (fixableServices.length > 0) {
|
|
443
|
+
await this.#offerCredentialFixes(fixableServices, services);
|
|
444
|
+
}
|
|
445
|
+
|
|
426
446
|
// Restore mode from session (e.g. plan mode on resume)
|
|
427
447
|
await this.#restoreModeFromSession();
|
|
428
448
|
|
|
@@ -913,6 +933,40 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
913
933
|
}
|
|
914
934
|
}
|
|
915
935
|
|
|
936
|
+
async #offerCredentialFixes(fixable: FixableService[], currentServices: ServiceStatus[]): Promise<void> {
|
|
937
|
+
for (const service of fixable) {
|
|
938
|
+
const confirmed = await this.#extensionUiController.showHookConfirm(
|
|
939
|
+
`${service.name} login`,
|
|
940
|
+
`${service.prompt}. Fix now?`,
|
|
941
|
+
);
|
|
942
|
+
if (!confirmed) continue;
|
|
943
|
+
|
|
944
|
+
this.ui.stop();
|
|
945
|
+
try {
|
|
946
|
+
const proc = Bun.spawn(service.command, {
|
|
947
|
+
stdin: "inherit",
|
|
948
|
+
stdout: "inherit",
|
|
949
|
+
stderr: "inherit",
|
|
950
|
+
});
|
|
951
|
+
await proc.exited;
|
|
952
|
+
} catch (error) {
|
|
953
|
+
logger.warn(`Auto-fix for ${service.name} failed`, { error: String(error) });
|
|
954
|
+
} finally {
|
|
955
|
+
const clearScreen = settings.get("startup.clearScreen");
|
|
956
|
+
this.ui.start(clearScreen);
|
|
957
|
+
this.ui.requestRender(true);
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
const updated = await service.recheck();
|
|
961
|
+
const idx = currentServices.findIndex(s => s.name === service.name);
|
|
962
|
+
if (idx !== -1) {
|
|
963
|
+
currentServices[idx] = updated;
|
|
964
|
+
this.#welcomeComponent?.setServices([...currentServices]);
|
|
965
|
+
this.ui.requestRender();
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
|
|
916
970
|
async #approvePlan(
|
|
917
971
|
planContent: string,
|
|
918
972
|
options: { planFilePath: string; finalPlanFilePath: string },
|
|
@@ -4,7 +4,7 @@ import { Text } from "@f5xc-salesdemos/pi-tui";
|
|
|
4
4
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
5
5
|
import type { Theme, ThemeColor } from "../modes/theme/theme";
|
|
6
6
|
import { highlightCode } from "../modes/theme/theme";
|
|
7
|
-
import { CachedOutputBlock, renderStatusLine } from "../tui";
|
|
7
|
+
import { CachedOutputBlock, F5_TOOL_BORDER_COLOR, renderStatusLine } from "../tui";
|
|
8
8
|
import { formatErrorMessage, replaceTabs } from "./render-utils";
|
|
9
9
|
import type { XcshApiToolDetails } from "./xcsh-api";
|
|
10
10
|
|
|
@@ -209,7 +209,7 @@ export const xcshApiToolRenderer = {
|
|
|
209
209
|
render(width: number): string[] {
|
|
210
210
|
const state = options.isPartial ? "pending" : "success";
|
|
211
211
|
return batchBlock.render(
|
|
212
|
-
{ header: batchHeader, state, sections: batchSections, width, borderColor:
|
|
212
|
+
{ header: batchHeader, state, sections: batchSections, width, borderColor: F5_TOOL_BORDER_COLOR },
|
|
213
213
|
uiTheme,
|
|
214
214
|
);
|
|
215
215
|
},
|
|
@@ -383,7 +383,10 @@ export const xcshApiToolRenderer = {
|
|
|
383
383
|
return {
|
|
384
384
|
render(width: number): string[] {
|
|
385
385
|
const state = options.isPartial ? "pending" : isError ? "error" : "success";
|
|
386
|
-
return outputBlock.render(
|
|
386
|
+
return outputBlock.render(
|
|
387
|
+
{ header, state, sections, width, borderColor: isError ? undefined : F5_TOOL_BORDER_COLOR },
|
|
388
|
+
uiTheme,
|
|
389
|
+
);
|
|
387
390
|
},
|
|
388
391
|
invalidate() {
|
|
389
392
|
outputBlock.invalidate();
|
package/src/tui/output-block.ts
CHANGED
|
@@ -8,6 +8,12 @@ import type { State } from "./types";
|
|
|
8
8
|
import type { RenderCache } from "./utils";
|
|
9
9
|
import { getStateBgColor, Hasher, padToWidth, truncateToWidth } from "./utils";
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Border color override for F5-branded tool renderers.
|
|
13
|
+
* Pass as `borderColor` in OutputBlockOptions to apply F5 red framing regardless of tool state.
|
|
14
|
+
*/
|
|
15
|
+
export const F5_TOOL_BORDER_COLOR: ThemeColor = "border";
|
|
16
|
+
|
|
11
17
|
export interface OutputBlockOptions {
|
|
12
18
|
header?: string;
|
|
13
19
|
headerMeta?: string;
|
|
@@ -33,17 +39,12 @@ export function renderOutputBlock(options: OutputBlockOptions, theme: Theme): st
|
|
|
33
39
|
const v = theme.boxSharp.vertical;
|
|
34
40
|
const cap = h.repeat(3);
|
|
35
41
|
const lineWidth = Math.max(0, width);
|
|
36
|
-
// Border colors: running/pending use accent,
|
|
37
|
-
//
|
|
42
|
+
// Border colors: running/pending use accent, everything else uses dim (gray).
|
|
43
|
+
// Error state uses dim — the red toolErrorBg background is the error signal; no red border on generic tools.
|
|
44
|
+
// borderColorOverride (from options) takes precedence for F5-branded tools (e.g. XC-API); use F5_TOOL_BORDER_COLOR.
|
|
38
45
|
const resolvedBorderColor: ThemeColor =
|
|
39
46
|
borderColorOverride ??
|
|
40
|
-
(state === "
|
|
41
|
-
? "error"
|
|
42
|
-
: state === "warning"
|
|
43
|
-
? "warning"
|
|
44
|
-
: state === "running" || state === "pending"
|
|
45
|
-
? "spinnerAccent"
|
|
46
|
-
: "dim");
|
|
47
|
+
(state === "warning" ? "warning" : state === "running" || state === "pending" ? "spinnerAccent" : "dim");
|
|
47
48
|
const border = (text: string) => theme.fg(resolvedBorderColor, text);
|
|
48
49
|
const bgFn = (() => {
|
|
49
50
|
if (!state || !applyBg) return undefined;
|