@f5xc-salesdemos/xcsh 19.3.0 → 19.5.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.3.0",
4
+ "version": "19.5.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.3.0",
54
- "@f5xc-salesdemos/pi-agent-core": "19.3.0",
55
- "@f5xc-salesdemos/pi-ai": "19.3.0",
56
- "@f5xc-salesdemos/pi-natives": "19.3.0",
57
- "@f5xc-salesdemos/pi-tui": "19.3.0",
58
- "@f5xc-salesdemos/pi-utils": "19.3.0",
53
+ "@f5xc-salesdemos/xcsh-stats": "19.5.0",
54
+ "@f5xc-salesdemos/pi-agent-core": "19.5.0",
55
+ "@f5xc-salesdemos/pi-ai": "19.5.0",
56
+ "@f5xc-salesdemos/pi-natives": "19.5.0",
57
+ "@f5xc-salesdemos/pi-tui": "19.5.0",
58
+ "@f5xc-salesdemos/pi-utils": "19.5.0",
59
59
  "@sinclair/typebox": "^0.34",
60
60
  "@xterm/headless": "^6.0",
61
61
  "ajv": "^8.20",
@@ -719,7 +719,7 @@ export class MarketplaceManager {
719
719
  } catch (err) {
720
720
  if (isEnoent(err)) {
721
721
  throw new Error(
722
- `Marketplace catalog not found at ${entry.catalogPath}. Try: /marketplace update ${entry.name}`,
722
+ `Marketplace catalog not found at ${entry.catalogPath}. Try: /plugin marketplace update ${entry.name}`,
723
723
  );
724
724
  }
725
725
  throw err;
@@ -17,17 +17,17 @@ export interface BuildInfo {
17
17
  }
18
18
 
19
19
  export const BUILD_INFO: BuildInfo = {
20
- "version": "19.3.0",
21
- "commit": "431fab2f7f41132d63fe9d61b0db814573d904e8",
22
- "shortCommit": "431fab2",
20
+ "version": "19.5.0",
21
+ "commit": "1845641b31e29a09b04c301d1bcfc297a719c5f9",
22
+ "shortCommit": "1845641",
23
23
  "branch": "main",
24
- "tag": "v19.3.0",
25
- "commitDate": "2026-06-03T16:14:58Z",
26
- "buildDate": "2026-06-03T16:40:22.620Z",
24
+ "tag": "v19.5.0",
25
+ "commitDate": "2026-06-04T16:42:25Z",
26
+ "buildDate": "2026-06-04T17:10:47.523Z",
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/431fab2f7f41132d63fe9d61b0db814573d904e8",
32
- "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v19.3.0"
31
+ "commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/1845641b31e29a09b04c301d1bcfc297a719c5f9",
32
+ "releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v19.5.0"
33
33
  };
@@ -146,13 +146,6 @@ export const TERRAFORM_INDEX: TerraformIndex = {
146
146
  resource_count: 4,
147
147
  resources: ["app_setting", "app_type", "discovery", "filter_set"],
148
148
  },
149
- {
150
- name: "BIG-IP Integration",
151
- slug: "big-ip-integration",
152
- description: "BIG-IP proxy, data groups, and iRules integration",
153
- resource_count: 3,
154
- resources: ["bigip_http_proxy", "data_group", "irule"],
155
- },
156
149
  {
157
150
  name: "Authentication",
158
151
  slug: "authentication",
@@ -167,6 +160,13 @@ export const TERRAFORM_INDEX: TerraformIndex = {
167
160
  resource_count: 3,
168
161
  resources: ["container_registry", "workload", "workload_flavor"],
169
162
  },
163
+ {
164
+ name: "BIG-IP Integration",
165
+ slug: "big-ip-integration",
166
+ description: "BIG-IP proxy, data groups, and iRules integration",
167
+ resource_count: 3,
168
+ resources: ["bigip_http_proxy", "data_group", "irule"],
169
+ },
170
170
  {
171
171
  name: "DNS",
172
172
  slug: "dns",
@@ -175,11 +175,11 @@ export const TERRAFORM_INDEX: TerraformIndex = {
175
175
  resources: ["dns_compliance_checks", "dns_domain", "dns_proxy"],
176
176
  },
177
177
  {
178
- name: "Cloud Resources",
179
- slug: "cloud-resources",
180
- description: "Cloud elastic IPs, address allocators, and geo-location resources",
178
+ name: "Uncategorized",
179
+ slug: "uncategorized",
180
+ description: "Resources pending categorization",
181
181
  resource_count: 2,
182
- resources: ["address_allocator", "cloud_elastic_ip"],
182
+ resources: ["application_profiles", "authorization_server"],
183
183
  },
184
184
  {
185
185
  name: "Organization",
@@ -189,11 +189,11 @@ export const TERRAFORM_INDEX: TerraformIndex = {
189
189
  resources: ["namespace", "tenant_configuration"],
190
190
  },
191
191
  {
192
- name: "Uncategorized",
193
- slug: "uncategorized",
194
- description: "Resources pending categorization",
192
+ name: "Cloud Resources",
193
+ slug: "cloud-resources",
194
+ description: "Cloud elastic IPs, address allocators, and geo-location resources",
195
195
  resource_count: 2,
196
- resources: ["application_profiles", "authorization_server"],
196
+ resources: ["address_allocator", "cloud_elastic_ip"],
197
197
  },
198
198
  {
199
199
  name: "Integrations",
@@ -1411,7 +1411,7 @@ export const TERRAFORM_INDEX: TerraformIndex = {
1411
1411
  required: ["name", "namespace"],
1412
1412
  oneof_groups: [
1413
1413
  {
1414
- fields: ["allow_list", "deny_list", "rule_list"],
1414
+ fields: ["allow_all_requests", "allow_list", "deny_all_requests", "deny_list", "rule_list"],
1415
1415
  },
1416
1416
  {
1417
1417
  fields: ["any_server", "server_name", "server_name_matcher", "server_selector"],
@@ -1419,7 +1419,7 @@ export const TERRAFORM_INDEX: TerraformIndex = {
1419
1419
  ],
1420
1420
  server_defaults: ["port_matcher", "any_server"],
1421
1421
  minimal_config:
1422
- 'resource "f5xc_service_policy" "example" {\n name = "example-service-policy"\n namespace = "staging"\n\n labels = {\n environment = "production"\n managed_by = "terraform"\n }\n\n annotations = {\n "owner" = "platform-team"\n }\n\n // One of the arguments from this list "allow_list deny_list rule_list" must be set\n\n rule_list {\n rules {\n metadata {\n name = "allow-api"\n }\n spec {\n action = "ALLOW"\n any_client {}\n any_ip {}\n path {\n prefix_values = ["/api/"]\n }\n }\n }\n }\n\n // One of the arguments from this list "any_server server_name server_name_matcher server_selector" must be set\n\n any_server {}\n}',
1422
+ 'resource "f5xc_service_policy" "example" {\n name = "example-service-policy"\n namespace = "staging"\n\n labels = {\n environment = "production"\n managed_by = "terraform"\n }\n\n annotations = {\n "owner" = "platform-team"\n }\n\n // One of the arguments from this list "allow_all_requests allow_list deny_all_requests deny_list rule_list" must be set\n\n rule_list {\n rules {\n metadata {\n name = "allow-api"\n }\n spec {\n action = "ALLOW"\n any_client {}\n any_ip {}\n path {\n prefix_values = ["/api/"]\n }\n }\n }\n }\n\n // One of the arguments from this list "any_server server_name server_name_matcher server_selector" must be set\n\n any_server {}\n}',
1423
1423
  dependencies: {
1424
1424
  requires: ["namespace"],
1425
1425
  },
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
  }
@@ -113,6 +113,12 @@ export class PluginDashboard extends Container {
113
113
  // Network failure on first run is fine — continue with whatever is available
114
114
  }
115
115
 
116
+ try {
117
+ await this.#mgr.refreshStaleMarketplaces();
118
+ } catch {
119
+ // Network failure is fine — display whatever is cached
120
+ }
121
+
116
122
  try {
117
123
  this.#state = await createInitialState(this.#mgr, this.#npmMgr);
118
124
  } catch (error) {
@@ -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;