@f5xc-salesdemos/xcsh 18.87.2 → 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 +6 -0
- package/package.json +7 -7
- package/src/config/settings-schema.ts +0 -11
- package/src/internal-urls/build-info-runtime.ts +0 -1
- package/src/internal-urls/build-info.generated.ts +8 -8
- package/src/internal-urls/index.ts +0 -1
- package/src/internal-urls/profile-collectors.ts +1 -104
- package/src/internal-urls/user-profile.ts +7 -11
- package/src/internal-urls/xcsh-protocol.ts +4 -26
- package/src/modes/components/welcome-checks.ts +0 -121
- package/src/modes/interactive-mode.ts +10 -21
- package/src/modes/rpc/rpc-mode.ts +4 -7
- package/src/prompts/system/system-prompt.md +0 -8
- package/src/sdk.ts +1 -13
- package/src/system-prompt.ts +0 -20
- package/src/tools/index.ts +0 -7
- package/src/tools/renderers.ts +0 -5
- package/src/internal-urls/salesforce-context.ts +0 -745
- package/src/pipeline-report/benchmark.ts +0 -405
- package/src/pipeline-report/generator.ts +0 -684
- package/src/pipeline-report/index.ts +0 -3
- package/src/pipeline-report/renderer.ts +0 -306
- package/src/pipeline-report/types.ts +0 -166
- package/src/prompts/tools/sf-org-display.md +0 -7
- package/src/prompts/tools/sf-pipeline-report.md +0 -25
- package/src/prompts/tools/sf-query.md +0 -122
- package/src/prompts/tools/sf-setup.md +0 -10
- package/src/tools/sf/exec.ts +0 -104
- package/src/tools/sf/formatters.ts +0 -150
- package/src/tools/sf/types.ts +0 -40
- package/src/tools/sf-pipeline-report.ts +0 -175
- package/src/tools/sf-renderer.ts +0 -332
- package/src/tools/sf.ts +0 -391
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.
|
|
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.
|
|
54
|
-
"@f5xc-salesdemos/pi-agent-core": "18.
|
|
55
|
-
"@f5xc-salesdemos/pi-ai": "18.
|
|
56
|
-
"@f5xc-salesdemos/pi-natives": "18.
|
|
57
|
-
"@f5xc-salesdemos/pi-tui": "18.
|
|
58
|
-
"@f5xc-salesdemos/pi-utils": "18.
|
|
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.
|
|
21
|
-
"commit": "
|
|
22
|
-
"shortCommit": "
|
|
20
|
+
"version": "18.88.0",
|
|
21
|
+
"commit": "f9efe12cf22df675e6202334728d5fcb797e1d6a",
|
|
22
|
+
"shortCommit": "f9efe12",
|
|
23
23
|
"branch": "main",
|
|
24
|
-
"tag": "v18.
|
|
25
|
-
"commitDate": "2026-05-
|
|
26
|
-
"buildDate": "2026-05-
|
|
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/
|
|
32
|
-
"releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.
|
|
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[] = [
|
|
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
|
|
49
|
-
/** User-authored: short role label, e.g. 'SE', 'AE', 'CSM', 'SA'. Set manually
|
|
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.
|
|
53
|
+
* Set manually in user-profile.json.
|
|
54
54
|
*/
|
|
55
55
|
partner?: {
|
|
56
|
-
/**
|
|
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.
|
|
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?: {
|
|
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 +
|
|
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,
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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,
|
|
632
|
-
|
|
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
|
|