@f5-sales-demo/xcsh 21.5.6 → 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 +8 -8
- package/src/internal-urls/build-info.generated.ts +8 -8
- package/src/modes/components/auth-link-presenter.ts +36 -0
- package/src/modes/components/index.ts +1 -0
- package/src/modes/components/login-dialog.ts +9 -6
- package/src/modes/controllers/mcp-command-controller.ts +53 -71
- package/src/modes/controllers/selector-controller.ts +3 -4
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.
|
|
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.
|
|
65
|
-
"@f5-sales-demo/pi-ai": "21.5.
|
|
66
|
-
"@f5-sales-demo/pi-natives": "21.5.
|
|
67
|
-
"@f5-sales-demo/pi-resource-management": "21.5.
|
|
68
|
-
"@f5-sales-demo/pi-tui": "21.5.
|
|
69
|
-
"@f5-sales-demo/pi-utils": "21.5.
|
|
70
|
-
"@f5-sales-demo/xcsh-stats": "21.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.
|
|
21
|
-
"commit": "
|
|
22
|
-
"shortCommit": "
|
|
20
|
+
"version": "21.5.7",
|
|
21
|
+
"commit": "08a80d4f1cb5b5010bffe60579e5808d6bdc90ac",
|
|
22
|
+
"shortCommit": "08a80d4",
|
|
23
23
|
"branch": "main",
|
|
24
|
-
"tag": "v21.5.
|
|
25
|
-
"commitDate": "2026-09-
|
|
26
|
-
"buildDate": "2026-09-
|
|
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/
|
|
32
|
-
"releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v21.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
|
+
}
|
|
@@ -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
|
|
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
|
-
|
|
1682
|
-
this
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
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
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
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
|
|
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
|
|
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 {
|