@f5xc-salesdemos/xcsh 18.90.0 → 18.91.1

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/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.91.0] - 2026-06-01
6
+
7
+ ### Changed
8
+
9
+ - Welcome screen fully plugin-driven: removed all hardcoded cloud service stubs (`getFixableServices` empty stub, `checkProfileStatus` dead code). Only F5 XC Context remains as a built-in service. All cloud connector status checks now come exclusively from the Extension API `registerServiceStatus()`. ([#1076](https://github.com/f5xc-salesdemos/xcsh/issues/1076))
10
+
5
11
  ## [18.90.0] - 2026-06-01
6
12
 
7
13
  ### Added
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@f5xc-salesdemos/xcsh",
4
- "version": "18.90.0",
4
+ "version": "18.91.1",
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": "18.90.0",
54
- "@f5xc-salesdemos/pi-agent-core": "18.90.0",
55
- "@f5xc-salesdemos/pi-ai": "18.90.0",
56
- "@f5xc-salesdemos/pi-natives": "18.90.0",
57
- "@f5xc-salesdemos/pi-tui": "18.90.0",
58
- "@f5xc-salesdemos/pi-utils": "18.90.0",
53
+ "@f5xc-salesdemos/xcsh-stats": "18.91.1",
54
+ "@f5xc-salesdemos/pi-agent-core": "18.91.1",
55
+ "@f5xc-salesdemos/pi-ai": "18.91.1",
56
+ "@f5xc-salesdemos/pi-natives": "18.91.1",
57
+ "@f5xc-salesdemos/pi-tui": "18.91.1",
58
+ "@f5xc-salesdemos/pi-utils": "18.91.1",
59
59
  "@sinclair/typebox": "^0.34",
60
60
  "@xterm/headless": "^6.0",
61
61
  "ajv": "^8.18",
@@ -939,6 +939,7 @@ export interface RegisteredCommand {
939
939
 
940
940
  export interface ServiceStatusContribution {
941
941
  name: string;
942
+ group?: string;
942
943
  check: () => Promise<{ state: "connected" | "unauthenticated" | "unavailable"; hint?: string }>;
943
944
  fix?: {
944
945
  prompt: string;
@@ -17,17 +17,17 @@ export interface BuildInfo {
17
17
  }
18
18
 
19
19
  export const BUILD_INFO: BuildInfo = {
20
- "version": "18.90.0",
21
- "commit": "b39669e1b23f62c71f7feb98e1881444f415a783",
22
- "shortCommit": "b39669e",
20
+ "version": "18.91.1",
21
+ "commit": "722eac01533203da9bd0bd3ddafb10c38f851d5c",
22
+ "shortCommit": "722eac0",
23
23
  "branch": "main",
24
- "tag": "v18.90.0",
25
- "commitDate": "2026-06-01T02:24:32Z",
26
- "buildDate": "2026-06-01T02:46:05.746Z",
24
+ "tag": "v18.91.1",
25
+ "commitDate": "2026-06-01T21:58:38Z",
26
+ "buildDate": "2026-06-01T22:19:50.728Z",
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/b39669e1b23f62c71f7feb98e1881444f415a783",
32
- "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.90.0"
31
+ "commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/722eac01533203da9bd0bd3ddafb10c38f851d5c",
32
+ "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.91.1"
33
33
  };
@@ -1,7 +1,6 @@
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 { loadProfile } from "../../internal-urls/user-profile";
5
4
  import { type AuthStatus, ContextService } from "../../services/f5xc-context";
6
5
  import { deriveTenantFromUrl } from "../../services/f5xc-env";
7
6
  import type { AuthStorage } from "../../session/auth-storage";
@@ -185,6 +184,8 @@ export interface ServiceStatus {
185
184
  name: string;
186
185
  state: ServiceState;
187
186
  hint?: string;
187
+ _isPlugin?: boolean;
188
+ _group?: string;
188
189
  }
189
190
 
190
191
  export function mapContextStatus(status: WelcomeContextStatus): ServiceStatus {
@@ -207,54 +208,9 @@ export function mapContextStatus(status: WelcomeContextStatus): ServiceStatus {
207
208
  }
208
209
  }
209
210
 
210
- export type ProfileCheckState = "current" | "stale" | "missing";
211
-
212
- export interface WelcomeProfileStatus {
213
- state: ProfileCheckState;
214
- name?: string;
215
- updatedAt?: string;
216
- staleDays?: number;
217
- }
218
-
219
- const PROFILE_STALE_HOURS = 24;
220
-
221
- /** Check user profile freshness. Returns undefined only on unexpected error. */
222
- export async function checkProfileStatus(): Promise<WelcomeProfileStatus | undefined> {
223
- try {
224
- const profile = await loadProfile();
225
- if (!profile.givenName && !profile.familyName) {
226
- return { state: "missing" };
227
- }
228
- const name = [profile.givenName, profile.familyName].filter(Boolean).join(" ");
229
- const updatedAt = profile.updatedAt;
230
-
231
- if (!updatedAt) {
232
- return { state: "stale", name };
233
- }
234
-
235
- const ageHours = (Date.now() - new Date(updatedAt).getTime()) / (1000 * 60 * 60);
236
- if (ageHours > PROFILE_STALE_HOURS) {
237
- return {
238
- state: "stale",
239
- name,
240
- updatedAt,
241
- staleDays: Math.floor(ageHours / 24),
242
- };
243
- }
244
-
245
- return { state: "current", name, updatedAt };
246
- } catch {
247
- return { state: "missing" };
248
- }
249
- }
250
-
251
211
  export interface FixableService {
252
212
  name: string;
253
213
  prompt: string;
254
214
  command: string[];
255
215
  recheck: () => Promise<ServiceStatus>;
256
216
  }
257
-
258
- export function getFixableServices(): FixableService[] {
259
- return [];
260
- }
@@ -122,15 +122,37 @@ export class WelcomeComponent implements Component {
122
122
 
123
123
  #measureStatusWidth(): number {
124
124
  const lines: string[] = [" Model Provider", ...this.#renderModelStatus()];
125
- for (const svc of this.services) {
125
+ const coreServices = this.services.filter(s => !s._isPlugin);
126
+ const pluginServices = this.services.filter(s => s._isPlugin);
127
+ for (const svc of coreServices) {
126
128
  lines.push(this.#renderServiceLine(svc));
127
129
  }
130
+ if (pluginServices.length > 0) {
131
+ const groups = this.#groupPluginServices(pluginServices);
132
+ for (const [groupName] of groups) {
133
+ lines.push(` ${groupName}`);
134
+ }
135
+ for (const svc of pluginServices) {
136
+ lines.push(this.#renderServiceLine(svc));
137
+ }
138
+ }
128
139
  if (this.#showUpdateSection()) {
129
140
  lines.push(this.#renderUpdateLine());
130
141
  }
131
142
  return Math.max(...lines.map(l => visibleWidth(l)));
132
143
  }
133
144
 
145
+ #groupPluginServices(plugins: ServiceStatus[]): Map<string, ServiceStatus[]> {
146
+ const groups = new Map<string, ServiceStatus[]>();
147
+ for (const svc of plugins) {
148
+ const groupName = svc._group ?? "Plugins";
149
+ const list = groups.get(groupName) ?? [];
150
+ list.push(svc);
151
+ groups.set(groupName, list);
152
+ }
153
+ return groups;
154
+ }
155
+
134
156
  #buildStatusLines(rightCol: number): string[] {
135
157
  const lines: string[] = [];
136
158
  const separatorWidth = Math.max(0, rightCol - 2);
@@ -139,11 +161,24 @@ export class WelcomeComponent implements Component {
139
161
  lines.push(` ${theme.bold(theme.fg("contentAccent", "Model Provider"))}`);
140
162
  lines.push(...this.#renderModelStatus());
141
163
  lines.push("");
142
- if (this.services.length > 0 || this.#showUpdateSection()) {
164
+ const coreServices = this.services.filter(s => !s._isPlugin);
165
+ const pluginServices = this.services.filter(s => s._isPlugin);
166
+ const hasContent = coreServices.length > 0 || pluginServices.length > 0 || this.#showUpdateSection();
167
+ if (hasContent) {
143
168
  lines.push(separator);
144
- for (const svc of this.services) {
169
+ for (const svc of coreServices) {
145
170
  lines.push(this.#renderServiceLine(svc));
146
171
  }
172
+ if (pluginServices.length > 0) {
173
+ const groups = this.#groupPluginServices(pluginServices);
174
+ for (const [groupName, groupServices] of groups) {
175
+ lines.push("");
176
+ lines.push(` ${theme.fg("dim", groupName)}`);
177
+ for (const svc of groupServices) {
178
+ lines.push(this.#renderServiceLine(svc));
179
+ }
180
+ }
181
+ }
147
182
  if (this.#showUpdateSection()) {
148
183
  lines.push(this.#renderUpdateLine());
149
184
  }
@@ -54,7 +54,6 @@ import type { ToolExecutionHandle } from "./components/tool-execution";
54
54
  import { type UpdateStatus, WelcomeComponent } from "./components/welcome";
55
55
  import {
56
56
  type FixableService,
57
- getFixableServices,
58
57
  mapContextStatus,
59
58
  runWelcomeChecks,
60
59
  type ServiceStatus,
@@ -346,15 +345,20 @@ export class InteractiveMode implements InteractiveModeContext {
346
345
  for (const contribution of pluginContributions) {
347
346
  try {
348
347
  const status = await contribution.check();
349
- services.push({ name: contribution.name, ...status });
348
+ services.push({ name: contribution.name, ...status, _isPlugin: true, _group: contribution.group });
350
349
  } catch {
351
- services.push({ name: contribution.name, state: "unavailable", hint: "check failed" });
350
+ services.push({
351
+ name: contribution.name,
352
+ state: "unavailable",
353
+ hint: "check failed",
354
+ _isPlugin: true,
355
+ _group: contribution.group,
356
+ });
352
357
  }
353
358
  }
354
359
  }
355
360
 
356
- const fixableServices: FixableService[] =
357
- !startupQuiet && welcomeResult.model.state === "connected" ? getFixableServices() : [];
361
+ const fixableServices: FixableService[] = [];
358
362
 
359
363
  // Add fixable services from plugins
360
364
  if (this.session.extensionRunner) {
@@ -559,6 +563,12 @@ export class InteractiveMode implements InteractiveModeContext {
559
563
  finishPendingSubmission(input: SubmittedUserInput): void {
560
564
  if (this.#pendingSubmittedInput === input) {
561
565
  this.#pendingSubmittedInput = undefined;
566
+ this.#pendingWorkingMessage = undefined;
567
+ if (this.loadingAnimation) {
568
+ this.loadingAnimation.stop();
569
+ this.loadingAnimation = undefined;
570
+ this.statusContainer.clear();
571
+ }
562
572
  }
563
573
  }
564
574
 
@@ -625,9 +625,15 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
625
625
  for (const contribution of pluginContributions) {
626
626
  try {
627
627
  const status = await contribution.check();
628
- services.push({ name: contribution.name, ...status });
628
+ services.push({ name: contribution.name, ...status, _isPlugin: true, _group: contribution.group });
629
629
  } catch {
630
- services.push({ name: contribution.name, state: "unavailable", hint: "check failed" });
630
+ services.push({
631
+ name: contribution.name,
632
+ state: "unavailable",
633
+ hint: "check failed",
634
+ _isPlugin: true,
635
+ _group: contribution.group,
636
+ });
631
637
  }
632
638
  }
633
639
  }