@f5xc-salesdemos/xcsh 18.87.3 → 18.88.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/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.88.0] - 2026-05-31
6
+
7
+ ### Changed
8
+
9
+ - Salesforce tools extracted to marketplace plugin: sf_setup, sf_query, sf_org_display, and sf_pipeline_report are now available as an installable plugin (`@f5xc-salesdemos/xcsh-salesforce`) via the Extension API instead of built-in tools. Install with `xcsh plugin install salesforce`. Context discovery, pipeline reporting, and container-adapted authentication are preserved with full feature parity. ([#1059](https://github.com/f5xc-salesdemos/xcsh/issues/1059))
10
+
5
11
  ## [18.75.0] - 2026-05-23
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.87.3",
4
+ "version": "18.88.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": "18.87.3",
54
- "@f5xc-salesdemos/pi-agent-core": "18.87.3",
55
- "@f5xc-salesdemos/pi-ai": "18.87.3",
56
- "@f5xc-salesdemos/pi-natives": "18.87.3",
57
- "@f5xc-salesdemos/pi-tui": "18.87.3",
58
- "@f5xc-salesdemos/pi-utils": "18.87.3",
53
+ "@f5xc-salesdemos/xcsh-stats": "18.88.0",
54
+ "@f5xc-salesdemos/pi-agent-core": "18.88.0",
55
+ "@f5xc-salesdemos/pi-ai": "18.88.0",
56
+ "@f5xc-salesdemos/pi-natives": "18.88.0",
57
+ "@f5xc-salesdemos/pi-tui": "18.88.0",
58
+ "@f5xc-salesdemos/pi-utils": "18.88.0",
59
59
  "@sinclair/typebox": "^0.34",
60
60
  "@xterm/headless": "^6.0",
61
61
  "ajv": "^8.18",
@@ -1360,17 +1360,6 @@ export const SETTINGS_SCHEMA = {
1360
1360
  },
1361
1361
  },
1362
1362
 
1363
- "salesforce.enabled": {
1364
- type: "boolean",
1365
- default: true,
1366
- ui: {
1367
- tab: "tools",
1368
- label: "Salesforce CLI",
1369
- description:
1370
- "Enable sf_* tools for Salesforce org management, SOQL queries, and pipeline reporting via sf CLI",
1371
- },
1372
- },
1373
-
1374
1363
  "web_search.enabled": {
1375
1364
  type: "boolean",
1376
1365
  default: true,
@@ -209,7 +209,6 @@ export function renderAboutDoc(info: RuntimeBuildInfo, context: ContextStatus |
209
209
  "SSH remote execution, image generation and analysis.",
210
210
  "",
211
211
  "SE specialization: F5 XC API integration (xcsh_api, api-catalog, api-spec),",
212
- "Salesforce pipeline intelligence (sf_query, xcsh://salesforce),",
213
212
  "F5 XC federated product docs (llms.txt hierarchy),",
214
213
  "user/computer profiling (xcsh://user, xcsh://computer),",
215
214
  "SE-specific subagents (deal-analyst, status-operator, cli-operator, github-ops).",
@@ -17,17 +17,17 @@ export interface BuildInfo {
17
17
  }
18
18
 
19
19
  export const BUILD_INFO: BuildInfo = {
20
- "version": "18.87.3",
21
- "commit": "13a382a1bfb96241f9e1710c1bba113f7e3ea0f5",
22
- "shortCommit": "13a382a",
20
+ "version": "18.88.0",
21
+ "commit": "f9efe12cf22df675e6202334728d5fcb797e1d6a",
22
+ "shortCommit": "f9efe12",
23
23
  "branch": "main",
24
- "tag": "v18.87.3",
25
- "commitDate": "2026-05-29T17:31:47Z",
26
- "buildDate": "2026-05-30T00:23:51.633Z",
24
+ "tag": "v18.88.0",
25
+ "commitDate": "2026-05-31T14:58:13Z",
26
+ "buildDate": "2026-05-31T15:17:36.756Z",
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/13a382a1bfb96241f9e1710c1bba113f7e3ea0f5",
32
- "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.87.3"
31
+ "commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/f9efe12cf22df675e6202334728d5fcb797e1d6a",
32
+ "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.88.0"
33
33
  };
@@ -36,7 +36,6 @@ export * from "./parse";
36
36
  export * from "./profile-collectors";
37
37
  export * from "./router";
38
38
  export * from "./rule-protocol";
39
- export * from "./salesforce-context";
40
39
  export * from "./skill-protocol";
41
40
  export * from "./terraform-resolve";
42
41
  export type * from "./terraform-types";
@@ -14,109 +14,6 @@ export interface ProfileCollector {
14
14
  collect(): Promise<Partial<UserProfile>>;
15
15
  }
16
16
 
17
- // ---------------------------------------------------------------------------
18
- // Salesforce
19
- // ---------------------------------------------------------------------------
20
-
21
- const salesforceCollector: ProfileCollector = {
22
- id: "salesforce",
23
- name: "Salesforce",
24
-
25
- async available(): Promise<boolean> {
26
- if (!$which("sf")) return false;
27
- try {
28
- const proc = await $`sf org display --json`.quiet().nothrow();
29
- if (proc.exitCode !== 0) return false;
30
- const parsed = JSON.parse(proc.stdout.toString()) as Record<string, unknown>;
31
- const result = parsed.result as Record<string, unknown> | undefined;
32
- return typeof result?.username === "string" && result.username.length > 0;
33
- } catch {
34
- return false;
35
- }
36
- },
37
-
38
- async collect(): Promise<Partial<UserProfile>> {
39
- try {
40
- // Get username
41
- const orgProc = await $`sf org display --json`.quiet().nothrow();
42
- if (orgProc.exitCode !== 0) return {};
43
- const orgData = JSON.parse(orgProc.stdout.toString()) as Record<string, unknown>;
44
- const orgResult = orgData.result as Record<string, unknown> | undefined;
45
- const username = orgResult?.username as string | undefined;
46
- if (!username) return {};
47
-
48
- // Build and run SOQL
49
- const soql = `SELECT Id, Username, FirstName, LastName, Email, Title, Department, Division, CompanyName, AboutMe, ManagerId, Manager.Name, Manager.Email, UserRole.Name, Profile.Name, Street, City, State, PostalCode, Country, Phone, MobilePhone FROM User WHERE Username = '${username}'`;
50
- const queryProc = await $`sf data query --query ${soql} --json`.quiet().nothrow();
51
- if (queryProc.exitCode !== 0) return {};
52
-
53
- const queryData = JSON.parse(queryProc.stdout.toString()) as Record<string, unknown>;
54
- const queryResult = queryData.result as Record<string, unknown> | undefined;
55
- const records = queryResult?.records as Record<string, unknown>[] | undefined;
56
- const rec = records?.[0];
57
- if (!rec) return {};
58
-
59
- // Map fields
60
- const profile: Partial<UserProfile> = {};
61
-
62
- if (rec.FirstName) profile.givenName = rec.FirstName as string;
63
- if (rec.LastName) profile.familyName = rec.LastName as string;
64
- if (rec.Email) profile.email = rec.Email as string;
65
-
66
- const phone = (rec.Phone || rec.MobilePhone) as string | undefined;
67
- if (phone) profile.telephone = phone;
68
-
69
- if (rec.Title) profile.jobTitle = rec.Title as string;
70
- if (rec.Department) profile.department = rec.Department as string;
71
- if (rec.Division) profile.division = rec.Division as string;
72
-
73
- const companyName = (rec.CompanyName as string) || "F5";
74
- profile.worksFor = { name: companyName };
75
-
76
- // Manager
77
- const mgr = rec.Manager as Record<string, unknown> | undefined;
78
- if (mgr) {
79
- const mgrName = mgr.Name as string | undefined;
80
- const mgrEmail = mgr.Email as string | undefined;
81
- if (mgrName || mgrEmail) {
82
- profile.manager = {};
83
- if (mgrName) {
84
- const parts = mgrName.split(" ");
85
- profile.manager.givenName = parts[0];
86
- if (parts.length > 1) profile.manager.familyName = parts.slice(1).join(" ");
87
- }
88
- if (mgrEmail) profile.manager.email = mgrEmail;
89
- }
90
- }
91
-
92
- // Address
93
- const street = rec.Street as string | undefined;
94
- const city = rec.City as string | undefined;
95
- const state = rec.State as string | undefined;
96
- const postalCode = rec.PostalCode as string | undefined;
97
- const country = rec.Country as string | undefined;
98
- if (street || city || state || postalCode || country) {
99
- profile.address = {};
100
- if (street) profile.address.streetAddress = street;
101
- if (city) profile.address.addressLocality = city;
102
- if (state) profile.address.addressRegion = state;
103
- if (postalCode) profile.address.postalCode = postalCode;
104
- if (country) profile.address.addressCountry = country;
105
- }
106
-
107
- // Identifiers
108
- if (rec.Id) {
109
- profile.identifiers = { salesforceId: rec.Id as string };
110
- }
111
-
112
- return profile;
113
- } catch (err: unknown) {
114
- logger.debug("salesforce collector failed", { error: err });
115
- return {};
116
- }
117
- },
118
- };
119
-
120
17
  // ---------------------------------------------------------------------------
121
18
  // GitHub
122
19
  // ---------------------------------------------------------------------------
@@ -225,4 +122,4 @@ const systemCollector: ProfileCollector = {
225
122
  // Registry
226
123
  // ---------------------------------------------------------------------------
227
124
 
228
- export const PROFILE_COLLECTORS: readonly ProfileCollector[] = [salesforceCollector, githubCollector, systemCollector];
125
+ export const PROFILE_COLLECTORS: readonly ProfileCollector[] = [githubCollector, systemCollector];
@@ -45,27 +45,27 @@ export interface UserProfile {
45
45
  description?: string;
46
46
  image?: string;
47
47
  sameAs?: string[];
48
- identifiers?: { github?: string; twitter?: string; salesforceId?: string };
49
- /** User-authored: short role label, e.g. 'SE', 'AE', 'CSM', 'SA'. Set manually; not derived from Salesforce. */
48
+ identifiers?: { github?: string; twitter?: string };
49
+ /** User-authored: short role label, e.g. 'SE', 'AE', 'CSM', 'SA'. Set manually. */
50
50
  role?: string;
51
51
  /**
52
52
  * User-authored: confirmed partner (AE/SE counterpart, CSM, etc.).
53
- * Set manually in user-profile.json. Survives Salesforce re-seeds.
53
+ * Set manually in user-profile.json.
54
54
  */
55
55
  partner?: {
56
- /** Salesforce User Id — used to scope pipeline queries */
56
+ /** Partner user ID — used to scope pipeline queries */
57
57
  id?: string;
58
58
  name: string;
59
59
  title?: string;
60
60
  /** Short role label, e.g. 'AE', 'SE', 'CSM' */
61
61
  role?: string;
62
62
  };
63
- /** User-authored: primary territory names. Exact Salesforce field values. Scopes pipeline reports. */
63
+ /** User-authored: primary territory names. Scopes pipeline reports. */
64
64
  territories?: string[];
65
65
  /** User-authored: quarterly quota target in dollars. Used for coverage ratio calculations. */
66
66
  quota?: number;
67
67
  observations?: UserProfileObservation[];
68
- sources?: { salesforce?: string; github?: string; system?: string; conversation?: string };
68
+ sources?: { github?: string; system?: string; conversation?: string };
69
69
  updatedAt?: string;
70
70
  }
71
71
 
@@ -164,9 +164,7 @@ export function renderProfileMarkdown(profile: UserProfile): string {
164
164
 
165
165
  const isEmpty = !profile.givenName && !profile.familyName && !profile.email && !profile.jobTitle;
166
166
  if (isEmpty) {
167
- sections.push(
168
- "No profile data yet. Use `xcsh://user?seed=true` to populate from Salesforce, GitHub, and system sources.\n",
169
- );
167
+ sections.push("No profile data yet. Use `xcsh://user?seed=true` to populate from GitHub and system sources.\n");
170
168
  sections.push("Profile facts can also be added progressively during conversation.\n");
171
169
  return sections.join("\n");
172
170
  }
@@ -267,7 +265,6 @@ export function renderProfileMarkdown(profile: UserProfile): string {
267
265
  if (profile.description) onlineLines.push(`- **Bio:** ${profile.description}`);
268
266
  if (profile.identifiers?.github) onlineLines.push(`- **GitHub:** ${profile.identifiers.github}`);
269
267
  if (profile.identifiers?.twitter) onlineLines.push(`- **Twitter/X:** ${profile.identifiers.twitter}`);
270
- if (profile.identifiers?.salesforceId) onlineLines.push(`- **Salesforce ID:** ${profile.identifiers.salesforceId}`);
271
268
  if (profile.sameAs && profile.sameAs.length > 0) {
272
269
  for (const link of profile.sameAs) {
273
270
  onlineLines.push(`- **Profile:** ${link}`);
@@ -293,7 +290,6 @@ export function renderProfileMarkdown(profile: UserProfile): string {
293
290
  sections.push("\n---\n");
294
291
  sections.push("**Sources:**");
295
292
  const srcLines: string[] = [];
296
- if (profile.sources.salesforce) srcLines.push(`Salesforce: ${profile.sources.salesforce}`);
297
293
  if (profile.sources.github) srcLines.push(`GitHub: ${profile.sources.github}`);
298
294
  if (profile.sources.system) srcLines.push(`System: ${profile.sources.system}`);
299
295
  if (profile.sources.conversation) srcLines.push(`Conversation: ${profile.sources.conversation}`);
@@ -21,6 +21,9 @@
21
21
  * - xcsh://terraform/{category}/{resource} - Self-contained resource doc
22
22
  * - xcsh://user - Human user profile
23
23
  * - xcsh://user?seed=true - Seed profile from sources and render
24
+ *
25
+ * Note: Salesforce context (xcsh://salesforce) has been extracted to the
26
+ * salesforce plugin. See packages/salesforce/ for the standalone implementation.
24
27
  */
25
28
  import * as path from "node:path";
26
29
  import { logger } from "@f5xc-salesdemos/pi-utils";
@@ -37,7 +40,6 @@ import type {
37
40
  import { getRuntimeBuildInfo, type RuntimeBuildInfo, renderAboutDoc } from "./build-info-runtime";
38
41
  import { loadComputerProfile, renderComputerProfileMarkdown, seedComputerProfile } from "./computer-profile";
39
42
  import { EMBEDDED_DOC_FILENAMES, EMBEDDED_DOCS } from "./docs-index.generated";
40
- import { loadSalesforceContext, renderSalesforceContextMarkdown, seedSalesforceContext } from "./salesforce-context";
41
43
  import { createTerraformResolver, type TerraformResolver } from "./terraform-resolve";
42
44
  import type { TerraformIndex } from "./terraform-types";
43
45
  import type { InternalResource, InternalUrl, ProtocolHandler } from "./types";
@@ -51,8 +53,6 @@ const BRANDING_HOST = "branding";
51
53
  const TERRAFORM_HOST = "terraform";
52
54
  const USER_ROUTE = "user";
53
55
  const COMPUTER_ROUTE = "computer";
54
- const SALESFORCE_ROUTE = "salesforce";
55
-
56
56
  const EMPTY_INDEX: ApiSpecIndex = { version: "unavailable", timestamp: "", domains: [] };
57
57
  const EMPTY_CATALOG_INDEX: ApiCatalogIndex = {
58
58
  version: "unavailable",
@@ -311,10 +311,6 @@ export class InternalDocsProtocolHandler implements ProtocolHandler {
311
311
  return this.#resolveComputerProfile(url);
312
312
  }
313
313
 
314
- if (host === SALESFORCE_ROUTE) {
315
- return this.#resolveSalesforceContext(url);
316
- }
317
-
318
314
  const pathname = url.rawPathname ?? url.pathname;
319
315
  const filename = host ? (pathname && pathname !== "/" ? host + pathname : host) : "";
320
316
 
@@ -357,22 +353,6 @@ export class InternalDocsProtocolHandler implements ProtocolHandler {
357
353
  };
358
354
  }
359
355
 
360
- async #resolveSalesforceContext(url: InternalUrl): Promise<InternalResource> {
361
- const params = new URLSearchParams(url.search);
362
- const shouldRefresh = params.get("refresh") === "true";
363
-
364
- const ctx = shouldRefresh ? await seedSalesforceContext() : await loadSalesforceContext();
365
- const content = renderSalesforceContextMarkdown(ctx);
366
-
367
- return {
368
- url: url.href,
369
- content,
370
- contentType: "text/markdown",
371
- size: Buffer.byteLength(content, "utf-8"),
372
- sourcePath: `xcsh://${SALESFORCE_ROUTE}`,
373
- };
374
- }
375
-
376
356
  async #listDocs(url: InternalUrl): Promise<InternalResource> {
377
357
  if (EMBEDDED_DOC_FILENAMES.length === 0) {
378
358
  throw new Error("No documentation files found");
@@ -387,7 +367,6 @@ export class InternalDocsProtocolHandler implements ProtocolHandler {
387
367
  const brandingEntry = `- [${BRANDING_HOST}](${SCHEME_PREFIX}${BRANDING_HOST}) — F5 XC branding and legacy name mapping (v${branding.version})`;
388
368
  const userEntry = `- [${USER_ROUTE}](${SCHEME_PREFIX}${USER_ROUTE}) — human user profile`;
389
369
  const computerEntry = `- [${COMPUTER_ROUTE}](${SCHEME_PREFIX}${COMPUTER_ROUTE}) — machine hardware and environment profile`;
390
- const salesforceEntry = `- [${SALESFORCE_ROUTE}](${SCHEME_PREFIX}${SALESFORCE_ROUTE}) — Salesforce pipeline context and team discovery`;
391
370
  const tf = loadTerraformIndex();
392
371
  const terraformEntry = `- [${TERRAFORM_HOST}/](${SCHEME_PREFIX}${TERRAFORM_HOST}/) — F5 XC Terraform provider (${Object.keys(tf.resources).length} resources, v${tf.version})`;
393
372
  const listing = [
@@ -398,10 +377,9 @@ export class InternalDocsProtocolHandler implements ProtocolHandler {
398
377
  terraformEntry,
399
378
  userEntry,
400
379
  computerEntry,
401
- salesforceEntry,
402
380
  ...EMBEDDED_DOC_FILENAMES.map(f => `- [${f}](${SCHEME_PREFIX}${f})`),
403
381
  ].join("\n");
404
- const totalCount = EMBEDDED_DOC_FILENAMES.length + 8;
382
+ const totalCount = EMBEDDED_DOC_FILENAMES.length + 7;
405
383
  const content = `# Documentation\n\n${totalCount} files available:\n\n${listing}\n`;
406
384
 
407
385
  return {
@@ -234,104 +234,6 @@ export async function checkGitLabStatus(cwd: string): Promise<WelcomeGitLabStatu
234
234
  }
235
235
  }
236
236
 
237
- export type SalesforceCheckState = "connected" | "auth_error" | "session_expired" | "not_configured";
238
-
239
- export interface WelcomeSalesforceStatus {
240
- state: SalesforceCheckState;
241
- username?: string;
242
- orgAlias?: string;
243
- instanceUrl?: string;
244
- }
245
-
246
- /** Idempotent startup check: sf installed -> org list -> default org -> display status. */
247
- export async function checkSalesforceStatus(_cwd: string): Promise<WelcomeSalesforceStatus | undefined> {
248
- try {
249
- if (!$which("sf")) return undefined;
250
-
251
- // Suppress telemetry consent nag (idempotent)
252
- await $`sf config set disable-telemetry true --global`.quiet().nothrow();
253
-
254
- // Step 1: Get org list
255
- const listResult = await $`sf org list --json`.quiet().nothrow();
256
- if (listResult.exitCode !== 0) return { state: "auth_error" };
257
-
258
- let listData: {
259
- result?: {
260
- nonScratchOrgs?: unknown[];
261
- sandboxes?: unknown[];
262
- scratchOrgs?: unknown[];
263
- devHubs?: unknown[];
264
- other?: unknown[];
265
- };
266
- };
267
- try {
268
- listData = JSON.parse(listResult.text());
269
- } catch {
270
- return { state: "auth_error" };
271
- }
272
-
273
- const r = listData.result ?? {};
274
- const seen = new Set<string>();
275
- const allRawOrgs = (
276
- [
277
- ...(r.nonScratchOrgs ?? []),
278
- ...(r.sandboxes ?? []),
279
- ...(r.scratchOrgs ?? []),
280
- ...(r.devHubs ?? []),
281
- ...(r.other ?? []),
282
- ] as Record<string, unknown>[]
283
- ).filter(org => {
284
- const id = String(org.orgId ?? org.orgid ?? "");
285
- if (!id || seen.has(id)) return false;
286
- seen.add(id);
287
- return true;
288
- });
289
-
290
- if (allRawOrgs.length === 0) return { state: "auth_error" };
291
-
292
- // Step 2: Find default org (normalize raw CLI fields)
293
- const defaultRaw = allRawOrgs.find(
294
- org =>
295
- (typeof org.defaultMarker === "string" && org.defaultMarker.includes("(U)")) ||
296
- org.isDefaultUsername === true,
297
- );
298
-
299
- if (!defaultRaw) {
300
- return { state: "not_configured", username: allRawOrgs[0]?.username as string | undefined };
301
- }
302
-
303
- const alias = (defaultRaw.alias ?? defaultRaw.username) as string;
304
-
305
- // Step 3: Display org details
306
- const displayResult = await $`sf org display --target-org ${alias} --json`.quiet().nothrow();
307
- if (displayResult.exitCode !== 0) {
308
- return { state: "session_expired", username: defaultRaw.username as string | undefined, orgAlias: alias };
309
- }
310
-
311
- let displayData: { result?: Record<string, unknown> };
312
- try {
313
- displayData = JSON.parse(displayResult.text());
314
- } catch {
315
- return { state: "session_expired", username: defaultRaw.username as string | undefined, orgAlias: alias };
316
- }
317
-
318
- const result = displayData.result;
319
- if (!result || result.connectedStatus !== "Connected") {
320
- return { state: "session_expired", username: defaultRaw.username as string | undefined, orgAlias: alias };
321
- }
322
-
323
- return {
324
- state: "connected",
325
- username: result.username as string | undefined,
326
- orgAlias: alias,
327
- instanceUrl: result.instanceUrl as string | undefined,
328
- };
329
- } catch (err) {
330
- logger.warn("Salesforce startup check failed", { error: String(err) });
331
- return { state: "auth_error" };
332
- }
333
- }
334
-
335
237
  export type GitHubCheckState = "connected" | "auth_error";
336
238
 
337
239
  export interface WelcomeGitHubStatus {
@@ -391,20 +293,6 @@ export function mapGitLabStatus(status: WelcomeGitLabStatus | undefined): Servic
391
293
  }
392
294
  }
393
295
 
394
- export function mapSalesforceStatus(status: WelcomeSalesforceStatus | undefined): ServiceStatus {
395
- if (!status) return { name: "Salesforce", state: "unavailable", hint: "not installed" };
396
- switch (status.state) {
397
- case "connected":
398
- return { name: "Salesforce", state: "connected" };
399
- case "session_expired":
400
- return { name: "Salesforce", state: "unauthenticated", hint: "session expired, run: sf org login web" };
401
- case "not_configured":
402
- return { name: "Salesforce", state: "unauthenticated", hint: "run: sf org login web --set-default" };
403
- default:
404
- return { name: "Salesforce", state: "unauthenticated", hint: "run: sf org login web" };
405
- }
406
- }
407
-
408
296
  export function mapGitHubStatus(status: WelcomeGitHubStatus | undefined): ServiceStatus {
409
297
  if (!status) return { name: "GitHub", state: "unavailable", hint: "not installed" };
410
298
  switch (status.state) {
@@ -609,7 +497,6 @@ export function getFixableServices(statuses: {
609
497
  gcloud: WelcomeGcloudStatus | undefined;
610
498
  github: WelcomeGitHubStatus | undefined;
611
499
  gitlab: WelcomeGitLabStatus | undefined;
612
- salesforce: WelcomeSalesforceStatus | undefined;
613
500
  }): FixableService[] {
614
501
  const fixable: FixableService[] = [];
615
502
 
@@ -629,14 +516,6 @@ export function getFixableServices(statuses: {
629
516
  recheck: async () => mapGitHubStatus(await checkGitHubStatus()),
630
517
  });
631
518
  }
632
- if (statuses.salesforce?.state === "session_expired") {
633
- fixable.push({
634
- name: "Salesforce",
635
- prompt: "Salesforce session expired",
636
- command: ["sf", "org", "login", "web"],
637
- recheck: async () => mapSalesforceStatus(await checkSalesforceStatus(getProjectDir())),
638
- });
639
- }
640
519
  if (statuses.azure?.state === "auth_error") {
641
520
  fixable.push({
642
521
  name: "Azure",
@@ -29,7 +29,6 @@ import type { CompactOptions } from "../extensibility/extensions/types";
29
29
  import { BUILTIN_SLASH_COMMANDS, loadSlashCommands } from "../extensibility/slash-commands";
30
30
  import { resolveLocalUrlToPath } from "../internal-urls";
31
31
  import { seedComputerProfile } from "../internal-urls/computer-profile";
32
- import { seedSalesforceContext } from "../internal-urls/salesforce-context";
33
32
  import { seedProfile } from "../internal-urls/user-profile";
34
33
  import { renameApprovedPlanFile } from "../plan-mode/approved-plan";
35
34
  import planModeApprovedPrompt from "../prompts/system/plan-mode-approved.md" with { type: "text" };
@@ -59,7 +58,6 @@ import {
59
58
  checkGcloudStatus,
60
59
  checkGitHubStatus,
61
60
  checkGitLabStatus,
62
- checkSalesforceStatus,
63
61
  type FixableService,
64
62
  getFixableServices,
65
63
  mapAwsStatus,
@@ -68,7 +66,6 @@ import {
68
66
  mapGcloudStatus,
69
67
  mapGitHubStatus,
70
68
  mapGitLabStatus,
71
- mapSalesforceStatus,
72
69
  runWelcomeChecks,
73
70
  type ServiceStatus,
74
71
  } from "./components/welcome-checks";
@@ -329,18 +326,16 @@ export class InteractiveMode implements InteractiveModeContext {
329
326
  );
330
327
 
331
328
  // Run blocking welcome screen status checks in parallel
332
- const [welcomeResult, gitlabStatus, salesforceStatus, githubStatus, azureStatus, awsStatus, gcloudStatus] =
333
- await Promise.all([
334
- logger.time("InteractiveMode.init:welcomeChecks", () =>
335
- runWelcomeChecks(this.session.model, this.session.modelRegistry.authStorage),
336
- ),
337
- checkGitLabStatus(getProjectDir()).catch(() => undefined),
338
- checkSalesforceStatus(getProjectDir()).catch(() => undefined),
339
- checkGitHubStatus().catch(() => undefined),
340
- checkAzureStatus().catch(() => undefined),
341
- checkAwsStatus().catch(() => undefined),
342
- checkGcloudStatus().catch(() => undefined),
343
- ]);
329
+ const [welcomeResult, gitlabStatus, githubStatus, azureStatus, awsStatus, gcloudStatus] = await Promise.all([
330
+ logger.time("InteractiveMode.init:welcomeChecks", () =>
331
+ runWelcomeChecks(this.session.model, this.session.modelRegistry.authStorage),
332
+ ),
333
+ checkGitLabStatus(getProjectDir()).catch(() => undefined),
334
+ checkGitHubStatus().catch(() => undefined),
335
+ checkAzureStatus().catch(() => undefined),
336
+ checkAwsStatus().catch(() => undefined),
337
+ checkGcloudStatus().catch(() => undefined),
338
+ ]);
344
339
 
345
340
  // Refresh user profile in background — fire and forget
346
341
  seedProfile().catch(err => logger.warn("Background profile refresh failed", { error: String(err) }));
@@ -348,10 +343,6 @@ export class InteractiveMode implements InteractiveModeContext {
348
343
  seedComputerProfile().catch(err =>
349
344
  logger.warn("Background computer profile refresh failed", { error: String(err) }),
350
345
  );
351
- // Refresh Salesforce pipeline context in background — fire and forget
352
- seedSalesforceContext().catch(err =>
353
- logger.warn("Background Salesforce context refresh failed", { error: String(err) }),
354
- );
355
346
  const startupQuiet = settings.get("startup.quiet");
356
347
  this.#welcomeComponent = undefined;
357
348
 
@@ -367,7 +358,6 @@ export class InteractiveMode implements InteractiveModeContext {
367
358
  mapContextStatus(welcomeResult.context ?? { state: "no_context" }),
368
359
  mapGitLabStatus(gitlabStatus),
369
360
  mapGitHubStatus(githubStatus),
370
- mapSalesforceStatus(salesforceStatus),
371
361
  mapAzureStatus(azureStatus),
372
362
  mapAwsStatus(awsStatus),
373
363
  mapGcloudStatus(gcloudStatus),
@@ -382,7 +372,6 @@ export class InteractiveMode implements InteractiveModeContext {
382
372
  gcloud: gcloudStatus,
383
373
  github: githubStatus,
384
374
  gitlab: gitlabStatus,
385
- salesforce: salesforceStatus,
386
375
  })
387
376
  : [];
388
377
 
@@ -24,14 +24,12 @@ import {
24
24
  checkGcloudStatus,
25
25
  checkGitHubStatus,
26
26
  checkGitLabStatus,
27
- checkSalesforceStatus,
28
27
  mapAwsStatus,
29
28
  mapAzureStatus,
30
29
  mapContextStatus,
31
30
  mapGcloudStatus,
32
31
  mapGitHubStatus,
33
32
  mapGitLabStatus,
34
- mapSalesforceStatus,
35
33
  runWelcomeChecks,
36
34
  } from "../components/welcome-checks";
37
35
  import { isRpcHostToolResult, isRpcHostToolUpdate, RpcHostToolBridge } from "./host-tools";
@@ -628,16 +626,16 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
628
626
 
629
627
  case "get_integrations": {
630
628
  const cwd = getProjectDir();
631
- const [welcomeResult, gitlabStatus, salesforceStatus, githubStatus, azureStatus, awsStatus, gcloudStatus] =
632
- await Promise.all([
629
+ const [welcomeResult, gitlabStatus, githubStatus, azureStatus, awsStatus, gcloudStatus] = await Promise.all(
630
+ [
633
631
  runWelcomeChecks(session.model, session.modelRegistry.authStorage),
634
632
  checkGitLabStatus(cwd).catch(() => undefined),
635
- checkSalesforceStatus(cwd).catch(() => undefined),
636
633
  checkGitHubStatus().catch(() => undefined),
637
634
  checkAzureStatus().catch(() => undefined),
638
635
  checkAwsStatus().catch(() => undefined),
639
636
  checkGcloudStatus().catch(() => undefined),
640
- ]);
637
+ ],
638
+ );
641
639
 
642
640
  const services =
643
641
  welcomeResult.model.state === "connected"
@@ -645,7 +643,6 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
645
643
  mapContextStatus(welcomeResult.context ?? { state: "no_context" }),
646
644
  mapGitLabStatus(gitlabStatus),
647
645
  mapGitHubStatus(githubStatus),
648
- mapSalesforceStatus(salesforceStatus),
649
646
  mapAzureStatus(azureStatus),
650
647
  mapAwsStatus(awsStatus),
651
648
  mapGcloudStatus(gcloudStatus),
@@ -189,12 +189,6 @@ Available F5 XC documentation topics: {{knowledgeTopics}}.
189
189
  `xcsh://computer`. {{computerProfile.ramGB}}GB, {{computerProfile.cpu}}, {{computerProfile.os}}{{#if computerProfile.shell}}, {{computerProfile.shell}}{{/if}}.{{#if computerProfile.managed}} Managed{{#unless computerProfile.admin}} (not admin{{#if computerProfile.endpointAgentCount}}, {{computerProfile.endpointAgentCount}} agents{{/if}}){{/unless}}.{{/if}}
190
190
  {{/if}}
191
191
 
192
- {{#if salesforceHint}}
193
- `xcsh://salesforce`{{#if salesforceHint.orgAlias}} ({{salesforceHint.orgAlias}}){{/if}}. {{salesforceHint.pipelineTotal}}{{#if salesforceHint.territories}} ({{salesforceHint.territories}}){{/if}}.{{#if salesforceHint.partnerName}} {{salesforceHint.partnerRole}}: {{salesforceHint.partnerName}}.{{/if}}{{#if salesforceHint.forecastBreakdown}} {{salesforceHint.forecastBreakdown}}.{{/if}}
194
-
195
- Pipeline queries: current fiscal quarter, team-member scoped, Commit/BestCase first. Do NOT dump all-time open pipeline.{{#if salesforceHint.orgAlias}} Always use target_org: {{salesforceHint.orgAlias}}.{{/if}}{{#if salesforceHint.partnerId}} AE UserId: {{salesforceHint.partnerId}}.{{/if}}{{#if salesforceHint.quota}} Quarterly quota: ${{salesforceHint.quota}}. Coverage = pipeline/quota, healthy is 3x-5x.{{/if}}
196
- {{/if}}
197
-
198
192
  {{#if contextFiles.length}}
199
193
  <context>
200
194
  Context files below **MUST** be followed for all tasks:
@@ -243,8 +237,6 @@ Most tools resolve custom protocol URLs to internal resources (not web URLs):
243
237
  - `xcsh://user?seed=true` — Refresh profile from Salesforce, GitHub, and system sources.
244
238
  - `xcsh://computer` — Machine hardware and environment profile. Read when platform-specific recommendations needed.
245
239
  - `xcsh://computer?refresh=true` — Re-collect hardware data.
246
- - `xcsh://salesforce` — Salesforce pipeline context: accounts, territories, team, forecast. Read when pipeline questions or Salesforce queries needed.
247
- - `xcsh://salesforce?refresh=true` — Re-discover pipeline context from Salesforce.
248
240
  - `xcsh://api-spec/` — F5 XC API specifications (schema introspection, field types, validation).
249
241
  - `xcsh://api-catalog/` — F5 XC API operations catalog (CRUD execution).
250
242