@f5xc-salesdemos/xcsh 19.42.0 → 19.42.2
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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5xc-salesdemos/xcsh",
|
|
4
|
-
"version": "19.42.
|
|
4
|
+
"version": "19.42.2",
|
|
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",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"fix": "biome check --write --unsafe . && bun run format-prompts && bun run generate-docs-index && bun run generate-api-spec-index && bun run generate-build-info",
|
|
40
40
|
"fmt": "biome format --write . && bun run format-prompts",
|
|
41
41
|
"format-prompts": "bun scripts/format-prompts.ts",
|
|
42
|
+
"mermaid:gallery": "bun scripts/mermaid-gallery.ts",
|
|
42
43
|
"generate-docs-index": "bun scripts/generate-docs-index.ts",
|
|
43
44
|
"generate-api-spec-index": "bun scripts/generate-api-spec-index.ts",
|
|
44
45
|
"generate-branding-index": "bun scripts/generate-branding-index.ts",
|
|
@@ -51,13 +52,13 @@
|
|
|
51
52
|
"dependencies": {
|
|
52
53
|
"@agentclientprotocol/sdk": "0.16.1",
|
|
53
54
|
"@mozilla/readability": "^0.6",
|
|
54
|
-
"@f5xc-salesdemos/xcsh-stats": "19.42.
|
|
55
|
-
"@f5xc-salesdemos/pi-agent-core": "19.42.
|
|
56
|
-
"@f5xc-salesdemos/pi-ai": "19.42.
|
|
57
|
-
"@f5xc-salesdemos/pi-natives": "19.42.
|
|
58
|
-
"@f5xc-salesdemos/pi-resource-management": "19.42.
|
|
59
|
-
"@f5xc-salesdemos/pi-tui": "19.42.
|
|
60
|
-
"@f5xc-salesdemos/pi-utils": "19.42.
|
|
55
|
+
"@f5xc-salesdemos/xcsh-stats": "19.42.2",
|
|
56
|
+
"@f5xc-salesdemos/pi-agent-core": "19.42.2",
|
|
57
|
+
"@f5xc-salesdemos/pi-ai": "19.42.2",
|
|
58
|
+
"@f5xc-salesdemos/pi-natives": "19.42.2",
|
|
59
|
+
"@f5xc-salesdemos/pi-resource-management": "19.42.2",
|
|
60
|
+
"@f5xc-salesdemos/pi-tui": "19.42.2",
|
|
61
|
+
"@f5xc-salesdemos/pi-utils": "19.42.2",
|
|
61
62
|
"@sinclair/typebox": "^0.34",
|
|
62
63
|
"@xterm/headless": "^6.0",
|
|
63
64
|
"ajv": "^8.20",
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* F5 XC Mermaid — visual gallery harness for human UAT.
|
|
4
|
+
*
|
|
5
|
+
* Renders the F5 Distributed Cloud mermaid sample library through the real
|
|
6
|
+
* display pipeline (themed per-role palette + per-node tint) so you can visually
|
|
7
|
+
* inspect the colorized output in your own terminal and give feedback.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* bun scripts/mermaid-gallery.ts # all samples, dark theme, truecolor
|
|
11
|
+
* bun scripts/mermaid-gallery.ts --theme light
|
|
12
|
+
* bun scripts/mermaid-gallery.ts --color ansi256 # none | ansi256 | truecolor
|
|
13
|
+
* bun scripts/mermaid-gallery.ts --ascii # ASCII box chars instead of Unicode
|
|
14
|
+
* bun scripts/mermaid-gallery.ts --type flowchart # filter by diagram type
|
|
15
|
+
* bun scripts/mermaid-gallery.ts --filter waf # regex over id/category/prompt
|
|
16
|
+
* bun scripts/mermaid-gallery.ts --list # list samples, don't render
|
|
17
|
+
*
|
|
18
|
+
* Tip: pipe through `less -R` to page while keeping color.
|
|
19
|
+
*/
|
|
20
|
+
import type { MermaidColorMode } from "@f5xc-salesdemos/pi-utils";
|
|
21
|
+
import { renderMermaidThemed } from "../src/modes/theme/mermaid-cache";
|
|
22
|
+
import { getThemeByName } from "../src/modes/theme/theme";
|
|
23
|
+
import { XC_MERMAID_SAMPLES, type XcMermaidSample } from "../test/fixtures/xc-mermaid-samples";
|
|
24
|
+
|
|
25
|
+
interface Args {
|
|
26
|
+
theme: string;
|
|
27
|
+
color: MermaidColorMode;
|
|
28
|
+
ascii: boolean;
|
|
29
|
+
type?: string;
|
|
30
|
+
filter?: RegExp;
|
|
31
|
+
list: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function parseArgs(argv: string[]): Args {
|
|
35
|
+
const args: Args = { theme: "xcsh-dark", color: "truecolor", ascii: false, list: false };
|
|
36
|
+
for (let i = 0; i < argv.length; i++) {
|
|
37
|
+
const a = argv[i];
|
|
38
|
+
const next = () => argv[++i] ?? "";
|
|
39
|
+
switch (a) {
|
|
40
|
+
case "--theme":
|
|
41
|
+
args.theme = next() === "light" ? "xcsh-light" : "xcsh-dark";
|
|
42
|
+
break;
|
|
43
|
+
case "--color":
|
|
44
|
+
args.color = next() as MermaidColorMode;
|
|
45
|
+
break;
|
|
46
|
+
case "--ascii":
|
|
47
|
+
args.ascii = true;
|
|
48
|
+
break;
|
|
49
|
+
case "--type":
|
|
50
|
+
args.type = next();
|
|
51
|
+
break;
|
|
52
|
+
case "--filter":
|
|
53
|
+
args.filter = new RegExp(next(), "i");
|
|
54
|
+
break;
|
|
55
|
+
case "--list":
|
|
56
|
+
args.list = true;
|
|
57
|
+
break;
|
|
58
|
+
case "--help":
|
|
59
|
+
case "-h":
|
|
60
|
+
console.log(
|
|
61
|
+
"bun scripts/mermaid-gallery.ts [--theme dark|light] [--color none|ansi256|truecolor] [--ascii] [--type <t>] [--filter <regex>] [--list]",
|
|
62
|
+
);
|
|
63
|
+
process.exit(0);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return args;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function selectSamples(args: Args): XcMermaidSample[] {
|
|
70
|
+
return XC_MERMAID_SAMPLES.filter(s => {
|
|
71
|
+
if (args.type && s.type !== args.type) return false;
|
|
72
|
+
if (args.filter && !args.filter.test(`${s.id} ${s.category} ${s.prompt}`)) return false;
|
|
73
|
+
return true;
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function main(): Promise<void> {
|
|
78
|
+
const args = parseArgs(process.argv.slice(2));
|
|
79
|
+
const theme = await getThemeByName(args.theme);
|
|
80
|
+
if (!theme) {
|
|
81
|
+
console.error(`Unknown theme: ${args.theme}`);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
const samples = selectSamples(args);
|
|
85
|
+
const width = Math.min(process.stdout.columns ?? 100, 110);
|
|
86
|
+
const rule = (ch: string) => theme.fg("accent", ch.repeat(width));
|
|
87
|
+
|
|
88
|
+
console.log(`\n${rule("━")}`);
|
|
89
|
+
console.log(
|
|
90
|
+
theme.fg("mdHeading", " F5 XC Mermaid Gallery") +
|
|
91
|
+
theme.fg(
|
|
92
|
+
"muted",
|
|
93
|
+
` theme=${args.theme} color=${args.color} ascii=${args.ascii} samples=${samples.length}`,
|
|
94
|
+
),
|
|
95
|
+
);
|
|
96
|
+
console.log(rule("━"));
|
|
97
|
+
|
|
98
|
+
if (args.list) {
|
|
99
|
+
for (const [i, s] of samples.entries()) {
|
|
100
|
+
console.log(
|
|
101
|
+
` ${theme.fg("dim", String(i + 1).padStart(2, "0"))} ` +
|
|
102
|
+
`${theme.fg("accent", s.type.padEnd(10))} ${theme.fg("mdHeading", s.id.padEnd(28))} ${theme.fg("muted", s.category)}`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
console.log("");
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
for (const [i, s] of samples.entries()) {
|
|
110
|
+
console.log(
|
|
111
|
+
`\n${theme.fg("accent", "━━━")} ${theme.fg("dim", `[${i + 1}/${samples.length}]`)} ${theme.fg("mdHeading", s.category)} ${theme.fg("muted", "·")} ${theme.fg("success", s.type)} ${theme.fg("accent", "━".repeat(Math.max(0, width - s.category.length - s.type.length - 18)))}`,
|
|
112
|
+
);
|
|
113
|
+
console.log(` ${theme.fg("dim", "▸")} ${theme.fg("mdLink", s.id)}`);
|
|
114
|
+
console.log(` ${theme.fg("muted", `"${s.prompt}"`)}\n`);
|
|
115
|
+
|
|
116
|
+
const out = renderMermaidThemed(s.source, theme, { colorMode: args.color, render: { useAscii: args.ascii } });
|
|
117
|
+
if (out == null) {
|
|
118
|
+
console.log(` ${theme.fg("error", "⚠ failed to render")}\n`);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
for (const line of out.split("\n")) console.log(` ${line}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
console.log(`\n${rule("━")}`);
|
|
125
|
+
console.log(
|
|
126
|
+
theme.fg("muted", ` Rendered ${samples.length} sample(s). Inspect the colors above and note anything to tune `) +
|
|
127
|
+
theme.fg("dim", "(borders, labels, edges, arrows, per-node tints, contrast)."),
|
|
128
|
+
);
|
|
129
|
+
console.log(rule("━") + "\n");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
await main();
|
|
@@ -17,17 +17,17 @@ export interface BuildInfo {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export const BUILD_INFO: BuildInfo = {
|
|
20
|
-
"version": "19.42.
|
|
21
|
-
"commit": "
|
|
22
|
-
"shortCommit": "
|
|
20
|
+
"version": "19.42.2",
|
|
21
|
+
"commit": "a018af25c2adec4357b1aa63213417ef9331ef18",
|
|
22
|
+
"shortCommit": "a018af2",
|
|
23
23
|
"branch": "main",
|
|
24
|
-
"tag": "v19.42.
|
|
25
|
-
"commitDate": "2026-06-
|
|
26
|
-
"buildDate": "2026-06-
|
|
24
|
+
"tag": "v19.42.2",
|
|
25
|
+
"commitDate": "2026-06-24T03:15:55Z",
|
|
26
|
+
"buildDate": "2026-06-24T03:45:49.205Z",
|
|
27
27
|
"dirty": true,
|
|
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/v19.42.
|
|
31
|
+
"commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/a018af25c2adec4357b1aa63213417ef9331ef18",
|
|
32
|
+
"releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v19.42.2"
|
|
33
33
|
};
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
logger,
|
|
4
4
|
type MermaidAsciiRenderOptions,
|
|
5
5
|
type MermaidColorMode,
|
|
6
|
+
mermaidSourceExceedsLimit,
|
|
6
7
|
pickColorMode,
|
|
7
8
|
renderMermaidAsciiSafe,
|
|
8
9
|
tintMermaidNodes,
|
|
@@ -48,13 +49,30 @@ export interface RenderMermaidThemedOptions {
|
|
|
48
49
|
* Result is memoized per (source, theme, colorMode). Returns null on parse failure.
|
|
49
50
|
*/
|
|
50
51
|
export function renderMermaidThemed(source: string, theme: Theme, opts?: RenderMermaidThemedOptions): string | null {
|
|
52
|
+
// Oversized graphs can hang the synchronous pathfinder for tens of seconds; skip
|
|
53
|
+
// them so the display falls back to the raw code block instead of freezing the UI.
|
|
54
|
+
if (mermaidSourceExceedsLimit(source)) return null;
|
|
55
|
+
|
|
51
56
|
const mode = colorModeFor(theme, opts?.colorMode);
|
|
52
|
-
|
|
57
|
+
// Layout options (useAscii, padding, …) change the output, so they must be part
|
|
58
|
+
// of the key. Omitted when absent so the key matches mermaidThemeSignature() —
|
|
59
|
+
// the no-options form used by the inline-markdown prerender/lookup path.
|
|
60
|
+
const optsSig = opts?.render && Object.keys(opts.render).length > 0 ? `|${JSON.stringify(opts.render)}` : "";
|
|
61
|
+
const key = `${Bun.hash(source.trim())}|${mode}:${paletteSignature(theme)}${optsSig}`;
|
|
53
62
|
const cached = cache.get(key);
|
|
54
63
|
if (cached !== undefined) return cached;
|
|
55
64
|
|
|
56
65
|
const asciiTheme = buildMermaidAsciiTheme(theme);
|
|
57
|
-
|
|
66
|
+
// Compact defaults: beautiful-mermaid's paddingX/paddingY of 5 spreads nodes far
|
|
67
|
+
// apart and bloats the diagram. Tighter spacing reads more elegantly and helps it
|
|
68
|
+
// fit the transcript width. Callers can still override via opts.render.
|
|
69
|
+
const colored = renderMermaidAsciiSafe(source, {
|
|
70
|
+
paddingX: 2,
|
|
71
|
+
paddingY: 1,
|
|
72
|
+
...opts?.render,
|
|
73
|
+
colorMode: mode,
|
|
74
|
+
theme: asciiTheme,
|
|
75
|
+
});
|
|
58
76
|
if (colored == null) {
|
|
59
77
|
cache.set(key, null);
|
|
60
78
|
return null;
|
|
@@ -10,7 +10,6 @@ import type { RenderMermaidToolDetails } from "./render-mermaid";
|
|
|
10
10
|
import { addSection, formatErrorMessage, replaceTabs } from "./render-utils";
|
|
11
11
|
|
|
12
12
|
const TOOL_TITLE = "Mermaid";
|
|
13
|
-
const MAX_DIAGRAM_LINES = 40;
|
|
14
13
|
|
|
15
14
|
/** Human-friendly caption for the diagram-type badge in the block header. */
|
|
16
15
|
function diagramTypeLabel(type: MermaidDiagramType): string {
|
|
@@ -77,8 +76,9 @@ export const mermaidRenderer = {
|
|
|
77
76
|
const caption = source ? diagramTypeLabel(detectDiagramType(source)) : "diagram";
|
|
78
77
|
|
|
79
78
|
// Keep the diagram's own colors — do NOT recolor each line a flat tool color.
|
|
79
|
+
// Show the FULL diagram: truncating a diagram with "… N more lines" defeats the purpose.
|
|
80
80
|
const diagramLines = diagramText.split("\n").map(line => replaceTabs(line));
|
|
81
|
-
addSection(sections, "Diagram", diagramLines, uiTheme
|
|
81
|
+
addSection(sections, "Diagram", diagramLines, uiTheme);
|
|
82
82
|
|
|
83
83
|
if (result.details?.artifactId) {
|
|
84
84
|
meta.push(uiTheme.fg("dim", `artifact:${result.details.artifactId.slice(0, 8)}`));
|
|
@@ -98,7 +98,12 @@ export const mermaidRenderer = {
|
|
|
98
98
|
return {
|
|
99
99
|
render(width: number): string[] {
|
|
100
100
|
const state = options.isPartial ? "pending" : "success";
|
|
101
|
-
return outputBlock.render(
|
|
101
|
+
return outputBlock.render(
|
|
102
|
+
// Clip, don't wrap: a diagram is a fixed grid — word-wrapping reflows it
|
|
103
|
+
// and breaks alignment. Wide diagrams are truncated to the block width.
|
|
104
|
+
{ header, state, sections, width, borderColor: F5_TOOL_BORDER_COLOR, wrapContent: false },
|
|
105
|
+
uiTheme,
|
|
106
|
+
);
|
|
102
107
|
},
|
|
103
108
|
invalidate() {
|
|
104
109
|
outputBlock.invalidate();
|
|
@@ -4,7 +4,12 @@ import type {
|
|
|
4
4
|
AgentToolResult,
|
|
5
5
|
AgentToolUpdateCallback,
|
|
6
6
|
} from "@f5xc-salesdemos/pi-agent-core";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
type MermaidAsciiRenderOptions,
|
|
9
|
+
mermaidSourceExceedsLimit,
|
|
10
|
+
prompt,
|
|
11
|
+
renderMermaidAsciiSafe,
|
|
12
|
+
} from "@f5xc-salesdemos/pi-utils";
|
|
8
13
|
import { type Static, Type } from "@sinclair/typebox";
|
|
9
14
|
import renderMermaidDescription from "../prompts/tools/render-mermaid.md" with { type: "text" };
|
|
10
15
|
import type { ToolSession } from "./index";
|
|
@@ -55,7 +60,24 @@ export class RenderMermaidTool implements AgentTool<typeof renderMermaidSchema,
|
|
|
55
60
|
_onUpdate?: AgentToolUpdateCallback<RenderMermaidToolDetails>,
|
|
56
61
|
_context?: AgentToolContext,
|
|
57
62
|
): Promise<AgentToolResult<RenderMermaidToolDetails>> {
|
|
58
|
-
|
|
63
|
+
// Reject oversized graphs up front: beautiful-mermaid's pathfinder can hang
|
|
64
|
+
// for tens of seconds and then throw `RangeError: Out of memory` on them.
|
|
65
|
+
// Throwing here (fast) surfaces a clean tool error the agent can recover from.
|
|
66
|
+
if (mermaidSourceExceedsLimit(params.mermaid)) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
"Mermaid diagram is too large to render safely. Reduce the number of nodes/edges and try again.",
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Safe variant: a parse failure or memory blow-up becomes null rather than a
|
|
73
|
+
// raw RangeError, so we can throw a clear, actionable message instead.
|
|
74
|
+
const ascii = renderMermaidAsciiSafe(params.mermaid, sanitizeRenderConfig(params.config));
|
|
75
|
+
if (ascii == null || ascii.trim() === "") {
|
|
76
|
+
throw new Error(
|
|
77
|
+
"Failed to render the Mermaid diagram — the syntax may be unsupported or too complex. Use newline-separated statements (no ';') and try a simpler diagram.",
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
59
81
|
const { path: artifactPath, id: artifactId } =
|
|
60
82
|
(await this.session.allocateOutputArtifact?.("render_mermaid")) ?? {};
|
|
61
83
|
if (artifactPath) {
|
package/src/tui/output-block.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type { Theme, ThemeColor } from "../modes/theme/theme";
|
|
|
6
6
|
import { getImageLineMask } from "../utils/image-passthrough";
|
|
7
7
|
import type { State } from "./types";
|
|
8
8
|
import type { RenderCache } from "./utils";
|
|
9
|
-
import { getStateBgColor, Hasher, padToWidth, truncateToWidth } from "./utils";
|
|
9
|
+
import { Ellipsis, getStateBgColor, Hasher, padToWidth, truncateToWidth } from "./utils";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Border color override for F5-branded tool renderers.
|
|
@@ -23,6 +23,12 @@ export interface OutputBlockOptions {
|
|
|
23
23
|
applyBg?: boolean;
|
|
24
24
|
/** Override the state-derived border color. Always takes precedence, including on error. Use for branded core tools. */
|
|
25
25
|
borderColor?: ThemeColor;
|
|
26
|
+
/**
|
|
27
|
+
* Word-wrap content lines to fit the inner width (default). Set false to CLIP
|
|
28
|
+
* (truncate) instead — required for pre-formatted grid content like mermaid
|
|
29
|
+
* diagrams, where wrapping reflows characters and destroys alignment.
|
|
30
|
+
*/
|
|
31
|
+
wrapContent?: boolean;
|
|
26
32
|
}
|
|
27
33
|
|
|
28
34
|
export function renderOutputBlock(options: OutputBlockOptions, theme: Theme): string[] {
|
|
@@ -34,6 +40,7 @@ export function renderOutputBlock(options: OutputBlockOptions, theme: Theme): st
|
|
|
34
40
|
width,
|
|
35
41
|
applyBg = true,
|
|
36
42
|
borderColor: borderColorOverride,
|
|
43
|
+
wrapContent = true,
|
|
37
44
|
} = options;
|
|
38
45
|
const h = theme.boxSharp.horizontal;
|
|
39
46
|
const v = theme.boxSharp.vertical;
|
|
@@ -100,7 +107,11 @@ export function renderOutputBlock(options: OutputBlockOptions, theme: Theme): st
|
|
|
100
107
|
lines.push(line);
|
|
101
108
|
continue;
|
|
102
109
|
}
|
|
103
|
-
const wrappedLines =
|
|
110
|
+
const wrappedLines = wrapContent
|
|
111
|
+
? wrapTextWithAnsi(line.trimEnd(), contentWidth)
|
|
112
|
+
: // Clip cleanly with no trailing ellipsis — a column of "…" down the
|
|
113
|
+
// right edge of a clipped diagram is clutter, not information.
|
|
114
|
+
[truncateToWidth(line.trimEnd(), contentWidth, Ellipsis.Omit)];
|
|
104
115
|
for (const wrappedLine of wrappedLines) {
|
|
105
116
|
const innerPadding = padding(Math.max(0, contentWidth - visibleWidth(wrappedLine)));
|
|
106
117
|
const fullLine = `${contentPrefix}${wrappedLine}${innerPadding}${contentSuffix}`;
|
|
@@ -150,6 +161,7 @@ export class CachedOutputBlock {
|
|
|
150
161
|
h.optional(options.state);
|
|
151
162
|
h.bool(options.applyBg ?? true);
|
|
152
163
|
h.optional(options.borderColor);
|
|
164
|
+
h.bool(options.wrapContent ?? true);
|
|
153
165
|
if (options.sections) {
|
|
154
166
|
for (const s of options.sections) {
|
|
155
167
|
h.optional(s.label);
|