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