@f5-sales-demo/xcsh 19.85.2 → 19.85.4
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 +8 -8
- package/src/browser/chat-handler.ts +6 -2
- package/src/cli/classify-install-target.ts +26 -3
- package/src/cli/plugin-cli.ts +33 -6
- package/src/extensibility/plugins/marketplace/manager.ts +19 -9
- package/src/internal-urls/build-info.generated.ts +8 -8
- package/src/modes/components/plugins/plugin-dashboard.ts +1 -1
- package/src/slash-commands/builtin-registry.ts +2 -2
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5-sales-demo/xcsh",
|
|
4
|
-
"version": "19.85.
|
|
4
|
+
"version": "19.85.4",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/f5-sales-demo/xcsh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -56,13 +56,13 @@
|
|
|
56
56
|
"dependencies": {
|
|
57
57
|
"@agentclientprotocol/sdk": "0.16.1",
|
|
58
58
|
"@mozilla/readability": "^0.6",
|
|
59
|
-
"@f5-sales-demo/xcsh-stats": "19.85.
|
|
60
|
-
"@f5-sales-demo/pi-agent-core": "19.85.
|
|
61
|
-
"@f5-sales-demo/pi-ai": "19.85.
|
|
62
|
-
"@f5-sales-demo/pi-natives": "19.85.
|
|
63
|
-
"@f5-sales-demo/pi-resource-management": "19.85.
|
|
64
|
-
"@f5-sales-demo/pi-tui": "19.85.
|
|
65
|
-
"@f5-sales-demo/pi-utils": "19.85.
|
|
59
|
+
"@f5-sales-demo/xcsh-stats": "19.85.4",
|
|
60
|
+
"@f5-sales-demo/pi-agent-core": "19.85.4",
|
|
61
|
+
"@f5-sales-demo/pi-ai": "19.85.4",
|
|
62
|
+
"@f5-sales-demo/pi-natives": "19.85.4",
|
|
63
|
+
"@f5-sales-demo/pi-resource-management": "19.85.4",
|
|
64
|
+
"@f5-sales-demo/pi-tui": "19.85.4",
|
|
65
|
+
"@f5-sales-demo/pi-utils": "19.85.4",
|
|
66
66
|
"@sinclair/typebox": "^0.34",
|
|
67
67
|
"@xterm/headless": "^6.0",
|
|
68
68
|
"ajv": "^8.20",
|
|
@@ -205,12 +205,16 @@ export class ChatHandler {
|
|
|
205
205
|
return;
|
|
206
206
|
}
|
|
207
207
|
if (event.type === "tool_execution_end" && "toolName" in event) {
|
|
208
|
+
// ToolExecutionEndEvent carries `isError` (NOT `error`) — checking the wrong
|
|
209
|
+
// field made this always ok:true, so errored tools rendered ✓ and "failed"
|
|
210
|
+
// never fired. Read the real field.
|
|
211
|
+
const failed = "isError" in event && Boolean(event.isError);
|
|
208
212
|
this.#server.send({
|
|
209
213
|
type: "chat_tool_notice",
|
|
210
214
|
id: chat.id,
|
|
211
215
|
tool: String(event.toolName),
|
|
212
|
-
ok: !
|
|
213
|
-
detail: `${event.toolName}: ${
|
|
216
|
+
ok: !failed,
|
|
217
|
+
detail: `${event.toolName}: ${failed ? "failed" : "done"}`,
|
|
214
218
|
});
|
|
215
219
|
return;
|
|
216
220
|
}
|
|
@@ -2,11 +2,17 @@
|
|
|
2
2
|
* Classify an install spec as a marketplace plugin reference or a plain npm package.
|
|
3
3
|
*
|
|
4
4
|
* Rules (applied in order):
|
|
5
|
+
* 0. `npm:<pkg>` prefix -> always npm (explicit escape hatch; the prefix is stripped).
|
|
5
6
|
* 1. Starts with `@` (scoped npm) -> always npm.
|
|
6
7
|
* 2. Contains `@` after the first character -> split on the LAST `@`.
|
|
7
8
|
* If the right-hand side is a known marketplace name, it's a marketplace ref.
|
|
8
9
|
* Otherwise it's an npm spec (e.g. `pkg@1.2.3`).
|
|
9
|
-
* 3.
|
|
10
|
+
* 3. Bare name (no `@`) -> look it up against the marketplace catalog index:
|
|
11
|
+
* - exactly one marketplace publishes it -> marketplace ref;
|
|
12
|
+
* - more than one -> ambiguous (caller errors and asks for `name@marketplace`);
|
|
13
|
+
* - none (or no index supplied) -> npm.
|
|
14
|
+
* This is what makes `xcsh plugin install azure` resolve to the marketplace
|
|
15
|
+
* `azure` plugin rather than the public npm package of the same name.
|
|
10
16
|
*/
|
|
11
17
|
// Common npm dist-tags that should never be interpreted as marketplace names
|
|
12
18
|
const NPM_DIST_TAGS = new Set([
|
|
@@ -25,10 +31,19 @@ const NPM_DIST_TAGS = new Set([
|
|
|
25
31
|
// Semver-like: starts with digit, or contains version range prefixes
|
|
26
32
|
const LOOKS_LIKE_VERSION = /^[\d~^>=<]/;
|
|
27
33
|
|
|
34
|
+
export type InstallTarget =
|
|
35
|
+
| { type: "marketplace"; name: string; marketplace: string }
|
|
36
|
+
| { type: "ambiguous"; name: string; marketplaces: string[] }
|
|
37
|
+
| { type: "npm"; spec: string };
|
|
38
|
+
|
|
28
39
|
export function classifyInstallTarget(
|
|
29
40
|
spec: string,
|
|
30
41
|
knownMarketplaces: Set<string>,
|
|
31
|
-
|
|
42
|
+
/** plugin name -> marketplaces whose catalog publishes a plugin with that name */
|
|
43
|
+
catalogIndex?: Map<string, string[]>,
|
|
44
|
+
): InstallTarget {
|
|
45
|
+
// Rule 0: explicit npm escape hatch — force npm even if a marketplace shares the name.
|
|
46
|
+
if (spec.startsWith("npm:")) return { type: "npm", spec: spec.slice("npm:".length) };
|
|
32
47
|
// Rule 1: scoped npm package — @ at position 0 is never a marketplace separator.
|
|
33
48
|
if (spec.startsWith("@")) return { type: "npm", spec };
|
|
34
49
|
// Rule 2: @ somewhere after the first character.
|
|
@@ -45,6 +60,14 @@ export function classifyInstallTarget(
|
|
|
45
60
|
// Not a known marketplace — treat as npm version specifier.
|
|
46
61
|
return { type: "npm", spec };
|
|
47
62
|
}
|
|
48
|
-
// Rule 3:
|
|
63
|
+
// Rule 3: bare name — resolve against the marketplace catalog index before npm.
|
|
64
|
+
const sources = catalogIndex?.get(spec);
|
|
65
|
+
if (sources && sources.length === 1) {
|
|
66
|
+
return { type: "marketplace", name: spec, marketplace: sources[0] };
|
|
67
|
+
}
|
|
68
|
+
if (sources && sources.length > 1) {
|
|
69
|
+
return { type: "ambiguous", name: spec, marketplaces: sources };
|
|
70
|
+
}
|
|
71
|
+
// Rule 4: no marketplace publishes this name — plain npm package.
|
|
49
72
|
return { type: "npm", spec };
|
|
50
73
|
}
|
package/src/cli/plugin-cli.ts
CHANGED
|
@@ -311,10 +311,10 @@ async function handleUpgrade(args: string[], flags: PluginCommandArgs["flags"]):
|
|
|
311
311
|
try {
|
|
312
312
|
if (pluginId) {
|
|
313
313
|
if (flags.scope) {
|
|
314
|
-
const result = await manager.upgradePlugin(pluginId, flags.scope);
|
|
314
|
+
const result = await manager.upgradePlugin(pluginId, flags.scope, { refresh: true });
|
|
315
315
|
console.log(chalk.green(`Upgraded ${pluginId} (${flags.scope}) to ${result.version}`));
|
|
316
316
|
} else {
|
|
317
|
-
const entries = await manager.upgradePluginAcrossScopes(pluginId);
|
|
317
|
+
const entries = await manager.upgradePluginAcrossScopes(pluginId, { refresh: true });
|
|
318
318
|
for (const entry of entries) {
|
|
319
319
|
console.log(chalk.green(`Upgraded ${pluginId} (${entry.scope}) to ${entry.version}`));
|
|
320
320
|
}
|
|
@@ -327,7 +327,7 @@ async function handleUpgrade(args: string[], flags: PluginCommandArgs["flags"]):
|
|
|
327
327
|
),
|
|
328
328
|
);
|
|
329
329
|
}
|
|
330
|
-
const results = await manager.upgradeAllPlugins();
|
|
330
|
+
const results = await manager.upgradeAllPlugins({ refresh: true });
|
|
331
331
|
if (results.length === 0) {
|
|
332
332
|
console.log("All marketplace plugins are up to date.");
|
|
333
333
|
} else {
|
|
@@ -355,12 +355,39 @@ async function handleInstall(
|
|
|
355
355
|
process.exit(1);
|
|
356
356
|
}
|
|
357
357
|
|
|
358
|
-
// Build known marketplace
|
|
358
|
+
// Build classification inputs: known marketplace names, plus a catalog index
|
|
359
|
+
// (plugin name -> marketplaces that publish it) so a bare name resolves to the
|
|
360
|
+
// marketplace plugin instead of a same-named public npm package.
|
|
359
361
|
const mktMgr = await makeMarketplaceManager();
|
|
360
|
-
const
|
|
362
|
+
const marketplaces = await mktMgr.listMarketplaces();
|
|
363
|
+
const knownMarketplaces = new Set(marketplaces.map(m => m.name));
|
|
364
|
+
const catalogIndex = new Map<string, string[]>();
|
|
365
|
+
for (const mp of marketplaces) {
|
|
366
|
+
let plugins: Awaited<ReturnType<typeof mktMgr.listAvailablePlugins>>;
|
|
367
|
+
try {
|
|
368
|
+
plugins = await mktMgr.listAvailablePlugins(mp.name);
|
|
369
|
+
} catch {
|
|
370
|
+
continue; // a broken/uncloned catalog must not block installs from others
|
|
371
|
+
}
|
|
372
|
+
for (const p of plugins) {
|
|
373
|
+
const sources = catalogIndex.get(p.name) ?? [];
|
|
374
|
+
if (!sources.includes(mp.name)) sources.push(mp.name);
|
|
375
|
+
catalogIndex.set(p.name, sources);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
361
378
|
|
|
362
379
|
for (const spec of packages) {
|
|
363
|
-
const target = classifyInstallTarget(spec, knownMarketplaces);
|
|
380
|
+
const target = classifyInstallTarget(spec, knownMarketplaces, catalogIndex);
|
|
381
|
+
|
|
382
|
+
if (target.type === "ambiguous") {
|
|
383
|
+
console.error(
|
|
384
|
+
chalk.red(
|
|
385
|
+
`${theme.status.error} "${target.name}" is published by multiple marketplaces: ${target.marketplaces.join(", ")}.`,
|
|
386
|
+
),
|
|
387
|
+
);
|
|
388
|
+
console.error(chalk.dim(`Disambiguate with ${target.name}@<marketplace>.`));
|
|
389
|
+
process.exit(1);
|
|
390
|
+
}
|
|
364
391
|
|
|
365
392
|
if (target.type === "marketplace") {
|
|
366
393
|
try {
|
|
@@ -563,9 +563,13 @@ export class MarketplaceManager {
|
|
|
563
563
|
// Compare installed plugin versions against their catalog entries.
|
|
564
564
|
// Returns one entry per (pluginId, scope) pair where the catalog declares a newer version.
|
|
565
565
|
// Catalog entries without a version field are skipped.
|
|
566
|
-
async checkForUpdates(
|
|
567
|
-
|
|
568
|
-
|
|
566
|
+
async checkForUpdates(opts?: {
|
|
567
|
+
refresh?: boolean;
|
|
568
|
+
}): Promise<Array<{ pluginId: string; scope: "user" | "project" | "local"; from: string; to: string }>> {
|
|
569
|
+
// Explicit upgrade flows pass refresh:true to re-fetch catalogs from source before
|
|
570
|
+
// comparing, so freshly-published versions are seen. Passive callers (startup notify,
|
|
571
|
+
// dashboard poll) omit it and rely on the 24h TTL (refreshStaleMarketplaces).
|
|
572
|
+
if (opts?.refresh) await this.updateAllMarketplaces();
|
|
569
573
|
const mktReg = await readMarketplacesRegistry(this.#opts.marketplacesRegistryPath);
|
|
570
574
|
const updates: Array<{ pluginId: string; scope: "user" | "project" | "local"; from: string; to: string }> = [];
|
|
571
575
|
|
|
@@ -620,7 +624,12 @@ export class MarketplaceManager {
|
|
|
620
624
|
}
|
|
621
625
|
|
|
622
626
|
// Re-install a specific plugin at the latest catalog version (force-overwrites).
|
|
623
|
-
async upgradePlugin(
|
|
627
|
+
async upgradePlugin(
|
|
628
|
+
pluginId: string,
|
|
629
|
+
scope?: "user" | "project" | "local",
|
|
630
|
+
opts?: { refresh?: boolean },
|
|
631
|
+
): Promise<InstalledPluginEntry> {
|
|
632
|
+
if (opts?.refresh) await this.updateAllMarketplaces();
|
|
624
633
|
const parsed = parsePluginId(pluginId);
|
|
625
634
|
if (!parsed) {
|
|
626
635
|
throw new Error(`Invalid plugin ID: "${pluginId}". Expected "name@marketplace".`);
|
|
@@ -656,7 +665,8 @@ export class MarketplaceManager {
|
|
|
656
665
|
|
|
657
666
|
// Upgrade a plugin across all scopes where it is installed.
|
|
658
667
|
// Returns one entry per scope upgraded (0–2 entries).
|
|
659
|
-
async upgradePluginAcrossScopes(pluginId: string): Promise<InstalledPluginEntry[]> {
|
|
668
|
+
async upgradePluginAcrossScopes(pluginId: string, opts?: { refresh?: boolean }): Promise<InstalledPluginEntry[]> {
|
|
669
|
+
if (opts?.refresh) await this.updateAllMarketplaces();
|
|
660
670
|
const parsed = parsePluginId(pluginId);
|
|
661
671
|
if (!parsed) {
|
|
662
672
|
throw new Error(`Invalid plugin ID: "${pluginId}". Expected "name@marketplace".`);
|
|
@@ -688,10 +698,10 @@ export class MarketplaceManager {
|
|
|
688
698
|
// Upgrade every (pluginId, scope) pair that checkForUpdates reports as outdated.
|
|
689
699
|
// Only stale scopes are touched; a current user install is not re-installed when only
|
|
690
700
|
// the project scope is stale. Per-entry failures are skipped — partial success is returned.
|
|
691
|
-
async upgradeAllPlugins(
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
const updates = await this.checkForUpdates();
|
|
701
|
+
async upgradeAllPlugins(opts?: {
|
|
702
|
+
refresh?: boolean;
|
|
703
|
+
}): Promise<Array<{ pluginId: string; scope: "user" | "project" | "local"; from: string; to: string }>> {
|
|
704
|
+
const updates = await this.checkForUpdates(opts);
|
|
695
705
|
const results: Array<{ pluginId: string; scope: "user" | "project" | "local"; from: string; to: string }> = [];
|
|
696
706
|
for (const update of updates) {
|
|
697
707
|
try {
|
|
@@ -17,17 +17,17 @@ export interface BuildInfo {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export const BUILD_INFO: BuildInfo = {
|
|
20
|
-
"version": "19.85.
|
|
21
|
-
"commit": "
|
|
22
|
-
"shortCommit": "
|
|
20
|
+
"version": "19.85.4",
|
|
21
|
+
"commit": "d014668cc60902dc4545082fc2c4c2062dff4046",
|
|
22
|
+
"shortCommit": "d014668",
|
|
23
23
|
"branch": "main",
|
|
24
|
-
"tag": "v19.85.
|
|
25
|
-
"commitDate": "2026-07-
|
|
26
|
-
"buildDate": "2026-07-
|
|
24
|
+
"tag": "v19.85.4",
|
|
25
|
+
"commitDate": "2026-07-23T21:02:17Z",
|
|
26
|
+
"buildDate": "2026-07-23T21:25:20.917Z",
|
|
27
27
|
"dirty": true,
|
|
28
28
|
"prNumber": "",
|
|
29
29
|
"repoUrl": "https://github.com/f5-sales-demo/xcsh",
|
|
30
30
|
"repoSlug": "f5-sales-demo/xcsh",
|
|
31
|
-
"commitUrl": "https://github.com/f5-sales-demo/xcsh/commit/
|
|
32
|
-
"releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v19.85.
|
|
31
|
+
"commitUrl": "https://github.com/f5-sales-demo/xcsh/commit/d014668cc60902dc4545082fc2c4c2062dff4046",
|
|
32
|
+
"releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v19.85.4"
|
|
33
33
|
};
|
|
@@ -353,7 +353,7 @@ export class PluginDashboard extends Container {
|
|
|
353
353
|
this.#rebuildAndRender();
|
|
354
354
|
|
|
355
355
|
try {
|
|
356
|
-
await this.#mgr.upgradePlugin(plugin.id, plugin.scope);
|
|
356
|
+
await this.#mgr.upgradePlugin(plugin.id, plugin.scope, { refresh: true });
|
|
357
357
|
this.#state.notice = t("plugins.dashboard.upgraded", { name: plugin.name });
|
|
358
358
|
await this.#reloadData();
|
|
359
359
|
} catch (error) {
|
|
@@ -1144,12 +1144,12 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<BuiltinSlashCommandSpec> = [
|
|
|
1144
1144
|
runtime.ctx.showStatus(upArgs.error);
|
|
1145
1145
|
return;
|
|
1146
1146
|
}
|
|
1147
|
-
const result = await mgr.upgradePlugin(upArgs.pluginId, upArgs.scope);
|
|
1147
|
+
const result = await mgr.upgradePlugin(upArgs.pluginId, upArgs.scope, { refresh: true });
|
|
1148
1148
|
runtime.ctx.showStatus(
|
|
1149
1149
|
t("commands.plugin.upgraded", { pluginId: upArgs.pluginId, version: result.version }),
|
|
1150
1150
|
);
|
|
1151
1151
|
} else {
|
|
1152
|
-
const results = await mgr.upgradeAllPlugins();
|
|
1152
|
+
const results = await mgr.upgradeAllPlugins({ refresh: true });
|
|
1153
1153
|
if (results.length === 0) {
|
|
1154
1154
|
runtime.ctx.showStatus(t("commands.plugin.allUpToDate"));
|
|
1155
1155
|
} else {
|