@f5xc-salesdemos/xcsh 18.44.0 → 18.45.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@f5xc-salesdemos/xcsh",
4
- "version": "18.44.0",
4
+ "version": "18.45.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",
@@ -48,12 +48,12 @@
48
48
  "dependencies": {
49
49
  "@agentclientprotocol/sdk": "0.16.1",
50
50
  "@mozilla/readability": "^0.6",
51
- "@f5xc-salesdemos/xcsh-stats": "18.44.0",
52
- "@f5xc-salesdemos/pi-agent-core": "18.44.0",
53
- "@f5xc-salesdemos/pi-ai": "18.44.0",
54
- "@f5xc-salesdemos/pi-natives": "18.44.0",
55
- "@f5xc-salesdemos/pi-tui": "18.44.0",
56
- "@f5xc-salesdemos/pi-utils": "18.44.0",
51
+ "@f5xc-salesdemos/xcsh-stats": "18.45.1",
52
+ "@f5xc-salesdemos/pi-agent-core": "18.45.1",
53
+ "@f5xc-salesdemos/pi-ai": "18.45.1",
54
+ "@f5xc-salesdemos/pi-natives": "18.45.1",
55
+ "@f5xc-salesdemos/pi-tui": "18.45.1",
56
+ "@f5xc-salesdemos/pi-utils": "18.45.1",
57
57
  "@sinclair/typebox": "^0.34",
58
58
  "@xterm/headless": "^6.0",
59
59
  "ajv": "^8.18",
@@ -17,17 +17,17 @@ export interface BuildInfo {
17
17
  }
18
18
 
19
19
  export const BUILD_INFO: BuildInfo = {
20
- "version": "18.44.0",
21
- "commit": "f7fdd6b7707603c64fc09e1ad591af368250a1a2",
22
- "shortCommit": "f7fdd6b",
20
+ "version": "18.45.1",
21
+ "commit": "b636990701856264c79e2544a376fc16c07ffe9f",
22
+ "shortCommit": "b636990",
23
23
  "branch": "main",
24
- "tag": "v18.44.0",
25
- "commitDate": "2026-05-06T02:37:32Z",
26
- "buildDate": "2026-05-06T02:56:20.883Z",
24
+ "tag": "v18.45.1",
25
+ "commitDate": "2026-05-06T04:30:34Z",
26
+ "buildDate": "2026-05-06T04:51:28.530Z",
27
27
  "dirty": false,
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/f7fdd6b7707603c64fc09e1ad591af368250a1a2",
32
- "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.44.0"
31
+ "commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/b636990701856264c79e2544a376fc16c07ffe9f",
32
+ "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.45.1"
33
33
  };
package/src/main.ts CHANGED
@@ -56,7 +56,6 @@ import type { AgentSession } from "./session/agent-session";
56
56
  import { resolveResumableSession, type SessionInfo, SessionManager } from "./session/session-manager";
57
57
  import { resolvePromptInput } from "./system-prompt";
58
58
  import type { LspStartupServerInfo } from "./tools";
59
- import { getChangelogPath, getNewEntries, parseChangelog } from "./utils/changelog";
60
59
  import type { EventBus } from "./utils/event-bus";
61
60
 
62
61
  async function checkForNewVersion(currentVersion: string): Promise<string | undefined> {
@@ -149,7 +148,6 @@ const INITIAL_UPDATE_CHECK_TIMEOUT_MS = 500;
149
148
  async function runInteractiveMode(
150
149
  session: AgentSession,
151
150
  version: string,
152
- changelogStatus: { hasNew: boolean; version: string } | undefined,
153
151
  notifs: (InteractiveModeNotify | null)[],
154
152
  versionCheckPromise: Promise<string | undefined>,
155
153
  initialMessages: string[],
@@ -171,7 +169,6 @@ async function runInteractiveMode(
171
169
  const mode = new InteractiveMode(
172
170
  session,
173
171
  version,
174
- changelogStatus,
175
172
  initialUpdateStatus,
176
173
  setExtensionUIContext,
177
174
  lspServers,
@@ -256,31 +253,6 @@ async function promptForkSession(session: SessionInfo): Promise<boolean> {
256
253
  }
257
254
  }
258
255
 
259
- async function getChangelogForDisplay(parsed: Args): Promise<{ hasNew: boolean; version: string } | undefined> {
260
- if (parsed.continue || parsed.resume) {
261
- return undefined;
262
- }
263
-
264
- const lastVersion = settings.get("lastChangelogVersion");
265
- const changelogPath = getChangelogPath();
266
- const entries = await parseChangelog(changelogPath);
267
-
268
- if (!lastVersion) {
269
- if (entries.length > 0) {
270
- settings.set("lastChangelogVersion", VERSION);
271
- return { hasNew: true, version: VERSION };
272
- }
273
- } else {
274
- const newEntries = getNewEntries(entries, lastVersion);
275
- if (newEntries.length > 0) {
276
- settings.set("lastChangelogVersion", VERSION);
277
- return { hasNew: true, version: VERSION };
278
- }
279
- }
280
-
281
- return undefined;
282
- }
283
-
284
256
  async function createSessionManager(parsed: Args, cwd: string): Promise<SessionManager | undefined> {
285
257
  if (parsed.fork) {
286
258
  if (parsed.noSession) {
@@ -890,8 +862,6 @@ export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise<v
890
862
  await runAcpMode(session, createAcpSession);
891
863
  } else if (isInteractive) {
892
864
  const versionCheckPromise = checkForNewVersion(VERSION).catch(() => undefined);
893
- logger.time("main:getChangelogForDisplay");
894
- const changelogStatus = await getChangelogForDisplay(parsedArgs);
895
865
 
896
866
  const scopedModelsForDisplay = sessionOptions.scopedModels ?? scopedModels;
897
867
  if (scopedModelsForDisplay.length > 0) {
@@ -915,7 +885,6 @@ export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise<v
915
885
  await runInteractiveMode(
916
886
  session,
917
887
  VERSION,
918
- changelogStatus,
919
888
  notifs,
920
889
  versionCheckPromise,
921
890
  parsedArgs.messages,
@@ -2,47 +2,30 @@ 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, WelcomeContextStatus, WelcomeGitLabStatus, WelcomeSalesforceStatus } from "./welcome-checks";
5
+ import type { ModelStatus, ServiceStatus } from "./welcome-checks";
6
6
 
7
7
  export interface UpdateStatus {
8
8
  available: boolean;
9
9
  latestVersion?: string;
10
10
  }
11
11
 
12
- export interface ChangelogStatus {
13
- hasNew: boolean;
14
- version: string;
15
- }
16
-
17
12
  export class WelcomeComponent implements Component {
18
13
  constructor(
19
14
  private readonly version: string,
20
15
  private modelStatus: ModelStatus,
21
- private contextStatus?: WelcomeContextStatus,
16
+ private services: ServiceStatus[] = [],
22
17
  private updateStatus?: UpdateStatus,
23
- private changelogStatus?: ChangelogStatus,
24
- private gitlabStatus?: WelcomeGitLabStatus,
25
- private salesforceStatus?: WelcomeSalesforceStatus,
26
18
  ) {}
27
19
  invalidate(): void {}
28
20
  setModelStatus(status: ModelStatus): void {
29
21
  this.modelStatus = status;
30
22
  }
31
- setContextStatus(status: WelcomeContextStatus | undefined): void {
32
- this.contextStatus = status;
23
+ setServices(services: ServiceStatus[]): void {
24
+ this.services = services;
33
25
  }
34
26
  setUpdateStatus(status: UpdateStatus | undefined): void {
35
27
  this.updateStatus = status;
36
28
  }
37
- setChangelogStatus(status: ChangelogStatus | undefined): void {
38
- this.changelogStatus = status;
39
- }
40
- setGitLabStatus(status: WelcomeGitLabStatus | undefined): void {
41
- this.gitlabStatus = status;
42
- }
43
- setSalesforceStatus(status: WelcomeSalesforceStatus | undefined): void {
44
- this.salesforceStatus = status;
45
- }
46
29
 
47
30
  render(termWidth: number): string[] {
48
31
  const minLeftCol = 48;
@@ -63,7 +46,8 @@ export class WelcomeComponent implements Component {
63
46
  ? preferredLeftCol
64
47
  : Math.max(minLeftCol, dualContentWidth - idealRight);
65
48
  const dualRightCol = Math.max(0, dualContentWidth - dualLeftCol);
66
- const showRightColumn = dualLeftCol >= minLeftCol && dualRightCol >= minRightCol;
49
+ // Only show dual column when both columns have enough room for their content
50
+ const showRightColumn = dualLeftCol >= minLeftCol && dualRightCol >= Math.max(minRightCol, naturalRight);
67
51
  const leftCol = showRightColumn ? dualLeftCol : boxWidth - 2;
68
52
  const rightCol = showRightColumn ? dualRightCol : 0;
69
53
 
@@ -95,7 +79,10 @@ export class WelcomeComponent implements Component {
95
79
  const logoBlockPad = Math.max(0, Math.floor((leftCol - logoMaxWidth) / 2));
96
80
  const logoPadStr = padding(logoBlockPad);
97
81
  const leftLines = [...logoColored.map(l => logoPadStr + l), ""];
98
- const rightLines = this.#buildStatusLines(rightCol);
82
+ const rightLines = this.#buildStatusLines(showRightColumn ? rightCol : leftCol);
83
+ if (!showRightColumn) {
84
+ leftLines.push(...rightLines);
85
+ }
99
86
  const border = (s: string) => theme.fg("borderMuted", s);
100
87
  const hChar = theme.boxRound.horizontal;
101
88
  const h = border(hChar);
@@ -135,20 +122,11 @@ export class WelcomeComponent implements Component {
135
122
 
136
123
  #measureStatusWidth(): number {
137
124
  const lines: string[] = [" Model Provider", ...this.#renderModelStatus()];
138
- if (this.contextStatus) {
139
- lines.push(" F5 XC Context", ...this.#renderContextStatus());
140
- }
141
- if (this.gitlabStatus) {
142
- lines.push(" GitLab", ...this.#renderGitLabStatus());
143
- }
144
- if (this.salesforceStatus) {
145
- lines.push(" Salesforce", ...this.#renderSalesforceStatus());
125
+ for (const svc of this.services) {
126
+ lines.push(this.#renderServiceLine(svc));
146
127
  }
147
128
  if (this.#showUpdateSection()) {
148
- lines.push(" Update Available", ...this.#renderUpdateStatus());
149
- }
150
- if (this.#showChangelogSection()) {
151
- lines.push(" What's New", ...this.#renderChangelogStatus());
129
+ lines.push(this.#renderUpdateLine());
152
130
  }
153
131
  return Math.max(...lines.map(l => visibleWidth(l)));
154
132
  }
@@ -161,41 +139,16 @@ export class WelcomeComponent implements Component {
161
139
  lines.push(` ${theme.bold(theme.fg("contentAccent", "Model Provider"))}`);
162
140
  lines.push(...this.#renderModelStatus());
163
141
  lines.push("");
164
- if (this.contextStatus) {
142
+ if (this.services.length > 0 || this.#showUpdateSection()) {
165
143
  lines.push(separator);
166
- lines.push("");
167
- lines.push(` ${theme.bold(theme.fg("contentAccent", "F5 XC Context"))}`);
168
- lines.push(...this.#renderContextStatus());
169
- lines.push("");
170
- }
171
- if (this.gitlabStatus) {
172
- lines.push(separator);
173
- lines.push("");
174
- lines.push(` ${theme.bold(theme.fg("contentAccent", "GitLab"))}`);
175
- lines.push(...this.#renderGitLabStatus());
176
- lines.push("");
177
- }
178
- if (this.salesforceStatus) {
179
- lines.push(separator);
180
- lines.push("");
181
- lines.push(` ${theme.bold(theme.fg("contentAccent", "Salesforce"))}`);
182
- lines.push(...this.#renderSalesforceStatus());
183
- lines.push("");
184
- }
185
- if (this.#showUpdateSection()) {
186
- lines.push(separator);
187
- lines.push("");
188
- lines.push(` ${theme.bold(theme.fg("contentAccent", "Update Available"))}`);
189
- lines.push(...this.#renderUpdateStatus());
190
- lines.push("");
191
- }
192
- if (this.#showChangelogSection()) {
193
- lines.push(separator);
194
- lines.push("");
195
- lines.push(` ${theme.bold(theme.fg("contentAccent", "What's New"))}`);
196
- lines.push(...this.#renderChangelogStatus());
197
- lines.push("");
144
+ for (const svc of this.services) {
145
+ lines.push(this.#renderServiceLine(svc));
146
+ }
147
+ if (this.#showUpdateSection()) {
148
+ lines.push(this.#renderUpdateLine());
149
+ }
198
150
  }
151
+ lines.push("");
199
152
  return lines;
200
153
  }
201
154
 
@@ -203,25 +156,18 @@ export class WelcomeComponent implements Component {
203
156
  return this.updateStatus?.available === true;
204
157
  }
205
158
 
206
- #showChangelogSection(): boolean {
207
- return this.changelogStatus?.hasNew === true;
159
+ #renderServiceLine(service: ServiceStatus): string {
160
+ if (service.state === "connected") {
161
+ return ` ${formatStatusIcon("connected")} ${theme.fg("muted", service.name)}`;
162
+ }
163
+ const hint = service.hint ?? "";
164
+ return ` ${formatStatusIcon("warning")} ${theme.fg("muted", service.name)} ${theme.fg("dim", hint)}`;
208
165
  }
209
166
 
210
- #renderUpdateStatus(): string[] {
167
+ #renderUpdateLine(): string {
211
168
  const latest = this.updateStatus?.latestVersion;
212
169
  const label = latest ? `v${latest}` : "new version";
213
- return [
214
- ` ${theme.fg("warning", "\u2191")} ${theme.fg("muted", label)}`,
215
- ` ${theme.fg("dim", "Run")} ${theme.fg("contentAccent", "xcsh update")}`,
216
- ];
217
- }
218
-
219
- #renderChangelogStatus(): string[] {
220
- const v = this.changelogStatus?.version ?? this.version;
221
- return [
222
- ` ${theme.fg("success", "\u2605")} ${theme.fg("muted", `v${v}`)}`,
223
- ` ${theme.fg("dim", "Run")} ${theme.fg("contentAccent", "/changelog")}`,
224
- ];
170
+ return ` ${theme.fg("warning", "↑")} ${theme.fg("muted", label)} ${theme.fg("dim", "run: xcsh update")}`;
225
171
  }
226
172
 
227
173
  #renderModelStatus(): string[] {
@@ -243,89 +189,6 @@ export class WelcomeComponent implements Component {
243
189
  }
244
190
  }
245
191
 
246
- #renderContextStatus(): string[] {
247
- if (!this.contextStatus) return [];
248
- const { state, name } = this.contextStatus;
249
- const n = name ?? "(unknown)";
250
- switch (state) {
251
- case "connected":
252
- return [` ${formatStatusIcon("connected")} ${theme.fg("muted", n)}`];
253
- case "auth_error":
254
- return [
255
- ` ${formatStatusIcon("error")} ${theme.fg("muted", n)} ${theme.fg("error", "\u2014 token invalid")}`,
256
- ` ${theme.fg("dim", "Run /context to update")}`,
257
- ];
258
- case "offline":
259
- if (this.contextStatus?.errorClass === "url_not_found") {
260
- return [
261
- ` ${formatStatusIcon("error")} ${theme.fg("muted", n)} ${theme.fg("error", "\u2014 tenant not found")}`,
262
- ` ${theme.fg("dim", "Recreate with /context create or check with /context show")}`,
263
- ];
264
- }
265
- return [
266
- ` ${formatStatusIcon("warning")} ${theme.fg("muted", n)} ${theme.fg("warning", "\u2014 unreachable")}`,
267
- ` ${theme.fg("dim", "Check network, /context")}`,
268
- ];
269
- case "no_context":
270
- return [
271
- ` ${formatStatusIcon("warning")} ${theme.fg("warning", "No context configured")}`,
272
- ` ${theme.fg("dim", "Run /context create <name> <url> <token>")}`,
273
- ];
274
- }
275
- }
276
-
277
- #renderGitLabStatus(): string[] {
278
- if (!this.gitlabStatus) return [];
279
- const { state, project } = this.gitlabStatus;
280
- switch (state) {
281
- case "connected":
282
- return [` ${formatStatusIcon("connected")} ${theme.fg("muted", project ?? "configured")}`];
283
- case "auth_error":
284
- return [
285
- ` ${formatStatusIcon("error")} ${theme.fg("error", "Not authenticated")}`,
286
- ` ${theme.fg("dim", "Run")} ${theme.fg("contentAccent", "glab auth login")}`,
287
- ];
288
- case "not_configured":
289
- return [
290
- ` ${formatStatusIcon("warning")} ${theme.fg("warning", "No project configured")}`,
291
- ` ${theme.fg("dim", "Run glab_setup action save_project project GROUP/REPO")}`,
292
- ];
293
- case "project_inaccessible":
294
- return [
295
- ` ${formatStatusIcon("warning")} ${theme.fg("muted", project ?? "unknown")} ${theme.fg("warning", "\u2014 access denied")}`,
296
- ` ${theme.fg("dim", "Check permissions or run glab_setup with action save_project")}`,
297
- ];
298
- case "not_installed":
299
- return [` ${formatStatusIcon("warning")} ${theme.fg("warning", "glab CLI not installed")}`];
300
- }
301
- }
302
-
303
- #renderSalesforceStatus(): string[] {
304
- if (!this.salesforceStatus) return [];
305
- const { state, username, orgAlias } = this.salesforceStatus;
306
- switch (state) {
307
- case "connected":
308
- return [
309
- ` ${formatStatusIcon("connected")} ${theme.fg("muted", orgAlias ?? "org")}${username ? ` ${theme.fg("dim", `(${username})`)}` : ""}`,
310
- ];
311
- case "not_configured":
312
- return [
313
- ` ${formatStatusIcon("warning")} ${theme.fg("warning", "Authenticated (no default org)")}`,
314
- ` ${theme.fg("dim", "Run")} ${theme.fg("contentAccent", "sf_setup")} ${theme.fg("dim", "with action set_default")}`,
315
- ];
316
- case "auth_error":
317
- return [
318
- ` ${formatStatusIcon("error")} ${theme.fg("error", "Not authenticated")}`,
319
- ` ${theme.fg("dim", "Run")} ${theme.fg("contentAccent", "sf org login web --set-default --alias SFDC")}`,
320
- ];
321
- case "session_expired":
322
- return [
323
- ` ${formatStatusIcon("warning")} ${theme.fg("muted", orgAlias ?? "org")} ${theme.fg("warning", "— session expired")}`,
324
- ` ${theme.fg("dim", "Re-authenticate with")} ${theme.fg("contentAccent", "sf org login web --set-default")}`,
325
- ];
326
- }
327
- }
328
-
329
192
  #f5ColorLine(line: string): string {
330
193
  const red = "\x1b[38;5;160m";
331
194
  const white = "\x1b[1;37m";
@@ -49,8 +49,15 @@ import type { HookSelectorComponent } from "./components/hook-selector";
49
49
  import type { PythonExecutionComponent } from "./components/python-execution";
50
50
  import { StatusLineComponent } from "./components/status-line";
51
51
  import type { ToolExecutionHandle } from "./components/tool-execution";
52
- import { type ChangelogStatus, type UpdateStatus, WelcomeComponent } from "./components/welcome";
53
- import { checkGitLabStatus, checkSalesforceStatus, runWelcomeChecks } from "./components/welcome-checks";
52
+ import { type UpdateStatus, WelcomeComponent } from "./components/welcome";
53
+ import {
54
+ checkGitLabStatus,
55
+ checkSalesforceStatus,
56
+ mapContextStatus,
57
+ mapGitLabStatus,
58
+ mapSalesforceStatus,
59
+ runWelcomeChecks,
60
+ } from "./components/welcome-checks";
54
61
  import { BtwController } from "./controllers/btw-controller";
55
62
  import { CommandController } from "./controllers/command-controller";
56
63
  import { EventController } from "./controllers/event-controller";
@@ -161,7 +168,6 @@ export class InteractiveMode implements InteractiveModeContext {
161
168
  #pendingSlashCommands: SlashCommand[] = [];
162
169
  #cleanupUnsubscribe?: () => void;
163
170
  readonly #version: string;
164
- readonly #changelogStatus: ChangelogStatus | undefined;
165
171
  readonly #initialUpdateStatus: UpdateStatus | undefined;
166
172
  #planModePreviousTools: string[] | undefined;
167
173
  #planModePreviousModelState: { model: Model; thinkingLevel?: ThinkingLevel } | undefined;
@@ -193,7 +199,6 @@ export class InteractiveMode implements InteractiveModeContext {
193
199
  constructor(
194
200
  session: AgentSession,
195
201
  version: string,
196
- changelogStatus: ChangelogStatus | undefined = undefined,
197
202
  initialUpdateStatus: UpdateStatus | undefined = undefined,
198
203
  setToolUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void = () => {},
199
204
  lspServers?: import("../tools").LspStartupServerInfo[],
@@ -206,7 +211,6 @@ export class InteractiveMode implements InteractiveModeContext {
206
211
  this.keybindings = KeybindingsManager.inMemory();
207
212
  this.agent = session.agent;
208
213
  this.#version = version;
209
- this.#changelogStatus = changelogStatus;
210
214
  this.#initialUpdateStatus = initialUpdateStatus;
211
215
  this.#toolUiContextSetter = setToolUIContext;
212
216
  this.lspServers = lspServers;
@@ -329,15 +333,20 @@ export class InteractiveMode implements InteractiveModeContext {
329
333
  }
330
334
 
331
335
  if (!startupQuiet) {
332
- // Welcome box owns all startup notifications (model, context, update, changelog)
336
+ // Welcome box owns all startup notifications (model, services, update)
337
+ const services =
338
+ welcomeResult.model.state === "connected"
339
+ ? [
340
+ mapContextStatus(welcomeResult.context ?? { state: "no_context" }),
341
+ mapGitLabStatus(gitlabStatus),
342
+ mapSalesforceStatus(salesforceStatus),
343
+ ]
344
+ : [];
333
345
  this.#welcomeComponent = new WelcomeComponent(
334
346
  this.#version,
335
347
  welcomeResult.model,
336
- welcomeResult.context,
348
+ services,
337
349
  this.#initialUpdateStatus,
338
- this.#changelogStatus,
339
- gitlabStatus,
340
- salesforceStatus,
341
350
  );
342
351
 
343
352
  // Setup UI layout