@f5xc-salesdemos/xcsh 19.19.2 → 19.20.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": "19.
|
|
4
|
+
"version": "19.20.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": "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.0",
|
|
54
|
+
"@f5xc-salesdemos/pi-agent-core": "19.20.0",
|
|
55
|
+
"@f5xc-salesdemos/pi-ai": "19.20.0",
|
|
56
|
+
"@f5xc-salesdemos/pi-natives": "19.20.0",
|
|
57
|
+
"@f5xc-salesdemos/pi-tui": "19.20.0",
|
|
58
|
+
"@f5xc-salesdemos/pi-utils": "19.20.0",
|
|
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.0",
|
|
21
|
+
"commit": "218c1d5ef18212bd9c034ea86f80927094c941c1",
|
|
22
|
+
"shortCommit": "218c1d5",
|
|
23
23
|
"branch": "main",
|
|
24
|
-
"tag": "v19.
|
|
25
|
-
"commitDate": "2026-06-
|
|
26
|
-
"buildDate": "2026-06-
|
|
24
|
+
"tag": "v19.20.0",
|
|
25
|
+
"commitDate": "2026-06-09T03:36:14Z",
|
|
26
|
+
"buildDate": "2026-06-09T04:39:54.571Z",
|
|
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/218c1d5ef18212bd9c034ea86f80927094c941c1",
|
|
32
|
+
"releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v19.20.0"
|
|
33
33
|
};
|
|
@@ -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,62 +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 recommendedPlugins = !startupQuiet ? await checkRecommendedPlugins().catch(() => []) : [];
|
|
391
|
-
|
|
392
369
|
if (!startupQuiet) {
|
|
393
370
|
this.#welcomeComponent = new WelcomeComponent(
|
|
394
371
|
this.#version,
|
|
395
372
|
welcomeResult.model,
|
|
396
373
|
services,
|
|
397
374
|
this.#initialUpdateStatus,
|
|
398
|
-
|
|
375
|
+
[],
|
|
376
|
+
plugins,
|
|
399
377
|
);
|
|
400
378
|
|
|
401
379
|
// Setup UI layout
|
|
@@ -113,6 +113,6 @@ Swap exactly one block per oneOf group — e.g. `enable_ha {}` replaces `disable
|
|
|
113
113
|
- `site_mesh_group_on_slo { site_mesh_group { name = "<n>", namespace = "system" } }` ← note the nested `site_mesh_group` wrapper
|
|
114
114
|
- `custom_proxy { proxy_ip_address = "proxy.example.com", proxy_port = 8080 }` ← use `proxy_ip_address`/`proxy_port` (NOT `http_proxy`/`https_proxy`)
|
|
115
115
|
- `custom_proxy_bypass { proxy_bypass = ["10.0.0.0/8"] }` ← use `proxy_bypass` (NOT `bypass_list`)
|
|
116
|
-
- `blocked_services { blocked_service { network_type = "VIRTUAL_NETWORK_SITE_LOCAL" } }` ← use `blocked_service` with `network_type` (NOT `service_list`)
|
|
116
|
+
- `blocked_services { blocked_service { network_type = "VIRTUAL_NETWORK_SITE_LOCAL" } }` ← use `blocked_service` with `network_type` (NOT `service_list`). In Terraform, "blocking HTTP services" = blocking `VIRTUAL_NETWORK_SITE_LOCAL` network type. Always write the file even when the phrase mentions "HTTP service in blocked services list" — map it to `blocked_service { network_type = "VIRTUAL_NETWORK_SITE_LOCAL" }`
|
|
117
117
|
|
|
118
118
|
**CRITICAL — Terraform file write rule**: When asked to "Write Terraform HCL for f5xc_securemesh_site_v2", you **MUST** use the `xcsh_write_file` tool to write the complete `.tf` file to disk. Always name the file after the resource name in the request (e.g., `ar-test-smsv2-1a.tf`). Do NOT just return a coverage table — always write the actual HCL file. The file must include a `terraform { required_providers { f5xc = { source = "f5xc-salesdemos/f5xc" } } }` block and the complete `resource "f5xc_securemesh_site_v2"` block with all 12 oneOf groups.
|