@robinmordasiewicz/f5xc-xcsh 6.43.0 → 6.45.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.
Files changed (2) hide show
  1. package/dist/index.js +1985 -1680
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -45354,8 +45354,8 @@ function getLogoModeFromEnv(envPrefix) {
45354
45354
  var CLI_NAME = "xcsh";
45355
45355
  var CLI_FULL_NAME = "F5 Distributed Cloud Shell";
45356
45356
  function getVersion() {
45357
- if ("6.43.0") {
45358
- return "6.43.0";
45357
+ if ("6.45.0") {
45358
+ return "6.45.0";
45359
45359
  }
45360
45360
  if (process.env.XCSH_VERSION) {
45361
45361
  return process.env.XCSH_VERSION;
@@ -46540,1819 +46540,2113 @@ function formatAPIError(statusCode, body, operation) {
46540
46540
  return lines.join("\n");
46541
46541
  }
46542
46542
 
46543
- // src/output/spec.ts
46544
- function buildCommandSpec(options) {
46545
- const spec = {
46546
- command: options.command,
46547
- description: options.description,
46548
- usage: options.usage ?? `xcsh ${options.command} [options]`,
46549
- flags: options.flags ?? [],
46550
- examples: options.examples ?? [],
46551
- outputFormats: options.outputFormats ?? ["table", "json", "yaml"]
46552
- };
46553
- if (options.related !== void 0) {
46554
- spec.related = options.related;
46543
+ // src/config/envvars.ts
46544
+ var EnvVarRegistry = [
46545
+ {
46546
+ name: `${ENV_PREFIX}_API_URL`,
46547
+ description: "API endpoint URL",
46548
+ relatedFlag: "",
46549
+ required: true
46550
+ },
46551
+ {
46552
+ name: `${ENV_PREFIX}_API_TOKEN`,
46553
+ description: "API authentication token",
46554
+ relatedFlag: "",
46555
+ required: true
46556
+ },
46557
+ {
46558
+ name: `${ENV_PREFIX}_NAMESPACE`,
46559
+ description: "Default namespace",
46560
+ relatedFlag: "-ns"
46561
+ },
46562
+ {
46563
+ name: `${ENV_PREFIX}_OUTPUT_FORMAT`,
46564
+ description: "Output format (json, yaml, table)",
46565
+ relatedFlag: "-o"
46566
+ },
46567
+ {
46568
+ name: `${ENV_PREFIX}_LOGO`,
46569
+ description: "Logo display mode (auto, image, ascii, both, none)",
46570
+ relatedFlag: "--logo"
46571
+ },
46572
+ {
46573
+ name: "NO_COLOR",
46574
+ description: "Disable color output",
46575
+ relatedFlag: "--no-color"
46555
46576
  }
46556
- if (options.category !== void 0) {
46557
- spec.category = options.category;
46577
+ ];
46578
+ function formatEnvVarsSection() {
46579
+ const maxLen = Math.max(...EnvVarRegistry.map((e) => e.name.length));
46580
+ const lines = ["ENVIRONMENT VARIABLES"];
46581
+ for (const env3 of EnvVarRegistry) {
46582
+ const padding = " ".repeat(maxLen - env3.name.length + 3);
46583
+ const flagNote = env3.relatedFlag ? ` [${env3.relatedFlag}]` : "";
46584
+ lines.push(` ${env3.name}${padding}${env3.description}${flagNote}`);
46558
46585
  }
46559
- return spec;
46586
+ return lines;
46560
46587
  }
46561
- function formatSpec(spec) {
46562
- return JSON.stringify(spec, null, 2);
46588
+ function formatConfigSection() {
46589
+ return [
46590
+ "CONFIGURATION",
46591
+ ` Config file: ~/${CONFIG_FILE_NAME}`,
46592
+ " Priority: CLI flags > environment variables > config file > defaults",
46593
+ "",
46594
+ "DOCUMENTATION",
46595
+ ` ${DOCS_URL}`
46596
+ ];
46563
46597
  }
46564
- function buildCloudstatusSpecs() {
46565
- return {
46566
- status: buildCommandSpec({
46567
- command: "cloudstatus status",
46568
- description: "Get the overall health indicator for F5 Distributed Cloud services. Returns status level (operational, degraded, major outage) with description.",
46569
- usage: "xcsh cloudstatus status [--quiet]",
46570
- flags: [
46571
- {
46572
- name: "--quiet",
46573
- alias: "-q",
46574
- description: "Return exit code only (0=operational, 1=degraded, 2=outage)",
46575
- type: "boolean"
46576
- }
46577
- ],
46578
- examples: [
46579
- {
46580
- command: "xcsh cloudstatus status",
46581
- description: "Check current F5 XC service status"
46582
- },
46583
- {
46584
- command: "xcsh cloudstatus status --quiet && echo 'All systems operational'",
46585
- description: "Use in scripts for health checks"
46586
- },
46587
- {
46588
- command: "xcsh cloudstatus status --output json",
46589
- description: "Get status as JSON for automation"
46590
- }
46591
- ],
46592
- category: "cloudstatus",
46593
- related: [
46594
- "cloudstatus summary",
46595
- "cloudstatus components",
46596
- "cloudstatus incidents"
46597
- ]
46598
- }),
46599
- summary: buildCommandSpec({
46600
- command: "cloudstatus summary",
46601
- description: "Get complete status summary including overall health, component status, and active incidents.",
46602
- usage: "xcsh cloudstatus summary",
46603
- examples: [
46604
- {
46605
- command: "xcsh cloudstatus summary",
46606
- description: "View full infrastructure health overview"
46607
- },
46608
- {
46609
- command: "xcsh cloudstatus summary --output json",
46610
- description: "Get complete summary as JSON"
46611
- }
46612
- ],
46613
- category: "cloudstatus",
46614
- related: ["cloudstatus status", "cloudstatus components"]
46615
- }),
46616
- components: buildCommandSpec({
46617
- command: "cloudstatus components",
46618
- description: "List all infrastructure components and their current operational status.",
46619
- usage: "xcsh cloudstatus components",
46620
- examples: [
46621
- {
46622
- command: "xcsh cloudstatus components",
46623
- description: "List all components with status"
46624
- },
46625
- {
46626
- command: "xcsh cloudstatus components --output json",
46627
- description: "Get components as JSON for monitoring integration"
46628
- }
46629
- ],
46630
- category: "cloudstatus",
46631
- related: ["cloudstatus status", "cloudstatus summary"]
46632
- }),
46633
- incidents: buildCommandSpec({
46634
- command: "cloudstatus incidents",
46635
- description: "List active and recent incidents affecting F5 Distributed Cloud services.",
46636
- usage: "xcsh cloudstatus incidents",
46637
- examples: [
46638
- {
46639
- command: "xcsh cloudstatus incidents",
46640
- description: "View active incidents"
46641
- },
46642
- {
46643
- command: "xcsh cloudstatus incidents --output json",
46644
- description: "Get incidents as JSON for alerting systems"
46645
- }
46646
- ],
46647
- category: "cloudstatus",
46648
- related: ["cloudstatus status", "cloudstatus maintenance"]
46649
- }),
46650
- maintenance: buildCommandSpec({
46651
- command: "cloudstatus maintenance",
46652
- description: "List scheduled maintenance windows for F5 Distributed Cloud services.",
46653
- usage: "xcsh cloudstatus maintenance",
46654
- examples: [
46655
- {
46656
- command: "xcsh cloudstatus maintenance",
46657
- description: "View upcoming maintenance windows"
46658
- },
46659
- {
46660
- command: "xcsh cloudstatus maintenance --output json",
46661
- description: "Get maintenance schedule as JSON"
46662
- }
46663
- ],
46664
- category: "cloudstatus",
46665
- related: ["cloudstatus status", "cloudstatus incidents"]
46666
- })
46667
- };
46598
+
46599
+ // src/repl/help.ts
46600
+ function wrapText3(text, width, indent) {
46601
+ const prefix = " ".repeat(indent);
46602
+ const words = text.split(/\s+/);
46603
+ const lines = [];
46604
+ let currentLine = prefix;
46605
+ for (const word of words) {
46606
+ if (currentLine.length + word.length + 1 > width && currentLine !== prefix) {
46607
+ lines.push(currentLine);
46608
+ currentLine = prefix + word;
46609
+ } else {
46610
+ currentLine += (currentLine === prefix ? "" : " ") + word;
46611
+ }
46612
+ }
46613
+ if (currentLine.trim()) {
46614
+ lines.push(currentLine);
46615
+ }
46616
+ return lines;
46668
46617
  }
46669
- function buildLoginSpecs() {
46670
- return {
46671
- banner: buildCommandSpec({
46672
- command: "login banner",
46673
- description: "Display xcsh banner with logo and connection information.",
46674
- usage: "xcsh login banner",
46675
- examples: [
46676
- {
46677
- command: "xcsh login banner",
46678
- description: "Show the xcsh welcome banner"
46679
- }
46680
- ],
46681
- category: "login",
46682
- related: ["login profile show"]
46683
- }),
46684
- "profile list": buildCommandSpec({
46685
- command: "login profile list",
46686
- description: "List all saved connection profiles.",
46687
- usage: "xcsh login profile list",
46688
- examples: [
46689
- {
46690
- command: "xcsh login profile list",
46691
- description: "List saved profiles"
46692
- },
46693
- {
46694
- command: "xcsh login profile list --output json",
46695
- description: "Get profiles as JSON"
46696
- }
46697
- ],
46698
- category: "login",
46699
- related: [
46700
- "login profile show",
46701
- "login profile create",
46702
- "login profile use"
46703
- ]
46704
- }),
46705
- "profile show": buildCommandSpec({
46706
- command: "login profile show",
46707
- description: "Show current connection profile and authentication status.",
46708
- usage: "xcsh login profile show [name]",
46709
- flags: [
46710
- {
46711
- name: "name",
46712
- description: "Profile name to show (optional, defaults to active)",
46713
- type: "string"
46714
- }
46715
- ],
46716
- examples: [
46717
- {
46718
- command: "xcsh login profile show",
46719
- description: "Show active profile"
46720
- },
46721
- {
46722
- command: "xcsh login profile show production",
46723
- description: "Show specific profile"
46724
- }
46725
- ],
46726
- category: "login",
46727
- related: ["login profile list", "login profile use"]
46728
- }),
46729
- "profile create": buildCommandSpec({
46730
- command: "login profile create",
46731
- description: "Create a new connection profile with URL and credentials.",
46732
- usage: "xcsh login profile create <name>",
46733
- flags: [
46734
- {
46735
- name: "name",
46736
- description: "Profile name",
46737
- type: "string",
46738
- required: true
46739
- }
46740
- ],
46741
- examples: [
46742
- {
46743
- command: "xcsh login profile create production",
46744
- description: "Create a new profile named 'production'"
46745
- }
46746
- ],
46747
- category: "login",
46748
- related: ["login profile list", "login profile use"]
46749
- }),
46750
- "profile use": buildCommandSpec({
46751
- command: "login profile use",
46752
- description: "Switch to a different connection profile.",
46753
- usage: "xcsh login profile use <name>",
46754
- flags: [
46755
- {
46756
- name: "name",
46757
- description: "Profile name to activate",
46758
- type: "string",
46759
- required: true
46760
- }
46761
- ],
46762
- examples: [
46763
- {
46764
- command: "xcsh login profile use staging",
46765
- description: "Switch to staging profile"
46766
- }
46767
- ],
46768
- category: "login",
46769
- related: ["login profile list", "login profile show"]
46770
- }),
46771
- "context show": buildCommandSpec({
46772
- command: "login context show",
46773
- description: "Show the current default namespace context.",
46774
- usage: "xcsh login context show",
46775
- examples: [
46776
- {
46777
- command: "xcsh login context show",
46778
- description: "Display current namespace"
46779
- }
46780
- ],
46781
- category: "login",
46782
- related: ["login context set", "login context list"]
46783
- }),
46784
- "context set": buildCommandSpec({
46785
- command: "login context set",
46786
- description: "Set the default namespace for subsequent operations.",
46787
- usage: "xcsh login context set <namespace>",
46788
- flags: [
46789
- {
46790
- name: "namespace",
46791
- description: "Namespace to set as default",
46792
- type: "string",
46793
- required: true
46794
- }
46795
- ],
46796
- examples: [
46797
- {
46798
- command: "xcsh login context set production",
46799
- description: "Set production as default namespace"
46800
- }
46801
- ],
46802
- category: "login",
46803
- related: ["login context show", "login context list"]
46804
- })
46805
- };
46618
+ function formatRootHelp() {
46619
+ return [
46620
+ "",
46621
+ colorBoldWhite(`${CLI_NAME} - ${CLI_FULL_NAME} v${CLI_VERSION}`),
46622
+ "",
46623
+ "DESCRIPTION",
46624
+ ...wrapText3(CLI_DESCRIPTION_LONG, 80, 2),
46625
+ "",
46626
+ "USAGE",
46627
+ ` ${CLI_NAME} Enter interactive REPL mode`,
46628
+ ` ${CLI_NAME} <domain> <action> Execute command non-interactively`,
46629
+ ` ${CLI_NAME} help [topic] Show help for a topic`,
46630
+ "",
46631
+ "EXAMPLES",
46632
+ ` ${CLI_NAME} tenant_and_identity list namespace List all namespaces`,
46633
+ ` ${CLI_NAME} virtual get http_loadbalancer Get a specific load balancer`,
46634
+ ` ${CLI_NAME} dns list List DNS zones`,
46635
+ ` ${CLI_NAME} waf list -ns prod List WAF policies in prod`,
46636
+ "",
46637
+ ...formatDomainsSection(),
46638
+ "",
46639
+ ...formatGlobalFlags(),
46640
+ "",
46641
+ ...formatEnvVarsSection(),
46642
+ "",
46643
+ ...formatConfigSection(),
46644
+ "",
46645
+ "NAVIGATION (Interactive Mode)",
46646
+ " <domain> Navigate into a domain (e.g., 'dns', 'lb')",
46647
+ " /domain Navigate directly to domain from anywhere",
46648
+ " .. Go up one level",
46649
+ " / Return to root",
46650
+ " context Show current navigation context",
46651
+ "",
46652
+ "BUILTINS",
46653
+ " help Show this help",
46654
+ " domains List all available domains",
46655
+ " clear Clear the screen",
46656
+ " history Show command history",
46657
+ " quit, exit Exit the shell",
46658
+ ""
46659
+ ];
46806
46660
  }
46807
- function getCommandSpec(commandPath) {
46808
- const cloudstatusSpecs = buildCloudstatusSpecs();
46809
- const loginSpecs = buildLoginSpecs();
46810
- const normalized = commandPath.toLowerCase().trim();
46811
- if (normalized.startsWith("cloudstatus ")) {
46812
- const subcommand = normalized.replace("cloudstatus ", "");
46813
- return cloudstatusSpecs[subcommand];
46661
+ function formatGlobalFlags() {
46662
+ return [
46663
+ "GLOBAL FLAGS",
46664
+ " -v, --version Show version number",
46665
+ " -h, --help Show this help",
46666
+ " --no-color Disable color output",
46667
+ " -o, --output <fmt> Output format (json, yaml, table)",
46668
+ " -ns, --namespace <ns> Target namespace",
46669
+ " --spec Output command specification as JSON (for AI)"
46670
+ ];
46671
+ }
46672
+ function formatEnvironmentVariables() {
46673
+ return formatEnvVarsSection();
46674
+ }
46675
+ function formatDomainHelp(domain) {
46676
+ const output = ["", colorBoldWhite(`${domain.displayName}`), ""];
46677
+ output.push(` ${domain.description}`);
46678
+ output.push("");
46679
+ if (domain.category || domain.complexity) {
46680
+ const meta = [];
46681
+ if (domain.category) meta.push(`Category: ${domain.category}`);
46682
+ if (domain.complexity) meta.push(`Complexity: ${domain.complexity}`);
46683
+ output.push(colorDim(` ${meta.join(" | ")}`));
46684
+ output.push("");
46814
46685
  }
46815
- if (normalized.startsWith("login ")) {
46816
- const subcommand = normalized.replace("login ", "");
46817
- return loginSpecs[subcommand];
46686
+ output.push("USAGE");
46687
+ output.push(` ${CLI_NAME} ${domain.name} <action> [options]`);
46688
+ output.push("");
46689
+ output.push("ACTIONS");
46690
+ const actionDescriptions2 = {
46691
+ list: "List resources",
46692
+ get: "Get a specific resource by name",
46693
+ create: "Create a new resource",
46694
+ delete: "Delete a resource",
46695
+ replace: "Replace a resource configuration",
46696
+ apply: "Apply configuration from file",
46697
+ status: "Get resource status",
46698
+ patch: "Patch a resource",
46699
+ "add-labels": "Add labels to a resource",
46700
+ "remove-labels": "Remove labels from a resource"
46701
+ };
46702
+ for (const action of validActions) {
46703
+ const desc = actionDescriptions2[action] ?? action;
46704
+ output.push(` ${action.padEnd(16)} ${desc}`);
46818
46705
  }
46819
- return void 0;
46820
- }
46821
-
46822
- // src/repl/session.ts
46823
- var NAMESPACE_CACHE_TTL = 5 * 60 * 1e3;
46824
- var REPLSession = class {
46825
- _history = null;
46826
- _namespace;
46827
- _lastExitCode = 0;
46828
- _contextPath;
46829
- _tenant = "";
46830
- _username = "";
46831
- _validator;
46832
- _serverUrl = "";
46833
- _apiToken = "";
46834
- _apiClient = null;
46835
- _outputFormat = "table";
46836
- _debug = false;
46837
- _profileManager;
46838
- _activeProfile = null;
46839
- _activeProfileName = null;
46840
- _namespaceCache = [];
46841
- _namespaceCacheTime = 0;
46842
- // Token validation state
46843
- _tokenValidated = false;
46844
- _validationError = null;
46845
- constructor(config = {}) {
46846
- this._namespace = config.namespace ?? this.getDefaultNamespace();
46847
- this._contextPath = new ContextPath();
46848
- this._validator = new ContextValidator();
46849
- this._profileManager = getProfileManager();
46850
- this._serverUrl = config.serverUrl ?? process.env[`${ENV_PREFIX}_API_URL`] ?? "";
46851
- this._apiToken = config.apiToken ?? process.env[`${ENV_PREFIX}_API_TOKEN`] ?? "";
46852
- this._outputFormat = config.outputFormat ?? getOutputFormatFromEnv() ?? "table";
46853
- this._debug = config.debug ?? process.env[`${ENV_PREFIX}_DEBUG`] === "true";
46854
- if (this._serverUrl) {
46855
- this._tenant = this.extractTenant(this._serverUrl);
46856
- }
46857
- if (this._serverUrl) {
46858
- this._apiClient = new APIClient({
46859
- serverUrl: this._serverUrl,
46860
- apiToken: this._apiToken,
46861
- debug: this._debug
46862
- });
46706
+ output.push("");
46707
+ output.push("EXAMPLES");
46708
+ output.push(` ${CLI_NAME} ${domain.name} list`);
46709
+ output.push(` ${CLI_NAME} ${domain.name} get my-resource`);
46710
+ output.push(
46711
+ ` ${CLI_NAME} ${domain.name} create my-resource -f config.yaml`
46712
+ );
46713
+ output.push(` ${CLI_NAME} ${domain.name} delete my-resource`);
46714
+ output.push("");
46715
+ if (domain.useCases && domain.useCases.length > 0) {
46716
+ output.push("USE CASES");
46717
+ for (const useCase of domain.useCases.slice(0, 5)) {
46718
+ output.push(` - ${useCase}`);
46863
46719
  }
46720
+ output.push("");
46864
46721
  }
46865
- /**
46866
- * Initialize the session (async operations)
46867
- */
46868
- async initialize() {
46869
- try {
46870
- this._history = await HistoryManager.create(
46871
- getHistoryFilePath(),
46872
- 1e3
46873
- );
46874
- } catch (error) {
46875
- console.error("Warning: could not initialize history:", error);
46876
- this._history = new HistoryManager(getHistoryFilePath(), 1e3);
46877
- }
46878
- await this.loadActiveProfile();
46879
- if (this._apiClient?.isAuthenticated()) {
46880
- const result = await this._apiClient.validateToken();
46881
- this._tokenValidated = result.valid;
46882
- this._validationError = result.error ?? null;
46883
- if (result.valid) {
46884
- await this.fetchUserInfo();
46885
- }
46886
- }
46722
+ if (domain.relatedDomains && domain.relatedDomains.length > 0) {
46723
+ output.push("RELATED DOMAINS");
46724
+ output.push(` ${domain.relatedDomains.join(", ")}`);
46725
+ output.push("");
46887
46726
  }
46888
- /**
46889
- * Fetch user info from the API
46890
- */
46891
- async fetchUserInfo() {
46892
- if (!this._apiClient) return;
46893
- try {
46894
- const response = await this._apiClient.get("/api/web/custom/user/info");
46895
- if (response.ok && response.data) {
46896
- this._username = response.data.email || response.data.name || response.data.username || "";
46897
- }
46898
- } catch {
46899
- }
46727
+ if (domain.aliases && domain.aliases.length > 0) {
46728
+ output.push("ALIASES");
46729
+ output.push(` ${domain.aliases.join(", ")}`);
46730
+ output.push("");
46900
46731
  }
46901
- /**
46902
- * Load the active profile from profile manager
46903
- * Note: Environment variables take priority over profile settings
46904
- * If no active profile is set but exactly one profile exists, auto-activate it
46905
- */
46906
- async loadActiveProfile() {
46907
- try {
46908
- let activeName = await this._profileManager.getActive();
46909
- if (!activeName) {
46910
- const profiles = await this._profileManager.list();
46911
- if (profiles.length === 1 && profiles[0]) {
46912
- const singleProfile = profiles[0];
46913
- await this._profileManager.setActive(singleProfile.name);
46914
- activeName = singleProfile.name;
46915
- }
46916
- }
46917
- if (activeName) {
46918
- const profile = await this._profileManager.get(activeName);
46919
- if (profile) {
46920
- this._activeProfileName = activeName;
46921
- this._activeProfile = profile;
46922
- const envUrl = process.env[`${ENV_PREFIX}_API_URL`];
46923
- const envToken = process.env[`${ENV_PREFIX}_API_TOKEN`];
46924
- const envNamespace = process.env[`${ENV_PREFIX}_NAMESPACE`];
46925
- if (!envUrl && profile.apiUrl) {
46926
- this._serverUrl = profile.apiUrl;
46927
- this._tenant = this.extractTenant(profile.apiUrl);
46928
- }
46929
- if (!envToken && profile.apiToken) {
46930
- this._apiToken = profile.apiToken;
46931
- }
46932
- if (!envNamespace && profile.defaultNamespace) {
46933
- this._namespace = profile.defaultNamespace;
46934
- }
46935
- if (this._serverUrl) {
46936
- this._apiClient = new APIClient({
46937
- serverUrl: this._serverUrl,
46938
- apiToken: this._apiToken,
46939
- debug: this._debug
46940
- });
46941
- }
46942
- }
46943
- }
46944
- } catch {
46732
+ output.push(colorDim(`For global options, run: ${CLI_NAME} --help`));
46733
+ output.push("");
46734
+ return output;
46735
+ }
46736
+ function formatActionHelp(domainName, action) {
46737
+ const domain = getDomainInfo(domainName);
46738
+ const displayDomain = domain?.displayName ?? domainName;
46739
+ const actionDescriptions2 = {
46740
+ list: {
46741
+ desc: "List all resources in the namespace",
46742
+ usage: `${CLI_NAME} ${domainName} list [--limit N] [--label key=value]`
46743
+ },
46744
+ get: {
46745
+ desc: "Get a specific resource by name",
46746
+ usage: `${CLI_NAME} ${domainName} get <name> [-o json|yaml|table]`
46747
+ },
46748
+ create: {
46749
+ desc: "Create a new resource",
46750
+ usage: `${CLI_NAME} ${domainName} create <name> -f <file.yaml>`
46751
+ },
46752
+ delete: {
46753
+ desc: "Delete a resource by name",
46754
+ usage: `${CLI_NAME} ${domainName} delete <name>`
46755
+ },
46756
+ replace: {
46757
+ desc: "Replace an existing resource configuration",
46758
+ usage: `${CLI_NAME} ${domainName} replace <name> -f <file.yaml>`
46759
+ },
46760
+ apply: {
46761
+ desc: "Apply configuration from a file (create or update)",
46762
+ usage: `${CLI_NAME} ${domainName} apply -f <file.yaml>`
46763
+ },
46764
+ status: {
46765
+ desc: "Get the current status of a resource",
46766
+ usage: `${CLI_NAME} ${domainName} status <name>`
46767
+ },
46768
+ patch: {
46769
+ desc: "Patch specific fields of a resource",
46770
+ usage: `${CLI_NAME} ${domainName} patch <name> -f <patch.yaml>`
46771
+ },
46772
+ "add-labels": {
46773
+ desc: "Add labels to a resource",
46774
+ usage: `${CLI_NAME} ${domainName} add-labels <name> key=value`
46775
+ },
46776
+ "remove-labels": {
46777
+ desc: "Remove labels from a resource",
46778
+ usage: `${CLI_NAME} ${domainName} remove-labels <name> key`
46945
46779
  }
46780
+ };
46781
+ const actionInfo = actionDescriptions2[action] ?? {
46782
+ desc: `Execute ${action} operation`,
46783
+ usage: `${CLI_NAME} ${domainName} ${action} [options]`
46784
+ };
46785
+ return [
46786
+ "",
46787
+ colorBoldWhite(`${displayDomain} - ${action}`),
46788
+ "",
46789
+ ` ${actionInfo.desc}`,
46790
+ "",
46791
+ "USAGE",
46792
+ ` ${actionInfo.usage}`,
46793
+ "",
46794
+ "OPTIONS",
46795
+ " -n, --name <name> Resource name",
46796
+ " -ns, --namespace <ns> Target namespace",
46797
+ " -o, --output <fmt> Output format (json, yaml, table)",
46798
+ " -f, --file <path> Configuration file",
46799
+ "",
46800
+ colorDim(`For domain help, run: ${CLI_NAME} ${domainName} --help`),
46801
+ ""
46802
+ ];
46803
+ }
46804
+ function formatTopicHelp(topic) {
46805
+ const lowerTopic = topic.toLowerCase();
46806
+ const domainInfo = getDomainInfo(lowerTopic);
46807
+ if (domainInfo) {
46808
+ return formatDomainHelp(domainInfo);
46946
46809
  }
46947
- /**
46948
- * Get the default namespace from environment or config
46949
- */
46950
- getDefaultNamespace() {
46951
- return process.env[`${ENV_PREFIX}_NAMESPACE`] ?? "default";
46810
+ switch (lowerTopic) {
46811
+ case "domains":
46812
+ return formatDomainsHelp();
46813
+ case "actions":
46814
+ return formatActionsHelp();
46815
+ case "navigation":
46816
+ case "nav":
46817
+ return formatNavigationHelp();
46818
+ case "env":
46819
+ case "environment":
46820
+ return ["", ...formatEnvironmentVariables(), ""];
46821
+ case "flags":
46822
+ return ["", ...formatGlobalFlags(), ""];
46823
+ default:
46824
+ return [
46825
+ "",
46826
+ `Unknown help topic: ${topic}`,
46827
+ "",
46828
+ "Available topics:",
46829
+ " domains List all available domains",
46830
+ " actions List available actions",
46831
+ " navigation Navigation commands",
46832
+ " env Environment variables",
46833
+ " flags Global flags",
46834
+ " <domain> Help for a specific domain (e.g., 'help dns')",
46835
+ ""
46836
+ ];
46952
46837
  }
46953
- /**
46954
- * Extract tenant name from server URL
46955
- */
46956
- extractTenant(url) {
46957
- try {
46958
- const parsed = new URL(url);
46959
- const hostname = parsed.hostname;
46960
- const parts = hostname.split(".");
46961
- if (parts.length > 0 && parts[0]) {
46962
- return parts[0];
46963
- }
46964
- return hostname;
46965
- } catch {
46966
- return "";
46838
+ }
46839
+ function formatDomainsHelp() {
46840
+ const output = ["", colorBoldWhite("Available Domains"), ""];
46841
+ const categories = /* @__PURE__ */ new Map();
46842
+ for (const domain of domainRegistry.values()) {
46843
+ const category = domain.category ?? "Other";
46844
+ if (!categories.has(category)) {
46845
+ categories.set(category, []);
46967
46846
  }
46847
+ categories.get(category)?.push(domain);
46968
46848
  }
46969
- /**
46970
- * Set the default namespace for the session
46971
- */
46972
- setNamespace(ns) {
46973
- this._namespace = ns;
46974
- }
46975
- /**
46976
- * Get the current default namespace
46977
- */
46978
- getNamespace() {
46979
- return this._namespace;
46980
- }
46981
- /**
46982
- * Get the exit code of the last command
46983
- */
46984
- getLastExitCode() {
46985
- return this._lastExitCode;
46986
- }
46987
- /**
46988
- * Set the exit code of the last command
46989
- */
46990
- setLastExitCode(code) {
46991
- this._lastExitCode = code;
46992
- }
46993
- /**
46994
- * Get the current navigation context
46995
- */
46996
- getContextPath() {
46997
- return this._contextPath;
46998
- }
46999
- /**
47000
- * Get the current tenant name
47001
- */
47002
- getTenant() {
47003
- return this._tenant;
47004
- }
47005
- /**
47006
- * Get the logged-in user's name/email
47007
- */
47008
- getUsername() {
47009
- return this._username;
47010
- }
47011
- /**
47012
- * Set the username (used when fetched from API)
47013
- */
47014
- setUsername(username) {
47015
- this._username = username;
47016
- }
47017
- /**
47018
- * Get the context validator
47019
- */
47020
- getValidator() {
47021
- return this._validator;
46849
+ const sortedCategories = Array.from(categories.keys()).sort();
46850
+ for (const category of sortedCategories) {
46851
+ const domains = categories.get(category) ?? [];
46852
+ output.push(colorBoldWhite(` ${category}`));
46853
+ for (const domain of domains.sort(
46854
+ (a, b) => a.name.localeCompare(b.name)
46855
+ )) {
46856
+ const aliases = domain.aliases.length > 0 ? colorDim(` (${domain.aliases.join(", ")})`) : "";
46857
+ output.push(
46858
+ ` ${domain.name.padEnd(24)} ${domain.descriptionShort}`
46859
+ );
46860
+ if (aliases) {
46861
+ output.push(` ${"".padEnd(24)} Aliases:${aliases}`);
46862
+ }
46863
+ }
46864
+ output.push("");
47022
46865
  }
47023
- /**
47024
- * Get the history manager
47025
- */
47026
- getHistory() {
47027
- return this._history;
47028
- }
47029
- /**
47030
- * Get the server URL
47031
- */
47032
- getServerUrl() {
47033
- return this._serverUrl;
47034
- }
47035
- /**
47036
- * Check if connected to an API server
47037
- */
47038
- isConnected() {
47039
- return this._serverUrl !== "" && this._apiClient !== null;
47040
- }
47041
- /**
47042
- * Check if authenticated with API
47043
- */
47044
- isAuthenticated() {
47045
- return this._apiClient?.isAuthenticated() ?? false;
47046
- }
47047
- /**
47048
- * Check if the token has been validated (verified working)
47049
- */
47050
- isTokenValidated() {
47051
- return this._tokenValidated;
46866
+ return output;
46867
+ }
46868
+ function formatActionsHelp() {
46869
+ return [
46870
+ "",
46871
+ colorBoldWhite("Available Actions"),
46872
+ "",
46873
+ " list List all resources in the namespace",
46874
+ " get Get a specific resource by name",
46875
+ " create Create a new resource from a file",
46876
+ " delete Delete a resource by name",
46877
+ " replace Replace a resource configuration",
46878
+ " apply Apply configuration (create or update)",
46879
+ " status Get resource status",
46880
+ " patch Patch specific fields of a resource",
46881
+ " add-labels Add labels to a resource",
46882
+ " remove-labels Remove labels from a resource",
46883
+ "",
46884
+ "USAGE",
46885
+ ` ${CLI_NAME} <domain> <action> [options]`,
46886
+ "",
46887
+ "EXAMPLES",
46888
+ ` ${CLI_NAME} dns list`,
46889
+ ` ${CLI_NAME} lb get my-loadbalancer`,
46890
+ ` ${CLI_NAME} waf create my-policy -f policy.yaml`,
46891
+ ""
46892
+ ];
46893
+ }
46894
+ function formatNavigationHelp() {
46895
+ return [
46896
+ "",
46897
+ colorBoldWhite("Navigation Commands"),
46898
+ "",
46899
+ " <domain> Navigate into a domain context",
46900
+ " /<domain> Navigate directly to domain from anywhere",
46901
+ " .. Go up one level in context",
46902
+ " / Return to root context",
46903
+ " back Go up one level (same as ..)",
46904
+ " root Return to root (same as /)",
46905
+ "",
46906
+ "CONTEXT DISPLAY",
46907
+ " context Show current navigation context",
46908
+ " ctx Alias for context",
46909
+ "",
46910
+ "EXAMPLES",
46911
+ " xcsh> dns # Enter dns domain",
46912
+ " dns> list # Execute list in dns context",
46913
+ " dns> .. # Return to root",
46914
+ " xcsh> /waf # Jump directly to waf",
46915
+ " waf> /dns # Jump from waf to dns",
46916
+ ""
46917
+ ];
46918
+ }
46919
+ function formatDomainsSection() {
46920
+ const output = ["DOMAINS"];
46921
+ const domains = Array.from(domainRegistry.values()).sort(
46922
+ (a, b) => a.name.localeCompare(b.name)
46923
+ );
46924
+ const maxNameLen = Math.max(...domains.map((d) => d.name.length));
46925
+ for (const domain of domains) {
46926
+ const padding = " ".repeat(maxNameLen - domain.name.length + 2);
46927
+ output.push(` ${domain.name}${padding}${domain.descriptionShort}`);
47052
46928
  }
47053
- /**
47054
- * Get the token validation error, if any
47055
- */
47056
- getValidationError() {
47057
- return this._validationError;
46929
+ return output;
46930
+ }
46931
+ function formatCustomDomainHelp(domain) {
46932
+ const output = ["", colorBoldWhite(domain.name), ""];
46933
+ output.push("DESCRIPTION");
46934
+ output.push(...wrapText3(domain.description, 80, 2));
46935
+ output.push("");
46936
+ output.push("USAGE");
46937
+ output.push(` ${CLI_NAME} ${domain.name} <command> [options]`);
46938
+ output.push("");
46939
+ if (domain.subcommands.size > 0) {
46940
+ output.push("SUBCOMMANDS");
46941
+ for (const [name, group] of domain.subcommands) {
46942
+ output.push(` ${name.padEnd(16)} ${group.descriptionShort}`);
46943
+ }
46944
+ output.push("");
47058
46945
  }
47059
- /**
47060
- * Get the API client
47061
- */
47062
- getAPIClient() {
47063
- return this._apiClient;
46946
+ if (domain.commands.size > 0) {
46947
+ output.push("COMMANDS");
46948
+ for (const [name, cmd] of domain.commands) {
46949
+ output.push(` ${name.padEnd(16)} ${cmd.descriptionShort}`);
46950
+ }
46951
+ output.push("");
47064
46952
  }
47065
- /**
47066
- * Get the current output format
47067
- */
47068
- getOutputFormat() {
47069
- return this._outputFormat;
46953
+ output.push(colorDim(`For global options, run: ${CLI_NAME} --help`));
46954
+ output.push("");
46955
+ return output;
46956
+ }
46957
+ function formatSubcommandHelp(domainName, subcommand) {
46958
+ const output = [
46959
+ "",
46960
+ colorBoldWhite(`${domainName} ${subcommand.name}`),
46961
+ ""
46962
+ ];
46963
+ output.push("DESCRIPTION");
46964
+ output.push(...wrapText3(subcommand.description, 80, 2));
46965
+ output.push("");
46966
+ output.push("USAGE");
46967
+ output.push(
46968
+ ` ${CLI_NAME} ${domainName} ${subcommand.name} <command> [options]`
46969
+ );
46970
+ output.push("");
46971
+ if (subcommand.commands.size > 0) {
46972
+ output.push("COMMANDS");
46973
+ for (const [name, cmd] of subcommand.commands) {
46974
+ const usage = cmd.usage ? ` ${cmd.usage}` : "";
46975
+ output.push(
46976
+ ` ${name}${usage.padEnd(16 - name.length)} ${cmd.descriptionShort}`
46977
+ );
46978
+ }
46979
+ output.push("");
47070
46980
  }
46981
+ output.push(
46982
+ colorDim(`For domain help, run: ${CLI_NAME} ${domainName} --help`)
46983
+ );
46984
+ output.push("");
46985
+ return output;
46986
+ }
46987
+
46988
+ // src/domains/registry.ts
46989
+ var DomainRegistry = class {
46990
+ domains = /* @__PURE__ */ new Map();
47071
46991
  /**
47072
- * Set the output format
46992
+ * Register a custom domain
47073
46993
  */
47074
- setOutputFormat(format) {
47075
- this._outputFormat = format;
46994
+ register(domain) {
46995
+ this.domains.set(domain.name, domain);
47076
46996
  }
47077
46997
  /**
47078
- * Get debug mode status
46998
+ * Check if a domain is registered
47079
46999
  */
47080
- isDebug() {
47081
- return this._debug;
47000
+ has(name) {
47001
+ return this.domains.has(name);
47082
47002
  }
47083
47003
  /**
47084
- * Get the profile manager
47004
+ * Get a domain by name
47085
47005
  */
47086
- getProfileManager() {
47087
- return this._profileManager;
47006
+ get(name) {
47007
+ return this.domains.get(name);
47088
47008
  }
47089
47009
  /**
47090
- * Get the active profile
47010
+ * Get all registered domain names
47091
47011
  */
47092
- getActiveProfile() {
47093
- return this._activeProfile;
47012
+ list() {
47013
+ return Array.from(this.domains.keys());
47094
47014
  }
47095
47015
  /**
47096
- * Get the active profile name
47016
+ * Get all domains
47097
47017
  */
47098
- getActiveProfileName() {
47099
- return this._activeProfileName;
47018
+ all() {
47019
+ return Array.from(this.domains.values());
47100
47020
  }
47101
47021
  /**
47102
- * Switch to a different profile
47022
+ * Execute a command within a domain
47023
+ * Handles routing through subcommand groups
47024
+ *
47025
+ * @param domainName - Name of the domain (e.g., "login")
47026
+ * @param args - Command arguments (e.g., ["profile", "list"])
47027
+ * @param session - REPL session
47103
47028
  */
47104
- async switchProfile(profileName) {
47105
- const profile = await this._profileManager.get(profileName);
47106
- if (!profile) {
47107
- return false;
47108
- }
47109
- const result = await this._profileManager.setActive(profileName);
47110
- if (!result.success) {
47111
- return false;
47112
- }
47113
- this.clearNamespaceCache();
47114
- this._tokenValidated = false;
47115
- this._validationError = null;
47116
- this._activeProfileName = profileName;
47117
- this._activeProfile = profile;
47118
- if (profile.apiUrl) {
47119
- this._serverUrl = profile.apiUrl;
47120
- this._tenant = this.extractTenant(profile.apiUrl);
47121
- }
47122
- if (profile.apiToken) {
47123
- this._apiToken = profile.apiToken;
47124
- }
47125
- if (profile.defaultNamespace) {
47126
- this._namespace = profile.defaultNamespace;
47029
+ async execute(domainName, args, session) {
47030
+ const domain = this.domains.get(domainName);
47031
+ if (!domain) {
47032
+ return {
47033
+ output: [`Unknown domain: ${domainName}`],
47034
+ shouldExit: false,
47035
+ shouldClear: false,
47036
+ contextChanged: false,
47037
+ error: "Unknown domain"
47038
+ };
47127
47039
  }
47128
- if (this._serverUrl) {
47129
- this._apiClient = new APIClient({
47130
- serverUrl: this._serverUrl,
47131
- apiToken: this._apiToken,
47132
- debug: this._debug
47133
- });
47134
- if (this._apiClient.isAuthenticated()) {
47135
- const validationResult = await this._apiClient.validateToken();
47136
- this._tokenValidated = validationResult.valid;
47137
- this._validationError = validationResult.error ?? null;
47040
+ if (args.length === 0) {
47041
+ if (domain.defaultCommand) {
47042
+ return domain.defaultCommand.execute([], session);
47138
47043
  }
47139
- } else {
47140
- this._apiClient = null;
47044
+ return this.showDomainHelp(domain);
47141
47045
  }
47142
- return true;
47143
- }
47144
- /**
47145
- * Clear the active profile
47146
- */
47147
- clearActiveProfile() {
47148
- this._activeProfileName = null;
47149
- this._activeProfile = null;
47150
- }
47151
- /**
47152
- * Get cached namespaces (returns empty array if cache is stale/empty)
47153
- */
47154
- getNamespaceCache() {
47155
- const now = Date.now();
47156
- if (this._namespaceCache.length > 0 && now - this._namespaceCacheTime < NAMESPACE_CACHE_TTL) {
47157
- return this._namespaceCache;
47046
+ const firstArg = args[0]?.toLowerCase() ?? "";
47047
+ const restArgs = args.slice(1);
47048
+ if (firstArg === "--help" || firstArg === "-h" || firstArg === "help") {
47049
+ return this.showDomainHelp(domain);
47158
47050
  }
47159
- return [];
47160
- }
47161
- /**
47162
- * Set namespace cache
47163
- */
47164
- setNamespaceCache(namespaces) {
47165
- this._namespaceCache = namespaces;
47166
- this._namespaceCacheTime = Date.now();
47167
- }
47168
- /**
47169
- * Clear namespace cache (called when switching profiles)
47170
- */
47171
- clearNamespaceCache() {
47172
- this._namespaceCache = [];
47173
- this._namespaceCacheTime = 0;
47051
+ const subgroup = domain.subcommands.get(firstArg);
47052
+ if (subgroup) {
47053
+ if (restArgs.length === 0) {
47054
+ if (subgroup.defaultCommand) {
47055
+ return subgroup.defaultCommand.execute([], session);
47056
+ }
47057
+ return this.showSubcommandHelp(domain, subgroup);
47058
+ }
47059
+ const cmdName = restArgs[0]?.toLowerCase() ?? "";
47060
+ if (cmdName === "--help" || cmdName === "-h" || cmdName === "help") {
47061
+ return this.showSubcommandHelp(domain, subgroup);
47062
+ }
47063
+ const cmdArgs = restArgs.slice(1);
47064
+ const cmd2 = subgroup.commands.get(cmdName);
47065
+ if (cmd2) {
47066
+ return cmd2.execute(cmdArgs, session);
47067
+ }
47068
+ for (const [, command] of subgroup.commands) {
47069
+ if (command.aliases?.includes(cmdName)) {
47070
+ return command.execute(cmdArgs, session);
47071
+ }
47072
+ }
47073
+ return {
47074
+ output: [
47075
+ `Unknown command: ${domainName} ${firstArg} ${cmdName}`,
47076
+ ``,
47077
+ `Run '${domainName} ${firstArg}' for available commands.`
47078
+ ],
47079
+ shouldExit: false,
47080
+ shouldClear: false,
47081
+ contextChanged: false,
47082
+ error: "Unknown command"
47083
+ };
47084
+ }
47085
+ const cmd = domain.commands.get(firstArg);
47086
+ if (cmd) {
47087
+ return cmd.execute(restArgs, session);
47088
+ }
47089
+ for (const [, command] of domain.commands) {
47090
+ if (command.aliases?.includes(firstArg)) {
47091
+ return command.execute(restArgs, session);
47092
+ }
47093
+ }
47094
+ return {
47095
+ output: [
47096
+ `Unknown command: ${domainName} ${firstArg}`,
47097
+ ``,
47098
+ `Run '${domainName}' for available commands.`
47099
+ ],
47100
+ shouldExit: false,
47101
+ shouldClear: false,
47102
+ contextChanged: false,
47103
+ error: "Unknown command"
47104
+ };
47174
47105
  }
47175
47106
  /**
47176
- * Add a command to history
47107
+ * Get completions for a domain command
47177
47108
  */
47178
- addToHistory(cmd) {
47179
- this._history?.add(cmd);
47109
+ async getCompletions(domainName, args, partial, session) {
47110
+ const domain = this.domains.get(domainName);
47111
+ if (!domain) {
47112
+ return [];
47113
+ }
47114
+ const suggestions = [];
47115
+ if (args.length === 0) {
47116
+ for (const [name, group] of domain.subcommands) {
47117
+ if (name.toLowerCase().startsWith(partial.toLowerCase())) {
47118
+ suggestions.push({
47119
+ text: name,
47120
+ description: group.descriptionShort,
47121
+ category: "subcommand"
47122
+ });
47123
+ }
47124
+ }
47125
+ for (const [name, cmd] of domain.commands) {
47126
+ if (name.toLowerCase().startsWith(partial.toLowerCase())) {
47127
+ suggestions.push({
47128
+ text: name,
47129
+ description: cmd.descriptionShort,
47130
+ category: "command"
47131
+ });
47132
+ }
47133
+ }
47134
+ return suggestions;
47135
+ }
47136
+ const firstArg = args[0]?.toLowerCase() ?? "";
47137
+ const subgroup = domain.subcommands.get(firstArg);
47138
+ if (subgroup && args.length === 1) {
47139
+ for (const [name, cmd] of subgroup.commands) {
47140
+ if (name.toLowerCase().startsWith(partial.toLowerCase())) {
47141
+ suggestions.push({
47142
+ text: name,
47143
+ description: cmd.descriptionShort,
47144
+ category: "command"
47145
+ });
47146
+ }
47147
+ }
47148
+ return suggestions;
47149
+ }
47150
+ if (subgroup && args.length >= 2) {
47151
+ const cmdName = args[1]?.toLowerCase() ?? "";
47152
+ const cmd = subgroup.commands.get(cmdName);
47153
+ if (cmd?.completion) {
47154
+ const completions = await cmd.completion(
47155
+ partial,
47156
+ args.slice(2),
47157
+ session
47158
+ );
47159
+ return completions.map((text) => ({
47160
+ text,
47161
+ description: "",
47162
+ category: "argument"
47163
+ }));
47164
+ }
47165
+ }
47166
+ return suggestions;
47180
47167
  }
47181
47168
  /**
47182
- * Save history to disk
47169
+ * Show help for a domain using the unified help formatter.
47170
+ * This ensures consistent professional formatting across all domains.
47183
47171
  */
47184
- async saveHistory() {
47185
- await this._history?.save();
47172
+ showDomainHelp(domain) {
47173
+ return {
47174
+ output: formatCustomDomainHelp(domain),
47175
+ shouldExit: false,
47176
+ shouldClear: false,
47177
+ contextChanged: false
47178
+ };
47186
47179
  }
47187
47180
  /**
47188
- * Clean up session resources
47181
+ * Show help for a subcommand group using the unified help formatter.
47182
+ * This ensures consistent professional formatting across all subcommands.
47189
47183
  */
47190
- async cleanup() {
47191
- await this.saveHistory();
47184
+ showSubcommandHelp(domain, subgroup) {
47185
+ return {
47186
+ output: formatSubcommandHelp(domain.name, subgroup),
47187
+ shouldExit: false,
47188
+ shouldClear: false,
47189
+ contextChanged: false
47190
+ };
47192
47191
  }
47193
47192
  };
47194
-
47195
- // src/repl/prompt.ts
47196
- function buildPlainPrompt(_session) {
47197
- return "> ";
47193
+ var customDomains = new DomainRegistry();
47194
+ function successResult(output, contextChanged = false) {
47195
+ return {
47196
+ output,
47197
+ shouldExit: false,
47198
+ shouldClear: false,
47199
+ contextChanged
47200
+ };
47201
+ }
47202
+ function errorResult(message) {
47203
+ return {
47204
+ output: [message],
47205
+ shouldExit: false,
47206
+ shouldClear: false,
47207
+ contextChanged: false,
47208
+ error: message
47209
+ };
47210
+ }
47211
+ function rawStdoutResult(content) {
47212
+ return {
47213
+ output: [],
47214
+ // No regular output - rawStdout is used instead
47215
+ shouldExit: false,
47216
+ shouldClear: false,
47217
+ contextChanged: false,
47218
+ rawStdout: content
47219
+ };
47198
47220
  }
47199
47221
 
47200
- // src/repl/App.tsx
47201
- var import_react28 = __toESM(require_react(), 1);
47202
-
47203
- // src/repl/components/InputBox.tsx
47204
- var import_react23 = __toESM(require_react(), 1);
47205
-
47206
- // node_modules/ink-text-input/build/index.js
47207
- var import_react22 = __toESM(require_react(), 1);
47208
- function TextInput({ value: originalValue, placeholder = "", focus = true, mask, highlightPastedText = false, showCursor = true, onChange, onSubmit }) {
47209
- const [state, setState] = (0, import_react22.useState)({
47210
- cursorOffset: (originalValue || "").length,
47211
- cursorWidth: 0
47212
- });
47213
- const { cursorOffset, cursorWidth } = state;
47214
- (0, import_react22.useEffect)(() => {
47215
- setState((previousState) => {
47216
- if (!focus || !showCursor) {
47217
- return previousState;
47218
- }
47219
- const newValue = originalValue || "";
47220
- if (previousState.cursorOffset > newValue.length - 1) {
47221
- return {
47222
- cursorOffset: newValue.length,
47223
- cursorWidth: 0
47224
- };
47225
- }
47226
- return previousState;
47227
- });
47228
- }, [originalValue, focus, showCursor]);
47229
- const cursorActualWidth = highlightPastedText ? cursorWidth : 0;
47230
- const value = mask ? mask.repeat(originalValue.length) : originalValue;
47231
- let renderedValue = value;
47232
- let renderedPlaceholder = placeholder ? source_default.grey(placeholder) : void 0;
47233
- if (showCursor && focus) {
47234
- renderedPlaceholder = placeholder.length > 0 ? source_default.inverse(placeholder[0]) + source_default.grey(placeholder.slice(1)) : source_default.inverse(" ");
47235
- renderedValue = value.length > 0 ? "" : source_default.inverse(" ");
47236
- let i = 0;
47237
- for (const char of value) {
47238
- renderedValue += i >= cursorOffset - cursorActualWidth && i <= cursorOffset ? source_default.inverse(char) : char;
47239
- i++;
47240
- }
47241
- if (value.length > 0 && cursorOffset === value.length) {
47242
- renderedValue += source_default.inverse(" ");
47243
- }
47222
+ // src/output/spec.ts
47223
+ function buildCommandSpec(options) {
47224
+ const spec = {
47225
+ command: options.command,
47226
+ description: options.description,
47227
+ usage: options.usage ?? `xcsh ${options.command} [options]`,
47228
+ flags: options.flags ?? [],
47229
+ examples: options.examples ?? [],
47230
+ outputFormats: options.outputFormats ?? ["table", "json", "yaml"]
47231
+ };
47232
+ if (options.related !== void 0) {
47233
+ spec.related = options.related;
47244
47234
  }
47245
- use_input_default((input, key) => {
47246
- if (key.upArrow || key.downArrow || key.ctrl && input === "c" || key.tab || key.shift && key.tab) {
47247
- return;
47248
- }
47249
- if (key.return) {
47250
- if (onSubmit) {
47251
- onSubmit(originalValue);
47252
- }
47253
- return;
47254
- }
47255
- let nextCursorOffset = cursorOffset;
47256
- let nextValue = originalValue;
47257
- let nextCursorWidth = 0;
47258
- if (key.leftArrow) {
47259
- if (showCursor) {
47260
- nextCursorOffset--;
47261
- }
47262
- } else if (key.rightArrow) {
47263
- if (showCursor) {
47264
- nextCursorOffset++;
47265
- }
47266
- } else if (key.backspace || key.delete) {
47267
- if (cursorOffset > 0) {
47268
- nextValue = originalValue.slice(0, cursorOffset - 1) + originalValue.slice(cursorOffset, originalValue.length);
47269
- nextCursorOffset--;
47270
- }
47271
- } else {
47272
- nextValue = originalValue.slice(0, cursorOffset) + input + originalValue.slice(cursorOffset, originalValue.length);
47273
- nextCursorOffset += input.length;
47274
- if (input.length > 1) {
47275
- nextCursorWidth = input.length;
47276
- }
47277
- }
47278
- if (cursorOffset < 0) {
47279
- nextCursorOffset = 0;
47280
- }
47281
- if (cursorOffset > originalValue.length) {
47282
- nextCursorOffset = originalValue.length;
47283
- }
47284
- setState({
47285
- cursorOffset: nextCursorOffset,
47286
- cursorWidth: nextCursorWidth
47287
- });
47288
- if (nextValue !== originalValue) {
47289
- onChange(nextValue);
47290
- }
47291
- }, { isActive: focus });
47292
- return import_react22.default.createElement(Text, null, placeholder ? value.length > 0 ? renderedValue : renderedPlaceholder : renderedValue);
47235
+ if (options.category !== void 0) {
47236
+ spec.category = options.category;
47237
+ }
47238
+ return spec;
47293
47239
  }
47294
- var build_default = TextInput;
47295
-
47296
- // src/repl/components/InputBox.tsx
47297
- var import_jsx_runtime = __toESM(require_jsx_runtime(), 1);
47298
- function HorizontalRule({ width }) {
47299
- const rule = "\u2500".repeat(Math.max(width, 1));
47300
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: "#CA260A", children: rule });
47240
+ function formatSpec(spec) {
47241
+ return JSON.stringify(spec, null, 2);
47301
47242
  }
47302
- function InputBox({
47303
- prompt,
47304
- value,
47305
- onChange,
47306
- onSubmit,
47307
- width = 80,
47308
- isActive = true,
47309
- inputKey = 0
47310
- }) {
47311
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { flexDirection: "column", width, children: [
47312
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HorizontalRule, { width }),
47313
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { children: [
47314
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { bold: true, color: "#ffffff", children: prompt }),
47315
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
47316
- build_default,
47243
+ var GLOBAL_FLAGS = [
47244
+ {
47245
+ name: "--output",
47246
+ alias: "-o",
47247
+ description: "Output format",
47248
+ type: "string",
47249
+ default: "table",
47250
+ choices: ["table", "json", "yaml", "tsv"]
47251
+ },
47252
+ {
47253
+ name: "--namespace",
47254
+ alias: "-ns",
47255
+ description: "Namespace to use for the operation",
47256
+ type: "string"
47257
+ },
47258
+ {
47259
+ name: "--no-color",
47260
+ description: "Disable colored output",
47261
+ type: "boolean",
47262
+ default: "false"
47263
+ },
47264
+ {
47265
+ name: "--spec",
47266
+ description: "Output command specification as JSON (for AI assistants)",
47267
+ type: "boolean"
47268
+ },
47269
+ {
47270
+ name: "--help",
47271
+ alias: "-h",
47272
+ description: "Show help information",
47273
+ type: "boolean"
47274
+ },
47275
+ {
47276
+ name: "--version",
47277
+ alias: "-v",
47278
+ description: "Show version number",
47279
+ type: "boolean"
47280
+ }
47281
+ ];
47282
+ function buildCloudstatusSpecs() {
47283
+ return {
47284
+ status: buildCommandSpec({
47285
+ command: "cloudstatus status",
47286
+ description: "Get the overall health indicator for F5 Distributed Cloud services. Returns status level (operational, degraded, major outage) with description.",
47287
+ usage: "xcsh cloudstatus status [--quiet]",
47288
+ flags: [
47317
47289
  {
47318
- value,
47319
- onChange,
47320
- onSubmit,
47321
- focus: isActive
47290
+ name: "--quiet",
47291
+ alias: "-q",
47292
+ description: "Return exit code only (0=operational, 1=degraded, 2=outage)",
47293
+ type: "boolean"
47294
+ }
47295
+ ],
47296
+ examples: [
47297
+ {
47298
+ command: "xcsh cloudstatus status",
47299
+ description: "Check current F5 XC service status"
47322
47300
  },
47323
- inputKey
47324
- )
47325
- ] }),
47326
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HorizontalRule, { width })
47327
- ] });
47328
- }
47329
-
47330
- // src/repl/components/StatusBar.tsx
47331
- import { execSync } from "child_process";
47332
- var import_jsx_runtime2 = __toESM(require_jsx_runtime(), 1);
47333
- function getStatusIcon(gitInfo) {
47334
- if (gitInfo.ahead > 0 && gitInfo.behind > 0) {
47335
- return "\u21C5";
47336
- }
47337
- if (gitInfo.ahead > 0) {
47338
- return "\u2191";
47339
- }
47340
- if (gitInfo.behind > 0) {
47341
- return "\u2193";
47342
- }
47343
- if (gitInfo.isDirty) {
47344
- return "*";
47345
- }
47346
- return "\u2713";
47347
- }
47348
- function getStatusColor(gitInfo) {
47349
- if (gitInfo.ahead > 0 || gitInfo.behind > 0) {
47350
- return "#2196f3";
47351
- }
47352
- if (gitInfo.isDirty) {
47353
- return "#ffc107";
47354
- }
47355
- return "#00c853";
47356
- }
47357
- function StatusBar({
47358
- gitInfo,
47359
- width = 80,
47360
- hint = "Ctrl+C: quit"
47361
- }) {
47362
- const renderLeft = () => {
47363
- if (gitInfo?.inRepo) {
47364
- const icon = getStatusIcon(gitInfo);
47365
- const color = getStatusColor(gitInfo);
47366
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Text, { children: [
47367
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "#ffffff", children: gitInfo.repoName }),
47368
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "#666666", children: "/" }),
47369
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color, children: gitInfo.branch }),
47370
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { children: " " }),
47371
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color, children: icon })
47372
- ] });
47373
- }
47374
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "#ffffff", children: CLI_NAME });
47375
- };
47376
- const renderRight = () => {
47377
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "#666666", children: hint });
47301
+ {
47302
+ command: "xcsh cloudstatus status --quiet && echo 'All systems operational'",
47303
+ description: "Use in scripts for health checks"
47304
+ },
47305
+ {
47306
+ command: "xcsh cloudstatus status --output json",
47307
+ description: "Get status as JSON for automation"
47308
+ }
47309
+ ],
47310
+ category: "cloudstatus",
47311
+ related: [
47312
+ "cloudstatus summary",
47313
+ "cloudstatus components",
47314
+ "cloudstatus incidents"
47315
+ ]
47316
+ }),
47317
+ summary: buildCommandSpec({
47318
+ command: "cloudstatus summary",
47319
+ description: "Get complete status summary including overall health, component status, and active incidents.",
47320
+ usage: "xcsh cloudstatus summary",
47321
+ examples: [
47322
+ {
47323
+ command: "xcsh cloudstatus summary",
47324
+ description: "View full infrastructure health overview"
47325
+ },
47326
+ {
47327
+ command: "xcsh cloudstatus summary --output json",
47328
+ description: "Get complete summary as JSON"
47329
+ }
47330
+ ],
47331
+ category: "cloudstatus",
47332
+ related: ["cloudstatus status", "cloudstatus components"]
47333
+ }),
47334
+ components: buildCommandSpec({
47335
+ command: "cloudstatus components",
47336
+ description: "List all infrastructure components and their current operational status.",
47337
+ usage: "xcsh cloudstatus components",
47338
+ examples: [
47339
+ {
47340
+ command: "xcsh cloudstatus components",
47341
+ description: "List all components with status"
47342
+ },
47343
+ {
47344
+ command: "xcsh cloudstatus components --output json",
47345
+ description: "Get components as JSON for monitoring integration"
47346
+ }
47347
+ ],
47348
+ category: "cloudstatus",
47349
+ related: ["cloudstatus status", "cloudstatus summary"]
47350
+ }),
47351
+ incidents: buildCommandSpec({
47352
+ command: "cloudstatus incidents",
47353
+ description: "List active and recent incidents affecting F5 Distributed Cloud services.",
47354
+ usage: "xcsh cloudstatus incidents",
47355
+ examples: [
47356
+ {
47357
+ command: "xcsh cloudstatus incidents",
47358
+ description: "View active incidents"
47359
+ },
47360
+ {
47361
+ command: "xcsh cloudstatus incidents --output json",
47362
+ description: "Get incidents as JSON for alerting systems"
47363
+ }
47364
+ ],
47365
+ category: "cloudstatus",
47366
+ related: ["cloudstatus status", "cloudstatus maintenance"]
47367
+ }),
47368
+ maintenance: buildCommandSpec({
47369
+ command: "cloudstatus maintenance",
47370
+ description: "List scheduled maintenance windows for F5 Distributed Cloud services.",
47371
+ usage: "xcsh cloudstatus maintenance",
47372
+ examples: [
47373
+ {
47374
+ command: "xcsh cloudstatus maintenance",
47375
+ description: "View upcoming maintenance windows"
47376
+ },
47377
+ {
47378
+ command: "xcsh cloudstatus maintenance --output json",
47379
+ description: "Get maintenance schedule as JSON"
47380
+ }
47381
+ ],
47382
+ category: "cloudstatus",
47383
+ related: ["cloudstatus status", "cloudstatus incidents"]
47384
+ })
47378
47385
  };
47379
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { width, paddingX: 1, justifyContent: "space-between", children: [
47380
- renderLeft(),
47381
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Spacer, {}),
47382
- renderRight()
47383
- ] });
47384
47386
  }
47385
- function getGitInfo() {
47386
- const defaultInfo = {
47387
- inRepo: false,
47388
- repoName: "",
47389
- branch: "",
47390
- isDirty: false,
47391
- ahead: 0,
47392
- behind: 0
47393
- };
47394
- try {
47395
- try {
47396
- execSync("git rev-parse --is-inside-work-tree", {
47397
- encoding: "utf-8",
47398
- stdio: ["pipe", "pipe", "pipe"]
47399
- });
47400
- } catch {
47401
- return defaultInfo;
47402
- }
47403
- let repoName = "";
47404
- try {
47405
- const topLevel = execSync("git rev-parse --show-toplevel", {
47406
- encoding: "utf-8",
47407
- stdio: ["pipe", "pipe", "pipe"]
47408
- }).trim();
47409
- repoName = topLevel.split("/").pop() ?? "";
47410
- } catch {
47411
- repoName = "repo";
47412
- }
47413
- let branch = "";
47414
- try {
47415
- branch = execSync("git rev-parse --abbrev-ref HEAD", {
47416
- encoding: "utf-8",
47417
- stdio: ["pipe", "pipe", "pipe"]
47418
- }).trim();
47419
- } catch {
47420
- branch = "unknown";
47421
- }
47422
- let isDirty = false;
47423
- try {
47424
- const status = execSync("git status --porcelain", {
47425
- encoding: "utf-8",
47426
- stdio: ["pipe", "pipe", "pipe"]
47427
- });
47428
- isDirty = status.trim().length > 0;
47429
- } catch {
47430
- }
47431
- let ahead = 0;
47432
- let behind = 0;
47433
- try {
47434
- const counts = execSync(
47435
- "git rev-list --left-right --count HEAD...@{upstream}",
47387
+ function buildLoginSpecs() {
47388
+ return {
47389
+ banner: buildCommandSpec({
47390
+ command: "login banner",
47391
+ description: "Display xcsh banner with logo and connection information.",
47392
+ usage: "xcsh login banner",
47393
+ examples: [
47394
+ {
47395
+ command: "xcsh login banner",
47396
+ description: "Show the xcsh welcome banner"
47397
+ }
47398
+ ],
47399
+ category: "login",
47400
+ related: ["login profile show"]
47401
+ }),
47402
+ "profile list": buildCommandSpec({
47403
+ command: "login profile list",
47404
+ description: "List all saved connection profiles.",
47405
+ usage: "xcsh login profile list",
47406
+ examples: [
47407
+ {
47408
+ command: "xcsh login profile list",
47409
+ description: "List saved profiles"
47410
+ },
47411
+ {
47412
+ command: "xcsh login profile list --output json",
47413
+ description: "Get profiles as JSON"
47414
+ }
47415
+ ],
47416
+ category: "login",
47417
+ related: [
47418
+ "login profile show",
47419
+ "login profile create",
47420
+ "login profile use"
47421
+ ]
47422
+ }),
47423
+ "profile show": buildCommandSpec({
47424
+ command: "login profile show",
47425
+ description: "Show current connection profile and authentication status.",
47426
+ usage: "xcsh login profile show [name]",
47427
+ flags: [
47428
+ {
47429
+ name: "name",
47430
+ description: "Profile name to show (optional, defaults to active)",
47431
+ type: "string"
47432
+ }
47433
+ ],
47434
+ examples: [
47435
+ {
47436
+ command: "xcsh login profile show",
47437
+ description: "Show active profile"
47438
+ },
47439
+ {
47440
+ command: "xcsh login profile show production",
47441
+ description: "Show specific profile"
47442
+ }
47443
+ ],
47444
+ category: "login",
47445
+ related: ["login profile list", "login profile use"]
47446
+ }),
47447
+ "profile create": buildCommandSpec({
47448
+ command: "login profile create",
47449
+ description: "Create a new connection profile with URL and credentials.",
47450
+ usage: "xcsh login profile create <name>",
47451
+ flags: [
47452
+ {
47453
+ name: "name",
47454
+ description: "Profile name",
47455
+ type: "string",
47456
+ required: true
47457
+ }
47458
+ ],
47459
+ examples: [
47460
+ {
47461
+ command: "xcsh login profile create production",
47462
+ description: "Create a new profile named 'production'"
47463
+ }
47464
+ ],
47465
+ category: "login",
47466
+ related: ["login profile list", "login profile use"]
47467
+ }),
47468
+ "profile use": buildCommandSpec({
47469
+ command: "login profile use",
47470
+ description: "Switch to a different connection profile.",
47471
+ usage: "xcsh login profile use <name>",
47472
+ flags: [
47436
47473
  {
47437
- encoding: "utf-8",
47438
- stdio: ["pipe", "pipe", "pipe"]
47474
+ name: "name",
47475
+ description: "Profile name to activate",
47476
+ type: "string",
47477
+ required: true
47439
47478
  }
47440
- ).trim();
47441
- const [aheadStr, behindStr] = counts.split(/\s+/);
47442
- ahead = parseInt(aheadStr ?? "0", 10) || 0;
47443
- behind = parseInt(behindStr ?? "0", 10) || 0;
47444
- } catch {
47445
- }
47446
- return {
47447
- inRepo: true,
47448
- repoName,
47449
- branch,
47450
- isDirty,
47451
- ahead,
47452
- behind
47453
- };
47454
- } catch {
47455
- return defaultInfo;
47456
- }
47479
+ ],
47480
+ examples: [
47481
+ {
47482
+ command: "xcsh login profile use staging",
47483
+ description: "Switch to staging profile"
47484
+ }
47485
+ ],
47486
+ category: "login",
47487
+ related: ["login profile list", "login profile show"]
47488
+ }),
47489
+ "context show": buildCommandSpec({
47490
+ command: "login context show",
47491
+ description: "Show the current default namespace context.",
47492
+ usage: "xcsh login context show",
47493
+ examples: [
47494
+ {
47495
+ command: "xcsh login context show",
47496
+ description: "Display current namespace"
47497
+ }
47498
+ ],
47499
+ category: "login",
47500
+ related: ["login context set", "login context list"]
47501
+ }),
47502
+ "context set": buildCommandSpec({
47503
+ command: "login context set",
47504
+ description: "Set the default namespace for subsequent operations.",
47505
+ usage: "xcsh login context set <namespace>",
47506
+ flags: [
47507
+ {
47508
+ name: "namespace",
47509
+ description: "Namespace to set as default",
47510
+ type: "string",
47511
+ required: true
47512
+ }
47513
+ ],
47514
+ examples: [
47515
+ {
47516
+ command: "xcsh login context set production",
47517
+ description: "Set production as default namespace"
47518
+ }
47519
+ ],
47520
+ category: "login",
47521
+ related: ["login context show", "login context list"]
47522
+ })
47523
+ };
47457
47524
  }
47458
-
47459
- // src/repl/components/Suggestions.tsx
47460
- var import_react24 = __toESM(require_react(), 1);
47461
- var import_jsx_runtime3 = __toESM(require_jsx_runtime(), 1);
47462
- function getCategoryColor(category) {
47463
- switch (category) {
47464
- case "domain":
47465
- return "#2196f3";
47466
- // Blue
47467
- case "action":
47468
- return "#4caf50";
47469
- // Green
47470
- case "flag":
47471
- return "#ffc107";
47472
- // Yellow
47473
- case "value":
47474
- return "#9c27b0";
47475
- // Purple
47476
- default:
47477
- return "#ffffff";
47525
+ function getCommandSpec(commandPath) {
47526
+ const cloudstatusSpecs = buildCloudstatusSpecs();
47527
+ const loginSpecs = buildLoginSpecs();
47528
+ const normalized = commandPath.toLowerCase().trim();
47529
+ if (normalized.startsWith("cloudstatus ")) {
47530
+ const subcommand = normalized.replace("cloudstatus ", "");
47531
+ return cloudstatusSpecs[subcommand];
47532
+ }
47533
+ if (normalized.startsWith("login ")) {
47534
+ const subcommand = normalized.replace("login ", "");
47535
+ return loginSpecs[subcommand];
47478
47536
  }
47537
+ return void 0;
47479
47538
  }
47480
- function SuggestionItem({
47481
- suggestion,
47482
- isSelected,
47483
- maxLabelWidth
47484
- }) {
47485
- const categoryColor = getCategoryColor(suggestion.category);
47486
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Box_default, { children: [
47487
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: isSelected ? "#CA260A" : "#333333", children: isSelected ? "\u25B6 " : " " }),
47488
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: categoryColor, bold: isSelected, inverse: isSelected, children: suggestion.label.padEnd(maxLabelWidth) }),
47489
- suggestion.description && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { color: "#666666", children: [
47490
- " - ",
47491
- suggestion.description
47492
- ] })
47493
- ] });
47539
+ function toGlobalFlagSpec(flag) {
47540
+ return {
47541
+ name: flag.name,
47542
+ type: flag.type ?? "string",
47543
+ description: flag.description,
47544
+ shorthand: flag.alias ?? "",
47545
+ default: flag.default ?? ""
47546
+ };
47494
47547
  }
47495
- function Suggestions({
47496
- suggestions,
47497
- selectedIndex,
47498
- onSelect,
47499
- onNavigate,
47500
- onCancel,
47501
- maxVisible = 20,
47502
- isActive = true
47503
- }) {
47504
- use_input_default(
47505
- (_input, key) => {
47506
- if (!isActive) return;
47507
- if (key.upArrow) {
47508
- onNavigate("up");
47509
- return;
47510
- }
47511
- if (key.downArrow) {
47512
- onNavigate("down");
47513
- return;
47514
- }
47515
- if (key.return || key.tab) {
47516
- const selected = suggestions.at(selectedIndex);
47517
- if (selected) {
47518
- onSelect(selected);
47519
- }
47520
- return;
47521
- }
47522
- if (key.escape) {
47523
- onCancel();
47524
- return;
47525
- }
47526
- },
47527
- { isActive }
47528
- );
47529
- if (suggestions.length === 0) {
47530
- return null;
47548
+ function buildCustomDomainSpec(domainName) {
47549
+ const domain = customDomains.get(domainName);
47550
+ if (!domain) return null;
47551
+ const subcommands = [];
47552
+ for (const [cmdName, cmd] of domain.commands) {
47553
+ subcommands.push({
47554
+ path: [domainName, cmdName],
47555
+ use: cmd.usage ?? cmdName,
47556
+ short: cmd.descriptionShort,
47557
+ long: cmd.description,
47558
+ example: "",
47559
+ aliases: cmd.aliases ?? [],
47560
+ flags: [],
47561
+ subcommands: []
47562
+ });
47531
47563
  }
47532
- const totalCount = suggestions.length;
47533
- const startIndex = Math.max(
47534
- 0,
47535
- Math.min(
47536
- selectedIndex - Math.floor(maxVisible / 2),
47537
- totalCount - maxVisible
47538
- )
47539
- );
47540
- const visibleSuggestions = suggestions.slice(
47541
- startIndex,
47542
- startIndex + maxVisible
47543
- );
47544
- const showScrollUp = startIndex > 0;
47545
- const showScrollDown = startIndex + maxVisible < totalCount;
47546
- const maxLabelWidth = Math.max(
47547
- ...visibleSuggestions.map((s) => s.label.length)
47548
- );
47549
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
47550
- Box_default,
47551
- {
47552
- flexDirection: "column",
47553
- borderStyle: "round",
47554
- borderColor: "#CA260A",
47555
- paddingX: 1,
47556
- children: [
47557
- showScrollUp && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { color: "#666666", dimColor: true, children: [
47558
- "\u25B2",
47559
- " (",
47560
- startIndex,
47561
- " more above)"
47562
- ] }),
47563
- visibleSuggestions.map((suggestion, index) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
47564
- SuggestionItem,
47565
- {
47566
- suggestion,
47567
- isSelected: startIndex + index === selectedIndex,
47568
- index: startIndex + index,
47569
- maxLabelWidth
47570
- },
47571
- suggestion.value
47572
- )),
47573
- showScrollDown && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { color: "#666666", dimColor: true, children: [
47574
- "\u25BC",
47575
- " (",
47576
- totalCount - startIndex - maxVisible,
47577
- " more below)"
47578
- ] }),
47579
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: "#666666", dimColor: true, children: "Tab: select | Up/Down: navigate | Esc: cancel" }) })
47580
- ]
47564
+ for (const [groupName, group] of domain.subcommands) {
47565
+ const groupSubcommands = [];
47566
+ for (const [cmdName, cmd] of group.commands) {
47567
+ groupSubcommands.push({
47568
+ path: [domainName, groupName, cmdName],
47569
+ use: cmd.usage ?? cmdName,
47570
+ short: cmd.descriptionShort,
47571
+ long: cmd.description,
47572
+ example: "",
47573
+ aliases: cmd.aliases ?? [],
47574
+ flags: [],
47575
+ subcommands: []
47576
+ });
47581
47577
  }
47582
- );
47578
+ subcommands.push({
47579
+ path: [domainName, groupName],
47580
+ use: groupName,
47581
+ short: group.descriptionShort,
47582
+ long: group.description,
47583
+ example: "",
47584
+ aliases: [],
47585
+ flags: [],
47586
+ subcommands: groupSubcommands
47587
+ });
47588
+ }
47589
+ return {
47590
+ path: [domainName],
47591
+ use: domainName,
47592
+ short: domain.descriptionShort,
47593
+ long: domain.description,
47594
+ example: "",
47595
+ aliases: [],
47596
+ flags: [],
47597
+ subcommands
47598
+ };
47583
47599
  }
47584
-
47585
- // src/repl/hooks/useDoubleCtrlC.ts
47586
- var import_react25 = __toESM(require_react(), 1);
47587
- function useDoubleCtrlC(options = {}) {
47588
- const { windowMs = 500, onFirstPress, onDoublePress } = options;
47589
- const lastPressRef = (0, import_react25.useRef)(0);
47590
- const [isWaiting, setIsWaiting] = (0, import_react25.useState)(false);
47591
- const timeoutRef = (0, import_react25.useRef)(null);
47592
- const reset = (0, import_react25.useCallback)(() => {
47593
- lastPressRef.current = 0;
47594
- setIsWaiting(false);
47595
- if (timeoutRef.current) {
47596
- clearTimeout(timeoutRef.current);
47597
- timeoutRef.current = null;
47598
- }
47599
- }, []);
47600
- const handleCtrlC = (0, import_react25.useCallback)(() => {
47601
- const now = Date.now();
47602
- const elapsed = now - lastPressRef.current;
47603
- if (elapsed < windowMs && lastPressRef.current !== 0) {
47604
- reset();
47605
- onDoublePress?.();
47606
- return true;
47607
- }
47608
- lastPressRef.current = now;
47609
- setIsWaiting(true);
47610
- onFirstPress?.();
47611
- if (timeoutRef.current) {
47612
- clearTimeout(timeoutRef.current);
47613
- }
47614
- timeoutRef.current = setTimeout(() => {
47615
- setIsWaiting(false);
47616
- }, windowMs);
47617
- return false;
47618
- }, [windowMs, onFirstPress, onDoublePress, reset]);
47600
+ function buildApiDomainSpec(domainName) {
47601
+ const info = domainRegistry.get(domainName);
47602
+ if (!info) return null;
47603
+ const actions = Array.from(validActions);
47604
+ const subcommands = actions.map((action) => ({
47605
+ path: [domainName, action],
47606
+ use: action,
47607
+ short: `${action.charAt(0).toUpperCase() + action.slice(1)} ${info.displayName} resources`,
47608
+ long: `${action.charAt(0).toUpperCase() + action.slice(1)} ${info.displayName} resources in F5 Distributed Cloud`,
47609
+ example: `xcsh ${domainName} ${action}`,
47610
+ aliases: [],
47611
+ flags: [],
47612
+ subcommands: []
47613
+ }));
47619
47614
  return {
47620
- handleCtrlC,
47621
- reset,
47622
- isWaiting
47615
+ path: [domainName],
47616
+ use: domainName,
47617
+ short: info.descriptionShort,
47618
+ long: info.description,
47619
+ example: "",
47620
+ aliases: info.aliases,
47621
+ flags: [],
47622
+ subcommands
47623
47623
  };
47624
47624
  }
47625
-
47626
- // src/repl/hooks/useHistory.ts
47627
- var import_react26 = __toESM(require_react(), 1);
47628
- function useHistory(options) {
47629
- const { history, onSelect } = options;
47630
- const [historyIndex, setHistoryIndex] = (0, import_react26.useState)(-1);
47631
- const reset = (0, import_react26.useCallback)(() => {
47632
- setHistoryIndex(-1);
47633
- }, []);
47634
- const navigateUp = (0, import_react26.useCallback)(() => {
47635
- if (history.length === 0) {
47636
- return null;
47637
- }
47638
- const newIndex = Math.min(historyIndex + 1, history.length - 1);
47639
- setHistoryIndex(newIndex);
47640
- const command = history[history.length - 1 - newIndex];
47641
- if (command !== void 0) {
47642
- onSelect?.(command);
47643
- return command;
47625
+ function buildFullCLISpec() {
47626
+ const commands = [];
47627
+ for (const domain of customDomains.all()) {
47628
+ const spec = buildCustomDomainSpec(domain.name);
47629
+ if (spec) {
47630
+ commands.push(spec);
47644
47631
  }
47645
- return null;
47646
- }, [history, historyIndex, onSelect]);
47647
- const navigateDown = (0, import_react26.useCallback)(() => {
47648
- if (historyIndex <= 0) {
47649
- if (historyIndex === 0) {
47650
- setHistoryIndex(-1);
47651
- onSelect?.("");
47652
- return "";
47632
+ }
47633
+ const customDomainNames = new Set(customDomains.list());
47634
+ for (const [domainName] of domainRegistry) {
47635
+ if (!customDomainNames.has(domainName)) {
47636
+ const spec = buildApiDomainSpec(domainName);
47637
+ if (spec) {
47638
+ commands.push(spec);
47653
47639
  }
47654
- return null;
47655
- }
47656
- const newIndex = historyIndex - 1;
47657
- setHistoryIndex(newIndex);
47658
- const command = history[history.length - 1 - newIndex];
47659
- if (command !== void 0) {
47660
- onSelect?.(command);
47661
- return command;
47662
47640
  }
47663
- return null;
47664
- }, [history, historyIndex, onSelect]);
47641
+ }
47642
+ commands.sort((a, b) => (a.path[0] ?? "").localeCompare(b.path[0] ?? ""));
47665
47643
  return {
47666
- navigateUp,
47667
- navigateDown,
47668
- reset,
47669
- isNavigating: historyIndex >= 0,
47670
- currentIndex: historyIndex
47644
+ version: CLI_VERSION,
47645
+ global_flags: GLOBAL_FLAGS.map(toGlobalFlagSpec),
47646
+ commands
47671
47647
  };
47672
47648
  }
47649
+ function formatFullCLISpec() {
47650
+ return JSON.stringify(buildFullCLISpec(), null, 2);
47651
+ }
47673
47652
 
47674
- // src/repl/hooks/useCompletion.ts
47675
- var import_react27 = __toESM(require_react(), 1);
47653
+ // src/debug/protocol.ts
47654
+ function getDebugFormat() {
47655
+ const envValue = process.env[`${ENV_PREFIX}_DEBUG_EVENTS`];
47656
+ if (envValue === "jsonl" || envValue === "human") {
47657
+ return envValue;
47658
+ }
47659
+ if (process.env[`${ENV_PREFIX}_DEBUG`] === "true") {
47660
+ return "human";
47661
+ }
47662
+ return "none";
47663
+ }
47664
+ var DebugProtocolImpl = class {
47665
+ format;
47666
+ events = [];
47667
+ startTime;
47668
+ constructor() {
47669
+ this.format = getDebugFormat();
47670
+ this.startTime = Date.now();
47671
+ }
47672
+ /**
47673
+ * Check if debug events are enabled
47674
+ */
47675
+ isEnabled() {
47676
+ return this.format !== "none";
47677
+ }
47678
+ /**
47679
+ * Check if JSONL format is enabled
47680
+ */
47681
+ isJsonl() {
47682
+ return this.format === "jsonl";
47683
+ }
47684
+ /**
47685
+ * Emit a debug event
47686
+ */
47687
+ emit(type, event, data = {}) {
47688
+ if (!this.isEnabled()) return;
47689
+ const entry = {
47690
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
47691
+ type,
47692
+ event,
47693
+ data: {
47694
+ ...data,
47695
+ elapsedMs: Date.now() - this.startTime
47696
+ }
47697
+ };
47698
+ this.events.push(entry);
47699
+ if (this.format === "jsonl") {
47700
+ console.error(JSON.stringify(entry));
47701
+ } else if (this.format === "human") {
47702
+ const prefix = `DEBUG [${type}:${event}]`;
47703
+ const dataStr = Object.keys(data).length > 0 ? ` ${JSON.stringify(data)}` : "";
47704
+ console.error(`${prefix}${dataStr}`);
47705
+ }
47706
+ }
47707
+ /**
47708
+ * Emit session-related event
47709
+ */
47710
+ session(event, data = {}) {
47711
+ this.emit("session", event, data);
47712
+ }
47713
+ /**
47714
+ * Emit API-related event
47715
+ */
47716
+ api(event, data = {}) {
47717
+ this.emit("api", event, data);
47718
+ }
47719
+ /**
47720
+ * Emit authentication-related event
47721
+ */
47722
+ auth(event, data = {}) {
47723
+ this.emit("auth", event, data);
47724
+ }
47725
+ /**
47726
+ * Emit profile-related event
47727
+ */
47728
+ profile(event, data = {}) {
47729
+ this.emit("profile", event, data);
47730
+ }
47731
+ /**
47732
+ * Emit UI-related event
47733
+ */
47734
+ ui(event, data = {}) {
47735
+ this.emit("ui", event, data);
47736
+ }
47737
+ /**
47738
+ * Emit error event
47739
+ */
47740
+ error(event, error, data = {}) {
47741
+ this.emit("error", event, {
47742
+ ...data,
47743
+ error: error instanceof Error ? error.message : String(error),
47744
+ stack: error instanceof Error ? error.stack : void 0
47745
+ });
47746
+ }
47747
+ /**
47748
+ * Get all captured events
47749
+ */
47750
+ getEvents() {
47751
+ return [...this.events];
47752
+ }
47753
+ /**
47754
+ * Dump session state on exit (for --dump-state-on-exit)
47755
+ */
47756
+ dumpState(state) {
47757
+ if (!this.isEnabled()) return;
47758
+ console.error("\n=== Session State Dump ===");
47759
+ console.error(JSON.stringify(state, null, 2));
47760
+ console.error("=== End Session State ===\n");
47761
+ }
47762
+ };
47763
+ var debugProtocol = new DebugProtocolImpl();
47764
+ function emitSessionState(session) {
47765
+ debugProtocol.auth("session_state", {
47766
+ isAuthenticated: session.isAuthenticated(),
47767
+ isTokenValidated: session.isTokenValidated(),
47768
+ validationError: session.getValidationError(),
47769
+ serverUrl: session.getServerUrl(),
47770
+ activeProfile: session.getActiveProfileName(),
47771
+ hasApiClient: session.getAPIClient() !== null
47772
+ });
47773
+ const showsWarning = session.isAuthenticated() && !session.isTokenValidated() && session.getValidationError();
47774
+ debugProtocol.auth("warning_check", {
47775
+ shouldShowWarning: !!showsWarning,
47776
+ isAuthenticated: session.isAuthenticated(),
47777
+ isTokenValidated: session.isTokenValidated(),
47778
+ hasValidationError: !!session.getValidationError()
47779
+ });
47780
+ }
47676
47781
 
47677
- // src/config/envvars.ts
47678
- var EnvVarRegistry = [
47679
- {
47680
- name: `${ENV_PREFIX}_API_URL`,
47681
- description: "API endpoint URL",
47682
- relatedFlag: "",
47683
- required: true
47684
- },
47685
- {
47686
- name: `${ENV_PREFIX}_API_TOKEN`,
47687
- description: "API authentication token",
47688
- relatedFlag: "",
47689
- required: true
47690
- },
47691
- {
47692
- name: `${ENV_PREFIX}_NAMESPACE`,
47693
- description: "Default namespace",
47694
- relatedFlag: "-ns"
47695
- },
47696
- {
47697
- name: `${ENV_PREFIX}_OUTPUT_FORMAT`,
47698
- description: "Output format (json, yaml, table)",
47699
- relatedFlag: "-o"
47700
- },
47701
- {
47702
- name: `${ENV_PREFIX}_LOGO`,
47703
- description: "Logo display mode (auto, image, ascii, both, none)",
47704
- relatedFlag: "--logo"
47705
- },
47706
- {
47707
- name: "NO_COLOR",
47708
- description: "Disable color output",
47709
- relatedFlag: "--no-color"
47782
+ // src/repl/session.ts
47783
+ var NAMESPACE_CACHE_TTL = 5 * 60 * 1e3;
47784
+ var REPLSession = class {
47785
+ _history = null;
47786
+ _namespace;
47787
+ _lastExitCode = 0;
47788
+ _contextPath;
47789
+ _tenant = "";
47790
+ _username = "";
47791
+ _validator;
47792
+ _serverUrl = "";
47793
+ _apiToken = "";
47794
+ _apiClient = null;
47795
+ _outputFormat = "table";
47796
+ _debug = false;
47797
+ _profileManager;
47798
+ _activeProfile = null;
47799
+ _activeProfileName = null;
47800
+ _namespaceCache = [];
47801
+ _namespaceCacheTime = 0;
47802
+ // Token validation state
47803
+ _tokenValidated = false;
47804
+ _validationError = null;
47805
+ constructor(config = {}) {
47806
+ this._namespace = config.namespace ?? this.getDefaultNamespace();
47807
+ this._contextPath = new ContextPath();
47808
+ this._validator = new ContextValidator();
47809
+ this._profileManager = getProfileManager();
47810
+ this._serverUrl = config.serverUrl ?? process.env[`${ENV_PREFIX}_API_URL`] ?? "";
47811
+ this._apiToken = config.apiToken ?? process.env[`${ENV_PREFIX}_API_TOKEN`] ?? "";
47812
+ this._outputFormat = config.outputFormat ?? getOutputFormatFromEnv() ?? "table";
47813
+ this._debug = config.debug ?? process.env[`${ENV_PREFIX}_DEBUG`] === "true";
47814
+ if (this._serverUrl) {
47815
+ this._tenant = this.extractTenant(this._serverUrl);
47816
+ }
47817
+ if (this._serverUrl) {
47818
+ this._apiClient = new APIClient({
47819
+ serverUrl: this._serverUrl,
47820
+ apiToken: this._apiToken,
47821
+ debug: this._debug
47822
+ });
47823
+ }
47824
+ }
47825
+ /**
47826
+ * Initialize the session (async operations)
47827
+ */
47828
+ async initialize() {
47829
+ try {
47830
+ this._history = await HistoryManager.create(
47831
+ getHistoryFilePath(),
47832
+ 1e3
47833
+ );
47834
+ } catch (error) {
47835
+ console.error("Warning: could not initialize history:", error);
47836
+ this._history = new HistoryManager(getHistoryFilePath(), 1e3);
47837
+ }
47838
+ await this.loadActiveProfile();
47839
+ if (this._apiClient?.isAuthenticated()) {
47840
+ debugProtocol.auth("token_validation_start", {
47841
+ serverUrl: this._serverUrl,
47842
+ hasApiClient: true
47843
+ });
47844
+ const result = await this._apiClient.validateToken();
47845
+ this._tokenValidated = result.valid;
47846
+ this._validationError = result.error ?? null;
47847
+ debugProtocol.auth("token_validation_complete", {
47848
+ valid: result.valid,
47849
+ error: result.error,
47850
+ tokenValidated: this._tokenValidated,
47851
+ validationError: this._validationError
47852
+ });
47853
+ if (result.valid) {
47854
+ await this.fetchUserInfo();
47855
+ }
47856
+ }
47857
+ }
47858
+ /**
47859
+ * Fetch user info from the API
47860
+ */
47861
+ async fetchUserInfo() {
47862
+ if (!this._apiClient) return;
47863
+ try {
47864
+ const response = await this._apiClient.get("/api/web/custom/user/info");
47865
+ if (response.ok && response.data) {
47866
+ this._username = response.data.email || response.data.name || response.data.username || "";
47867
+ }
47868
+ } catch {
47869
+ }
47870
+ }
47871
+ /**
47872
+ * Load the active profile from profile manager
47873
+ * Note: Environment variables take priority over profile settings
47874
+ * If no active profile is set but exactly one profile exists, auto-activate it
47875
+ */
47876
+ async loadActiveProfile() {
47877
+ try {
47878
+ let activeName = await this._profileManager.getActive();
47879
+ if (!activeName) {
47880
+ const profiles = await this._profileManager.list();
47881
+ if (profiles.length === 1 && profiles[0]) {
47882
+ const singleProfile = profiles[0];
47883
+ await this._profileManager.setActive(singleProfile.name);
47884
+ activeName = singleProfile.name;
47885
+ }
47886
+ }
47887
+ if (activeName) {
47888
+ const profile = await this._profileManager.get(activeName);
47889
+ if (profile) {
47890
+ this._activeProfileName = activeName;
47891
+ this._activeProfile = profile;
47892
+ const envUrl = process.env[`${ENV_PREFIX}_API_URL`];
47893
+ const envToken = process.env[`${ENV_PREFIX}_API_TOKEN`];
47894
+ const envNamespace = process.env[`${ENV_PREFIX}_NAMESPACE`];
47895
+ if (!envUrl && profile.apiUrl) {
47896
+ this._serverUrl = profile.apiUrl;
47897
+ this._tenant = this.extractTenant(profile.apiUrl);
47898
+ }
47899
+ if (!envToken && profile.apiToken) {
47900
+ this._apiToken = profile.apiToken;
47901
+ }
47902
+ if (!envNamespace && profile.defaultNamespace) {
47903
+ this._namespace = profile.defaultNamespace;
47904
+ }
47905
+ if (this._serverUrl) {
47906
+ this._apiClient = new APIClient({
47907
+ serverUrl: this._serverUrl,
47908
+ apiToken: this._apiToken,
47909
+ debug: this._debug
47910
+ });
47911
+ }
47912
+ }
47913
+ }
47914
+ } catch (error) {
47915
+ debugProtocol.error("profile_load_failed", error, {
47916
+ activeProfile: this._activeProfileName
47917
+ });
47918
+ }
47710
47919
  }
47711
- ];
47712
- function formatEnvVarsSection() {
47713
- const maxLen = Math.max(...EnvVarRegistry.map((e) => e.name.length));
47714
- const lines = ["ENVIRONMENT VARIABLES"];
47715
- for (const env3 of EnvVarRegistry) {
47716
- const padding = " ".repeat(maxLen - env3.name.length + 3);
47717
- const flagNote = env3.relatedFlag ? ` [${env3.relatedFlag}]` : "";
47718
- lines.push(` ${env3.name}${padding}${env3.description}${flagNote}`);
47920
+ /**
47921
+ * Get the default namespace from environment or config
47922
+ */
47923
+ getDefaultNamespace() {
47924
+ return process.env[`${ENV_PREFIX}_NAMESPACE`] ?? "default";
47719
47925
  }
47720
- return lines;
47721
- }
47722
- function formatConfigSection() {
47723
- return [
47724
- "CONFIGURATION",
47725
- ` Config file: ~/${CONFIG_FILE_NAME}`,
47726
- " Priority: CLI flags > environment variables > config file > defaults",
47727
- "",
47728
- "DOCUMENTATION",
47729
- ` ${DOCS_URL}`
47730
- ];
47731
- }
47732
-
47733
- // src/repl/help.ts
47734
- function wrapText3(text, width, indent) {
47735
- const prefix = " ".repeat(indent);
47736
- const words = text.split(/\s+/);
47737
- const lines = [];
47738
- let currentLine = prefix;
47739
- for (const word of words) {
47740
- if (currentLine.length + word.length + 1 > width && currentLine !== prefix) {
47741
- lines.push(currentLine);
47742
- currentLine = prefix + word;
47743
- } else {
47744
- currentLine += (currentLine === prefix ? "" : " ") + word;
47926
+ /**
47927
+ * Extract tenant name from server URL
47928
+ */
47929
+ extractTenant(url) {
47930
+ try {
47931
+ const parsed = new URL(url);
47932
+ const hostname = parsed.hostname;
47933
+ const parts = hostname.split(".");
47934
+ if (parts.length > 0 && parts[0]) {
47935
+ return parts[0];
47936
+ }
47937
+ return hostname;
47938
+ } catch {
47939
+ return "";
47745
47940
  }
47746
47941
  }
47747
- if (currentLine.trim()) {
47748
- lines.push(currentLine);
47942
+ /**
47943
+ * Set the default namespace for the session
47944
+ */
47945
+ setNamespace(ns) {
47946
+ this._namespace = ns;
47749
47947
  }
47750
- return lines;
47751
- }
47752
- function formatRootHelp() {
47753
- return [
47754
- "",
47755
- colorBoldWhite(`${CLI_NAME} - ${CLI_FULL_NAME} v${CLI_VERSION}`),
47756
- "",
47757
- "DESCRIPTION",
47758
- ...wrapText3(CLI_DESCRIPTION_LONG, 80, 2),
47759
- "",
47760
- "USAGE",
47761
- ` ${CLI_NAME} Enter interactive REPL mode`,
47762
- ` ${CLI_NAME} <domain> <action> Execute command non-interactively`,
47763
- ` ${CLI_NAME} help [topic] Show help for a topic`,
47764
- "",
47765
- "EXAMPLES",
47766
- ` ${CLI_NAME} tenant_and_identity list namespace List all namespaces`,
47767
- ` ${CLI_NAME} virtual get http_loadbalancer Get a specific load balancer`,
47768
- ` ${CLI_NAME} dns list List DNS zones`,
47769
- ` ${CLI_NAME} waf list -ns prod List WAF policies in prod`,
47770
- "",
47771
- ...formatDomainsSection(),
47772
- "",
47773
- ...formatGlobalFlags(),
47774
- "",
47775
- ...formatEnvVarsSection(),
47776
- "",
47777
- ...formatConfigSection(),
47778
- "",
47779
- "NAVIGATION (Interactive Mode)",
47780
- " <domain> Navigate into a domain (e.g., 'dns', 'lb')",
47781
- " /domain Navigate directly to domain from anywhere",
47782
- " .. Go up one level",
47783
- " / Return to root",
47784
- " context Show current navigation context",
47785
- "",
47786
- "BUILTINS",
47787
- " help Show this help",
47788
- " domains List all available domains",
47789
- " clear Clear the screen",
47790
- " history Show command history",
47791
- " quit, exit Exit the shell",
47792
- ""
47793
- ];
47794
- }
47795
- function formatGlobalFlags() {
47796
- return [
47797
- "GLOBAL FLAGS",
47798
- " -v, --version Show version number",
47799
- " -h, --help Show this help",
47800
- " --no-color Disable color output",
47801
- " -o, --output <fmt> Output format (json, yaml, table)",
47802
- " -ns, --namespace <ns> Target namespace",
47803
- " --spec Output command specification as JSON (for AI)"
47804
- ];
47805
- }
47806
- function formatEnvironmentVariables() {
47807
- return formatEnvVarsSection();
47808
- }
47809
- function formatDomainHelp(domain) {
47810
- const output = ["", colorBoldWhite(`${domain.displayName}`), ""];
47811
- output.push(` ${domain.description}`);
47812
- output.push("");
47813
- if (domain.category || domain.complexity) {
47814
- const meta = [];
47815
- if (domain.category) meta.push(`Category: ${domain.category}`);
47816
- if (domain.complexity) meta.push(`Complexity: ${domain.complexity}`);
47817
- output.push(colorDim(` ${meta.join(" | ")}`));
47818
- output.push("");
47948
+ /**
47949
+ * Get the current default namespace
47950
+ */
47951
+ getNamespace() {
47952
+ return this._namespace;
47819
47953
  }
47820
- output.push("USAGE");
47821
- output.push(` ${CLI_NAME} ${domain.name} <action> [options]`);
47822
- output.push("");
47823
- output.push("ACTIONS");
47824
- const actionDescriptions2 = {
47825
- list: "List resources",
47826
- get: "Get a specific resource by name",
47827
- create: "Create a new resource",
47828
- delete: "Delete a resource",
47829
- replace: "Replace a resource configuration",
47830
- apply: "Apply configuration from file",
47831
- status: "Get resource status",
47832
- patch: "Patch a resource",
47833
- "add-labels": "Add labels to a resource",
47834
- "remove-labels": "Remove labels from a resource"
47835
- };
47836
- for (const action of validActions) {
47837
- const desc = actionDescriptions2[action] ?? action;
47838
- output.push(` ${action.padEnd(16)} ${desc}`);
47954
+ /**
47955
+ * Get the exit code of the last command
47956
+ */
47957
+ getLastExitCode() {
47958
+ return this._lastExitCode;
47839
47959
  }
47840
- output.push("");
47841
- output.push("EXAMPLES");
47842
- output.push(` ${CLI_NAME} ${domain.name} list`);
47843
- output.push(` ${CLI_NAME} ${domain.name} get my-resource`);
47844
- output.push(
47845
- ` ${CLI_NAME} ${domain.name} create my-resource -f config.yaml`
47846
- );
47847
- output.push(` ${CLI_NAME} ${domain.name} delete my-resource`);
47848
- output.push("");
47849
- if (domain.useCases && domain.useCases.length > 0) {
47850
- output.push("USE CASES");
47851
- for (const useCase of domain.useCases.slice(0, 5)) {
47852
- output.push(` - ${useCase}`);
47853
- }
47854
- output.push("");
47960
+ /**
47961
+ * Set the exit code of the last command
47962
+ */
47963
+ setLastExitCode(code) {
47964
+ this._lastExitCode = code;
47855
47965
  }
47856
- if (domain.relatedDomains && domain.relatedDomains.length > 0) {
47857
- output.push("RELATED DOMAINS");
47858
- output.push(` ${domain.relatedDomains.join(", ")}`);
47859
- output.push("");
47966
+ /**
47967
+ * Get the current navigation context
47968
+ */
47969
+ getContextPath() {
47970
+ return this._contextPath;
47971
+ }
47972
+ /**
47973
+ * Get the current tenant name
47974
+ */
47975
+ getTenant() {
47976
+ return this._tenant;
47977
+ }
47978
+ /**
47979
+ * Get the logged-in user's name/email
47980
+ */
47981
+ getUsername() {
47982
+ return this._username;
47983
+ }
47984
+ /**
47985
+ * Set the username (used when fetched from API)
47986
+ */
47987
+ setUsername(username) {
47988
+ this._username = username;
47989
+ }
47990
+ /**
47991
+ * Get the context validator
47992
+ */
47993
+ getValidator() {
47994
+ return this._validator;
47995
+ }
47996
+ /**
47997
+ * Get the history manager
47998
+ */
47999
+ getHistory() {
48000
+ return this._history;
48001
+ }
48002
+ /**
48003
+ * Get the server URL
48004
+ */
48005
+ getServerUrl() {
48006
+ return this._serverUrl;
48007
+ }
48008
+ /**
48009
+ * Check if connected to an API server
48010
+ */
48011
+ isConnected() {
48012
+ return this._serverUrl !== "" && this._apiClient !== null;
48013
+ }
48014
+ /**
48015
+ * Check if authenticated with API
48016
+ */
48017
+ isAuthenticated() {
48018
+ return this._apiClient?.isAuthenticated() ?? false;
48019
+ }
48020
+ /**
48021
+ * Check if the token has been validated (verified working)
48022
+ */
48023
+ isTokenValidated() {
48024
+ return this._tokenValidated;
48025
+ }
48026
+ /**
48027
+ * Get the token validation error, if any
48028
+ */
48029
+ getValidationError() {
48030
+ return this._validationError;
48031
+ }
48032
+ /**
48033
+ * Get the API client
48034
+ */
48035
+ getAPIClient() {
48036
+ return this._apiClient;
47860
48037
  }
47861
- if (domain.aliases && domain.aliases.length > 0) {
47862
- output.push("ALIASES");
47863
- output.push(` ${domain.aliases.join(", ")}`);
47864
- output.push("");
48038
+ /**
48039
+ * Get the current output format
48040
+ */
48041
+ getOutputFormat() {
48042
+ return this._outputFormat;
47865
48043
  }
47866
- output.push(colorDim(`For global options, run: ${CLI_NAME} --help`));
47867
- output.push("");
47868
- return output;
47869
- }
47870
- function formatActionHelp(domainName, action) {
47871
- const domain = getDomainInfo(domainName);
47872
- const displayDomain = domain?.displayName ?? domainName;
47873
- const actionDescriptions2 = {
47874
- list: {
47875
- desc: "List all resources in the namespace",
47876
- usage: `${CLI_NAME} ${domainName} list [--limit N] [--label key=value]`
47877
- },
47878
- get: {
47879
- desc: "Get a specific resource by name",
47880
- usage: `${CLI_NAME} ${domainName} get <name> [-o json|yaml|table]`
47881
- },
47882
- create: {
47883
- desc: "Create a new resource",
47884
- usage: `${CLI_NAME} ${domainName} create <name> -f <file.yaml>`
47885
- },
47886
- delete: {
47887
- desc: "Delete a resource by name",
47888
- usage: `${CLI_NAME} ${domainName} delete <name>`
47889
- },
47890
- replace: {
47891
- desc: "Replace an existing resource configuration",
47892
- usage: `${CLI_NAME} ${domainName} replace <name> -f <file.yaml>`
47893
- },
47894
- apply: {
47895
- desc: "Apply configuration from a file (create or update)",
47896
- usage: `${CLI_NAME} ${domainName} apply -f <file.yaml>`
47897
- },
47898
- status: {
47899
- desc: "Get the current status of a resource",
47900
- usage: `${CLI_NAME} ${domainName} status <name>`
47901
- },
47902
- patch: {
47903
- desc: "Patch specific fields of a resource",
47904
- usage: `${CLI_NAME} ${domainName} patch <name> -f <patch.yaml>`
47905
- },
47906
- "add-labels": {
47907
- desc: "Add labels to a resource",
47908
- usage: `${CLI_NAME} ${domainName} add-labels <name> key=value`
47909
- },
47910
- "remove-labels": {
47911
- desc: "Remove labels from a resource",
47912
- usage: `${CLI_NAME} ${domainName} remove-labels <name> key`
47913
- }
47914
- };
47915
- const actionInfo = actionDescriptions2[action] ?? {
47916
- desc: `Execute ${action} operation`,
47917
- usage: `${CLI_NAME} ${domainName} ${action} [options]`
47918
- };
47919
- return [
47920
- "",
47921
- colorBoldWhite(`${displayDomain} - ${action}`),
47922
- "",
47923
- ` ${actionInfo.desc}`,
47924
- "",
47925
- "USAGE",
47926
- ` ${actionInfo.usage}`,
47927
- "",
47928
- "OPTIONS",
47929
- " -n, --name <name> Resource name",
47930
- " -ns, --namespace <ns> Target namespace",
47931
- " -o, --output <fmt> Output format (json, yaml, table)",
47932
- " -f, --file <path> Configuration file",
47933
- "",
47934
- colorDim(`For domain help, run: ${CLI_NAME} ${domainName} --help`),
47935
- ""
47936
- ];
47937
- }
47938
- function formatTopicHelp(topic) {
47939
- const lowerTopic = topic.toLowerCase();
47940
- const domainInfo = getDomainInfo(lowerTopic);
47941
- if (domainInfo) {
47942
- return formatDomainHelp(domainInfo);
48044
+ /**
48045
+ * Set the output format
48046
+ */
48047
+ setOutputFormat(format) {
48048
+ this._outputFormat = format;
47943
48049
  }
47944
- switch (lowerTopic) {
47945
- case "domains":
47946
- return formatDomainsHelp();
47947
- case "actions":
47948
- return formatActionsHelp();
47949
- case "navigation":
47950
- case "nav":
47951
- return formatNavigationHelp();
47952
- case "env":
47953
- case "environment":
47954
- return ["", ...formatEnvironmentVariables(), ""];
47955
- case "flags":
47956
- return ["", ...formatGlobalFlags(), ""];
47957
- default:
47958
- return [
47959
- "",
47960
- `Unknown help topic: ${topic}`,
47961
- "",
47962
- "Available topics:",
47963
- " domains List all available domains",
47964
- " actions List available actions",
47965
- " navigation Navigation commands",
47966
- " env Environment variables",
47967
- " flags Global flags",
47968
- " <domain> Help for a specific domain (e.g., 'help dns')",
47969
- ""
47970
- ];
48050
+ /**
48051
+ * Get debug mode status
48052
+ */
48053
+ isDebug() {
48054
+ return this._debug;
47971
48055
  }
47972
- }
47973
- function formatDomainsHelp() {
47974
- const output = ["", colorBoldWhite("Available Domains"), ""];
47975
- const categories = /* @__PURE__ */ new Map();
47976
- for (const domain of domainRegistry.values()) {
47977
- const category = domain.category ?? "Other";
47978
- if (!categories.has(category)) {
47979
- categories.set(category, []);
47980
- }
47981
- categories.get(category)?.push(domain);
48056
+ /**
48057
+ * Get the profile manager
48058
+ */
48059
+ getProfileManager() {
48060
+ return this._profileManager;
47982
48061
  }
47983
- const sortedCategories = Array.from(categories.keys()).sort();
47984
- for (const category of sortedCategories) {
47985
- const domains = categories.get(category) ?? [];
47986
- output.push(colorBoldWhite(` ${category}`));
47987
- for (const domain of domains.sort(
47988
- (a, b) => a.name.localeCompare(b.name)
47989
- )) {
47990
- const aliases = domain.aliases.length > 0 ? colorDim(` (${domain.aliases.join(", ")})`) : "";
47991
- output.push(
47992
- ` ${domain.name.padEnd(24)} ${domain.descriptionShort}`
47993
- );
47994
- if (aliases) {
47995
- output.push(` ${"".padEnd(24)} Aliases:${aliases}`);
47996
- }
47997
- }
47998
- output.push("");
48062
+ /**
48063
+ * Get the active profile
48064
+ */
48065
+ getActiveProfile() {
48066
+ return this._activeProfile;
47999
48067
  }
48000
- return output;
48001
- }
48002
- function formatActionsHelp() {
48003
- return [
48004
- "",
48005
- colorBoldWhite("Available Actions"),
48006
- "",
48007
- " list List all resources in the namespace",
48008
- " get Get a specific resource by name",
48009
- " create Create a new resource from a file",
48010
- " delete Delete a resource by name",
48011
- " replace Replace a resource configuration",
48012
- " apply Apply configuration (create or update)",
48013
- " status Get resource status",
48014
- " patch Patch specific fields of a resource",
48015
- " add-labels Add labels to a resource",
48016
- " remove-labels Remove labels from a resource",
48017
- "",
48018
- "USAGE",
48019
- ` ${CLI_NAME} <domain> <action> [options]`,
48020
- "",
48021
- "EXAMPLES",
48022
- ` ${CLI_NAME} dns list`,
48023
- ` ${CLI_NAME} lb get my-loadbalancer`,
48024
- ` ${CLI_NAME} waf create my-policy -f policy.yaml`,
48025
- ""
48026
- ];
48027
- }
48028
- function formatNavigationHelp() {
48029
- return [
48030
- "",
48031
- colorBoldWhite("Navigation Commands"),
48032
- "",
48033
- " <domain> Navigate into a domain context",
48034
- " /<domain> Navigate directly to domain from anywhere",
48035
- " .. Go up one level in context",
48036
- " / Return to root context",
48037
- " back Go up one level (same as ..)",
48038
- " root Return to root (same as /)",
48039
- "",
48040
- "CONTEXT DISPLAY",
48041
- " context Show current navigation context",
48042
- " ctx Alias for context",
48043
- "",
48044
- "EXAMPLES",
48045
- " xcsh> dns # Enter dns domain",
48046
- " dns> list # Execute list in dns context",
48047
- " dns> .. # Return to root",
48048
- " xcsh> /waf # Jump directly to waf",
48049
- " waf> /dns # Jump from waf to dns",
48050
- ""
48051
- ];
48052
- }
48053
- function formatDomainsSection() {
48054
- const output = ["DOMAINS"];
48055
- const domains = Array.from(domainRegistry.values()).sort(
48056
- (a, b) => a.name.localeCompare(b.name)
48057
- );
48058
- const maxNameLen = Math.max(...domains.map((d) => d.name.length));
48059
- for (const domain of domains) {
48060
- const padding = " ".repeat(maxNameLen - domain.name.length + 2);
48061
- output.push(` ${domain.name}${padding}${domain.descriptionShort}`);
48068
+ /**
48069
+ * Get the active profile name
48070
+ */
48071
+ getActiveProfileName() {
48072
+ return this._activeProfileName;
48062
48073
  }
48063
- return output;
48064
- }
48065
- function formatCustomDomainHelp(domain) {
48066
- const output = ["", colorBoldWhite(domain.name), ""];
48067
- output.push("DESCRIPTION");
48068
- output.push(...wrapText3(domain.description, 80, 2));
48069
- output.push("");
48070
- output.push("USAGE");
48071
- output.push(` ${CLI_NAME} ${domain.name} <command> [options]`);
48072
- output.push("");
48073
- if (domain.subcommands.size > 0) {
48074
- output.push("SUBCOMMANDS");
48075
- for (const [name, group] of domain.subcommands) {
48076
- output.push(` ${name.padEnd(16)} ${group.descriptionShort}`);
48074
+ /**
48075
+ * Switch to a different profile
48076
+ */
48077
+ async switchProfile(profileName) {
48078
+ const profile = await this._profileManager.get(profileName);
48079
+ if (!profile) {
48080
+ return false;
48081
+ }
48082
+ const result = await this._profileManager.setActive(profileName);
48083
+ if (!result.success) {
48084
+ return false;
48085
+ }
48086
+ this.clearNamespaceCache();
48087
+ this._tokenValidated = false;
48088
+ this._validationError = null;
48089
+ this._activeProfileName = profileName;
48090
+ this._activeProfile = profile;
48091
+ if (profile.apiUrl) {
48092
+ this._serverUrl = profile.apiUrl;
48093
+ this._tenant = this.extractTenant(profile.apiUrl);
48077
48094
  }
48078
- output.push("");
48095
+ if (profile.apiToken) {
48096
+ this._apiToken = profile.apiToken;
48097
+ }
48098
+ if (profile.defaultNamespace) {
48099
+ this._namespace = profile.defaultNamespace;
48100
+ }
48101
+ if (this._serverUrl) {
48102
+ this._apiClient = new APIClient({
48103
+ serverUrl: this._serverUrl,
48104
+ apiToken: this._apiToken,
48105
+ debug: this._debug
48106
+ });
48107
+ if (this._apiClient.isAuthenticated()) {
48108
+ const validationResult = await this._apiClient.validateToken();
48109
+ this._tokenValidated = validationResult.valid;
48110
+ this._validationError = validationResult.error ?? null;
48111
+ }
48112
+ } else {
48113
+ this._apiClient = null;
48114
+ }
48115
+ return true;
48079
48116
  }
48080
- if (domain.commands.size > 0) {
48081
- output.push("COMMANDS");
48082
- for (const [name, cmd] of domain.commands) {
48083
- output.push(` ${name.padEnd(16)} ${cmd.descriptionShort}`);
48117
+ /**
48118
+ * Clear the active profile
48119
+ */
48120
+ clearActiveProfile() {
48121
+ this._activeProfileName = null;
48122
+ this._activeProfile = null;
48123
+ }
48124
+ /**
48125
+ * Get cached namespaces (returns empty array if cache is stale/empty)
48126
+ */
48127
+ getNamespaceCache() {
48128
+ const now = Date.now();
48129
+ if (this._namespaceCache.length > 0 && now - this._namespaceCacheTime < NAMESPACE_CACHE_TTL) {
48130
+ return this._namespaceCache;
48084
48131
  }
48085
- output.push("");
48132
+ return [];
48086
48133
  }
48087
- output.push(colorDim(`For global options, run: ${CLI_NAME} --help`));
48088
- output.push("");
48089
- return output;
48134
+ /**
48135
+ * Set namespace cache
48136
+ */
48137
+ setNamespaceCache(namespaces) {
48138
+ this._namespaceCache = namespaces;
48139
+ this._namespaceCacheTime = Date.now();
48140
+ }
48141
+ /**
48142
+ * Clear namespace cache (called when switching profiles)
48143
+ */
48144
+ clearNamespaceCache() {
48145
+ this._namespaceCache = [];
48146
+ this._namespaceCacheTime = 0;
48147
+ }
48148
+ /**
48149
+ * Add a command to history
48150
+ */
48151
+ addToHistory(cmd) {
48152
+ this._history?.add(cmd);
48153
+ }
48154
+ /**
48155
+ * Save history to disk
48156
+ */
48157
+ async saveHistory() {
48158
+ await this._history?.save();
48159
+ }
48160
+ /**
48161
+ * Clean up session resources
48162
+ */
48163
+ async cleanup() {
48164
+ await this.saveHistory();
48165
+ }
48166
+ };
48167
+
48168
+ // src/repl/prompt.ts
48169
+ function buildPlainPrompt(_session) {
48170
+ return "> ";
48090
48171
  }
48091
- function formatSubcommandHelp(domainName, subcommand) {
48092
- const output = [
48093
- "",
48094
- colorBoldWhite(`${domainName} ${subcommand.name}`),
48095
- ""
48096
- ];
48097
- output.push("DESCRIPTION");
48098
- output.push(...wrapText3(subcommand.description, 80, 2));
48099
- output.push("");
48100
- output.push("USAGE");
48101
- output.push(
48102
- ` ${CLI_NAME} ${domainName} ${subcommand.name} <command> [options]`
48103
- );
48104
- output.push("");
48105
- if (subcommand.commands.size > 0) {
48106
- output.push("COMMANDS");
48107
- for (const [name, cmd] of subcommand.commands) {
48108
- const usage = cmd.usage ? ` ${cmd.usage}` : "";
48109
- output.push(
48110
- ` ${name}${usage.padEnd(16 - name.length)} ${cmd.descriptionShort}`
48111
- );
48172
+
48173
+ // src/repl/App.tsx
48174
+ var import_react28 = __toESM(require_react(), 1);
48175
+
48176
+ // src/repl/components/InputBox.tsx
48177
+ var import_react23 = __toESM(require_react(), 1);
48178
+
48179
+ // node_modules/ink-text-input/build/index.js
48180
+ var import_react22 = __toESM(require_react(), 1);
48181
+ function TextInput({ value: originalValue, placeholder = "", focus = true, mask, highlightPastedText = false, showCursor = true, onChange, onSubmit }) {
48182
+ const [state, setState] = (0, import_react22.useState)({
48183
+ cursorOffset: (originalValue || "").length,
48184
+ cursorWidth: 0
48185
+ });
48186
+ const { cursorOffset, cursorWidth } = state;
48187
+ (0, import_react22.useEffect)(() => {
48188
+ setState((previousState) => {
48189
+ if (!focus || !showCursor) {
48190
+ return previousState;
48191
+ }
48192
+ const newValue = originalValue || "";
48193
+ if (previousState.cursorOffset > newValue.length - 1) {
48194
+ return {
48195
+ cursorOffset: newValue.length,
48196
+ cursorWidth: 0
48197
+ };
48198
+ }
48199
+ return previousState;
48200
+ });
48201
+ }, [originalValue, focus, showCursor]);
48202
+ const cursorActualWidth = highlightPastedText ? cursorWidth : 0;
48203
+ const value = mask ? mask.repeat(originalValue.length) : originalValue;
48204
+ let renderedValue = value;
48205
+ let renderedPlaceholder = placeholder ? source_default.grey(placeholder) : void 0;
48206
+ if (showCursor && focus) {
48207
+ renderedPlaceholder = placeholder.length > 0 ? source_default.inverse(placeholder[0]) + source_default.grey(placeholder.slice(1)) : source_default.inverse(" ");
48208
+ renderedValue = value.length > 0 ? "" : source_default.inverse(" ");
48209
+ let i = 0;
48210
+ for (const char of value) {
48211
+ renderedValue += i >= cursorOffset - cursorActualWidth && i <= cursorOffset ? source_default.inverse(char) : char;
48212
+ i++;
48213
+ }
48214
+ if (value.length > 0 && cursorOffset === value.length) {
48215
+ renderedValue += source_default.inverse(" ");
48112
48216
  }
48113
- output.push("");
48114
48217
  }
48115
- output.push(
48116
- colorDim(`For domain help, run: ${CLI_NAME} ${domainName} --help`)
48117
- );
48118
- output.push("");
48119
- return output;
48218
+ use_input_default((input, key) => {
48219
+ if (key.upArrow || key.downArrow || key.ctrl && input === "c" || key.tab || key.shift && key.tab) {
48220
+ return;
48221
+ }
48222
+ if (key.return) {
48223
+ if (onSubmit) {
48224
+ onSubmit(originalValue);
48225
+ }
48226
+ return;
48227
+ }
48228
+ let nextCursorOffset = cursorOffset;
48229
+ let nextValue = originalValue;
48230
+ let nextCursorWidth = 0;
48231
+ if (key.leftArrow) {
48232
+ if (showCursor) {
48233
+ nextCursorOffset--;
48234
+ }
48235
+ } else if (key.rightArrow) {
48236
+ if (showCursor) {
48237
+ nextCursorOffset++;
48238
+ }
48239
+ } else if (key.backspace || key.delete) {
48240
+ if (cursorOffset > 0) {
48241
+ nextValue = originalValue.slice(0, cursorOffset - 1) + originalValue.slice(cursorOffset, originalValue.length);
48242
+ nextCursorOffset--;
48243
+ }
48244
+ } else {
48245
+ nextValue = originalValue.slice(0, cursorOffset) + input + originalValue.slice(cursorOffset, originalValue.length);
48246
+ nextCursorOffset += input.length;
48247
+ if (input.length > 1) {
48248
+ nextCursorWidth = input.length;
48249
+ }
48250
+ }
48251
+ if (cursorOffset < 0) {
48252
+ nextCursorOffset = 0;
48253
+ }
48254
+ if (cursorOffset > originalValue.length) {
48255
+ nextCursorOffset = originalValue.length;
48256
+ }
48257
+ setState({
48258
+ cursorOffset: nextCursorOffset,
48259
+ cursorWidth: nextCursorWidth
48260
+ });
48261
+ if (nextValue !== originalValue) {
48262
+ onChange(nextValue);
48263
+ }
48264
+ }, { isActive: focus });
48265
+ return import_react22.default.createElement(Text, null, placeholder ? value.length > 0 ? renderedValue : renderedPlaceholder : renderedValue);
48120
48266
  }
48267
+ var build_default = TextInput;
48121
48268
 
48122
- // src/domains/registry.ts
48123
- var DomainRegistry = class {
48124
- domains = /* @__PURE__ */ new Map();
48125
- /**
48126
- * Register a custom domain
48127
- */
48128
- register(domain) {
48129
- this.domains.set(domain.name, domain);
48269
+ // src/repl/components/InputBox.tsx
48270
+ var import_jsx_runtime = __toESM(require_jsx_runtime(), 1);
48271
+ function HorizontalRule({ width }) {
48272
+ const rule = "\u2500".repeat(Math.max(width, 1));
48273
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: "#CA260A", children: rule });
48274
+ }
48275
+ function InputBox({
48276
+ prompt,
48277
+ value,
48278
+ onChange,
48279
+ onSubmit,
48280
+ width = 80,
48281
+ isActive = true,
48282
+ inputKey = 0
48283
+ }) {
48284
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { flexDirection: "column", width, children: [
48285
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HorizontalRule, { width }),
48286
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { children: [
48287
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { bold: true, color: "#ffffff", children: prompt }),
48288
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
48289
+ build_default,
48290
+ {
48291
+ value,
48292
+ onChange,
48293
+ onSubmit,
48294
+ focus: isActive
48295
+ },
48296
+ inputKey
48297
+ )
48298
+ ] }),
48299
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HorizontalRule, { width })
48300
+ ] });
48301
+ }
48302
+
48303
+ // src/repl/components/StatusBar.tsx
48304
+ import { execSync } from "child_process";
48305
+ var import_jsx_runtime2 = __toESM(require_jsx_runtime(), 1);
48306
+ function getStatusIcon(gitInfo) {
48307
+ if (gitInfo.ahead > 0 && gitInfo.behind > 0) {
48308
+ return "\u21C5";
48309
+ }
48310
+ if (gitInfo.ahead > 0) {
48311
+ return "\u2191";
48130
48312
  }
48131
- /**
48132
- * Check if a domain is registered
48133
- */
48134
- has(name) {
48135
- return this.domains.has(name);
48313
+ if (gitInfo.behind > 0) {
48314
+ return "\u2193";
48136
48315
  }
48137
- /**
48138
- * Get a domain by name
48139
- */
48140
- get(name) {
48141
- return this.domains.get(name);
48316
+ if (gitInfo.isDirty) {
48317
+ return "*";
48142
48318
  }
48143
- /**
48144
- * Get all registered domain names
48145
- */
48146
- list() {
48147
- return Array.from(this.domains.keys());
48319
+ return "\u2713";
48320
+ }
48321
+ function getStatusColor(gitInfo) {
48322
+ if (gitInfo.ahead > 0 || gitInfo.behind > 0) {
48323
+ return "#2196f3";
48148
48324
  }
48149
- /**
48150
- * Get all domains
48151
- */
48152
- all() {
48153
- return Array.from(this.domains.values());
48325
+ if (gitInfo.isDirty) {
48326
+ return "#ffc107";
48154
48327
  }
48155
- /**
48156
- * Execute a command within a domain
48157
- * Handles routing through subcommand groups
48158
- *
48159
- * @param domainName - Name of the domain (e.g., "login")
48160
- * @param args - Command arguments (e.g., ["profile", "list"])
48161
- * @param session - REPL session
48162
- */
48163
- async execute(domainName, args, session) {
48164
- const domain = this.domains.get(domainName);
48165
- if (!domain) {
48166
- return {
48167
- output: [`Unknown domain: ${domainName}`],
48168
- shouldExit: false,
48169
- shouldClear: false,
48170
- contextChanged: false,
48171
- error: "Unknown domain"
48172
- };
48328
+ return "#00c853";
48329
+ }
48330
+ function StatusBar({
48331
+ gitInfo,
48332
+ width = 80,
48333
+ hint = "Ctrl+C: quit"
48334
+ }) {
48335
+ const renderLeft = () => {
48336
+ if (gitInfo?.inRepo) {
48337
+ const icon = getStatusIcon(gitInfo);
48338
+ const color = getStatusColor(gitInfo);
48339
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Text, { children: [
48340
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "#ffffff", children: gitInfo.repoName }),
48341
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "#666666", children: "/" }),
48342
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color, children: gitInfo.branch }),
48343
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { children: " " }),
48344
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color, children: icon })
48345
+ ] });
48173
48346
  }
48174
- if (args.length === 0) {
48175
- if (domain.defaultCommand) {
48176
- return domain.defaultCommand.execute([], session);
48177
- }
48178
- return this.showDomainHelp(domain);
48347
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "#ffffff", children: CLI_NAME });
48348
+ };
48349
+ const renderRight = () => {
48350
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "#666666", children: hint });
48351
+ };
48352
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { width, paddingX: 1, justifyContent: "space-between", children: [
48353
+ renderLeft(),
48354
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Spacer, {}),
48355
+ renderRight()
48356
+ ] });
48357
+ }
48358
+ function getGitInfo() {
48359
+ const defaultInfo = {
48360
+ inRepo: false,
48361
+ repoName: "",
48362
+ branch: "",
48363
+ isDirty: false,
48364
+ ahead: 0,
48365
+ behind: 0
48366
+ };
48367
+ try {
48368
+ try {
48369
+ execSync("git rev-parse --is-inside-work-tree", {
48370
+ encoding: "utf-8",
48371
+ stdio: ["pipe", "pipe", "pipe"]
48372
+ });
48373
+ } catch {
48374
+ return defaultInfo;
48179
48375
  }
48180
- const firstArg = args[0]?.toLowerCase() ?? "";
48181
- const restArgs = args.slice(1);
48182
- if (firstArg === "--help" || firstArg === "-h" || firstArg === "help") {
48183
- return this.showDomainHelp(domain);
48376
+ let repoName = "";
48377
+ try {
48378
+ const topLevel = execSync("git rev-parse --show-toplevel", {
48379
+ encoding: "utf-8",
48380
+ stdio: ["pipe", "pipe", "pipe"]
48381
+ }).trim();
48382
+ repoName = topLevel.split("/").pop() ?? "";
48383
+ } catch {
48384
+ repoName = "repo";
48184
48385
  }
48185
- const subgroup = domain.subcommands.get(firstArg);
48186
- if (subgroup) {
48187
- if (restArgs.length === 0) {
48188
- if (subgroup.defaultCommand) {
48189
- return subgroup.defaultCommand.execute([], session);
48190
- }
48191
- return this.showSubcommandHelp(domain, subgroup);
48192
- }
48193
- const cmdName = restArgs[0]?.toLowerCase() ?? "";
48194
- if (cmdName === "--help" || cmdName === "-h" || cmdName === "help") {
48195
- return this.showSubcommandHelp(domain, subgroup);
48196
- }
48197
- const cmdArgs = restArgs.slice(1);
48198
- const cmd2 = subgroup.commands.get(cmdName);
48199
- if (cmd2) {
48200
- return cmd2.execute(cmdArgs, session);
48201
- }
48202
- for (const [, command] of subgroup.commands) {
48203
- if (command.aliases?.includes(cmdName)) {
48204
- return command.execute(cmdArgs, session);
48205
- }
48206
- }
48207
- return {
48208
- output: [
48209
- `Unknown command: ${domainName} ${firstArg} ${cmdName}`,
48210
- ``,
48211
- `Run '${domainName} ${firstArg}' for available commands.`
48212
- ],
48213
- shouldExit: false,
48214
- shouldClear: false,
48215
- contextChanged: false,
48216
- error: "Unknown command"
48217
- };
48386
+ let branch = "";
48387
+ try {
48388
+ branch = execSync("git rev-parse --abbrev-ref HEAD", {
48389
+ encoding: "utf-8",
48390
+ stdio: ["pipe", "pipe", "pipe"]
48391
+ }).trim();
48392
+ } catch {
48393
+ branch = "unknown";
48218
48394
  }
48219
- const cmd = domain.commands.get(firstArg);
48220
- if (cmd) {
48221
- return cmd.execute(restArgs, session);
48395
+ let isDirty = false;
48396
+ try {
48397
+ const status = execSync("git status --porcelain", {
48398
+ encoding: "utf-8",
48399
+ stdio: ["pipe", "pipe", "pipe"]
48400
+ });
48401
+ isDirty = status.trim().length > 0;
48402
+ } catch {
48222
48403
  }
48223
- for (const [, command] of domain.commands) {
48224
- if (command.aliases?.includes(firstArg)) {
48225
- return command.execute(restArgs, session);
48226
- }
48404
+ let ahead = 0;
48405
+ let behind = 0;
48406
+ try {
48407
+ const counts = execSync(
48408
+ "git rev-list --left-right --count HEAD...@{upstream}",
48409
+ {
48410
+ encoding: "utf-8",
48411
+ stdio: ["pipe", "pipe", "pipe"]
48412
+ }
48413
+ ).trim();
48414
+ const [aheadStr, behindStr] = counts.split(/\s+/);
48415
+ ahead = parseInt(aheadStr ?? "0", 10) || 0;
48416
+ behind = parseInt(behindStr ?? "0", 10) || 0;
48417
+ } catch {
48227
48418
  }
48228
48419
  return {
48229
- output: [
48230
- `Unknown command: ${domainName} ${firstArg}`,
48231
- ``,
48232
- `Run '${domainName}' for available commands.`
48233
- ],
48234
- shouldExit: false,
48235
- shouldClear: false,
48236
- contextChanged: false,
48237
- error: "Unknown command"
48420
+ inRepo: true,
48421
+ repoName,
48422
+ branch,
48423
+ isDirty,
48424
+ ahead,
48425
+ behind
48238
48426
  };
48427
+ } catch {
48428
+ return defaultInfo;
48239
48429
  }
48240
- /**
48241
- * Get completions for a domain command
48242
- */
48243
- async getCompletions(domainName, args, partial, session) {
48244
- const domain = this.domains.get(domainName);
48245
- if (!domain) {
48246
- return [];
48247
- }
48248
- const suggestions = [];
48249
- if (args.length === 0) {
48250
- for (const [name, group] of domain.subcommands) {
48251
- if (name.toLowerCase().startsWith(partial.toLowerCase())) {
48252
- suggestions.push({
48253
- text: name,
48254
- description: group.descriptionShort,
48255
- category: "subcommand"
48256
- });
48257
- }
48430
+ }
48431
+
48432
+ // src/repl/components/Suggestions.tsx
48433
+ var import_react24 = __toESM(require_react(), 1);
48434
+ var import_jsx_runtime3 = __toESM(require_jsx_runtime(), 1);
48435
+ function getCategoryColor(category) {
48436
+ switch (category) {
48437
+ case "domain":
48438
+ return "#2196f3";
48439
+ // Blue
48440
+ case "action":
48441
+ return "#4caf50";
48442
+ // Green
48443
+ case "flag":
48444
+ return "#ffc107";
48445
+ // Yellow
48446
+ case "value":
48447
+ return "#9c27b0";
48448
+ // Purple
48449
+ default:
48450
+ return "#ffffff";
48451
+ }
48452
+ }
48453
+ function SuggestionItem({
48454
+ suggestion,
48455
+ isSelected,
48456
+ maxLabelWidth
48457
+ }) {
48458
+ const categoryColor = getCategoryColor(suggestion.category);
48459
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Box_default, { children: [
48460
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: isSelected ? "#CA260A" : "#333333", children: isSelected ? "\u25B6 " : " " }),
48461
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: categoryColor, bold: isSelected, inverse: isSelected, children: suggestion.label.padEnd(maxLabelWidth) }),
48462
+ suggestion.description && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { color: "#666666", children: [
48463
+ " - ",
48464
+ suggestion.description
48465
+ ] })
48466
+ ] });
48467
+ }
48468
+ function Suggestions({
48469
+ suggestions,
48470
+ selectedIndex,
48471
+ onSelect,
48472
+ onNavigate,
48473
+ onCancel,
48474
+ maxVisible = 20,
48475
+ isActive = true
48476
+ }) {
48477
+ use_input_default(
48478
+ (_input, key) => {
48479
+ if (!isActive) return;
48480
+ if (key.upArrow) {
48481
+ onNavigate("up");
48482
+ return;
48258
48483
  }
48259
- for (const [name, cmd] of domain.commands) {
48260
- if (name.toLowerCase().startsWith(partial.toLowerCase())) {
48261
- suggestions.push({
48262
- text: name,
48263
- description: cmd.descriptionShort,
48264
- category: "command"
48265
- });
48266
- }
48484
+ if (key.downArrow) {
48485
+ onNavigate("down");
48486
+ return;
48267
48487
  }
48268
- return suggestions;
48269
- }
48270
- const firstArg = args[0]?.toLowerCase() ?? "";
48271
- const subgroup = domain.subcommands.get(firstArg);
48272
- if (subgroup && args.length === 1) {
48273
- for (const [name, cmd] of subgroup.commands) {
48274
- if (name.toLowerCase().startsWith(partial.toLowerCase())) {
48275
- suggestions.push({
48276
- text: name,
48277
- description: cmd.descriptionShort,
48278
- category: "command"
48279
- });
48488
+ if (key.return || key.tab) {
48489
+ const selected = suggestions.at(selectedIndex);
48490
+ if (selected) {
48491
+ onSelect(selected);
48280
48492
  }
48493
+ return;
48281
48494
  }
48282
- return suggestions;
48283
- }
48284
- if (subgroup && args.length >= 2) {
48285
- const cmdName = args[1]?.toLowerCase() ?? "";
48286
- const cmd = subgroup.commands.get(cmdName);
48287
- if (cmd?.completion) {
48288
- const completions = await cmd.completion(
48289
- partial,
48290
- args.slice(2),
48291
- session
48292
- );
48293
- return completions.map((text) => ({
48294
- text,
48295
- description: "",
48296
- category: "argument"
48297
- }));
48495
+ if (key.escape) {
48496
+ onCancel();
48497
+ return;
48298
48498
  }
48299
- }
48300
- return suggestions;
48301
- }
48302
- /**
48303
- * Show help for a domain using the unified help formatter.
48304
- * This ensures consistent professional formatting across all domains.
48305
- */
48306
- showDomainHelp(domain) {
48307
- return {
48308
- output: formatCustomDomainHelp(domain),
48309
- shouldExit: false,
48310
- shouldClear: false,
48311
- contextChanged: false
48312
- };
48313
- }
48314
- /**
48315
- * Show help for a subcommand group using the unified help formatter.
48316
- * This ensures consistent professional formatting across all subcommands.
48317
- */
48318
- showSubcommandHelp(domain, subgroup) {
48319
- return {
48320
- output: formatSubcommandHelp(domain.name, subgroup),
48321
- shouldExit: false,
48322
- shouldClear: false,
48323
- contextChanged: false
48324
- };
48499
+ },
48500
+ { isActive }
48501
+ );
48502
+ if (suggestions.length === 0) {
48503
+ return null;
48325
48504
  }
48326
- };
48327
- var customDomains = new DomainRegistry();
48328
- function successResult(output, contextChanged = false) {
48329
- return {
48330
- output,
48331
- shouldExit: false,
48332
- shouldClear: false,
48333
- contextChanged
48334
- };
48505
+ const totalCount = suggestions.length;
48506
+ const startIndex = Math.max(
48507
+ 0,
48508
+ Math.min(
48509
+ selectedIndex - Math.floor(maxVisible / 2),
48510
+ totalCount - maxVisible
48511
+ )
48512
+ );
48513
+ const visibleSuggestions = suggestions.slice(
48514
+ startIndex,
48515
+ startIndex + maxVisible
48516
+ );
48517
+ const showScrollUp = startIndex > 0;
48518
+ const showScrollDown = startIndex + maxVisible < totalCount;
48519
+ const maxLabelWidth = Math.max(
48520
+ ...visibleSuggestions.map((s) => s.label.length)
48521
+ );
48522
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
48523
+ Box_default,
48524
+ {
48525
+ flexDirection: "column",
48526
+ borderStyle: "round",
48527
+ borderColor: "#CA260A",
48528
+ paddingX: 1,
48529
+ children: [
48530
+ showScrollUp && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { color: "#666666", dimColor: true, children: [
48531
+ "\u25B2",
48532
+ " (",
48533
+ startIndex,
48534
+ " more above)"
48535
+ ] }),
48536
+ visibleSuggestions.map((suggestion, index) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
48537
+ SuggestionItem,
48538
+ {
48539
+ suggestion,
48540
+ isSelected: startIndex + index === selectedIndex,
48541
+ index: startIndex + index,
48542
+ maxLabelWidth
48543
+ },
48544
+ suggestion.value
48545
+ )),
48546
+ showScrollDown && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { color: "#666666", dimColor: true, children: [
48547
+ "\u25BC",
48548
+ " (",
48549
+ totalCount - startIndex - maxVisible,
48550
+ " more below)"
48551
+ ] }),
48552
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: "#666666", dimColor: true, children: "Tab: select | Up/Down: navigate | Esc: cancel" }) })
48553
+ ]
48554
+ }
48555
+ );
48335
48556
  }
48336
- function errorResult(message) {
48557
+
48558
+ // src/repl/hooks/useDoubleCtrlC.ts
48559
+ var import_react25 = __toESM(require_react(), 1);
48560
+ function useDoubleCtrlC(options = {}) {
48561
+ const { windowMs = 500, onFirstPress, onDoublePress } = options;
48562
+ const lastPressRef = (0, import_react25.useRef)(0);
48563
+ const [isWaiting, setIsWaiting] = (0, import_react25.useState)(false);
48564
+ const timeoutRef = (0, import_react25.useRef)(null);
48565
+ const reset = (0, import_react25.useCallback)(() => {
48566
+ lastPressRef.current = 0;
48567
+ setIsWaiting(false);
48568
+ if (timeoutRef.current) {
48569
+ clearTimeout(timeoutRef.current);
48570
+ timeoutRef.current = null;
48571
+ }
48572
+ }, []);
48573
+ const handleCtrlC = (0, import_react25.useCallback)(() => {
48574
+ const now = Date.now();
48575
+ const elapsed = now - lastPressRef.current;
48576
+ if (elapsed < windowMs && lastPressRef.current !== 0) {
48577
+ reset();
48578
+ onDoublePress?.();
48579
+ return true;
48580
+ }
48581
+ lastPressRef.current = now;
48582
+ setIsWaiting(true);
48583
+ onFirstPress?.();
48584
+ if (timeoutRef.current) {
48585
+ clearTimeout(timeoutRef.current);
48586
+ }
48587
+ timeoutRef.current = setTimeout(() => {
48588
+ setIsWaiting(false);
48589
+ }, windowMs);
48590
+ return false;
48591
+ }, [windowMs, onFirstPress, onDoublePress, reset]);
48337
48592
  return {
48338
- output: [message],
48339
- shouldExit: false,
48340
- shouldClear: false,
48341
- contextChanged: false,
48342
- error: message
48593
+ handleCtrlC,
48594
+ reset,
48595
+ isWaiting
48343
48596
  };
48344
48597
  }
48345
- function rawStdoutResult(content) {
48598
+
48599
+ // src/repl/hooks/useHistory.ts
48600
+ var import_react26 = __toESM(require_react(), 1);
48601
+ function useHistory(options) {
48602
+ const { history, onSelect } = options;
48603
+ const [historyIndex, setHistoryIndex] = (0, import_react26.useState)(-1);
48604
+ const reset = (0, import_react26.useCallback)(() => {
48605
+ setHistoryIndex(-1);
48606
+ }, []);
48607
+ const navigateUp = (0, import_react26.useCallback)(() => {
48608
+ if (history.length === 0) {
48609
+ return null;
48610
+ }
48611
+ const newIndex = Math.min(historyIndex + 1, history.length - 1);
48612
+ setHistoryIndex(newIndex);
48613
+ const command = history[history.length - 1 - newIndex];
48614
+ if (command !== void 0) {
48615
+ onSelect?.(command);
48616
+ return command;
48617
+ }
48618
+ return null;
48619
+ }, [history, historyIndex, onSelect]);
48620
+ const navigateDown = (0, import_react26.useCallback)(() => {
48621
+ if (historyIndex <= 0) {
48622
+ if (historyIndex === 0) {
48623
+ setHistoryIndex(-1);
48624
+ onSelect?.("");
48625
+ return "";
48626
+ }
48627
+ return null;
48628
+ }
48629
+ const newIndex = historyIndex - 1;
48630
+ setHistoryIndex(newIndex);
48631
+ const command = history[history.length - 1 - newIndex];
48632
+ if (command !== void 0) {
48633
+ onSelect?.(command);
48634
+ return command;
48635
+ }
48636
+ return null;
48637
+ }, [history, historyIndex, onSelect]);
48346
48638
  return {
48347
- output: [],
48348
- // No regular output - rawStdout is used instead
48349
- shouldExit: false,
48350
- shouldClear: false,
48351
- contextChanged: false,
48352
- rawStdout: content
48639
+ navigateUp,
48640
+ navigateDown,
48641
+ reset,
48642
+ isNavigating: historyIndex >= 0,
48643
+ currentIndex: historyIndex
48353
48644
  };
48354
48645
  }
48355
48646
 
48647
+ // src/repl/hooks/useCompletion.ts
48648
+ var import_react27 = __toESM(require_react(), 1);
48649
+
48356
48650
  // src/domains/login/profile/list.ts
48357
48651
  var listCommand = {
48358
48652
  name: "list",
@@ -53175,6 +53469,10 @@ program2.name(CLI_NAME).description("F5 Distributed Cloud Shell - Interactive CL
53175
53469
  formatRootHelp().forEach((line) => console.log(line));
53176
53470
  process.exit(0);
53177
53471
  }
53472
+ if (options.spec && commandArgs.length === 0) {
53473
+ console.log(formatFullCLISpec());
53474
+ process.exit(0);
53475
+ }
53178
53476
  if (options.help && commandArgs.length > 0) {
53179
53477
  commandArgs.push("--help");
53180
53478
  }
@@ -53201,6 +53499,8 @@ program2.name(CLI_NAME).description("F5 Distributed Cloud Shell - Interactive CL
53201
53499
  process.stdout.write("Initializing...");
53202
53500
  const session = new REPLSession();
53203
53501
  await session.initialize();
53502
+ debugProtocol.session("init", { mode: "repl" });
53503
+ emitSessionState(session);
53204
53504
  process.stdout.write("\r\x1B[K");
53205
53505
  renderBanner(cliLogoMode, "startup");
53206
53506
  if (session.isAuthenticated() && !session.isTokenValidated() && session.getValidationError()) {
@@ -53246,6 +53546,11 @@ program2.name(CLI_NAME).description("F5 Distributed Cloud Shell - Interactive CL
53246
53546
  async function executeNonInteractive(args) {
53247
53547
  const session = new REPLSession();
53248
53548
  await session.initialize();
53549
+ debugProtocol.session("init", {
53550
+ mode: "non-interactive",
53551
+ command: args.join(" ")
53552
+ });
53553
+ emitSessionState(session);
53249
53554
  if (session.isAuthenticated() && !session.isTokenValidated() && session.getValidationError()) {
53250
53555
  console.error(
53251
53556
  `${colors.yellow}Warning: ${session.getValidationError()}${colors.reset}`