@tangle-network/sandbox-ui 0.10.2 → 0.10.4

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/pages.js CHANGED
@@ -298,16 +298,65 @@ function BillingPage({
298
298
 
299
299
  // src/pages/provisioning-wizard.tsx
300
300
  import * as React2 from "react";
301
- import { ArrowLeft, Layers, Cpu, Bot, Info, Loader2, Settings, Plus, Trash2, Check } from "lucide-react";
301
+ import {
302
+ ArrowLeft,
303
+ Layers,
304
+ Cpu,
305
+ Bot,
306
+ Info,
307
+ Loader2,
308
+ Settings,
309
+ Plus,
310
+ Trash2,
311
+ Check
312
+ } from "lucide-react";
302
313
  import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
303
- var VALID_DRIVERS = /* @__PURE__ */ new Set(["docker", "firecracker", "tangle"]);
314
+ var VALID_DRIVERS = /* @__PURE__ */ new Set([
315
+ "docker",
316
+ "firecracker",
317
+ "tangle"
318
+ ]);
319
+ var DEFAULT_MODEL_OPTIONS = [
320
+ { value: "claude-sonnet", label: "Claude Sonnet 4.6 (Highly Capable)" }
321
+ ];
322
+ var DEFAULT_MODEL_TIER = DEFAULT_MODEL_OPTIONS[0]?.value ?? "claude-sonnet";
304
323
  var STACK_DISPLAY = {
305
- universal: { name: "Default", abbr: "D", color: "violet", textClass: "text-[var(--surface-violet-text)]" },
306
- ethereum: { name: "Ethereum", abbr: "\u039E", color: "blue", textClass: "text-[var(--surface-info-text)]" },
307
- solana: { name: "Solana", abbr: "S", color: "green", textClass: "text-[var(--surface-success-text)]" },
308
- tangle: { name: "Tangle", abbr: "T", color: "purple", textClass: "text-[var(--surface-violet-text)]" },
309
- "ai-sdk": { name: "AI SDK", abbr: "AI", color: "teal", textClass: "text-[var(--surface-teal-text)]" },
310
- rust: { name: "Rust", abbr: "Rs", color: "orange", textClass: "text-[var(--surface-orange-text)]" }
324
+ universal: {
325
+ name: "Default",
326
+ abbr: "D",
327
+ color: "violet",
328
+ textClass: "text-[var(--surface-violet-text)]"
329
+ },
330
+ ethereum: {
331
+ name: "Ethereum",
332
+ abbr: "\u039E",
333
+ color: "blue",
334
+ textClass: "text-[var(--surface-info-text)]"
335
+ },
336
+ solana: {
337
+ name: "Solana",
338
+ abbr: "S",
339
+ color: "green",
340
+ textClass: "text-[var(--surface-success-text)]"
341
+ },
342
+ tangle: {
343
+ name: "Tangle",
344
+ abbr: "T",
345
+ color: "purple",
346
+ textClass: "text-[var(--surface-violet-text)]"
347
+ },
348
+ "ai-sdk": {
349
+ name: "AI SDK",
350
+ abbr: "AI",
351
+ color: "teal",
352
+ textClass: "text-[var(--surface-teal-text)]"
353
+ },
354
+ rust: {
355
+ name: "Rust",
356
+ abbr: "Rs",
357
+ color: "orange",
358
+ textClass: "text-[var(--surface-orange-text)]"
359
+ }
311
360
  };
312
361
  function resolveEnvironment(env) {
313
362
  if (env.id.startsWith("template:")) {
@@ -334,9 +383,27 @@ function resolveEnvironment(env) {
334
383
  };
335
384
  }
336
385
  var defaultEnvironments = [
337
- { id: "node", name: "Node.js", description: "v20.x LTS with optimized runtime for asynchronous event-driven agents.", icon: /* @__PURE__ */ jsx2("span", { className: "text-[var(--code-success)] text-2xl font-bold", children: "N" }), color: "green" },
338
- { id: "python", name: "Python", description: "v3.11 pre-installed with PyTorch and common data science libraries.", icon: /* @__PURE__ */ jsx2("span", { className: "text-sky-400 text-2xl font-bold", children: "Py" }), color: "blue" },
339
- { id: "ubuntu", name: "Ubuntu", description: "Full 22.04 LTS terminal access for custom containerized workloads.", icon: /* @__PURE__ */ jsx2("span", { className: "text-orange-400 text-2xl font-bold", children: "U" }), color: "orange" }
386
+ {
387
+ id: "node",
388
+ name: "Node.js",
389
+ description: "v20.x LTS with optimized runtime for asynchronous event-driven agents.",
390
+ icon: /* @__PURE__ */ jsx2("span", { className: "text-[var(--code-success)] text-2xl font-bold", children: "N" }),
391
+ color: "green"
392
+ },
393
+ {
394
+ id: "python",
395
+ name: "Python",
396
+ description: "v3.11 pre-installed with PyTorch and common data science libraries.",
397
+ icon: /* @__PURE__ */ jsx2("span", { className: "text-sky-400 text-2xl font-bold", children: "Py" }),
398
+ color: "blue"
399
+ },
400
+ {
401
+ id: "ubuntu",
402
+ name: "Ubuntu",
403
+ description: "Full 22.04 LTS terminal access for custom containerized workloads.",
404
+ icon: /* @__PURE__ */ jsx2("span", { className: "text-orange-400 text-2xl font-bold", children: "U" }),
405
+ color: "orange"
406
+ }
340
407
  ];
341
408
  var CPU_MIN = 0.5;
342
409
  var CPU_MAX = 8;
@@ -344,9 +411,35 @@ var RAM_MIN = 2;
344
411
  var RAM_MAX = 32;
345
412
  var STORAGE_MIN = 20;
346
413
  var STORAGE_MAX = 512;
347
- function calcCost(cpu, ram, storage) {
348
- const cost = cpu * 0.045 + ram * 5e-3 + storage * 11e-4;
349
- return cost.toFixed(2);
414
+ var DEFAULT_PRICING_RATES = {
415
+ cpuPerHr: 0.045,
416
+ ramPerGbHr: 5e-3,
417
+ diskPerGbHr: 11e-4,
418
+ minChargePerHr: void 0
419
+ };
420
+ function formatPerSecondValue(hourlyValue) {
421
+ return (hourlyValue / 3600).toFixed(8);
422
+ }
423
+ var RAW_PRESETS = [
424
+ { name: "Lightweight", cpu: 2, ram: 4, storage: 50 },
425
+ { name: "Standard", cpu: 4, ram: 16, storage: 128 },
426
+ { name: "Performance", cpu: 8, ram: 32, storage: 256 }
427
+ ];
428
+ function computeHourlyCost(cpu, ram, storage, rates) {
429
+ const compute = cpu * rates.cpuPerHr;
430
+ const memory = ram * rates.ramPerGbHr;
431
+ const storageCost = storage * rates.diskPerGbHr;
432
+ const lineSum = compute + memory + storageCost;
433
+ const floor = rates.minChargePerHr ?? 0;
434
+ return {
435
+ compute,
436
+ memory,
437
+ storage: storageCost,
438
+ lineSum,
439
+ floor,
440
+ floorApplies: floor > lineSum,
441
+ total: Math.max(floor, lineSum)
442
+ };
350
443
  }
351
444
  function ProvisioningWizard({
352
445
  environments: environmentsProp,
@@ -359,13 +452,27 @@ function ProvisioningWizard({
359
452
  defaultConfig,
360
453
  skipToReview,
361
454
  onLoadStartupScripts,
362
- resourceLimits
455
+ resourceLimits,
456
+ modelOptions,
457
+ pricingRates,
458
+ planTiers
363
459
  }) {
364
- const cpuMax = Math.max(CPU_MIN, Math.min(resourceLimits?.cpuMax ?? CPU_MAX, CPU_MAX));
365
- const ramMax = Math.max(RAM_MIN, Math.min(resourceLimits?.ramMaxGB ?? RAM_MAX, RAM_MAX));
366
- const storageMax = Math.max(STORAGE_MIN, Math.min(resourceLimits?.storageMaxGB ?? STORAGE_MAX, STORAGE_MAX));
460
+ const cpuMax = Math.max(
461
+ CPU_MIN,
462
+ Math.min(resourceLimits?.cpuMax ?? CPU_MAX, CPU_MAX)
463
+ );
464
+ const ramMax = Math.max(
465
+ RAM_MIN,
466
+ Math.min(resourceLimits?.ramMaxGB ?? RAM_MAX, RAM_MAX)
467
+ );
468
+ const storageMax = Math.max(
469
+ STORAGE_MIN,
470
+ Math.min(resourceLimits?.storageMaxGB ?? STORAGE_MAX, STORAGE_MAX)
471
+ );
367
472
  const dc = defaultConfig;
368
- const [envList, setEnvList] = React2.useState(environmentsProp ?? defaultEnvironments);
473
+ const [envList, setEnvList] = React2.useState(
474
+ environmentsProp ?? defaultEnvironments
475
+ );
369
476
  const onLoadEnvironmentsRef = React2.useRef(onLoadEnvironments);
370
477
  onLoadEnvironmentsRef.current = onLoadEnvironments;
371
478
  React2.useEffect(() => {
@@ -374,7 +481,10 @@ function ProvisioningWizard({
374
481
  onLoadEnvironmentsRef.current().then((entries) => {
375
482
  if (!cancelled) setEnvList(entries.map(resolveEnvironment));
376
483
  }).catch((err) => {
377
- if (!cancelled) setLoadError(err instanceof Error ? err.message : "Failed to load environments");
484
+ if (!cancelled)
485
+ setLoadError(
486
+ err instanceof Error ? err.message : "Failed to load environments"
487
+ );
378
488
  });
379
489
  } else if (environmentsProp) {
380
490
  setEnvList(environmentsProp);
@@ -385,30 +495,54 @@ function ProvisioningWizard({
385
495
  }, [environmentsProp]);
386
496
  const environments = envList;
387
497
  const effectiveDefault = dc?.environment ?? defaultEnvironment;
388
- const [selectedEnv, setSelectedEnv] = React2.useState(effectiveDefault ?? environments[0]?.id ?? "");
498
+ const [selectedEnv, setSelectedEnv] = React2.useState(
499
+ effectiveDefault ?? environments[0]?.id ?? ""
500
+ );
389
501
  React2.useEffect(() => {
390
502
  if (effectiveDefault && envList.some((e) => e.id === effectiveDefault)) {
391
503
  setSelectedEnv(effectiveDefault);
392
504
  }
393
505
  }, [envList, effectiveDefault]);
394
- const [cpuCores, setCpuCores] = React2.useState(Math.min(dc?.cpuCores ?? 4, cpuMax));
506
+ const [cpuCores, setCpuCores] = React2.useState(
507
+ Math.min(dc?.cpuCores ?? 4, cpuMax)
508
+ );
395
509
  const [ramGB, setRamGB] = React2.useState(Math.min(dc?.ramGB ?? 16, ramMax));
396
- const [storageGB, setStorageGB] = React2.useState(Math.min(dc?.storageGB ?? 128, storageMax));
510
+ const [storageGB, setStorageGB] = React2.useState(
511
+ Math.min(dc?.storageGB ?? 128, storageMax)
512
+ );
397
513
  React2.useEffect(() => {
398
514
  setCpuCores((prev) => Math.min(prev, cpuMax));
399
515
  setRamGB((prev) => Math.min(prev, ramMax));
400
516
  setStorageGB((prev) => Math.min(prev, storageMax));
401
517
  }, [cpuMax, ramMax, storageMax]);
402
- const [modelTier, setModelTier] = React2.useState(dc?.modelTier ?? "claude-sonnet");
403
- const [systemPrompt, setSystemPrompt] = React2.useState(dc?.systemPrompt ?? "");
518
+ const [modelTier, setModelTier] = React2.useState(
519
+ dc?.modelTier ?? DEFAULT_MODEL_TIER
520
+ );
521
+ const [systemPrompt, setSystemPrompt] = React2.useState(
522
+ dc?.systemPrompt ?? ""
523
+ );
524
+ React2.useEffect(() => {
525
+ const options = modelOptions ?? DEFAULT_MODEL_OPTIONS;
526
+ if (options.length === 0) return;
527
+ const currentOption = options.find((o) => o.value === modelTier);
528
+ if (!currentOption || currentOption.disabled) {
529
+ const firstAvailable = options.find((o) => !o.disabled);
530
+ if (firstAvailable && firstAvailable.value !== modelTier) {
531
+ setModelTier(firstAvailable.value);
532
+ }
533
+ }
534
+ }, [modelOptions, modelTier]);
404
535
  const [name, setName] = React2.useState(dc?.name ?? "");
405
536
  const [gitUrl, setGitUrl] = React2.useState(dc?.gitUrl ?? "");
406
537
  const [envVars, setEnvVars] = React2.useState(dc?.envVars ?? [{ key: "", value: "" }]);
407
538
  const [driver, setDriver] = React2.useState(dc?.driver ?? "docker");
408
539
  const [bare, setBare] = React2.useState(dc?.bare ?? false);
409
- const [startupScriptIds, setStartupScriptIds] = React2.useState(dc?.startupScriptIds ?? []);
540
+ const [startupScriptIds, setStartupScriptIds] = React2.useState(
541
+ dc?.startupScriptIds ?? []
542
+ );
410
543
  const [availableScripts, setAvailableScripts] = React2.useState([]);
411
544
  const [activePreset, setActivePreset] = React2.useState(null);
545
+ const [pricingView, setPricingView] = React2.useState("hourly");
412
546
  const [showAdvanced, setShowAdvanced] = React2.useState(false);
413
547
  const [loadError, setLoadError] = React2.useState(null);
414
548
  const onLoadStartupScriptsRef = React2.useRef(onLoadStartupScripts);
@@ -419,7 +553,10 @@ function ProvisioningWizard({
419
553
  onLoadStartupScriptsRef.current().then((scripts) => {
420
554
  if (!cancelled) setAvailableScripts(scripts);
421
555
  }).catch((err) => {
422
- if (!cancelled) setLoadError(err instanceof Error ? err.message : "Failed to load startup scripts");
556
+ if (!cancelled)
557
+ setLoadError(
558
+ err instanceof Error ? err.message : "Failed to load startup scripts"
559
+ );
423
560
  });
424
561
  }
425
562
  return () => {
@@ -427,7 +564,9 @@ function ProvisioningWizard({
427
564
  };
428
565
  }, []);
429
566
  const isMultistep = variant === "multistep";
430
- const [currentStep, setCurrentStep] = React2.useState(skipToReview && dc && isMultistep ? 3 : 1);
567
+ const [currentStep, setCurrentStep] = React2.useState(
568
+ skipToReview && dc && isMultistep ? 3 : 1
569
+ );
431
570
  const [isDeploying, setIsDeploying] = React2.useState(false);
432
571
  const [deployError, setDeployError] = React2.useState(null);
433
572
  const handleDeploy = async () => {
@@ -435,8 +574,25 @@ function ProvisioningWizard({
435
574
  setIsDeploying(true);
436
575
  setDeployError(null);
437
576
  try {
438
- const validScriptIds = new Set(availableScripts.filter((s) => s.enabled).map((s) => s.id));
439
- await onSubmit({ environment: selectedEnv, cpuCores, ramGB, storageGB, modelTier, systemPrompt, name, gitUrl, envVars: envVars.filter((e) => e.key.trim() !== ""), driver, bare, startupScriptIds: startupScriptIds.filter((id) => validScriptIds.has(id)) });
577
+ const validScriptIds = new Set(
578
+ availableScripts.filter((s) => s.enabled).map((s) => s.id)
579
+ );
580
+ await onSubmit({
581
+ environment: selectedEnv,
582
+ cpuCores,
583
+ ramGB,
584
+ storageGB,
585
+ modelTier,
586
+ systemPrompt,
587
+ name,
588
+ gitUrl,
589
+ envVars: envVars.filter((e) => e.key.trim() !== ""),
590
+ driver,
591
+ bare,
592
+ startupScriptIds: startupScriptIds.filter(
593
+ (id) => validScriptIds.has(id)
594
+ )
595
+ });
440
596
  } catch (err) {
441
597
  setDeployError(err instanceof Error ? err.message : "Deployment failed");
442
598
  } finally {
@@ -449,15 +605,73 @@ function ProvisioningWizard({
449
605
  setStorageGB(Math.min(storage, storageMax));
450
606
  setActivePreset(name2);
451
607
  };
452
- const presets = [
453
- { name: "Lightweight", cpu: Math.min(2, cpuMax), ram: Math.min(4, ramMax), storage: Math.min(50, storageMax) },
454
- { name: "Standard", cpu: Math.min(4, cpuMax), ram: Math.min(16, ramMax), storage: Math.min(128, storageMax) },
455
- { name: "Performance", cpu: Math.min(8, cpuMax), ram: Math.min(32, ramMax), storage: Math.min(256, storageMax) }
456
- ];
457
- const hourCost = calcCost(cpuCores, ramGB, storageGB);
608
+ const presets = RAW_PRESETS.map((p) => {
609
+ const locked = p.cpu > cpuMax || p.ram > ramMax || p.storage > storageMax;
610
+ let unlockLabel;
611
+ if (locked && planTiers && planTiers.length > 0) {
612
+ const unlocking = planTiers.find(
613
+ (t) => p.cpu <= t.cpuMax && p.ram <= t.ramMaxGB && p.storage <= t.storageMaxGB
614
+ );
615
+ unlockLabel = unlocking?.label;
616
+ }
617
+ return {
618
+ ...p,
619
+ fits: !locked,
620
+ locked,
621
+ unlockLabel: unlockLabel ?? "Pro"
622
+ };
623
+ });
624
+ const didInitPresetFromDcRef = React2.useRef(false);
625
+ const lastLimitsRef = React2.useRef(null);
626
+ React2.useEffect(() => {
627
+ const limitsUnchanged = lastLimitsRef.current !== null && lastLimitsRef.current.cpu === cpuMax && lastLimitsRef.current.ram === ramMax && lastLimitsRef.current.storage === storageMax;
628
+ if (limitsUnchanged) return;
629
+ lastLimitsRef.current = { cpu: cpuMax, ram: ramMax, storage: storageMax };
630
+ if (dc && !didInitPresetFromDcRef.current) {
631
+ didInitPresetFromDcRef.current = true;
632
+ const matching = RAW_PRESETS.find(
633
+ (p) => p.cpu === dc.cpuCores && p.ram === dc.ramGB && p.storage === dc.storageGB
634
+ );
635
+ if (matching) setActivePreset(matching.name);
636
+ return;
637
+ }
638
+ const largestFitting = [...RAW_PRESETS].reverse().find(
639
+ (p) => p.cpu <= cpuMax && p.ram <= ramMax && p.storage <= storageMax
640
+ );
641
+ if (largestFitting) {
642
+ applyPreset(
643
+ largestFitting.name,
644
+ largestFitting.cpu,
645
+ largestFitting.ram,
646
+ largestFitting.storage
647
+ );
648
+ } else {
649
+ setActivePreset(null);
650
+ }
651
+ }, [cpuMax, ramMax, storageMax, dc]);
652
+ const effectivePricingRates = pricingRates ?? DEFAULT_PRICING_RATES;
653
+ const hourlyCostBreakdown = computeHourlyCost(
654
+ cpuCores,
655
+ ramGB,
656
+ storageGB,
657
+ effectivePricingRates
658
+ );
659
+ const hourCost = hourlyCostBreakdown.total.toFixed(2);
660
+ const displayValue = pricingView === "hourly" ? hourCost : formatPerSecondValue(hourlyCostBreakdown.total);
661
+ const pricingSuffix = pricingView === "hourly" ? "/ hour" : "/ sec";
662
+ const rateSuffix = pricingView === "hourly" ? "/h" : "/s";
663
+ const fmtRate = (v) => pricingView === "hourly" ? v.toFixed(2) : formatPerSecondValue(v);
458
664
  return /* @__PURE__ */ jsxs2("div", { className: cn("max-w-6xl mx-auto flex flex-col", className), children: [
459
665
  /* @__PURE__ */ jsxs2("div", { className: "mb-6 flex items-center gap-4 shrink-0", children: [
460
- onBack && /* @__PURE__ */ jsx2("button", { type: "button", onClick: onBack, className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-xl border border-border hover:bg-muted/50 transition-colors text-foreground", children: /* @__PURE__ */ jsx2(ArrowLeft, { className: "h-5 w-5" }) }),
666
+ onBack && /* @__PURE__ */ jsx2(
667
+ "button",
668
+ {
669
+ type: "button",
670
+ onClick: onBack,
671
+ className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-xl border border-border hover:bg-muted/50 transition-colors text-foreground",
672
+ children: /* @__PURE__ */ jsx2(ArrowLeft, { className: "h-5 w-5" })
673
+ }
674
+ ),
461
675
  /* @__PURE__ */ jsxs2("div", { children: [
462
676
  /* @__PURE__ */ jsx2("h1", { className: "text-3xl font-extrabold tracking-tight text-foreground mb-1", children: "Sandbox Provisioning" }),
463
677
  /* @__PURE__ */ jsx2("p", { className: "text-muted-foreground text-sm", children: "Select your stack, allocate resources, and configure your agent." })
@@ -466,16 +680,39 @@ function ProvisioningWizard({
466
680
  /* @__PURE__ */ jsxs2("div", { className: "grid grid-cols-12 gap-6 flex-1 min-h-0", children: [
467
681
  /* @__PURE__ */ jsxs2("div", { className: "col-span-12 xl:col-span-8 flex flex-col min-h-0", children: [
468
682
  isMultistep && /* @__PURE__ */ jsx2("div", { className: "flex items-center gap-2 mb-4 bg-card border border-border p-3 rounded-2xl mx-auto max-w-2xl justify-between shrink-0", children: [1, 2, 3].map((s) => /* @__PURE__ */ jsxs2("div", { className: "flex items-center", children: [
469
- /* @__PURE__ */ jsx2("div", { className: cn(
470
- "w-7 h-7 rounded-full flex items-center justify-center font-bold text-xs shrink-0 transition-all duration-200",
471
- currentStep === s ? "bg-primary text-primary-foreground ring-2 ring-primary/30 ring-offset-2 ring-offset-card shadow-sm" : currentStep > s ? "bg-primary text-primary-foreground" : "bg-muted border border-border text-muted-foreground"
472
- ), children: currentStep > s ? /* @__PURE__ */ jsx2(Check, { className: "h-3.5 w-3.5" }) : s }),
473
- /* @__PURE__ */ jsxs2("span", { className: cn("ml-2 sm:ml-3 font-bold text-sm tracking-tight hidden sm:inline transition-colors duration-200", currentStep === s ? "text-foreground" : currentStep > s ? "text-primary" : "text-muted-foreground"), children: [
474
- s === 1 && "Environment",
475
- s === 2 && "Resources",
476
- s === 3 && "AI Agent"
477
- ] }),
478
- s < 3 && /* @__PURE__ */ jsx2("div", { className: cn("w-4 sm:w-8 h-0.5 mx-2 sm:mx-4 rounded-full transition-colors duration-300", currentStep > s ? "bg-primary" : "bg-border") })
683
+ /* @__PURE__ */ jsx2(
684
+ "div",
685
+ {
686
+ className: cn(
687
+ "w-7 h-7 rounded-full flex items-center justify-center font-bold text-xs shrink-0 transition-all duration-200",
688
+ currentStep === s ? "bg-primary text-primary-foreground ring-2 ring-primary/30 ring-offset-2 ring-offset-card shadow-sm" : currentStep > s ? "bg-primary text-primary-foreground" : "bg-muted border border-border text-muted-foreground"
689
+ ),
690
+ children: currentStep > s ? /* @__PURE__ */ jsx2(Check, { className: "h-3.5 w-3.5" }) : s
691
+ }
692
+ ),
693
+ /* @__PURE__ */ jsxs2(
694
+ "span",
695
+ {
696
+ className: cn(
697
+ "ml-2 sm:ml-3 font-bold text-sm tracking-tight hidden sm:inline transition-colors duration-200",
698
+ currentStep === s ? "text-foreground" : currentStep > s ? "text-primary" : "text-muted-foreground"
699
+ ),
700
+ children: [
701
+ s === 1 && "Environment",
702
+ s === 2 && "Resources",
703
+ s === 3 && "AI Agent"
704
+ ]
705
+ }
706
+ ),
707
+ s < 3 && /* @__PURE__ */ jsx2(
708
+ "div",
709
+ {
710
+ className: cn(
711
+ "w-4 sm:w-8 h-0.5 mx-2 sm:mx-4 rounded-full transition-colors duration-300",
712
+ currentStep > s ? "bg-primary" : "bg-border"
713
+ )
714
+ }
715
+ )
479
716
  ] }, s)) }),
480
717
  dc && isMultistep && /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between bg-card border border-border rounded-2xl px-4 py-3 shrink-0", children: [
481
718
  /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 text-sm", children: [
@@ -492,7 +729,11 @@ function ProvisioningWizard({
492
729
  setCpuCores(Math.min(4, cpuMax));
493
730
  setRamGB(Math.min(16, ramMax));
494
731
  setStorageGB(Math.min(128, storageMax));
495
- setModelTier("claude-sonnet");
732
+ const resetOptions = modelOptions ?? DEFAULT_MODEL_OPTIONS;
733
+ const firstAvailable = resetOptions.find(
734
+ (o) => !o.disabled
735
+ );
736
+ setModelTier(firstAvailable?.value ?? DEFAULT_MODEL_TIER);
496
737
  setSystemPrompt("");
497
738
  setName("");
498
739
  setGitUrl("");
@@ -501,6 +742,7 @@ function ProvisioningWizard({
501
742
  setBare(false);
502
743
  setStartupScriptIds([]);
503
744
  setActivePreset(null);
745
+ setPricingView("hourly");
504
746
  },
505
747
  className: "text-xs font-bold text-primary hover:text-primary/70 transition-colors",
506
748
  children: "Start from scratch"
@@ -530,10 +772,16 @@ function ProvisioningWizard({
530
772
  selectedEnv === env.id && /* @__PURE__ */ jsx2("div", { className: "absolute inset-0 bg-gradient-to-br from-primary/8 to-transparent pointer-events-none" }),
531
773
  /* @__PURE__ */ jsxs2("div", { className: "flex justify-between items-start mb-3 relative z-10", children: [
532
774
  /* @__PURE__ */ jsx2("div", { className: "w-10 h-10 rounded-full flex items-center justify-center bg-muted/50 border border-border shadow-inner", children: env.icon }),
533
- /* @__PURE__ */ jsx2("div", { className: cn(
534
- "w-5 h-5 rounded-full border-2 flex items-center justify-center transition-all duration-200",
535
- selectedEnv === env.id ? "border-primary bg-primary" : "border-border group-hover:border-primary/40"
536
- ), children: selectedEnv === env.id && /* @__PURE__ */ jsx2(Check, { className: "h-3 w-3 text-primary-foreground animate-in zoom-in duration-200" }) })
775
+ /* @__PURE__ */ jsx2(
776
+ "div",
777
+ {
778
+ className: cn(
779
+ "w-5 h-5 rounded-full border-2 flex items-center justify-center transition-all duration-200",
780
+ selectedEnv === env.id ? "border-primary bg-primary" : "border-border group-hover:border-primary/40"
781
+ ),
782
+ children: selectedEnv === env.id && /* @__PURE__ */ jsx2(Check, { className: "h-3 w-3 text-primary-foreground animate-in zoom-in duration-200" })
783
+ }
784
+ )
537
785
  ] }),
538
786
  /* @__PURE__ */ jsx2("h3", { className: "font-bold text-sm mb-0.5 text-foreground relative z-10", children: env.name }),
539
787
  /* @__PURE__ */ jsx2("p", { className: "text-xs text-muted-foreground leading-relaxed relative z-10", children: env.description })
@@ -550,59 +798,110 @@ function ProvisioningWizard({
550
798
  /* @__PURE__ */ jsxs2("div", { className: "mb-6", children: [
551
799
  /* @__PURE__ */ jsx2("label", { className: "block font-label text-xs font-bold uppercase tracking-widest text-muted-foreground mb-3", children: "Compute Presets" }),
552
800
  /* @__PURE__ */ jsx2("div", { className: "grid grid-cols-3 gap-3", children: presets.map((p) => {
553
- const active = activePreset === p.name;
554
- return /* @__PURE__ */ jsxs2("button", { type: "button", onClick: () => applyPreset(p.name, p.cpu, p.ram, p.storage), className: cn("p-3 rounded-[14px] transition-all duration-200 text-center group border", active ? "bg-primary/5 border-primary ring-1 ring-primary/20 shadow-sm" : "bg-card border-border hover:border-primary/30 hover:shadow-sm active:scale-[0.97]"), children: [
555
- /* @__PURE__ */ jsx2("div", { className: cn("font-bold text-sm transition-colors duration-200", active ? "text-primary" : "text-foreground"), children: p.name }),
556
- /* @__PURE__ */ jsxs2("div", { className: "text-xs text-muted-foreground mt-0.5 font-mono", children: [
557
- p.cpu,
558
- "C / ",
559
- p.ram,
560
- "G / ",
561
- p.storage,
562
- "G"
563
- ] })
564
- ] }, p.name);
801
+ const active = activePreset === p.name && !p.locked;
802
+ return /* @__PURE__ */ jsxs2(
803
+ "button",
804
+ {
805
+ type: "button",
806
+ onClick: () => !p.locked && applyPreset(p.name, p.cpu, p.ram, p.storage),
807
+ disabled: p.locked,
808
+ className: cn(
809
+ "p-3 rounded-[14px] transition-all duration-200 text-center group border relative",
810
+ active ? "bg-primary/5 border-primary ring-1 ring-primary/20 shadow-sm" : p.locked ? "bg-muted/30 border-border opacity-60 cursor-not-allowed" : "bg-card border-border hover:border-primary/30 hover:shadow-sm active:scale-[0.97]"
811
+ ),
812
+ children: [
813
+ p.locked && /* @__PURE__ */ jsx2("div", { className: "absolute -top-1.5 -right-1.5 bg-primary text-primary-foreground text-[9px] font-bold px-1.5 py-0.5 rounded-full uppercase tracking-wider", children: p.unlockLabel }),
814
+ /* @__PURE__ */ jsx2(
815
+ "div",
816
+ {
817
+ className: cn(
818
+ "font-bold text-sm transition-colors duration-200",
819
+ active ? "text-primary" : p.locked ? "text-muted-foreground" : "text-foreground"
820
+ ),
821
+ children: p.name
822
+ }
823
+ ),
824
+ /* @__PURE__ */ jsxs2("div", { className: "text-xs text-muted-foreground mt-0.5 font-mono", children: [
825
+ p.cpu,
826
+ " vCPU",
827
+ p.cpu === 1 ? "" : "s",
828
+ " / ",
829
+ p.ram,
830
+ "GB /",
831
+ " ",
832
+ p.storage,
833
+ "GB"
834
+ ] })
835
+ ]
836
+ },
837
+ p.name
838
+ );
565
839
  }) })
566
840
  ] }),
567
841
  /* @__PURE__ */ jsx2("div", { className: "space-y-6", children: [
568
- { label: "Compute Cores (CPU)", value: cpuCores, setter: setCpuCores, min: CPU_MIN, max: cpuMax, step: 0.5, unit: "vCPUs" },
569
- { label: "Memory (RAM)", value: ramGB, setter: setRamGB, min: RAM_MIN, max: ramMax, step: 1, unit: "GB" },
570
- { label: "Ephemeral Storage", value: storageGB, setter: setStorageGB, min: STORAGE_MIN, max: storageMax, step: 8, unit: "GB" }
571
- ].map(({ label, value, setter, min, max, step: s, unit }) => /* @__PURE__ */ jsxs2("div", { children: [
572
- /* @__PURE__ */ jsxs2("div", { className: "flex justify-between items-end border-b border-border pb-1.5 mb-2", children: [
573
- /* @__PURE__ */ jsx2("label", { className: "font-label text-xs font-bold uppercase tracking-widest text-muted-foreground", children: label }),
574
- /* @__PURE__ */ jsxs2("span", { className: "text-xl font-bold text-foreground tracking-tight", children: [
575
- value,
576
- " ",
577
- /* @__PURE__ */ jsx2("span", { className: "text-xs text-primary ml-1", children: unit })
578
- ] })
579
- ] }),
580
- /* @__PURE__ */ jsx2(
581
- "input",
582
- {
583
- type: "range",
584
- min,
585
- max,
586
- step: s,
587
- value,
588
- onChange: (e) => {
589
- setter(+e.target.value);
590
- setActivePreset(null);
591
- },
592
- className: "w-full h-2 rounded-full appearance-none cursor-pointer accent-primary [&::-webkit-slider-runnable-track]:bg-border [&::-webkit-slider-runnable-track]:rounded-full [&::-webkit-slider-runnable-track]:h-2 [&::-moz-range-track]:bg-border [&::-moz-range-track]:rounded-full [&::-moz-range-track]:h-2 [&::-webkit-slider-thumb]:bg-primary [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:h-5 [&::-webkit-slider-thumb]:w-5 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:-mt-[6px] [&::-webkit-slider-thumb]:shadow-md [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-primary-foreground [&::-webkit-slider-thumb]:transition-transform [&::-webkit-slider-thumb]:hover:scale-110"
593
- }
594
- ),
595
- /* @__PURE__ */ jsxs2("div", { className: "flex justify-between text-[10px] font-mono text-muted-foreground/60 mt-1", children: [
596
- /* @__PURE__ */ jsxs2("span", { children: [
597
- min,
598
- unit
599
- ] }),
600
- /* @__PURE__ */ jsxs2("span", { children: [
601
- max,
602
- unit
603
- ] })
604
- ] })
605
- ] }, label)) })
842
+ {
843
+ label: "Compute Cores (CPU)",
844
+ value: cpuCores,
845
+ setter: setCpuCores,
846
+ min: CPU_MIN,
847
+ max: cpuMax,
848
+ step: 0.5,
849
+ unit: "vCPU"
850
+ },
851
+ {
852
+ label: "Memory (RAM)",
853
+ value: ramGB,
854
+ setter: setRamGB,
855
+ min: RAM_MIN,
856
+ max: ramMax,
857
+ step: 1,
858
+ unit: "GB"
859
+ },
860
+ {
861
+ label: "Ephemeral Storage",
862
+ value: storageGB,
863
+ setter: setStorageGB,
864
+ min: STORAGE_MIN,
865
+ max: storageMax,
866
+ step: 8,
867
+ unit: "GB"
868
+ }
869
+ ].map(
870
+ ({ label, value, setter, min, max, step: s, unit }) => {
871
+ const displayUnit = unit === "vCPU" ? `${value} vCPU${value === 1 ? "" : "s"}` : `${value}${unit}`;
872
+ return /* @__PURE__ */ jsxs2("div", { children: [
873
+ /* @__PURE__ */ jsxs2("div", { className: "flex justify-between items-end border-b border-border pb-1.5 mb-2", children: [
874
+ /* @__PURE__ */ jsx2("label", { className: "font-label text-xs font-bold uppercase tracking-widest text-muted-foreground", children: label }),
875
+ /* @__PURE__ */ jsx2("span", { className: "text-xl font-bold text-foreground tracking-tight", children: displayUnit })
876
+ ] }),
877
+ /* @__PURE__ */ jsx2(
878
+ "input",
879
+ {
880
+ type: "range",
881
+ min,
882
+ max,
883
+ step: s,
884
+ value,
885
+ onChange: (e) => {
886
+ setter(+e.target.value);
887
+ setActivePreset(null);
888
+ },
889
+ className: "w-full h-2 rounded-full appearance-none cursor-pointer accent-primary [&::-webkit-slider-runnable-track]:bg-border [&::-webkit-slider-runnable-track]:rounded-full [&::-webkit-slider-runnable-track]:h-2 [&::-moz-range-track]:bg-border [&::-moz-range-track]:rounded-full [&::-moz-range-track]:h-2 [&::-webkit-slider-thumb]:bg-primary [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:h-5 [&::-webkit-slider-thumb]:w-5 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:-mt-[6px] [&::-webkit-slider-thumb]:shadow-md [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-primary-foreground [&::-webkit-slider-thumb]:transition-transform [&::-webkit-slider-thumb]:hover:scale-110"
890
+ }
891
+ ),
892
+ /* @__PURE__ */ jsxs2("div", { className: "flex justify-between text-[10px] font-mono text-muted-foreground/60 mt-1", children: [
893
+ /* @__PURE__ */ jsxs2("span", { children: [
894
+ min,
895
+ unit === "vCPU" ? min === 1 ? " vCPU" : " vCPUs" : unit
896
+ ] }),
897
+ /* @__PURE__ */ jsxs2("span", { children: [
898
+ max,
899
+ unit === "vCPU" ? max === 1 ? " vCPU" : " vCPUs" : unit
900
+ ] })
901
+ ] })
902
+ ] }, label);
903
+ }
904
+ ) })
606
905
  ] }) }),
607
906
  (!isMultistep || currentStep === 3) && /* @__PURE__ */ jsx2(React2.Fragment, { children: /* @__PURE__ */ jsxs2("section", { className: "bg-card border border-border rounded-[24px] p-6 shadow-2xl relative overflow-hidden animate-in fade-in slide-in-from-bottom-4 duration-300", children: [
608
907
  /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-3 mb-5", children: [
@@ -612,19 +911,28 @@ function ProvisioningWizard({
612
911
  /* @__PURE__ */ jsxs2("div", { className: "space-y-5", children: [
613
912
  /* @__PURE__ */ jsxs2("div", { children: [
614
913
  /* @__PURE__ */ jsx2("label", { className: "block font-label text-xs font-bold uppercase tracking-widest text-muted-foreground mb-2", children: "Model Engine" }),
615
- /* @__PURE__ */ jsxs2(
914
+ /* @__PURE__ */ jsx2(
616
915
  "select",
617
916
  {
618
917
  value: modelTier,
619
918
  onChange: (e) => setModelTier(e.target.value),
620
- className: "w-full bg-card border border-border rounded-xl h-12 px-4 font-bold text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent appearance-none",
621
- children: [
622
- /* @__PURE__ */ jsx2("option", { value: "llama-3-8b", className: "bg-gray-900", children: "Llama-3-8B-Instruct (Lightweight)" }),
623
- /* @__PURE__ */ jsx2("option", { value: "mistral-7b", className: "bg-gray-900", children: "Mistral-7B-v0.2 (Efficient)" }),
624
- /* @__PURE__ */ jsx2("option", { value: "claude-sonnet", className: "bg-gray-900", children: "Claude Sonnet 4.6 (Highly Capable)" })
625
- ]
919
+ disabled: modelOptions && modelOptions.filter((o) => !o.disabled).length === 0,
920
+ className: "w-full bg-card border border-border rounded-xl h-12 px-4 font-bold text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent appearance-none disabled:opacity-50 disabled:cursor-not-allowed",
921
+ children: (modelOptions ?? DEFAULT_MODEL_OPTIONS).map(
922
+ (option) => /* @__PURE__ */ jsx2(
923
+ "option",
924
+ {
925
+ value: option.value,
926
+ disabled: option.disabled,
927
+ className: "bg-gray-900",
928
+ children: option.label
929
+ },
930
+ option.value
931
+ )
932
+ )
626
933
  }
627
- )
934
+ ),
935
+ modelOptions && modelOptions.length > 0 && modelOptions.every((o) => o.disabled) && /* @__PURE__ */ jsx2("p", { className: "text-xs text-muted-foreground mt-2", children: "All model options are currently disabled. Please upgrade your plan or contact support." })
628
936
  ] }),
629
937
  /* @__PURE__ */ jsxs2("div", { children: [
630
938
  /* @__PURE__ */ jsx2("label", { className: "block font-label text-xs font-bold uppercase tracking-widest text-muted-foreground mb-2", children: "Core Directives (System Prompt)" }),
@@ -675,12 +983,22 @@ function ProvisioningWizard({
675
983
  {
676
984
  value: driver,
677
985
  onChange: (e) => {
678
- if (VALID_DRIVERS.has(e.target.value)) setDriver(e.target.value);
986
+ if (VALID_DRIVERS.has(e.target.value))
987
+ setDriver(
988
+ e.target.value
989
+ );
679
990
  },
680
991
  className: "w-full bg-card border border-border rounded-xl h-12 px-4 font-bold text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent appearance-none",
681
992
  children: [
682
993
  /* @__PURE__ */ jsx2("option", { value: "docker", className: "bg-gray-900", children: "Docker container (Default)" }),
683
- /* @__PURE__ */ jsx2("option", { value: "firecracker", className: "bg-gray-900", children: "Firecracker microVM (Secure)" }),
994
+ /* @__PURE__ */ jsx2(
995
+ "option",
996
+ {
997
+ value: "firecracker",
998
+ className: "bg-gray-900",
999
+ children: "Firecracker microVM (Secure)"
1000
+ }
1001
+ ),
684
1002
  /* @__PURE__ */ jsx2("option", { value: "tangle", className: "bg-gray-900", children: "Tangle Distributed Node" })
685
1003
  ]
686
1004
  }
@@ -703,10 +1021,21 @@ function ProvisioningWizard({
703
1021
  /* @__PURE__ */ jsxs2("div", { children: [
704
1022
  /* @__PURE__ */ jsxs2("div", { className: "flex justify-between items-center mb-2", children: [
705
1023
  /* @__PURE__ */ jsx2("label", { className: "block font-label text-xs font-bold uppercase tracking-widest text-muted-foreground", children: "Environment Variables" }),
706
- /* @__PURE__ */ jsxs2("button", { type: "button", onClick: () => setEnvVars([...envVars, { key: "", value: "" }]), className: "flex items-center gap-1 text-xs text-primary hover:text-primary/70 transition-colors font-bold", children: [
707
- /* @__PURE__ */ jsx2(Plus, { className: "h-3 w-3" }),
708
- " Add Var"
709
- ] })
1024
+ /* @__PURE__ */ jsxs2(
1025
+ "button",
1026
+ {
1027
+ type: "button",
1028
+ onClick: () => setEnvVars([
1029
+ ...envVars,
1030
+ { key: "", value: "" }
1031
+ ]),
1032
+ className: "flex items-center gap-1 text-xs text-primary hover:text-primary/70 transition-colors font-bold",
1033
+ children: [
1034
+ /* @__PURE__ */ jsx2(Plus, { className: "h-3 w-3" }),
1035
+ " Add Var"
1036
+ ]
1037
+ }
1038
+ )
710
1039
  ] }),
711
1040
  /* @__PURE__ */ jsxs2("div", { className: "space-y-2", children: [
712
1041
  envVars.map((env, i) => /* @__PURE__ */ jsxs2("div", { className: "flex gap-2", children: [
@@ -715,7 +1044,11 @@ function ProvisioningWizard({
715
1044
  {
716
1045
  type: "text",
717
1046
  value: env.key,
718
- onChange: (e) => setEnvVars(envVars.map((v, idx) => idx === i ? { ...v, key: e.target.value } : v)),
1047
+ onChange: (e) => setEnvVars(
1048
+ envVars.map(
1049
+ (v, idx) => idx === i ? { ...v, key: e.target.value } : v
1050
+ )
1051
+ ),
719
1052
  className: "flex-1 bg-card border border-border rounded-xl h-10 px-3 font-mono text-sm text-foreground focus:outline-none focus:ring-1 focus:ring-primary placeholder:text-muted-foreground",
720
1053
  placeholder: "API_KEY"
721
1054
  }
@@ -725,12 +1058,26 @@ function ProvisioningWizard({
725
1058
  {
726
1059
  type: "password",
727
1060
  value: env.value,
728
- onChange: (e) => setEnvVars(envVars.map((v, idx) => idx === i ? { ...v, value: e.target.value } : v)),
1061
+ onChange: (e) => setEnvVars(
1062
+ envVars.map(
1063
+ (v, idx) => idx === i ? { ...v, value: e.target.value } : v
1064
+ )
1065
+ ),
729
1066
  className: "flex-[2] bg-card border border-border rounded-xl h-10 px-3 font-mono text-sm text-foreground focus:outline-none focus:ring-1 focus:ring-primary placeholder:text-muted-foreground",
730
1067
  placeholder: "sk-xxxxxxxxxxx"
731
1068
  }
732
1069
  ),
733
- /* @__PURE__ */ jsx2("button", { type: "button", onClick: () => setEnvVars(envVars.filter((_, idx) => idx !== i)), className: "h-10 w-10 flex items-center justify-center shrink-0 rounded-xl bg-card border border-border text-red-400 hover:bg-red-500/10 hover:border-red-500/30 transition-colors", children: /* @__PURE__ */ jsx2(Trash2, { className: "h-4 w-4" }) })
1070
+ /* @__PURE__ */ jsx2(
1071
+ "button",
1072
+ {
1073
+ type: "button",
1074
+ onClick: () => setEnvVars(
1075
+ envVars.filter((_, idx) => idx !== i)
1076
+ ),
1077
+ className: "h-10 w-10 flex items-center justify-center shrink-0 rounded-xl bg-card border border-border text-red-400 hover:bg-red-500/10 hover:border-red-500/30 transition-colors",
1078
+ children: /* @__PURE__ */ jsx2(Trash2, { className: "h-4 w-4" })
1079
+ }
1080
+ )
734
1081
  ] }, i)),
735
1082
  envVars.length === 0 && /* @__PURE__ */ jsx2("div", { className: "text-center p-3 bg-card border border-border rounded-xl text-muted-foreground/60 text-sm italic", children: "No environment variables set" })
736
1083
  ] })
@@ -738,36 +1085,82 @@ function ProvisioningWizard({
738
1085
  availableScripts.length > 0 && /* @__PURE__ */ jsxs2("div", { children: [
739
1086
  /* @__PURE__ */ jsx2("div", { className: "text-xs font-bold uppercase tracking-widest text-muted-foreground mb-2", children: "Startup Scripts" }),
740
1087
  /* @__PURE__ */ jsx2("div", { className: "space-y-2", children: availableScripts.filter((s) => s.enabled).map((script) => {
741
- const selected = startupScriptIds.includes(script.id);
742
- return /* @__PURE__ */ jsxs2("label", { className: "flex items-start gap-3 cursor-pointer group rounded-lg border border-border p-3 transition-colors hover:border-primary/30", children: [
743
- /* @__PURE__ */ jsx2(
744
- "input",
745
- {
746
- type: "checkbox",
747
- checked: selected,
748
- onChange: () => setStartupScriptIds(
749
- (prev) => selected ? prev.filter((id) => id !== script.id) : [...prev, script.id]
1088
+ const selected = startupScriptIds.includes(
1089
+ script.id
1090
+ );
1091
+ return /* @__PURE__ */ jsxs2(
1092
+ "label",
1093
+ {
1094
+ className: "flex items-start gap-3 cursor-pointer group rounded-lg border border-border p-3 transition-colors hover:border-primary/30",
1095
+ children: [
1096
+ /* @__PURE__ */ jsx2(
1097
+ "input",
1098
+ {
1099
+ type: "checkbox",
1100
+ checked: selected,
1101
+ onChange: () => setStartupScriptIds(
1102
+ (prev) => selected ? prev.filter(
1103
+ (id) => id !== script.id
1104
+ ) : [...prev, script.id]
1105
+ ),
1106
+ className: "mt-0.5 h-4 w-4 rounded border-border text-primary focus:ring-primary/30"
1107
+ }
750
1108
  ),
751
- className: "mt-0.5 h-4 w-4 rounded border-border text-primary focus:ring-primary/30"
752
- }
753
- ),
754
- /* @__PURE__ */ jsxs2("div", { className: "flex-1 min-w-0", children: [
755
- /* @__PURE__ */ jsx2("div", { className: "text-sm font-medium text-foreground group-hover:text-primary transition-colors", children: script.name }),
756
- script.description && /* @__PURE__ */ jsx2("div", { className: "text-xs text-muted-foreground mt-0.5", children: script.description }),
757
- script.injectSecrets.length > 0 && /* @__PURE__ */ jsx2("div", { className: "flex flex-wrap gap-1 mt-1.5", children: script.injectSecrets.map((s) => /* @__PURE__ */ jsxs2("span", { className: "inline-flex items-center gap-0.5 rounded-full bg-muted px-2 py-0.5 text-[10px] text-muted-foreground", children: [
758
- /* @__PURE__ */ jsxs2("svg", { className: "h-2.5 w-2.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
759
- /* @__PURE__ */ jsx2("rect", { x: "3", y: "11", width: "18", height: "11", rx: "2", ry: "2" }),
760
- /* @__PURE__ */ jsx2("path", { d: "M7 11V7a5 5 0 0 1 10 0v4" })
761
- ] }),
762
- s
763
- ] }, s)) })
764
- ] })
765
- ] }, script.id);
1109
+ /* @__PURE__ */ jsxs2("div", { className: "flex-1 min-w-0", children: [
1110
+ /* @__PURE__ */ jsx2("div", { className: "text-sm font-medium text-foreground group-hover:text-primary transition-colors", children: script.name }),
1111
+ script.description && /* @__PURE__ */ jsx2("div", { className: "text-xs text-muted-foreground mt-0.5", children: script.description }),
1112
+ script.injectSecrets.length > 0 && /* @__PURE__ */ jsx2("div", { className: "flex flex-wrap gap-1 mt-1.5", children: script.injectSecrets.map((s) => /* @__PURE__ */ jsxs2(
1113
+ "span",
1114
+ {
1115
+ className: "inline-flex items-center gap-0.5 rounded-full bg-muted px-2 py-0.5 text-[10px] text-muted-foreground",
1116
+ children: [
1117
+ /* @__PURE__ */ jsxs2(
1118
+ "svg",
1119
+ {
1120
+ className: "h-2.5 w-2.5",
1121
+ viewBox: "0 0 24 24",
1122
+ fill: "none",
1123
+ stroke: "currentColor",
1124
+ strokeWidth: "2",
1125
+ children: [
1126
+ /* @__PURE__ */ jsx2(
1127
+ "rect",
1128
+ {
1129
+ x: "3",
1130
+ y: "11",
1131
+ width: "18",
1132
+ height: "11",
1133
+ rx: "2",
1134
+ ry: "2"
1135
+ }
1136
+ ),
1137
+ /* @__PURE__ */ jsx2("path", { d: "M7 11V7a5 5 0 0 1 10 0v4" })
1138
+ ]
1139
+ }
1140
+ ),
1141
+ s
1142
+ ]
1143
+ },
1144
+ s
1145
+ )) })
1146
+ ] })
1147
+ ]
1148
+ },
1149
+ script.id
1150
+ );
766
1151
  }) })
767
1152
  ] }),
768
1153
  /* @__PURE__ */ jsx2("div", { className: "pt-2 border-t border-border", children: /* @__PURE__ */ jsxs2("label", { className: "flex items-center gap-3 cursor-pointer group", children: [
769
1154
  /* @__PURE__ */ jsxs2("div", { className: "relative flex items-center justify-center shrink-0", children: [
770
- /* @__PURE__ */ jsx2("input", { type: "checkbox", className: "sr-only peer", checked: bare, onChange: (e) => setBare(e.target.checked) }),
1155
+ /* @__PURE__ */ jsx2(
1156
+ "input",
1157
+ {
1158
+ type: "checkbox",
1159
+ className: "sr-only peer",
1160
+ checked: bare,
1161
+ onChange: (e) => setBare(e.target.checked)
1162
+ }
1163
+ ),
771
1164
  /* @__PURE__ */ jsx2("div", { className: "w-10 h-6 bg-muted peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary hover:bg-muted/80 transition-colors" })
772
1165
  ] }),
773
1166
  /* @__PURE__ */ jsxs2("div", { children: [
@@ -782,93 +1175,97 @@ function ProvisioningWizard({
782
1175
  ] })
783
1176
  ] }),
784
1177
  /* @__PURE__ */ jsxs2("div", { className: "col-span-12 xl:col-span-4 sticky top-4 space-y-4", children: [
785
- /* @__PURE__ */ jsxs2("div", { className: "bg-card border border-primary/15 rounded-[24px] overflow-hidden shadow-2xl relative", children: [
786
- /* @__PURE__ */ jsx2("div", { className: "absolute inset-0 bg-[radial-gradient(circle_at_top,rgba(173,163,255,0.05)_0,transparent_100%)] pointer-events-none" }),
787
- /* @__PURE__ */ jsxs2("div", { className: "bg-muted/50 border-b border-border px-4 py-3 flex items-center gap-3", children: [
788
- /* @__PURE__ */ jsxs2("div", { className: "flex gap-2", children: [
789
- /* @__PURE__ */ jsx2("div", { className: "h-3 w-3 rounded-full bg-[#ff5f56]/80" }),
790
- /* @__PURE__ */ jsx2("div", { className: "h-3 w-3 rounded-full bg-[#ffbd2e]/80" }),
791
- /* @__PURE__ */ jsx2("div", { className: "h-3 w-3 rounded-full bg-[#27c93f]/80" })
792
- ] }),
793
- /* @__PURE__ */ jsx2("div", { className: "font-mono text-[10px] text-muted-foreground uppercase tracking-widest ml-2 border-l border-border pl-3", children: "deploy_sequence.sh" })
794
- ] }),
795
- /* @__PURE__ */ jsxs2("div", { className: "p-5 font-mono text-xs space-y-3 min-h-[240px] relative z-10 text-[13px]", children: [
796
- /* @__PURE__ */ jsxs2("div", { className: "text-green-400", children: [
797
- "root@tangle:~# ",
798
- /* @__PURE__ */ jsx2("span", { className: "text-foreground/80", children: "tangle-cli provision --new" })
799
- ] }),
800
- /* @__PURE__ */ jsx2("div", { className: "text-muted-foreground/70", children: "Initializing deployment handshake..." }),
801
- /* @__PURE__ */ jsxs2("div", { className: "text-foreground/70", children: [
802
- /* @__PURE__ */ jsx2("span", { className: "text-primary mr-2", children: "\u2713" }),
803
- " Bound Platform: ",
804
- /* @__PURE__ */ jsx2("span", { className: "text-foreground font-bold animate-in fade-in duration-300", children: environments.find((e) => e.id === selectedEnv)?.name ?? "Node.js" }, `env-${selectedEnv}`)
805
- ] }),
806
- /* @__PURE__ */ jsxs2("div", { className: "text-foreground/70", children: [
807
- /* @__PURE__ */ jsx2("span", { className: "text-primary mr-2", children: "\u2713" }),
808
- " Allocation CPU: ",
809
- /* @__PURE__ */ jsxs2("span", { className: "text-foreground font-bold animate-in fade-in duration-300", children: [
810
- cpuCores,
811
- " Cores"
812
- ] }, `cpu-${cpuCores}`)
813
- ] }),
814
- /* @__PURE__ */ jsxs2("div", { className: "text-foreground/70", children: [
815
- /* @__PURE__ */ jsx2("span", { className: "text-primary mr-2", children: "\u2713" }),
816
- " Allocation RAM: ",
817
- /* @__PURE__ */ jsxs2("span", { className: "text-foreground font-bold animate-in fade-in duration-300", children: [
818
- ramGB,
819
- "GB"
820
- ] }, `ram-${ramGB}`)
821
- ] }),
822
- /* @__PURE__ */ jsxs2("div", { className: "text-foreground/70", children: [
823
- /* @__PURE__ */ jsx2("span", { className: "text-primary mr-2", children: "\u2713" }),
824
- " Mounted Storage: ",
825
- /* @__PURE__ */ jsxs2("span", { className: "text-foreground font-bold animate-in fade-in duration-300", children: [
826
- storageGB,
827
- "GB NVMe"
828
- ] }, `disk-${storageGB}`)
829
- ] }),
830
- /* @__PURE__ */ jsxs2("div", { className: "pt-3 flex items-center gap-3", children: [
831
- /* @__PURE__ */ jsx2("div", { className: "w-2 h-4 bg-primary animate-pulse" }),
832
- /* @__PURE__ */ jsx2("span", { className: "text-muted-foreground", children: "Awaiting user confirmation..." })
833
- ] })
834
- ] })
835
- ] }),
836
1178
  /* @__PURE__ */ jsxs2("div", { className: "p-6 rounded-[24px] bg-card border border-primary/15 relative overflow-hidden", children: [
837
1179
  /* @__PURE__ */ jsx2("div", { className: "hidden" }),
838
1180
  /* @__PURE__ */ jsxs2("div", { className: "flex justify-between items-center mb-4 relative z-10", children: [
839
1181
  /* @__PURE__ */ jsx2("span", { className: "font-label text-xs font-bold uppercase tracking-widest text-muted-foreground", children: "Run Cost" }),
840
- /* @__PURE__ */ jsx2("div", { className: "h-7 w-7 rounded-full bg-muted/30 flex items-center justify-center border border-border", children: /* @__PURE__ */ jsx2(Info, { className: "h-3.5 w-3.5 text-primary" }) })
1182
+ /* @__PURE__ */ jsxs2(
1183
+ "div",
1184
+ {
1185
+ role: "group",
1186
+ "aria-label": "Pricing view",
1187
+ className: "inline-flex items-center rounded-full border border-border bg-muted/50 p-0.5",
1188
+ children: [
1189
+ /* @__PURE__ */ jsx2(
1190
+ "button",
1191
+ {
1192
+ type: "button",
1193
+ "aria-pressed": pricingView === "hourly",
1194
+ onClick: () => setPricingView("hourly"),
1195
+ className: cn(
1196
+ "rounded-full px-2.5 py-0.5 text-[10px] font-bold transition-all",
1197
+ pricingView === "hourly" ? "bg-card text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"
1198
+ ),
1199
+ children: "Per Hour"
1200
+ }
1201
+ ),
1202
+ /* @__PURE__ */ jsx2(
1203
+ "button",
1204
+ {
1205
+ type: "button",
1206
+ "aria-pressed": pricingView === "perSecond",
1207
+ onClick: () => setPricingView("perSecond"),
1208
+ className: cn(
1209
+ "rounded-full px-2.5 py-0.5 text-[10px] font-bold transition-all",
1210
+ pricingView === "perSecond" ? "bg-card text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"
1211
+ ),
1212
+ children: "Per Second"
1213
+ }
1214
+ )
1215
+ ]
1216
+ }
1217
+ )
841
1218
  ] }),
842
1219
  /* @__PURE__ */ jsxs2("div", { className: "flex items-baseline gap-2 mb-5 relative z-10", children: [
843
- /* @__PURE__ */ jsxs2("span", { className: "text-4xl font-black text-foreground tracking-tighter animate-in fade-in duration-200", children: [
844
- "$",
845
- hourCost
846
- ] }, hourCost),
847
- /* @__PURE__ */ jsx2("span", { className: "text-muted-foreground text-sm font-bold", children: "/ hour" })
1220
+ /* @__PURE__ */ jsxs2(
1221
+ "span",
1222
+ {
1223
+ className: cn(
1224
+ "font-black text-foreground tracking-tighter animate-in fade-in duration-200",
1225
+ pricingView === "hourly" ? "text-4xl" : "text-2xl"
1226
+ ),
1227
+ children: [
1228
+ "$",
1229
+ displayValue
1230
+ ]
1231
+ },
1232
+ pricingView
1233
+ ),
1234
+ /* @__PURE__ */ jsx2("span", { className: "text-muted-foreground text-sm font-bold", children: pricingSuffix })
848
1235
  ] }),
849
1236
  /* @__PURE__ */ jsxs2("div", { className: "space-y-2 relative z-10 bg-card border border-border rounded-xl p-3", children: [
850
1237
  /* @__PURE__ */ jsxs2("div", { className: "flex justify-between text-xs font-mono tracking-widest text-muted-foreground", children: [
851
1238
  /* @__PURE__ */ jsx2("span", { children: "COMPUTE" }),
852
1239
  /* @__PURE__ */ jsxs2("span", { className: "text-foreground", children: [
853
1240
  "$",
854
- (cpuCores * 0.045).toFixed(2),
855
- "/h"
1241
+ fmtRate(hourlyCostBreakdown.compute),
1242
+ rateSuffix
856
1243
  ] })
857
1244
  ] }),
858
1245
  /* @__PURE__ */ jsxs2("div", { className: "flex justify-between text-xs font-mono tracking-widest text-muted-foreground", children: [
859
1246
  /* @__PURE__ */ jsx2("span", { children: "MEMORY" }),
860
1247
  /* @__PURE__ */ jsxs2("span", { className: "text-foreground/80", children: [
861
1248
  "$",
862
- (ramGB * 5e-3).toFixed(2),
863
- "/h"
1249
+ fmtRate(hourlyCostBreakdown.memory),
1250
+ rateSuffix
864
1251
  ] })
865
1252
  ] }),
866
1253
  /* @__PURE__ */ jsxs2("div", { className: "flex justify-between text-xs font-mono tracking-widest text-muted-foreground", children: [
867
1254
  /* @__PURE__ */ jsx2("span", { children: "STORAGE" }),
868
1255
  /* @__PURE__ */ jsxs2("span", { className: "text-foreground/80", children: [
869
1256
  "$",
870
- (storageGB * 11e-4).toFixed(2),
871
- "/h"
1257
+ fmtRate(hourlyCostBreakdown.storage),
1258
+ rateSuffix
1259
+ ] })
1260
+ ] }),
1261
+ hourlyCostBreakdown.floorApplies && /* @__PURE__ */ jsxs2("div", { className: "flex justify-between text-xs font-mono tracking-widest text-primary border-t border-border pt-2", children: [
1262
+ /* @__PURE__ */ jsx2("span", { children: "MIN CHARGE" }),
1263
+ /* @__PURE__ */ jsxs2("span", { children: [
1264
+ "$",
1265
+ fmtRate(
1266
+ hourlyCostBreakdown.floor - hourlyCostBreakdown.lineSum
1267
+ ),
1268
+ rateSuffix
872
1269
  ] })
873
1270
  ] })
874
1271
  ] })
@@ -885,7 +1282,8 @@ function ProvisioningWizard({
885
1282
  onClick: () => setCurrentStep((s) => s + 1),
886
1283
  className: "w-full relative overflow-hidden h-12 bg-primary text-primary-foreground font-extrabold text-sm rounded-2xl hover:brightness-110 transition-all active:scale-[0.98] shadow-md",
887
1284
  children: [
888
- "Continue to ",
1285
+ "Continue to",
1286
+ " ",
889
1287
  currentStep === 1 ? "Resources" : "Agent Config"
890
1288
  ]
891
1289
  }
@@ -1839,46 +2237,64 @@ function SecretsPage({ apiClient, className }) {
1839
2237
  /* @__PURE__ */ jsx5(DialogTitle, { children: "Create Secret" }),
1840
2238
  /* @__PURE__ */ jsx5(DialogDescription, { children: "Secrets are automatically exposed as environment variables across all your new sandboxes." })
1841
2239
  ] }),
1842
- /* @__PURE__ */ jsxs5("div", { className: "space-y-4", children: [
1843
- /* @__PURE__ */ jsxs5("div", { children: [
1844
- /* @__PURE__ */ jsx5("label", { className: "block text-xs font-bold uppercase tracking-widest text-muted-foreground mb-2", children: "Name" }),
1845
- /* @__PURE__ */ jsx5(
1846
- "input",
1847
- {
1848
- type: "text",
1849
- value: newName,
1850
- onChange: (e) => setNewName(e.target.value.toUpperCase().replace(/[^A-Z0-9_]/g, "_")),
1851
- placeholder: "MY_SECRET_KEY",
1852
- className: "w-full rounded-md border border-border bg-card px-3 py-2.5 text-sm font-mono text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring"
1853
- }
1854
- )
1855
- ] }),
1856
- /* @__PURE__ */ jsxs5("div", { children: [
1857
- /* @__PURE__ */ jsx5("label", { className: "block text-xs font-bold uppercase tracking-widest text-muted-foreground mb-2", children: "Value" }),
1858
- /* @__PURE__ */ jsxs5("div", { className: "relative", children: [
1859
- /* @__PURE__ */ jsx5(
1860
- "input",
1861
- {
1862
- type: showValue ? "text" : "password",
1863
- value: newValue,
1864
- onChange: (e) => setNewValue(e.target.value),
1865
- placeholder: "Enter secret value...",
1866
- className: "w-full rounded-md border border-border bg-card px-3 py-2.5 pr-10 text-sm font-mono text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring"
1867
- }
1868
- ),
1869
- /* @__PURE__ */ jsx5(
1870
- "button",
1871
- {
1872
- type: "button",
1873
- onClick: () => setShowValue(!showValue),
1874
- className: "absolute right-2 top-1/2 -translate-y-1/2 p-1 text-muted-foreground hover:text-foreground",
1875
- children: showValue ? /* @__PURE__ */ jsx5(EyeOff, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx5(Eye, { className: "h-4 w-4" })
1876
- }
1877
- )
1878
- ] }),
1879
- /* @__PURE__ */ jsx5("p", { className: "mt-1.5 text-xs text-muted-foreground", children: "This value cannot be retrieved after creation." })
1880
- ] })
1881
- ] }),
2240
+ /* @__PURE__ */ jsxs5(
2241
+ "form",
2242
+ {
2243
+ onSubmit: (e) => {
2244
+ e.preventDefault();
2245
+ if (newName.trim() && newValue.trim() && !isCreating) handleCreate();
2246
+ },
2247
+ className: "space-y-4",
2248
+ children: [
2249
+ /* @__PURE__ */ jsxs5("div", { children: [
2250
+ /* @__PURE__ */ jsx5("label", { htmlFor: "secret-name", className: "block text-xs font-bold uppercase tracking-widest text-muted-foreground mb-2", children: "Name" }),
2251
+ /* @__PURE__ */ jsx5(
2252
+ "input",
2253
+ {
2254
+ id: "secret-name",
2255
+ name: "secret-name",
2256
+ type: "text",
2257
+ value: newName,
2258
+ onChange: (e) => setNewName(e.target.value.toUpperCase().replace(/[^A-Z0-9_]/g, "_")),
2259
+ placeholder: "MY_SECRET_KEY",
2260
+ autoComplete: "off",
2261
+ className: "w-full rounded-md border border-border bg-card px-3 py-2.5 text-sm font-mono text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring"
2262
+ }
2263
+ )
2264
+ ] }),
2265
+ /* @__PURE__ */ jsxs5("div", { children: [
2266
+ /* @__PURE__ */ jsx5("label", { htmlFor: "secret-value", className: "block text-xs font-bold uppercase tracking-widest text-muted-foreground mb-2", children: "Value" }),
2267
+ /* @__PURE__ */ jsxs5("div", { className: "relative", children: [
2268
+ /* @__PURE__ */ jsx5(
2269
+ "input",
2270
+ {
2271
+ id: "secret-value",
2272
+ name: "secret-value",
2273
+ type: showValue ? "text" : "password",
2274
+ value: newValue,
2275
+ onChange: (e) => setNewValue(e.target.value),
2276
+ placeholder: "Enter secret value...",
2277
+ autoComplete: "new-password",
2278
+ className: "w-full rounded-md border border-border bg-card px-3 py-2.5 pr-10 text-sm font-mono text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring"
2279
+ }
2280
+ ),
2281
+ /* @__PURE__ */ jsx5(
2282
+ "button",
2283
+ {
2284
+ type: "button",
2285
+ onClick: () => setShowValue(!showValue),
2286
+ className: "absolute right-2 top-1/2 -translate-y-1/2 p-1 text-muted-foreground hover:text-foreground",
2287
+ "aria-label": showValue ? "Hide value" : "Show value",
2288
+ children: showValue ? /* @__PURE__ */ jsx5(EyeOff, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx5(Eye, { className: "h-4 w-4" })
2289
+ }
2290
+ )
2291
+ ] }),
2292
+ /* @__PURE__ */ jsx5("p", { className: "mt-1.5 text-xs text-muted-foreground", children: "This value cannot be retrieved after creation." })
2293
+ ] }),
2294
+ /* @__PURE__ */ jsx5("button", { type: "submit", className: "hidden", tabIndex: -1, "aria-hidden": "true", children: "Submit" })
2295
+ ]
2296
+ }
2297
+ ),
1882
2298
  createError && /* @__PURE__ */ jsx5("p", { className: "mt-3 text-sm text-destructive", children: createError }),
1883
2299
  /* @__PURE__ */ jsxs5(DialogFooter, { children: [
1884
2300
  /* @__PURE__ */ jsx5(
@@ -1958,10 +2374,11 @@ function SecretsPage({ apiClient, className }) {
1958
2374
  {
1959
2375
  type: "button",
1960
2376
  onClick: () => setIsCreateOpen(true),
2377
+ "aria-label": "Create your first secret",
1961
2378
  className: "mt-6 inline-flex items-center gap-2 rounded-md bg-[var(--btn-primary-bg)] px-4 py-2 text-sm font-semibold text-[var(--btn-primary-text)] hover:bg-[var(--btn-primary-hover)] transition-colors active:scale-[0.97]",
1962
2379
  children: [
1963
2380
  /* @__PURE__ */ jsx5(Plus3, { className: "h-4 w-4" }),
1964
- "Create Secret"
2381
+ "New Secret"
1965
2382
  ]
1966
2383
  }
1967
2384
  )
@@ -2858,10 +3275,11 @@ function StartupScriptsPage({ apiClient, className }) {
2858
3275
  {
2859
3276
  type: "button",
2860
3277
  onClick: openCreate,
3278
+ "aria-label": "Create your first startup script",
2861
3279
  className: "mt-4 inline-flex items-center gap-2 rounded-lg bg-[var(--btn-primary-bg)] px-4 py-2.5 text-sm font-bold text-[var(--btn-primary-text)] shadow-sm transition-colors hover:bg-[var(--btn-primary-hover)]",
2862
3280
  children: [
2863
3281
  /* @__PURE__ */ jsx7(Plus4, { className: "h-4 w-4" }),
2864
- "Create Script"
3282
+ "New Script"
2865
3283
  ]
2866
3284
  }
2867
3285
  )