@robinmordasiewicz/f5xc-xcsh 6.39.0 → 6.41.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 +170 -28
  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.39.0") {
45358
- return "6.39.0";
45357
+ if ("6.41.0") {
45358
+ return "6.41.0";
45359
45359
  }
45360
45360
  if (process.env.XCSH_VERSION) {
45361
45361
  return process.env.XCSH_VERSION;
@@ -45791,6 +45791,9 @@ var APIClient = class {
45791
45791
  timeout;
45792
45792
  debug;
45793
45793
  retryConfig;
45794
+ // Token validation state
45795
+ _isValidated = false;
45796
+ _validationError = null;
45794
45797
  constructor(config) {
45795
45798
  this.serverUrl = config.serverUrl.replace(/\/+$/, "");
45796
45799
  this.apiToken = config.apiToken ?? "";
@@ -45802,11 +45805,64 @@ var APIClient = class {
45802
45805
  };
45803
45806
  }
45804
45807
  /**
45805
- * Check if client has authentication configured
45808
+ * Check if client has authentication configured (token exists).
45809
+ * Note: This does NOT verify the token is valid. Use isValidated() for that.
45806
45810
  */
45807
45811
  isAuthenticated() {
45808
45812
  return this.apiToken !== "";
45809
45813
  }
45814
+ /**
45815
+ * Validate the API token by making a lightweight API call.
45816
+ * Returns true if token is valid, false otherwise.
45817
+ */
45818
+ async validateToken() {
45819
+ if (!this.apiToken) {
45820
+ this._isValidated = false;
45821
+ this._validationError = "No API token configured";
45822
+ return { valid: false, error: this._validationError };
45823
+ }
45824
+ try {
45825
+ await this.get("/api/web/namespaces");
45826
+ this._isValidated = true;
45827
+ this._validationError = null;
45828
+ return { valid: true };
45829
+ } catch (error) {
45830
+ this._isValidated = false;
45831
+ if (error instanceof APIError) {
45832
+ if (error.statusCode === 401) {
45833
+ this._validationError = "Invalid or expired API token";
45834
+ } else if (error.statusCode === 403) {
45835
+ this._validationError = "Token lacks required permissions";
45836
+ } else if (error.statusCode === 0) {
45837
+ this._validationError = "Network error - could not reach server";
45838
+ } else {
45839
+ this._validationError = `Validation failed: HTTP ${error.statusCode}`;
45840
+ }
45841
+ } else {
45842
+ this._validationError = "Validation failed: Unknown error";
45843
+ }
45844
+ return { valid: false, error: this._validationError };
45845
+ }
45846
+ }
45847
+ /**
45848
+ * Check if client has a validated (verified working) token
45849
+ */
45850
+ isValidated() {
45851
+ return this._isValidated;
45852
+ }
45853
+ /**
45854
+ * Get the validation error message, if any
45855
+ */
45856
+ getValidationError() {
45857
+ return this._validationError;
45858
+ }
45859
+ /**
45860
+ * Clear validation state (called on profile switch)
45861
+ */
45862
+ clearValidationCache() {
45863
+ this._isValidated = false;
45864
+ this._validationError = null;
45865
+ }
45810
45866
  /**
45811
45867
  * Get the server URL
45812
45868
  */
@@ -46766,6 +46822,9 @@ var REPLSession = class {
46766
46822
  _activeProfileName = null;
46767
46823
  _namespaceCache = [];
46768
46824
  _namespaceCacheTime = 0;
46825
+ // Token validation state
46826
+ _tokenValidated = false;
46827
+ _validationError = null;
46769
46828
  constructor(config = {}) {
46770
46829
  this._namespace = config.namespace ?? this.getDefaultNamespace();
46771
46830
  this._contextPath = new ContextPath();
@@ -46801,7 +46860,12 @@ var REPLSession = class {
46801
46860
  }
46802
46861
  await this.loadActiveProfile();
46803
46862
  if (this._apiClient?.isAuthenticated()) {
46804
- await this.fetchUserInfo();
46863
+ const result = await this._apiClient.validateToken();
46864
+ this._tokenValidated = result.valid;
46865
+ this._validationError = result.error ?? null;
46866
+ if (result.valid) {
46867
+ await this.fetchUserInfo();
46868
+ }
46805
46869
  }
46806
46870
  }
46807
46871
  /**
@@ -46963,6 +47027,18 @@ var REPLSession = class {
46963
47027
  isAuthenticated() {
46964
47028
  return this._apiClient?.isAuthenticated() ?? false;
46965
47029
  }
47030
+ /**
47031
+ * Check if the token has been validated (verified working)
47032
+ */
47033
+ isTokenValidated() {
47034
+ return this._tokenValidated;
47035
+ }
47036
+ /**
47037
+ * Get the token validation error, if any
47038
+ */
47039
+ getValidationError() {
47040
+ return this._validationError;
47041
+ }
46966
47042
  /**
46967
47043
  * Get the API client
46968
47044
  */
@@ -47018,6 +47094,8 @@ var REPLSession = class {
47018
47094
  return false;
47019
47095
  }
47020
47096
  this.clearNamespaceCache();
47097
+ this._tokenValidated = false;
47098
+ this._validationError = null;
47021
47099
  this._activeProfileName = profileName;
47022
47100
  this._activeProfile = profile;
47023
47101
  if (profile.apiUrl) {
@@ -47036,6 +47114,11 @@ var REPLSession = class {
47036
47114
  apiToken: this._apiToken,
47037
47115
  debug: this._debug
47038
47116
  });
47117
+ if (this._apiClient.isAuthenticated()) {
47118
+ const validationResult = await this._apiClient.validateToken();
47119
+ this._tokenValidated = validationResult.valid;
47120
+ this._validationError = validationResult.error ?? null;
47121
+ }
47039
47122
  } else {
47040
47123
  this._apiClient = null;
47041
47124
  }
@@ -48390,6 +48473,18 @@ function extractTenantFromUrl(url) {
48390
48473
  return "unknown";
48391
48474
  }
48392
48475
  }
48476
+ function getAuthStatusValue(info, colorStatus) {
48477
+ if (!info.hasToken) {
48478
+ return colorStatus("\u2717 No token", false);
48479
+ }
48480
+ if (info.isValidated) {
48481
+ return colorStatus("\u2713 Authenticated", true);
48482
+ }
48483
+ if (info.validationError) {
48484
+ return colorStatus(`\u2717 ${info.validationError}`, false);
48485
+ }
48486
+ return colorStatus("\u26A0 Token not verified", false);
48487
+ }
48393
48488
  function formatConnectionTable(info, noColor = false) {
48394
48489
  const useColors = shouldUseColors(void 0, noColor);
48395
48490
  const box = useColors ? UNICODE_BOX2 : ASCII_BOX2;
@@ -48404,7 +48499,7 @@ function formatConnectionTable(info, noColor = false) {
48404
48499
  { label: "API URL", value: info.apiUrl },
48405
48500
  {
48406
48501
  label: "Auth",
48407
- value: info.hasToken ? colorStatus("\u2713 Token configured", true) : colorStatus("\u2717 No token", false)
48502
+ value: getAuthStatusValue(info, colorStatus)
48408
48503
  },
48409
48504
  { label: "Namespace", value: info.namespace || "default" },
48410
48505
  {
@@ -48445,8 +48540,8 @@ function formatConnectionTable(info, noColor = false) {
48445
48540
  function stripAnsi2(str) {
48446
48541
  return str.replace(/\x1b\[[0-9;]*m/g, "");
48447
48542
  }
48448
- function buildConnectionInfo(profileName, apiUrl, hasToken, namespace, isConnected) {
48449
- return {
48543
+ function buildConnectionInfo(profileName, apiUrl, hasToken, namespace, isConnected, isValidated, validationError) {
48544
+ const info = {
48450
48545
  profileName,
48451
48546
  tenant: extractTenantFromUrl(apiUrl),
48452
48547
  apiUrl,
@@ -48454,6 +48549,13 @@ function buildConnectionInfo(profileName, apiUrl, hasToken, namespace, isConnect
48454
48549
  namespace,
48455
48550
  isConnected
48456
48551
  };
48552
+ if (isValidated !== void 0) {
48553
+ info.isValidated = isValidated;
48554
+ }
48555
+ if (validationError) {
48556
+ info.validationError = validationError;
48557
+ }
48558
+ return info;
48457
48559
  }
48458
48560
 
48459
48561
  // src/domains/login/profile/create.ts
@@ -48568,7 +48670,9 @@ var useCommand = {
48568
48670
  profile?.apiUrl || "",
48569
48671
  !!profile?.apiToken,
48570
48672
  profile?.defaultNamespace || session.getNamespace(),
48571
- session.isAuthenticated()
48673
+ session.isAuthenticated(),
48674
+ session.isTokenValidated(),
48675
+ session.getValidationError() ?? void 0
48572
48676
  );
48573
48677
  const tableLines = formatConnectionTable(connectionInfo);
48574
48678
  return successResult(
@@ -48920,6 +49024,21 @@ function colorizeLogoLine(line) {
48920
49024
  }
48921
49025
  return result;
48922
49026
  }
49027
+ function wrapText4(text, maxWidth) {
49028
+ const words = text.split(" ");
49029
+ const lines = [];
49030
+ let currentLine = "";
49031
+ for (const word of words) {
49032
+ if (currentLine.length + word.length + 1 <= maxWidth) {
49033
+ currentLine += (currentLine ? " " : "") + word;
49034
+ } else {
49035
+ if (currentLine) lines.push(currentLine);
49036
+ currentLine = word;
49037
+ }
49038
+ }
49039
+ if (currentLine) lines.push(currentLine);
49040
+ return lines;
49041
+ }
48923
49042
  function printImageBanner(imageSeq, imageHeight, imageWidth) {
48924
49043
  const BOX = {
48925
49044
  topLeft: "\u256D",
@@ -48933,11 +49052,7 @@ function printImageBanner(imageSeq, imageHeight, imageWidth) {
48933
49052
  const INNER_WIDTH = TOTAL_WIDTH - 2;
48934
49053
  const IMAGE_COL_WIDTH = 1 + imageWidth + 1;
48935
49054
  const TEXT_COL_WIDTH = INNER_WIDTH - IMAGE_COL_WIDTH;
48936
- const HELP_LINES = [
48937
- "Type 'help' for commands",
48938
- "Run 'namespace <ns>' to set",
48939
- "Press Ctrl+C twice to exit"
48940
- ];
49055
+ const HELP_LINES = wrapText4(CLI_DESCRIPTION_MEDIUM, TEXT_COL_WIDTH - 2);
48941
49056
  const helpStartRow = Math.floor((imageHeight - HELP_LINES.length) / 2);
48942
49057
  const title = ` ${CLI_FULL_NAME} v${CLI_VERSION} `;
48943
49058
  const leftDashes = 3;
@@ -48987,11 +49102,8 @@ function printAsciiBanner() {
48987
49102
  const logoWidth = Math.max(...logoLines.map((l) => [...l].length));
48988
49103
  const TOTAL_WIDTH = Math.max(80, logoWidth + 4);
48989
49104
  const INNER_WIDTH = TOTAL_WIDTH - 2;
48990
- const HELP_LINES = [
48991
- "Type 'help' for commands",
48992
- "Run 'namespace <ns>' to set",
48993
- "Press Ctrl+C twice to exit"
48994
- ];
49105
+ const helpColumnWidth = INNER_WIDTH - logoWidth - 1;
49106
+ const HELP_LINES = wrapText4(CLI_DESCRIPTION_MEDIUM, helpColumnWidth - 2);
48995
49107
  const HELP_START_ROW = 8;
48996
49108
  const title = ` ${CLI_FULL_NAME} v${CLI_VERSION} `;
48997
49109
  const leftDashes = 3;
@@ -49008,7 +49120,6 @@ function printAsciiBanner() {
49008
49120
  const helpText = helpIndex >= 0 && helpIndex < HELP_LINES.length ? HELP_LINES[helpIndex] ?? "" : "";
49009
49121
  const paddedLogo = logoLine.padEnd(logoWidth);
49010
49122
  const coloredLogo = colorizeLogoLine(paddedLogo);
49011
- const helpColumnWidth = INNER_WIDTH - logoWidth - 1;
49012
49123
  const paddedHelp = helpText.padEnd(helpColumnWidth);
49013
49124
  output.push(
49014
49125
  colorRed(BOX.vertical) + coloredLogo + " " + colorBoldWhite(paddedHelp) + colorRed(BOX.vertical)
@@ -49039,11 +49150,7 @@ function getBannerLines(logoMode, useImage) {
49039
49150
  const logoWidth = Math.max(...logoLines.map((l) => [...l].length));
49040
49151
  const TOTAL_WIDTH = Math.max(80, logoWidth + 4);
49041
49152
  const INNER_WIDTH = TOTAL_WIDTH - 2;
49042
- const HELP_LINES = [
49043
- "Type 'help' for commands",
49044
- "Run 'namespace <ns>' to set",
49045
- "Press Ctrl+C twice to exit"
49046
- ];
49153
+ const helpColumnWidth = INNER_WIDTH - logoWidth - 1;
49047
49154
  const title = ` ${CLI_FULL_NAME} v${CLI_VERSION} `;
49048
49155
  const leftDashes = 3;
49049
49156
  const rightDashes = TOTAL_WIDTH - 1 - leftDashes - title.length - 1;
@@ -49064,8 +49171,12 @@ function getBannerLines(logoMode, useImage) {
49064
49171
  const imageWidth = F5_LOGO_DISPLAY_WIDTH;
49065
49172
  const IMAGE_COL_WIDTH = 1 + imageWidth + 1;
49066
49173
  const TEXT_COL_WIDTH = INNER_WIDTH - IMAGE_COL_WIDTH;
49174
+ const HELP_LINES2 = wrapText4(
49175
+ CLI_DESCRIPTION_MEDIUM,
49176
+ TEXT_COL_WIDTH - 2
49177
+ );
49067
49178
  const helpStartRow = Math.floor(
49068
- (imageHeight - HELP_LINES.length) / 2
49179
+ (imageHeight - HELP_LINES2.length) / 2
49069
49180
  );
49070
49181
  let bannerStr = "";
49071
49182
  bannerStr += "\n";
@@ -49075,7 +49186,7 @@ function getBannerLines(logoMode, useImage) {
49075
49186
  bannerStr += colorRed(BOX.vertical) + " ".repeat(INNER_WIDTH) + colorRed(BOX.vertical) + "\n";
49076
49187
  for (let row = 0; row < imageHeight; row++) {
49077
49188
  const helpIndex = row - helpStartRow;
49078
- const helpText = helpIndex >= 0 && helpIndex < HELP_LINES.length ? HELP_LINES[helpIndex] ?? "" : "";
49189
+ const helpText = helpIndex >= 0 && helpIndex < HELP_LINES2.length ? HELP_LINES2[helpIndex] ?? "" : "";
49079
49190
  const imageSpace = " ".repeat(IMAGE_COL_WIDTH);
49080
49191
  const paddedHelp = helpText.padEnd(TEXT_COL_WIDTH);
49081
49192
  bannerStr += colorRed(BOX.vertical) + imageSpace + colorBoldWhite(paddedHelp) + colorRed(BOX.vertical) + "\n";
@@ -49094,6 +49205,7 @@ function getBannerLines(logoMode, useImage) {
49094
49205
  }
49095
49206
  }
49096
49207
  const HELP_START_ROW = 8;
49208
+ const HELP_LINES = wrapText4(CLI_DESCRIPTION_MEDIUM, helpColumnWidth - 2);
49097
49209
  output.push("");
49098
49210
  output.push(
49099
49211
  colorRed(BOX.topLeft + BOX.horizontal.repeat(leftDashes)) + colorBoldWhite(title) + colorRed(
@@ -49106,7 +49218,6 @@ function getBannerLines(logoMode, useImage) {
49106
49218
  const helpText = helpIndex >= 0 && helpIndex < HELP_LINES.length ? HELP_LINES[helpIndex] ?? "" : "";
49107
49219
  const paddedLogo = logoLine.padEnd(logoWidth);
49108
49220
  const coloredLogo = colorizeLogoLine(paddedLogo);
49109
- const helpColumnWidth = INNER_WIDTH - logoWidth - 1;
49110
49221
  const paddedHelp = helpText.padEnd(helpColumnWidth);
49111
49222
  output.push(
49112
49223
  colorRed(BOX.vertical) + coloredLogo + " " + colorBoldWhite(paddedHelp) + colorRed(BOX.vertical)
@@ -49260,6 +49371,14 @@ async function getWhoamiInfo(session, _options = {}) {
49260
49371
  namespace: session.getNamespace(),
49261
49372
  isAuthenticated: session.isAuthenticated()
49262
49373
  };
49374
+ const isValidated = session.isTokenValidated();
49375
+ const validationError = session.getValidationError();
49376
+ if (isValidated !== void 0) {
49377
+ info.isValidated = isValidated;
49378
+ }
49379
+ if (validationError) {
49380
+ info.validationError = validationError;
49381
+ }
49263
49382
  if (!info.isAuthenticated) {
49264
49383
  return info;
49265
49384
  }
@@ -49298,6 +49417,18 @@ function formatWhoamiJson(info) {
49298
49417
  output.isAuthenticated = info.isAuthenticated;
49299
49418
  return [JSON.stringify(output, null, 2)];
49300
49419
  }
49420
+ function getAuthStatusDisplay(info) {
49421
+ if (!info.isAuthenticated) {
49422
+ return "Not authenticated";
49423
+ }
49424
+ if (info.isValidated) {
49425
+ return "\u2713 Authenticated";
49426
+ }
49427
+ if (info.validationError) {
49428
+ return `\u2717 ${info.validationError}`;
49429
+ }
49430
+ return "\u26A0 Token not verified";
49431
+ }
49301
49432
  function formatWhoamiBox(info) {
49302
49433
  const lines = [];
49303
49434
  const red = colors.red;
@@ -49323,7 +49454,7 @@ function formatWhoamiBox(info) {
49323
49454
  contentLines.push({ label: "Server", value: info.serverUrl });
49324
49455
  contentLines.push({
49325
49456
  label: "Auth",
49326
- value: info.isAuthenticated ? "\u2713 Authenticated" : "Not authenticated"
49457
+ value: getAuthStatusDisplay(info)
49327
49458
  });
49328
49459
  const maxLabelWidth = Math.max(...contentLines.map((c) => c.label.length));
49329
49460
  const formattedContent = contentLines.map((c) => {
@@ -53055,6 +53186,12 @@ program2.name(CLI_NAME).description("F5 Distributed Cloud Shell - Interactive CL
53055
53186
  await session.initialize();
53056
53187
  process.stdout.write("\r\x1B[K");
53057
53188
  renderBanner(cliLogoMode, "startup");
53189
+ if (session.isAuthenticated() && !session.isTokenValidated() && session.getValidationError()) {
53190
+ console.log("");
53191
+ console.log(
53192
+ `${colors.yellow}Warning: ${session.getValidationError()}${colors.reset}`
53193
+ );
53194
+ }
53058
53195
  const profiles = await session.getProfileManager().list();
53059
53196
  const envConfigured = process.env[`${ENV_PREFIX}_API_URL`] && process.env[`${ENV_PREFIX}_API_TOKEN`];
53060
53197
  if (profiles.length === 0 && !envConfigured) {
@@ -53092,6 +53229,11 @@ program2.name(CLI_NAME).description("F5 Distributed Cloud Shell - Interactive CL
53092
53229
  async function executeNonInteractive(args) {
53093
53230
  const session = new REPLSession();
53094
53231
  await session.initialize();
53232
+ if (session.isAuthenticated() && !session.isTokenValidated() && session.getValidationError()) {
53233
+ console.error(
53234
+ `${colors.yellow}Warning: ${session.getValidationError()}${colors.reset}`
53235
+ );
53236
+ }
53095
53237
  const command = args.join(" ");
53096
53238
  const result = await executeCommand(command, session);
53097
53239
  result.output.forEach((line) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robinmordasiewicz/f5xc-xcsh",
3
- "version": "6.39.0",
3
+ "version": "6.41.0",
4
4
  "description": "F5 Distributed Cloud Shell - Interactive CLI for F5 XC",
5
5
  "type": "module",
6
6
  "bin": {