@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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@f5xc-salesdemos/xcsh",
4
- "version": "19.10.0",
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.10.0",
54
- "@f5xc-salesdemos/pi-agent-core": "19.10.0",
55
- "@f5xc-salesdemos/pi-ai": "19.10.0",
56
- "@f5xc-salesdemos/pi-natives": "19.10.0",
57
- "@f5xc-salesdemos/pi-tui": "19.10.0",
58
- "@f5xc-salesdemos/pi-utils": "19.10.0",
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.10.0",
21
- "commit": "d46f8e9c1e3828c21cfa36c04509b3ec562d3d07",
22
- "shortCommit": "d46f8e9",
20
+ "version": "19.11.0",
21
+ "commit": "f1aa8804fd9d391462a8c13d4ff0de70b01e20b8",
22
+ "shortCommit": "f1aa880",
23
23
  "branch": "main",
24
- "tag": "v19.10.0",
25
- "commitDate": "2026-06-05T04:49:35Z",
26
- "buildDate": "2026-06-05T05:11:51.351Z",
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/d46f8e9c1e3828c21cfa36c04509b3ec562d3d07",
32
- "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v19.10.0"
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 function normalizePluginDisplayName(name: string): string {
7
- let result = name;
8
- if (result.startsWith("f5xc-")) result = result.slice(5);
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 {
@@ -0,0 +1,6 @@
1
+ export function normalizePluginDisplayName(name: string): string {
2
+ let result = name;
3
+ if (result.startsWith("f5xc-")) result = result.slice(5);
4
+ if (result.length > 0 && result.endsWith("-status")) result = result.slice(0, -7);
5
+ return result || name;
6
+ }
@@ -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 = coreServices.length > 0 || pluginServices.length > 0 || this.#showUpdateSection();
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