@f5xc-salesdemos/xcsh 19.4.0 → 19.5.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.0",
4
+ "version": "19.5.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.4.0",
54
- "@f5xc-salesdemos/pi-agent-core": "19.4.0",
55
- "@f5xc-salesdemos/pi-ai": "19.4.0",
56
- "@f5xc-salesdemos/pi-natives": "19.4.0",
57
- "@f5xc-salesdemos/pi-tui": "19.4.0",
58
- "@f5xc-salesdemos/pi-utils": "19.4.0",
53
+ "@f5xc-salesdemos/xcsh-stats": "19.5.1",
54
+ "@f5xc-salesdemos/pi-agent-core": "19.5.1",
55
+ "@f5xc-salesdemos/pi-ai": "19.5.1",
56
+ "@f5xc-salesdemos/pi-natives": "19.5.1",
57
+ "@f5xc-salesdemos/pi-tui": "19.5.1",
58
+ "@f5xc-salesdemos/pi-utils": "19.5.1",
59
59
  "@sinclair/typebox": "^0.34",
60
60
  "@xterm/headless": "^6.0",
61
61
  "ajv": "^8.20",
@@ -323,13 +323,15 @@ export class MarketplaceManager {
323
323
  const now = new Date().toISOString();
324
324
  // Carry over enabled flag from existing entry — a disabled plugin must stay disabled after upgrade
325
325
  const wasDisabled = existing?.some(e => e.enabled === false);
326
+ // Honor defaultEnabled from catalog — new installs with defaultEnabled: false start disabled
327
+ const defaultDisabled = !existing && pluginEntry.defaultEnabled === false;
326
328
  const installedEntry: InstalledPluginEntry = {
327
329
  scope,
328
330
  installPath: cachePath,
329
331
  version,
330
332
  installedAt: now,
331
333
  lastUpdated: now,
332
- ...(wasDisabled ? { enabled: false } : {}),
334
+ ...(wasDisabled || defaultDisabled ? { enabled: false } : {}),
333
335
  };
334
336
 
335
337
  const freshInstReg = await readInstalledPluginsRegistry(registryPath);
@@ -719,7 +721,7 @@ export class MarketplaceManager {
719
721
  } catch (err) {
720
722
  if (isEnoent(err)) {
721
723
  throw new Error(
722
- `Marketplace catalog not found at ${entry.catalogPath}. Try: /marketplace update ${entry.name}`,
724
+ `Marketplace catalog not found at ${entry.catalogPath}. Try: /plugin marketplace update ${entry.name}`,
723
725
  );
724
726
  }
725
727
  throw err;
@@ -76,6 +76,7 @@ export interface MarketplacePluginAuthor {
76
76
 
77
77
  export interface MarketplacePluginEntry {
78
78
  name: string;
79
+ displayName?: string;
79
80
  source: PluginSource;
80
81
  description?: string;
81
82
  version?: string;
@@ -87,6 +88,7 @@ export interface MarketplacePluginEntry {
87
88
  category?: string;
88
89
  tags?: string[];
89
90
  strict?: boolean;
91
+ defaultEnabled?: boolean;
90
92
  commands?: string | string[];
91
93
  agents?: string | string[];
92
94
  hooks?: string | Record<string, unknown>;
@@ -17,17 +17,17 @@ export interface BuildInfo {
17
17
  }
18
18
 
19
19
  export const BUILD_INFO: BuildInfo = {
20
- "version": "19.4.0",
21
- "commit": "fd384b6c522bb33ac933274223d2c98d247737e2",
22
- "shortCommit": "fd384b6",
20
+ "version": "19.5.1",
21
+ "commit": "9c15a98f105051030aa1e0f5376d315d0214e8fd",
22
+ "shortCommit": "9c15a98",
23
23
  "branch": "main",
24
- "tag": "v19.4.0",
25
- "commitDate": "2026-06-04T16:08:47Z",
26
- "buildDate": "2026-06-04T16:50:46.727Z",
24
+ "tag": "v19.5.1",
25
+ "commitDate": "2026-06-04T17:32:07Z",
26
+ "buildDate": "2026-06-04T17:56:43.676Z",
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/fd384b6c522bb33ac933274223d2c98d247737e2",
32
- "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v19.4.0"
31
+ "commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/9c15a98f105051030aa1e0f5376d315d0214e8fd",
32
+ "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v19.5.1"
33
33
  };
package/src/main.ts CHANGED
@@ -749,7 +749,7 @@ export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise<v
749
749
  await mgr.upgradeAllPlugins();
750
750
  logger.debug(`Auto-upgraded ${updates.length} marketplace plugin(s)`);
751
751
  } else {
752
- logger.debug(`${updates.length} marketplace plugin update(s) available \u2014 /marketplace upgrade`);
752
+ logger.debug(`${updates.length} marketplace plugin update(s) available \u2014 /plugin upgrade`);
753
753
  }
754
754
  } catch {
755
755
  // Silently ignore — network failure, corrupt data, offline.
@@ -54,7 +54,7 @@ export class PluginSelectorComponent extends Container {
54
54
  label: "No plugins available",
55
55
  description:
56
56
  marketplaceCount === 0
57
- ? "Add a marketplace first: /marketplace add <source>"
57
+ ? "Add a marketplace first: /plugin marketplace add <source>"
58
58
  : "Configured marketplaces have no plugins",
59
59
  });
60
60
  }
@@ -251,7 +251,7 @@ export class PluginDashboard extends Container {
251
251
  if (!plugin) return;
252
252
  const tabId = this.#activeTabId();
253
253
 
254
- if (tabId === "available" && !plugin.installed) {
254
+ if (tabId === "discover" && !plugin.installed) {
255
255
  await this.#installPlugin(plugin);
256
256
  } else if (tabId === "updates" && plugin.hasUpdate) {
257
257
  await this.#upgradePlugin(plugin);
@@ -320,7 +320,7 @@ export class PluginDashboard extends Container {
320
320
  #getHelpText(): string {
321
321
  const tabId = this.#activeTabId();
322
322
  switch (tabId) {
323
- case "available":
323
+ case "discover":
324
324
  return " ↑/↓: navigate Enter: install Tab: next tab Ctrl+R: reload Esc: close";
325
325
  case "updates":
326
326
  return " ↑/↓: navigate Enter: upgrade Tab: next tab Ctrl+R: reload Esc: close";
@@ -14,7 +14,7 @@ export class PluginInspectorPane implements Component {
14
14
  const lines: string[] = [];
15
15
  const p = this.plugin;
16
16
 
17
- lines.push(theme.bold(theme.fg("contentAccent", replaceTabs(p.name))));
17
+ lines.push(theme.bold(theme.fg("contentAccent", replaceTabs(p.displayName || p.name))));
18
18
  lines.push("");
19
19
 
20
20
  lines.push(`${theme.fg("muted", "Source:")} ${p.source}`);
@@ -22,7 +22,7 @@ export class PluginListPane implements Component {
22
22
 
23
23
  if (this.plugins.length === 0) {
24
24
  const msg =
25
- this.activeTab === "available"
25
+ this.activeTab === "discover"
26
26
  ? "No plugins available. Add a marketplace first."
27
27
  : this.activeTab === "updates"
28
28
  ? "All plugins are up to date."
@@ -71,7 +71,7 @@ export class PluginListPane implements Component {
71
71
  }
72
72
 
73
73
  parts.push(" ");
74
- parts.push(plugin.name);
74
+ parts.push(plugin.displayName || plugin.name);
75
75
 
76
76
  if (plugin.version) {
77
77
  parts.push(theme.fg("dim", ` v${plugin.version}`));
@@ -48,6 +48,7 @@ function catalogToDashboard(entry: MarketplacePluginEntry, marketplace: string):
48
48
  return {
49
49
  id: `${entry.name}@${marketplace}`,
50
50
  name: normalizePluginDisplayName(entry.name),
51
+ displayName: entry.displayName,
51
52
  marketplace,
52
53
  source: "marketplace",
53
54
  version: entry.version,
@@ -96,6 +97,7 @@ export async function loadAllPlugins(mgr: MarketplaceManager, npmMgr: PluginMana
96
97
  if (installedIds.has(pluginId)) {
97
98
  const existing = plugins.find(p => p.id === pluginId);
98
99
  if (existing) {
100
+ existing.displayName = existing.displayName || entry.displayName;
99
101
  existing.description = existing.description || entry.description;
100
102
  existing.category = existing.category || entry.category;
101
103
  existing.tags = existing.tags || entry.tags;
@@ -124,12 +126,12 @@ export async function loadAllPlugins(mgr: MarketplaceManager, npmMgr: PluginMana
124
126
  export function buildTabs(plugins: DashboardPlugin[]): PluginTab[] {
125
127
  const tabs: PluginTab[] = [];
126
128
  const installedCount = plugins.filter(p => p.installed).length;
127
- const availableCount = plugins.filter(p => !p.installed).length;
129
+ const discoverCount = plugins.filter(p => !p.installed).length;
128
130
  const updatesCount = plugins.filter(p => p.hasUpdate).length;
129
131
 
130
132
  tabs.push({ id: "installed", label: "Installed", count: installedCount });
131
- if (availableCount > 0) {
132
- tabs.push({ id: "available", label: "Available", count: availableCount });
133
+ if (discoverCount > 0) {
134
+ tabs.push({ id: "discover", label: "Discover", count: discoverCount });
133
135
  }
134
136
  if (updatesCount > 0) {
135
137
  tabs.push({ id: "updates", label: "Updates", count: updatesCount });
@@ -141,7 +143,7 @@ export function filterByTab(plugins: DashboardPlugin[], tabId: PluginTabId): Das
141
143
  switch (tabId) {
142
144
  case "installed":
143
145
  return plugins.filter(p => p.installed);
144
- case "available":
146
+ case "discover":
145
147
  return plugins.filter(p => !p.installed);
146
148
  case "updates":
147
149
  return plugins.filter(p => p.hasUpdate);
@@ -155,6 +157,7 @@ export function applySearch(plugins: DashboardPlugin[], query: string): Dashboar
155
157
  const q = query.toLowerCase();
156
158
  return plugins.filter(p => {
157
159
  if (p.name.toLowerCase().includes(q)) return true;
160
+ if (p.displayName?.toLowerCase().includes(q)) return true;
158
161
  if (p.description?.toLowerCase().includes(q)) return true;
159
162
  if (p.marketplace?.toLowerCase().includes(q)) return true;
160
163
  if (p.category?.toLowerCase().includes(q)) return true;
@@ -1,6 +1,7 @@
1
1
  export interface DashboardPlugin {
2
2
  id: string;
3
3
  name: string;
4
+ displayName?: string;
4
5
  marketplace?: string;
5
6
  source: "npm" | "marketplace";
6
7
  scope?: "user" | "project";
@@ -19,7 +20,7 @@ export interface DashboardPlugin {
19
20
  updateVersion?: string;
20
21
  }
21
22
 
22
- export type PluginTabId = "installed" | "available" | "updates";
23
+ export type PluginTabId = "installed" | "discover" | "updates";
23
24
 
24
25
  export interface PluginTab {
25
26
  id: PluginTabId;
@@ -858,37 +858,60 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<BuiltinSlashCommandSpec> = [
858
858
  handle: shutdownHandler,
859
859
  },
860
860
  {
861
- name: "marketplace",
862
- description: "Manage marketplace plugin sources and installed plugins",
861
+ name: "plugin",
862
+ aliases: ["marketplace", "plugins"],
863
+ description: "Manage plugins and marketplace sources",
863
864
  subcommands: [
864
- { name: "add", description: "Add a marketplace source", usage: "<source>" },
865
- { name: "remove", description: "Remove a marketplace source", usage: "<name>" },
866
- { name: "update", description: "Update marketplace catalog(s)", usage: "[name]" },
867
- { name: "list", description: "List configured marketplaces" },
868
- { name: "discover", description: "Browse available plugins", usage: "[marketplace]" },
865
+ { name: "marketplace", description: "Manage marketplace sources (add, remove, update, list)" },
869
866
  {
870
867
  name: "install",
871
- description: "Install a plugin (interactive browser if no args)",
872
- usage: "[--force] [name@marketplace]",
868
+ description: "Install a plugin",
869
+ usage: "[--force] [--scope user|project] <name@marketplace>",
873
870
  },
874
- { name: "uninstall", description: "Uninstall a plugin (selector if no args)", usage: "[name@marketplace]" },
875
- { name: "installed", description: "List installed marketplace plugins" },
876
- { name: "upgrade", description: "Upgrade outdated plugins", usage: "[name@marketplace]" },
871
+ { name: "uninstall", description: "Uninstall a plugin", usage: "[--scope user|project] <name@marketplace>" },
872
+ { name: "enable", description: "Enable a plugin", usage: "[--scope user|project] <name@marketplace>" },
873
+ { name: "disable", description: "Disable a plugin", usage: "[--scope user|project] <name@marketplace>" },
874
+ { name: "upgrade", description: "Upgrade plugins", usage: "[--scope user|project] [name@marketplace]" },
875
+ { name: "discover", description: "Browse available plugins", usage: "[marketplace]" },
876
+ { name: "list", description: "List all installed plugins" },
877
+ { name: "validate", description: "Validate marketplace or plugin manifest", usage: "[path]" },
877
878
  { name: "help", description: "Show usage guide" },
878
879
  ],
879
880
  allowArgs: true,
880
881
  handle: async (command, runtime) => {
881
882
  runtime.ctx.editor.setText("");
882
883
  const args = command.args.trim().split(/\s+/);
883
- const sub = args[0] || "install";
884
+ const sub = args[0] || "";
884
885
  const rest = args.slice(1).join(" ").trim();
885
886
 
886
- // /marketplace (no args) or /marketplace install (no args) → interactive browser
887
- if ((sub === "install" && !rest) || (!args[0] && !command.args.trim())) {
887
+ // /plugin (no args) open interactive dashboard
888
+ if (!sub) {
889
+ runtime.ctx.showPluginDashboard();
890
+ return;
891
+ }
892
+
893
+ // /plugin install (no args) → interactive browser
894
+ if (sub === "install" && !rest) {
888
895
  try {
889
896
  runtime.ctx.showPluginSelector("install");
890
897
  } catch (err) {
891
- runtime.ctx.showStatus(`Marketplace error: ${err}`);
898
+ runtime.ctx.showStatus(`Plugin error: ${err}`);
899
+ }
900
+ return;
901
+ }
902
+
903
+ // /plugin list (no args) → open interactive dashboard
904
+ if (sub === "list" && !rest) {
905
+ runtime.ctx.showPluginDashboard();
906
+ return;
907
+ }
908
+
909
+ // /plugin uninstall (no args) → interactive uninstall selector
910
+ if (sub === "uninstall" && !rest) {
911
+ try {
912
+ runtime.ctx.showPluginSelector("uninstall");
913
+ } catch (err) {
914
+ runtime.ctx.showStatus(`Plugin error: ${err}`);
892
915
  }
893
916
  return;
894
917
  }
@@ -911,9 +934,63 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<BuiltinSlashCommandSpec> = [
911
934
 
912
935
  try {
913
936
  switch (sub) {
937
+ // ── Marketplace management (/plugin marketplace add|remove|update|list) ──
938
+ case "marketplace": {
939
+ const mktArgs = rest.split(/\s+/);
940
+ const mktSub = mktArgs[0] || "";
941
+ const mktRest = mktArgs.slice(1).join(" ").trim();
942
+ switch (mktSub) {
943
+ case "add": {
944
+ if (!mktRest) {
945
+ runtime.ctx.showStatus("Usage: /plugin marketplace add <source>");
946
+ return;
947
+ }
948
+ const entry = await mgr.addMarketplace(mktRest);
949
+ runtime.ctx.showStatus(`Added marketplace: ${entry.name}`);
950
+ break;
951
+ }
952
+ case "remove":
953
+ case "rm": {
954
+ if (!mktRest) {
955
+ runtime.ctx.showStatus("Usage: /plugin marketplace remove <name>");
956
+ return;
957
+ }
958
+ await mgr.removeMarketplace(mktRest);
959
+ runtime.ctx.showStatus(`Removed marketplace: ${mktRest}`);
960
+ break;
961
+ }
962
+ case "update": {
963
+ if (mktRest) {
964
+ await mgr.updateMarketplace(mktRest);
965
+ runtime.ctx.showStatus(`Updated marketplace: ${mktRest}`);
966
+ } else {
967
+ const results = await mgr.updateAllMarketplaces();
968
+ runtime.ctx.showStatus(`Updated ${results.length} marketplace(s)`);
969
+ }
970
+ break;
971
+ }
972
+ case "list":
973
+ default: {
974
+ const marketplaces = await mgr.listMarketplaces();
975
+ if (marketplaces.length === 0) {
976
+ runtime.ctx.showStatus(
977
+ "No marketplaces configured.\n\nGet started:\n /plugin marketplace add f5xc-salesdemos/marketplace",
978
+ );
979
+ } else {
980
+ const lines = marketplaces.map(m => ` ${m.name} ${m.sourceUri}`);
981
+ runtime.ctx.showStatus(
982
+ `Marketplaces:\n${lines.join("\n")}\n\nUse /plugin discover to browse plugins`,
983
+ );
984
+ }
985
+ break;
986
+ }
987
+ }
988
+ break;
989
+ }
990
+ // ── Legacy shorthand: /marketplace add|remove|update → /plugin marketplace ──
914
991
  case "add": {
915
992
  if (!rest) {
916
- runtime.ctx.showStatus("Usage: /marketplace add <source>");
993
+ runtime.ctx.showStatus("Usage: /plugin marketplace add <source>");
917
994
  return;
918
995
  }
919
996
  const entry = await mgr.addMarketplace(rest);
@@ -923,7 +1000,7 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<BuiltinSlashCommandSpec> = [
923
1000
  case "remove":
924
1001
  case "rm": {
925
1002
  if (!rest) {
926
- runtime.ctx.showStatus("Usage: /marketplace remove <name>");
1003
+ runtime.ctx.showStatus("Usage: /plugin marketplace remove <name>");
927
1004
  return;
928
1005
  }
929
1006
  await mgr.removeMarketplace(rest);
@@ -940,13 +1017,14 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<BuiltinSlashCommandSpec> = [
940
1017
  }
941
1018
  break;
942
1019
  }
1020
+ // ── Plugin discovery ──
943
1021
  case "discover": {
944
1022
  const plugins = await mgr.listAvailablePlugins(rest || undefined);
945
1023
  if (plugins.length === 0) {
946
1024
  const marketplaces = await mgr.listMarketplaces();
947
1025
  if (marketplaces.length === 0) {
948
1026
  runtime.ctx.showStatus(
949
- "No marketplaces configured. Try:\n /marketplace add f5xc-salesdemos/marketplace",
1027
+ "No marketplaces configured. Try:\n /plugin marketplace add f5xc-salesdemos/marketplace",
950
1028
  );
951
1029
  } else {
952
1030
  runtime.ctx.showStatus("No plugins available in configured marketplaces");
@@ -960,8 +1038,8 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<BuiltinSlashCommandSpec> = [
960
1038
  }
961
1039
  break;
962
1040
  }
1041
+ // ── Install ──
963
1042
  case "install": {
964
- // Parse: /marketplace install [--force] [--scope user|project] name@marketplace
965
1043
  const parsed = parseMarketplaceInstallArgs(rest);
966
1044
  if ("error" in parsed) {
967
1045
  runtime.ctx.showStatus(parsed.error);
@@ -974,15 +1052,11 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<BuiltinSlashCommandSpec> = [
974
1052
  runtime.ctx.showStatus(`Installed ${name} from ${marketplace}`);
975
1053
  break;
976
1054
  }
1055
+ // ── Uninstall ──
977
1056
  case "uninstall": {
978
- if (!rest) {
979
- // No args → open interactive uninstall selector
980
- runtime.ctx.showPluginSelector("uninstall");
981
- return;
982
- }
983
1057
  const uninstArgs = parsePluginScopeArgs(
984
1058
  rest,
985
- "Usage: /marketplace uninstall [--scope user|project] <name@marketplace>",
1059
+ "Usage: /plugin uninstall [--scope user|project] <name@marketplace>",
986
1060
  );
987
1061
  if ("error" in uninstArgs) {
988
1062
  runtime.ctx.showStatus(uninstArgs.error);
@@ -992,23 +1066,28 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<BuiltinSlashCommandSpec> = [
992
1066
  runtime.ctx.showStatus(`Uninstalled ${uninstArgs.pluginId}`);
993
1067
  break;
994
1068
  }
995
- case "installed": {
996
- const installed = await mgr.listInstalledPlugins();
997
- if (installed.length === 0) {
998
- runtime.ctx.showStatus("No marketplace plugins installed");
999
- } else {
1000
- const lines = installed.map(
1001
- p => ` ${p.id} [${p.scope}]${p.shadowedBy ? " [shadowed]" : ""} (${p.entries.length} entry)`,
1002
- );
1003
- runtime.ctx.showStatus(`Installed plugins:\n${lines.join("\n")}`);
1069
+ // ── Enable / Disable ──
1070
+ case "enable":
1071
+ case "disable": {
1072
+ const parsed = parsePluginScopeArgs(
1073
+ rest ?? "",
1074
+ `Usage: /plugin ${sub} [--scope user|project] <name@marketplace>`,
1075
+ );
1076
+ if ("error" in parsed) {
1077
+ runtime.ctx.showStatus(parsed.error);
1078
+ return;
1004
1079
  }
1080
+ const isEnable = sub === "enable";
1081
+ await mgr.setPluginEnabled(parsed.pluginId, isEnable, parsed.scope);
1082
+ runtime.ctx.showStatus(`${isEnable ? "Enabled" : "Disabled"} ${parsed.pluginId}`);
1005
1083
  break;
1006
1084
  }
1085
+ // ── Upgrade ──
1007
1086
  case "upgrade": {
1008
1087
  if (rest) {
1009
1088
  const upArgs = parsePluginScopeArgs(
1010
1089
  rest,
1011
- "Usage: /marketplace upgrade [--scope user|project] <name@marketplace>",
1090
+ "Usage: /plugin upgrade [--scope user|project] <name@marketplace>",
1012
1091
  );
1013
1092
  if ("error" in upArgs) {
1014
1093
  runtime.ctx.showStatus(upArgs.error);
@@ -1019,7 +1098,7 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<BuiltinSlashCommandSpec> = [
1019
1098
  } else {
1020
1099
  const results = await mgr.upgradeAllPlugins();
1021
1100
  if (results.length === 0) {
1022
- runtime.ctx.showStatus("All marketplace plugins are up to date");
1101
+ runtime.ctx.showStatus("All plugins are up to date");
1023
1102
  } else {
1024
1103
  const lines = results.map(r => ` ${r.pluginId}: ${r.from} -> ${r.to}`);
1025
1104
  runtime.ctx.showStatus(`Upgraded ${results.length} plugin(s):\n${lines.join("\n")}`);
@@ -1027,106 +1106,9 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<BuiltinSlashCommandSpec> = [
1027
1106
  }
1028
1107
  break;
1029
1108
  }
1030
- case "help": {
1031
- runtime.ctx.showStatus(
1032
- [
1033
- "Marketplace commands:",
1034
- " /marketplace Browse and install plugins",
1035
- " /marketplace add <source> Add a marketplace (e.g. owner/repo)",
1036
- " /marketplace remove <name> Remove a marketplace",
1037
- " /marketplace update [name] Re-fetch catalog(s)",
1038
- " /marketplace list List configured marketplaces",
1039
- " /marketplace discover [marketplace] Browse available plugins",
1040
- " /marketplace install <name@marketplace> Install a plugin",
1041
- " /marketplace uninstall <name@marketplace> Uninstall a plugin",
1042
- " /marketplace installed List installed plugins",
1043
- " /marketplace upgrade [name@marketplace] Upgrade plugin(s)",
1044
- "",
1045
- "Quick start:",
1046
- " /marketplace add f5xc-salesdemos/marketplace",
1047
- " /marketplace (opens interactive browser)",
1048
- ].join("\n"),
1049
- );
1050
- break;
1051
- }
1052
- default: {
1053
- const marketplaces = await mgr.listMarketplaces();
1054
- if (marketplaces.length === 0) {
1055
- runtime.ctx.showStatus(
1056
- "No marketplaces configured.\n\nGet started:\n /marketplace add f5xc-salesdemos/marketplace\n\nThen browse plugins with /marketplace or /marketplace discover",
1057
- );
1058
- } else {
1059
- const lines = marketplaces.map(m => ` ${m.name} ${m.sourceUri}`);
1060
- runtime.ctx.showStatus(
1061
- `Marketplaces:\n${lines.join("\n")}\n\nUse /marketplace discover to browse plugins, or /marketplace help for all commands`,
1062
- );
1063
- }
1064
- break;
1065
- }
1066
- }
1067
- } catch (err) {
1068
- const msg = err instanceof Error ? err.message : String(err);
1069
- runtime.ctx.showStatus(`Marketplace error: ${msg}`);
1070
- }
1071
- },
1072
- },
1073
- {
1074
- name: "plugins",
1075
- description: "View and manage installed plugins",
1076
- subcommands: [
1077
- { name: "list", description: "List all installed plugins (npm + marketplace)" },
1078
- { name: "enable", description: "Enable a marketplace plugin", usage: "<name@marketplace>" },
1079
- { name: "disable", description: "Disable a marketplace plugin", usage: "<name@marketplace>" },
1080
- ],
1081
- allowArgs: true,
1082
- handle: async (command, runtime) => {
1083
- runtime.ctx.editor.setText("");
1084
- const args = command.args.trim().split(/\s+/);
1085
- const sub = args[0] || "";
1086
- const rest = args.slice(1).join(" ").trim();
1087
-
1088
- // No args or bare "list" with no further args → open interactive dashboard
1089
- if (!sub || (sub === "list" && !rest)) {
1090
- runtime.ctx.showPluginDashboard();
1091
- return;
1092
- }
1093
-
1094
- try {
1095
- const mgr = new MarketplaceManager({
1096
- marketplacesRegistryPath: getMarketplacesRegistryPath(),
1097
- installedRegistryPath: getInstalledPluginsRegistryPath(),
1098
- projectInstalledRegistryPath: await resolveOrDefaultProjectRegistryPath(
1099
- runtime.ctx.sessionManager.getCwd(),
1100
- ),
1101
- marketplacesCacheDir: getMarketplacesCacheDir(),
1102
- pluginsCacheDir: getPluginsCacheDir(),
1103
- clearPluginRootsCache: (extraPaths?: readonly string[]) => {
1104
- const home = os.homedir();
1105
- invalidateFsCache(path.join(home, getConfigDirName(), "plugins", "installed_plugins.json"));
1106
- for (const p of extraPaths ?? []) invalidateFsCache(p);
1107
- clearXcshPluginRootsCache();
1108
- },
1109
- });
1110
-
1111
- switch (sub) {
1112
- case "enable":
1113
- case "disable": {
1114
- const parsed = parsePluginScopeArgs(
1115
- rest ?? "",
1116
- `Usage: /plugins ${sub} [--scope user|project] <name@marketplace>`,
1117
- );
1118
- if ("error" in parsed) {
1119
- runtime.ctx.showStatus(parsed.error);
1120
- return;
1121
- }
1122
- const isEnable = sub === "enable";
1123
- await mgr.setPluginEnabled(parsed.pluginId, isEnable, parsed.scope);
1124
- runtime.ctx.showStatus(`${isEnable ? "Enabled" : "Disabled"} ${parsed.pluginId}`);
1125
- break;
1126
- }
1127
- case "list": {
1109
+ // ── Installed list ──
1110
+ case "installed": {
1128
1111
  const lines: string[] = [];
1129
-
1130
1112
  const npm = new PluginManager();
1131
1113
  const npmPlugins = await npm.list();
1132
1114
  if (npmPlugins.length > 0) {
@@ -1136,7 +1118,6 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<BuiltinSlashCommandSpec> = [
1136
1118
  lines.push(` ${p.name}@${p.version}${status}`);
1137
1119
  }
1138
1120
  }
1139
-
1140
1121
  const mktPlugins = await mgr.listInstalledPlugins();
1141
1122
  if (mktPlugins.length > 0) {
1142
1123
  if (lines.length > 0) lines.push("");
@@ -1148,7 +1129,6 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<BuiltinSlashCommandSpec> = [
1148
1129
  lines.push(` ${p.id} v${entry?.version ?? "?"}${status} [${p.scope}]${shadowed}`);
1149
1130
  }
1150
1131
  }
1151
-
1152
1132
  if (lines.length === 0) {
1153
1133
  runtime.ctx.showStatus("No plugins installed");
1154
1134
  } else {
@@ -1156,19 +1136,78 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<BuiltinSlashCommandSpec> = [
1156
1136
  }
1157
1137
  break;
1158
1138
  }
1159
- default: {
1139
+ // ── Validate ──
1140
+ case "validate": {
1141
+ const targetPath = rest
1142
+ ? path.resolve(runtime.ctx.sessionManager.getCwd(), rest)
1143
+ : runtime.ctx.sessionManager.getCwd();
1144
+ const catalogPath = path.join(targetPath, ".xcsh-plugin", "marketplace.json");
1145
+ const pluginPath = path.join(targetPath, ".xcsh-plugin", "plugin.json");
1146
+ const { existsSync } = await import("node:fs");
1147
+ if (existsSync(catalogPath)) {
1148
+ const { parseMarketplaceCatalog } = await import("../extensibility/plugins/marketplace/fetcher");
1149
+ const content = await Bun.file(catalogPath).text();
1150
+ const catalog = parseMarketplaceCatalog(content, catalogPath);
1151
+ runtime.ctx.showStatus(
1152
+ `Marketplace "${catalog.name}" is valid (${catalog.plugins.length} plugin(s))`,
1153
+ );
1154
+ } else if (existsSync(pluginPath)) {
1155
+ const content = await Bun.file(pluginPath).text();
1156
+ const manifest = JSON.parse(content);
1157
+ runtime.ctx.showStatus(`Plugin "${manifest.name ?? path.basename(targetPath)}" manifest is valid`);
1158
+ } else {
1159
+ runtime.ctx.showStatus(
1160
+ `No .xcsh-plugin/marketplace.json or .xcsh-plugin/plugin.json found at ${targetPath}`,
1161
+ );
1162
+ }
1163
+ break;
1164
+ }
1165
+ // ── Help ──
1166
+ case "help": {
1160
1167
  runtime.ctx.showStatus(
1161
- "Usage: /plugins [list|enable|disable]\n\n" +
1162
- " /plugins Open plugin dashboard\n" +
1163
- " /plugins list <query> List plugins matching query\n" +
1164
- " /plugins enable <id> Enable a plugin\n" +
1165
- " /plugins disable <id> Disable a plugin",
1168
+ [
1169
+ "Plugin commands:",
1170
+ " /plugin Open plugin dashboard",
1171
+ " /plugin marketplace add <source> Add a marketplace (e.g. owner/repo)",
1172
+ " /plugin marketplace remove <name> Remove a marketplace",
1173
+ " /plugin marketplace update [name] Re-fetch catalog(s)",
1174
+ " /plugin marketplace list List configured marketplaces",
1175
+ " /plugin discover [marketplace] Browse available plugins",
1176
+ " /plugin install <name@marketplace> Install a plugin",
1177
+ " /plugin uninstall <name@marketplace> Uninstall a plugin",
1178
+ " /plugin enable <name@marketplace> Enable a plugin",
1179
+ " /plugin disable <name@marketplace> Disable a plugin",
1180
+ " /plugin upgrade [name@marketplace] Upgrade plugin(s)",
1181
+ " /plugin list List installed plugins",
1182
+ " /plugin validate [path] Validate marketplace or plugin",
1183
+ "",
1184
+ "Quick start:",
1185
+ " /plugin marketplace add f5xc-salesdemos/marketplace",
1186
+ " /plugin (opens plugin dashboard)",
1187
+ "",
1188
+ "Aliases: /marketplace, /plugins",
1189
+ ].join("\n"),
1166
1190
  );
1167
1191
  break;
1168
1192
  }
1193
+ default: {
1194
+ const marketplaces = await mgr.listMarketplaces();
1195
+ if (marketplaces.length === 0) {
1196
+ runtime.ctx.showStatus(
1197
+ "No marketplaces configured.\n\nGet started:\n /plugin marketplace add f5xc-salesdemos/marketplace\n\nThen browse plugins with /plugin or /plugin discover",
1198
+ );
1199
+ } else {
1200
+ const lines = marketplaces.map(m => ` ${m.name} ${m.sourceUri}`);
1201
+ runtime.ctx.showStatus(
1202
+ `Marketplaces:\n${lines.join("\n")}\n\nUse /plugin discover to browse plugins, or /plugin help for all commands`,
1203
+ );
1204
+ }
1205
+ break;
1206
+ }
1169
1207
  }
1170
1208
  } catch (err) {
1171
- runtime.ctx.showStatus(`Plugin error: ${err}`);
1209
+ const msg = err instanceof Error ? err.message : String(err);
1210
+ runtime.ctx.showStatus(`Plugin error: ${msg}`);
1172
1211
  }
1173
1212
  },
1174
1213
  },
@@ -1,4 +1,4 @@
1
- const USAGE = "Usage: /marketplace install [--force] [--scope user|project] <name@marketplace>";
1
+ const USAGE = "Usage: /plugin install [--force] [--scope user|project] <name@marketplace>";
2
2
 
3
3
  export interface MarketplaceInstallArgs {
4
4
  force: boolean;