@f5xc-salesdemos/xcsh 19.19.3 → 19.20.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": "19.
|
|
4
|
+
"version": "19.20.1",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/f5xc-salesdemos/xcsh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -50,12 +50,12 @@
|
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@agentclientprotocol/sdk": "0.16.1",
|
|
52
52
|
"@mozilla/readability": "^0.6",
|
|
53
|
-
"@f5xc-salesdemos/xcsh-stats": "19.
|
|
54
|
-
"@f5xc-salesdemos/pi-agent-core": "19.
|
|
55
|
-
"@f5xc-salesdemos/pi-ai": "19.
|
|
56
|
-
"@f5xc-salesdemos/pi-natives": "19.
|
|
57
|
-
"@f5xc-salesdemos/pi-tui": "19.
|
|
58
|
-
"@f5xc-salesdemos/pi-utils": "19.
|
|
53
|
+
"@f5xc-salesdemos/xcsh-stats": "19.20.1",
|
|
54
|
+
"@f5xc-salesdemos/pi-agent-core": "19.20.1",
|
|
55
|
+
"@f5xc-salesdemos/pi-ai": "19.20.1",
|
|
56
|
+
"@f5xc-salesdemos/pi-natives": "19.20.1",
|
|
57
|
+
"@f5xc-salesdemos/pi-tui": "19.20.1",
|
|
58
|
+
"@f5xc-salesdemos/pi-utils": "19.20.1",
|
|
59
59
|
"@sinclair/typebox": "^0.34",
|
|
60
60
|
"@xterm/headless": "^6.0",
|
|
61
61
|
"ajv": "^8.20",
|
|
@@ -17,17 +17,17 @@ export interface BuildInfo {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export const BUILD_INFO: BuildInfo = {
|
|
20
|
-
"version": "19.
|
|
21
|
-
"commit": "
|
|
22
|
-
"shortCommit": "
|
|
20
|
+
"version": "19.20.1",
|
|
21
|
+
"commit": "0ecbb73107f9ece06f7f82f243ff413ff30d6f06",
|
|
22
|
+
"shortCommit": "0ecbb73",
|
|
23
23
|
"branch": "main",
|
|
24
|
-
"tag": "v19.
|
|
25
|
-
"commitDate": "2026-06-
|
|
26
|
-
"buildDate": "2026-06-
|
|
24
|
+
"tag": "v19.20.1",
|
|
25
|
+
"commitDate": "2026-06-09T12:19:32Z",
|
|
26
|
+
"buildDate": "2026-06-09T12:49:05.875Z",
|
|
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/v19.
|
|
31
|
+
"commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/0ecbb73107f9ece06f7f82f243ff413ff30d6f06",
|
|
32
|
+
"releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v19.20.1"
|
|
33
33
|
};
|
|
@@ -328,12 +328,26 @@ export class InternalDocsProtocolHandler implements ProtocolHandler {
|
|
|
328
328
|
const profile = shouldSeed ? await seedProfile() : await loadProfile();
|
|
329
329
|
const content = renderProfileMarkdown(profile);
|
|
330
330
|
|
|
331
|
+
const hasOwnership = profile._fieldOwnership && Object.keys(profile._fieldOwnership).length > 0;
|
|
332
|
+
const notes: string[] = [
|
|
333
|
+
"This profile is the authoritative source for person data (manager, partner, territories, role).",
|
|
334
|
+
"Do NOT query Salesforce or other services for fields that are already populated here.",
|
|
335
|
+
];
|
|
336
|
+
if (hasOwnership) {
|
|
337
|
+
notes.push(
|
|
338
|
+
`Fields owned by external sources: ${Object.entries(profile._fieldOwnership!)
|
|
339
|
+
.map(([k, v]) => `${k} (${v})`)
|
|
340
|
+
.join(", ")}. These are automatically kept in sync.`,
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
|
|
331
344
|
return {
|
|
332
345
|
url: url.href,
|
|
333
346
|
content,
|
|
334
347
|
contentType: "text/markdown",
|
|
335
348
|
size: Buffer.byteLength(content, "utf-8"),
|
|
336
349
|
sourcePath: `xcsh://${USER_ROUTE}`,
|
|
350
|
+
notes,
|
|
337
351
|
};
|
|
338
352
|
}
|
|
339
353
|
|
|
@@ -228,6 +228,95 @@ export interface RecommendedPluginStatus {
|
|
|
228
228
|
installed: boolean;
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
export type UnifiedPluginState = "connected" | "unauthenticated" | "unavailable" | "installed" | "not_installed";
|
|
232
|
+
|
|
233
|
+
export interface UnifiedPluginStatus {
|
|
234
|
+
name: string;
|
|
235
|
+
state: UnifiedPluginState;
|
|
236
|
+
hint?: string;
|
|
237
|
+
group?: string;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export interface ServiceStatusContributionInput {
|
|
241
|
+
name: string;
|
|
242
|
+
group?: string;
|
|
243
|
+
check: () => Promise<{ state: ServiceState; hint?: string }>;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export async function buildUnifiedPluginList(
|
|
247
|
+
contributions: ServiceStatusContributionInput[],
|
|
248
|
+
): Promise<UnifiedPluginStatus[]> {
|
|
249
|
+
const map = new Map<string, UnifiedPluginStatus>();
|
|
250
|
+
|
|
251
|
+
for (const contribution of contributions) {
|
|
252
|
+
try {
|
|
253
|
+
const status = await contribution.check();
|
|
254
|
+
map.set(contribution.name.toLowerCase(), {
|
|
255
|
+
name: contribution.name,
|
|
256
|
+
state: status.state,
|
|
257
|
+
hint: status.hint,
|
|
258
|
+
group: contribution.group,
|
|
259
|
+
});
|
|
260
|
+
} catch {
|
|
261
|
+
map.set(contribution.name.toLowerCase(), {
|
|
262
|
+
name: contribution.name,
|
|
263
|
+
state: "unavailable",
|
|
264
|
+
hint: "check failed",
|
|
265
|
+
group: contribution.group,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
try {
|
|
271
|
+
const mgr = new MarketplaceManager({
|
|
272
|
+
marketplacesRegistryPath: getMarketplacesRegistryPath(),
|
|
273
|
+
installedRegistryPath: getInstalledPluginsRegistryPath(),
|
|
274
|
+
marketplacesCacheDir: getMarketplacesCacheDir(),
|
|
275
|
+
pluginsCacheDir: getPluginsCacheDir(),
|
|
276
|
+
clearPluginRootsCache: () => {},
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
const [marketplaces, installedSummaries] = await Promise.all([
|
|
280
|
+
mgr.listMarketplaces(),
|
|
281
|
+
mgr.listInstalledPlugins(),
|
|
282
|
+
]);
|
|
283
|
+
|
|
284
|
+
const installedIds = new Set(installedSummaries.map(s => s.id));
|
|
285
|
+
|
|
286
|
+
for (const mkt of marketplaces) {
|
|
287
|
+
const available = await mgr.listAvailablePlugins(mkt.name).catch(() => []);
|
|
288
|
+
for (const entry of available) {
|
|
289
|
+
if (!entry.recommended) continue;
|
|
290
|
+
const displayName = normalizePluginDisplayName(entry.name);
|
|
291
|
+
const key = displayName.toLowerCase();
|
|
292
|
+
if (map.has(key)) continue;
|
|
293
|
+
const pluginId = `${entry.name}@${mkt.name}`;
|
|
294
|
+
map.set(key, {
|
|
295
|
+
name: displayName,
|
|
296
|
+
state: installedIds.has(pluginId) ? "installed" : "not_installed",
|
|
297
|
+
hint: installedIds.has(pluginId) ? undefined : "run: /plugin setup",
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
} catch (err) {
|
|
302
|
+
logger.debug("buildUnifiedPluginList marketplace check failed", { error: String(err) });
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const stateOrder: Record<UnifiedPluginState, number> = {
|
|
306
|
+
connected: 0,
|
|
307
|
+
unauthenticated: 1,
|
|
308
|
+
unavailable: 2,
|
|
309
|
+
installed: 3,
|
|
310
|
+
not_installed: 4,
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
return [...map.values()].sort((a, b) => {
|
|
314
|
+
const orderDiff = stateOrder[a.state] - stateOrder[b.state];
|
|
315
|
+
if (orderDiff !== 0) return orderDiff;
|
|
316
|
+
return a.name.localeCompare(b.name);
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
231
320
|
export async function checkRecommendedPlugins(): Promise<RecommendedPluginStatus[]> {
|
|
232
321
|
try {
|
|
233
322
|
const mgr = new MarketplaceManager({
|
|
@@ -2,7 +2,7 @@ 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, RecommendedPluginStatus, ServiceStatus } from "./welcome-checks";
|
|
5
|
+
import type { ModelStatus, RecommendedPluginStatus, ServiceStatus, UnifiedPluginStatus } from "./welcome-checks";
|
|
6
6
|
|
|
7
7
|
export interface UpdateStatus {
|
|
8
8
|
available: boolean;
|
|
@@ -15,7 +15,8 @@ export class WelcomeComponent implements Component {
|
|
|
15
15
|
private modelStatus: ModelStatus,
|
|
16
16
|
private services: ServiceStatus[] = [],
|
|
17
17
|
private updateStatus?: UpdateStatus,
|
|
18
|
-
private
|
|
18
|
+
private _recommendedPlugins: RecommendedPluginStatus[] = [],
|
|
19
|
+
private plugins: UnifiedPluginStatus[] = [],
|
|
19
20
|
) {}
|
|
20
21
|
invalidate(): void {}
|
|
21
22
|
setModelStatus(status: ModelStatus): void {
|
|
@@ -28,7 +29,10 @@ export class WelcomeComponent implements Component {
|
|
|
28
29
|
this.updateStatus = status;
|
|
29
30
|
}
|
|
30
31
|
setRecommendedPlugins(plugins: RecommendedPluginStatus[]): void {
|
|
31
|
-
this.
|
|
32
|
+
this._recommendedPlugins = plugins;
|
|
33
|
+
}
|
|
34
|
+
setPlugins(plugins: UnifiedPluginStatus[]): void {
|
|
35
|
+
this.plugins = plugins;
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
render(termWidth: number): string[] {
|
|
@@ -127,23 +131,13 @@ export class WelcomeComponent implements Component {
|
|
|
127
131
|
#measureStatusWidth(): number {
|
|
128
132
|
const lines: string[] = [" Model Provider", ...this.#renderModelStatus()];
|
|
129
133
|
const coreServices = this.services.filter(s => !s._isPlugin);
|
|
130
|
-
const pluginServices = this.services.filter(s => s._isPlugin);
|
|
131
134
|
for (const svc of coreServices) {
|
|
132
135
|
lines.push(this.#renderServiceLine(svc));
|
|
133
136
|
}
|
|
134
|
-
if (
|
|
135
|
-
|
|
136
|
-
for (const
|
|
137
|
-
lines.push(
|
|
138
|
-
}
|
|
139
|
-
for (const svc of pluginServices) {
|
|
140
|
-
lines.push(this.#renderServiceLine(svc));
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
if (this.recommendedPlugins.length > 0) {
|
|
144
|
-
lines.push(" Recommended Plugins");
|
|
145
|
-
for (const p of this.recommendedPlugins) {
|
|
146
|
-
lines.push(this.#renderRecommendedLine(p));
|
|
137
|
+
if (this.plugins.length > 0) {
|
|
138
|
+
lines.push(" Plugins");
|
|
139
|
+
for (const p of this.plugins) {
|
|
140
|
+
lines.push(this.#renderUnifiedPluginLine(p));
|
|
147
141
|
}
|
|
148
142
|
}
|
|
149
143
|
if (this.#showUpdateSection()) {
|
|
@@ -152,15 +146,15 @@ export class WelcomeComponent implements Component {
|
|
|
152
146
|
return Math.max(...lines.map(l => visibleWidth(l)));
|
|
153
147
|
}
|
|
154
148
|
|
|
155
|
-
#
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
groups.set(groupName, list);
|
|
149
|
+
#renderUnifiedPluginLine(plugin: UnifiedPluginStatus): string {
|
|
150
|
+
if (plugin.state === "connected" || plugin.state === "installed") {
|
|
151
|
+
return ` ${formatStatusIcon("connected")} ${theme.fg("muted", plugin.name)}`;
|
|
152
|
+
}
|
|
153
|
+
if (plugin.state === "not_installed") {
|
|
154
|
+
return ` ${theme.fg("dim", "·")} ${theme.fg("dim", plugin.name)}`;
|
|
162
155
|
}
|
|
163
|
-
|
|
156
|
+
const hint = plugin.hint ?? "";
|
|
157
|
+
return ` ${formatStatusIcon("warning")} ${theme.fg("muted", plugin.name)} ${theme.fg("dim", hint)}`;
|
|
164
158
|
}
|
|
165
159
|
|
|
166
160
|
#buildStatusLines(rightCol: number): string[] {
|
|
@@ -172,34 +166,19 @@ export class WelcomeComponent implements Component {
|
|
|
172
166
|
lines.push(...this.#renderModelStatus());
|
|
173
167
|
lines.push("");
|
|
174
168
|
const coreServices = this.services.filter(s => !s._isPlugin);
|
|
175
|
-
const
|
|
176
|
-
const hasContent =
|
|
177
|
-
coreServices.length > 0 ||
|
|
178
|
-
pluginServices.length > 0 ||
|
|
179
|
-
this.recommendedPlugins.length > 0 ||
|
|
180
|
-
this.#showUpdateSection();
|
|
169
|
+
const hasContent = coreServices.length > 0 || this.plugins.length > 0 || this.#showUpdateSection();
|
|
181
170
|
if (hasContent) {
|
|
182
171
|
lines.push(separator);
|
|
183
172
|
for (const svc of coreServices) {
|
|
184
173
|
lines.push(this.#renderServiceLine(svc));
|
|
185
174
|
}
|
|
186
|
-
if (
|
|
187
|
-
const groups = this.#groupPluginServices(pluginServices);
|
|
188
|
-
for (const [groupName, groupServices] of groups) {
|
|
189
|
-
lines.push("");
|
|
190
|
-
lines.push(` ${theme.fg("dim", groupName)}`);
|
|
191
|
-
for (const svc of groupServices) {
|
|
192
|
-
lines.push(this.#renderServiceLine(svc));
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
if (this.recommendedPlugins.length > 0) {
|
|
175
|
+
if (this.plugins.length > 0) {
|
|
197
176
|
lines.push("");
|
|
198
|
-
lines.push(` ${theme.fg("dim", "
|
|
199
|
-
for (const p of this.
|
|
200
|
-
lines.push(this.#
|
|
177
|
+
lines.push(` ${theme.fg("dim", "Plugins")}`);
|
|
178
|
+
for (const p of this.plugins) {
|
|
179
|
+
lines.push(this.#renderUnifiedPluginLine(p));
|
|
201
180
|
}
|
|
202
|
-
const missing = this.
|
|
181
|
+
const missing = this.plugins.filter(p => p.state === "not_installed");
|
|
203
182
|
if (missing.length > 0) {
|
|
204
183
|
lines.push(` ${theme.fg("dim", "run: /plugin setup")}`);
|
|
205
184
|
}
|
|
@@ -213,13 +192,6 @@ export class WelcomeComponent implements Component {
|
|
|
213
192
|
return lines;
|
|
214
193
|
}
|
|
215
194
|
|
|
216
|
-
#renderRecommendedLine(plugin: RecommendedPluginStatus): string {
|
|
217
|
-
if (plugin.installed) {
|
|
218
|
-
return ` ${formatStatusIcon("connected")} ${theme.fg("muted", plugin.name)}`;
|
|
219
|
-
}
|
|
220
|
-
return ` ${theme.fg("dim", "·")} ${theme.fg("dim", plugin.name)}`;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
195
|
#showUpdateSection(): boolean {
|
|
224
196
|
return this.updateStatus?.available === true;
|
|
225
197
|
}
|
|
@@ -53,7 +53,7 @@ import { StatusLineComponent } from "./components/status-line";
|
|
|
53
53
|
import type { ToolExecutionHandle } from "./components/tool-execution";
|
|
54
54
|
import { type UpdateStatus, WelcomeComponent } from "./components/welcome";
|
|
55
55
|
import {
|
|
56
|
-
|
|
56
|
+
buildUnifiedPluginList,
|
|
57
57
|
type FixableService,
|
|
58
58
|
mapContextStatus,
|
|
59
59
|
runWelcomeChecks,
|
|
@@ -340,64 +340,40 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
340
340
|
? [mapContextStatus(welcomeResult.context ?? { state: "no_context" })]
|
|
341
341
|
: [];
|
|
342
342
|
|
|
343
|
-
//
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
for (const contribution of pluginContributions) {
|
|
347
|
-
try {
|
|
348
|
-
const status = await contribution.check();
|
|
349
|
-
services.push({ name: contribution.name, ...status, _isPlugin: true, _group: contribution.group });
|
|
350
|
-
} catch {
|
|
351
|
-
services.push({
|
|
352
|
-
name: contribution.name,
|
|
353
|
-
state: "unavailable",
|
|
354
|
-
hint: "check failed",
|
|
355
|
-
_isPlugin: true,
|
|
356
|
-
_group: contribution.group,
|
|
357
|
-
});
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
}
|
|
343
|
+
// Build unified plugin list from service status contributions + marketplace
|
|
344
|
+
const pluginContributions = this.session.extensionRunner?.getAllRegisteredServiceStatuses() ?? [];
|
|
345
|
+
const plugins = !startupQuiet ? await buildUnifiedPluginList(pluginContributions).catch(() => []) : [];
|
|
361
346
|
|
|
362
347
|
const fixableServices: FixableService[] = [];
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
} catch {
|
|
381
|
-
return { name: contribution.name, state: "unavailable" as const, hint: "recheck failed" };
|
|
382
|
-
}
|
|
383
|
-
},
|
|
384
|
-
});
|
|
385
|
-
}
|
|
348
|
+
for (const contribution of pluginContributions) {
|
|
349
|
+
if (contribution.fix) {
|
|
350
|
+
const plugin = plugins.find(p => p.name.toLowerCase() === contribution.name.toLowerCase());
|
|
351
|
+
if (plugin && plugin.state === "unauthenticated") {
|
|
352
|
+
fixableServices.push({
|
|
353
|
+
name: contribution.name,
|
|
354
|
+
prompt: contribution.fix.prompt,
|
|
355
|
+
command: contribution.fix.command,
|
|
356
|
+
recheck: async () => {
|
|
357
|
+
try {
|
|
358
|
+
const result = await contribution.check();
|
|
359
|
+
return { name: contribution.name, ...result };
|
|
360
|
+
} catch {
|
|
361
|
+
return { name: contribution.name, state: "unavailable" as const, hint: "recheck failed" };
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
});
|
|
386
365
|
}
|
|
387
366
|
}
|
|
388
367
|
}
|
|
389
368
|
|
|
390
|
-
const allRecommendedPlugins = !startupQuiet ? await checkRecommendedPlugins().catch(() => []) : [];
|
|
391
|
-
const pluginServiceNames = new Set(services.filter(s => s._isPlugin).map(s => s.name.toLowerCase()));
|
|
392
|
-
const recommendedPlugins = allRecommendedPlugins.filter(p => !pluginServiceNames.has(p.name.toLowerCase()));
|
|
393
|
-
|
|
394
369
|
if (!startupQuiet) {
|
|
395
370
|
this.#welcomeComponent = new WelcomeComponent(
|
|
396
371
|
this.#version,
|
|
397
372
|
welcomeResult.model,
|
|
398
373
|
services,
|
|
399
374
|
this.#initialUpdateStatus,
|
|
400
|
-
|
|
375
|
+
[],
|
|
376
|
+
plugins,
|
|
401
377
|
);
|
|
402
378
|
|
|
403
379
|
// Setup UI layout
|