@robinmordasiewicz/f5xc-xcsh 2.0.41-2601192002 → 2.0.41-2601192346

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 +687 -127
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -41229,8 +41229,8 @@ var init_logo_renderer = __esm({
41229
41229
 
41230
41230
  // src/branding/index.ts
41231
41231
  function getVersion() {
41232
- if ("v2.0.41-2601192002") {
41233
- return "v2.0.41-2601192002";
41232
+ if ("v2.0.41-2601192346") {
41233
+ return "v2.0.41-2601192346";
41234
41234
  }
41235
41235
  if (process.env.XCSH_VERSION) {
41236
41236
  return process.env.XCSH_VERSION;
@@ -150546,91 +150546,6 @@ var init_service = __esm({
150546
150546
  });
150547
150547
 
150548
150548
  // src/domains/login/whoami/formatter.ts
150549
- function formatWhoami(info, options = {}) {
150550
- if (!info.isAuthenticated) {
150551
- return ["Enter '/login' to authenticate"];
150552
- }
150553
- if (options.json) {
150554
- return formatWhoamiJson(info);
150555
- }
150556
- return formatWhoamiBox(info);
150557
- }
150558
- function formatWhoamiJson(info) {
150559
- const output = {};
150560
- if (info.tenant) output.tenant = info.tenant;
150561
- if (info.username) output.username = info.username;
150562
- if (info.email) output.email = info.email;
150563
- output.namespace = info.namespace;
150564
- output.serverUrl = info.serverUrl;
150565
- output.isAuthenticated = info.isAuthenticated;
150566
- return [JSON.stringify(output, null, 2)];
150567
- }
150568
- function getAuthStatusDisplay(info) {
150569
- if (!info.isAuthenticated) {
150570
- return "Not authenticated";
150571
- }
150572
- if (info.isValidated) {
150573
- return "\u2713 Authenticated";
150574
- }
150575
- if (info.validationError) {
150576
- return `\u2717 ${info.validationError}`;
150577
- }
150578
- return "\u26A0 Token not verified";
150579
- }
150580
- function formatWhoamiBox(info) {
150581
- const lines = [];
150582
- const red = colors.red;
150583
- const reset = colors.reset;
150584
- const BOX = {
150585
- topLeft: "\u256D",
150586
- topRight: "\u256E",
150587
- bottomLeft: "\u2570",
150588
- bottomRight: "\u256F",
150589
- horizontal: "\u2500",
150590
- vertical: "\u2502"
150591
- };
150592
- const contentLines = [];
150593
- if (info.tenant) {
150594
- contentLines.push({ label: "Tenant", value: info.tenant });
150595
- }
150596
- if (info.email) {
150597
- contentLines.push({ label: "User", value: info.email });
150598
- } else if (info.username) {
150599
- contentLines.push({ label: "User", value: info.username });
150600
- }
150601
- contentLines.push({ label: "Namespace", value: info.namespace });
150602
- contentLines.push({ label: "Server", value: info.serverUrl });
150603
- contentLines.push({
150604
- label: "Auth",
150605
- value: getAuthStatusDisplay(info)
150606
- });
150607
- const maxLabelWidth = Math.max(...contentLines.map((c) => c.label.length));
150608
- const formattedContent = contentLines.map((c) => {
150609
- const paddedLabel = c.label.padEnd(maxLabelWidth);
150610
- return `${paddedLabel}: ${c.value}`;
150611
- });
150612
- const headerTitle = "Connection Info";
150613
- const allTextLines = [...formattedContent, headerTitle];
150614
- const maxContentWidth = Math.max(...allTextLines.map((l) => l.length));
150615
- const innerWidth = maxContentWidth + 2;
150616
- const title = " Connection Info ";
150617
- const remainingWidth = innerWidth - title.length;
150618
- const leftDashes = 1;
150619
- const rightDashes = Math.max(0, remainingWidth - leftDashes);
150620
- lines.push(
150621
- `${red}${BOX.topLeft}${BOX.horizontal.repeat(leftDashes)}${reset}${title}${red}${BOX.horizontal.repeat(rightDashes)}${BOX.topRight}${reset}`
150622
- );
150623
- for (const text of formattedContent) {
150624
- const padding = innerWidth - text.length;
150625
- lines.push(
150626
- `${red}${BOX.vertical}${reset} ${text}${" ".repeat(Math.max(0, padding - 1))}${red}${BOX.vertical}${reset}`
150627
- );
150628
- }
150629
- lines.push(
150630
- `${red}${BOX.bottomLeft}${BOX.horizontal.repeat(innerWidth)}${BOX.bottomRight}${reset}`
150631
- );
150632
- return lines;
150633
- }
150634
150549
  var init_formatter2 = __esm({
150635
150550
  "src/domains/login/whoami/formatter.ts"() {
150636
150551
  "use strict";
@@ -150658,7 +150573,7 @@ var init_whoami = __esm({
150658
150573
  "use strict";
150659
150574
  init_registry();
150660
150575
  init_service();
150661
- init_formatter2();
150576
+ init_connection_table();
150662
150577
  init_domain_formatter();
150663
150578
  init_service();
150664
150579
  init_formatter2();
@@ -150682,11 +150597,22 @@ var init_whoami = __esm({
150682
150597
  return successResult(
150683
150598
  formatKeyValueOutput(keyValueData, {
150684
150599
  ...options,
150685
- title: "Connection Info"
150600
+ title: "Connection Summary"
150686
150601
  })
150687
150602
  );
150688
150603
  }
150689
- const output = formatWhoami(info);
150604
+ const connectionInfo = buildConnectionInfo(
150605
+ session.getActiveProfileName() || "(environment)",
150606
+ session.getServerUrl(),
150607
+ session.isAuthenticated(),
150608
+ session.getNamespace(),
150609
+ !session.isOfflineMode(),
150610
+ session.isTokenValidated(),
150611
+ session.getValidationError() || void 0,
150612
+ session.getAuthSource() || void 0,
150613
+ session.getEnvVarsPresent()
150614
+ );
150615
+ const output = formatConnectionTable(connectionInfo);
150690
150616
  return successResult(output);
150691
150617
  } catch (error) {
150692
150618
  return errorResult(
@@ -152936,12 +152862,21 @@ var init_ai_services = __esm({
152936
152862
  });
152937
152863
 
152938
152864
  // src/domains/subscription/client.ts
152865
+ var client_exports = {};
152866
+ __export(client_exports, {
152867
+ SubscriptionClient: () => SubscriptionClient,
152868
+ getSubscriptionClient: () => getSubscriptionClient,
152869
+ resetSubscriptionClient: () => resetSubscriptionClient
152870
+ });
152939
152871
  function getSubscriptionClient(apiClient) {
152940
152872
  if (!cachedClient2) {
152941
152873
  cachedClient2 = new SubscriptionClient(apiClient);
152942
152874
  }
152943
152875
  return cachedClient2;
152944
152876
  }
152877
+ function resetSubscriptionClient() {
152878
+ cachedClient2 = null;
152879
+ }
152945
152880
  var SubscriptionClient, cachedClient2;
152946
152881
  var init_client3 = __esm({
152947
152882
  "src/domains/subscription/client.ts"() {
@@ -163364,6 +163299,294 @@ var init_creation_flags = __esm({
163364
163299
  }
163365
163300
  });
163366
163301
 
163302
+ // src/quota/cache.ts
163303
+ function getQuotaCache() {
163304
+ if (!globalCache) {
163305
+ globalCache = new QuotaCache();
163306
+ }
163307
+ return globalCache;
163308
+ }
163309
+ function resetQuotaCache() {
163310
+ if (globalCache) {
163311
+ globalCache.invalidate();
163312
+ }
163313
+ globalCache = null;
163314
+ }
163315
+ var CACHE_TTL_MS, QuotaCache, globalCache;
163316
+ var init_cache2 = __esm({
163317
+ "src/quota/cache.ts"() {
163318
+ "use strict";
163319
+ CACHE_TTL_MS = 6e4;
163320
+ QuotaCache = class {
163321
+ cache = /* @__PURE__ */ new Map();
163322
+ /**
163323
+ * Get quota usage, using cache if available and fresh
163324
+ *
163325
+ * @param client - Subscription client for API calls
163326
+ * @param namespace - Namespace to get quota for
163327
+ * @returns Array of quota usage items
163328
+ */
163329
+ async getQuotaUsage(client, namespace) {
163330
+ const cacheKey = this.getCacheKey(namespace);
163331
+ const cached = this.cache.get(cacheKey);
163332
+ if (cached && !this.isExpired(cached.fetchedAt)) {
163333
+ return cached.usage;
163334
+ }
163335
+ const response = await client.getQuotaUsage(namespace);
163336
+ const usage = [];
163337
+ if (response.quota_usage) {
163338
+ for (const [name, item] of Object.entries(response.quota_usage)) {
163339
+ const current = item.usage?.current ?? 0;
163340
+ const limit = item.limit?.maximum ?? 0;
163341
+ const percentage = limit > 0 ? Math.round(current / limit * 100) : 0;
163342
+ usage.push({
163343
+ name,
163344
+ display_name: item.display_name || name,
163345
+ current,
163346
+ limit,
163347
+ percentage
163348
+ });
163349
+ }
163350
+ }
163351
+ this.cache.set(cacheKey, {
163352
+ usage,
163353
+ fetchedAt: Date.now(),
163354
+ namespace
163355
+ });
163356
+ return usage;
163357
+ }
163358
+ /**
163359
+ * Find quota usage for a specific quota name
163360
+ *
163361
+ * @param client - Subscription client for API calls
163362
+ * @param namespace - Namespace to get quota for
163363
+ * @param quotaName - API quota name to find
163364
+ * @returns Quota usage item or undefined if not found
163365
+ */
163366
+ async findQuotaUsage(client, namespace, quotaName) {
163367
+ const usage = await this.getQuotaUsage(client, namespace);
163368
+ return usage.find((q) => q.name === quotaName);
163369
+ }
163370
+ /**
163371
+ * Invalidate cache for a namespace or all namespaces
163372
+ *
163373
+ * @param namespace - Optional namespace to invalidate; if omitted, clears all
163374
+ */
163375
+ invalidate(namespace) {
163376
+ if (namespace) {
163377
+ this.cache.delete(this.getCacheKey(namespace));
163378
+ } else {
163379
+ this.cache.clear();
163380
+ }
163381
+ }
163382
+ /**
163383
+ * Generate cache key for a namespace
163384
+ */
163385
+ getCacheKey(namespace) {
163386
+ return `quota:${namespace}`;
163387
+ }
163388
+ /**
163389
+ * Check if cached data is expired
163390
+ */
163391
+ isExpired(fetchedAt) {
163392
+ return Date.now() - fetchedAt > CACHE_TTL_MS;
163393
+ }
163394
+ };
163395
+ globalCache = null;
163396
+ }
163397
+ });
163398
+
163399
+ // src/quota/mapping.ts
163400
+ var mapping_exports = {};
163401
+ __export(mapping_exports, {
163402
+ getAllQuotaMappings: () => getAllQuotaMappings,
163403
+ getQuotaMapping: () => getQuotaMapping,
163404
+ getQuotaMappingByName: () => getQuotaMappingByName,
163405
+ hasQuotaMapping: () => hasQuotaMapping
163406
+ });
163407
+ function getQuotaMapping(resourceType) {
163408
+ return MAPPING_BY_RESOURCE_TYPE.get(resourceType);
163409
+ }
163410
+ function getQuotaMappingByName(quotaName) {
163411
+ return MAPPING_BY_QUOTA_NAME.get(quotaName);
163412
+ }
163413
+ function getAllQuotaMappings() {
163414
+ return QUOTA_MAPPINGS;
163415
+ }
163416
+ function hasQuotaMapping(resourceType) {
163417
+ return MAPPING_BY_RESOURCE_TYPE.has(resourceType);
163418
+ }
163419
+ var QUOTA_MAPPINGS, MAPPING_BY_RESOURCE_TYPE, MAPPING_BY_QUOTA_NAME;
163420
+ var init_mapping = __esm({
163421
+ "src/quota/mapping.ts"() {
163422
+ "use strict";
163423
+ QUOTA_MAPPINGS = [
163424
+ // Load Balancing
163425
+ // API returns: "HTTP Load Balancer", "TCP Load Balancer"
163426
+ {
163427
+ resourceType: "http_loadbalancer",
163428
+ quotaName: "HTTP Load Balancer",
163429
+ displayName: "HTTP Load Balancers"
163430
+ },
163431
+ {
163432
+ resourceType: "tcp_loadbalancer",
163433
+ quotaName: "TCP Load Balancer",
163434
+ displayName: "TCP Load Balancers"
163435
+ },
163436
+ // Origin & Health
163437
+ // API returns: "origin_pool" (snake_case), "Healthcheck" (title case)
163438
+ {
163439
+ resourceType: "origin_pool",
163440
+ quotaName: "origin_pool",
163441
+ displayName: "Origin Pools"
163442
+ },
163443
+ {
163444
+ resourceType: "healthcheck",
163445
+ quotaName: "Healthcheck",
163446
+ displayName: "Health Checks"
163447
+ },
163448
+ // Sites
163449
+ // API returns: "AWS VPC Site", "Azure VNET Site", "GCP VPC Site"
163450
+ {
163451
+ resourceType: "aws_vpc_site",
163452
+ quotaName: "AWS VPC Site",
163453
+ displayName: "AWS VPC Sites"
163454
+ },
163455
+ {
163456
+ resourceType: "azure_vnet_site",
163457
+ quotaName: "Azure VNET Site",
163458
+ displayName: "Azure VNET Sites"
163459
+ },
163460
+ {
163461
+ resourceType: "gcp_vpc_site",
163462
+ quotaName: "GCP VPC Site",
163463
+ displayName: "GCP VPC Sites"
163464
+ },
163465
+ // Networking
163466
+ // API returns: "Virtual Network", "Network Connector"
163467
+ {
163468
+ resourceType: "virtual_network",
163469
+ quotaName: "Virtual Network",
163470
+ displayName: "Virtual Networks"
163471
+ },
163472
+ {
163473
+ resourceType: "network_connector",
163474
+ quotaName: "Network Connector",
163475
+ displayName: "Network Connectors"
163476
+ },
163477
+ // Security
163478
+ // API returns: "Application Firewall", "Service Policy", "Rate Limiter"
163479
+ {
163480
+ resourceType: "app_firewall",
163481
+ quotaName: "Application Firewall",
163482
+ displayName: "Application Firewalls"
163483
+ },
163484
+ {
163485
+ resourceType: "service_policy",
163486
+ quotaName: "Service Policy",
163487
+ displayName: "Service Policies"
163488
+ },
163489
+ {
163490
+ resourceType: "rate_limiter",
163491
+ quotaName: "Rate Limiter",
163492
+ displayName: "Rate Limiters"
163493
+ },
163494
+ // DNS
163495
+ // API returns: "DNS Zone", "DNS Domain"
163496
+ {
163497
+ resourceType: "dns_zone",
163498
+ quotaName: "DNS Zone",
163499
+ displayName: "DNS Zones"
163500
+ },
163501
+ {
163502
+ resourceType: "dns_domain",
163503
+ quotaName: "DNS Domain",
163504
+ displayName: "DNS Domains"
163505
+ },
163506
+ // API Security
163507
+ // API returns: "API Definition"
163508
+ {
163509
+ resourceType: "api_definition",
163510
+ quotaName: "API Definition",
163511
+ displayName: "API Definitions"
163512
+ },
163513
+ // Namespaces
163514
+ // API returns: "Namespace"
163515
+ {
163516
+ resourceType: "namespace",
163517
+ quotaName: "Namespace",
163518
+ displayName: "Namespaces"
163519
+ }
163520
+ ];
163521
+ MAPPING_BY_RESOURCE_TYPE = new Map(
163522
+ QUOTA_MAPPINGS.map((m) => [m.resourceType, m])
163523
+ );
163524
+ MAPPING_BY_QUOTA_NAME = new Map(
163525
+ QUOTA_MAPPINGS.map((m) => [m.quotaName, m])
163526
+ );
163527
+ }
163528
+ });
163529
+
163530
+ // src/repl/completion/quota-helper.ts
163531
+ var quota_helper_exports = {};
163532
+ __export(quota_helper_exports, {
163533
+ getQuotaForResourceType: () => getQuotaForResourceType,
163534
+ getQuotasForResourceTypes: () => getQuotasForResourceTypes
163535
+ });
163536
+ async function getQuotaForResourceType(apiClient, namespace, resourceType) {
163537
+ const mapping = getQuotaMapping(resourceType);
163538
+ if (!mapping) {
163539
+ return null;
163540
+ }
163541
+ try {
163542
+ const subscriptionClient = new SubscriptionClient(apiClient);
163543
+ const cache3 = getQuotaCache();
163544
+ const usage = await cache3.findQuotaUsage(
163545
+ subscriptionClient,
163546
+ namespace,
163547
+ mapping.quotaName
163548
+ );
163549
+ if (!usage || usage.limit <= 0) {
163550
+ return null;
163551
+ }
163552
+ const percentage = Math.round(usage.current / usage.limit * 100);
163553
+ const level = percentage >= 100 ? "error" : percentage >= 90 ? "warning" : "none";
163554
+ return {
163555
+ current: usage.current,
163556
+ limit: usage.limit,
163557
+ percentage,
163558
+ level,
163559
+ display: `${usage.current}/${usage.limit}`
163560
+ };
163561
+ } catch {
163562
+ return null;
163563
+ }
163564
+ }
163565
+ async function getQuotasForResourceTypes(apiClient, namespace, resourceTypes) {
163566
+ const results = /* @__PURE__ */ new Map();
163567
+ await Promise.all(
163568
+ resourceTypes.map(async (rt) => {
163569
+ const info = await getQuotaForResourceType(
163570
+ apiClient,
163571
+ namespace,
163572
+ rt
163573
+ );
163574
+ if (info) {
163575
+ results.set(rt, info);
163576
+ }
163577
+ })
163578
+ );
163579
+ return results;
163580
+ }
163581
+ var init_quota_helper = __esm({
163582
+ "src/repl/completion/quota-helper.ts"() {
163583
+ "use strict";
163584
+ init_cache2();
163585
+ init_mapping();
163586
+ init_client3();
163587
+ }
163588
+ });
163589
+
163367
163590
  // src/repl/completion/completer.ts
163368
163591
  function isResourceCompletionAppropriate(resource) {
163369
163592
  if (resource.isPrimary) {
@@ -163628,8 +163851,10 @@ var init_completer = __esm({
163628
163851
  }
163629
163852
  return suggestions2;
163630
163853
  }
163631
- const resourceTypes = this.getResourceTypeSuggestions(
163632
- resourceCtx.domain
163854
+ const resourceTypes = await this.getResourceTypeSuggestionsWithQuota(
163855
+ resourceCtx.domain,
163856
+ this.session?.getNamespace() ?? null,
163857
+ this.session?.getAPIClient() ?? null
163633
163858
  );
163634
163859
  if (resourceTypes.length > 0) {
163635
163860
  let suggestions2 = resourceTypes;
@@ -163698,6 +163923,50 @@ var init_completer = __esm({
163698
163923
  }
163699
163924
  return suggestions;
163700
163925
  }
163926
+ /**
163927
+ * Get suggestions with metadata (including resource quota info)
163928
+ * Use this when you need quota info for the status line
163929
+ */
163930
+ async completeWithMeta(text) {
163931
+ const trimmed = text.trimStart();
163932
+ const parsed = parseInput(trimmed);
163933
+ const resourceCtx = this.parseResourceContext(parsed);
163934
+ const suggestions = await this.complete(text);
163935
+ let resourceQuotaInfo;
163936
+ if (resourceCtx.resourceType && resourceCtx.action && CREATION_ACTIONS.has(resourceCtx.action) && hasCreationFlags(resourceCtx.resourceType) && this.session) {
163937
+ try {
163938
+ const { getQuotaForResourceType: getQuotaForResourceType2 } = await Promise.resolve().then(() => (init_quota_helper(), quota_helper_exports));
163939
+ const { getQuotaMapping: getQuotaMapping2 } = await Promise.resolve().then(() => (init_mapping(), mapping_exports));
163940
+ const namespace = this.session.getNamespace();
163941
+ const client = this.session.getAPIClient();
163942
+ const mapping = getQuotaMapping2(resourceCtx.resourceType);
163943
+ if (namespace && client && mapping) {
163944
+ const quotaInfo = await getQuotaForResourceType2(
163945
+ client,
163946
+ namespace,
163947
+ resourceCtx.resourceType
163948
+ );
163949
+ if (quotaInfo) {
163950
+ resourceQuotaInfo = {
163951
+ resourceType: resourceCtx.resourceType,
163952
+ displayName: mapping.displayName,
163953
+ current: quotaInfo.current,
163954
+ limit: quotaInfo.limit,
163955
+ level: quotaInfo.level
163956
+ };
163957
+ }
163958
+ }
163959
+ } catch {
163960
+ }
163961
+ }
163962
+ const result = {
163963
+ suggestions
163964
+ };
163965
+ if (resourceQuotaInfo) {
163966
+ result.resourceQuotaInfo = resourceQuotaInfo;
163967
+ }
163968
+ return result;
163969
+ }
163701
163970
  /**
163702
163971
  * Get completions for custom domain commands
163703
163972
  * Uses unified completion registry for structure navigation,
@@ -164003,6 +164272,47 @@ var init_completer = __esm({
164003
164272
  };
164004
164273
  });
164005
164274
  }
164275
+ /**
164276
+ * Get resource type suggestions with quota information
164277
+ *
164278
+ * Enhanced version of getResourceTypeSuggestions that includes quota usage
164279
+ * data for resources that have quota mappings. Quota info is displayed
164280
+ * right-justified with color coding:
164281
+ * - White: Normal usage (< 90%)
164282
+ * - Yellow: Warning (90-99%)
164283
+ * - Red: At quota limit (100%)
164284
+ *
164285
+ * @param domain - The domain to get resource types for
164286
+ * @param namespace - The namespace to get quota for
164287
+ * @param client - The API client for quota lookups
164288
+ * @returns Promise of completion suggestions with quota info
164289
+ */
164290
+ async getResourceTypeSuggestionsWithQuota(domain, namespace, client) {
164291
+ const baseSuggestions = this.getResourceTypeSuggestions(domain);
164292
+ if (!client || !namespace) {
164293
+ return baseSuggestions;
164294
+ }
164295
+ try {
164296
+ const { getQuotasForResourceTypes: getQuotasForResourceTypes2 } = await Promise.resolve().then(() => (init_quota_helper(), quota_helper_exports));
164297
+ const resourceTypes = baseSuggestions.map((s) => s.text);
164298
+ const quotas = await getQuotasForResourceTypes2(
164299
+ client,
164300
+ namespace,
164301
+ resourceTypes
164302
+ );
164303
+ return baseSuggestions.map((suggestion) => {
164304
+ const quota = quotas.get(suggestion.text);
164305
+ if (!quota) return suggestion;
164306
+ return {
164307
+ ...suggestion,
164308
+ quotaLevel: quota.level,
164309
+ quotaInfo: quota.display
164310
+ };
164311
+ });
164312
+ } catch {
164313
+ return baseSuggestions;
164314
+ }
164315
+ }
164006
164316
  /**
164007
164317
  * Get resource name suggestions from live API
164008
164318
  * Fetches actual resource names for a given resource type
@@ -165276,6 +165586,131 @@ var init_verb_handler = __esm({
165276
165586
  }
165277
165587
  });
165278
165588
 
165589
+ // src/quota/pre-check.ts
165590
+ async function checkQuotaBeforeCreate(client, options) {
165591
+ const { resourceType, namespace, force = false } = options;
165592
+ const mapping = getQuotaMapping(resourceType);
165593
+ if (!mapping) {
165594
+ return { proceed: true, level: "none" };
165595
+ }
165596
+ try {
165597
+ const cache3 = getQuotaCache();
165598
+ const quotaUsage = await cache3.findQuotaUsage(
165599
+ client,
165600
+ namespace,
165601
+ mapping.quotaName
165602
+ );
165603
+ if (!quotaUsage) {
165604
+ return { proceed: true, level: "none" };
165605
+ }
165606
+ const percentage = quotaUsage.percentage ?? (quotaUsage.limit > 0 ? Math.round(quotaUsage.current / quotaUsage.limit * 100) : 0);
165607
+ const quotaInfo = {
165608
+ name: quotaUsage.name,
165609
+ displayName: mapping.displayName,
165610
+ current: quotaUsage.current,
165611
+ limit: quotaUsage.limit,
165612
+ percentage
165613
+ };
165614
+ if (percentage >= ERROR_THRESHOLD) {
165615
+ return {
165616
+ proceed: force,
165617
+ level: "error",
165618
+ message: force ? `Quota limit reached for ${mapping.displayName}, proceeding due to --force` : `Quota limit reached for ${mapping.displayName}`,
165619
+ quota: quotaInfo
165620
+ };
165621
+ }
165622
+ if (percentage >= WARNING_THRESHOLD) {
165623
+ return {
165624
+ proceed: true,
165625
+ level: "warning",
165626
+ message: `Approaching quota limit for ${mapping.displayName}`,
165627
+ quota: quotaInfo
165628
+ };
165629
+ }
165630
+ return { proceed: true, level: "none" };
165631
+ } catch (error) {
165632
+ const message = error instanceof Error ? error.message : "Unknown error";
165633
+ return {
165634
+ proceed: true,
165635
+ level: "none",
165636
+ message: `Could not check quota: ${message}`
165637
+ };
165638
+ }
165639
+ }
165640
+ function formatQuotaWarning(result) {
165641
+ if (result.level === "none" || !result.quota) {
165642
+ return [];
165643
+ }
165644
+ const { quota, level } = result;
165645
+ const lines = [];
165646
+ const icon = level === "error" ? "\u274C" : "\u26A0\uFE0F";
165647
+ const title = level === "error" ? "Quota Exceeded" : "Quota Warning";
165648
+ lines.push(`${icon} ${title}: ${quota.displayName}`);
165649
+ lines.push(
165650
+ ` Usage: ${quota.current}/${quota.limit} (${quota.percentage}%)`
165651
+ );
165652
+ const barWidth = 20;
165653
+ const filledWidth = Math.round(quota.percentage / 100 * barWidth);
165654
+ const emptyWidth = barWidth - filledWidth;
165655
+ const filledChar = "\u2588";
165656
+ const emptyChar = "\u2591";
165657
+ const progressBar2 = filledChar.repeat(filledWidth) + emptyChar.repeat(emptyWidth);
165658
+ lines.push(` ${progressBar2}`);
165659
+ lines.push("");
165660
+ if (level === "error") {
165661
+ lines.push(" Cannot create - quota limit reached.");
165662
+ lines.push(
165663
+ " Use --force to attempt anyway, or delete existing resources."
165664
+ );
165665
+ } else {
165666
+ lines.push(
165667
+ " Creating this resource will approach your quota limit."
165668
+ );
165669
+ }
165670
+ return lines;
165671
+ }
165672
+ function formatQuotaStatusLine(result) {
165673
+ if (result.level === "none" || !result.quota) {
165674
+ return "";
165675
+ }
165676
+ const { quota, level } = result;
165677
+ const icon = level === "error" ? "\u274C" : "\u26A0\uFE0F";
165678
+ return `${icon} ${quota.displayName}: ${quota.current}/${quota.limit} (${quota.percentage}%)`;
165679
+ }
165680
+ var WARNING_THRESHOLD, ERROR_THRESHOLD;
165681
+ var init_pre_check = __esm({
165682
+ "src/quota/pre-check.ts"() {
165683
+ "use strict";
165684
+ init_mapping();
165685
+ init_cache2();
165686
+ WARNING_THRESHOLD = 90;
165687
+ ERROR_THRESHOLD = 100;
165688
+ }
165689
+ });
165690
+
165691
+ // src/quota/index.ts
165692
+ var quota_exports = {};
165693
+ __export(quota_exports, {
165694
+ QuotaCache: () => QuotaCache,
165695
+ checkQuotaBeforeCreate: () => checkQuotaBeforeCreate,
165696
+ formatQuotaStatusLine: () => formatQuotaStatusLine,
165697
+ formatQuotaWarning: () => formatQuotaWarning,
165698
+ getAllQuotaMappings: () => getAllQuotaMappings,
165699
+ getQuotaCache: () => getQuotaCache,
165700
+ getQuotaMapping: () => getQuotaMapping,
165701
+ getQuotaMappingByName: () => getQuotaMappingByName,
165702
+ hasQuotaMapping: () => hasQuotaMapping,
165703
+ resetQuotaCache: () => resetQuotaCache
165704
+ });
165705
+ var init_quota = __esm({
165706
+ "src/quota/index.ts"() {
165707
+ "use strict";
165708
+ init_pre_check();
165709
+ init_mapping();
165710
+ init_cache2();
165711
+ }
165712
+ });
165713
+
165279
165714
  // src/profiling/profiler.ts
165280
165715
  var DEFAULT_THRESHOLDS = {
165281
165716
  slowPhaseMs: 100,
@@ -173347,6 +173782,16 @@ function getCategoryColor(category) {
173347
173782
  return "#ffffff";
173348
173783
  }
173349
173784
  }
173785
+ function getQuotaColor(quotaLevel) {
173786
+ if (quotaLevel === "error") return "#CA260A";
173787
+ if (quotaLevel === "warning") return "#ffc107";
173788
+ return null;
173789
+ }
173790
+ function getQuotaIndicator(quotaLevel) {
173791
+ if (quotaLevel === "error") return " \u274C";
173792
+ if (quotaLevel === "warning") return " \u26A0\uFE0F";
173793
+ return "";
173794
+ }
173350
173795
  function SuggestionItem({
173351
173796
  suggestion,
173352
173797
  isSelected,
@@ -173354,20 +173799,45 @@ function SuggestionItem({
173354
173799
  isContextOnly
173355
173800
  }) {
173356
173801
  const categoryColor = getCategoryColor(suggestion.category);
173357
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Box_default, { children: [
173358
- isContextOnly ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { children: " " }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: isSelected ? "#CA260A" : "#333333", children: isSelected ? "\u25B6 " : " " }),
173359
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: categoryColor, bold: isSelected, inverse: isSelected, children: suggestion.label.padEnd(maxLabelWidth) }),
173360
- suggestion.description && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
173802
+ const quotaColor = getQuotaColor(suggestion.quotaLevel);
173803
+ const quotaIndicator = getQuotaIndicator(suggestion.quotaLevel);
173804
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Box_default, { justifyContent: "space-between", width: "100%", children: [
173805
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Box_default, { children: [
173806
+ isContextOnly ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { children: " " }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: isSelected ? "#CA260A" : "#333333", children: isSelected ? "\u25B6 " : " " }),
173807
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
173808
+ Text,
173809
+ {
173810
+ color: categoryColor,
173811
+ bold: isSelected,
173812
+ inverse: isSelected,
173813
+ children: suggestion.label.padEnd(maxLabelWidth)
173814
+ }
173815
+ ),
173816
+ suggestion.description && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
173817
+ Text,
173818
+ {
173819
+ color: suggestion.isPrimary ? "#ffffff" : "#666666",
173820
+ bold: suggestion.isPrimary || false,
173821
+ children: [
173822
+ " - ",
173823
+ suggestion.description
173824
+ ]
173825
+ }
173826
+ )
173827
+ ] }),
173828
+ suggestion.quotaInfo && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { marginLeft: 2, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
173361
173829
  Text,
173362
173830
  {
173363
- color: suggestion.isPrimary ? "#ffffff" : "#666666",
173364
- bold: suggestion.isPrimary || false,
173831
+ color: quotaColor ?? "#666666",
173832
+ bold: suggestion.quotaLevel === "error",
173365
173833
  children: [
173366
- " - ",
173367
- suggestion.description
173834
+ "(",
173835
+ suggestion.quotaInfo,
173836
+ quotaIndicator,
173837
+ ")"
173368
173838
  ]
173369
173839
  }
173370
- )
173840
+ ) })
173371
173841
  ] });
173372
173842
  }
173373
173843
  function Suggestions({
@@ -173377,7 +173847,8 @@ function Suggestions({
173377
173847
  onNavigate,
173378
173848
  onCancel,
173379
173849
  maxVisible = 20,
173380
- isActive = true
173850
+ isActive = true,
173851
+ resourceQuotaInfo
173381
173852
  }) {
173382
173853
  const isContextOnlyMode = suggestions.every(
173383
173854
  (s) => s.category === "context"
@@ -173461,7 +173932,25 @@ function Suggestions({
173461
173932
  totalCount - startIndex - maxVisible,
173462
173933
  " more below)"
173463
173934
  ] }),
173464
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: "#666666", dimColor: true, children: isContextOnlyMode ? "\u2191\u2193: scroll | Esc: close" : "Tab/\u2192: select | Enter: execute | \u2191\u2193: navigate | Esc: cancel" }) })
173935
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Box_default, { marginTop: 1, justifyContent: "space-between", width: "100%", children: [
173936
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: "#666666", dimColor: true, children: isContextOnlyMode ? "\u2191\u2193: scroll | Esc: close" : "Tab/\u2192: select | Enter: execute | \u2191\u2193: navigate | Esc: cancel" }),
173937
+ resourceQuotaInfo && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
173938
+ Text,
173939
+ {
173940
+ color: getQuotaColor(resourceQuotaInfo.level) ?? "#666666",
173941
+ bold: resourceQuotaInfo.level === "error",
173942
+ children: [
173943
+ resourceQuotaInfo.displayName,
173944
+ ":",
173945
+ " ",
173946
+ resourceQuotaInfo.current,
173947
+ "/",
173948
+ resourceQuotaInfo.limit,
173949
+ getQuotaIndicator(resourceQuotaInfo.level)
173950
+ ]
173951
+ }
173952
+ )
173953
+ ] })
173465
173954
  ]
173466
173955
  }
173467
173956
  );
@@ -174257,6 +174746,7 @@ function useCompletion(options) {
174257
174746
  const [suggestions, setSuggestions] = (0, import_react32.useState)([]);
174258
174747
  const [selectedIndex, setSelectedIndex] = (0, import_react32.useState)(0);
174259
174748
  const [isShowing, setIsShowing] = (0, import_react32.useState)(false);
174749
+ const [resourceQuotaInfo, setResourceQuotaInfo] = (0, import_react32.useState)(null);
174260
174750
  (0, import_react32.useEffect)(() => {
174261
174751
  if (session) {
174262
174752
  completer.setSession(session);
@@ -174266,10 +174756,13 @@ function useCompletion(options) {
174266
174756
  setIsShowing(false);
174267
174757
  setSuggestions([]);
174268
174758
  setSelectedIndex(0);
174759
+ setResourceQuotaInfo(null);
174269
174760
  }, []);
174270
174761
  const triggerCompletion = (0, import_react32.useCallback)(
174271
174762
  async (input) => {
174272
- const newSuggestions = await completer.complete(input);
174763
+ const result = await completer.completeWithMeta(input);
174764
+ const newSuggestions = result.suggestions;
174765
+ setResourceQuotaInfo(result.resourceQuotaInfo ?? null);
174273
174766
  if (newSuggestions.length === 1) {
174274
174767
  const singleSuggestion = newSuggestions[0];
174275
174768
  if (singleSuggestion?.category === "context") {
@@ -174295,7 +174788,9 @@ function useCompletion(options) {
174295
174788
  const filterSuggestions = (0, import_react32.useCallback)(
174296
174789
  async (input) => {
174297
174790
  if (!isShowing) return;
174298
- const newSuggestions = await completer.complete(input);
174791
+ const result = await completer.completeWithMeta(input);
174792
+ const newSuggestions = result.suggestions;
174793
+ setResourceQuotaInfo(result.resourceQuotaInfo ?? null);
174299
174794
  if (newSuggestions.length === 0) {
174300
174795
  hide();
174301
174796
  } else {
@@ -174330,6 +174825,7 @@ function useCompletion(options) {
174330
174825
  suggestions,
174331
174826
  selectedIndex,
174332
174827
  isShowing,
174828
+ resourceQuotaInfo,
174333
174829
  triggerCompletion,
174334
174830
  navigateUp,
174335
174831
  navigateDown,
@@ -174386,7 +174882,7 @@ function useGitStatus(options = {}) {
174386
174882
  init_domains();
174387
174883
  init_domains2();
174388
174884
  init_extensions();
174389
- init_whoami();
174885
+ init_connection_table();
174390
174886
  init_output();
174391
174887
 
174392
174888
  // src/dependencies/relationships.ts
@@ -176249,9 +176745,8 @@ function executeBuiltin(cmd, session, ctx) {
176249
176745
  };
176250
176746
  }
176251
176747
  if (command === "whoami" || command.startsWith("whoami ")) {
176252
- const parts = parseInputArgs(command).slice(1);
176253
176748
  const options = {};
176254
- for (const arg of parts) {
176749
+ for (const arg of cmd.args) {
176255
176750
  const lowerArg = arg.toLowerCase();
176256
176751
  switch (lowerArg) {
176257
176752
  case "--quota":
@@ -176274,20 +176769,41 @@ function executeBuiltin(cmd, session, ctx) {
176274
176769
  break;
176275
176770
  }
176276
176771
  }
176277
- return getWhoamiInfo(session, options).then((info) => ({
176278
- output: formatWhoami(info, options),
176772
+ const connectionInfo = buildConnectionInfo(
176773
+ session.getActiveProfileName() || "(environment)",
176774
+ session.getServerUrl(),
176775
+ session.isAuthenticated(),
176776
+ session.getNamespace(),
176777
+ !session.isOfflineMode(),
176778
+ session.isTokenValidated(),
176779
+ session.getValidationError() || void 0,
176780
+ session.getAuthSource() || void 0,
176781
+ session.getEnvVarsPresent()
176782
+ );
176783
+ if (options.json) {
176784
+ const jsonOutput = {
176785
+ profileName: connectionInfo.profileName,
176786
+ tenant: connectionInfo.tenant,
176787
+ apiUrl: connectionInfo.apiUrl,
176788
+ namespace: connectionInfo.namespace,
176789
+ isAuthenticated: connectionInfo.hasToken,
176790
+ isConnected: connectionInfo.isConnected,
176791
+ isValidated: connectionInfo.isValidated,
176792
+ authSource: connectionInfo.authSource
176793
+ };
176794
+ return {
176795
+ output: [JSON.stringify(jsonOutput, null, 2)],
176796
+ shouldExit: false,
176797
+ shouldClear: false,
176798
+ contextChanged: false
176799
+ };
176800
+ }
176801
+ return {
176802
+ output: formatConnectionTable(connectionInfo),
176279
176803
  shouldExit: false,
176280
176804
  shouldClear: false,
176281
176805
  contextChanged: false
176282
- })).catch((error) => ({
176283
- output: [
176284
- `Failed to get whoami info: ${error instanceof Error ? error.message : "Unknown error"}`
176285
- ],
176286
- shouldExit: false,
176287
- shouldClear: false,
176288
- contextChanged: false,
176289
- error: "whoami failed"
176290
- }));
176806
+ };
176291
176807
  }
176292
176808
  if (command === "debug-completion" || command.startsWith("debug-completion ")) {
176293
176809
  const parts = parseInputArgs(command).slice(1);
@@ -177132,6 +177648,35 @@ async function executeAPICommand(session, ctx, cmd) {
177132
177648
  contextChanged: false
177133
177649
  };
177134
177650
  }
177651
+ if (action === "create" && effectiveResourceType) {
177652
+ const { SubscriptionClient: SubscriptionClient2 } = await Promise.resolve().then(() => (init_client3(), client_exports));
177653
+ const { checkQuotaBeforeCreate: checkQuotaBeforeCreate2, formatQuotaWarning: formatQuotaWarning2 } = await Promise.resolve().then(() => (init_quota(), quota_exports));
177654
+ const subscriptionClient = new SubscriptionClient2(client);
177655
+ const forceFlag = args.some(
177656
+ (a) => a === "--force" || a === "-f"
177657
+ );
177658
+ const quotaResult = await checkQuotaBeforeCreate2(
177659
+ subscriptionClient,
177660
+ {
177661
+ resourceType: effectiveResourceType,
177662
+ namespace: effectiveNamespace,
177663
+ force: forceFlag
177664
+ }
177665
+ );
177666
+ if (quotaResult.level !== "none") {
177667
+ const quotaWarningLines = formatQuotaWarning2(quotaResult);
177668
+ if (!quotaResult.proceed) {
177669
+ return {
177670
+ output: quotaWarningLines,
177671
+ shouldExit: false,
177672
+ shouldClear: false,
177673
+ contextChanged: false,
177674
+ error: "Quota exceeded"
177675
+ };
177676
+ }
177677
+ warningOutput.push(...quotaWarningLines, "");
177678
+ }
177679
+ }
177135
177680
  if (action === "create") {
177136
177681
  const response = await client.post(apiPath, requestBody);
177137
177682
  result = response.data ?? {
@@ -177483,13 +178028,25 @@ function isValidDomain2(word) {
177483
178028
  return false;
177484
178029
  }
177485
178030
  function toUISuggestions(suggestions) {
177486
- return suggestions.map((s) => ({
177487
- label: s.text,
177488
- value: s.text,
177489
- description: s.description,
177490
- category: s.category ?? "builtin"
177491
- // Provide default to avoid undefined
177492
- }));
178031
+ return suggestions.map((s) => {
178032
+ const suggestion = {
178033
+ label: s.text,
178034
+ value: s.text,
178035
+ description: s.description,
178036
+ category: s.category ?? "builtin"
178037
+ // Provide default to avoid undefined
178038
+ };
178039
+ if (s.isPrimary !== void 0) {
178040
+ suggestion.isPrimary = s.isPrimary;
178041
+ }
178042
+ if (s.quotaLevel !== void 0) {
178043
+ suggestion.quotaLevel = s.quotaLevel;
178044
+ }
178045
+ if (s.quotaInfo !== void 0) {
178046
+ suggestion.quotaInfo = s.quotaInfo;
178047
+ }
178048
+ return suggestion;
178049
+ });
177493
178050
  }
177494
178051
  function App2({ initialSession } = {}) {
177495
178052
  const { exit } = use_app_default();
@@ -177964,7 +178521,10 @@ function App2({ initialSession } = {}) {
177964
178521
  onNavigate: handleSuggestionNavigate,
177965
178522
  onCancel: completion.hide,
177966
178523
  maxVisible: 20,
177967
- isActive: false
178524
+ isActive: false,
178525
+ ...completion.resourceQuotaInfo ? {
178526
+ resourceQuotaInfo: completion.resourceQuotaInfo
178527
+ } : {}
177968
178528
  }
177969
178529
  ) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
177970
178530
  StatusBar,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robinmordasiewicz/f5xc-xcsh",
3
- "version": "2.0.41-2601192002",
3
+ "version": "2.0.41-2601192346",
4
4
  "description": "F5 Distributed Cloud Shell - Interactive CLI for F5 XC",
5
5
  "type": "module",
6
6
  "bin": {