@robinmordasiewicz/f5xc-xcsh 2.0.21-2601100223 → 2.0.21-2601100619

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 +827 -26
  2. package/package.json +6 -1
package/dist/index.js CHANGED
@@ -7941,7 +7941,7 @@ var require_react_reconciler_development = __commonJS({
7941
7941
  var ContextConsumer = 9;
7942
7942
  var ContextProvider = 10;
7943
7943
  var ForwardRef = 11;
7944
- var Profiler = 12;
7944
+ var Profiler2 = 12;
7945
7945
  var SuspenseComponent = 13;
7946
7946
  var MemoComponent = 14;
7947
7947
  var SimpleMemoComponent = 15;
@@ -8095,7 +8095,7 @@ var require_react_reconciler_development = __commonJS({
8095
8095
  return "Mode";
8096
8096
  case OffscreenComponent:
8097
8097
  return "Offscreen";
8098
- case Profiler:
8098
+ case Profiler2:
8099
8099
  return "Profiler";
8100
8100
  case ScopeComponent:
8101
8101
  return "Scope";
@@ -14477,7 +14477,7 @@ var require_react_reconciler_development = __commonJS({
14477
14477
  var root = parentFiber.stateNode;
14478
14478
  root.effectDuration += elapsedTime;
14479
14479
  return;
14480
- case Profiler:
14480
+ case Profiler2:
14481
14481
  var parentStateNode = parentFiber.stateNode;
14482
14482
  parentStateNode.effectDuration += elapsedTime;
14483
14483
  return;
@@ -14499,7 +14499,7 @@ var require_react_reconciler_development = __commonJS({
14499
14499
  root.passiveEffectDuration += elapsedTime;
14500
14500
  }
14501
14501
  return;
14502
- case Profiler:
14502
+ case Profiler2:
14503
14503
  var parentStateNode = parentFiber.stateNode;
14504
14504
  if (parentStateNode !== null) {
14505
14505
  parentStateNode.passiveEffectDuration += elapsedTime;
@@ -16964,7 +16964,7 @@ var require_react_reconciler_development = __commonJS({
16964
16964
  pushProvider(workInProgress2, context, newValue);
16965
16965
  break;
16966
16966
  }
16967
- case Profiler:
16967
+ case Profiler2:
16968
16968
  {
16969
16969
  var hasChildWork = includesSomeLane(renderLanes2, workInProgress2.childLanes);
16970
16970
  if (hasChildWork) {
@@ -17108,7 +17108,7 @@ var require_react_reconciler_development = __commonJS({
17108
17108
  return updateFragment(current2, workInProgress2, renderLanes2);
17109
17109
  case Mode:
17110
17110
  return updateMode(current2, workInProgress2, renderLanes2);
17111
- case Profiler:
17111
+ case Profiler2:
17112
17112
  return updateProfiler(current2, workInProgress2, renderLanes2);
17113
17113
  case ContextProvider:
17114
17114
  return updateContextProvider(current2, workInProgress2, renderLanes2);
@@ -17543,7 +17543,7 @@ var require_react_reconciler_development = __commonJS({
17543
17543
  case ForwardRef:
17544
17544
  case Fragment2:
17545
17545
  case Mode:
17546
- case Profiler:
17546
+ case Profiler2:
17547
17547
  case ContextConsumer:
17548
17548
  case MemoComponent:
17549
17549
  bubbleProperties(workInProgress2);
@@ -18383,7 +18383,7 @@ var require_react_reconciler_development = __commonJS({
18383
18383
  {
18384
18384
  if ((finishedWork.flags & Update) !== NoFlags) {
18385
18385
  switch (finishedWork.tag) {
18386
- case Profiler: {
18386
+ case Profiler2: {
18387
18387
  var passiveEffectDuration = finishedWork.stateNode.passiveEffectDuration;
18388
18388
  var _finishedWork$memoize = finishedWork.memoizedProps, id = _finishedWork$memoize.id, onPostCommit = _finishedWork$memoize.onPostCommit;
18389
18389
  var commitTime2 = getCommitTime();
@@ -18403,7 +18403,7 @@ var require_react_reconciler_development = __commonJS({
18403
18403
  var root = parentFiber.stateNode;
18404
18404
  root.passiveEffectDuration += passiveEffectDuration;
18405
18405
  break outer;
18406
- case Profiler:
18406
+ case Profiler2:
18407
18407
  var parentStateNode = parentFiber.stateNode;
18408
18408
  parentStateNode.passiveEffectDuration += passiveEffectDuration;
18409
18409
  break outer;
@@ -18536,7 +18536,7 @@ var require_react_reconciler_development = __commonJS({
18536
18536
  case HostPortal: {
18537
18537
  break;
18538
18538
  }
18539
- case Profiler: {
18539
+ case Profiler2: {
18540
18540
  {
18541
18541
  var _finishedWork$memoize2 = finishedWork.memoizedProps, onCommit = _finishedWork$memoize2.onCommit, onRender = _finishedWork$memoize2.onRender;
18542
18542
  var effectDuration = finishedWork.stateNode.effectDuration;
@@ -18562,7 +18562,7 @@ var require_react_reconciler_development = __commonJS({
18562
18562
  var root = parentFiber.stateNode;
18563
18563
  root.effectDuration += effectDuration;
18564
18564
  break outer;
18565
- case Profiler:
18565
+ case Profiler2:
18566
18566
  var parentStateNode = parentFiber.stateNode;
18567
18567
  parentStateNode.effectDuration += effectDuration;
18568
18568
  break outer;
@@ -22319,7 +22319,7 @@ var require_react_reconciler_development = __commonJS({
22319
22319
  error('Profiler must specify an "id" of type `string` as a prop. Received the type `%s` instead.', typeof pendingProps.id);
22320
22320
  }
22321
22321
  }
22322
- var fiber = createFiber(Profiler, pendingProps, key, mode | ProfileMode);
22322
+ var fiber = createFiber(Profiler2, pendingProps, key, mode | ProfileMode);
22323
22323
  fiber.elementType = REACT_PROFILER_TYPE;
22324
22324
  fiber.lanes = lanes;
22325
22325
  {
@@ -40058,6 +40058,582 @@ var require_cli_spinners = __commonJS({
40058
40058
  }
40059
40059
  });
40060
40060
 
40061
+ // src/profiling/profiler.ts
40062
+ var DEFAULT_THRESHOLDS = {
40063
+ slowPhaseMs: 100,
40064
+ slowNetworkMs: 500,
40065
+ memorySpikeMB: 50
40066
+ };
40067
+ function getProfileLevel() {
40068
+ const level = process.env.XCSH_PROFILE_LEVEL;
40069
+ if (level === "basic" || level === "detailed" || level === "full") {
40070
+ return level;
40071
+ }
40072
+ if (process.env.XCSH_PROFILE === "true") {
40073
+ return "basic";
40074
+ }
40075
+ return "none";
40076
+ }
40077
+ var Profiler = class {
40078
+ config;
40079
+ spans = /* @__PURE__ */ new Map();
40080
+ networkSpans = /* @__PURE__ */ new Map();
40081
+ memorySnapshots = [];
40082
+ startTime;
40083
+ spanCounter = 0;
40084
+ rootSpanId = null;
40085
+ constructor(config) {
40086
+ const level = getProfileLevel();
40087
+ this.config = {
40088
+ enabled: level !== "none",
40089
+ level,
40090
+ thresholds: {
40091
+ ...DEFAULT_THRESHOLDS,
40092
+ ...config?.thresholds
40093
+ }
40094
+ };
40095
+ this.startTime = Date.now();
40096
+ if (this.isEnabled()) {
40097
+ this.memorySnapshot("profiler_init");
40098
+ }
40099
+ }
40100
+ /**
40101
+ * Check if profiling is enabled
40102
+ */
40103
+ isEnabled() {
40104
+ return this.config.enabled;
40105
+ }
40106
+ /**
40107
+ * Get current profiling level
40108
+ */
40109
+ getLevel() {
40110
+ return this.config.level;
40111
+ }
40112
+ /**
40113
+ * Get elapsed time since profiler start
40114
+ */
40115
+ elapsed() {
40116
+ return Date.now() - this.startTime;
40117
+ }
40118
+ /**
40119
+ * Generate unique span ID
40120
+ */
40121
+ generateSpanId() {
40122
+ return `span_${++this.spanCounter}`;
40123
+ }
40124
+ /**
40125
+ * Start a new profiling span
40126
+ */
40127
+ startSpan(name, label, parentId) {
40128
+ if (!this.isEnabled()) return "";
40129
+ const id = this.generateSpanId();
40130
+ const span = {
40131
+ id,
40132
+ name,
40133
+ label: label ?? name,
40134
+ parentId: parentId ?? void 0,
40135
+ startTime: this.elapsed(),
40136
+ endTime: void 0,
40137
+ duration: void 0,
40138
+ metadata: {},
40139
+ isNetwork: void 0,
40140
+ children: []
40141
+ };
40142
+ this.spans.set(id, span);
40143
+ if (!parentId && !this.rootSpanId) {
40144
+ this.rootSpanId = id;
40145
+ }
40146
+ if (parentId) {
40147
+ const parent = this.spans.get(parentId);
40148
+ if (parent) {
40149
+ parent.children.push(span);
40150
+ }
40151
+ }
40152
+ if (this.config.level === "basic") {
40153
+ console.error(`[PROFILE] ${name}:start: ${span.startTime}ms`);
40154
+ }
40155
+ return id;
40156
+ }
40157
+ /**
40158
+ * End a profiling span
40159
+ */
40160
+ endSpan(spanId, metadata) {
40161
+ if (!this.isEnabled() || !spanId) return;
40162
+ const span = this.spans.get(spanId);
40163
+ if (!span) return;
40164
+ span.endTime = this.elapsed();
40165
+ span.duration = span.endTime - span.startTime;
40166
+ if (metadata) {
40167
+ span.metadata = { ...span.metadata, ...metadata };
40168
+ }
40169
+ if (this.config.level === "basic") {
40170
+ console.error(
40171
+ `[PROFILE] ${span.name}:end: ${span.endTime}ms (${span.duration}ms)`
40172
+ );
40173
+ }
40174
+ }
40175
+ /**
40176
+ * Create a checkpoint within current context (simple timing marker)
40177
+ */
40178
+ checkpoint(name) {
40179
+ if (!this.isEnabled()) return;
40180
+ const elapsed = this.elapsed();
40181
+ if (this.config.level === "basic") {
40182
+ console.error(`[PROFILE] ${name}: ${elapsed}ms`);
40183
+ }
40184
+ }
40185
+ /**
40186
+ * Start a network span with additional request details
40187
+ */
40188
+ startNetworkSpan(url, method, parentId) {
40189
+ if (!this.isEnabled()) return "";
40190
+ const id = this.generateSpanId();
40191
+ const span = {
40192
+ id,
40193
+ name: `network:${method}`,
40194
+ label: `${method} ${this.truncateUrl(url)}`,
40195
+ parentId: parentId ?? void 0,
40196
+ startTime: this.elapsed(),
40197
+ endTime: void 0,
40198
+ duration: void 0,
40199
+ metadata: {},
40200
+ children: [],
40201
+ isNetwork: true,
40202
+ url,
40203
+ method,
40204
+ statusCode: void 0,
40205
+ requestSize: void 0,
40206
+ responseSize: void 0,
40207
+ retryCount: 0,
40208
+ error: void 0
40209
+ };
40210
+ this.networkSpans.set(id, span);
40211
+ this.spans.set(id, span);
40212
+ if (parentId) {
40213
+ const parent = this.spans.get(parentId);
40214
+ if (parent) {
40215
+ parent.children.push(span);
40216
+ }
40217
+ }
40218
+ if (this.config.level !== "none") {
40219
+ console.error(
40220
+ `[PROFILE] network:${method}:start: ${span.startTime}ms - ${this.truncateUrl(url)}`
40221
+ );
40222
+ }
40223
+ return id;
40224
+ }
40225
+ /**
40226
+ * End a network span with response details
40227
+ */
40228
+ endNetworkSpan(spanId, details) {
40229
+ if (!this.isEnabled() || !spanId) return;
40230
+ const span = this.networkSpans.get(spanId);
40231
+ if (!span) return;
40232
+ span.endTime = this.elapsed();
40233
+ span.duration = span.endTime - span.startTime;
40234
+ span.statusCode = details.statusCode;
40235
+ span.responseSize = details.responseSize;
40236
+ span.retryCount = details.retryCount ?? 0;
40237
+ span.error = details.error;
40238
+ if (this.config.level !== "none") {
40239
+ const status = details.error ? `ERROR: ${details.error}` : `${details.statusCode ?? "?"}`;
40240
+ console.error(
40241
+ `[PROFILE] network:end: ${span.endTime}ms (${span.duration}ms) - ${status}`
40242
+ );
40243
+ }
40244
+ }
40245
+ /**
40246
+ * Take a memory snapshot
40247
+ */
40248
+ memorySnapshot(label) {
40249
+ if (!this.isEnabled()) return;
40250
+ if (this.config.level === "basic") return;
40251
+ const memory = process.memoryUsage();
40252
+ const snapshot = {
40253
+ timestamp: this.elapsed(),
40254
+ label,
40255
+ heapUsedMB: Math.round(memory.heapUsed / 1024 / 1024 * 100) / 100,
40256
+ heapTotalMB: Math.round(memory.heapTotal / 1024 / 1024 * 100) / 100,
40257
+ externalMB: Math.round(memory.external / 1024 / 1024 * 100) / 100,
40258
+ rssMB: Math.round(memory.rss / 1024 / 1024 * 100) / 100
40259
+ };
40260
+ this.memorySnapshots.push(snapshot);
40261
+ if (this.config.level === "detailed" || this.config.level === "full") {
40262
+ console.error(
40263
+ `[PROFILE] memory:${label}: heap=${snapshot.heapUsedMB}MB rss=${snapshot.rssMB}MB`
40264
+ );
40265
+ }
40266
+ }
40267
+ /**
40268
+ * Identify bottlenecks based on configured thresholds
40269
+ */
40270
+ identifyBottlenecks() {
40271
+ const bottlenecks = [];
40272
+ for (const span of this.spans.values()) {
40273
+ if (!span.duration) continue;
40274
+ const isNetwork = this.networkSpans.has(span.id);
40275
+ const threshold = isNetwork ? this.config.thresholds.slowNetworkMs : this.config.thresholds.slowPhaseMs;
40276
+ if (span.duration > threshold) {
40277
+ const suggestion = isNetwork ? "Check network connectivity or API server status" : "Consider lazy loading or parallelizing this operation";
40278
+ bottlenecks.push({
40279
+ spanId: span.id,
40280
+ spanName: span.name,
40281
+ duration: span.duration,
40282
+ threshold,
40283
+ reason: `Duration ${span.duration}ms exceeds threshold ${threshold}ms`,
40284
+ suggestion
40285
+ });
40286
+ }
40287
+ }
40288
+ if (this.memorySnapshots.length >= 2) {
40289
+ const first = this.memorySnapshots[0];
40290
+ const peak = Math.max(...this.memorySnapshots.map((s) => s.rssMB));
40291
+ const spike = peak - (first?.rssMB ?? 0);
40292
+ if (spike > this.config.thresholds.memorySpikeMB) {
40293
+ bottlenecks.push({
40294
+ spanId: "memory",
40295
+ spanName: "memory_spike",
40296
+ duration: spike,
40297
+ threshold: this.config.thresholds.memorySpikeMB,
40298
+ reason: `Memory increased by ${spike.toFixed(1)}MB`,
40299
+ suggestion: "Review large object allocations during startup"
40300
+ });
40301
+ }
40302
+ }
40303
+ return bottlenecks.sort((a, b) => b.duration - a.duration);
40304
+ }
40305
+ /**
40306
+ * Get all root-level spans (no parent)
40307
+ */
40308
+ getRootSpans() {
40309
+ return Array.from(this.spans.values()).filter((span) => !span.parentId);
40310
+ }
40311
+ /**
40312
+ * Generate complete profile report
40313
+ */
40314
+ generateReport() {
40315
+ const spans = this.getRootSpans();
40316
+ const networkSpans = Array.from(this.networkSpans.values());
40317
+ const bottlenecks = this.identifyBottlenecks();
40318
+ const memoryStart = this.memorySnapshots[0]?.rssMB ?? 0;
40319
+ const memoryEnd = this.memorySnapshots[this.memorySnapshots.length - 1]?.rssMB ?? 0;
40320
+ const memoryPeak = Math.max(
40321
+ ...this.memorySnapshots.map((s) => s.rssMB),
40322
+ 0
40323
+ );
40324
+ return {
40325
+ startupTimeMs: this.elapsed(),
40326
+ spans,
40327
+ networkSpans,
40328
+ memorySnapshots: this.memorySnapshots,
40329
+ memoryPeakMB: memoryPeak,
40330
+ memoryDeltaMB: memoryEnd - memoryStart,
40331
+ bottlenecks,
40332
+ level: this.config.level
40333
+ };
40334
+ }
40335
+ /**
40336
+ * Truncate URL for display
40337
+ */
40338
+ truncateUrl(url) {
40339
+ try {
40340
+ const parsed = new URL(url);
40341
+ return parsed.pathname.slice(0, 40) + (parsed.pathname.length > 40 ? "..." : "");
40342
+ } catch {
40343
+ return url.slice(0, 40);
40344
+ }
40345
+ }
40346
+ };
40347
+ var globalProfiler = null;
40348
+ function getProfiler() {
40349
+ if (!globalProfiler) {
40350
+ globalProfiler = new Profiler();
40351
+ }
40352
+ return globalProfiler;
40353
+ }
40354
+ function initProfiler(config) {
40355
+ globalProfiler = new Profiler(config);
40356
+ return globalProfiler;
40357
+ }
40358
+
40359
+ // src/profiling/reporter.ts
40360
+ function formatDuration(ms) {
40361
+ if (ms < 1e3) {
40362
+ return `${Math.round(ms)}ms`;
40363
+ }
40364
+ return `${(ms / 1e3).toFixed(2)}s`;
40365
+ }
40366
+ function formatSpanText(span, indent = 0, totalTime) {
40367
+ const lines = [];
40368
+ const prefix = " ".repeat(indent);
40369
+ const duration = span.duration ?? 0;
40370
+ const percentage = totalTime > 0 ? (duration / totalTime * 100).toFixed(1) : "0";
40371
+ const networkTag = span.isNetwork ? " [N]" : "";
40372
+ const warning = duration > 500 ? " !!" : duration > 100 ? " !" : "";
40373
+ lines.push(
40374
+ `${prefix}${span.label}${networkTag}: ${formatDuration(duration)} (${percentage}%)${warning}`
40375
+ );
40376
+ if (Object.keys(span.metadata).length > 0) {
40377
+ const metaStr = Object.entries(span.metadata).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(", ");
40378
+ lines.push(`${prefix} metadata: ${metaStr}`);
40379
+ }
40380
+ for (const child of span.children) {
40381
+ lines.push(...formatSpanText(child, indent + 1, totalTime));
40382
+ }
40383
+ return lines;
40384
+ }
40385
+ function formatBottlenecksText(bottlenecks) {
40386
+ if (bottlenecks.length === 0) {
40387
+ return [" No bottlenecks detected"];
40388
+ }
40389
+ const lines = [];
40390
+ for (let i = 0; i < bottlenecks.length; i++) {
40391
+ const b = bottlenecks[i];
40392
+ if (!b) continue;
40393
+ lines.push(` ${i + 1}. ${b.spanName}: ${formatDuration(b.duration)}`);
40394
+ lines.push(` Threshold: ${formatDuration(b.threshold)}`);
40395
+ lines.push(` Suggestion: ${b.suggestion}`);
40396
+ }
40397
+ return lines;
40398
+ }
40399
+ function formatMemoryText(snapshots) {
40400
+ if (snapshots.length === 0) {
40401
+ return [" No memory snapshots"];
40402
+ }
40403
+ const first = snapshots[0];
40404
+ const last = snapshots[snapshots.length - 1];
40405
+ const peak = Math.max(...snapshots.map((s) => s.rssMB));
40406
+ const lines = [];
40407
+ lines.push(
40408
+ ` Start: ${first?.rssMB.toFixed(1)}MB -> Peak: ${peak.toFixed(1)}MB -> End: ${last?.rssMB.toFixed(1)}MB`
40409
+ );
40410
+ lines.push(
40411
+ ` Delta: ${((last?.rssMB ?? 0) - (first?.rssMB ?? 0)).toFixed(1)}MB`
40412
+ );
40413
+ return lines;
40414
+ }
40415
+ function formatReportText(report) {
40416
+ const lines = [];
40417
+ lines.push("=".repeat(60));
40418
+ lines.push(`XCSH Startup Profile Report`);
40419
+ lines.push(`Total Time: ${formatDuration(report.startupTimeMs)}`);
40420
+ lines.push(`Profile Level: ${report.level}`);
40421
+ lines.push("=".repeat(60));
40422
+ lines.push("");
40423
+ lines.push("PHASES:");
40424
+ lines.push("-".repeat(40));
40425
+ for (const span of report.spans) {
40426
+ lines.push(...formatSpanText(span, 0, report.startupTimeMs));
40427
+ }
40428
+ if (report.networkSpans.length > 0) {
40429
+ lines.push("");
40430
+ lines.push("NETWORK CALLS:");
40431
+ lines.push("-".repeat(40));
40432
+ for (const span of report.networkSpans) {
40433
+ const status = span.error ? `ERROR: ${span.error}` : `${span.statusCode ?? "?"}`;
40434
+ const retries = span.retryCount > 0 ? ` (${span.retryCount} retries)` : "";
40435
+ lines.push(
40436
+ ` ${span.method} ${span.url.slice(0, 50)}: ${formatDuration(span.duration ?? 0)} - ${status}${retries}`
40437
+ );
40438
+ }
40439
+ }
40440
+ if (report.memorySnapshots.length > 0) {
40441
+ lines.push("");
40442
+ lines.push("MEMORY:");
40443
+ lines.push("-".repeat(40));
40444
+ lines.push(...formatMemoryText(report.memorySnapshots));
40445
+ }
40446
+ if (report.bottlenecks.length > 0) {
40447
+ lines.push("");
40448
+ lines.push("BOTTLENECKS:");
40449
+ lines.push("-".repeat(40));
40450
+ lines.push(...formatBottlenecksText(report.bottlenecks));
40451
+ }
40452
+ lines.push("");
40453
+ lines.push("=".repeat(60));
40454
+ lines.push(
40455
+ "[N] = Network call !! = Critical (>500ms) ! = Warning (>100ms)"
40456
+ );
40457
+ lines.push("=".repeat(60));
40458
+ return lines.join("\n");
40459
+ }
40460
+
40461
+ // src/profiling/waterfall.ts
40462
+ var BOX_CHARS = {
40463
+ topLeft: "\u250C",
40464
+ topRight: "\u2510",
40465
+ bottomLeft: "\u2514",
40466
+ bottomRight: "\u2518",
40467
+ horizontal: "\u2500",
40468
+ vertical: "\u2502",
40469
+ cross: "\u253C",
40470
+ teeDown: "\u252C",
40471
+ teeUp: "\u2534",
40472
+ teeRight: "\u251C",
40473
+ teeLeft: "\u2524"
40474
+ };
40475
+ var TREE_CHARS = {
40476
+ branch: "\u251C",
40477
+ last: "\u2514",
40478
+ vertical: "\u2502",
40479
+ horizontal: "\u2500"
40480
+ };
40481
+ function flattenSpans(spans, depth = 0, bottleneckIds) {
40482
+ const result = [];
40483
+ for (let i = 0; i < spans.length; i++) {
40484
+ const span = spans[i];
40485
+ if (!span) continue;
40486
+ const isLast = i === spans.length - 1;
40487
+ result.push({
40488
+ name: span.name,
40489
+ label: span.label,
40490
+ startTime: span.startTime,
40491
+ duration: span.duration ?? 0,
40492
+ depth,
40493
+ isNetwork: span.isNetwork ?? false,
40494
+ isBottleneck: bottleneckIds.has(span.id),
40495
+ isLast
40496
+ });
40497
+ if (span.children.length > 0) {
40498
+ result.push(
40499
+ ...flattenSpans(span.children, depth + 1, bottleneckIds)
40500
+ );
40501
+ }
40502
+ }
40503
+ return result;
40504
+ }
40505
+ function createTreePrefix(depth, isLast) {
40506
+ if (depth === 0) return "";
40507
+ const indent = " ".repeat(depth - 1);
40508
+ const branch = isLast ? TREE_CHARS.last : TREE_CHARS.branch;
40509
+ return indent + branch + TREE_CHARS.horizontal;
40510
+ }
40511
+ function renderBar(startTime, duration, totalTime, barWidth) {
40512
+ if (totalTime === 0) return " ".repeat(barWidth);
40513
+ const startPos = Math.floor(startTime / totalTime * barWidth);
40514
+ const barLen = Math.max(1, Math.ceil(duration / totalTime * barWidth));
40515
+ const before = " ".repeat(Math.min(startPos, barWidth));
40516
+ const bar = "\u2588".repeat(Math.min(barLen, barWidth - startPos));
40517
+ const after = " ".repeat(Math.max(0, barWidth - startPos - barLen));
40518
+ return (before + bar + after).slice(0, barWidth);
40519
+ }
40520
+ function formatDuration2(ms) {
40521
+ if (ms < 1e3) {
40522
+ return `${Math.round(ms)}ms`;
40523
+ }
40524
+ return `${(ms / 1e3).toFixed(1)}s`;
40525
+ }
40526
+ function renderWaterfall(report) {
40527
+ const lines = [];
40528
+ const totalTime = report.startupTimeMs;
40529
+ const labelWidth = 28;
40530
+ const barWidth = 35;
40531
+ const timeWidth = 10;
40532
+ const totalWidth = labelWidth + barWidth + timeWidth + 6;
40533
+ const bottleneckIds = new Set(report.bottlenecks.map((b) => b.spanId));
40534
+ const flatSpans = flattenSpans(report.spans, 0, bottleneckIds);
40535
+ lines.push(
40536
+ BOX_CHARS.topLeft + BOX_CHARS.horizontal.repeat(totalWidth - 2) + BOX_CHARS.topRight
40537
+ );
40538
+ lines.push(
40539
+ BOX_CHARS.vertical + ` XCSH Startup Profile`.padEnd(totalWidth - 14) + `Total: ${formatDuration2(totalTime)}`.padStart(12) + BOX_CHARS.vertical
40540
+ );
40541
+ lines.push(
40542
+ BOX_CHARS.teeRight + BOX_CHARS.horizontal.repeat(totalWidth - 2) + BOX_CHARS.teeLeft
40543
+ );
40544
+ const headerPhase = "Phase".padEnd(labelWidth);
40545
+ const headerTimeline = "Timeline".padEnd(barWidth);
40546
+ const headerTime = "Time".padStart(timeWidth);
40547
+ lines.push(
40548
+ BOX_CHARS.vertical + ` ${headerPhase}${BOX_CHARS.vertical} ${headerTimeline}${BOX_CHARS.vertical}${headerTime} ` + BOX_CHARS.vertical
40549
+ );
40550
+ lines.push(
40551
+ BOX_CHARS.teeRight + BOX_CHARS.horizontal.repeat(labelWidth + 1) + BOX_CHARS.cross + BOX_CHARS.horizontal.repeat(barWidth + 1) + BOX_CHARS.cross + BOX_CHARS.horizontal.repeat(timeWidth + 1) + BOX_CHARS.teeLeft
40552
+ );
40553
+ for (const span of flatSpans) {
40554
+ const prefix = createTreePrefix(span.depth, span.isLast);
40555
+ const networkTag = span.isNetwork ? " [N]" : "";
40556
+ const label = (prefix + span.label + networkTag).slice(0, labelWidth).padEnd(labelWidth);
40557
+ const bar = renderBar(
40558
+ span.startTime,
40559
+ span.duration,
40560
+ totalTime,
40561
+ barWidth
40562
+ );
40563
+ const time = formatDuration2(span.duration).padStart(timeWidth - 2);
40564
+ const warning = span.isBottleneck ? " !!" : span.duration > 100 ? " !" : " ";
40565
+ lines.push(
40566
+ BOX_CHARS.vertical + ` ${label}${BOX_CHARS.vertical} ${bar}${BOX_CHARS.vertical}${time}${warning}` + BOX_CHARS.vertical
40567
+ );
40568
+ }
40569
+ lines.push(
40570
+ BOX_CHARS.bottomLeft + BOX_CHARS.horizontal.repeat(totalWidth - 2) + BOX_CHARS.bottomRight
40571
+ );
40572
+ lines.push(
40573
+ "[N] = Network call !! = Bottleneck (>500ms) ! = Slow (>100ms)"
40574
+ );
40575
+ if (report.memorySnapshots.length > 0) {
40576
+ const first = report.memorySnapshots[0];
40577
+ const last = report.memorySnapshots[report.memorySnapshots.length - 1];
40578
+ lines.push("");
40579
+ lines.push(
40580
+ `Memory: ${first?.rssMB.toFixed(1)}MB -> Peak ${report.memoryPeakMB.toFixed(1)}MB -> ${last?.rssMB.toFixed(1)}MB (${report.memoryDeltaMB >= 0 ? "+" : ""}${report.memoryDeltaMB.toFixed(1)}MB)`
40581
+ );
40582
+ }
40583
+ if (report.bottlenecks.length > 0) {
40584
+ lines.push("");
40585
+ lines.push("Bottlenecks Identified:");
40586
+ for (let i = 0; i < Math.min(report.bottlenecks.length, 3); i++) {
40587
+ const b = report.bottlenecks[i];
40588
+ if (!b) continue;
40589
+ lines.push(
40590
+ ` ${i + 1}. ${b.spanName}: ${formatDuration2(b.duration)} - ${b.suggestion}`
40591
+ );
40592
+ }
40593
+ }
40594
+ return lines.join("\n");
40595
+ }
40596
+ function renderCompactWaterfall(report) {
40597
+ const lines = [];
40598
+ const totalTime = report.startupTimeMs;
40599
+ lines.push(`
40600
+ [PROFILE] Startup: ${formatDuration2(totalTime)}`);
40601
+ for (const span of report.spans) {
40602
+ const duration = span.duration ?? 0;
40603
+ const percentage = (duration / totalTime * 100).toFixed(0);
40604
+ const bar = "\u2588".repeat(
40605
+ Math.min(20, Math.ceil(duration / totalTime * 20))
40606
+ );
40607
+ lines.push(
40608
+ ` ${span.label.padEnd(20)} ${bar.padEnd(20)} ${formatDuration2(duration).padStart(8)} (${percentage}%)`
40609
+ );
40610
+ }
40611
+ return lines.join("\n");
40612
+ }
40613
+
40614
+ // src/profiling/index.ts
40615
+ function printProfileReport() {
40616
+ const profiler2 = getProfiler();
40617
+ if (!profiler2.isEnabled()) return;
40618
+ const report = profiler2.generateReport();
40619
+ const level = profiler2.getLevel();
40620
+ let output;
40621
+ switch (level) {
40622
+ case "full":
40623
+ output = renderWaterfall(report);
40624
+ break;
40625
+ case "detailed":
40626
+ output = formatReportText(report);
40627
+ break;
40628
+ case "basic":
40629
+ output = renderCompactWaterfall(report);
40630
+ break;
40631
+ default:
40632
+ return;
40633
+ }
40634
+ console.error("\n" + output);
40635
+ }
40636
+
40061
40637
  // node_modules/ink/build/render.js
40062
40638
  import { Stream } from "stream";
40063
40639
  import process12 from "process";
@@ -48639,8 +49215,8 @@ function getLogoModeFromEnv(envPrefix) {
48639
49215
  var CLI_NAME = "xcsh";
48640
49216
  var CLI_FULL_NAME = "F5 Distributed Cloud Shell";
48641
49217
  function getVersion() {
48642
- if ("v2.0.21-2601100223") {
48643
- return "v2.0.21-2601100223";
49218
+ if ("v2.0.21-2601100619") {
49219
+ return "v2.0.21-2601100619";
48644
49220
  }
48645
49221
  if (process.env.XCSH_VERSION) {
48646
49222
  return process.env.XCSH_VERSION;
@@ -49040,6 +49616,9 @@ var DEFAULT_RETRY_CONFIG = {
49040
49616
  backoffMultiplier: 2,
49041
49617
  jitter: true
49042
49618
  };
49619
+ var STARTUP_TIMEOUT = 3e3;
49620
+ var STARTUP_MAX_RETRIES = 0;
49621
+ var CONNECTIVITY_TIMEOUT = 2e3;
49043
49622
  var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([
49044
49623
  408,
49045
49624
  // Request Timeout
@@ -49095,18 +49674,33 @@ var APIClient = class {
49095
49674
  /**
49096
49675
  * Validate the API token by making a lightweight API call.
49097
49676
  * Returns true if token is valid, false otherwise.
49677
+ *
49678
+ * @param options.startupMode - Use aggressive timeouts for fast-fail during startup
49098
49679
  */
49099
- async validateToken() {
49680
+ async validateToken(options) {
49681
+ const startupMode = options?.startupMode ?? false;
49100
49682
  if (!this.apiToken) {
49101
49683
  this._isValidated = false;
49102
49684
  this._validationError = "No API token configured";
49103
49685
  return { valid: false, error: this._validationError };
49104
49686
  }
49105
49687
  if (this.debug) {
49106
- console.error(`DEBUG: Validating token against ${this.serverUrl}`);
49688
+ console.error(
49689
+ `DEBUG: Validating token against ${this.serverUrl}${startupMode ? " (startup mode)" : ""}`
49690
+ );
49107
49691
  }
49108
49692
  try {
49109
- await this.get("/api/web/namespaces");
49693
+ const requestOptions = {
49694
+ method: "GET",
49695
+ path: "/api/web/namespaces"
49696
+ };
49697
+ if (startupMode) {
49698
+ requestOptions.timeout = STARTUP_TIMEOUT;
49699
+ }
49700
+ await this.requestWithOptions(
49701
+ requestOptions,
49702
+ startupMode ? { maxRetries: STARTUP_MAX_RETRIES } : void 0
49703
+ );
49110
49704
  this._isValidated = true;
49111
49705
  this._validationError = null;
49112
49706
  return { valid: true };
@@ -49120,6 +49714,20 @@ var APIClient = class {
49120
49714
  this._isValidated = false;
49121
49715
  this._validationError = "Token lacks required permissions";
49122
49716
  return { valid: false, error: this._validationError };
49717
+ } else if (error.statusCode === 408 || error.statusCode === 0) {
49718
+ if (startupMode) {
49719
+ this._isValidated = false;
49720
+ this._validationError = "API endpoint unreachable - request timed out";
49721
+ return { valid: false, error: this._validationError };
49722
+ }
49723
+ if (this.debug) {
49724
+ console.error(
49725
+ `DEBUG: Validation timed out, assuming token is valid`
49726
+ );
49727
+ }
49728
+ this._isValidated = true;
49729
+ this._validationError = null;
49730
+ return { valid: true };
49123
49731
  } else {
49124
49732
  if (this.debug) {
49125
49733
  console.error(
@@ -49131,6 +49739,11 @@ var APIClient = class {
49131
49739
  return { valid: true };
49132
49740
  }
49133
49741
  } else {
49742
+ if (startupMode) {
49743
+ this._isValidated = false;
49744
+ this._validationError = `API endpoint unreachable - ${error instanceof Error ? error.message : "Unknown error"}`;
49745
+ return { valid: false, error: this._validationError };
49746
+ }
49134
49747
  if (this.debug) {
49135
49748
  console.error(
49136
49749
  `DEBUG: Validation error: ${error instanceof Error ? error.message : "Unknown"}, assuming token is valid`
@@ -49167,6 +49780,30 @@ var APIClient = class {
49167
49780
  getServerUrl() {
49168
49781
  return this.serverUrl;
49169
49782
  }
49783
+ /**
49784
+ * Check connectivity to the API server.
49785
+ * Uses a lightweight HEAD request with aggressive timeout for fast-fail.
49786
+ * This is used during startup to quickly detect if API is unreachable.
49787
+ */
49788
+ async checkConnectivity() {
49789
+ const start = Date.now();
49790
+ const controller = new AbortController();
49791
+ const timeoutId = setTimeout(
49792
+ () => controller.abort(),
49793
+ CONNECTIVITY_TIMEOUT
49794
+ );
49795
+ try {
49796
+ await fetch(this.serverUrl, {
49797
+ method: "HEAD",
49798
+ signal: controller.signal
49799
+ });
49800
+ clearTimeout(timeoutId);
49801
+ return { reachable: true, latencyMs: Date.now() - start };
49802
+ } catch {
49803
+ clearTimeout(timeoutId);
49804
+ return { reachable: false };
49805
+ }
49806
+ }
49170
49807
  /**
49171
49808
  * Build full URL from path and query parameters
49172
49809
  */
@@ -49271,11 +49908,25 @@ var APIClient = class {
49271
49908
  throw error;
49272
49909
  }
49273
49910
  }
49911
+ /**
49912
+ * Execute an HTTP request with retry logic and optional retry override
49913
+ */
49914
+ async requestWithOptions(options, retryOverride) {
49915
+ return this.requestInternal(options, retryOverride);
49916
+ }
49274
49917
  /**
49275
49918
  * Execute an HTTP request with retry logic
49276
49919
  */
49277
49920
  async request(options) {
49921
+ return this.requestInternal(options);
49922
+ }
49923
+ /**
49924
+ * Internal request implementation with optional retry override
49925
+ */
49926
+ async requestInternal(options, retryOverride) {
49278
49927
  const url = this.buildUrl(options.path, options.query);
49928
+ const profiler2 = getProfiler();
49929
+ const networkSpan = profiler2.startNetworkSpan(url, options.method);
49279
49930
  const headers = {
49280
49931
  "Content-Type": "application/json",
49281
49932
  Accept: "application/json",
@@ -49292,19 +49943,28 @@ var APIClient = class {
49292
49943
  }
49293
49944
  }
49294
49945
  let lastError;
49295
- for (let attempt = 0; attempt <= this.retryConfig.maxRetries; attempt++) {
49946
+ let retryCount = 0;
49947
+ const maxRetries = retryOverride?.maxRetries ?? this.retryConfig.maxRetries;
49948
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
49296
49949
  try {
49297
- return await this.executeRequest(
49950
+ const result = await this.executeRequest(
49298
49951
  options,
49299
49952
  url,
49300
49953
  headers,
49301
49954
  body
49302
49955
  );
49956
+ profiler2.endNetworkSpan(networkSpan, {
49957
+ statusCode: result.statusCode,
49958
+ responseSize: JSON.stringify(result.data).length,
49959
+ retryCount
49960
+ });
49961
+ return result;
49303
49962
  } catch (error) {
49304
49963
  lastError = error instanceof Error ? error : new Error(String(error));
49305
49964
  const isRetryable = this.isRetryableError(error);
49306
- const hasRetriesLeft = attempt < this.retryConfig.maxRetries;
49965
+ const hasRetriesLeft = attempt < maxRetries;
49307
49966
  if (isRetryable && hasRetriesLeft) {
49967
+ retryCount++;
49308
49968
  const delay = calculateBackoffDelay(
49309
49969
  attempt,
49310
49970
  this.retryConfig
@@ -49312,12 +49972,17 @@ var APIClient = class {
49312
49972
  if (this.debug) {
49313
49973
  const statusInfo = error instanceof APIError ? ` (${error.statusCode})` : "";
49314
49974
  console.error(
49315
- `DEBUG: Request failed${statusInfo}, retrying in ${delay}ms (attempt ${attempt + 1}/${this.retryConfig.maxRetries})`
49975
+ `DEBUG: Request failed${statusInfo}, retrying in ${delay}ms (attempt ${attempt + 1}/${maxRetries})`
49316
49976
  );
49317
49977
  }
49318
49978
  await sleep(delay);
49319
49979
  continue;
49320
49980
  }
49981
+ profiler2.endNetworkSpan(networkSpan, {
49982
+ statusCode: error instanceof APIError ? error.statusCode : 0,
49983
+ retryCount,
49984
+ error: lastError.message
49985
+ });
49321
49986
  throw error;
49322
49987
  }
49323
49988
  }
@@ -75275,6 +75940,8 @@ var REPLSession = class {
75275
75940
  // Fallback tracking for improved error messages
75276
75941
  _fallbackAttempted = false;
75277
75942
  _fallbackReason = null;
75943
+ // Connection status for graceful offline handling
75944
+ _connectionStatus = "unknown";
75278
75945
  constructor(config = {}) {
75279
75946
  this._namespace = config.namespace ?? this.getDefaultNamespace();
75280
75947
  this._contextPath = new ContextPath();
@@ -75304,34 +75971,91 @@ var REPLSession = class {
75304
75971
  }
75305
75972
  /**
75306
75973
  * Initialize the session (async operations)
75974
+ * Uses fast-fail connectivity check to avoid long waits when API is unreachable.
75307
75975
  */
75308
75976
  async initialize() {
75977
+ const profiler2 = getProfiler();
75978
+ const historySpan = profiler2.startSpan(
75979
+ "history_load",
75980
+ "History Loading"
75981
+ );
75309
75982
  try {
75310
75983
  this._history = await HistoryManager.create(
75311
75984
  getHistoryFilePath(),
75312
75985
  1e3
75313
75986
  );
75987
+ profiler2.endSpan(historySpan, { status: "loaded" });
75314
75988
  } catch (error) {
75315
75989
  console.error("Warning: could not initialize history:", error);
75316
75990
  this._history = new HistoryManager(getHistoryFilePath(), 1e3);
75991
+ profiler2.endSpan(historySpan, {
75992
+ status: "fallback",
75993
+ error: String(error)
75994
+ });
75317
75995
  }
75996
+ const profileSpan = profiler2.startSpan(
75997
+ "profile_load",
75998
+ "Profile Loading"
75999
+ );
75318
76000
  await this.loadActiveProfile();
76001
+ profiler2.endSpan(profileSpan, { profileName: this._activeProfileName });
75319
76002
  if (this._apiClient?.isAuthenticated()) {
76003
+ const connectivitySpan = profiler2.startSpan(
76004
+ "connectivity_check",
76005
+ "Connectivity Check"
76006
+ );
76007
+ const connectivity = await this._apiClient.checkConnectivity();
76008
+ profiler2.endSpan(connectivitySpan, {
76009
+ reachable: connectivity.reachable,
76010
+ latencyMs: connectivity.latencyMs
76011
+ });
76012
+ if (!connectivity.reachable) {
76013
+ this._connectionStatus = "offline";
76014
+ this._validationError = "API endpoint unreachable - running in offline mode";
76015
+ debugProtocol.auth("connectivity_failed", {
76016
+ serverUrl: this._serverUrl,
76017
+ status: "offline"
76018
+ });
76019
+ return;
76020
+ }
76021
+ const tokenSpan = profiler2.startSpan(
76022
+ "token_validation",
76023
+ "Token Validation"
76024
+ );
75320
76025
  debugProtocol.auth("token_validation_start", {
75321
76026
  serverUrl: this._serverUrl,
75322
76027
  hasApiClient: true,
75323
- authSource: this._authSource
76028
+ authSource: this._authSource,
76029
+ startupMode: true
76030
+ });
76031
+ const result = await this._apiClient.validateToken({
76032
+ startupMode: true
75324
76033
  });
75325
- const result = await this._apiClient.validateToken();
75326
76034
  this._tokenValidated = result.valid;
75327
76035
  this._validationError = result.error ?? null;
76036
+ if (result.valid) {
76037
+ this._connectionStatus = "connected";
76038
+ } else if (result.error?.includes("unreachable") || result.error?.includes("timed out")) {
76039
+ this._connectionStatus = "offline";
76040
+ } else {
76041
+ this._connectionStatus = "error";
76042
+ }
76043
+ profiler2.endSpan(tokenSpan, {
76044
+ valid: result.valid,
76045
+ error: result.error,
76046
+ connectionStatus: this._connectionStatus
76047
+ });
75328
76048
  debugProtocol.auth("token_validation_complete", {
75329
76049
  valid: result.valid,
75330
76050
  error: result.error,
75331
76051
  tokenValidated: this._tokenValidated,
75332
76052
  validationError: this._validationError,
75333
- authSource: this._authSource
76053
+ authSource: this._authSource,
76054
+ connectionStatus: this._connectionStatus
75334
76055
  });
76056
+ if (this._connectionStatus === "offline") {
76057
+ return;
76058
+ }
75335
76059
  if (!result.valid && (this._authSource === "env" || this._authSource === "mixed")) {
75336
76060
  if (this._activeProfile?.apiToken && this._activeProfile.apiToken !== this._apiToken) {
75337
76061
  this._fallbackAttempted = true;
@@ -75352,18 +76076,29 @@ var REPLSession = class {
75352
76076
  apiToken: this._apiToken,
75353
76077
  debug: this._debug
75354
76078
  });
75355
- const fallbackResult = await this._apiClient.validateToken();
76079
+ const fallbackSpan = profiler2.startSpan(
76080
+ "fallback_validation",
76081
+ "Fallback Token Validation"
76082
+ );
76083
+ const fallbackResult = await this._apiClient.validateToken({
76084
+ startupMode: true
76085
+ });
76086
+ profiler2.endSpan(fallbackSpan, {
76087
+ valid: fallbackResult.valid
76088
+ });
75356
76089
  if (fallbackResult.valid) {
75357
76090
  this._tokenValidated = true;
75358
76091
  this._validationError = null;
75359
76092
  this._authSource = "profile-fallback";
75360
76093
  this._fallbackReason = null;
76094
+ this._connectionStatus = "connected";
75361
76095
  debugProtocol.auth("token_fallback_success", {
75362
76096
  authSource: this._authSource,
75363
76097
  profileName: this._activeProfileName
75364
76098
  });
75365
76099
  } else {
75366
76100
  this._fallbackReason = `Profile '${this._activeProfileName}' credentials are also invalid`;
76101
+ this._connectionStatus = "error";
75367
76102
  debugProtocol.auth("token_fallback_failed", {
75368
76103
  error: fallbackResult.error,
75369
76104
  profileName: this._activeProfileName
@@ -75387,8 +76122,13 @@ var REPLSession = class {
75387
76122
  });
75388
76123
  }
75389
76124
  }
75390
- if (this._tokenValidated) {
76125
+ if (this._tokenValidated && this._connectionStatus === "connected") {
76126
+ const userInfoSpan = profiler2.startSpan(
76127
+ "user_info",
76128
+ "User Info Fetch"
76129
+ );
75391
76130
  await this.fetchUserInfo();
76131
+ profiler2.endSpan(userInfoSpan, { username: this._username });
75392
76132
  }
75393
76133
  }
75394
76134
  }
@@ -75594,6 +76334,18 @@ var REPLSession = class {
75594
76334
  getFallbackReason() {
75595
76335
  return this._fallbackReason;
75596
76336
  }
76337
+ /**
76338
+ * Get the current connection status
76339
+ */
76340
+ getConnectionStatus() {
76341
+ return this._connectionStatus;
76342
+ }
76343
+ /**
76344
+ * Check if running in offline mode (API unreachable)
76345
+ */
76346
+ isOfflineMode() {
76347
+ return this._connectionStatus === "offline";
76348
+ }
75597
76349
  /**
75598
76350
  * Get the API client
75599
76351
  */
@@ -85472,7 +86224,26 @@ var HeadlessController = class {
85472
86224
 
85473
86225
  // src/index.tsx
85474
86226
  var import_jsx_runtime6 = __toESM(require_jsx_runtime(), 1);
86227
+ var profiler = initProfiler();
86228
+ var importsSpan = profiler.startSpan("imports", "Module Imports");
86229
+ profiler.memorySnapshot("script_start");
86230
+ profiler.checkpoint("import:ink");
86231
+ profiler.checkpoint("import:commander");
86232
+ profiler.checkpoint("import:repl");
86233
+ profiler.checkpoint("import:branding");
86234
+ profiler.checkpoint("import:executor");
86235
+ profiler.checkpoint("import:session");
86236
+ profiler.checkpoint("import:help");
86237
+ profiler.checkpoint("import:config");
86238
+ profiler.checkpoint("import:output-types");
86239
+ profiler.checkpoint("import:banner");
86240
+ profiler.checkpoint("import:debug");
86241
+ profiler.checkpoint("import:spec");
86242
+ profiler.checkpoint("import:headless");
86243
+ profiler.endSpan(importsSpan);
86244
+ profiler.memorySnapshot("imports_complete");
85475
86245
  var program2 = new Command();
86246
+ profiler.checkpoint("commander:created");
85476
86247
  program2.configureHelp({
85477
86248
  formatHelp: () => formatRootHelp().join("\n")
85478
86249
  });
@@ -85518,12 +86289,28 @@ program2.name(CLI_NAME).description("F5 Distributed Cloud Shell - Interactive CL
85518
86289
  }
85519
86290
  const cliLogoMode = options.logo && isValidLogoMode(options.logo) ? options.logo : void 0;
85520
86291
  process.stdout.write("Initializing...");
86292
+ const sessionInitSpan = profiler.startSpan(
86293
+ "session_init",
86294
+ "Session Initialization"
86295
+ );
85521
86296
  const session = new REPLSession();
85522
86297
  await session.initialize();
86298
+ profiler.endSpan(sessionInitSpan);
85523
86299
  debugProtocol.session("init", { mode: "repl" });
85524
86300
  emitSessionState(session);
85525
86301
  process.stdout.write("\r\x1B[K");
86302
+ profiler.memorySnapshot("pre_banner");
86303
+ printProfileReport();
85526
86304
  renderBanner(cliLogoMode, "startup");
86305
+ if (session.isOfflineMode()) {
86306
+ console.log("");
86307
+ console.log(
86308
+ `${colors.yellow}\u26A0\uFE0F Offline Mode: API endpoint unreachable${colors.reset}`
86309
+ );
86310
+ console.log(
86311
+ `${colors.dim} Commands requiring API access will fail. Check network and try again.${colors.reset}`
86312
+ );
86313
+ }
85527
86314
  if (session.getAuthSource() === "profile-fallback") {
85528
86315
  const profileName = session.getActiveProfileName();
85529
86316
  console.log("");
@@ -85588,13 +86375,27 @@ program2.name(CLI_NAME).description("F5 Distributed Cloud Shell - Interactive CL
85588
86375
  }
85589
86376
  );
85590
86377
  async function executeNonInteractive(args) {
86378
+ const sessionInitSpan = profiler.startSpan(
86379
+ "session_init",
86380
+ "Session Initialization"
86381
+ );
85591
86382
  const session = new REPLSession();
85592
86383
  await session.initialize();
86384
+ profiler.endSpan(sessionInitSpan);
86385
+ printProfileReport();
85593
86386
  debugProtocol.session("init", {
85594
86387
  mode: "non-interactive",
85595
86388
  command: args.join(" ")
85596
86389
  });
85597
86390
  emitSessionState(session);
86391
+ if (session.isOfflineMode()) {
86392
+ console.error(
86393
+ `${colors.yellow}\u26A0\uFE0F Offline Mode: API endpoint unreachable${colors.reset}`
86394
+ );
86395
+ console.error(
86396
+ `${colors.dim} Commands requiring API access will fail.${colors.reset}`
86397
+ );
86398
+ }
85598
86399
  if (session.getAuthSource() === "profile-fallback") {
85599
86400
  const profileName = session.getActiveProfileName();
85600
86401
  console.error(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robinmordasiewicz/f5xc-xcsh",
3
- "version": "2.0.21-2601100223",
3
+ "version": "2.0.21-2601100619",
4
4
  "description": "F5 Distributed Cloud Shell - Interactive CLI for F5 XC",
5
5
  "type": "module",
6
6
  "bin": {
@@ -25,8 +25,13 @@
25
25
  "build:binaries": "./scripts/build-binaries.sh",
26
26
  "build:release": "npm run build && npm run build:binaries",
27
27
  "dev": "tsx src/index.tsx",
28
+ "dev:profile": "XCSH_PROFILE_LEVEL=detailed tsx src/index.tsx",
29
+ "dev:profile:full": "XCSH_PROFILE_LEVEL=full tsx src/index.tsx",
28
30
  "build:dev": "npm run build && ${HOME}/.bun/bin/bun build dist/index.js --compile --outfile ./xcsh",
31
+ "build:dev:debug": "tsup --sourcemap && bun build dist/index.js --compile --outfile ./xcsh-debug",
29
32
  "start": "node dist/index.js",
33
+ "start:profile": "XCSH_PROFILE_LEVEL=detailed node dist/index.js",
34
+ "start:profile:full": "XCSH_PROFILE_LEVEL=full node dist/index.js",
30
35
  "test": "vitest",
31
36
  "test:coverage": "vitest --coverage",
32
37
  "lint": "eslint src",