@f5-sales-demo/xcsh 21.5.5 → 21.5.7

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": "@f5-sales-demo/xcsh",
4
- "version": "21.5.5",
4
+ "version": "21.5.7",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://github.com/f5-sales-demo/xcsh",
7
7
  "author": "Can Boluk",
@@ -61,13 +61,13 @@
61
61
  },
62
62
  "dependencies": {
63
63
  "@agentclientprotocol/sdk": "1.3.0",
64
- "@f5-sales-demo/pi-agent-core": "21.5.5",
65
- "@f5-sales-demo/pi-ai": "21.5.5",
66
- "@f5-sales-demo/pi-natives": "21.5.5",
67
- "@f5-sales-demo/pi-resource-management": "21.5.5",
68
- "@f5-sales-demo/pi-tui": "21.5.5",
69
- "@f5-sales-demo/pi-utils": "21.5.5",
70
- "@f5-sales-demo/xcsh-stats": "21.5.5",
64
+ "@f5-sales-demo/pi-agent-core": "21.5.7",
65
+ "@f5-sales-demo/pi-ai": "21.5.7",
66
+ "@f5-sales-demo/pi-natives": "21.5.7",
67
+ "@f5-sales-demo/pi-resource-management": "21.5.7",
68
+ "@f5-sales-demo/pi-tui": "21.5.7",
69
+ "@f5-sales-demo/pi-utils": "21.5.7",
70
+ "@f5-sales-demo/xcsh-stats": "21.5.7",
71
71
  "@mozilla/readability": "^0.6",
72
72
  "@sinclair/typebox": "^0.34",
73
73
  "@xterm/headless": "^6.0",
@@ -17,17 +17,17 @@ export interface BuildInfo {
17
17
  }
18
18
 
19
19
  export const BUILD_INFO: BuildInfo = {
20
- "version": "21.5.5",
21
- "commit": "f400415fb48f3c9de062439032ee0e79434dccd2",
22
- "shortCommit": "f400415",
20
+ "version": "21.5.7",
21
+ "commit": "08a80d4f1cb5b5010bffe60579e5808d6bdc90ac",
22
+ "shortCommit": "08a80d4",
23
23
  "branch": "main",
24
- "tag": "v21.5.5",
25
- "commitDate": "2026-09-01T23:39:11+00:00",
26
- "buildDate": "2026-09-02T00:09:05.855Z",
24
+ "tag": "v21.5.7",
25
+ "commitDate": "2026-09-02T03:25:55+00:00",
26
+ "buildDate": "2026-09-02T04:00:07.787Z",
27
27
  "dirty": true,
28
28
  "prNumber": "",
29
29
  "repoUrl": "https://github.com/f5-sales-demo/xcsh",
30
30
  "repoSlug": "f5-sales-demo/xcsh",
31
- "commitUrl": "https://github.com/f5-sales-demo/xcsh/commit/f400415fb48f3c9de062439032ee0e79434dccd2",
32
- "releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v21.5.5"
31
+ "commitUrl": "https://github.com/f5-sales-demo/xcsh/commit/08a80d4f1cb5b5010bffe60579e5808d6bdc90ac",
32
+ "releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v21.5.7"
33
33
  };
@@ -0,0 +1,36 @@
1
+ import { type Container, Text } from "@f5-sales-demo/pi-tui";
2
+ import { copyToClipboard } from "../../utils/clipboard";
3
+ import { theme } from "../theme/theme";
4
+
5
+ type AuthLinkContainer = Pick<Container, "addChild">;
6
+
7
+ export interface AuthLinkPresenterOptions {
8
+ copy?: (url: string) => void | Promise<void>;
9
+ platform?: NodeJS.Platform;
10
+ }
11
+
12
+ /** Present a browser authorization target without exposing its long URL as visible terminal text. */
13
+ export function presentAuthLink(
14
+ container: AuthLinkContainer,
15
+ url: string,
16
+ options: AuthLinkPresenterOptions = {},
17
+ ): void {
18
+ const platform = options.platform ?? process.platform;
19
+ const clickHint = platform === "darwin" ? "Cmd+click to open" : "Ctrl+click to open";
20
+ const hyperlink = `\x1b]8;;${url}\x07Open sign-in page\x1b]8;;\x07`;
21
+ container.addChild(new Text(`${theme.fg("accent", hyperlink)} ${theme.fg("dim", `(${clickHint})`)}`, 1, 0));
22
+ container.addChild(
23
+ new Text(
24
+ theme.fg("dim", "Sign-in URL copied when supported. Clipboard availability depends on terminal support."),
25
+ 1,
26
+ 0,
27
+ ),
28
+ );
29
+
30
+ const copy = options.copy ?? copyToClipboard;
31
+ try {
32
+ void Promise.resolve(copy(url)).catch(() => undefined);
33
+ } catch {
34
+ // Clipboard access is best-effort; the OSC 8 link remains available.
35
+ }
36
+ }
@@ -1,5 +1,6 @@
1
1
  // UI Components barrel export
2
2
  export * from "./assistant-message";
3
+ export * from "./auth-link-presenter";
3
4
  export * from "./bash-execution";
4
5
  export * from "./bordered-loader";
5
6
  export * from "./branch-summary-message";
@@ -2,8 +2,14 @@ import { getOAuthProviders } from "@f5-sales-demo/pi-ai";
2
2
  import { Container, getKeybindings, Input, Spacer, Text, type TUI } from "@f5-sales-demo/pi-tui";
3
3
  import { theme } from "../../modes/theme/theme";
4
4
  import { openPath } from "../../utils/open";
5
+ import { presentAuthLink } from "./auth-link-presenter";
5
6
  import { DynamicBorder } from "./dynamic-border";
6
7
 
8
+ interface LoginDialogDependencies {
9
+ openUrl?: (url: string) => void;
10
+ presentLink?: typeof presentAuthLink;
11
+ }
12
+
7
13
  /**
8
14
  * Login dialog component - replaces editor during OAuth login flow
9
15
  */
@@ -19,6 +25,7 @@ export class LoginDialogComponent extends Container {
19
25
  tui: TUI,
20
26
  providerId: string,
21
27
  private onComplete: (success: boolean, message?: string) => void,
28
+ private dependencies: LoginDialogDependencies = {},
22
29
  ) {
23
30
  super();
24
31
  this.#tui = tui;
@@ -73,11 +80,7 @@ export class LoginDialogComponent extends Container {
73
80
  showAuth(url: string, instructions?: string): void {
74
81
  this.#contentContainer.clear();
75
82
  this.#contentContainer.addChild(new Spacer(1));
76
- this.#contentContainer.addChild(new Text(theme.fg("contentAccent", url), 1, 0));
77
-
78
- const clickHint = process.platform === "darwin" ? "Cmd+click to open" : "Ctrl+click to open";
79
- const hyperlink = `\x1b]8;;${url}\x07${clickHint}\x1b]8;;\x07`;
80
- this.#contentContainer.addChild(new Text(theme.fg("dim", hyperlink), 1, 0));
83
+ (this.dependencies.presentLink ?? presentAuthLink)(this.#contentContainer, url);
81
84
 
82
85
  if (instructions) {
83
86
  this.#contentContainer.addChild(new Spacer(1));
@@ -85,7 +88,7 @@ export class LoginDialogComponent extends Container {
85
88
  }
86
89
 
87
90
  // Open browser (best-effort)
88
- openPath(url);
91
+ (this.dependencies.openUrl ?? openPath)(url);
89
92
 
90
93
  this.#tui.requestRender();
91
94
  }
@@ -21,7 +21,6 @@ import {
21
21
  clearSmitheryApiKey,
22
22
  createSmitheryCliAuthSession,
23
23
  getSmitheryApiKey,
24
- getSmitheryLoginUrl,
25
24
  pollSmitheryCliAuthSession,
26
25
  saveSmitheryApiKey,
27
26
  } from "../../mcp/smithery-auth";
@@ -36,6 +35,7 @@ import type { MCPAuthConfig, MCPServerConfig, MCPServerConnection } from "../../
36
35
  import type { OAuthCredential } from "../../session/auth-storage";
37
36
  import { shortenPath } from "../../tools/render-utils";
38
37
  import { openPath } from "../../utils/open";
38
+ import { presentAuthLink } from "../components/auth-link-presenter";
39
39
  import { DynamicBorder } from "../components/dynamic-border";
40
40
  import { MCPAddWizard } from "../components/mcp-add-wizard";
41
41
  import { parseCommandArgs } from "../shared";
@@ -68,6 +68,42 @@ type MCPSearchParsed = {
68
68
  error?: string;
69
69
  };
70
70
 
71
+ interface McpOAuthPresentationDependencies {
72
+ openUrl?: (url: string) => void;
73
+ presentLink?: typeof presentAuthLink;
74
+ }
75
+
76
+ /** Render the MCP controller's browser-authorization state without exposing the raw URL. */
77
+ export function showMcpOAuthAuthorization(
78
+ ctx: Pick<InteractiveModeContext, "chatContainer" | "ui">,
79
+ info: { url: string; instructions?: string },
80
+ dependencies: McpOAuthPresentationDependencies = {},
81
+ ): void {
82
+ const showLink = dependencies.presentLink ?? presentAuthLink;
83
+ const openUrl = dependencies.openUrl ?? openPath;
84
+
85
+ ctx.chatContainer.addChild(new Spacer(1));
86
+ ctx.chatContainer.addChild(new Text(theme.fg("contentAccent", "━━━ OAuth Authorization Required ━━━"), 1, 0));
87
+ ctx.chatContainer.addChild(new Spacer(1));
88
+ showLink(ctx.chatContainer, info.url);
89
+ if (info.instructions) {
90
+ ctx.chatContainer.addChild(new Spacer(1));
91
+ ctx.chatContainer.addChild(new Text(theme.fg("warning", info.instructions), 1, 0));
92
+ }
93
+ ctx.chatContainer.addChild(new Spacer(1));
94
+ ctx.chatContainer.addChild(
95
+ new Text(theme.fg("muted", "Waiting for authorization... (Press Ctrl+C to cancel, 5 minute timeout)"), 1, 0),
96
+ );
97
+ ctx.chatContainer.addChild(new Spacer(1));
98
+ ctx.chatContainer.addChild(new Text(theme.fg("contentAccent", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"), 1, 0));
99
+ ctx.ui.requestRender();
100
+
101
+ openUrl(info.url);
102
+ ctx.chatContainer.addChild(new Spacer(1));
103
+ ctx.chatContainer.addChild(new Text(theme.fg("success", "→ Opening browser automatically..."), 1, 0));
104
+ ctx.ui.requestRender();
105
+ }
106
+
71
107
  export class MCPCommandController {
72
108
  constructor(private ctx: InteractiveModeContext) {}
73
109
 
@@ -518,60 +554,7 @@ export class MCPCommandController {
518
554
  callbackPath,
519
555
  },
520
556
  {
521
- onAuth: (info: { url: string; instructions?: string }) => {
522
- // Show auth URL prominently in chat
523
- this.ctx.chatContainer.addChild(new Spacer(1));
524
- this.ctx.chatContainer.addChild(
525
- new Text(theme.fg("contentAccent", "━━━ OAuth Authorization Required ━━━"), 1, 0),
526
- );
527
- this.ctx.chatContainer.addChild(new Spacer(1));
528
- this.ctx.chatContainer.addChild(
529
- new Text(theme.fg("muted", "Preparing browser authorization..."), 1, 0),
530
- );
531
- this.ctx.chatContainer.addChild(new Spacer(1));
532
- this.ctx.chatContainer.addChild(
533
- new Text(
534
- theme.fg("muted", "Waiting for authorization... (Press Ctrl+C to cancel, 5 minute timeout)"),
535
- 1,
536
- 0,
537
- ),
538
- );
539
- this.ctx.chatContainer.addChild(new Spacer(1));
540
- this.ctx.chatContainer.addChild(
541
- new Text(theme.fg("contentAccent", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"), 1, 0),
542
- );
543
- this.ctx.ui.requestRender();
544
- // Try to open browser automatically
545
- try {
546
- openPath(info.url);
547
-
548
- // Show confirmation that browser should open
549
- this.ctx.chatContainer.addChild(new Spacer(1));
550
- this.ctx.chatContainer.addChild(
551
- new Text(theme.fg("success", "→ Opening browser automatically..."), 1, 0),
552
- );
553
- this.ctx.chatContainer.addChild(new Spacer(1));
554
- this.ctx.chatContainer.addChild(
555
- new Text(theme.fg("muted", "Alternative if browser did not open:"), 1, 0),
556
- );
557
- this.ctx.chatContainer.addChild(
558
- new Text(theme.fg("success", "Copy this exact URL in your browser:"), 1, 0),
559
- );
560
- this.ctx.chatContainer.addChild(new Text(theme.fg("contentAccent", info.url), 1, 0));
561
- this.ctx.ui.requestRender();
562
- } catch (_error) {
563
- // Show error if browser doesn't open
564
- this.ctx.chatContainer.addChild(new Spacer(1));
565
- this.ctx.chatContainer.addChild(
566
- new Text(theme.fg("warning", "→ Could not open browser automatically"), 1, 0),
567
- );
568
- this.ctx.chatContainer.addChild(
569
- new Text(theme.fg("success", "Copy this exact URL in your browser:"), 1, 0),
570
- );
571
- this.ctx.chatContainer.addChild(new Text(theme.fg("contentAccent", info.url), 1, 0));
572
- this.ctx.ui.requestRender();
573
- }
574
- },
557
+ onAuth: (info: { url: string; instructions?: string }) => showMcpOAuthAuthorization(this.ctx, info),
575
558
  onProgress: (message: string) => {
576
559
  this.ctx.chatContainer.addChild(new Spacer(1));
577
560
  this.ctx.chatContainer.addChild(new Text(theme.fg("muted", message), 1, 0));
@@ -1678,23 +1661,22 @@ export class MCPCommandController {
1678
1661
 
1679
1662
  async #handleSmitheryBrowserLogin(): Promise<boolean> {
1680
1663
  const session = await createSmitheryCliAuthSession();
1681
- const fallbackLoginUrl = getSmitheryLoginUrl();
1682
- this.#showMessage(
1683
- [
1684
- "",
1685
- theme.bold("Smithery Login"),
1686
- theme.fg("muted", "Browser authorization started. Complete auth in your browser."),
1687
- theme.fg("dim", "Authorize URL:"),
1688
- theme.fg("contentAccent", session.authUrl),
1689
- theme.fg("dim", `Fallback: ${fallbackLoginUrl}`),
1690
- "",
1691
- ].join("\n"),
1664
+ this.ctx.chatContainer.addChild(new Spacer(1));
1665
+ this.ctx.chatContainer.addChild(new DynamicBorder());
1666
+ this.ctx.chatContainer.addChild(
1667
+ new Text(
1668
+ [
1669
+ theme.bold("Smithery Login"),
1670
+ theme.fg("muted", "Browser authorization started. Complete auth in your browser."),
1671
+ ].join("\n"),
1672
+ 1,
1673
+ 1,
1674
+ ),
1692
1675
  );
1693
- try {
1694
- openPath(session.authUrl);
1695
- } catch {
1696
- // URL is already shown above.
1697
- }
1676
+ presentAuthLink(this.ctx.chatContainer, session.authUrl);
1677
+ this.ctx.chatContainer.addChild(new DynamicBorder());
1678
+ this.ctx.ui.requestRender();
1679
+ openPath(session.authUrl);
1698
1680
 
1699
1681
  const apiKey = await this.#waitForSmitheryCliApiKey(session.sessionId, new AbortController().signal);
1700
1682
  await this.#validateSmitheryApiKey(apiKey);
@@ -48,6 +48,7 @@ import { isSearchProviderPreference, setPreferredImageProvider, setPreferredSear
48
48
  import { setSessionTerminalTitle } from "../../utils/title-generator";
49
49
  import { AgentDashboard } from "../components/agent-dashboard";
50
50
  import { AssistantMessageComponent } from "../components/assistant-message";
51
+ import { presentAuthLink } from "../components/auth-link-presenter";
51
52
  import { ExtensionDashboard } from "../components/extensions";
52
53
  import { GutterBlock } from "../components/gutter-block";
53
54
  import { HistorySearchComponent } from "../components/history-search";
@@ -1243,9 +1244,7 @@ export class SelectorController {
1243
1244
  const loginCallbacks = {
1244
1245
  onAuth: (info: { url: string; instructions?: string }) => {
1245
1246
  this.ctx.chatContainer.addChild(new Spacer(1));
1246
- this.ctx.chatContainer.addChild(new Text(theme.fg("dim", info.url), 1, 0));
1247
- const hyperlink = `\x1b]8;;${info.url}\x07Click here to login\x1b]8;;\x07`;
1248
- this.ctx.chatContainer.addChild(new Text(theme.fg("accent", hyperlink), 1, 0));
1247
+ presentAuthLink(this.ctx.chatContainer, info.url);
1249
1248
  if (info.instructions) {
1250
1249
  this.ctx.chatContainer.addChild(new Spacer(1));
1251
1250
  this.ctx.chatContainer.addChild(new Text(theme.fg("warning", info.instructions), 1, 0));
@@ -1361,7 +1360,7 @@ export class SelectorController {
1361
1360
  this.ctx.showStatus("Authenticating Corporate Vertex with the authorized Google enterprise flow…");
1362
1361
  await authStorage.login("google-vertex", {
1363
1362
  onAuth: info => {
1364
- this.ctx.chatContainer.addChild(new Text(theme.fg("dim", info.url), 1, 0));
1363
+ presentAuthLink(this.ctx.chatContainer, info.url);
1365
1364
  if (isHeadlessTerminal(runtime.environment)) {
1366
1365
  this.ctx.chatContainer.addChild(new Text(theme.fg("dim", VERTEX_MANUAL_LOGIN_TIP), 1, 0));
1367
1366
  } else {