@f5xc-salesdemos/xcsh 18.50.0 → 18.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@f5xc-salesdemos/xcsh",
4
- "version": "18.50.0",
4
+ "version": "18.52.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",
@@ -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.50.0",
52
- "@f5xc-salesdemos/pi-agent-core": "18.50.0",
53
- "@f5xc-salesdemos/pi-ai": "18.50.0",
54
- "@f5xc-salesdemos/pi-natives": "18.50.0",
55
- "@f5xc-salesdemos/pi-tui": "18.50.0",
56
- "@f5xc-salesdemos/pi-utils": "18.50.0",
51
+ "@f5xc-salesdemos/xcsh-stats": "18.52.0",
52
+ "@f5xc-salesdemos/pi-agent-core": "18.52.0",
53
+ "@f5xc-salesdemos/pi-ai": "18.52.0",
54
+ "@f5xc-salesdemos/pi-natives": "18.52.0",
55
+ "@f5xc-salesdemos/pi-tui": "18.52.0",
56
+ "@f5xc-salesdemos/pi-utils": "18.52.0",
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.50.0",
21
- "commit": "026774fcc6c6e909bc5a3dffa6f1e019f7bb95d3",
22
- "shortCommit": "026774f",
20
+ "version": "18.52.0",
21
+ "commit": "896d3c21a90cd0c7b02ce19558b5799c72369056",
22
+ "shortCommit": "896d3c2",
23
23
  "branch": "main",
24
- "tag": "v18.50.0",
25
- "commitDate": "2026-05-08T23:09:25Z",
26
- "buildDate": "2026-05-08T23:30:02.931Z",
27
- "dirty": true,
24
+ "tag": "v18.52.0",
25
+ "commitDate": "2026-05-09T01:55:47Z",
26
+ "buildDate": "2026-05-09T02:23:57.907Z",
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/026774fcc6c6e909bc5a3dffa6f1e019f7bb95d3",
32
- "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.50.0"
31
+ "commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/896d3c21a90cd0c7b02ce19558b5799c72369056",
32
+ "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.52.0"
33
33
  };
@@ -125,6 +125,12 @@ export interface SalesforceHint {
125
125
  partnerName?: string;
126
126
  /** Partner role label, e.g. 'AE', 'SE', 'CSM' */
127
127
  partnerRole?: string;
128
+ /** Org alias for SOQL queries, e.g. 'SFDC' */
129
+ orgAlias?: string;
130
+ /** Partner Salesforce UserId for AE-owned deal queries */
131
+ partnerId?: string;
132
+ /** Quarterly quota target for coverage ratio, from user profile */
133
+ quota?: number;
128
134
  }
129
135
 
130
136
  // ---------------------------------------------------------------------------
@@ -483,12 +489,31 @@ export async function seedSalesforceContext(): Promise<SalesforceContext | null>
483
489
  }
484
490
 
485
491
  // ---------------------------------------------------------------------------
492
+ /** Format territory list with a character budget. If joined string exceeds budget, truncate with +N more. */
493
+ function formatTerritoryDisplay(territories: string[] | undefined, budget: number): string | undefined {
494
+ if (!territories?.length) return undefined;
495
+ const joined = territories.join(", ");
496
+ if (joined.length <= budget) return joined;
497
+ // Include as many territories as fit, leaving room for the "+N more" suffix
498
+ let result = territories[0];
499
+ let included = 1;
500
+ for (let i = 1; i < territories.length; i++) {
501
+ const candidate = `${result}, ${territories[i]}`;
502
+ // Reserve ~10 chars for ", +N more" suffix
503
+ if (candidate.length > budget - 10) break;
504
+ result = candidate;
505
+ included++;
506
+ }
507
+ const remaining = territories.length - included;
508
+ if (remaining > 0) result += `, +${remaining} more`;
509
+ return result;
510
+ }
486
511
  // Hint builder
487
512
  // ---------------------------------------------------------------------------
488
513
 
489
514
  export function buildSalesforceHint(
490
515
  ctx: SalesforceContext | null,
491
- profile?: { partner?: UserProfile["partner"]; territories?: string[] },
516
+ profile?: { partner?: UserProfile["partner"]; territories?: string[]; quota?: number },
492
517
  ): SalesforceHint | undefined {
493
518
  if (!ctx?.pipelineSummary) return undefined;
494
519
  const total = ctx.pipelineSummary.total;
@@ -506,7 +531,9 @@ export function buildSalesforceHint(
506
531
  : ctx.confirmedTerritories?.length
507
532
  ? ctx.confirmedTerritories
508
533
  : ctx.territories?.slice(0, 3);
509
- const topTerritories = territorySource?.join(", ");
534
+ // Display budget: if joined string exceeds 60 chars, truncate with +N more
535
+ const TERRITORY_CHAR_BUDGET = 60;
536
+ const topTerritories = formatTerritoryDisplay(territorySource, TERRITORY_CHAR_BUDGET);
510
537
 
511
538
  // Forecast breakdown
512
539
  const byForecast = ctx.pipelineSummary.byForecast;
@@ -535,6 +562,9 @@ export function buildSalesforceHint(
535
562
  forecastBreakdown,
536
563
  partnerName,
537
564
  partnerRole,
565
+ orgAlias: ctx.orgAlias,
566
+ partnerId: partner?.id,
567
+ quota: profile?.quota,
538
568
  };
539
569
  }
540
570
 
@@ -62,6 +62,8 @@ export interface UserProfile {
62
62
  };
63
63
  /** User-authored: primary territory names. Exact Salesforce field values. Scopes pipeline reports. */
64
64
  territories?: string[];
65
+ /** User-authored: quarterly quota target in dollars. Used for coverage ratio calculations. */
66
+ quota?: number;
65
67
  observations?: UserProfileObservation[];
66
68
  sources?: { salesforce?: string; github?: string; system?: string; conversation?: string };
67
69
  updatedAt?: string;
@@ -8,12 +8,12 @@ export interface SkuClassification {
8
8
 
9
9
  /** Default classification — discoverable from actual pipeline data. */
10
10
  export const DEFAULT_SKU_CLASSIFICATION: SkuClassification = {
11
- platform: ["F5-V-O", "F5-XC", "F5-FAS-WAF", "F5-FAS-API", "F5-UTIL", "F5-CST"],
11
+ platform: ["F5-V-O", "F5-XC", "F5-FAS-WAF", "F5-FAS-API", "F5-UTIL", "F5-CST", "F5-ELA"],
12
12
  shape: ["F5-SHP", "F5-FAS-BOT", "F5-FAS-DOS"],
13
13
  };
14
14
 
15
15
  /** All SKU prefixes that define the XC/Shape overlay product scope. */
16
- export const DEFAULT_SKU_PREFIXES = ["F5-V-O", "F5-XC", "F5-FAS", "F5-SHP", "F5-UTIL", "F5-CST"];
16
+ export const DEFAULT_SKU_PREFIXES = ["F5-V-O", "F5-XC", "F5-FAS", "F5-SHP", "F5-UTIL", "F5-CST", "F5-ELA"];
17
17
 
18
18
  export interface LineItemRecord {
19
19
  opportunityId: string;
@@ -160,7 +160,9 @@ Available F5 XC documentation topics: {{knowledgeTopics}}.
160
160
  {{/if}}
161
161
 
162
162
  {{#if salesforceHint}}
163
- `xcsh://salesforce`. {{salesforceHint.pipelineTotal}}{{#if salesforceHint.territories}} ({{salesforceHint.territories}}){{/if}}.{{#if salesforceHint.partnerName}} {{salesforceHint.partnerRole}}: {{salesforceHint.partnerName}}.{{/if}}{{#if salesforceHint.forecastBreakdown}} {{salesforceHint.forecastBreakdown}}.{{/if}}
163
+ `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}}
164
+
165
+ 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}}
164
166
  {{/if}}
165
167
 
166
168
  {{#if contextFiles.length}}
@@ -5,11 +5,50 @@ Use for pipeline reporting, case management, account intelligence, and ad-hoc da
5
5
 
6
6
  Common query templates (substitute {userId} from user profile — read `xcsh://user` to get identifiers.salesforceId):
7
7
 
8
- Pipeline summary:
9
- SELECT StageName, COUNT(Id) TotalDeals, SUM(Amount) TotalAmount FROM Opportunity WHERE IsClosed = false GROUP BY StageName ORDER BY SUM(Amount) DESC LIMIT 50
8
+ In-quarter pipeline (current fiscal quarter, team-scoped):
9
+ SELECT Account.Name, Name, Amount, StageName, ForecastCategoryName, CloseDate, Owner.Name, LastActivityDate FROM Opportunity WHERE Id IN (SELECT OpportunityId FROM OpportunityTeamMember WHERE UserId = '{userId}') AND IsClosed = false AND CloseDate = THIS_FISCAL_QUARTER AND ForecastCategoryName <> 'Omitted' ORDER BY Amount DESC NULLS LAST LIMIT 50
10
10
 
11
- My open deals:
12
- SELECT Name, StageName, Amount, CloseDate, Account.Name FROM Opportunity WHERE OwnerId = '{userId}' AND IsClosed = false ORDER BY CloseDate LIMIT 50
11
+ Forecast breakdown (current quarter):
12
+ SELECT ForecastCategoryName, SUM(Amount) TotalAmount, COUNT(Id) TotalDeals FROM Opportunity WHERE Id IN (SELECT OpportunityId FROM OpportunityTeamMember WHERE UserId = '{userId}') AND IsClosed = false AND CloseDate = THIS_FISCAL_QUARTER AND ForecastCategoryName <> 'Omitted' GROUP BY ForecastCategoryName ORDER BY SUM(Amount) DESC
13
+
14
+ Closing within 30 days:
15
+ SELECT Account.Name, Name, Amount, StageName, ForecastCategoryName, CloseDate FROM Opportunity WHERE Id IN (SELECT OpportunityId FROM OpportunityTeamMember WHERE UserId = '{userId}') AND IsClosed = false AND CloseDate = NEXT_N_DAYS:30 ORDER BY CloseDate ASC LIMIT 20
16
+
17
+ Booked this quarter (closed-won):
18
+ SELECT Account.Name, Name, Amount, CloseDate FROM Opportunity WHERE Id IN (SELECT OpportunityId FROM OpportunityTeamMember WHERE UserId = '{userId}') AND IsWon = true AND CloseDate = THIS_FISCAL_QUARTER ORDER BY Amount DESC LIMIT 30
19
+
20
+ Slipped deals (close date in the past but recent — last 6 months):
21
+ SELECT Account.Name, Name, Amount, StageName, CloseDate FROM Opportunity WHERE Id IN (SELECT OpportunityId FROM OpportunityTeamMember WHERE UserId = '{userId}') AND IsClosed = false AND CloseDate < TODAY AND CloseDate = LAST_N_DAYS:180 ORDER BY Amount DESC NULLS LAST LIMIT 20
22
+
23
+ Commit deals only ("what's my commit"):
24
+ SELECT Account.Name, Name, Amount, StageName, CloseDate FROM Opportunity WHERE Id IN (SELECT OpportunityId FROM OpportunityTeamMember WHERE UserId = '{userId}') AND IsClosed = false AND CloseDate = THIS_FISCAL_QUARTER AND ForecastCategoryName = 'Commit' ORDER BY Amount DESC NULLS LAST LIMIT 20
25
+
26
+ Account pipeline ("show me [account]"):
27
+ SELECT Name, Amount, StageName, ForecastCategoryName, CloseDate FROM Opportunity WHERE Id IN (SELECT OpportunityId FROM OpportunityTeamMember WHERE UserId = '{userId}') AND IsClosed = false AND Account.Name LIKE '%{account}%' ORDER BY Amount DESC NULLS LAST LIMIT 20
28
+
29
+ Pipeline by account ("which accounts have the most pipeline"):
30
+ SELECT Account.Name, COUNT(Id) DealCount, SUM(Amount) TotalAmount FROM Opportunity WHERE Id IN (SELECT OpportunityId FROM OpportunityTeamMember WHERE UserId = '{userId}') AND IsClosed = false AND ForecastCategoryName <> 'Omitted' GROUP BY Account.Name ORDER BY SUM(Amount) DESC NULLS LAST LIMIT 15
31
+
32
+ Recently changed in-quarter deals ("what changed this week"):
33
+ SELECT Account.Name, Name, Amount, StageName, ForecastCategoryName, CloseDate, LastModifiedDate FROM Opportunity WHERE Id IN (SELECT OpportunityId FROM OpportunityTeamMember WHERE UserId = '{userId}') AND IsClosed = false AND CloseDate = THIS_FISCAL_QUARTER AND LastModifiedDate = LAST_N_DAYS:7 ORDER BY LastModifiedDate DESC LIMIT 20
34
+
35
+ Lost/abandoned deals this year:
36
+ SELECT Account.Name, Name, Amount, StageName, CloseDate FROM Opportunity WHERE Id IN (SELECT OpportunityId FROM OpportunityTeamMember WHERE UserId = '{userId}') AND IsClosed = true AND IsWon = false AND CloseDate = THIS_FISCAL_YEAR ORDER BY CloseDate DESC NULLS LAST LIMIT 20
37
+
38
+ Last quarter booked (closed-won):
39
+ SELECT Account.Name, Name, Amount, CloseDate FROM Opportunity WHERE Id IN (SELECT OpportunityId FROM OpportunityTeamMember WHERE UserId = '{userId}') AND IsWon = true AND CloseDate = LAST_FISCAL_QUARTER ORDER BY Amount DESC LIMIT 20
40
+
41
+ Pipeline generation this quarter ("what's my pipeline generation", "what deals were created this quarter"):
42
+ SELECT Account.Name, Name, Amount, StageName, ForecastCategoryName, CreatedDate, CloseDate FROM Opportunity WHERE Id IN (SELECT OpportunityId FROM OpportunityTeamMember WHERE UserId = '{userId}') AND CreatedDate = THIS_FISCAL_QUARTER ORDER BY Amount DESC NULLS LAST LIMIT 20
43
+
44
+ Win rate ("what's my win rate"):
45
+ SELECT IsWon, COUNT(Id) DealCount, SUM(Amount) TotalAmount FROM Opportunity WHERE Id IN (SELECT OpportunityId FROM OpportunityTeamMember WHERE UserId = '{userId}') AND IsClosed = true AND CloseDate = THIS_FISCAL_YEAR GROUP BY IsWon
46
+
47
+ Year-to-date bookings / top wins ("what are my top wins this year", "year-to-date bookings"):
48
+ SELECT Account.Name, Name, Amount, CloseDate FROM Opportunity WHERE Id IN (SELECT OpportunityId FROM OpportunityTeamMember WHERE UserId = '{userId}') AND IsWon = true AND CloseDate = THIS_FISCAL_YEAR ORDER BY Amount DESC LIMIT 20
49
+
50
+ Pipeline by territory ("break down pipeline by territory", "territory performance summary"):
51
+ SELECT ETM_Core_Territory__c, COUNT(Id) DealCount, SUM(Amount) TotalAmount FROM Opportunity WHERE Id IN (SELECT OpportunityId FROM OpportunityTeamMember WHERE UserId = '{userId}') AND IsClosed = false AND ForecastCategoryName <> 'Omitted' GROUP BY ETM_Core_Territory__c ORDER BY SUM(Amount) DESC NULLS LAST
13
52
 
14
53
  Open cases:
15
54
  SELECT CaseNumber, Subject, Status, Priority, Account.Name, CreatedDate FROM Case WHERE IsClosed = false ORDER BY Priority, CreatedDate DESC LIMIT 50
@@ -17,6 +56,31 @@ Open cases:
17
56
  Account overview:
18
57
  SELECT Name, Industry, AnnualRevenue, Type, Owner.Name FROM Account WHERE Type = 'Customer' ORDER BY AnnualRevenue DESC LIMIT 50
19
58
 
59
+ Pipeline report structure — when user asks for "pipeline report", "forecast", or "what's my pipeline":
60
+ 1. Run forecast breakdown query first to get the shape of the quarter
61
+ 2. Executive summary: in-quarter total, Commit/Best Case/Pipeline split, booked-to-date
62
+ 3. Top deals by account within each forecast category (Commit first, then Best Case)
63
+ 4. At-risk: slipped deals (CloseDate < TODAY) and early-stage deals closing soon
64
+ 5. Booked this quarter — what has already closed
65
+ 6. Recommended actions — for each risk, suggest a concrete next step (exec sponsor call, POC timeline, close plan review)
66
+ Focus on in-quarter pipeline. Do NOT include deals closing in future quarters unless user asks.
67
+ Flag deals with close dates in the past — these are slipped and need attention.
68
+ Keep to 5-7 key metrics. A pipeline report is for action, not data inventory.
69
+
70
+ Audience-aware formatting — adjust output based on who will read it:
71
+ - **Self / AE partner:** Deal-level detail, close dates, stages, next technical actions.
72
+ - **Manager ("report for my manager"):** Lead with commit total + deal-level evidence. Then risks: what slipped, what's stalled, mitigation plan. No technical detail — managers need forecast confidence, not architecture.
73
+ - **Director/VP ("executive summary"):** Territory-level totals only. Commit/Best Case/Pipeline split. Coverage ratio if quota is known. One line per risk. No deal names unless asked.
74
+
75
+ Scoping: User may be an overlay SE. Use OpportunityTeamMember scoping (not OwnerId) as the primary filter.
76
+ AE-owned deals: SFDC does not allow OR with semi-join subselects. Run a SEPARATE query with OwnerId = '{aeId}' and merge results. Do not combine into one WHERE clause.
77
+
78
+ Stage-based filtering: Add WHERE StageName clauses to any template when the user asks about deals needing technical engagement, demos, POCs, or specific stages. Early stages: 'Awareness', 'Research and Internal Education', 'Pending Initial Meeting'. Active stages: 'Budget and Timing Determination', 'Solution - Front Runner'. Late stages: 'Negotiation', 'Close - Booked'. Deals in early stages with close dates within 60 days are at-risk (insufficient time to progress).
79
+
80
+ Territory-based filtering: Add WHERE clauses on territory fields when the user asks about specific territories, regions, or countries. Available fields: `ETM_Core_Territory__c` (exact territory, e.g. 'AMER: Major Accounts FinSvcs Red 9'), `Territory_Credited_Category__c` (category, e.g. 'Financial', 'OEM'), `Territory_Grouping__c` (region, e.g. 'USA', 'Canada'). Use LIKE '%keyword%' for partial matches (e.g. `ETM_Core_Territory__c LIKE '%Canada%'`). Always combine territory filters with `ForecastCategoryName <> 'Omitted'` or quarter scoping to avoid zombie pipeline noise.
81
+
82
+ Coverage ratio: When the user asks about pipeline coverage or "do I have enough pipeline", calculate coverage = in-quarter pipeline total / quarterly quota target. Healthy coverage is 3x-5x quota. Below 2x is a risk. Use the forecast breakdown (T2) total as the numerator. Quota is available from the user profile when set.
83
+
20
84
  Results with relationship fields (e.g., Account.Name) are automatically flattened into dot-notation columns.
21
85
  If the query returns more than 10,000 records, suggest using sf data export bulk instead.
22
86
  Set use_tooling_api to true when querying metadata objects (ApexTrigger, ApexClass, CustomField).
package/src/sdk.ts CHANGED
@@ -73,7 +73,7 @@ import {
73
73
  SkillProtocolHandler,
74
74
  } from "./internal-urls";
75
75
  import { buildComputerHint, loadComputerProfile } from "./internal-urls/computer-profile";
76
- import { buildSalesforceHint, loadSalesforceContext } from "./internal-urls/salesforce-context";
76
+ import { buildSalesforceHint, loadSalesforceContext, type SalesforceHint } from "./internal-urls/salesforce-context";
77
77
  import { loadProfile, type UserProfile } from "./internal-urls/user-profile";
78
78
  import { disposeAllKernelSessions, disposeKernelSessionsByOwner } from "./ipy/executor";
79
79
  import { LSP_STARTUP_EVENT_CHANNEL, type LspStartupEvent } from "./lsp/startup-events";
@@ -1495,9 +1495,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1495
1495
  }
1496
1496
 
1497
1497
  // Load compact Salesforce pipeline hint — profile provides partner/territory context
1498
- let salesforceHint:
1499
- | { pipelineTotal: string; dealCount: number; accountCount: number; territories?: string }
1500
- | undefined;
1498
+ let salesforceHint: SalesforceHint | undefined;
1501
1499
  try {
1502
1500
  const _sfContext = await loadSalesforceContext();
1503
1501
  salesforceHint = buildSalesforceHint(_sfContext, _profile) ?? undefined;
@@ -490,6 +490,12 @@ export interface BuildSystemPromptOptions {
490
490
  partnerName?: string;
491
491
  /** Partner role abbreviation: 'AE', 'SE', 'other' */
492
492
  partnerRole?: string;
493
+ /** Org alias for SOQL queries, e.g. 'SFDC' */
494
+ orgAlias?: string;
495
+ /** Partner Salesforce UserId for AE-owned deal queries */
496
+ partnerId?: string;
497
+ /** Quarterly quota target for coverage ratio */
498
+ quota?: number;
493
499
  };
494
500
  knowledgeTopics?: string;
495
501
  contextSkillDirs?: string[];