@f5xc-salesdemos/xcsh 19.10.0 → 19.11.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/internal-urls/build-info.generated.ts +8 -8
- package/src/modes/components/plugins/state-manager.ts +3 -6
- package/src/modes/components/plugins/utils.ts +6 -0
- package/src/modes/components/welcome-checks.ts +50 -0
- package/src/modes/components/welcome.ts +35 -2
- package/src/modes/interactive-mode.ts +4 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5xc-salesdemos/xcsh",
|
|
4
|
-
"version": "19.
|
|
4
|
+
"version": "19.11.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",
|
|
@@ -50,12 +50,12 @@
|
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@agentclientprotocol/sdk": "0.16.1",
|
|
52
52
|
"@mozilla/readability": "^0.6",
|
|
53
|
-
"@f5xc-salesdemos/xcsh-stats": "19.
|
|
54
|
-
"@f5xc-salesdemos/pi-agent-core": "19.
|
|
55
|
-
"@f5xc-salesdemos/pi-ai": "19.
|
|
56
|
-
"@f5xc-salesdemos/pi-natives": "19.
|
|
57
|
-
"@f5xc-salesdemos/pi-tui": "19.
|
|
58
|
-
"@f5xc-salesdemos/pi-utils": "19.
|
|
53
|
+
"@f5xc-salesdemos/xcsh-stats": "19.11.0",
|
|
54
|
+
"@f5xc-salesdemos/pi-agent-core": "19.11.0",
|
|
55
|
+
"@f5xc-salesdemos/pi-ai": "19.11.0",
|
|
56
|
+
"@f5xc-salesdemos/pi-natives": "19.11.0",
|
|
57
|
+
"@f5xc-salesdemos/pi-tui": "19.11.0",
|
|
58
|
+
"@f5xc-salesdemos/pi-utils": "19.11.0",
|
|
59
59
|
"@sinclair/typebox": "^0.34",
|
|
60
60
|
"@xterm/headless": "^6.0",
|
|
61
61
|
"ajv": "^8.20",
|
|
@@ -17,17 +17,17 @@ export interface BuildInfo {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export const BUILD_INFO: BuildInfo = {
|
|
20
|
-
"version": "19.
|
|
21
|
-
"commit": "
|
|
22
|
-
"shortCommit": "
|
|
20
|
+
"version": "19.11.0",
|
|
21
|
+
"commit": "f1aa8804fd9d391462a8c13d4ff0de70b01e20b8",
|
|
22
|
+
"shortCommit": "f1aa880",
|
|
23
23
|
"branch": "main",
|
|
24
|
-
"tag": "v19.
|
|
25
|
-
"commitDate": "2026-06-
|
|
26
|
-
"buildDate": "2026-06-
|
|
24
|
+
"tag": "v19.11.0",
|
|
25
|
+
"commitDate": "2026-06-05T12:59:46Z",
|
|
26
|
+
"buildDate": "2026-06-05T13:30:16.635Z",
|
|
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.
|
|
31
|
+
"commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/f1aa8804fd9d391462a8c13d4ff0de70b01e20b8",
|
|
32
|
+
"releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v19.11.0"
|
|
33
33
|
};
|
|
@@ -3,12 +3,9 @@ import type { MarketplaceManager } from "../../../extensibility/plugins/marketpl
|
|
|
3
3
|
import type { InstalledPluginSummary, MarketplacePluginEntry } from "../../../extensibility/plugins/marketplace/types";
|
|
4
4
|
import type { DashboardPlugin, PluginDashboardState, PluginTab, PluginTabId } from "./types";
|
|
5
5
|
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
if (result.length > 0 && result.endsWith("-status")) result = result.slice(0, -7);
|
|
10
|
-
return result || name;
|
|
11
|
-
}
|
|
6
|
+
export { normalizePluginDisplayName } from "./utils";
|
|
7
|
+
|
|
8
|
+
import { normalizePluginDisplayName } from "./utils";
|
|
12
9
|
|
|
13
10
|
function npmToDashboard(npm: { name: string; version: string; enabled: boolean }): DashboardPlugin {
|
|
14
11
|
return {
|
|
@@ -1,9 +1,17 @@
|
|
|
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 { MarketplaceManager } from "../../extensibility/plugins/marketplace";
|
|
5
|
+
import {
|
|
6
|
+
getInstalledPluginsRegistryPath,
|
|
7
|
+
getMarketplacesCacheDir,
|
|
8
|
+
getMarketplacesRegistryPath,
|
|
9
|
+
getPluginsCacheDir,
|
|
10
|
+
} from "../../extensibility/plugins/marketplace/registry";
|
|
4
11
|
import { type AuthStatus, ContextService } from "../../services/f5xc-context";
|
|
5
12
|
import { deriveTenantFromUrl } from "../../services/f5xc-env";
|
|
6
13
|
import type { AuthStorage } from "../../session/auth-storage";
|
|
14
|
+
import { normalizePluginDisplayName } from "./plugins/utils";
|
|
7
15
|
|
|
8
16
|
// Startup validation budget. These are longer than validateToken's 3000ms default because
|
|
9
17
|
// the welcome path runs during TLS/DNS cold-start — a single 3s shot races against warm-up
|
|
@@ -214,3 +222,45 @@ export interface FixableService {
|
|
|
214
222
|
command: string[];
|
|
215
223
|
recheck: () => Promise<ServiceStatus>;
|
|
216
224
|
}
|
|
225
|
+
|
|
226
|
+
export interface RecommendedPluginStatus {
|
|
227
|
+
name: string;
|
|
228
|
+
installed: boolean;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export async function checkRecommendedPlugins(): Promise<RecommendedPluginStatus[]> {
|
|
232
|
+
try {
|
|
233
|
+
const mgr = new MarketplaceManager({
|
|
234
|
+
marketplacesRegistryPath: getMarketplacesRegistryPath(),
|
|
235
|
+
installedRegistryPath: getInstalledPluginsRegistryPath(),
|
|
236
|
+
marketplacesCacheDir: getMarketplacesCacheDir(),
|
|
237
|
+
pluginsCacheDir: getPluginsCacheDir(),
|
|
238
|
+
clearPluginRootsCache: () => {},
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
const [marketplaces, installedSummaries] = await Promise.all([
|
|
242
|
+
mgr.listMarketplaces(),
|
|
243
|
+
mgr.listInstalledPlugins(),
|
|
244
|
+
]);
|
|
245
|
+
|
|
246
|
+
const installedIds = new Set(installedSummaries.map(s => s.id));
|
|
247
|
+
const results: RecommendedPluginStatus[] = [];
|
|
248
|
+
|
|
249
|
+
for (const mkt of marketplaces) {
|
|
250
|
+
const available = await mgr.listAvailablePlugins(mkt.name).catch(() => []);
|
|
251
|
+
for (const entry of available) {
|
|
252
|
+
if (!entry.recommended) continue;
|
|
253
|
+
const pluginId = `${entry.name}@${mkt.name}`;
|
|
254
|
+
results.push({
|
|
255
|
+
name: normalizePluginDisplayName(entry.name),
|
|
256
|
+
installed: installedIds.has(pluginId),
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return results.sort((a, b) => a.name.localeCompare(b.name));
|
|
262
|
+
} catch (err) {
|
|
263
|
+
logger.debug("checkRecommendedPlugins failed", { error: String(err) });
|
|
264
|
+
return [];
|
|
265
|
+
}
|
|
266
|
+
}
|
|
@@ -2,7 +2,7 @@ import { type Component, padding, truncateToWidth, visibleWidth } from "@f5xc-sa
|
|
|
2
2
|
import { APP_NAME } from "@f5xc-salesdemos/pi-utils";
|
|
3
3
|
import { theme } from "../../modes/theme/theme";
|
|
4
4
|
import { formatStatusIcon } from "../../services/f5xc-context-indicators";
|
|
5
|
-
import type { ModelStatus, ServiceStatus } from "./welcome-checks";
|
|
5
|
+
import type { ModelStatus, RecommendedPluginStatus, ServiceStatus } from "./welcome-checks";
|
|
6
6
|
|
|
7
7
|
export interface UpdateStatus {
|
|
8
8
|
available: boolean;
|
|
@@ -15,6 +15,7 @@ export class WelcomeComponent implements Component {
|
|
|
15
15
|
private modelStatus: ModelStatus,
|
|
16
16
|
private services: ServiceStatus[] = [],
|
|
17
17
|
private updateStatus?: UpdateStatus,
|
|
18
|
+
private recommendedPlugins: RecommendedPluginStatus[] = [],
|
|
18
19
|
) {}
|
|
19
20
|
invalidate(): void {}
|
|
20
21
|
setModelStatus(status: ModelStatus): void {
|
|
@@ -26,6 +27,9 @@ export class WelcomeComponent implements Component {
|
|
|
26
27
|
setUpdateStatus(status: UpdateStatus | undefined): void {
|
|
27
28
|
this.updateStatus = status;
|
|
28
29
|
}
|
|
30
|
+
setRecommendedPlugins(plugins: RecommendedPluginStatus[]): void {
|
|
31
|
+
this.recommendedPlugins = plugins;
|
|
32
|
+
}
|
|
29
33
|
|
|
30
34
|
render(termWidth: number): string[] {
|
|
31
35
|
const minLeftCol = 48;
|
|
@@ -136,6 +140,12 @@ export class WelcomeComponent implements Component {
|
|
|
136
140
|
lines.push(this.#renderServiceLine(svc));
|
|
137
141
|
}
|
|
138
142
|
}
|
|
143
|
+
if (this.recommendedPlugins.length > 0) {
|
|
144
|
+
lines.push(" Recommended Plugins");
|
|
145
|
+
for (const p of this.recommendedPlugins) {
|
|
146
|
+
lines.push(this.#renderRecommendedLine(p));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
139
149
|
if (this.#showUpdateSection()) {
|
|
140
150
|
lines.push(this.#renderUpdateLine());
|
|
141
151
|
}
|
|
@@ -163,7 +173,11 @@ export class WelcomeComponent implements Component {
|
|
|
163
173
|
lines.push("");
|
|
164
174
|
const coreServices = this.services.filter(s => !s._isPlugin);
|
|
165
175
|
const pluginServices = this.services.filter(s => s._isPlugin);
|
|
166
|
-
const hasContent =
|
|
176
|
+
const hasContent =
|
|
177
|
+
coreServices.length > 0 ||
|
|
178
|
+
pluginServices.length > 0 ||
|
|
179
|
+
this.recommendedPlugins.length > 0 ||
|
|
180
|
+
this.#showUpdateSection();
|
|
167
181
|
if (hasContent) {
|
|
168
182
|
lines.push(separator);
|
|
169
183
|
for (const svc of coreServices) {
|
|
@@ -179,6 +193,18 @@ export class WelcomeComponent implements Component {
|
|
|
179
193
|
}
|
|
180
194
|
}
|
|
181
195
|
}
|
|
196
|
+
if (this.recommendedPlugins.length > 0) {
|
|
197
|
+
lines.push("");
|
|
198
|
+
lines.push(` ${theme.fg("dim", "Recommended Plugins")}`);
|
|
199
|
+
for (const p of this.recommendedPlugins) {
|
|
200
|
+
lines.push(this.#renderRecommendedLine(p));
|
|
201
|
+
}
|
|
202
|
+
const missing = this.recommendedPlugins.filter(p => !p.installed);
|
|
203
|
+
if (missing.length > 0) {
|
|
204
|
+
lines.push(` ${theme.fg("dim", "run: /plugin setup")}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
182
208
|
if (this.#showUpdateSection()) {
|
|
183
209
|
lines.push(this.#renderUpdateLine());
|
|
184
210
|
}
|
|
@@ -187,6 +213,13 @@ export class WelcomeComponent implements Component {
|
|
|
187
213
|
return lines;
|
|
188
214
|
}
|
|
189
215
|
|
|
216
|
+
#renderRecommendedLine(plugin: RecommendedPluginStatus): string {
|
|
217
|
+
if (plugin.installed) {
|
|
218
|
+
return ` ${formatStatusIcon("connected")} ${theme.fg("muted", plugin.name)}`;
|
|
219
|
+
}
|
|
220
|
+
return ` ${theme.fg("dim", "·")} ${theme.fg("dim", plugin.name)}`;
|
|
221
|
+
}
|
|
222
|
+
|
|
190
223
|
#showUpdateSection(): boolean {
|
|
191
224
|
return this.updateStatus?.available === true;
|
|
192
225
|
}
|
|
@@ -53,6 +53,7 @@ import { StatusLineComponent } from "./components/status-line";
|
|
|
53
53
|
import type { ToolExecutionHandle } from "./components/tool-execution";
|
|
54
54
|
import { type UpdateStatus, WelcomeComponent } from "./components/welcome";
|
|
55
55
|
import {
|
|
56
|
+
checkRecommendedPlugins,
|
|
56
57
|
type FixableService,
|
|
57
58
|
mapContextStatus,
|
|
58
59
|
runWelcomeChecks,
|
|
@@ -386,12 +387,15 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
386
387
|
}
|
|
387
388
|
}
|
|
388
389
|
|
|
390
|
+
const recommendedPlugins = !startupQuiet ? await checkRecommendedPlugins().catch(() => []) : [];
|
|
391
|
+
|
|
389
392
|
if (!startupQuiet) {
|
|
390
393
|
this.#welcomeComponent = new WelcomeComponent(
|
|
391
394
|
this.#version,
|
|
392
395
|
welcomeResult.model,
|
|
393
396
|
services,
|
|
394
397
|
this.#initialUpdateStatus,
|
|
398
|
+
recommendedPlugins,
|
|
395
399
|
);
|
|
396
400
|
|
|
397
401
|
// Setup UI layout
|