@f5xc-salesdemos/xcsh 19.43.1 → 19.43.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.43.1",
4
+ "version": "19.43.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",
@@ -52,13 +52,13 @@
52
52
  "dependencies": {
53
53
  "@agentclientprotocol/sdk": "0.16.1",
54
54
  "@mozilla/readability": "^0.6",
55
- "@f5xc-salesdemos/xcsh-stats": "19.43.1",
56
- "@f5xc-salesdemos/pi-agent-core": "19.43.1",
57
- "@f5xc-salesdemos/pi-ai": "19.43.1",
58
- "@f5xc-salesdemos/pi-natives": "19.43.1",
59
- "@f5xc-salesdemos/pi-resource-management": "19.43.1",
60
- "@f5xc-salesdemos/pi-tui": "19.43.1",
61
- "@f5xc-salesdemos/pi-utils": "19.43.1",
55
+ "@f5xc-salesdemos/xcsh-stats": "19.43.2",
56
+ "@f5xc-salesdemos/pi-agent-core": "19.43.2",
57
+ "@f5xc-salesdemos/pi-ai": "19.43.2",
58
+ "@f5xc-salesdemos/pi-natives": "19.43.2",
59
+ "@f5xc-salesdemos/pi-resource-management": "19.43.2",
60
+ "@f5xc-salesdemos/pi-tui": "19.43.2",
61
+ "@f5xc-salesdemos/pi-utils": "19.43.2",
62
62
  "@sinclair/typebox": "^0.34",
63
63
  "@xterm/headless": "^6.0",
64
64
  "ajv": "^8.20",
@@ -17,17 +17,17 @@ export interface BuildInfo {
17
17
  }
18
18
 
19
19
  export const BUILD_INFO: BuildInfo = {
20
- "version": "19.43.1",
21
- "commit": "5b36c788a2d9ad0fb4a7da83458621d0e995a05a",
22
- "shortCommit": "5b36c78",
20
+ "version": "19.43.2",
21
+ "commit": "09742430243eddeb0ed597e94079c21ceca02d7f",
22
+ "shortCommit": "0974243",
23
23
  "branch": "main",
24
- "tag": "v19.43.1",
25
- "commitDate": "2026-06-24T15:09:09Z",
26
- "buildDate": "2026-06-24T15:34:16.139Z",
24
+ "tag": "v19.43.2",
25
+ "commitDate": "2026-06-24T16:28:28Z",
26
+ "buildDate": "2026-06-24T16:56:22.826Z",
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/5b36c788a2d9ad0fb4a7da83458621d0e995a05a",
32
- "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v19.43.1"
31
+ "commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/09742430243eddeb0ed597e94079c21ceca02d7f",
32
+ "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v19.43.2"
33
33
  };
@@ -41,6 +41,33 @@ export interface RenderMermaidThemedOptions {
41
41
  colorMode?: MermaidColorMode;
42
42
  /** Extra layout options (padding, useAscii, …). */
43
43
  render?: MermaidAsciiRenderOptions;
44
+ /**
45
+ * Available content width. When set (and paddingX isn't overridden), node spacing
46
+ * is expanded toward this width so the diagram uses the available space instead of
47
+ * rendering tiny — capped so small diagrams don't sprawl.
48
+ */
49
+ targetWidth?: number;
50
+ }
51
+
52
+ const DEFAULT_PADDING_X = 2;
53
+ const DEFAULT_PADDING_Y = 1;
54
+ // Candidate horizontal spacings (ascending). Widening paddingX grows width WITHOUT
55
+ // growing height, so we can use much of the terminal; capped so a tiny diagram
56
+ // doesn't sprawl into absurdly long edges on a very wide terminal.
57
+ const PADDING_X_CANDIDATES = [1, 2, 4, 6, 8, 10, 12, 16, 20, 24];
58
+
59
+ /** Largest candidate paddingX whose rendered width still fits targetWidth (>= smallest). */
60
+ function pickPaddingXForWidth(source: string, targetWidth: number): number {
61
+ let best = PADDING_X_CANDIDATES[0]!;
62
+ for (const px of PADDING_X_CANDIDATES) {
63
+ const out = renderMermaidAsciiSafe(source, { colorMode: "none", paddingX: px, paddingY: DEFAULT_PADDING_Y });
64
+ if (out == null) break;
65
+ let w = 0;
66
+ for (const line of out.split("\n")) w = Math.max(w, Bun.stringWidth(line));
67
+ if (w <= targetWidth) best = px;
68
+ else break; // wider candidates only grow; stop early (also avoids costly large-padding renders)
69
+ }
70
+ return best;
44
71
  }
45
72
 
46
73
  /**
@@ -54,25 +81,27 @@ export function renderMermaidThemed(source: string, theme: Theme, opts?: RenderM
54
81
  if (mermaidSourceExceedsLimit(source)) return null;
55
82
 
56
83
  const mode = colorModeFor(theme, opts?.colorMode);
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)}` : "";
84
+ const baseRender = opts?.render ?? {};
85
+ // Resolve horizontal spacing: explicit override wins; else expand toward the target
86
+ // width (capped) so the diagram uses the space; else the compact default.
87
+ const paddingX =
88
+ baseRender.paddingX ??
89
+ (opts?.targetWidth !== undefined ? pickPaddingXForWidth(source, opts.targetWidth) : DEFAULT_PADDING_X);
90
+ const renderOpts: MermaidAsciiRenderOptions = { paddingY: DEFAULT_PADDING_Y, ...baseRender, paddingX };
91
+
92
+ // Layout options change the output, so they must be part of the key — but the
93
+ // compact DEFAULT is treated as "no options" so the key still matches
94
+ // mermaidThemeSignature()/getMermaidAscii() (the inline prerender/lookup path).
95
+ const sigObj: Record<string, unknown> = { ...renderOpts };
96
+ if (sigObj.paddingX === DEFAULT_PADDING_X) delete sigObj.paddingX;
97
+ if (sigObj.paddingY === DEFAULT_PADDING_Y) delete sigObj.paddingY;
98
+ const optsSig = Object.keys(sigObj).length > 0 ? `|${JSON.stringify(sigObj)}` : "";
61
99
  const key = `${Bun.hash(source.trim())}|${mode}:${paletteSignature(theme)}${optsSig}`;
62
100
  const cached = cache.get(key);
63
101
  if (cached !== undefined) return cached;
64
102
 
65
103
  const asciiTheme = buildMermaidAsciiTheme(theme);
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
- });
104
+ const colored = renderMermaidAsciiSafe(source, { ...renderOpts, colorMode: mode, theme: asciiTheme });
76
105
  if (colored == null) {
77
106
  cache.set(key, null);
78
107
  return null;
@@ -1,7 +1,8 @@
1
1
  /**
2
- * Map the active xcsh UI theme onto a mermaid `AsciiTheme` (per-role hues) and a
3
- * cycling list of node-accent ANSI escapes for the per-node tint pass. Reading the
4
- * theme keeps diagrams on-brand and dark/light adaptive.
2
+ * Map the active xcsh UI theme onto a mermaid `AsciiTheme`. The palette is
3
+ * deliberately restrained and professional: neutral gray structure with a single
4
+ * F5-brand accent on the arrowheads — no rainbow of per-node colors. Reading the
5
+ * theme keeps it dark/light adaptive and on-brand.
5
6
  */
6
7
  import type { MermaidAsciiRenderOptions } from "@f5xc-salesdemos/pi-utils";
7
8
  import type { Theme } from "./theme";
@@ -9,35 +10,30 @@ import type { Theme } from "./theme";
9
10
  type AsciiTheme = NonNullable<MermaidAsciiRenderOptions["theme"]>;
10
11
 
11
12
  /**
12
- * Per-role color palette: red node borders, gold labels, blue edges, green
13
- * arrowheads. These show through wherever the per-node tint pass doesn't apply
14
- * (edges, arrows, subgraph frames, and as the fallback when tinting is skipped).
13
+ * Restrained per-role palette neutral grays for the diagram structure, with one
14
+ * brand accent on the arrowheads:
15
+ * labels → neutral text gray
16
+ * borders → soft gray
17
+ * edges → dim gray (recede behind the boxes)
18
+ * arrows → F5 brand accent (the single pop of color)
19
+ * Nodes render uniformly (see buildNodeAccents — no per-node tint).
15
20
  */
16
21
  export function buildMermaidAsciiTheme(theme: Theme): AsciiTheme {
17
22
  return {
18
- fg: theme.getFgHex("mdHeading", "#febc38"), // node/edge labels
19
- border: theme.getFgHex("accent", "#ca260a"), // node + subgraph borders
20
- line: theme.getFgHex("mdLink", "#0088fa"), // edge lines
21
- arrow: theme.getFgHex("success", "#00ff88"), // arrowheads
22
- corner: theme.getFgHex("mdLink", "#0088fa"),
23
- junction: theme.getFgHex("accent", "#ca260a"),
23
+ fg: theme.getFgHex("toolOutput", "#9ca3b0"), // node + edge labels
24
+ border: theme.getFgHex("muted", "#9ca3b0"), // node + subgraph borders
25
+ line: theme.getFgHex("dim", "#6b7280"), // edge lines — subtle
26
+ arrow: theme.getFgHex("accent", "#ca260a"), // arrowheads — single brand accent
27
+ corner: theme.getFgHex("dim", "#6b7280"),
28
+ junction: theme.getFgHex("muted", "#9ca3b0"),
24
29
  };
25
30
  }
26
31
 
27
32
  /**
28
- * ANSI foreground escapes cycled across detected node boxes so each node reads as
29
- * a distinct hue. Drawn from vivid, well-separated theme roles.
33
+ * Per-node accent colors for the tint pass. Intentionally EMPTY: a professional
34
+ * diagram reads as one consistent palette, not a different bright hue per box.
35
+ * With no accents, the tint pass is a no-op and nodes use the role colors above.
30
36
  */
31
- export function buildNodeAccents(theme: Theme): string[] {
32
- const roles = ["chromeAccent", "success", "warning", "mdLink", "accent"] as const;
33
- const seen = new Set<string>();
34
- const accents: string[] = [];
35
- for (const role of roles) {
36
- const ansi = theme.getFgAnsi(role);
37
- if (!seen.has(ansi)) {
38
- seen.add(ansi);
39
- accents.push(ansi);
40
- }
41
- }
42
- return accents;
37
+ export function buildNodeAccents(_theme: Theme): string[] {
38
+ return [];
43
39
  }
@@ -2513,7 +2513,7 @@ export function getMarkdownTheme(): MarkdownTheme {
2513
2513
  symbols: getSymbolTheme(),
2514
2514
  getMermaidAscii: (hash: bigint | number) => getMermaidAscii(hash, mermaidThemeSignature(theme)),
2515
2515
  renderMermaidBlock: (source: string, width: number): string[] | null => {
2516
- const diagram = renderMermaidThemed(source, theme);
2516
+ const diagram = renderMermaidThemed(source, theme, { targetWidth: Math.max(20, width - 4) });
2517
2517
  if (!diagram) return null;
2518
2518
  return renderOutputBlock(
2519
2519
  buildMermaidBlockOptions(diagram, { width, theme, typeLabel: diagramTypeLabel(detectDiagramType(source)) }),
@@ -101,13 +101,16 @@ export const mermaidRenderer = {
101
101
 
102
102
  // Prefer re-rendering from the original source: themed, per-node-tinted, colored.
103
103
  const source = args?.mermaid?.trim();
104
- const diagramText = (source ? renderMermaidThemed(source, uiTheme) : null) ?? plainText;
105
104
  const typeLabel = source ? diagramTypeLabel(detectDiagramType(source)) : "diagram";
106
105
  const artifactId = result.details?.artifactId;
107
106
 
108
107
  const block = new CachedOutputBlock();
109
108
  return {
110
109
  render(width: number): string[] {
110
+ // Expand the diagram toward the available width (inside the frame), then
111
+ // size the frame snugly around the result.
112
+ const targetWidth = Math.max(20, width - 4);
113
+ const diagramText = (source ? renderMermaidThemed(source, uiTheme, { targetWidth }) : null) ?? plainText;
111
114
  return block.render(
112
115
  buildMermaidBlockOptions(diagramText, { width, theme: uiTheme, typeLabel, artifactId }),
113
116
  uiTheme,