@f5xc-salesdemos/xcsh 18.5.5 → 18.7.0
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/src/config/settings-schema.ts +12 -0
- package/src/config/settings.ts +12 -1
- package/src/internal-urls/build-info-runtime.ts +10 -0
- package/src/internal-urls/build-info.generated.ts +8 -8
- package/src/main.ts +1 -0
- package/src/modes/components/welcome-checks.ts +37 -2
- package/src/modes/components/welcome.ts +12 -7
- package/src/modes/theme/defaults/xcsh-dark.json +1 -1
- package/src/modes/theme/defaults/xcsh-light.json +1 -1
- package/src/modes/theme/theme.ts +42 -2
- package/src/prompts/system/system-prompt.md +12 -0
- package/src/services/f5xc-profile-indicators.ts +16 -0
- package/src/services/f5xc-table.ts +5 -6
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.7.0",
|
|
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",
|
|
@@ -47,12 +47,12 @@
|
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@agentclientprotocol/sdk": "0.16.1",
|
|
49
49
|
"@mozilla/readability": "^0.6",
|
|
50
|
-
"@f5xc-salesdemos/xcsh-stats": "18.
|
|
51
|
-
"@f5xc-salesdemos/pi-agent-core": "18.
|
|
52
|
-
"@f5xc-salesdemos/pi-ai": "18.
|
|
53
|
-
"@f5xc-salesdemos/pi-natives": "18.
|
|
54
|
-
"@f5xc-salesdemos/pi-tui": "18.
|
|
55
|
-
"@f5xc-salesdemos/pi-utils": "18.
|
|
50
|
+
"@f5xc-salesdemos/xcsh-stats": "18.7.0",
|
|
51
|
+
"@f5xc-salesdemos/pi-agent-core": "18.7.0",
|
|
52
|
+
"@f5xc-salesdemos/pi-ai": "18.7.0",
|
|
53
|
+
"@f5xc-salesdemos/pi-natives": "18.7.0",
|
|
54
|
+
"@f5xc-salesdemos/pi-tui": "18.7.0",
|
|
55
|
+
"@f5xc-salesdemos/pi-utils": "18.7.0",
|
|
56
56
|
"@sinclair/typebox": "^0.34",
|
|
57
57
|
"@xterm/headless": "^6.0",
|
|
58
58
|
"ajv": "^8.18",
|
|
@@ -263,6 +263,18 @@ export const SETTINGS_SCHEMA = {
|
|
|
263
263
|
},
|
|
264
264
|
},
|
|
265
265
|
|
|
266
|
+
"theme.forceSlot": {
|
|
267
|
+
type: "enum",
|
|
268
|
+
values: ["auto", "dark", "light"] as const,
|
|
269
|
+
default: "auto",
|
|
270
|
+
ui: {
|
|
271
|
+
tab: "appearance",
|
|
272
|
+
label: "Force Theme Slot",
|
|
273
|
+
description: "Override auto dark/light detection (useful inside tmux where OSC 11 is unreliable)",
|
|
274
|
+
submenu: true,
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
|
|
266
278
|
symbolPreset: {
|
|
267
279
|
type: "enum",
|
|
268
280
|
values: ["unicode", "nerd", "ascii"] as const,
|
package/src/config/settings.ts
CHANGED
|
@@ -26,7 +26,13 @@ import { YAML } from "bun";
|
|
|
26
26
|
import { type Settings as SettingsCapabilityItem, settingsCapability } from "../capability/settings";
|
|
27
27
|
import type { ModelRole } from "../config/model-registry";
|
|
28
28
|
import { loadCapability } from "../discovery";
|
|
29
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
isLightTheme,
|
|
31
|
+
setAutoThemeMapping,
|
|
32
|
+
setColorBlindMode,
|
|
33
|
+
setForceSlot,
|
|
34
|
+
setSymbolPreset,
|
|
35
|
+
} from "../modes/theme/theme";
|
|
30
36
|
import { AgentStorage } from "../session/agent-storage";
|
|
31
37
|
import { type EditMode, normalizeEditMode } from "../utils/edit-mode";
|
|
32
38
|
import { withFileLock } from "./file-lock";
|
|
@@ -649,6 +655,11 @@ const SETTING_HOOKS: Partial<Record<SettingPath, SettingHook<any>>> = {
|
|
|
649
655
|
setAutoThemeMapping("light", value);
|
|
650
656
|
}
|
|
651
657
|
},
|
|
658
|
+
"theme.forceSlot": value => {
|
|
659
|
+
if (value === "auto" || value === "dark" || value === "light") {
|
|
660
|
+
setForceSlot(value);
|
|
661
|
+
}
|
|
662
|
+
},
|
|
652
663
|
symbolPreset: value => {
|
|
653
664
|
if (typeof value === "string" && (value === "unicode" || value === "nerd" || value === "ascii")) {
|
|
654
665
|
setSymbolPreset(value).catch(err => {
|
|
@@ -110,6 +110,16 @@ export function renderAboutDoc(info: RuntimeBuildInfo): string {
|
|
|
110
110
|
`- This commit on GitHub: ${info.commitUrl}`,
|
|
111
111
|
`- Release for this version: ${info.releaseUrl}`,
|
|
112
112
|
"",
|
|
113
|
+
"## Product knowledge",
|
|
114
|
+
"",
|
|
115
|
+
"xcsh serves F5 Distributed Cloud sales engineers. Product documentation is",
|
|
116
|
+
"federated across the f5xc-salesdemos GitHub organization. Entry point:",
|
|
117
|
+
"https://f5xc-salesdemos.github.io/docs/llms.txt",
|
|
118
|
+
"",
|
|
119
|
+
"Each product repo publishes: llms.txt (index with sidebar nav), custom sets",
|
|
120
|
+
"at /_llms-txt/{topic}.txt, per-page content at /{slug}.md, plus",
|
|
121
|
+
"llms-small.txt (compact) and llms-full.txt (complete).",
|
|
122
|
+
"",
|
|
113
123
|
"## What to do when asked about xcsh itself",
|
|
114
124
|
"",
|
|
115
125
|
"1. Confirm the user is running the version above. If unsure, ask them to run `xcsh --version`.",
|
|
@@ -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.7.0",
|
|
21
|
+
"commit": "134e551dc4f0c8c481931d64c92d6c2f704aad9e",
|
|
22
|
+
"shortCommit": "134e551",
|
|
23
23
|
"branch": "main",
|
|
24
|
-
"tag": "v18.
|
|
25
|
-
"commitDate": "2026-04-
|
|
26
|
-
"buildDate": "2026-04-
|
|
24
|
+
"tag": "v18.7.0",
|
|
25
|
+
"commitDate": "2026-04-22T03:10:13-04:00",
|
|
26
|
+
"buildDate": "2026-04-22T07:28:38.519Z",
|
|
27
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/134e551dc4f0c8c481931d64c92d6c2f704aad9e",
|
|
32
|
+
"releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.7.0"
|
|
33
33
|
};
|
package/src/main.ts
CHANGED
|
@@ -704,6 +704,7 @@ export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise<v
|
|
|
704
704
|
settings.get("colorBlindMode"),
|
|
705
705
|
settings.get("theme.dark"),
|
|
706
706
|
settings.get("theme.light"),
|
|
707
|
+
settings.get("theme.forceSlot"),
|
|
707
708
|
);
|
|
708
709
|
|
|
709
710
|
let scopedModels: ScopedModel[] = [];
|
|
@@ -1,9 +1,44 @@
|
|
|
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
3
|
import { logger } from "@f5xc-salesdemos/pi-utils";
|
|
4
|
-
import { ProfileService } from "../../services/f5xc-profile";
|
|
4
|
+
import { type AuthStatus, ProfileService } from "../../services/f5xc-profile";
|
|
5
5
|
import type { AuthStorage } from "../../session/auth-storage";
|
|
6
6
|
|
|
7
|
+
// Startup validation budget. These are longer than validateToken's 3000ms default because
|
|
8
|
+
// the welcome path runs during TLS/DNS cold-start — a single 3s shot races against warm-up
|
|
9
|
+
// and falsely reports offline for profiles that reconnect cleanly moments later.
|
|
10
|
+
const STARTUP_FIRST_TIMEOUT_MS = 4000;
|
|
11
|
+
const STARTUP_RETRY_TIMEOUT_MS = 5000;
|
|
12
|
+
const STARTUP_RETRY_DELAY_MS = 500;
|
|
13
|
+
|
|
14
|
+
type ProfileValidator = (opts: { timeoutMs: number }) => Promise<{ status: AuthStatus; latencyMs?: number }>;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Runs the profile validator once with a startup-sized timeout; if the result is `offline`
|
|
18
|
+
* (the only transient class — auth_error/connected/unknown are definitive), waits briefly
|
|
19
|
+
* to let DNS/TLS warm up, then tries once more with a longer timeout.
|
|
20
|
+
*/
|
|
21
|
+
export async function validateProfileWithStartupRetry(
|
|
22
|
+
validate: ProfileValidator,
|
|
23
|
+
options?: {
|
|
24
|
+
firstTimeoutMs?: number;
|
|
25
|
+
retryTimeoutMs?: number;
|
|
26
|
+
retryDelayMs?: number;
|
|
27
|
+
},
|
|
28
|
+
): Promise<{ status: AuthStatus; latencyMs?: number }> {
|
|
29
|
+
const firstTimeoutMs = options?.firstTimeoutMs ?? STARTUP_FIRST_TIMEOUT_MS;
|
|
30
|
+
const retryTimeoutMs = options?.retryTimeoutMs ?? STARTUP_RETRY_TIMEOUT_MS;
|
|
31
|
+
const retryDelayMs = options?.retryDelayMs ?? STARTUP_RETRY_DELAY_MS;
|
|
32
|
+
|
|
33
|
+
const first = await validate({ timeoutMs: firstTimeoutMs });
|
|
34
|
+
if (first.status !== "offline") return first;
|
|
35
|
+
|
|
36
|
+
if (retryDelayMs > 0) {
|
|
37
|
+
await new Promise(resolve => setTimeout(resolve, retryDelayMs));
|
|
38
|
+
}
|
|
39
|
+
return await validate({ timeoutMs: retryTimeoutMs });
|
|
40
|
+
}
|
|
41
|
+
|
|
7
42
|
export type ModelCheckState = "no_provider" | "connected" | "auth_error";
|
|
8
43
|
|
|
9
44
|
export interface ModelStatus {
|
|
@@ -117,7 +152,7 @@ async function checkProfileStatus(): Promise<WelcomeProfileStatus> {
|
|
|
117
152
|
}
|
|
118
153
|
|
|
119
154
|
const name = status.activeProfileName ?? "default";
|
|
120
|
-
const result = await profileService.validateToken();
|
|
155
|
+
const result = await validateProfileWithStartupRetry(opts => profileService.validateToken(opts));
|
|
121
156
|
|
|
122
157
|
switch (result.status) {
|
|
123
158
|
case "connected":
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type Component, padding, truncateToWidth, visibleWidth } from "@f5xc-salesdemos/pi-tui";
|
|
2
2
|
import { APP_NAME } from "@f5xc-salesdemos/pi-utils";
|
|
3
3
|
import { theme } from "../../modes/theme/theme";
|
|
4
|
+
import { formatStatusIcon } from "../../services/f5xc-profile-indicators";
|
|
4
5
|
import type { ModelStatus, WelcomeProfileStatus } from "./welcome-checks";
|
|
5
6
|
|
|
6
7
|
export interface UpdateStatus {
|
|
@@ -200,15 +201,17 @@ export class WelcomeComponent implements Component {
|
|
|
200
201
|
const p = provider ?? "unknown";
|
|
201
202
|
switch (state) {
|
|
202
203
|
case "connected":
|
|
203
|
-
return [
|
|
204
|
+
return [
|
|
205
|
+
` ${formatStatusIcon("connected")} ${theme.fg("muted", p)} ${theme.fg("dim", `\u2014 connected (${latencyMs ?? "?"}ms)`)}`,
|
|
206
|
+
];
|
|
204
207
|
case "auth_error":
|
|
205
208
|
return [
|
|
206
|
-
`
|
|
209
|
+
` ${formatStatusIcon("error")} ${theme.fg("muted", p)} ${theme.fg("error", "\u2014 connection failed")}`,
|
|
207
210
|
` ${theme.fg("dim", "Run /login to reconnect")}`,
|
|
208
211
|
];
|
|
209
212
|
case "no_provider":
|
|
210
213
|
return [
|
|
211
|
-
`
|
|
214
|
+
` ${formatStatusIcon("error")} ${theme.fg("error", "No model provider configured")}`,
|
|
212
215
|
` ${theme.fg("dim", "Run /login to connect")}`,
|
|
213
216
|
];
|
|
214
217
|
}
|
|
@@ -220,20 +223,22 @@ export class WelcomeComponent implements Component {
|
|
|
220
223
|
const n = name ?? "default";
|
|
221
224
|
switch (state) {
|
|
222
225
|
case "connected":
|
|
223
|
-
return [
|
|
226
|
+
return [
|
|
227
|
+
` ${formatStatusIcon("connected")} ${theme.fg("muted", n)} ${theme.fg("dim", `\u2014 connected (${latencyMs ?? "?"}ms)`)}`,
|
|
228
|
+
];
|
|
224
229
|
case "auth_error":
|
|
225
230
|
return [
|
|
226
|
-
`
|
|
231
|
+
` ${formatStatusIcon("error")} ${theme.fg("muted", n)} ${theme.fg("error", "\u2014 token invalid")}`,
|
|
227
232
|
` ${theme.fg("dim", "Run /profile to update")}`,
|
|
228
233
|
];
|
|
229
234
|
case "offline":
|
|
230
235
|
return [
|
|
231
|
-
`
|
|
236
|
+
` ${formatStatusIcon("warning")} ${theme.fg("muted", n)} ${theme.fg("warning", "\u2014 unreachable")}`,
|
|
232
237
|
` ${theme.fg("dim", "Check network, /profile")}`,
|
|
233
238
|
];
|
|
234
239
|
case "no_profile":
|
|
235
240
|
return [
|
|
236
|
-
`
|
|
241
|
+
` ${formatStatusIcon("warning")} ${theme.fg("warning", "No profile configured")}`,
|
|
237
242
|
` ${theme.fg("dim", "Run /profile create <name> <url> <token>")}`,
|
|
238
243
|
];
|
|
239
244
|
}
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
"statusLineContextPctWarningBg": 22,
|
|
106
106
|
"statusLineContextPctPurpleBg": 94,
|
|
107
107
|
"statusLineContextPctErrorBg": 88,
|
|
108
|
-
"statusLineProfileF5xcBg":
|
|
108
|
+
"statusLineProfileF5xcBg": "f5Red",
|
|
109
109
|
"statusLineProfileF5xcFg": 231,
|
|
110
110
|
"pythonMode": "#f0c040",
|
|
111
111
|
"syntaxControl": "#569CD6"
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
"statusLineContextPctWarningBg": 22,
|
|
108
108
|
"statusLineContextPctPurpleBg": 94,
|
|
109
109
|
"statusLineContextPctErrorBg": 88,
|
|
110
|
-
"statusLineProfileF5xcBg":
|
|
110
|
+
"statusLineProfileF5xcBg": "f5Red",
|
|
111
111
|
"statusLineProfileF5xcFg": 231,
|
|
112
112
|
"pythonMode": "warningAmber",
|
|
113
113
|
"syntaxControl": "f5Red"
|
package/src/modes/theme/theme.ts
CHANGED
|
@@ -1894,9 +1894,33 @@ function detectTerminalBackground(): "dark" | "light" {
|
|
|
1894
1894
|
return "dark";
|
|
1895
1895
|
}
|
|
1896
1896
|
|
|
1897
|
+
/**
|
|
1898
|
+
* Theme-slot override mode. `"auto"` uses terminal-appearance detection to
|
|
1899
|
+
* pick between the dark and light slots. `"dark"` / `"light"` skip detection
|
|
1900
|
+
* and always load that slot — useful when running inside tmux where OSC 11
|
|
1901
|
+
* queries and `COLORFGBG` don't reliably surface (see issue #228).
|
|
1902
|
+
*/
|
|
1903
|
+
export type ThemeForceSlot = "auto" | "dark" | "light";
|
|
1904
|
+
|
|
1905
|
+
/**
|
|
1906
|
+
* Pure slot-resolution helper. Exported so it can be unit-tested without a
|
|
1907
|
+
* terminal. When `forceSlot` is `"dark"` or `"light"`, returns that slot's
|
|
1908
|
+
* theme name unconditionally. Otherwise maps `detected` bg to the matching
|
|
1909
|
+
* slot.
|
|
1910
|
+
*/
|
|
1911
|
+
export function resolveThemeSlot(
|
|
1912
|
+
forceSlot: ThemeForceSlot,
|
|
1913
|
+
detected: "dark" | "light",
|
|
1914
|
+
darkTheme: string,
|
|
1915
|
+
lightTheme: string,
|
|
1916
|
+
): string {
|
|
1917
|
+
if (forceSlot === "dark") return darkTheme;
|
|
1918
|
+
if (forceSlot === "light") return lightTheme;
|
|
1919
|
+
return detected === "light" ? lightTheme : darkTheme;
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1897
1922
|
function getDefaultTheme(): string {
|
|
1898
|
-
|
|
1899
|
-
return bg === "light" ? autoLightTheme : autoDarkTheme;
|
|
1923
|
+
return resolveThemeSlot(currentForceSlot, detectTerminalBackground(), autoDarkTheme, autoLightTheme);
|
|
1900
1924
|
}
|
|
1901
1925
|
|
|
1902
1926
|
// ============================================================================
|
|
@@ -1918,6 +1942,7 @@ var sigwinchHandler: (() => void) | undefined;
|
|
|
1918
1942
|
var autoDetectedTheme: boolean = false;
|
|
1919
1943
|
var autoDarkTheme: string = "xcsh-dark";
|
|
1920
1944
|
var autoLightTheme: string = "xcsh-light";
|
|
1945
|
+
var currentForceSlot: ThemeForceSlot = "auto";
|
|
1921
1946
|
var onThemeChangeCallback: (() => void) | undefined;
|
|
1922
1947
|
var themeLoadRequestId: number = 0;
|
|
1923
1948
|
|
|
@@ -1934,10 +1959,12 @@ export async function initTheme(
|
|
|
1934
1959
|
colorBlindMode?: boolean,
|
|
1935
1960
|
darkTheme?: string,
|
|
1936
1961
|
lightTheme?: string,
|
|
1962
|
+
forceSlot?: ThemeForceSlot,
|
|
1937
1963
|
): Promise<void> {
|
|
1938
1964
|
autoDetectedTheme = true;
|
|
1939
1965
|
autoDarkTheme = darkTheme ?? "xcsh-dark";
|
|
1940
1966
|
autoLightTheme = lightTheme ?? "xcsh-light";
|
|
1967
|
+
currentForceSlot = forceSlot ?? "auto";
|
|
1941
1968
|
const name = getDefaultTheme();
|
|
1942
1969
|
currentThemeName = name;
|
|
1943
1970
|
currentSymbolPresetOverride = symbolPreset;
|
|
@@ -2032,6 +2059,19 @@ export function setAutoThemeMapping(mode: "dark" | "light", themeName: string):
|
|
|
2032
2059
|
reevaluateAutoTheme("setAutoThemeMapping");
|
|
2033
2060
|
}
|
|
2034
2061
|
|
|
2062
|
+
/**
|
|
2063
|
+
* Update the forced-slot override. Callers pass `"auto"` to restore the
|
|
2064
|
+
* detection chain, or `"dark"` / `"light"` to skip detection entirely and
|
|
2065
|
+
* always load that slot. Re-evaluates the active theme immediately.
|
|
2066
|
+
*
|
|
2067
|
+
* Primary use case: tmux sessions where OSC 11 and COLORFGBG don't surface
|
|
2068
|
+
* a reliable bg signal (issue #228).
|
|
2069
|
+
*/
|
|
2070
|
+
export function setForceSlot(slot: ThemeForceSlot): void {
|
|
2071
|
+
currentForceSlot = slot;
|
|
2072
|
+
reevaluateAutoTheme("setForceSlot");
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2035
2075
|
/**
|
|
2036
2076
|
* Called when the terminal detects a dark/light appearance change.
|
|
2037
2077
|
* The terminal layer queries OSC 11 (background color) and computes luminance;
|
|
@@ -189,6 +189,18 @@ Most tools resolve custom protocol URLs to internal resources (not web URLs):
|
|
|
189
189
|
|
|
190
190
|
In `bash`, URIs auto-resolve to filesystem paths (e.g., `python skill://my-skill/scripts/init.py`).
|
|
191
191
|
|
|
192
|
+
# Product knowledge
|
|
193
|
+
|
|
194
|
+
For F5 Distributed Cloud product questions (capabilities, demos, APIs, configuration),
|
|
195
|
+
you **MUST** start at the live knowledge index:
|
|
196
|
+
|
|
197
|
+
`https://f5xc-salesdemos.github.io/docs/llms.txt`
|
|
198
|
+
|
|
199
|
+
Follow links from there to the specific product's own `llms.txt`, then fetch only the
|
|
200
|
+
tier you need: a custom set (`/_llms-txt/{topic}.txt`), a single page (`/{slug}.md`),
|
|
201
|
+
or `llms-small.txt` / `llms-full.txt` when breadth is required. Content is live —
|
|
202
|
+
never assume a cached snapshot is current.
|
|
203
|
+
|
|
192
204
|
# Skills
|
|
193
205
|
|
|
194
206
|
Specialized knowledge packs loaded for this session. Relative paths in skill files resolve against the skill directory.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { theme } from "../modes/theme/theme";
|
|
2
|
+
|
|
3
|
+
export type StatusCategory = "connected" | "error" | "warning" | "unknown";
|
|
4
|
+
|
|
5
|
+
export function formatStatusIcon(status: StatusCategory): string {
|
|
6
|
+
switch (status) {
|
|
7
|
+
case "connected":
|
|
8
|
+
return theme.fg("success", "●");
|
|
9
|
+
case "error":
|
|
10
|
+
return theme.fg("error", "○");
|
|
11
|
+
case "warning":
|
|
12
|
+
return theme.fg("warning", "⚠");
|
|
13
|
+
case "unknown":
|
|
14
|
+
return theme.fg("dim", "○");
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type { AuthStatus } from "./f5xc-profile";
|
|
2
|
+
import { formatStatusIcon } from "./f5xc-profile-indicators";
|
|
2
3
|
|
|
3
4
|
// F5 Brand Red — same as welcome.ts line 203
|
|
4
5
|
const F5_RED = "\x1b[38;5;160m";
|
|
5
|
-
const GREEN = "\x1b[38;5;34m";
|
|
6
|
-
const RED_TEXT = "\x1b[38;5;196m";
|
|
7
6
|
const RESET = "\x1b[0m";
|
|
8
7
|
const BOLD = "\x1b[1m";
|
|
9
8
|
|
|
@@ -25,13 +24,13 @@ export function formatAuthIndicator(status: AuthStatus, latencyMs?: number): str
|
|
|
25
24
|
const ms = latencyMs !== undefined ? ` (${latencyMs}ms)` : "";
|
|
26
25
|
switch (status) {
|
|
27
26
|
case "connected":
|
|
28
|
-
return `${
|
|
27
|
+
return `${formatStatusIcon("connected")} Connected${ms}`;
|
|
29
28
|
case "auth_error":
|
|
30
|
-
return `${
|
|
29
|
+
return `${formatStatusIcon("error")} Auth Error${ms}`;
|
|
31
30
|
case "offline":
|
|
32
|
-
return `${
|
|
31
|
+
return `${formatStatusIcon("warning")} Offline`;
|
|
33
32
|
default:
|
|
34
|
-
return
|
|
33
|
+
return `${formatStatusIcon("unknown")} Unknown`;
|
|
35
34
|
}
|
|
36
35
|
}
|
|
37
36
|
|