@artemiskit/cli 0.2.2 → 0.2.3

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.
package/dist/index.js CHANGED
@@ -41618,6 +41618,136 @@ function nanoid(size = 21) {
41618
41618
  }
41619
41619
  return id;
41620
41620
  }
41621
+ function getModelPricing(model) {
41622
+ if (MODEL_PRICING[model]) {
41623
+ return MODEL_PRICING[model];
41624
+ }
41625
+ const lowerModel = model.toLowerCase();
41626
+ for (const [key, pricing] of Object.entries(MODEL_PRICING)) {
41627
+ if (key.toLowerCase() === lowerModel) {
41628
+ return pricing;
41629
+ }
41630
+ }
41631
+ if (lowerModel.includes("gpt-5.2")) {
41632
+ return MODEL_PRICING["gpt-5.2"];
41633
+ }
41634
+ if (lowerModel.includes("gpt-5.1")) {
41635
+ return MODEL_PRICING["gpt-5.1"];
41636
+ }
41637
+ if (lowerModel.includes("gpt-5-mini")) {
41638
+ return MODEL_PRICING["gpt-5-mini"];
41639
+ }
41640
+ if (lowerModel.includes("gpt-5-nano")) {
41641
+ return MODEL_PRICING["gpt-5-nano"];
41642
+ }
41643
+ if (lowerModel.includes("gpt-5")) {
41644
+ return MODEL_PRICING["gpt-5"];
41645
+ }
41646
+ if (lowerModel.includes("gpt-4.1-mini")) {
41647
+ return MODEL_PRICING["gpt-4.1-mini"];
41648
+ }
41649
+ if (lowerModel.includes("gpt-4.1-nano")) {
41650
+ return MODEL_PRICING["gpt-4.1-nano"];
41651
+ }
41652
+ if (lowerModel.includes("gpt-4.1")) {
41653
+ return MODEL_PRICING["gpt-4.1"];
41654
+ }
41655
+ if (lowerModel.includes("gpt-4o-mini")) {
41656
+ return MODEL_PRICING["gpt-4o-mini"];
41657
+ }
41658
+ if (lowerModel.includes("gpt-4o")) {
41659
+ return MODEL_PRICING["gpt-4o"];
41660
+ }
41661
+ if (lowerModel.includes("o4-mini")) {
41662
+ return MODEL_PRICING["o4-mini"];
41663
+ }
41664
+ if (lowerModel.includes("o3-mini")) {
41665
+ return MODEL_PRICING["o3-mini"];
41666
+ }
41667
+ if (lowerModel.includes("o3")) {
41668
+ return MODEL_PRICING.o3;
41669
+ }
41670
+ if (lowerModel.includes("o1")) {
41671
+ return MODEL_PRICING.o1;
41672
+ }
41673
+ if (lowerModel.includes("gpt-4-turbo")) {
41674
+ return MODEL_PRICING["gpt-4-turbo"];
41675
+ }
41676
+ if (lowerModel.includes("gpt-4")) {
41677
+ return MODEL_PRICING["gpt-4"];
41678
+ }
41679
+ if (lowerModel.includes("gpt-3.5")) {
41680
+ return MODEL_PRICING["gpt-3.5-turbo"];
41681
+ }
41682
+ if (lowerModel.includes("opus-4.5") || lowerModel.includes("opus-4-5")) {
41683
+ return MODEL_PRICING["claude-opus-4.5"];
41684
+ }
41685
+ if (lowerModel.includes("sonnet-4.5") || lowerModel.includes("sonnet-4-5")) {
41686
+ return MODEL_PRICING["claude-sonnet-4.5"];
41687
+ }
41688
+ if (lowerModel.includes("haiku-4.5") || lowerModel.includes("haiku-4-5")) {
41689
+ return MODEL_PRICING["claude-haiku-4.5"];
41690
+ }
41691
+ if (lowerModel.includes("opus-4.1") || lowerModel.includes("opus-4-1")) {
41692
+ return MODEL_PRICING["claude-opus-4.1"];
41693
+ }
41694
+ if (lowerModel.includes("opus-4")) {
41695
+ return MODEL_PRICING["claude-opus-4"];
41696
+ }
41697
+ if (lowerModel.includes("sonnet-4")) {
41698
+ return MODEL_PRICING["claude-sonnet-4"];
41699
+ }
41700
+ if (lowerModel.includes("sonnet-3.7") || lowerModel.includes("sonnet-3-7")) {
41701
+ return MODEL_PRICING["claude-sonnet-3.7"];
41702
+ }
41703
+ if (lowerModel.includes("claude-3-5-sonnet") || lowerModel.includes("claude-3.5-sonnet")) {
41704
+ return MODEL_PRICING["claude-3.5-sonnet"];
41705
+ }
41706
+ if (lowerModel.includes("claude-3-5-haiku") || lowerModel.includes("claude-3.5-haiku")) {
41707
+ return MODEL_PRICING["claude-3.5-haiku"];
41708
+ }
41709
+ if (lowerModel.includes("claude-3-opus")) {
41710
+ return MODEL_PRICING["claude-3-opus"];
41711
+ }
41712
+ if (lowerModel.includes("claude-3-sonnet")) {
41713
+ return MODEL_PRICING["claude-3-sonnet"];
41714
+ }
41715
+ if (lowerModel.includes("claude-3-haiku")) {
41716
+ return MODEL_PRICING["claude-3-haiku"];
41717
+ }
41718
+ if (lowerModel.includes("claude")) {
41719
+ return MODEL_PRICING["claude-sonnet-4.5"];
41720
+ }
41721
+ return DEFAULT_PRICING;
41722
+ }
41723
+ function estimateCost(promptTokens, completionTokens, model) {
41724
+ const pricing = getModelPricing(model);
41725
+ const promptCostUsd = promptTokens / 1000 * pricing.promptPer1K;
41726
+ const completionCostUsd = completionTokens / 1000 * pricing.completionPer1K;
41727
+ const totalUsd = promptCostUsd + completionCostUsd;
41728
+ return {
41729
+ totalUsd,
41730
+ promptCostUsd,
41731
+ completionCostUsd,
41732
+ model,
41733
+ pricing
41734
+ };
41735
+ }
41736
+ function formatCost(costUsd) {
41737
+ if (costUsd < 0.01) {
41738
+ return `$${(costUsd * 100).toFixed(4)} cents`;
41739
+ }
41740
+ if (costUsd < 1) {
41741
+ return `$${costUsd.toFixed(4)}`;
41742
+ }
41743
+ return `$${costUsd.toFixed(2)}`;
41744
+ }
41745
+ function listKnownModels() {
41746
+ return Object.entries(MODEL_PRICING).map(([model, pricing]) => ({
41747
+ model,
41748
+ pricing
41749
+ }));
41750
+ }
41621
41751
  function getEnvironmentInfo() {
41622
41752
  return {
41623
41753
  node_version: process.version,
@@ -41670,7 +41800,8 @@ function createRunManifest(options) {
41670
41800
  runReason,
41671
41801
  redaction
41672
41802
  } = options;
41673
- const metrics = calculateMetrics(cases);
41803
+ const modelForCost = resolvedConfig?.model || config.model;
41804
+ const metrics = calculateMetrics(cases, modelForCost);
41674
41805
  const git = getGitInfo();
41675
41806
  const environment = getEnvironmentInfo();
41676
41807
  return {
@@ -41694,7 +41825,7 @@ function createRunManifest(options) {
41694
41825
  redaction
41695
41826
  };
41696
41827
  }
41697
- function calculateMetrics(cases) {
41828
+ function calculateMetrics(cases, model) {
41698
41829
  const passedCases = cases.filter((c2) => c2.ok);
41699
41830
  const latencies = cases.map((c2) => c2.latencyMs).sort((a, b) => a - b);
41700
41831
  const medianLatency = latencies.length > 0 ? latencies[Math.floor(latencies.length / 2)] : 0;
@@ -41702,6 +41833,21 @@ function calculateMetrics(cases) {
41702
41833
  const p95Latency = latencies.length > 0 ? latencies[p95Index] : 0;
41703
41834
  const totalPromptTokens = cases.reduce((sum, c2) => sum + c2.tokens.prompt, 0);
41704
41835
  const totalCompletionTokens = cases.reduce((sum, c2) => sum + c2.tokens.completion, 0);
41836
+ let cost;
41837
+ if (model && (totalPromptTokens > 0 || totalCompletionTokens > 0)) {
41838
+ const costEstimate = estimateCost(totalPromptTokens, totalCompletionTokens, model);
41839
+ const pricing = getModelPricing(model);
41840
+ cost = {
41841
+ total_usd: costEstimate.totalUsd,
41842
+ prompt_cost_usd: costEstimate.promptCostUsd,
41843
+ completion_cost_usd: costEstimate.completionCostUsd,
41844
+ model: costEstimate.model,
41845
+ pricing: {
41846
+ prompt_per_1k: pricing.promptPer1K,
41847
+ completion_per_1k: pricing.completionPer1K
41848
+ }
41849
+ };
41850
+ }
41705
41851
  return {
41706
41852
  success_rate: cases.length > 0 ? passedCases.length / cases.length : 0,
41707
41853
  total_cases: cases.length,
@@ -41711,7 +41857,8 @@ function calculateMetrics(cases) {
41711
41857
  p95_latency_ms: p95Latency,
41712
41858
  total_tokens: totalPromptTokens + totalCompletionTokens,
41713
41859
  total_prompt_tokens: totalPromptTokens,
41714
- total_completion_tokens: totalCompletionTokens
41860
+ total_completion_tokens: totalCompletionTokens,
41861
+ cost
41715
41862
  };
41716
41863
  }
41717
41864
  function detectCIEnvironment() {
@@ -41877,6 +42024,16 @@ function getSuccessRate(manifest) {
41877
42024
  }
41878
42025
  return manifest.metrics.success_rate;
41879
42026
  }
42027
+ function getEstimatedCost(manifest) {
42028
+ const type = getManifestType(manifest);
42029
+ if (type === "stress") {
42030
+ return manifest.metrics.cost?.estimated_total_usd;
42031
+ }
42032
+ if (type === "run") {
42033
+ return manifest.metrics.cost?.total_usd;
42034
+ }
42035
+ return;
42036
+ }
41880
42037
  function getScenario(manifest) {
41881
42038
  return manifest.config.scenario;
41882
42039
  }
@@ -41946,13 +42103,17 @@ class LocalStorageAdapter {
41946
42103
  if (options?.scenario && getScenario(manifest) !== options.scenario) {
41947
42104
  continue;
41948
42105
  }
41949
- results.push({
42106
+ const item = {
41950
42107
  runId: manifest.run_id,
41951
42108
  scenario: getScenario(manifest),
41952
42109
  successRate: getSuccessRate(manifest),
41953
42110
  createdAt: manifest.start_time,
41954
42111
  type: manifestType
41955
- });
42112
+ };
42113
+ if (options?.includeCost) {
42114
+ item.estimatedCostUsd = getEstimatedCost(manifest);
42115
+ }
42116
+ results.push(item);
41956
42117
  } catch {}
41957
42118
  }
41958
42119
  }
@@ -48430,136 +48591,6 @@ class Logger {
48430
48591
  return childLogger;
48431
48592
  }
48432
48593
  }
48433
- function getModelPricing(model) {
48434
- if (MODEL_PRICING[model]) {
48435
- return MODEL_PRICING[model];
48436
- }
48437
- const lowerModel = model.toLowerCase();
48438
- for (const [key, pricing] of Object.entries(MODEL_PRICING)) {
48439
- if (key.toLowerCase() === lowerModel) {
48440
- return pricing;
48441
- }
48442
- }
48443
- if (lowerModel.includes("gpt-5.2")) {
48444
- return MODEL_PRICING["gpt-5.2"];
48445
- }
48446
- if (lowerModel.includes("gpt-5.1")) {
48447
- return MODEL_PRICING["gpt-5.1"];
48448
- }
48449
- if (lowerModel.includes("gpt-5-mini")) {
48450
- return MODEL_PRICING["gpt-5-mini"];
48451
- }
48452
- if (lowerModel.includes("gpt-5-nano")) {
48453
- return MODEL_PRICING["gpt-5-nano"];
48454
- }
48455
- if (lowerModel.includes("gpt-5")) {
48456
- return MODEL_PRICING["gpt-5"];
48457
- }
48458
- if (lowerModel.includes("gpt-4.1-mini")) {
48459
- return MODEL_PRICING["gpt-4.1-mini"];
48460
- }
48461
- if (lowerModel.includes("gpt-4.1-nano")) {
48462
- return MODEL_PRICING["gpt-4.1-nano"];
48463
- }
48464
- if (lowerModel.includes("gpt-4.1")) {
48465
- return MODEL_PRICING["gpt-4.1"];
48466
- }
48467
- if (lowerModel.includes("gpt-4o-mini")) {
48468
- return MODEL_PRICING["gpt-4o-mini"];
48469
- }
48470
- if (lowerModel.includes("gpt-4o")) {
48471
- return MODEL_PRICING["gpt-4o"];
48472
- }
48473
- if (lowerModel.includes("o4-mini")) {
48474
- return MODEL_PRICING["o4-mini"];
48475
- }
48476
- if (lowerModel.includes("o3-mini")) {
48477
- return MODEL_PRICING["o3-mini"];
48478
- }
48479
- if (lowerModel.includes("o3")) {
48480
- return MODEL_PRICING["o3"];
48481
- }
48482
- if (lowerModel.includes("o1")) {
48483
- return MODEL_PRICING["o1"];
48484
- }
48485
- if (lowerModel.includes("gpt-4-turbo")) {
48486
- return MODEL_PRICING["gpt-4-turbo"];
48487
- }
48488
- if (lowerModel.includes("gpt-4")) {
48489
- return MODEL_PRICING["gpt-4"];
48490
- }
48491
- if (lowerModel.includes("gpt-3.5")) {
48492
- return MODEL_PRICING["gpt-3.5-turbo"];
48493
- }
48494
- if (lowerModel.includes("opus-4.5") || lowerModel.includes("opus-4-5")) {
48495
- return MODEL_PRICING["claude-opus-4.5"];
48496
- }
48497
- if (lowerModel.includes("sonnet-4.5") || lowerModel.includes("sonnet-4-5")) {
48498
- return MODEL_PRICING["claude-sonnet-4.5"];
48499
- }
48500
- if (lowerModel.includes("haiku-4.5") || lowerModel.includes("haiku-4-5")) {
48501
- return MODEL_PRICING["claude-haiku-4.5"];
48502
- }
48503
- if (lowerModel.includes("opus-4.1") || lowerModel.includes("opus-4-1")) {
48504
- return MODEL_PRICING["claude-opus-4.1"];
48505
- }
48506
- if (lowerModel.includes("opus-4")) {
48507
- return MODEL_PRICING["claude-opus-4"];
48508
- }
48509
- if (lowerModel.includes("sonnet-4")) {
48510
- return MODEL_PRICING["claude-sonnet-4"];
48511
- }
48512
- if (lowerModel.includes("sonnet-3.7") || lowerModel.includes("sonnet-3-7")) {
48513
- return MODEL_PRICING["claude-sonnet-3.7"];
48514
- }
48515
- if (lowerModel.includes("claude-3-5-sonnet") || lowerModel.includes("claude-3.5-sonnet")) {
48516
- return MODEL_PRICING["claude-3.5-sonnet"];
48517
- }
48518
- if (lowerModel.includes("claude-3-5-haiku") || lowerModel.includes("claude-3.5-haiku")) {
48519
- return MODEL_PRICING["claude-3.5-haiku"];
48520
- }
48521
- if (lowerModel.includes("claude-3-opus")) {
48522
- return MODEL_PRICING["claude-3-opus"];
48523
- }
48524
- if (lowerModel.includes("claude-3-sonnet")) {
48525
- return MODEL_PRICING["claude-3-sonnet"];
48526
- }
48527
- if (lowerModel.includes("claude-3-haiku")) {
48528
- return MODEL_PRICING["claude-3-haiku"];
48529
- }
48530
- if (lowerModel.includes("claude")) {
48531
- return MODEL_PRICING["claude-sonnet-4.5"];
48532
- }
48533
- return DEFAULT_PRICING;
48534
- }
48535
- function estimateCost(promptTokens, completionTokens, model) {
48536
- const pricing = getModelPricing(model);
48537
- const promptCostUsd = promptTokens / 1000 * pricing.promptPer1K;
48538
- const completionCostUsd = completionTokens / 1000 * pricing.completionPer1K;
48539
- const totalUsd = promptCostUsd + completionCostUsd;
48540
- return {
48541
- totalUsd,
48542
- promptCostUsd,
48543
- completionCostUsd,
48544
- model,
48545
- pricing
48546
- };
48547
- }
48548
- function formatCost(costUsd) {
48549
- if (costUsd < 0.01) {
48550
- return `$${(costUsd * 100).toFixed(4)} cents`;
48551
- }
48552
- if (costUsd < 1) {
48553
- return `$${costUsd.toFixed(4)}`;
48554
- }
48555
- return `$${costUsd.toFixed(2)}`;
48556
- }
48557
- function listKnownModels() {
48558
- return Object.entries(MODEL_PRICING).map(([model, pricing]) => ({
48559
- model,
48560
- pricing
48561
- }));
48562
- }
48563
48594
  var __create2, __getProtoOf2, __defProp2, __getOwnPropNames2, __hasOwnProp2, __toESM2 = (mod, isNodeMode, target) => {
48564
48595
  target = mod != null ? __create2(__getProtoOf2(mod)) : {};
48565
48596
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp2(target, "default", { value: mod, enumerable: true }) : target;
@@ -49151,7 +49182,7 @@ ${e.cyan(d)}
49151
49182
  `;
49152
49183
  }
49153
49184
  } }).prompt();
49154
- }, kCancel, init_prompt2, ArtemisError, adapterRegistry, initialized = false, BUILTIN_PATTERNS, BUILTIN_REGEX_PATTERNS, DEFAULT_REDACTION_PATTERNS, RedactionConfigSchema, ProviderSchema, ProviderConfigSchema2, BaseExpectedSchema, CombinedExpectedSchema, ExpectedSchema, ChatMessageSchema, VariablesSchema, RedactionConfigSchema2, TestCaseSchema, ScenarioSchema, composer2, Document2, Schema2, errors22, Alias2, identity2, Pair2, Scalar2, YAMLMap2, YAMLSeq2, cst2, lexer2, lineCounter2, parser2, publicApi2, visit2, $Composer2, $Document2, $Schema2, $YAMLError2, $YAMLParseError2, $YAMLWarning2, $Alias2, $isAlias2, $isCollection2, $isDocument2, $isMap2, $isNode2, $isPair2, $isScalar2, $isSeq2, $Pair2, $Scalar2, $YAMLMap2, $YAMLSeq2, $Lexer2, $LineCounter2, $Parser2, $parse2, $parseAllDocuments2, $parseDocument2, $stringify2, $visit2, $visitAsync2, DEFAULT_EXTENSIONS, DEFAULT_MAX_DEPTH = 10, DEFAULT_EXCLUDE, urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict", POOL_SIZE_MULTIPLIER = 128, pool, poolOffset, import_tslib, __extends, __assign, __rest, __decorate, __param, __esDecorate, __runInitializers, __propKey, __setFunctionName, __metadata, __awaiter, __generator, __exportStar, __createBinding, __values, __read, __spread, __spreadArrays, __spreadArray, __await, __asyncGenerator, __asyncDelegator, __asyncValues, __makeTemplateObject, __importStar, __importDefault, __classPrivateFieldGet, __classPrivateFieldSet, __classPrivateFieldIn, __addDisposableResource, __disposeResources, __rewriteRelativeImportExtension, resolveFetch = (customFetch) => {
49185
+ }, kCancel, init_prompt2, ArtemisError, adapterRegistry, initialized = false, BUILTIN_PATTERNS, BUILTIN_REGEX_PATTERNS, DEFAULT_REDACTION_PATTERNS, RedactionConfigSchema, ProviderSchema, ProviderConfigSchema2, BaseExpectedSchema, CombinedExpectedSchema, ExpectedSchema, ChatMessageSchema, VariablesSchema, RedactionConfigSchema2, TestCaseSchema, ScenarioSchema, composer2, Document2, Schema2, errors22, Alias2, identity2, Pair2, Scalar2, YAMLMap2, YAMLSeq2, cst2, lexer2, lineCounter2, parser2, publicApi2, visit2, $Composer2, $Document2, $Schema2, $YAMLError2, $YAMLParseError2, $YAMLWarning2, $Alias2, $isAlias2, $isCollection2, $isDocument2, $isMap2, $isNode2, $isPair2, $isScalar2, $isSeq2, $Pair2, $Scalar2, $YAMLMap2, $YAMLSeq2, $Lexer2, $LineCounter2, $Parser2, $parse2, $parseAllDocuments2, $parseDocument2, $stringify2, $visit2, $visitAsync2, DEFAULT_EXTENSIONS, DEFAULT_MAX_DEPTH = 10, DEFAULT_EXCLUDE, urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict", POOL_SIZE_MULTIPLIER = 128, pool, poolOffset, MODEL_PRICING, DEFAULT_PRICING, import_tslib, __extends, __assign, __rest, __decorate, __param, __esDecorate, __runInitializers, __propKey, __setFunctionName, __metadata, __awaiter, __generator, __exportStar, __createBinding, __values, __read, __spread, __spreadArrays, __spreadArray, __await, __asyncGenerator, __asyncDelegator, __asyncValues, __makeTemplateObject, __importStar, __importDefault, __classPrivateFieldGet, __classPrivateFieldSet, __classPrivateFieldIn, __addDisposableResource, __disposeResources, __rewriteRelativeImportExtension, resolveFetch = (customFetch) => {
49155
49186
  if (customFetch) {
49156
49187
  return (...args) => customFetch(...args);
49157
49188
  }
@@ -51120,7 +51151,7 @@ ${cause.stack}`;
51120
51151
  return new SupabaseClient(supabaseUrl, supabaseKey, options);
51121
51152
  }, LogLevels, LogTypes, defu, paused = false, queue, bracket = (x2) => x2 ? `[${x2}]` : "", env2, argv, platform, isDisabled, isForced, isWindows, isDumbTerminal, isCompatibleTerminal, isCI, isColorSupported, colorDefs, colors13, ansiRegex2, boxStylePresets, defaultStyle, r2, i = (e2) => globalThis.process?.env || import.meta.env || globalThis.Deno?.env.toObject() || globalThis.__env__ || (e2 ? r2 : globalThis), o2, t, f2, l, I2, T2, a, g2, R2, A2, C2, y2, _22, c2, O2, D, L2, S2, u2, N2, F2, P2, regex2, emojiRegex22 = () => {
51122
51153
  return /[#*0-9]\uFE0F?\u20E3|[\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23ED-\u23EF\u23F1\u23F2\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB\u25FC\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692\u2694-\u2697\u2699\u269B\u269C\u26A0\u26A7\u26AA\u26B0\u26B1\u26BD\u26BE\u26C4\u26C8\u26CF\u26D1\u26E9\u26F0-\u26F5\u26F7\u26F8\u26FA\u2702\u2708\u2709\u270F\u2712\u2714\u2716\u271D\u2721\u2733\u2734\u2744\u2747\u2757\u2763\u27A1\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B55\u3030\u303D\u3297\u3299]\uFE0F?|[\u261D\u270C\u270D](?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?|[\u270A\u270B](?:\uD83C[\uDFFB-\uDFFF])?|[\u23E9-\u23EC\u23F0\u23F3\u25FD\u2693\u26A1\u26AB\u26C5\u26CE\u26D4\u26EA\u26FD\u2705\u2728\u274C\u274E\u2753-\u2755\u2795-\u2797\u27B0\u27BF\u2B50]|\u26D3\uFE0F?(?:\u200D\uD83D\uDCA5)?|\u26F9(?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?(?:\u200D[\u2640\u2642]\uFE0F?)?|\u2764\uFE0F?(?:\u200D(?:\uD83D\uDD25|\uD83E\uDE79))?|\uD83C(?:[\uDC04\uDD70\uDD71\uDD7E\uDD7F\uDE02\uDE37\uDF21\uDF24-\uDF2C\uDF36\uDF7D\uDF96\uDF97\uDF99-\uDF9B\uDF9E\uDF9F\uDFCD\uDFCE\uDFD4-\uDFDF\uDFF5\uDFF7]\uFE0F?|[\uDF85\uDFC2\uDFC7](?:\uD83C[\uDFFB-\uDFFF])?|[\uDFC4\uDFCA](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDFCB\uDFCC](?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDCCF\uDD8E\uDD91-\uDD9A\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF43\uDF45-\uDF4A\uDF4C-\uDF7C\uDF7E-\uDF84\uDF86-\uDF93\uDFA0-\uDFC1\uDFC5\uDFC6\uDFC8\uDFC9\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF8-\uDFFF]|\uDDE6\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF]|\uDDE7\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF]|\uDDE8\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF7\uDDFA-\uDDFF]|\uDDE9\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF]|\uDDEA\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA]|\uDDEB\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7]|\uDDEC\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE]|\uDDED\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA]|\uDDEE\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9]|\uDDEF\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5]|\uDDF0\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF]|\uDDF1\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE]|\uDDF2\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF]|\uDDF3\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF]|\uDDF4\uD83C\uDDF2|\uDDF5\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE]|\uDDF6\uD83C\uDDE6|\uDDF7\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC]|\uDDF8\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF]|\uDDF9\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF]|\uDDFA\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF]|\uDDFB\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA]|\uDDFC\uD83C[\uDDEB\uDDF8]|\uDDFD\uD83C\uDDF0|\uDDFE\uD83C[\uDDEA\uDDF9]|\uDDFF\uD83C[\uDDE6\uDDF2\uDDFC]|\uDF44(?:\u200D\uD83D\uDFEB)?|\uDF4B(?:\u200D\uD83D\uDFE9)?|\uDFC3(?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D(?:[\u2640\u2642]\uFE0F?(?:\u200D\u27A1\uFE0F?)?|\u27A1\uFE0F?))?|\uDFF3\uFE0F?(?:\u200D(?:\u26A7\uFE0F?|\uD83C\uDF08))?|\uDFF4(?:\u200D\u2620\uFE0F?|\uDB40\uDC67\uDB40\uDC62\uDB40(?:\uDC65\uDB40\uDC6E\uDB40\uDC67|\uDC73\uDB40\uDC63\uDB40\uDC74|\uDC77\uDB40\uDC6C\uDB40\uDC73)\uDB40\uDC7F)?)|\uD83D(?:[\uDC3F\uDCFD\uDD49\uDD4A\uDD6F\uDD70\uDD73\uDD76-\uDD79\uDD87\uDD8A-\uDD8D\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA\uDECB\uDECD-\uDECF\uDEE0-\uDEE5\uDEE9\uDEF0\uDEF3]\uFE0F?|[\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC6B-\uDC6D\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDC8F\uDC91\uDCAA\uDD7A\uDD95\uDD96\uDE4C\uDE4F\uDEC0\uDECC](?:\uD83C[\uDFFB-\uDFFF])?|[\uDC6E\uDC70\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4\uDEB5](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDD74\uDD90](?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?|[\uDC00-\uDC07\uDC09-\uDC14\uDC16-\uDC25\uDC27-\uDC3A\uDC3C-\uDC3E\uDC40\uDC44\uDC45\uDC51-\uDC65\uDC6A\uDC79-\uDC7B\uDC7D-\uDC80\uDC84\uDC88-\uDC8E\uDC90\uDC92-\uDCA9\uDCAB-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDDA4\uDDFB-\uDE2D\uDE2F-\uDE34\uDE37-\uDE41\uDE43\uDE44\uDE48-\uDE4A\uDE80-\uDEA2\uDEA4-\uDEB3\uDEB7-\uDEBF\uDEC1-\uDEC5\uDED0-\uDED2\uDED5-\uDED7\uDEDC-\uDEDF\uDEEB\uDEEC\uDEF4-\uDEFC\uDFE0-\uDFEB\uDFF0]|\uDC08(?:\u200D\u2B1B)?|\uDC15(?:\u200D\uD83E\uDDBA)?|\uDC26(?:\u200D(?:\u2B1B|\uD83D\uDD25))?|\uDC3B(?:\u200D\u2744\uFE0F?)?|\uDC41\uFE0F?(?:\u200D\uD83D\uDDE8\uFE0F?)?|\uDC68(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDC68\uDC69]\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFC-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFD-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFD\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFE])))?))?|\uDC69(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?[\uDC68\uDC69]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?|\uDC69\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?))|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFC-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB\uDFFD-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB-\uDFFD\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB-\uDFFE])))?))?|\uDC6F(?:\u200D[\u2640\u2642]\uFE0F?)?|\uDD75(?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?(?:\u200D[\u2640\u2642]\uFE0F?)?|\uDE2E(?:\u200D\uD83D\uDCA8)?|\uDE35(?:\u200D\uD83D\uDCAB)?|\uDE36(?:\u200D\uD83C\uDF2B\uFE0F?)?|\uDE42(?:\u200D[\u2194\u2195]\uFE0F?)?|\uDEB6(?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D(?:[\u2640\u2642]\uFE0F?(?:\u200D\u27A1\uFE0F?)?|\u27A1\uFE0F?))?)|\uD83E(?:[\uDD0C\uDD0F\uDD18-\uDD1F\uDD30-\uDD34\uDD36\uDD77\uDDB5\uDDB6\uDDBB\uDDD2\uDDD3\uDDD5\uDEC3-\uDEC5\uDEF0\uDEF2-\uDEF8](?:\uD83C[\uDFFB-\uDFFF])?|[\uDD26\uDD35\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD\uDDCF\uDDD4\uDDD6-\uDDDD](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDDDE\uDDDF](?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDD0D\uDD0E\uDD10-\uDD17\uDD20-\uDD25\uDD27-\uDD2F\uDD3A\uDD3F-\uDD45\uDD47-\uDD76\uDD78-\uDDB4\uDDB7\uDDBA\uDDBC-\uDDCC\uDDD0\uDDE0-\uDDFF\uDE70-\uDE7C\uDE80-\uDE89\uDE8F-\uDEC2\uDEC6\uDECE-\uDEDC\uDEDF-\uDEE9]|\uDD3C(?:\u200D[\u2640\u2642]\uFE0F?|\uD83C[\uDFFB-\uDFFF])?|\uDDCE(?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D(?:[\u2640\u2642]\uFE0F?(?:\u200D\u27A1\uFE0F?)?|\u27A1\uFE0F?))?|\uDDD1(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83E\uDDD1|\uDDD1\u200D\uD83E\uDDD2(?:\u200D\uD83E\uDDD2)?|\uDDD2(?:\u200D\uD83E\uDDD2)?))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFC-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB\uDFFD-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB-\uDFFD\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB-\uDFFE]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?))?|\uDEF1(?:\uD83C(?:\uDFFB(?:\u200D\uD83E\uDEF2\uD83C[\uDFFC-\uDFFF])?|\uDFFC(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB\uDFFD-\uDFFF])?|\uDFFD(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])?|\uDFFE(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB-\uDFFD\uDFFF])?|\uDFFF(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB-\uDFFE])?))?)/g;
51123
- }, segmenter2, defaultIgnorableCodePointRegex2, TYPE_COLOR_MAP, LEVEL_COLOR_MAP, unicode, s = (c3, fallback2) => unicode ? c3 : fallback2, TYPE_ICONS, FancyReporter, consola, LOG_LEVEL_MAP, level, baseLogger, logger, MODEL_PRICING, DEFAULT_PRICING;
51154
+ }, segmenter2, defaultIgnorableCodePointRegex2, TYPE_COLOR_MAP, LEVEL_COLOR_MAP, unicode, s = (c3, fallback2) => unicode ? c3 : fallback2, TYPE_ICONS, FancyReporter, consola, LOG_LEVEL_MAP, level, baseLogger, logger;
51124
51155
  var init_dist = __esm(() => {
51125
51156
  __create2 = Object.create;
51126
51157
  __getProtoOf2 = Object.getPrototypeOf;
@@ -62116,6 +62147,187 @@ ${end.comment}` : end.comment;
62116
62147
  DEFAULT_EXCLUDE = ["node_modules", ".git", "dist", "build", "coverage"];
62117
62148
  init_evaluators();
62118
62149
  init_evaluators();
62150
+ MODEL_PRICING = {
62151
+ "gpt-5": {
62152
+ promptPer1K: 0.00125,
62153
+ completionPer1K: 0.01,
62154
+ lastUpdated: "2026-01",
62155
+ notes: "400K context window"
62156
+ },
62157
+ "gpt-5.1": {
62158
+ promptPer1K: 0.00125,
62159
+ completionPer1K: 0.01,
62160
+ lastUpdated: "2026-01"
62161
+ },
62162
+ "gpt-5.2": {
62163
+ promptPer1K: 0.00175,
62164
+ completionPer1K: 0.014,
62165
+ lastUpdated: "2026-01"
62166
+ },
62167
+ "gpt-5-mini": {
62168
+ promptPer1K: 0.00025,
62169
+ completionPer1K: 0.002,
62170
+ lastUpdated: "2026-01"
62171
+ },
62172
+ "gpt-5-nano": {
62173
+ promptPer1K: 0.00005,
62174
+ completionPer1K: 0.0004,
62175
+ lastUpdated: "2026-01"
62176
+ },
62177
+ "gpt-4.1": {
62178
+ promptPer1K: 0.002,
62179
+ completionPer1K: 0.008,
62180
+ lastUpdated: "2026-01",
62181
+ notes: "1M context window"
62182
+ },
62183
+ "gpt-4.1-mini": {
62184
+ promptPer1K: 0.0004,
62185
+ completionPer1K: 0.0016,
62186
+ lastUpdated: "2026-01"
62187
+ },
62188
+ "gpt-4.1-nano": {
62189
+ promptPer1K: 0.0001,
62190
+ completionPer1K: 0.0004,
62191
+ lastUpdated: "2026-01"
62192
+ },
62193
+ "gpt-4o": {
62194
+ promptPer1K: 0.0025,
62195
+ completionPer1K: 0.01,
62196
+ lastUpdated: "2026-01",
62197
+ notes: "128K context window"
62198
+ },
62199
+ "gpt-4o-mini": {
62200
+ promptPer1K: 0.00015,
62201
+ completionPer1K: 0.0006,
62202
+ lastUpdated: "2026-01",
62203
+ notes: "128K context window"
62204
+ },
62205
+ o1: {
62206
+ promptPer1K: 0.015,
62207
+ completionPer1K: 0.06,
62208
+ lastUpdated: "2026-01",
62209
+ notes: "Reasoning model - internal thinking tokens billed as output"
62210
+ },
62211
+ o3: {
62212
+ promptPer1K: 0.002,
62213
+ completionPer1K: 0.008,
62214
+ lastUpdated: "2026-01"
62215
+ },
62216
+ "o3-mini": {
62217
+ promptPer1K: 0.0011,
62218
+ completionPer1K: 0.0044,
62219
+ lastUpdated: "2026-01"
62220
+ },
62221
+ "o4-mini": {
62222
+ promptPer1K: 0.0011,
62223
+ completionPer1K: 0.0044,
62224
+ lastUpdated: "2026-01"
62225
+ },
62226
+ "gpt-4-turbo": {
62227
+ promptPer1K: 0.01,
62228
+ completionPer1K: 0.03,
62229
+ lastUpdated: "2026-01"
62230
+ },
62231
+ "gpt-4": {
62232
+ promptPer1K: 0.03,
62233
+ completionPer1K: 0.06,
62234
+ lastUpdated: "2026-01"
62235
+ },
62236
+ "gpt-3.5-turbo": {
62237
+ promptPer1K: 0.0005,
62238
+ completionPer1K: 0.0015,
62239
+ lastUpdated: "2026-01"
62240
+ },
62241
+ "claude-opus-4.5": {
62242
+ promptPer1K: 0.005,
62243
+ completionPer1K: 0.025,
62244
+ lastUpdated: "2026-01",
62245
+ notes: "Most capable Claude model"
62246
+ },
62247
+ "claude-sonnet-4.5": {
62248
+ promptPer1K: 0.003,
62249
+ completionPer1K: 0.015,
62250
+ lastUpdated: "2026-01",
62251
+ notes: "Balanced performance and cost"
62252
+ },
62253
+ "claude-haiku-4.5": {
62254
+ promptPer1K: 0.001,
62255
+ completionPer1K: 0.005,
62256
+ lastUpdated: "2026-01",
62257
+ notes: "Fastest Claude model"
62258
+ },
62259
+ "claude-opus-4": {
62260
+ promptPer1K: 0.015,
62261
+ completionPer1K: 0.075,
62262
+ lastUpdated: "2026-01"
62263
+ },
62264
+ "claude-opus-4.1": {
62265
+ promptPer1K: 0.015,
62266
+ completionPer1K: 0.075,
62267
+ lastUpdated: "2026-01"
62268
+ },
62269
+ "claude-sonnet-4": {
62270
+ promptPer1K: 0.003,
62271
+ completionPer1K: 0.015,
62272
+ lastUpdated: "2026-01"
62273
+ },
62274
+ "claude-sonnet-3.7": {
62275
+ promptPer1K: 0.003,
62276
+ completionPer1K: 0.015,
62277
+ lastUpdated: "2026-01"
62278
+ },
62279
+ "claude-3-7-sonnet": {
62280
+ promptPer1K: 0.003,
62281
+ completionPer1K: 0.015,
62282
+ lastUpdated: "2026-01"
62283
+ },
62284
+ "claude-3-5-sonnet-20241022": {
62285
+ promptPer1K: 0.003,
62286
+ completionPer1K: 0.015,
62287
+ lastUpdated: "2026-01"
62288
+ },
62289
+ "claude-3-5-haiku-20241022": {
62290
+ promptPer1K: 0.0008,
62291
+ completionPer1K: 0.004,
62292
+ lastUpdated: "2026-01"
62293
+ },
62294
+ "claude-haiku-3.5": {
62295
+ promptPer1K: 0.0008,
62296
+ completionPer1K: 0.004,
62297
+ lastUpdated: "2026-01"
62298
+ },
62299
+ "claude-3-opus": {
62300
+ promptPer1K: 0.015,
62301
+ completionPer1K: 0.075,
62302
+ lastUpdated: "2026-01"
62303
+ },
62304
+ "claude-3-sonnet": {
62305
+ promptPer1K: 0.003,
62306
+ completionPer1K: 0.015,
62307
+ lastUpdated: "2026-01"
62308
+ },
62309
+ "claude-3-haiku": {
62310
+ promptPer1K: 0.00025,
62311
+ completionPer1K: 0.00125,
62312
+ lastUpdated: "2026-01"
62313
+ },
62314
+ "claude-3.5-sonnet": {
62315
+ promptPer1K: 0.003,
62316
+ completionPer1K: 0.015,
62317
+ lastUpdated: "2026-01"
62318
+ },
62319
+ "claude-3.5-haiku": {
62320
+ promptPer1K: 0.0008,
62321
+ completionPer1K: 0.004,
62322
+ lastUpdated: "2026-01"
62323
+ }
62324
+ };
62325
+ DEFAULT_PRICING = {
62326
+ promptPer1K: 0.003,
62327
+ completionPer1K: 0.015,
62328
+ lastUpdated: "2026-01",
62329
+ notes: "Default pricing - verify with provider"
62330
+ };
62119
62331
  import_tslib = __toESM2(require_tslib(), 1);
62120
62332
  ({
62121
62333
  __extends,
@@ -63312,187 +63524,6 @@ ${indent}`);
63312
63524
  }
63313
63525
  });
63314
63526
  logger = new Logger("artemis");
63315
- MODEL_PRICING = {
63316
- "gpt-5": {
63317
- promptPer1K: 0.00125,
63318
- completionPer1K: 0.01,
63319
- lastUpdated: "2026-01",
63320
- notes: "400K context window"
63321
- },
63322
- "gpt-5.1": {
63323
- promptPer1K: 0.00125,
63324
- completionPer1K: 0.01,
63325
- lastUpdated: "2026-01"
63326
- },
63327
- "gpt-5.2": {
63328
- promptPer1K: 0.00175,
63329
- completionPer1K: 0.014,
63330
- lastUpdated: "2026-01"
63331
- },
63332
- "gpt-5-mini": {
63333
- promptPer1K: 0.00025,
63334
- completionPer1K: 0.002,
63335
- lastUpdated: "2026-01"
63336
- },
63337
- "gpt-5-nano": {
63338
- promptPer1K: 0.00005,
63339
- completionPer1K: 0.0004,
63340
- lastUpdated: "2026-01"
63341
- },
63342
- "gpt-4.1": {
63343
- promptPer1K: 0.002,
63344
- completionPer1K: 0.008,
63345
- lastUpdated: "2026-01",
63346
- notes: "1M context window"
63347
- },
63348
- "gpt-4.1-mini": {
63349
- promptPer1K: 0.0004,
63350
- completionPer1K: 0.0016,
63351
- lastUpdated: "2026-01"
63352
- },
63353
- "gpt-4.1-nano": {
63354
- promptPer1K: 0.0001,
63355
- completionPer1K: 0.0004,
63356
- lastUpdated: "2026-01"
63357
- },
63358
- "gpt-4o": {
63359
- promptPer1K: 0.0025,
63360
- completionPer1K: 0.01,
63361
- lastUpdated: "2026-01",
63362
- notes: "128K context window"
63363
- },
63364
- "gpt-4o-mini": {
63365
- promptPer1K: 0.00015,
63366
- completionPer1K: 0.0006,
63367
- lastUpdated: "2026-01",
63368
- notes: "128K context window"
63369
- },
63370
- o1: {
63371
- promptPer1K: 0.015,
63372
- completionPer1K: 0.06,
63373
- lastUpdated: "2026-01",
63374
- notes: "Reasoning model - internal thinking tokens billed as output"
63375
- },
63376
- o3: {
63377
- promptPer1K: 0.002,
63378
- completionPer1K: 0.008,
63379
- lastUpdated: "2026-01"
63380
- },
63381
- "o3-mini": {
63382
- promptPer1K: 0.0011,
63383
- completionPer1K: 0.0044,
63384
- lastUpdated: "2026-01"
63385
- },
63386
- "o4-mini": {
63387
- promptPer1K: 0.0011,
63388
- completionPer1K: 0.0044,
63389
- lastUpdated: "2026-01"
63390
- },
63391
- "gpt-4-turbo": {
63392
- promptPer1K: 0.01,
63393
- completionPer1K: 0.03,
63394
- lastUpdated: "2026-01"
63395
- },
63396
- "gpt-4": {
63397
- promptPer1K: 0.03,
63398
- completionPer1K: 0.06,
63399
- lastUpdated: "2026-01"
63400
- },
63401
- "gpt-3.5-turbo": {
63402
- promptPer1K: 0.0005,
63403
- completionPer1K: 0.0015,
63404
- lastUpdated: "2026-01"
63405
- },
63406
- "claude-opus-4.5": {
63407
- promptPer1K: 0.005,
63408
- completionPer1K: 0.025,
63409
- lastUpdated: "2026-01",
63410
- notes: "Most capable Claude model"
63411
- },
63412
- "claude-sonnet-4.5": {
63413
- promptPer1K: 0.003,
63414
- completionPer1K: 0.015,
63415
- lastUpdated: "2026-01",
63416
- notes: "Balanced performance and cost"
63417
- },
63418
- "claude-haiku-4.5": {
63419
- promptPer1K: 0.001,
63420
- completionPer1K: 0.005,
63421
- lastUpdated: "2026-01",
63422
- notes: "Fastest Claude model"
63423
- },
63424
- "claude-opus-4": {
63425
- promptPer1K: 0.015,
63426
- completionPer1K: 0.075,
63427
- lastUpdated: "2026-01"
63428
- },
63429
- "claude-opus-4.1": {
63430
- promptPer1K: 0.015,
63431
- completionPer1K: 0.075,
63432
- lastUpdated: "2026-01"
63433
- },
63434
- "claude-sonnet-4": {
63435
- promptPer1K: 0.003,
63436
- completionPer1K: 0.015,
63437
- lastUpdated: "2026-01"
63438
- },
63439
- "claude-sonnet-3.7": {
63440
- promptPer1K: 0.003,
63441
- completionPer1K: 0.015,
63442
- lastUpdated: "2026-01"
63443
- },
63444
- "claude-3-7-sonnet": {
63445
- promptPer1K: 0.003,
63446
- completionPer1K: 0.015,
63447
- lastUpdated: "2026-01"
63448
- },
63449
- "claude-3-5-sonnet-20241022": {
63450
- promptPer1K: 0.003,
63451
- completionPer1K: 0.015,
63452
- lastUpdated: "2026-01"
63453
- },
63454
- "claude-3-5-haiku-20241022": {
63455
- promptPer1K: 0.0008,
63456
- completionPer1K: 0.004,
63457
- lastUpdated: "2026-01"
63458
- },
63459
- "claude-haiku-3.5": {
63460
- promptPer1K: 0.0008,
63461
- completionPer1K: 0.004,
63462
- lastUpdated: "2026-01"
63463
- },
63464
- "claude-3-opus": {
63465
- promptPer1K: 0.015,
63466
- completionPer1K: 0.075,
63467
- lastUpdated: "2026-01"
63468
- },
63469
- "claude-3-sonnet": {
63470
- promptPer1K: 0.003,
63471
- completionPer1K: 0.015,
63472
- lastUpdated: "2026-01"
63473
- },
63474
- "claude-3-haiku": {
63475
- promptPer1K: 0.00025,
63476
- completionPer1K: 0.00125,
63477
- lastUpdated: "2026-01"
63478
- },
63479
- "claude-3.5-sonnet": {
63480
- promptPer1K: 0.003,
63481
- completionPer1K: 0.015,
63482
- lastUpdated: "2026-01"
63483
- },
63484
- "claude-3.5-haiku": {
63485
- promptPer1K: 0.0008,
63486
- completionPer1K: 0.004,
63487
- lastUpdated: "2026-01"
63488
- }
63489
- };
63490
- DEFAULT_PRICING = {
63491
- promptPer1K: 0.003,
63492
- completionPer1K: 0.015,
63493
- lastUpdated: "2026-01",
63494
- notes: "Default pricing - verify with provider"
63495
- };
63496
63527
  });
63497
63528
 
63498
63529
  // src/ui/prompts.ts
@@ -76358,6 +76389,269 @@ function generateCompareHTMLReport(baseline, current) {
76358
76389
  const template = import_handlebars4.default.compile(COMPARE_HTML_TEMPLATE);
76359
76390
  return template({ data });
76360
76391
  }
76392
+ function truncate2(text, maxLength) {
76393
+ if (text.length <= maxLength)
76394
+ return text;
76395
+ return `${text.slice(0, maxLength)}...`;
76396
+ }
76397
+ function formatCostMd(costUsd) {
76398
+ if (costUsd < 0.01) {
76399
+ return `$${(costUsd * 100).toFixed(4)} cents`;
76400
+ }
76401
+ if (costUsd < 1) {
76402
+ return `$${costUsd.toFixed(4)}`;
76403
+ }
76404
+ return `$${costUsd.toFixed(2)}`;
76405
+ }
76406
+ function formatDuration2(ms) {
76407
+ if (ms < 1000)
76408
+ return `${ms}ms`;
76409
+ if (ms < 60000)
76410
+ return `${(ms / 1000).toFixed(1)}s`;
76411
+ const minutes = Math.floor(ms / 60000);
76412
+ const seconds = (ms % 60000 / 1000).toFixed(0);
76413
+ return `${minutes}m ${seconds}s`;
76414
+ }
76415
+ function generateMarkdownReport(manifest, options = {}) {
76416
+ const { includeDetails = true, truncateAt = 500 } = options;
76417
+ const lines = [];
76418
+ lines.push("# ArtemisKit Test Results");
76419
+ lines.push("");
76420
+ lines.push(`**Scenario:** ${manifest.config.scenario}`);
76421
+ lines.push(`**Run ID:** ${manifest.run_id}`);
76422
+ lines.push(`**Date:** ${new Date(manifest.start_time).toISOString()}`);
76423
+ lines.push(`**Provider:** ${manifest.config.provider}${manifest.config.model ? ` (${manifest.config.model})` : ""}`);
76424
+ lines.push("");
76425
+ lines.push("---");
76426
+ lines.push("");
76427
+ lines.push("## Summary");
76428
+ lines.push("");
76429
+ lines.push("| Metric | Value |");
76430
+ lines.push("|--------|-------|");
76431
+ lines.push(`| Total Cases | ${manifest.metrics.total_cases} |`);
76432
+ lines.push(`| Passed | ${manifest.metrics.passed_cases} (${(manifest.metrics.success_rate * 100).toFixed(1)}%) |`);
76433
+ lines.push(`| Failed | ${manifest.metrics.failed_cases} |`);
76434
+ lines.push(`| Duration | ${formatDuration2(manifest.duration_ms)} |`);
76435
+ lines.push(`| Median Latency | ${manifest.metrics.median_latency_ms}ms |`);
76436
+ lines.push(`| P95 Latency | ${manifest.metrics.p95_latency_ms}ms |`);
76437
+ lines.push(`| Total Tokens | ${manifest.metrics.total_tokens.toLocaleString()} |`);
76438
+ if (manifest.metrics.cost) {
76439
+ lines.push(`| Estimated Cost | ${formatCostMd(manifest.metrics.cost.total_usd)} |`);
76440
+ }
76441
+ lines.push("");
76442
+ lines.push("---");
76443
+ lines.push("");
76444
+ lines.push("## Results by Case");
76445
+ lines.push("");
76446
+ const passed = manifest.cases.filter((c3) => c3.ok);
76447
+ lines.push(`### Passed (${passed.length})`);
76448
+ lines.push("");
76449
+ if (passed.length > 0) {
76450
+ lines.push("<details>");
76451
+ lines.push("<summary>Click to expand passed cases</summary>");
76452
+ lines.push("");
76453
+ lines.push("| Case ID | Latency | Tokens | Score |");
76454
+ lines.push("|---------|---------|--------|-------|");
76455
+ for (const c3 of passed) {
76456
+ lines.push(`| ${c3.id} | ${formatDuration2(c3.latencyMs)} | ${c3.tokens?.total || "-"} | ${(c3.score * 100).toFixed(0)}% |`);
76457
+ }
76458
+ lines.push("");
76459
+ lines.push("</details>");
76460
+ } else {
76461
+ lines.push("_No passed cases_");
76462
+ }
76463
+ lines.push("");
76464
+ const failed = manifest.cases.filter((c3) => !c3.ok);
76465
+ lines.push(`### Failed (${failed.length})`);
76466
+ lines.push("");
76467
+ if (failed.length > 0) {
76468
+ for (const c3 of failed) {
76469
+ lines.push(`#### \`${c3.id}\``);
76470
+ lines.push("");
76471
+ if (includeDetails) {
76472
+ const promptStr = typeof c3.prompt === "string" ? c3.prompt : JSON.stringify(c3.prompt, null, 2);
76473
+ lines.push("**Prompt:**");
76474
+ lines.push("```");
76475
+ lines.push(truncate2(promptStr, truncateAt));
76476
+ lines.push("```");
76477
+ lines.push("");
76478
+ lines.push("**Expected:**");
76479
+ lines.push(`- Type: \`${c3.matcherType}\``);
76480
+ lines.push("```json");
76481
+ lines.push(truncate2(JSON.stringify(c3.expected, null, 2), truncateAt));
76482
+ lines.push("```");
76483
+ lines.push("");
76484
+ lines.push("**Actual Response:**");
76485
+ lines.push("```");
76486
+ lines.push(truncate2(c3.response || "(empty)", truncateAt));
76487
+ lines.push("```");
76488
+ lines.push("");
76489
+ }
76490
+ lines.push(`**Reason:** ${c3.reason || "Unknown"}`);
76491
+ lines.push("");
76492
+ lines.push("---");
76493
+ lines.push("");
76494
+ }
76495
+ } else {
76496
+ lines.push("_No failed cases_");
76497
+ lines.push("");
76498
+ }
76499
+ if (manifest.resolved_config) {
76500
+ lines.push("## Configuration");
76501
+ lines.push("");
76502
+ lines.push("```yaml");
76503
+ lines.push(`provider: ${manifest.resolved_config.provider}`);
76504
+ if (manifest.resolved_config.model) {
76505
+ lines.push(`model: ${manifest.resolved_config.model}`);
76506
+ }
76507
+ if (manifest.resolved_config.temperature !== undefined) {
76508
+ lines.push(`temperature: ${manifest.resolved_config.temperature}`);
76509
+ }
76510
+ if (manifest.resolved_config.max_tokens !== undefined) {
76511
+ lines.push(`max_tokens: ${manifest.resolved_config.max_tokens}`);
76512
+ }
76513
+ lines.push("```");
76514
+ lines.push("");
76515
+ }
76516
+ if (manifest.redaction?.enabled) {
76517
+ lines.push("## Redaction");
76518
+ lines.push("");
76519
+ lines.push(`- **Patterns Used:** ${manifest.redaction.patternsUsed.join(", ")}`);
76520
+ lines.push(`- **Prompts Redacted:** ${manifest.redaction.summary.promptsRedacted}`);
76521
+ lines.push(`- **Responses Redacted:** ${manifest.redaction.summary.responsesRedacted}`);
76522
+ lines.push(`- **Total Redactions:** ${manifest.redaction.summary.totalRedactions}`);
76523
+ lines.push("");
76524
+ }
76525
+ lines.push("---");
76526
+ lines.push("");
76527
+ lines.push(`*Generated by [ArtemisKit](https://artemiskit.vercel.app) v${manifest.version}*`);
76528
+ return lines.join(`
76529
+ `);
76530
+ }
76531
+ function generateRedTeamMarkdownReport(manifest, options = {}) {
76532
+ const { includeDetails = true, truncateAt = 500 } = options;
76533
+ const lines = [];
76534
+ lines.push("# ArtemisKit Security Report");
76535
+ lines.push("");
76536
+ lines.push(`**Scenario:** ${manifest.config.scenario}`);
76537
+ lines.push(`**Run ID:** ${manifest.run_id}`);
76538
+ lines.push(`**Date:** ${new Date(manifest.start_time).toISOString()}`);
76539
+ lines.push("**Test Type:** Red Team Security Scan");
76540
+ lines.push("");
76541
+ lines.push("---");
76542
+ lines.push("");
76543
+ const testable = manifest.metrics.total_tests - manifest.metrics.error_responses;
76544
+ const defenseRate = manifest.metrics.defense_rate * 100;
76545
+ let riskLevel;
76546
+ let riskEmoji;
76547
+ if (defenseRate >= 95) {
76548
+ riskLevel = "LOW";
76549
+ riskEmoji = "";
76550
+ } else if (defenseRate >= 80) {
76551
+ riskLevel = "MEDIUM";
76552
+ riskEmoji = "";
76553
+ } else if (defenseRate >= 50) {
76554
+ riskLevel = "HIGH";
76555
+ riskEmoji = "";
76556
+ } else {
76557
+ riskLevel = "CRITICAL";
76558
+ riskEmoji = "";
76559
+ }
76560
+ lines.push("## Security Summary");
76561
+ lines.push("");
76562
+ lines.push("| Metric | Value |");
76563
+ lines.push("|--------|-------|");
76564
+ lines.push(`| Defense Rate | ${defenseRate.toFixed(1)}% |`);
76565
+ lines.push(`| Total Attacks | ${manifest.metrics.total_tests} |`);
76566
+ lines.push(`| Defended | ${manifest.metrics.defended} |`);
76567
+ lines.push(`| Safe Responses | ${manifest.metrics.safe_responses} |`);
76568
+ lines.push(`| Blocked | ${manifest.metrics.blocked_responses} |`);
76569
+ lines.push(`| Vulnerabilities | ${manifest.metrics.unsafe_responses} |`);
76570
+ lines.push(`| Errors | ${manifest.metrics.error_responses} |`);
76571
+ lines.push(`| Risk Level | **${riskEmoji} ${riskLevel}** |`);
76572
+ lines.push("");
76573
+ lines.push("---");
76574
+ lines.push("");
76575
+ const { by_severity } = manifest.metrics;
76576
+ if (manifest.metrics.unsafe_responses > 0) {
76577
+ lines.push("## Severity Breakdown");
76578
+ lines.push("");
76579
+ lines.push("| Severity | Count | Description |");
76580
+ lines.push("|----------|-------|-------------|");
76581
+ if (by_severity.critical > 0) {
76582
+ lines.push(`| Critical | ${by_severity.critical} | Severe security vulnerability |`);
76583
+ }
76584
+ if (by_severity.high > 0) {
76585
+ lines.push(`| High | ${by_severity.high} | Significant security concern |`);
76586
+ }
76587
+ if (by_severity.medium > 0) {
76588
+ lines.push(`| Medium | ${by_severity.medium} | Moderate security issue |`);
76589
+ }
76590
+ if (by_severity.low > 0) {
76591
+ lines.push(`| Low | ${by_severity.low} | Minor security concern |`);
76592
+ }
76593
+ lines.push("");
76594
+ lines.push("---");
76595
+ lines.push("");
76596
+ }
76597
+ const unsafeResults = manifest.results.filter((r3) => r3.status === "unsafe");
76598
+ if (unsafeResults.length > 0) {
76599
+ lines.push("## Vulnerabilities Found");
76600
+ lines.push("");
76601
+ for (let i2 = 0;i2 < unsafeResults.length; i2++) {
76602
+ const result = unsafeResults[i2];
76603
+ lines.push(`### ${i2 + 1}. ${result.mutation} (${result.severity.toUpperCase()})`);
76604
+ lines.push("");
76605
+ lines.push(`**Case ID:** ${result.caseId}`);
76606
+ lines.push(`**Severity:** ${result.severity.toUpperCase()}`);
76607
+ lines.push(`**Mutation:** ${result.mutation}`);
76608
+ lines.push("");
76609
+ if (includeDetails) {
76610
+ lines.push("**Attack Prompt:**");
76611
+ lines.push("```");
76612
+ lines.push(truncate2(result.prompt, truncateAt));
76613
+ lines.push("```");
76614
+ lines.push("");
76615
+ lines.push("**Model Response:**");
76616
+ lines.push("```");
76617
+ lines.push(truncate2(result.response || "(empty)", truncateAt));
76618
+ lines.push("```");
76619
+ lines.push("");
76620
+ }
76621
+ if (result.reasons.length > 0) {
76622
+ lines.push("**Reasons:**");
76623
+ for (const reason of result.reasons) {
76624
+ lines.push(`- ${reason}`);
76625
+ }
76626
+ lines.push("");
76627
+ }
76628
+ lines.push("---");
76629
+ lines.push("");
76630
+ }
76631
+ }
76632
+ lines.push("## Recommendations");
76633
+ lines.push("");
76634
+ if (manifest.metrics.unsafe_responses === 0) {
76635
+ lines.push("- All attacks were successfully defended");
76636
+ lines.push("- Continue monitoring and testing regularly");
76637
+ } else {
76638
+ if (by_severity.critical > 0 || by_severity.high > 0) {
76639
+ lines.push("- **High Priority:** Review and address critical/high severity vulnerabilities immediately");
76640
+ }
76641
+ if (manifest.metrics.unsafe_responses > testable * 0.1) {
76642
+ lines.push("- Consider implementing additional input validation and output filtering");
76643
+ }
76644
+ lines.push("- Review system prompts for potential information leakage");
76645
+ lines.push("- Implement role-play and persona detection");
76646
+ lines.push("- Add output filtering for dangerous content patterns");
76647
+ }
76648
+ lines.push("");
76649
+ lines.push("---");
76650
+ lines.push("");
76651
+ lines.push(`*Generated by [ArtemisKit](https://artemiskit.vercel.app) v${manifest.version}*`);
76652
+ return lines.join(`
76653
+ `);
76654
+ }
76361
76655
 
76362
76656
  // src/commands/compare.ts
76363
76657
  init_source();
@@ -76502,20 +76796,27 @@ function compareCommand() {
76502
76796
  }
76503
76797
 
76504
76798
  // src/commands/history.ts
76799
+ init_dist();
76505
76800
  init_source();
76506
76801
  init_ui();
76507
- function renderHistoryTable(runs) {
76802
+ function renderHistoryTable(runs, showCost = false) {
76508
76803
  const runIdWidth = 16;
76509
- const scenarioWidth = 30;
76804
+ const scenarioWidth = showCost ? 25 : 30;
76510
76805
  const rateWidth = 12;
76511
76806
  const dateWidth = 20;
76512
- const width = 2 + runIdWidth + 1 + scenarioWidth + 1 + rateWidth + 1 + dateWidth + 2;
76807
+ const costWidth = 10;
76808
+ const baseWidth = 2 + runIdWidth + 1 + scenarioWidth + 1 + rateWidth + 1 + dateWidth + 2;
76809
+ const width = showCost ? baseWidth + costWidth + 1 : baseWidth;
76513
76810
  const border = "\u2550".repeat(width - 2);
76514
76811
  const formatHeaderRow = () => {
76515
76812
  const runIdPad = padText("Run ID", runIdWidth);
76516
76813
  const scenarioPad = padText("Scenario", scenarioWidth);
76517
76814
  const ratePad = padText("Success Rate", rateWidth, "right");
76518
76815
  const datePad = padText("Date", dateWidth, "right");
76816
+ if (showCost) {
76817
+ const costPad = padText("Cost", costWidth, "right");
76818
+ return `\u2551 ${runIdPad} ${scenarioPad} ${ratePad} ${costPad} ${datePad} \u2551`;
76819
+ }
76519
76820
  return `\u2551 ${runIdPad} ${scenarioPad} ${ratePad} ${datePad} \u2551`;
76520
76821
  };
76521
76822
  const lines = [
@@ -76525,6 +76826,7 @@ function renderHistoryTable(runs) {
76525
76826
  formatHeaderRow(),
76526
76827
  `\u255F${"\u2500".repeat(width - 2)}\u2562`
76527
76828
  ];
76829
+ let totalCost = 0;
76528
76830
  for (const run of runs) {
76529
76831
  const rateColor = run.successRate >= 0.9 ? source_default.green : run.successRate >= 0.7 ? source_default.yellow : source_default.red;
76530
76832
  const runIdPad = padText(run.runId, runIdWidth);
@@ -76536,25 +76838,54 @@ function renderHistoryTable(runs) {
76536
76838
  const dateObj = new Date(run.createdAt);
76537
76839
  const dateStr = `${dateObj.toLocaleDateString()} ${dateObj.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}`;
76538
76840
  const datePad = padText(dateStr, dateWidth, "right");
76539
- lines.push(`\u2551 ${runIdPad} ${scenarioPad} ${rateColored} ${datePad} \u2551`);
76841
+ if (showCost) {
76842
+ const costValue = run.estimatedCostUsd !== undefined ? formatCost(run.estimatedCostUsd) : "-";
76843
+ const costPad = padText(costValue, costWidth, "right");
76844
+ if (run.estimatedCostUsd !== undefined) {
76845
+ totalCost += run.estimatedCostUsd;
76846
+ }
76847
+ lines.push(`\u2551 ${runIdPad} ${scenarioPad} ${rateColored} ${source_default.dim(costPad)} ${datePad} \u2551`);
76848
+ } else {
76849
+ lines.push(`\u2551 ${runIdPad} ${scenarioPad} ${rateColored} ${datePad} \u2551`);
76850
+ }
76851
+ }
76852
+ if (showCost) {
76853
+ lines.push(`\u255F${"\u2500".repeat(width - 2)}\u2562`);
76854
+ const totalLabel = padText("Total", runIdWidth + 1 + scenarioWidth + 1 + rateWidth, "right");
76855
+ const totalCostStr = padText(formatCost(totalCost), costWidth, "right");
76856
+ const emptyDate = padText("", dateWidth, "right");
76857
+ lines.push(`\u2551 ${totalLabel} ${source_default.bold(totalCostStr)} ${emptyDate} \u2551`);
76540
76858
  }
76541
76859
  lines.push(`\u255A${border}\u255D`);
76542
76860
  return lines.join(`
76543
76861
  `);
76544
76862
  }
76545
- function renderPlainHistory(runs) {
76863
+ function renderPlainHistory(runs, showCost = false) {
76546
76864
  const lines = ["=== RUN HISTORY ===", ""];
76865
+ let totalCost = 0;
76547
76866
  for (const run of runs) {
76548
76867
  const rate = `${(run.successRate * 100).toFixed(1)}%`;
76549
76868
  const date = new Date(run.createdAt).toLocaleString();
76550
- lines.push(`${run.runId} ${run.scenario} ${rate} ${date}`);
76869
+ if (showCost) {
76870
+ const cost = run.estimatedCostUsd !== undefined ? formatCost(run.estimatedCostUsd) : "-";
76871
+ if (run.estimatedCostUsd !== undefined) {
76872
+ totalCost += run.estimatedCostUsd;
76873
+ }
76874
+ lines.push(`${run.runId} ${run.scenario} ${rate} ${cost} ${date}`);
76875
+ } else {
76876
+ lines.push(`${run.runId} ${run.scenario} ${rate} ${date}`);
76877
+ }
76878
+ }
76879
+ if (showCost) {
76880
+ lines.push("");
76881
+ lines.push(`Total: ${formatCost(totalCost)}`);
76551
76882
  }
76552
76883
  return lines.join(`
76553
76884
  `);
76554
76885
  }
76555
76886
  function historyCommand() {
76556
76887
  const cmd = new Command("history");
76557
- cmd.description("View run history").option("-p, --project <project>", "Filter by project").option("-s, --scenario <scenario>", "Filter by scenario").option("-l, --limit <number>", "Limit number of results", "20").option("--config <path>", "Path to config file").action(async (options) => {
76888
+ cmd.description("View run history").option("-p, --project <project>", "Filter by project").option("-s, --scenario <scenario>", "Filter by scenario").option("-l, --limit <number>", "Limit number of results", "20").option("--config <path>", "Path to config file").option("--show-cost", "Show cost column and total").action(async (options) => {
76558
76889
  const spinner = createSpinner("Loading history...");
76559
76890
  spinner.start();
76560
76891
  try {
@@ -76564,7 +76895,8 @@ function historyCommand() {
76564
76895
  const runs = await storage.list({
76565
76896
  project: options.project,
76566
76897
  scenario: options.scenario,
76567
- limit
76898
+ limit,
76899
+ includeCost: options.showCost
76568
76900
  });
76569
76901
  spinner.succeed("Loaded history");
76570
76902
  console.log();
@@ -76583,9 +76915,9 @@ function historyCommand() {
76583
76915
  return;
76584
76916
  }
76585
76917
  if (isTTY) {
76586
- console.log(renderHistoryTable(runs));
76918
+ console.log(renderHistoryTable(runs, options.showCost));
76587
76919
  } else {
76588
- console.log(renderPlainHistory(runs));
76920
+ console.log(renderPlainHistory(runs, options.showCost));
76589
76921
  }
76590
76922
  console.log();
76591
76923
  console.log(source_default.dim(`Showing ${runs.length} run${runs.length === 1 ? "" : "s"}${options.limit ? ` (limit: ${limit})` : ""}`));
@@ -85177,7 +85509,7 @@ function resolveModelWithSource(cliModel, scenarioModel, configModel) {
85177
85509
  // src/commands/redteam.ts
85178
85510
  function redteamCommand() {
85179
85511
  const cmd = new Command("redteam");
85180
- cmd.description("Run red-team adversarial tests against an LLM").argument("<scenario>", "Path to scenario YAML file").option("-p, --provider <provider>", "Provider to use").option("-m, --model <model>", "Model to use").option("--mutations <mutations...>", "Mutations to apply (typo, role-spoof, instruction-flip, cot-injection, encoding, multi-turn)").option("-c, --count <number>", "Number of mutated prompts per case", "5").option("--custom-attacks <path>", "Path to custom attacks YAML file").option("--save", "Save results to storage").option("-o, --output <dir>", "Output directory for reports").option("-v, --verbose", "Verbose output").option("--config <path>", "Path to config file").option("--redact", "Enable PII/sensitive data redaction in results").option("--redact-patterns <patterns...>", "Custom redaction patterns (regex or built-in: email, phone, credit_card, ssn, api_key)").action(async (scenarioPath, options) => {
85512
+ cmd.description("Run red-team adversarial tests against an LLM").argument("<scenario>", "Path to scenario YAML file").option("-p, --provider <provider>", "Provider to use").option("-m, --model <model>", "Model to use").option("--mutations <mutations...>", "Mutations to apply (typo, role-spoof, instruction-flip, cot-injection, encoding, multi-turn)").option("-c, --count <number>", "Number of mutated prompts per case", "5").option("--custom-attacks <path>", "Path to custom attacks YAML file").option("--save", "Save results to storage").option("-o, --output <dir>", "Output directory for reports").option("-v, --verbose", "Verbose output").option("--config <path>", "Path to config file").option("--redact", "Enable PII/sensitive data redaction in results").option("--redact-patterns <patterns...>", "Custom redaction patterns (regex or built-in: email, phone, credit_card, ssn, api_key)").option("--export <format>", "Export results to format (markdown)").option("--export-output <dir>", "Output directory for exports (default: ./artemis-exports)").action(async (scenarioPath, options) => {
85181
85513
  const spinner = createSpinner("Loading configuration...");
85182
85514
  spinner.start();
85183
85515
  const startTime = new Date;
@@ -85478,6 +85810,14 @@ function redteamCommand() {
85478
85810
  console.log(source_default.dim(` HTML: ${htmlPath}`));
85479
85811
  console.log(source_default.dim(` JSON: ${jsonPath}`));
85480
85812
  }
85813
+ if (options.export === "markdown") {
85814
+ const exportDir = options.exportOutput || "./artemis-exports";
85815
+ await mkdir3(exportDir, { recursive: true });
85816
+ const markdown = generateRedTeamMarkdownReport(manifest);
85817
+ const mdPath = join4(exportDir, `${runId}.md`);
85818
+ await writeFile3(mdPath, markdown);
85819
+ console.log(source_default.dim(`Exported: ${mdPath}`));
85820
+ }
85481
85821
  if (metrics.unsafe_responses > 0) {
85482
85822
  process.exit(1);
85483
85823
  }
@@ -85652,8 +85992,10 @@ function reportCommand() {
85652
85992
 
85653
85993
  // src/commands/run.ts
85654
85994
  init_dist();
85655
- init_source();
85995
+ import { mkdir as mkdir5, writeFile as writeFile5 } from "fs/promises";
85656
85996
  import { basename as basename2 } from "path";
85997
+ import { join as join6 } from "path";
85998
+ init_source();
85657
85999
  init_ui();
85658
86000
  function isBaselineStorage2(storage) {
85659
86001
  return typeof storage === "object" && storage !== null && "setBaseline" in storage && "getBaseline" in storage && "listBaselines" in storage && "compareToBaseline" in storage;
@@ -85666,6 +86008,10 @@ function buildCISummary(results) {
85666
86008
  const passedCases = results.reduce((sum, r3) => sum + (r3.manifest.metrics?.passed_cases || 0), 0);
85667
86009
  const failedCases = results.reduce((sum, r3) => sum + (r3.manifest.metrics?.failed_cases || 0), 0);
85668
86010
  const totalDuration = results.reduce((sum, r3) => sum + (r3.manifest.duration_ms || 0), 0);
86011
+ const totalPromptTokens = results.reduce((sum, r3) => sum + (r3.manifest.metrics?.total_prompt_tokens || 0), 0);
86012
+ const totalCompletionTokens = results.reduce((sum, r3) => sum + (r3.manifest.metrics?.total_completion_tokens || 0), 0);
86013
+ const totalTokens = results.reduce((sum, r3) => sum + (r3.manifest.metrics?.total_tokens || 0), 0);
86014
+ const totalCostUsd = results.reduce((sum, r3) => sum + (r3.manifest.metrics?.cost?.total_usd || 0), 0);
85669
86015
  return {
85670
86016
  success: failedScenarios === 0,
85671
86017
  scenarios: {
@@ -85683,6 +86029,15 @@ function buildCISummary(results) {
85683
86029
  totalMs: totalDuration,
85684
86030
  formatted: formatDuration(totalDuration)
85685
86031
  },
86032
+ tokens: {
86033
+ prompt: totalPromptTokens,
86034
+ completion: totalCompletionTokens,
86035
+ total: totalTokens
86036
+ },
86037
+ cost: {
86038
+ estimatedUsd: totalCostUsd,
86039
+ formatted: formatCost(totalCostUsd)
86040
+ },
85686
86041
  runs: results.map((r3) => ({
85687
86042
  runId: r3.manifest.run_id || "",
85688
86043
  scenario: r3.scenarioName,
@@ -85691,7 +86046,8 @@ function buildCISummary(results) {
85691
86046
  passedCases: r3.manifest.metrics?.passed_cases || 0,
85692
86047
  failedCases: r3.manifest.metrics?.failed_cases || 0,
85693
86048
  totalCases: r3.manifest.metrics?.total_cases || 0,
85694
- durationMs: r3.manifest.duration_ms || 0
86049
+ durationMs: r3.manifest.duration_ms || 0,
86050
+ estimatedCostUsd: r3.manifest.metrics?.cost?.total_usd
85695
86051
  }))
85696
86052
  };
85697
86053
  }
@@ -85909,7 +86265,7 @@ async function runScenariosInParallel(scenarioPaths, options, config, parallelLi
85909
86265
  }
85910
86266
  function runCommand() {
85911
86267
  const cmd = new Command("run");
85912
- cmd.description("Run test scenarios against an LLM. Accepts a file path, directory, or glob pattern.").argument("[scenario]", "Path to scenario file, directory, or glob pattern (e.g., scenarios/**/*.yaml)").option("-p, --provider <provider>", "Provider to use (openai, azure-openai, vercel-ai)").option("-m, --model <model>", "Model to use").option("-o, --output <dir>", "Output directory for results").option("-v, --verbose", "Verbose output").option("-t, --tags <tags...>", "Filter test cases by tags").option("--save", "Save results to storage", true).option("-c, --concurrency <number>", "Number of concurrent test cases per scenario", "1").option("--parallel <number>", "Number of scenarios to run in parallel (default: sequential)").option("--timeout <ms>", "Timeout per test case in milliseconds").option("--retries <number>", "Number of retries per test case").option("--config <path>", "Path to config file").option("--redact", "Enable PII/sensitive data redaction in results").option("--redact-patterns <patterns...>", "Custom redaction patterns (regex or built-in: email, phone, credit_card, ssn, api_key)").option("-i, --interactive", "Enable interactive mode for scenario/provider selection").option("--ci", "CI mode: machine-readable output, no colors/spinners, JSON summary").option("--summary <format>", "Summary output format: json, text, or security (implies --ci for json/security)", "text").option("--baseline", "Compare against baseline and detect regression").option("--threshold <number>", "Regression threshold (0-1), e.g., 0.05 for 5%", "0.05").action(async (scenarioPath, options) => {
86268
+ cmd.description("Run test scenarios against an LLM. Accepts a file path, directory, or glob pattern.").argument("[scenario]", "Path to scenario file, directory, or glob pattern (e.g., scenarios/**/*.yaml)").option("-p, --provider <provider>", "Provider to use (openai, azure-openai, vercel-ai)").option("-m, --model <model>", "Model to use").option("-o, --output <dir>", "Output directory for results").option("-v, --verbose", "Verbose output").option("-t, --tags <tags...>", "Filter test cases by tags").option("--save", "Save results to storage", true).option("-c, --concurrency <number>", "Number of concurrent test cases per scenario", "1").option("--parallel <number>", "Number of scenarios to run in parallel (default: sequential)").option("--timeout <ms>", "Timeout per test case in milliseconds").option("--retries <number>", "Number of retries per test case").option("--config <path>", "Path to config file").option("--redact", "Enable PII/sensitive data redaction in results").option("--redact-patterns <patterns...>", "Custom redaction patterns (regex or built-in: email, phone, credit_card, ssn, api_key)").option("-i, --interactive", "Enable interactive mode for scenario/provider selection").option("--ci", "CI mode: machine-readable output, no colors/spinners, JSON summary").option("--summary <format>", "Summary output format: json, text, or security (implies --ci for json/security)", "text").option("--baseline", "Compare against baseline and detect regression").option("--threshold <number>", "Regression threshold (0-1), e.g., 0.05 for 5%", "0.05").option("--budget <amount>", "Maximum budget in USD - fail if estimated cost exceeds this").option("--export <format>", "Export format: markdown").option("--export-output <dir>", "Output directory for exports (default: ./artemis-exports)").action(async (scenarioPath, options) => {
85913
86269
  const isCIMode = options.ci || process.env.CI === "true" || options.summary === "json" || options.summary === "security";
85914
86270
  const spinner = isCIMode ? {
85915
86271
  start: () => {},
@@ -86032,7 +86388,8 @@ No scenarios selected. Exiting.`));
86032
86388
  console.log();
86033
86389
  console.log(renderSummaryPanel(summaryData));
86034
86390
  console.log();
86035
- console.log(source_default.dim(`Run ID: ${result.manifest.run_id} | Median Latency: ${result.manifest.metrics.median_latency_ms}ms | Tokens: ${result.manifest.metrics.total_tokens.toLocaleString()}`));
86391
+ const costInfo = result.manifest.metrics.cost ? ` | Est. Cost: ${formatCost(result.manifest.metrics.cost.total_usd)}` : "";
86392
+ console.log(source_default.dim(`Run ID: ${result.manifest.run_id} | Median Latency: ${result.manifest.metrics.median_latency_ms}ms | Tokens: ${result.manifest.metrics.total_tokens.toLocaleString()}${costInfo}`));
86036
86393
  if (result.manifest.redaction?.enabled) {
86037
86394
  const r3 = result.manifest.redaction;
86038
86395
  console.log(source_default.dim(`Redactions: ${r3.summary.totalRedactions} (${r3.summary.promptsRedacted} prompts, ${r3.summary.responsesRedacted} responses)`));
@@ -86041,6 +86398,14 @@ No scenarios selected. Exiting.`));
86041
86398
  const savedPath = await storage.save(result.manifest);
86042
86399
  console.log(source_default.dim(`Saved: ${savedPath}`));
86043
86400
  }
86401
+ if (options.export === "markdown") {
86402
+ const exportDir = options.exportOutput || "./artemis-exports";
86403
+ await mkdir5(exportDir, { recursive: true });
86404
+ const markdown = generateMarkdownReport(result.manifest);
86405
+ const mdPath = join6(exportDir, `${result.manifest.run_id}.md`);
86406
+ await writeFile5(mdPath, markdown);
86407
+ console.log(source_default.dim(`Exported: ${mdPath}`));
86408
+ }
86044
86409
  } catch (error) {
86045
86410
  console.log();
86046
86411
  console.log(source_default.red(`${icons.failed} Failed to run: ${basename2(path3)}`));
@@ -86108,6 +86473,8 @@ No scenarios selected. Exiting.`));
86108
86473
  console.log(`ARTEMISKIT_CASES_FAILED=${failedCases}`);
86109
86474
  console.log(`ARTEMISKIT_SUCCESS_RATE=${successRate}`);
86110
86475
  console.log(`ARTEMISKIT_DURATION_MS=${ciSummary.duration.totalMs}`);
86476
+ console.log(`ARTEMISKIT_TOKENS_TOTAL=${ciSummary.tokens.total}`);
86477
+ console.log(`ARTEMISKIT_COST_USD=${ciSummary.cost.estimatedUsd.toFixed(4)}`);
86111
86478
  if (baselineResult) {
86112
86479
  console.log("ARTEMISKIT_BASELINE_COMPARED=true");
86113
86480
  console.log(`ARTEMISKIT_REGRESSION=${baselineResult.hasRegression ? "true" : "false"}`);
@@ -86156,9 +86523,37 @@ No scenarios selected. Exiting.`));
86156
86523
  console.log(`${icons.passed} ${source_default.green("No regression detected")}`);
86157
86524
  }
86158
86525
  }
86526
+ let budgetExceeded = false;
86527
+ if (options.budget !== undefined) {
86528
+ const budgetLimit = Number.parseFloat(String(options.budget));
86529
+ const totalCost = ciSummary.cost.estimatedUsd;
86530
+ if (totalCost > budgetLimit) {
86531
+ budgetExceeded = true;
86532
+ const overBy = totalCost - budgetLimit;
86533
+ ciSummary.budget = {
86534
+ limit: budgetLimit,
86535
+ exceeded: true,
86536
+ overBy
86537
+ };
86538
+ if (isCIMode) {
86539
+ if (options.summary === "json") {} else {
86540
+ console.log(`ARTEMISKIT_BUDGET_LIMIT=${budgetLimit.toFixed(2)}`);
86541
+ console.log("ARTEMISKIT_BUDGET_EXCEEDED=true");
86542
+ console.log(`ARTEMISKIT_BUDGET_OVER_BY=${overBy.toFixed(4)}`);
86543
+ }
86544
+ } else {
86545
+ console.log();
86546
+ console.log(source_default.red(`${icons.failed} BUDGET EXCEEDED`));
86547
+ console.log(source_default.red(` Budget: $${budgetLimit.toFixed(2)} | Actual: ${formatCost(totalCost)} | Over by: ${formatCost(overBy)}`));
86548
+ console.log();
86549
+ }
86550
+ } else if (!isCIMode) {
86551
+ console.log(`${icons.passed} ${source_default.green("Within budget")} ${source_default.dim(`($${budgetLimit.toFixed(2)} limit, ${formatCost(totalCost)} used)`)}`);
86552
+ }
86553
+ }
86159
86554
  const hasFailures = results.some((r3) => !r3.success);
86160
86555
  const hasRegression = baselineResult?.hasRegression || false;
86161
- if (hasFailures || hasRegression) {
86556
+ if (hasFailures || hasRegression || budgetExceeded) {
86162
86557
  process.exit(1);
86163
86558
  }
86164
86559
  } catch (error) {
@@ -86179,13 +86574,13 @@ No scenarios selected. Exiting.`));
86179
86574
 
86180
86575
  // src/commands/stress.ts
86181
86576
  init_dist();
86182
- import { mkdir as mkdir5, writeFile as writeFile5 } from "fs/promises";
86183
- import { basename as basename3, join as join6 } from "path";
86577
+ import { mkdir as mkdir6, writeFile as writeFile6 } from "fs/promises";
86578
+ import { basename as basename3, join as join7 } from "path";
86184
86579
  init_source();
86185
86580
  init_ui();
86186
86581
  function stressCommand() {
86187
86582
  const cmd = new Command("stress");
86188
- cmd.description("Run load/stress tests against an LLM").argument("<scenario>", "Path to scenario YAML file").option("-p, --provider <provider>", "Provider to use").option("-m, --model <model>", "Model to use").option("-c, --concurrency <number>", "Number of concurrent requests", "10").option("-n, --requests <number>", "Total number of requests to make").option("-d, --duration <seconds>", "Duration to run the test in seconds", "30").option("--ramp-up <seconds>", "Ramp-up time in seconds", "5").option("--save", "Save results to storage").option("-o, --output <dir>", "Output directory for reports").option("-v, --verbose", "Verbose output").option("--config <path>", "Path to config file").option("--redact", "Enable PII/sensitive data redaction in results").option("--redact-patterns <patterns...>", "Custom redaction patterns (regex or built-in: email, phone, credit_card, ssn, api_key)").action(async (scenarioPath, options) => {
86583
+ cmd.description("Run load/stress tests against an LLM").argument("<scenario>", "Path to scenario YAML file").option("-p, --provider <provider>", "Provider to use").option("-m, --model <model>", "Model to use").option("-c, --concurrency <number>", "Number of concurrent requests", "10").option("-n, --requests <number>", "Total number of requests to make").option("-d, --duration <seconds>", "Duration to run the test in seconds", "30").option("--ramp-up <seconds>", "Ramp-up time in seconds", "5").option("--save", "Save results to storage").option("-o, --output <dir>", "Output directory for reports").option("-v, --verbose", "Verbose output").option("--config <path>", "Path to config file").option("--redact", "Enable PII/sensitive data redaction in results").option("--redact-patterns <patterns...>", "Custom redaction patterns (regex or built-in: email, phone, credit_card, ssn, api_key)").option("--budget <amount>", "Maximum budget in USD - fail if estimated cost exceeds this").action(async (scenarioPath, options) => {
86189
86584
  const spinner = createSpinner("Loading configuration...");
86190
86585
  spinner.start();
86191
86586
  const startTime = new Date;
@@ -86352,17 +86747,31 @@ function stressCommand() {
86352
86747
  }
86353
86748
  if (options.output) {
86354
86749
  spinner.start("Generating reports...");
86355
- await mkdir5(options.output, { recursive: true });
86750
+ await mkdir6(options.output, { recursive: true });
86356
86751
  const html = generateStressHTMLReport(manifest);
86357
- const htmlPath = join6(options.output, `${runId}.html`);
86358
- await writeFile5(htmlPath, html);
86752
+ const htmlPath = join7(options.output, `${runId}.html`);
86753
+ await writeFile6(htmlPath, html);
86359
86754
  const json = generateJSONReport(manifest);
86360
- const jsonPath = join6(options.output, `${runId}.json`);
86361
- await writeFile5(jsonPath, json);
86755
+ const jsonPath = join7(options.output, `${runId}.json`);
86756
+ await writeFile6(jsonPath, json);
86362
86757
  spinner.succeed(`Reports generated: ${options.output}`);
86363
86758
  console.log(source_default.dim(` HTML: ${htmlPath}`));
86364
86759
  console.log(source_default.dim(` JSON: ${jsonPath}`));
86365
86760
  }
86761
+ if (options.budget !== undefined && metrics.cost) {
86762
+ const budgetLimit = Number.parseFloat(String(options.budget));
86763
+ const totalCost = metrics.cost.estimated_total_usd;
86764
+ if (totalCost > budgetLimit) {
86765
+ const overBy = totalCost - budgetLimit;
86766
+ console.log();
86767
+ console.log(source_default.red(`${icons.failed} BUDGET EXCEEDED`));
86768
+ console.log(source_default.red(` Budget: $${budgetLimit.toFixed(2)} | Actual: ${formatCost(totalCost)} | Over by: ${formatCost(overBy)}`));
86769
+ process.exit(1);
86770
+ } else {
86771
+ console.log();
86772
+ console.log(`${icons.passed} ${source_default.green("Within budget")} ${source_default.dim(`($${budgetLimit.toFixed(2)} limit, ${formatCost(totalCost)} used)`)}`);
86773
+ }
86774
+ }
86366
86775
  } catch (error) {
86367
86776
  spinner.fail("Error");
86368
86777
  const provider = options.provider || "unknown";