@tangle-network/sandbox-ui 0.8.4 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/pages.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  DialogFooter,
6
6
  DialogHeader,
7
7
  DialogTitle
8
- } from "./chunk-3CJ2SOEI.js";
8
+ } from "./chunk-US6JKJKH.js";
9
9
  import {
10
10
  Tabs,
11
11
  TabsContent,
@@ -17,13 +17,14 @@ import {
17
17
  Input
18
18
  } from "./chunk-MA7YKRUP.js";
19
19
  import {
20
+ InfoPanel,
20
21
  TemplateCard
21
- } from "./chunk-7U2Z23NE.js";
22
+ } from "./chunk-VX3XOUEB.js";
22
23
  import {
23
24
  BillingDashboard,
24
25
  PricingPage,
25
26
  UsageChart
26
- } from "./chunk-UXQMIR3D.js";
27
+ } from "./chunk-OHPW55EV.js";
27
28
  import {
28
29
  Skeleton,
29
30
  SkeletonCard
@@ -34,7 +35,7 @@ import {
34
35
  } from "./chunk-ZMNSRDMH.js";
35
36
  import {
36
37
  Button
37
- } from "./chunk-2QZ6G7NM.js";
38
+ } from "./chunk-MKTSMWVD.js";
38
39
  import {
39
40
  cn
40
41
  } from "./chunk-RQHJBTEU.js";
@@ -297,10 +298,11 @@ function BillingPage({
297
298
 
298
299
  // src/pages/provisioning-wizard.tsx
299
300
  import * as React2 from "react";
300
- import { ArrowLeft, Layers, Cpu, Bot, Info, Loader2, Settings, Plus, Trash2 } from "lucide-react";
301
+ import { ArrowLeft, Layers, Cpu, Bot, Info, Loader2, Settings, Plus, Trash2, Check } from "lucide-react";
301
302
  import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
303
+ var VALID_DRIVERS = /* @__PURE__ */ new Set(["docker", "firecracker", "tangle"]);
302
304
  var STACK_DISPLAY = {
303
- universal: { name: "Universal", abbr: "U", color: "violet", textClass: "text-[var(--surface-violet-text)]" },
305
+ universal: { name: "Default", abbr: "D", color: "violet", textClass: "text-[var(--surface-violet-text)]" },
304
306
  ethereum: { name: "Ethereum", abbr: "\u039E", color: "blue", textClass: "text-[var(--surface-info-text)]" },
305
307
  solana: { name: "Solana", abbr: "S", color: "green", textClass: "text-[var(--surface-success-text)]" },
306
308
  tangle: { name: "Tangle", abbr: "T", color: "purple", textClass: "text-[var(--surface-violet-text)]" },
@@ -308,6 +310,16 @@ var STACK_DISPLAY = {
308
310
  rust: { name: "Rust", abbr: "Rs", color: "orange", textClass: "text-[var(--surface-orange-text)]" }
309
311
  };
310
312
  function resolveEnvironment(env) {
313
+ if (env.id.startsWith("template:")) {
314
+ const templateName = env.description?.replace(/^Template:\s*/, "") ?? "Custom Template";
315
+ return {
316
+ id: env.id,
317
+ name: templateName,
318
+ description: env.description ?? "User template from snapshot",
319
+ icon: /* @__PURE__ */ jsx2("span", { className: "text-[var(--surface-success-text)] text-2xl font-bold", children: "T" }),
320
+ color: "green"
321
+ };
322
+ }
311
323
  const display = STACK_DISPLAY[env.id];
312
324
  const name = display?.name ?? (env.id.length > 0 ? env.id.charAt(0).toUpperCase() + env.id.slice(1).replace(/-/g, " ") : "Unknown");
313
325
  const abbr = display?.abbr ?? (env.id.length > 0 ? env.id[0].toUpperCase() : "?");
@@ -332,8 +344,8 @@ var RAM_MIN = 2;
332
344
  var RAM_MAX = 32;
333
345
  var STORAGE_MIN = 20;
334
346
  var STORAGE_MAX = 512;
335
- function calcCost(cpu, ram) {
336
- const cost = cpu * 0.045 + ram * 5e-3;
347
+ function calcCost(cpu, ram, storage) {
348
+ const cost = cpu * 0.045 + ram * 5e-3 + storage * 11e-4;
337
349
  return cost.toFixed(2);
338
350
  }
339
351
  function ProvisioningWizard({
@@ -345,17 +357,32 @@ function ProvisioningWizard({
345
357
  variant = "flat",
346
358
  defaultEnvironment,
347
359
  defaultConfig,
348
- skipToReview
360
+ skipToReview,
361
+ onLoadStartupScripts,
362
+ resourceLimits
349
363
  }) {
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));
350
367
  const dc = defaultConfig;
351
368
  const [envList, setEnvList] = React2.useState(environmentsProp ?? defaultEnvironments);
369
+ const onLoadEnvironmentsRef = React2.useRef(onLoadEnvironments);
370
+ onLoadEnvironmentsRef.current = onLoadEnvironments;
352
371
  React2.useEffect(() => {
353
- if (onLoadEnvironments) {
354
- onLoadEnvironments().then((entries) => setEnvList(entries.map(resolveEnvironment)));
372
+ let cancelled = false;
373
+ if (onLoadEnvironmentsRef.current) {
374
+ onLoadEnvironmentsRef.current().then((entries) => {
375
+ if (!cancelled) setEnvList(entries.map(resolveEnvironment));
376
+ }).catch((err) => {
377
+ if (!cancelled) setLoadError(err instanceof Error ? err.message : "Failed to load environments");
378
+ });
355
379
  } else if (environmentsProp) {
356
380
  setEnvList(environmentsProp);
357
381
  }
358
- }, [onLoadEnvironments, environmentsProp]);
382
+ return () => {
383
+ cancelled = true;
384
+ };
385
+ }, [environmentsProp]);
359
386
  const environments = envList;
360
387
  const effectiveDefault = dc?.environment ?? defaultEnvironment;
361
388
  const [selectedEnv, setSelectedEnv] = React2.useState(effectiveDefault ?? environments[0]?.id ?? "");
@@ -364,9 +391,14 @@ function ProvisioningWizard({
364
391
  setSelectedEnv(effectiveDefault);
365
392
  }
366
393
  }, [envList, effectiveDefault]);
367
- const [cpuCores, setCpuCores] = React2.useState(dc?.cpuCores ?? 4);
368
- const [ramGB, setRamGB] = React2.useState(dc?.ramGB ?? 16);
369
- const [storageGB, setStorageGB] = React2.useState(dc?.storageGB ?? 128);
394
+ const [cpuCores, setCpuCores] = React2.useState(Math.min(dc?.cpuCores ?? 4, cpuMax));
395
+ const [ramGB, setRamGB] = React2.useState(Math.min(dc?.ramGB ?? 16, ramMax));
396
+ const [storageGB, setStorageGB] = React2.useState(Math.min(dc?.storageGB ?? 128, storageMax));
397
+ React2.useEffect(() => {
398
+ setCpuCores((prev) => Math.min(prev, cpuMax));
399
+ setRamGB((prev) => Math.min(prev, ramMax));
400
+ setStorageGB((prev) => Math.min(prev, storageMax));
401
+ }, [cpuMax, ramMax, storageMax]);
370
402
  const [modelTier, setModelTier] = React2.useState(dc?.modelTier ?? "claude-sonnet");
371
403
  const [systemPrompt, setSystemPrompt] = React2.useState(dc?.systemPrompt ?? "");
372
404
  const [name, setName] = React2.useState(dc?.name ?? "");
@@ -374,20 +406,55 @@ function ProvisioningWizard({
374
406
  const [envVars, setEnvVars] = React2.useState(dc?.envVars ?? [{ key: "", value: "" }]);
375
407
  const [driver, setDriver] = React2.useState(dc?.driver ?? "docker");
376
408
  const [bare, setBare] = React2.useState(dc?.bare ?? false);
409
+ const [startupScriptIds, setStartupScriptIds] = React2.useState(dc?.startupScriptIds ?? []);
410
+ const [availableScripts, setAvailableScripts] = React2.useState([]);
411
+ const [activePreset, setActivePreset] = React2.useState(null);
377
412
  const [showAdvanced, setShowAdvanced] = React2.useState(false);
413
+ const [loadError, setLoadError] = React2.useState(null);
414
+ const onLoadStartupScriptsRef = React2.useRef(onLoadStartupScripts);
415
+ onLoadStartupScriptsRef.current = onLoadStartupScripts;
416
+ React2.useEffect(() => {
417
+ let cancelled = false;
418
+ if (onLoadStartupScriptsRef.current) {
419
+ onLoadStartupScriptsRef.current().then((scripts) => {
420
+ if (!cancelled) setAvailableScripts(scripts);
421
+ }).catch((err) => {
422
+ if (!cancelled) setLoadError(err instanceof Error ? err.message : "Failed to load startup scripts");
423
+ });
424
+ }
425
+ return () => {
426
+ cancelled = true;
427
+ };
428
+ }, []);
378
429
  const isMultistep = variant === "multistep";
379
430
  const [currentStep, setCurrentStep] = React2.useState(skipToReview && dc && isMultistep ? 3 : 1);
380
431
  const [isDeploying, setIsDeploying] = React2.useState(false);
381
- const handleDeploy = () => {
432
+ const [deployError, setDeployError] = React2.useState(null);
433
+ const handleDeploy = async () => {
434
+ if (!onSubmit) return;
382
435
  setIsDeploying(true);
383
- onSubmit?.({ environment: selectedEnv, cpuCores, ramGB, storageGB, modelTier, systemPrompt, name, gitUrl, envVars: envVars.filter((e) => e.key.trim() !== ""), driver, bare });
436
+ setDeployError(null);
437
+ 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)) });
440
+ } catch (err) {
441
+ setDeployError(err instanceof Error ? err.message : "Deployment failed");
442
+ } finally {
443
+ setIsDeploying(false);
444
+ }
384
445
  };
385
- const applyPreset = (cpu, ram, storage) => {
386
- setCpuCores(cpu);
387
- setRamGB(ram);
388
- setStorageGB(storage);
446
+ const applyPreset = (name2, cpu, ram, storage) => {
447
+ setCpuCores(Math.min(cpu, cpuMax));
448
+ setRamGB(Math.min(ram, ramMax));
449
+ setStorageGB(Math.min(storage, storageMax));
450
+ setActivePreset(name2);
389
451
  };
390
- const hourCost = calcCost(cpuCores, ramGB);
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);
391
458
  return /* @__PURE__ */ jsxs2("div", { className: cn("max-w-6xl mx-auto flex flex-col", className), children: [
392
459
  /* @__PURE__ */ jsxs2("div", { className: "mb-6 flex items-center gap-4 shrink-0", children: [
393
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" }) }),
@@ -398,18 +465,21 @@ function ProvisioningWizard({
398
465
  ] }),
399
466
  /* @__PURE__ */ jsxs2("div", { className: "grid grid-cols-12 gap-6 flex-1 min-h-0", children: [
400
467
  /* @__PURE__ */ jsxs2("div", { className: "col-span-12 xl:col-span-8 flex flex-col min-h-0", children: [
401
- isMultistep && /* @__PURE__ */ jsx2("div", { className: "flex items-center gap-2 mb-4 glass-panel 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: [
402
- /* @__PURE__ */ jsx2("div", { className: cn("w-7 h-7 rounded-full flex items-center justify-center font-bold text-xs transition-colors shrink-0", currentStep === s ? "bg-primary text-primary-foreground" : currentStep > s ? "bg-primary/40 text-primary-foreground" : "bg-muted text-muted-foreground"), children: s }),
403
- /* @__PURE__ */ jsxs2("span", { className: cn("ml-2 sm:ml-3 font-bold text-sm tracking-tight hidden sm:inline", currentStep === s ? "text-foreground" : currentStep > s ? "text-primary/60" : "text-muted-foreground"), children: [
468
+ 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: [
404
474
  s === 1 && "Environment",
405
475
  s === 2 && "Resources",
406
476
  s === 3 && "AI Agent"
407
477
  ] }),
408
- s < 3 && /* @__PURE__ */ jsx2("div", { className: cn("w-4 sm:w-8 h-px mx-2 sm:mx-4 transition-colors", currentStep > s ? "bg-primary/40" : "bg-border") })
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") })
409
479
  ] }, s)) }),
410
- dc && isMultistep && /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between glass-panel rounded-2xl px-4 py-3 shrink-0", children: [
480
+ 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: [
411
481
  /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 text-sm", children: [
412
- /* @__PURE__ */ jsx2(Info, { className: "h-4 w-4 text-accent shrink-0" }),
482
+ /* @__PURE__ */ jsx2(Info, { className: "h-4 w-4 text-primary shrink-0" }),
413
483
  /* @__PURE__ */ jsx2("span", { className: "text-muted-foreground", children: "Pre-configured from template." })
414
484
  ] }),
415
485
  /* @__PURE__ */ jsx2(
@@ -418,22 +488,33 @@ function ProvisioningWizard({
418
488
  type: "button",
419
489
  onClick: () => {
420
490
  setCurrentStep(1);
421
- setCpuCores(4);
422
- setRamGB(16);
423
- setStorageGB(128);
491
+ setSelectedEnv(environments[0]?.id ?? "");
492
+ setCpuCores(Math.min(4, cpuMax));
493
+ setRamGB(Math.min(16, ramMax));
494
+ setStorageGB(Math.min(128, storageMax));
424
495
  setModelTier("claude-sonnet");
425
496
  setSystemPrompt("");
497
+ setName("");
498
+ setGitUrl("");
499
+ setEnvVars([{ key: "", value: "" }]);
500
+ setDriver("docker");
426
501
  setBare(false);
502
+ setStartupScriptIds([]);
503
+ setActivePreset(null);
427
504
  },
428
- className: "text-xs font-bold text-accent hover:text-accent-deep transition-colors",
505
+ className: "text-xs font-bold text-primary hover:text-primary/70 transition-colors",
429
506
  children: "Start from scratch"
430
507
  }
431
508
  )
432
509
  ] }),
510
+ loadError && /* @__PURE__ */ jsxs2("div", { className: "rounded-2xl border border-destructive/30 bg-destructive/10 p-3 flex items-center gap-2 shrink-0", children: [
511
+ /* @__PURE__ */ jsx2(Info, { className: "h-4 w-4 text-destructive shrink-0" }),
512
+ /* @__PURE__ */ jsx2("p", { className: "text-sm font-medium text-destructive", children: loadError })
513
+ ] }),
433
514
  /* @__PURE__ */ jsxs2("div", { className: "flex-1 overflow-y-auto min-h-0 space-y-4", children: [
434
- (!isMultistep || currentStep === 1) && /* @__PURE__ */ jsx2(React2.Fragment, { children: /* @__PURE__ */ jsxs2("section", { className: "glass-panel rounded-[24px] p-6 shadow-2xl relative overflow-hidden", children: [
515
+ (!isMultistep || currentStep === 1) && /* @__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: [
435
516
  /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-3 mb-6", children: [
436
- /* @__PURE__ */ jsx2("div", { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-accent/10 border border-accent/20 text-accent glow-primary", children: /* @__PURE__ */ jsx2(Layers, { className: "h-5 w-5" }) }),
517
+ /* @__PURE__ */ jsx2("div", { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-primary/10 border border-primary/20 text-primary", children: /* @__PURE__ */ jsx2(Layers, { className: "h-5 w-5" }) }),
437
518
  /* @__PURE__ */ jsx2("h2", { className: "text-lg font-bold text-foreground tracking-tight", children: "Environment Selection" })
438
519
  ] }),
439
520
  /* @__PURE__ */ jsx2("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-3", children: environments.map((env) => /* @__PURE__ */ jsxs2(
@@ -442,14 +523,17 @@ function ProvisioningWizard({
442
523
  type: "button",
443
524
  onClick: () => setSelectedEnv(env.id),
444
525
  className: cn(
445
- "group relative p-4 rounded-[16px] transition-all text-left overflow-hidden border border-transparent",
446
- selectedEnv === env.id ? "glass-panel-heavy border-accent glow-primary" : "glass-panel hover:border-[var(--glass-border-color)]"
526
+ "group relative p-4 rounded-[16px] text-left overflow-hidden border transition-all duration-200",
527
+ selectedEnv === env.id ? "bg-primary/5 border-primary ring-2 ring-primary/20 shadow-md" : "bg-card border-border hover:border-primary/30 hover:shadow-sm hover:-translate-y-0.5 active:scale-[0.98]"
447
528
  ),
448
529
  children: [
449
- selectedEnv === env.id && /* @__PURE__ */ jsx2("div", { className: "absolute inset-0 bg-gradient-to-br from-accent/10 to-transparent pointer-events-none" }),
530
+ selectedEnv === env.id && /* @__PURE__ */ jsx2("div", { className: "absolute inset-0 bg-gradient-to-br from-primary/8 to-transparent pointer-events-none" }),
450
531
  /* @__PURE__ */ jsxs2("div", { className: "flex justify-between items-start mb-3 relative z-10", children: [
451
532
  /* @__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 }),
452
- /* @__PURE__ */ jsx2("div", { className: cn("w-5 h-5 rounded-full border-2 flex items-center justify-center transition-colors", selectedEnv === env.id ? "border-accent bg-accent/20" : "border-border"), children: selectedEnv === env.id && /* @__PURE__ */ jsx2("div", { className: "w-2.5 h-2.5 bg-accent rounded-full animate-in zoom-in" }) })
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" }) })
453
537
  ] }),
454
538
  /* @__PURE__ */ jsx2("h3", { className: "font-bold text-sm mb-0.5 text-foreground relative z-10", children: env.name }),
455
539
  /* @__PURE__ */ jsx2("p", { className: "text-xs text-muted-foreground leading-relaxed relative z-10", children: env.description })
@@ -458,39 +542,39 @@ function ProvisioningWizard({
458
542
  env.id
459
543
  )) })
460
544
  ] }) }),
461
- (!isMultistep || currentStep === 2) && /* @__PURE__ */ jsx2(React2.Fragment, { children: /* @__PURE__ */ jsxs2("section", { className: "glass-panel rounded-[24px] p-6 shadow-2xl relative overflow-hidden", children: [
545
+ (!isMultistep || currentStep === 2) && /* @__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: [
462
546
  /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-3 mb-5", children: [
463
- /* @__PURE__ */ jsx2("div", { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-accent/10 border border-accent/20 text-accent glow-primary", children: /* @__PURE__ */ jsx2(Cpu, { className: "h-5 w-5" }) }),
547
+ /* @__PURE__ */ jsx2("div", { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-primary/10 border border-primary/20 text-primary", children: /* @__PURE__ */ jsx2(Cpu, { className: "h-5 w-5" }) }),
464
548
  /* @__PURE__ */ jsx2("h2", { className: "text-lg font-bold text-foreground tracking-tight", children: "Resource Allocation" })
465
549
  ] }),
466
550
  /* @__PURE__ */ jsxs2("div", { className: "mb-6", children: [
467
551
  /* @__PURE__ */ jsx2("label", { className: "block font-label text-xs font-bold uppercase tracking-widest text-muted-foreground mb-3", children: "Compute Presets" }),
468
- /* @__PURE__ */ jsxs2("div", { className: "grid grid-cols-3 gap-3", children: [
469
- /* @__PURE__ */ jsxs2("button", { type: "button", onClick: () => applyPreset(2, 4, 50), className: cn("p-3 rounded-[14px] transition-all text-center group", cpuCores === 2 && ramGB === 4 && storageGB === 50 ? "glass-panel-heavy border-accent glow-primary" : "glass-panel hover:border-[var(--glass-border-color)]"), children: [
470
- /* @__PURE__ */ jsx2("div", { className: cn("font-bold text-sm transition-colors", cpuCores === 2 && ramGB === 4 && storageGB === 50 ? "text-accent" : "text-foreground"), children: "Lightweight" }),
471
- /* @__PURE__ */ jsx2("div", { className: "text-xs text-muted-foreground mt-0.5 font-mono", children: "2C / 4G / 50G" })
472
- ] }),
473
- /* @__PURE__ */ jsxs2("button", { type: "button", onClick: () => applyPreset(4, 16, 128), className: cn("p-3 rounded-[14px] transition-all text-center group", cpuCores === 4 && ramGB === 16 && storageGB === 128 ? "glass-panel-heavy border-accent glow-primary" : "glass-panel hover:border-[var(--glass-border-color)]"), children: [
474
- /* @__PURE__ */ jsx2("div", { className: cn("font-bold text-sm transition-colors", cpuCores === 4 && ramGB === 16 && storageGB === 128 ? "text-accent" : "text-foreground"), children: "Standard" }),
475
- /* @__PURE__ */ jsx2("div", { className: "text-xs text-muted-foreground mt-0.5 font-mono", children: "4C / 16G / 128G" })
476
- ] }),
477
- /* @__PURE__ */ jsxs2("button", { type: "button", onClick: () => applyPreset(8, 32, 256), className: cn("p-3 rounded-[14px] transition-all text-center group", cpuCores === 8 && ramGB === 32 && storageGB === 256 ? "glass-panel-heavy border-accent glow-primary" : "glass-panel hover:border-[var(--glass-border-color)]"), children: [
478
- /* @__PURE__ */ jsx2("div", { className: cn("font-bold text-sm transition-colors", cpuCores === 8 && ramGB === 32 && storageGB === 256 ? "text-accent" : "text-foreground"), children: "Performance" }),
479
- /* @__PURE__ */ jsx2("div", { className: "text-xs text-muted-foreground mt-0.5 font-mono", children: "8C / 32G / 256G" })
480
- ] })
481
- ] })
552
+ /* @__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);
565
+ }) })
482
566
  ] }),
483
567
  /* @__PURE__ */ jsx2("div", { className: "space-y-6", children: [
484
- { label: "Compute Cores (CPU)", value: cpuCores, setter: setCpuCores, min: CPU_MIN, max: CPU_MAX, step: 0.5, unit: "vCPUs" },
485
- { label: "Memory (RAM)", value: ramGB, setter: setRamGB, min: RAM_MIN, max: RAM_MAX, step: 1, unit: "GB" },
486
- { label: "Ephemeral Storage", value: storageGB, setter: setStorageGB, min: STORAGE_MIN, max: STORAGE_MAX, step: 8, unit: "GB" }
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" }
487
571
  ].map(({ label, value, setter, min, max, step: s, unit }) => /* @__PURE__ */ jsxs2("div", { children: [
488
572
  /* @__PURE__ */ jsxs2("div", { className: "flex justify-between items-end border-b border-border pb-1.5 mb-2", children: [
489
573
  /* @__PURE__ */ jsx2("label", { className: "font-label text-xs font-bold uppercase tracking-widest text-muted-foreground", children: label }),
490
574
  /* @__PURE__ */ jsxs2("span", { className: "text-xl font-bold text-foreground tracking-tight", children: [
491
575
  value,
492
576
  " ",
493
- /* @__PURE__ */ jsx2("span", { className: "text-xs text-accent/80 ml-1", children: unit })
577
+ /* @__PURE__ */ jsx2("span", { className: "text-xs text-primary ml-1", children: unit })
494
578
  ] })
495
579
  ] }),
496
580
  /* @__PURE__ */ jsx2(
@@ -501,8 +585,11 @@ function ProvisioningWizard({
501
585
  max,
502
586
  step: s,
503
587
  value,
504
- onChange: (e) => setter(+e.target.value),
505
- className: "w-full h-1.5 bg-muted rounded-full appearance-none cursor-pointer accent-accent"
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"
506
593
  }
507
594
  ),
508
595
  /* @__PURE__ */ jsxs2("div", { className: "flex justify-between text-[10px] font-mono text-muted-foreground/60 mt-1", children: [
@@ -517,9 +604,9 @@ function ProvisioningWizard({
517
604
  ] })
518
605
  ] }, label)) })
519
606
  ] }) }),
520
- (!isMultistep || currentStep === 3) && /* @__PURE__ */ jsx2(React2.Fragment, { children: /* @__PURE__ */ jsxs2("section", { className: "glass-panel rounded-[24px] p-6 shadow-2xl relative overflow-hidden", children: [
607
+ (!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: [
521
608
  /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-3 mb-5", children: [
522
- /* @__PURE__ */ jsx2("div", { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-accent/10 border border-accent/20 text-accent glow-primary", children: /* @__PURE__ */ jsx2(Bot, { className: "h-5 w-5" }) }),
609
+ /* @__PURE__ */ jsx2("div", { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-primary/10 border border-primary/20 text-primary", children: /* @__PURE__ */ jsx2(Bot, { className: "h-5 w-5" }) }),
523
610
  /* @__PURE__ */ jsx2("h2", { className: "text-lg font-bold text-foreground tracking-tight", children: "AI Agent Capability" })
524
611
  ] }),
525
612
  /* @__PURE__ */ jsxs2("div", { className: "space-y-5", children: [
@@ -530,7 +617,7 @@ function ProvisioningWizard({
530
617
  {
531
618
  value: modelTier,
532
619
  onChange: (e) => setModelTier(e.target.value),
533
- className: "w-full glass-panel rounded-xl h-12 px-4 font-bold text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent appearance-none",
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",
534
621
  children: [
535
622
  /* @__PURE__ */ jsx2("option", { value: "llama-3-8b", className: "bg-gray-900", children: "Llama-3-8B-Instruct (Lightweight)" }),
536
623
  /* @__PURE__ */ jsx2("option", { value: "mistral-7b", className: "bg-gray-900", children: "Mistral-7B-v0.2 (Efficient)" }),
@@ -546,7 +633,8 @@ function ProvisioningWizard({
546
633
  {
547
634
  value: systemPrompt,
548
635
  onChange: (e) => setSystemPrompt(e.target.value),
549
- className: "w-full glass-panel rounded-xl p-4 font-mono text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent h-32 resize-none placeholder:text-muted-foreground",
636
+ maxLength: 1e4,
637
+ className: "w-full bg-card border border-border rounded-xl p-4 font-mono text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent h-32 resize-none placeholder:text-muted-foreground",
550
638
  placeholder: "Define the autonomous directives or operational boundaries..."
551
639
  }
552
640
  )
@@ -574,7 +662,8 @@ function ProvisioningWizard({
574
662
  type: "text",
575
663
  value: name,
576
664
  onChange: (e) => setName(e.target.value),
577
- className: "w-full glass-panel rounded-xl h-12 px-4 font-bold text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent placeholder:text-muted-foreground",
665
+ maxLength: 128,
666
+ 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 placeholder:text-muted-foreground",
578
667
  placeholder: "my-cool-sandbox"
579
668
  }
580
669
  )
@@ -585,8 +674,10 @@ function ProvisioningWizard({
585
674
  "select",
586
675
  {
587
676
  value: driver,
588
- onChange: (e) => setDriver(e.target.value),
589
- className: "w-full glass-panel rounded-xl h-12 px-4 font-bold text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent appearance-none",
677
+ onChange: (e) => {
678
+ if (VALID_DRIVERS.has(e.target.value)) setDriver(e.target.value);
679
+ },
680
+ 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",
590
681
  children: [
591
682
  /* @__PURE__ */ jsx2("option", { value: "docker", className: "bg-gray-900", children: "Docker container (Default)" }),
592
683
  /* @__PURE__ */ jsx2("option", { value: "firecracker", className: "bg-gray-900", children: "Firecracker microVM (Secure)" }),
@@ -604,7 +695,7 @@ function ProvisioningWizard({
604
695
  type: "text",
605
696
  value: gitUrl,
606
697
  onChange: (e) => setGitUrl(e.target.value),
607
- className: "w-full glass-panel rounded-xl h-12 px-4 font-bold text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent placeholder:text-muted-foreground",
698
+ 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 placeholder:text-muted-foreground",
608
699
  placeholder: "https://github.com/my-org/my-repo.git"
609
700
  }
610
701
  )
@@ -612,7 +703,7 @@ function ProvisioningWizard({
612
703
  /* @__PURE__ */ jsxs2("div", { children: [
613
704
  /* @__PURE__ */ jsxs2("div", { className: "flex justify-between items-center mb-2", children: [
614
705
  /* @__PURE__ */ jsx2("label", { className: "block font-label text-xs font-bold uppercase tracking-widest text-muted-foreground", children: "Environment Variables" }),
615
- /* @__PURE__ */ jsxs2("button", { type: "button", onClick: () => setEnvVars([...envVars, { key: "", value: "" }]), className: "flex items-center gap-1 text-xs text-accent hover:text-accent-deep transition-colors font-bold", children: [
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: [
616
707
  /* @__PURE__ */ jsx2(Plus, { className: "h-3 w-3" }),
617
708
  " Add Var"
618
709
  ] })
@@ -625,7 +716,7 @@ function ProvisioningWizard({
625
716
  type: "text",
626
717
  value: env.key,
627
718
  onChange: (e) => setEnvVars(envVars.map((v, idx) => idx === i ? { ...v, key: e.target.value } : v)),
628
- className: "flex-1 glass-panel rounded-xl h-10 px-3 font-mono text-sm text-foreground focus:outline-none focus:ring-1 focus:ring-accent placeholder:text-muted-foreground",
719
+ 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",
629
720
  placeholder: "API_KEY"
630
721
  }
631
722
  ),
@@ -635,22 +726,52 @@ function ProvisioningWizard({
635
726
  type: "password",
636
727
  value: env.value,
637
728
  onChange: (e) => setEnvVars(envVars.map((v, idx) => idx === i ? { ...v, value: e.target.value } : v)),
638
- className: "flex-[2] glass-panel rounded-xl h-10 px-3 font-mono text-sm text-foreground focus:outline-none focus:ring-1 focus:ring-accent placeholder:text-muted-foreground",
729
+ 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",
639
730
  placeholder: "sk-xxxxxxxxxxx"
640
731
  }
641
732
  ),
642
- /* @__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 glass-panel text-red-400 hover:bg-red-500/10 hover:border-red-500/30 transition-colors", children: /* @__PURE__ */ jsx2(Trash2, { className: "h-4 w-4" }) })
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" }) })
643
734
  ] }, i)),
644
- envVars.length === 0 && /* @__PURE__ */ jsx2("div", { className: "text-center p-3 glass-panel rounded-xl text-muted-foreground/60 text-sm italic", children: "No environment variables set" })
735
+ 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" })
645
736
  ] })
646
737
  ] }),
738
+ availableScripts.length > 0 && /* @__PURE__ */ jsxs2("div", { children: [
739
+ /* @__PURE__ */ jsx2("div", { className: "text-xs font-bold uppercase tracking-widest text-muted-foreground mb-2", children: "Startup Scripts" }),
740
+ /* @__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]
750
+ ),
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);
766
+ }) })
767
+ ] }),
647
768
  /* @__PURE__ */ jsx2("div", { className: "pt-2 border-t border-border", children: /* @__PURE__ */ jsxs2("label", { className: "flex items-center gap-3 cursor-pointer group", children: [
648
769
  /* @__PURE__ */ jsxs2("div", { className: "relative flex items-center justify-center shrink-0", children: [
649
770
  /* @__PURE__ */ jsx2("input", { type: "checkbox", className: "sr-only peer", checked: bare, onChange: (e) => setBare(e.target.checked) }),
650
- /* @__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-accent hover:bg-white/20 transition-colors" })
771
+ /* @__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" })
651
772
  ] }),
652
773
  /* @__PURE__ */ jsxs2("div", { children: [
653
- /* @__PURE__ */ jsx2("div", { className: "text-sm font-bold text-foreground mb-0.5 group-hover:text-accent transition-colors", children: "Bare Mode" }),
774
+ /* @__PURE__ */ jsx2("div", { className: "text-sm font-bold text-foreground mb-0.5 group-hover:text-primary transition-colors", children: "Bare Mode" }),
654
775
  /* @__PURE__ */ jsx2("div", { className: "text-xs text-muted-foreground", children: "Start as a raw container without an embedded AI Agent backend." })
655
776
  ] })
656
777
  ] }) })
@@ -661,7 +782,7 @@ function ProvisioningWizard({
661
782
  ] })
662
783
  ] }),
663
784
  /* @__PURE__ */ jsxs2("div", { className: "col-span-12 xl:col-span-4 sticky top-4 space-y-4", children: [
664
- /* @__PURE__ */ jsxs2("div", { className: "glass-panel-heavy rounded-[24px] overflow-hidden shadow-2xl relative", children: [
785
+ /* @__PURE__ */ jsxs2("div", { className: "bg-card border border-primary/15 rounded-[24px] overflow-hidden shadow-2xl relative", children: [
665
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" }),
666
787
  /* @__PURE__ */ jsxs2("div", { className: "bg-muted/50 border-b border-border px-4 py-3 flex items-center gap-3", children: [
667
788
  /* @__PURE__ */ jsxs2("div", { className: "flex gap-2", children: [
@@ -678,54 +799,54 @@ function ProvisioningWizard({
678
799
  ] }),
679
800
  /* @__PURE__ */ jsx2("div", { className: "text-muted-foreground/70", children: "Initializing deployment handshake..." }),
680
801
  /* @__PURE__ */ jsxs2("div", { className: "text-foreground/70", children: [
681
- /* @__PURE__ */ jsx2("span", { className: "text-accent mr-2", children: "\u2713" }),
802
+ /* @__PURE__ */ jsx2("span", { className: "text-primary mr-2", children: "\u2713" }),
682
803
  " Bound Platform: ",
683
- /* @__PURE__ */ jsx2("span", { className: "text-foreground font-bold", children: environments.find((e) => e.id === selectedEnv)?.name ?? "Node.js" })
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}`)
684
805
  ] }),
685
806
  /* @__PURE__ */ jsxs2("div", { className: "text-foreground/70", children: [
686
- /* @__PURE__ */ jsx2("span", { className: "text-accent mr-2", children: "\u2713" }),
807
+ /* @__PURE__ */ jsx2("span", { className: "text-primary mr-2", children: "\u2713" }),
687
808
  " Allocation CPU: ",
688
- /* @__PURE__ */ jsxs2("span", { className: "text-foreground font-bold", children: [
809
+ /* @__PURE__ */ jsxs2("span", { className: "text-foreground font-bold animate-in fade-in duration-300", children: [
689
810
  cpuCores,
690
811
  " Cores"
691
- ] })
812
+ ] }, `cpu-${cpuCores}`)
692
813
  ] }),
693
814
  /* @__PURE__ */ jsxs2("div", { className: "text-foreground/70", children: [
694
- /* @__PURE__ */ jsx2("span", { className: "text-accent mr-2", children: "\u2713" }),
815
+ /* @__PURE__ */ jsx2("span", { className: "text-primary mr-2", children: "\u2713" }),
695
816
  " Allocation RAM: ",
696
- /* @__PURE__ */ jsxs2("span", { className: "text-foreground font-bold", children: [
817
+ /* @__PURE__ */ jsxs2("span", { className: "text-foreground font-bold animate-in fade-in duration-300", children: [
697
818
  ramGB,
698
819
  "GB"
699
- ] })
820
+ ] }, `ram-${ramGB}`)
700
821
  ] }),
701
822
  /* @__PURE__ */ jsxs2("div", { className: "text-foreground/70", children: [
702
- /* @__PURE__ */ jsx2("span", { className: "text-accent mr-2", children: "\u2713" }),
823
+ /* @__PURE__ */ jsx2("span", { className: "text-primary mr-2", children: "\u2713" }),
703
824
  " Mounted Storage: ",
704
- /* @__PURE__ */ jsxs2("span", { className: "text-foreground font-bold", children: [
825
+ /* @__PURE__ */ jsxs2("span", { className: "text-foreground font-bold animate-in fade-in duration-300", children: [
705
826
  storageGB,
706
827
  "GB NVMe"
707
- ] })
828
+ ] }, `disk-${storageGB}`)
708
829
  ] }),
709
830
  /* @__PURE__ */ jsxs2("div", { className: "pt-3 flex items-center gap-3", children: [
710
- /* @__PURE__ */ jsx2("div", { className: "w-2 h-4 bg-accent animate-pulse" }),
831
+ /* @__PURE__ */ jsx2("div", { className: "w-2 h-4 bg-primary animate-pulse" }),
711
832
  /* @__PURE__ */ jsx2("span", { className: "text-muted-foreground", children: "Awaiting user confirmation..." })
712
833
  ] })
713
834
  ] })
714
835
  ] }),
715
- /* @__PURE__ */ jsxs2("div", { className: "p-6 rounded-2xl bg-accent/5 backdrop-blur-md border border-accent/20 shadow-[0_0_30px_rgba(173,163,255,0.1)] relative overflow-hidden", children: [
716
- /* @__PURE__ */ jsx2("div", { className: "absolute -top-20 -right-20 w-40 h-40 bg-accent/20 blur-[50px] rounded-full" }),
836
+ /* @__PURE__ */ jsxs2("div", { className: "p-6 rounded-[24px] bg-card border border-primary/15 relative overflow-hidden", children: [
837
+ /* @__PURE__ */ jsx2("div", { className: "hidden" }),
717
838
  /* @__PURE__ */ jsxs2("div", { className: "flex justify-between items-center mb-4 relative z-10", children: [
718
839
  /* @__PURE__ */ jsx2("span", { className: "font-label text-xs font-bold uppercase tracking-widest text-muted-foreground", children: "Run Cost" }),
719
- /* @__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-accent" }) })
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" }) })
720
841
  ] }),
721
842
  /* @__PURE__ */ jsxs2("div", { className: "flex items-baseline gap-2 mb-5 relative z-10", children: [
722
- /* @__PURE__ */ jsxs2("span", { className: "text-4xl font-black text-foreground tracking-tighter", children: [
843
+ /* @__PURE__ */ jsxs2("span", { className: "text-4xl font-black text-foreground tracking-tighter animate-in fade-in duration-200", children: [
723
844
  "$",
724
845
  hourCost
725
- ] }),
846
+ ] }, hourCost),
726
847
  /* @__PURE__ */ jsx2("span", { className: "text-muted-foreground text-sm font-bold", children: "/ hour" })
727
848
  ] }),
728
- /* @__PURE__ */ jsxs2("div", { className: "space-y-2 relative z-10 glass-panel rounded-xl p-3 border border-border", children: [
849
+ /* @__PURE__ */ jsxs2("div", { className: "space-y-2 relative z-10 bg-card border border-border rounded-xl p-3", children: [
729
850
  /* @__PURE__ */ jsxs2("div", { className: "flex justify-between text-xs font-mono tracking-widest text-muted-foreground", children: [
730
851
  /* @__PURE__ */ jsx2("span", { children: "COMPUTE" }),
731
852
  /* @__PURE__ */ jsxs2("span", { className: "text-foreground", children: [
@@ -738,37 +859,47 @@ function ProvisioningWizard({
738
859
  /* @__PURE__ */ jsx2("span", { children: "MEMORY" }),
739
860
  /* @__PURE__ */ jsxs2("span", { className: "text-foreground/80", children: [
740
861
  "$",
741
- (ramGB * 5e-3).toFixed(2)
862
+ (ramGB * 5e-3).toFixed(2),
863
+ "/h"
864
+ ] })
865
+ ] }),
866
+ /* @__PURE__ */ jsxs2("div", { className: "flex justify-between text-xs font-mono tracking-widest text-muted-foreground", children: [
867
+ /* @__PURE__ */ jsx2("span", { children: "STORAGE" }),
868
+ /* @__PURE__ */ jsxs2("span", { className: "text-foreground/80", children: [
869
+ "$",
870
+ (storageGB * 11e-4).toFixed(2),
871
+ "/h"
742
872
  ] })
743
873
  ] })
744
874
  ] })
745
875
  ] }),
876
+ deployError && /* @__PURE__ */ jsxs2("div", { className: "rounded-xl border border-destructive/30 bg-destructive/10 p-3 flex items-center gap-2", children: [
877
+ /* @__PURE__ */ jsx2(Info, { className: "h-4 w-4 text-destructive shrink-0" }),
878
+ /* @__PURE__ */ jsx2("p", { className: "text-sm font-medium text-destructive", children: deployError })
879
+ ] }),
746
880
  /* @__PURE__ */ jsx2("div", { className: "space-y-3", children: isMultistep ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
747
881
  currentStep < 3 ? /* @__PURE__ */ jsxs2(
748
882
  "button",
749
883
  {
750
884
  type: "button",
751
885
  onClick: () => setCurrentStep((s) => s + 1),
752
- className: "w-full relative overflow-hidden h-12 bg-accent/10 text-accent font-extrabold text-sm rounded-2xl border border-accent/20 hover:bg-accent/20 transition-colors shadow-sm shadow-accent/10",
886
+ 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",
753
887
  children: [
754
888
  "Continue to ",
755
889
  currentStep === 1 ? "Resources" : "Agent Config"
756
890
  ]
757
891
  }
758
- ) : /* @__PURE__ */ jsxs2(
892
+ ) : /* @__PURE__ */ jsx2(
759
893
  "button",
760
894
  {
761
895
  type: "button",
762
896
  onClick: handleDeploy,
763
897
  disabled: isDeploying || !selectedEnv,
764
- className: "w-full relative overflow-hidden h-12 bg-gradient-to-r from-accent to-accent-deep text-white font-extrabold text-sm rounded-2xl tracking-wide shadow-[0_0_20px_rgba(130,99,255,0.2)] disabled:opacity-50 transition-transform active:scale-[0.98]",
765
- children: [
766
- isDeploying ? /* @__PURE__ */ jsxs2("span", { className: "relative z-10 flex items-center justify-center gap-2", children: [
767
- /* @__PURE__ */ jsx2(Loader2, { className: "h-4 w-4 animate-spin" }),
768
- "Deploying..."
769
- ] }) : /* @__PURE__ */ jsx2("span", { className: "relative z-10", children: "Deploy Workspace" }),
770
- /* @__PURE__ */ jsx2("div", { className: "absolute inset-0 bg-white/20 opacity-0 hover:opacity-100 transition-opacity" })
771
- ]
898
+ className: "w-full h-12 bg-primary text-primary-foreground font-extrabold text-sm rounded-2xl tracking-wide shadow-md disabled:opacity-50 hover:brightness-110 active:scale-[0.98] transition-all",
899
+ children: isDeploying ? /* @__PURE__ */ jsxs2("span", { className: "flex items-center justify-center gap-2", children: [
900
+ /* @__PURE__ */ jsx2(Loader2, { className: "h-4 w-4 animate-spin" }),
901
+ "Deploying..."
902
+ ] }) : "Deploy Workspace"
772
903
  }
773
904
  ),
774
905
  currentStep > 1 && /* @__PURE__ */ jsx2(
@@ -776,24 +907,21 @@ function ProvisioningWizard({
776
907
  {
777
908
  type: "button",
778
909
  onClick: () => setCurrentStep((s) => s - 1),
779
- className: "w-full h-10 glass-panel text-foreground/70 font-bold text-sm rounded-2xl hover:text-foreground hover:border-[var(--glass-border-color)] transition-colors",
910
+ className: "w-full h-10 bg-secondary text-secondary-foreground border border-border font-bold text-sm rounded-2xl hover:brightness-95 active:scale-[0.98] transition-all",
780
911
  children: "Back"
781
912
  }
782
913
  )
783
- ] }) : /* @__PURE__ */ jsxs2(
914
+ ] }) : /* @__PURE__ */ jsx2(
784
915
  "button",
785
916
  {
786
917
  type: "button",
787
918
  onClick: handleDeploy,
788
919
  disabled: isDeploying || !selectedEnv,
789
- className: "w-full relative overflow-hidden h-12 bg-gradient-to-r from-accent to-accent-deep text-white font-extrabold text-sm rounded-2xl tracking-wide shadow-[0_0_20px_rgba(130,99,255,0.2)] disabled:opacity-50 transition-transform active:scale-[0.98]",
790
- children: [
791
- isDeploying ? /* @__PURE__ */ jsxs2("span", { className: "relative z-10 flex items-center justify-center gap-2", children: [
792
- /* @__PURE__ */ jsx2(Loader2, { className: "h-4 w-4 animate-spin" }),
793
- "Spinning up environment..."
794
- ] }) : /* @__PURE__ */ jsx2("span", { className: "relative z-10", children: "Deploy Workspace" }),
795
- /* @__PURE__ */ jsx2("div", { className: "absolute inset-0 bg-white/20 opacity-0 hover:opacity-100 transition-opacity" })
796
- ]
920
+ className: "w-full h-12 bg-primary text-primary-foreground font-extrabold text-sm rounded-2xl tracking-wide shadow-md disabled:opacity-50 hover:brightness-110 active:scale-[0.98] transition-all",
921
+ children: isDeploying ? /* @__PURE__ */ jsxs2("span", { className: "flex items-center justify-center gap-2", children: [
922
+ /* @__PURE__ */ jsx2(Loader2, { className: "h-4 w-4 animate-spin" }),
923
+ "Spinning up environment..."
924
+ ] }) : "Deploy Workspace"
797
925
  }
798
926
  ) })
799
927
  ] })
@@ -813,16 +941,16 @@ async function fetchTiersFromApi(apiBasePath) {
813
941
  }
814
942
  var FAQ = [
815
943
  {
816
- q: "What are credits?",
817
- a: "Credits are used to pay for AI model usage. Different models consume different amounts of credits based on their complexity and the length of your requests."
944
+ q: "What is included usage?",
945
+ a: "Each plan includes a monthly USD usage balance. Compute and AI model usage are billed per second from this balance. If you exceed your included amount, overage is billed at the same rates with no penalty."
818
946
  },
819
947
  {
820
948
  q: "Can I change plans later?",
821
949
  a: "Yes. You can upgrade or downgrade your plan at any time. When you upgrade, you are charged the prorated difference. When you downgrade, the change takes effect at the end of your billing cycle."
822
950
  },
823
951
  {
824
- q: "Do unused credits roll over?",
825
- a: "Monthly credits do not roll over to the next month. However, purchased credit packs never expire and can be used at any time."
952
+ q: "Does unused balance roll over?",
953
+ a: "Monthly included usage does not roll over to the next month. Your balance resets at the start of each billing cycle."
826
954
  }
827
955
  ];
828
956
  function StandalonePricingPage({
@@ -833,6 +961,7 @@ function StandalonePricingPage({
833
961
  fetchTiers,
834
962
  title = "Simple, transparent pricing",
835
963
  subtitle = "Choose the plan that fits your needs. Upgrade or downgrade at any time.",
964
+ eyebrow,
836
965
  className
837
966
  }) {
838
967
  const [state, setState] = React3.useState({
@@ -889,7 +1018,8 @@ function StandalonePricingPage({
889
1018
  );
890
1019
  return /* @__PURE__ */ jsxs3("div", { className: cn("mx-auto max-w-6xl px-6 py-16 space-y-16", className), children: [
891
1020
  /* @__PURE__ */ jsxs3("div", { className: "space-y-4 text-center", children: [
892
- /* @__PURE__ */ jsx3("h1", { className: "text-4xl font-extrabold tracking-tight text-foreground sm:text-5xl", children: title }),
1021
+ eyebrow && /* @__PURE__ */ jsx3("span", { className: "text-xs font-bold uppercase tracking-widest text-[var(--brand-emerald,#10B981)]", children: eyebrow }),
1022
+ /* @__PURE__ */ jsx3("h1", { className: "text-4xl font-extrabold tracking-tight text-foreground sm:text-5xl font-display", children: title }),
893
1023
  /* @__PURE__ */ jsx3("p", { className: "mx-auto max-w-2xl text-lg text-muted-foreground", children: subtitle })
894
1024
  ] }),
895
1025
  state.loading ? /* @__PURE__ */ jsxs3("div", { className: "space-y-8", children: [
@@ -1577,7 +1707,7 @@ function ProfileDetailDialog({
1577
1707
 
1578
1708
  // src/pages/secrets-page.tsx
1579
1709
  import * as React5 from "react";
1580
- import { Lock, Plus as Plus3, Trash2 as Trash23, Eye, EyeOff, AlertCircle as AlertCircle2 } from "lucide-react";
1710
+ import { Lock, Plus as Plus3, Trash2 as Trash23, Eye, EyeOff, AlertCircle as AlertCircle2, Key, Shield, CheckCircle } from "lucide-react";
1581
1711
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1582
1712
  function SecretsPage({ apiClient, className }) {
1583
1713
  const [secrets, setSecrets] = React5.useState([]);
@@ -1593,23 +1723,27 @@ function SecretsPage({ apiClient, className }) {
1593
1723
  const [isDeleting, setIsDeleting] = React5.useState(false);
1594
1724
  const apiRef = React5.useRef(apiClient);
1595
1725
  apiRef.current = apiClient;
1596
- const loadSecrets = React5.useCallback(async () => {
1726
+ const loadGenRef = React5.useRef(0);
1727
+ const loadSecrets = React5.useCallback(async (showSpinner = true) => {
1728
+ const gen = ++loadGenRef.current;
1597
1729
  try {
1598
- setLoading(true);
1730
+ if (showSpinner) setLoading(true);
1599
1731
  setError(null);
1600
1732
  const data = await apiRef.current.listSecrets();
1733
+ if (gen !== loadGenRef.current) return;
1601
1734
  setSecrets(data);
1602
1735
  } catch (err) {
1736
+ if (gen !== loadGenRef.current) return;
1603
1737
  setError(err instanceof Error ? err.message : "Failed to load secrets");
1604
1738
  } finally {
1605
- setLoading(false);
1739
+ if (gen === loadGenRef.current) setLoading(false);
1606
1740
  }
1607
1741
  }, []);
1608
1742
  React5.useEffect(() => {
1609
1743
  loadSecrets();
1610
1744
  }, [loadSecrets]);
1611
1745
  const handleCreate = async () => {
1612
- if (!newName.trim()) return;
1746
+ if (!newName.trim() || !newValue.trim()) return;
1613
1747
  setIsCreating(true);
1614
1748
  setCreateError(null);
1615
1749
  try {
@@ -1618,7 +1752,7 @@ function SecretsPage({ apiClient, className }) {
1618
1752
  setNewName("");
1619
1753
  setNewValue("");
1620
1754
  setShowValue(false);
1621
- loadSecrets();
1755
+ await loadSecrets(false);
1622
1756
  } catch (err) {
1623
1757
  setCreateError(err instanceof Error ? err.message : "Failed to create secret");
1624
1758
  } finally {
@@ -1630,7 +1764,7 @@ function SecretsPage({ apiClient, className }) {
1630
1764
  try {
1631
1765
  await apiRef.current.deleteSecret(name);
1632
1766
  setDeleteTarget(null);
1633
- loadSecrets();
1767
+ await loadSecrets(false);
1634
1768
  } catch (err) {
1635
1769
  setError(err instanceof Error ? err.message : "Failed to delete secret");
1636
1770
  } finally {
@@ -1642,31 +1776,53 @@ function SecretsPage({ apiClient, className }) {
1642
1776
  const ts = /^\d+$/.test(dateStr) ? Number(dateStr) : dateStr;
1643
1777
  const date = new Date(ts);
1644
1778
  if (Number.isNaN(date.getTime())) return dateStr;
1645
- return date.toLocaleDateString();
1779
+ return date.toLocaleDateString("en-US");
1646
1780
  } catch {
1647
1781
  return dateStr;
1648
1782
  }
1649
1783
  };
1650
- return /* @__PURE__ */ jsxs5("div", { className: cn("space-y-6", className), children: [
1651
- /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between", children: [
1784
+ return /* @__PURE__ */ jsxs5("div", { className: cn("mx-auto w-full max-w-7xl space-y-8", className), children: [
1785
+ /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-4 md:flex-row md:items-end md:justify-between", children: [
1652
1786
  /* @__PURE__ */ jsxs5("div", { children: [
1653
- /* @__PURE__ */ jsx5("h1", { className: "font-bold text-3xl text-foreground", children: "Secrets" }),
1654
- /* @__PURE__ */ jsx5("p", { className: "mt-1 text-muted-foreground", children: "Secrets securely stored here will be automatically exposed as environment variables across all your new sandboxes." })
1787
+ /* @__PURE__ */ jsx5("h1", { className: "font-display text-3xl font-extrabold tracking-tight text-foreground", children: "Environment Secrets" }),
1788
+ /* @__PURE__ */ jsx5("p", { className: "mt-1 text-sm text-muted-foreground", children: "Secrets are securely stored and automatically exposed as environment variables across all your sandboxes." })
1655
1789
  ] }),
1656
1790
  /* @__PURE__ */ jsxs5(
1657
1791
  "button",
1658
1792
  {
1659
1793
  type: "button",
1660
1794
  onClick: () => setIsCreateOpen(true),
1661
- className: "inline-flex items-center gap-2 rounded-lg bg-primary/20 border border-primary/30 px-4 py-2 text-sm font-bold text-primary hover:bg-primary hover:text-primary-foreground transition-colors active:scale-[0.97]",
1795
+ className: "inline-flex items-center gap-2 rounded-lg bg-[var(--btn-primary-bg)] border border-[var(--border-accent,transparent)] px-5 py-2.5 text-sm font-semibold text-[var(--btn-primary-text)] hover:bg-[var(--btn-primary-hover)] transition-colors active:scale-[0.97]",
1662
1796
  children: [
1663
1797
  /* @__PURE__ */ jsx5(Plus3, { className: "h-4 w-4" }),
1664
- "Create Secret"
1798
+ "New Secret"
1665
1799
  ]
1666
1800
  }
1667
1801
  )
1668
1802
  ] }),
1669
- error && /* @__PURE__ */ jsxs5("div", { className: "rounded-xl border border-destructive/30 bg-destructive/10 p-4 flex items-center gap-3", children: [
1803
+ /* @__PURE__ */ jsxs5("div", { className: "grid grid-cols-1 gap-6 md:grid-cols-4", children: [
1804
+ /* @__PURE__ */ jsxs5("div", { className: "rounded-lg border border-border bg-card p-5 shadow-[var(--shadow-card)]", children: [
1805
+ /* @__PURE__ */ jsx5("p", { className: "text-[10px] font-bold uppercase tracking-widest text-muted-foreground", children: "Total Active Secrets" }),
1806
+ /* @__PURE__ */ jsx5("div", { className: "mt-2 flex items-baseline gap-2", children: /* @__PURE__ */ jsx5("span", { className: "font-display text-2xl font-extrabold text-foreground", children: secrets.length }) })
1807
+ ] }),
1808
+ /* @__PURE__ */ jsxs5("div", { className: "rounded-lg border border-border bg-card p-5 shadow-[var(--shadow-card)]", children: [
1809
+ /* @__PURE__ */ jsx5("p", { className: "text-[10px] font-bold uppercase tracking-widest text-muted-foreground", children: "Status" }),
1810
+ /* @__PURE__ */ jsxs5("div", { className: "mt-2 flex items-center gap-2", children: [
1811
+ /* @__PURE__ */ jsx5(CheckCircle, { className: "h-4 w-4 text-[var(--surface-success-text,#047857)]" }),
1812
+ /* @__PURE__ */ jsx5("span", { className: "text-sm font-semibold text-[var(--surface-success-text,#047857)]", children: "Encrypted" })
1813
+ ] })
1814
+ ] }),
1815
+ /* @__PURE__ */ jsx5(
1816
+ InfoPanel,
1817
+ {
1818
+ className: "md:col-span-2",
1819
+ label: "Security Audit",
1820
+ title: "All engines operational.",
1821
+ description: "Secrets are encrypted at rest using AES-256."
1822
+ }
1823
+ )
1824
+ ] }),
1825
+ error && /* @__PURE__ */ jsxs5("div", { className: "rounded-lg border border-destructive/30 bg-destructive/10 p-4 flex items-center gap-3", children: [
1670
1826
  /* @__PURE__ */ jsx5(AlertCircle2, { className: "h-5 w-5 text-destructive shrink-0" }),
1671
1827
  /* @__PURE__ */ jsx5("p", { className: "text-destructive text-sm font-medium", children: error })
1672
1828
  ] }),
@@ -1681,11 +1837,11 @@ function SecretsPage({ apiClient, className }) {
1681
1837
  }, children: /* @__PURE__ */ jsxs5(DialogContent, { className: "max-w-md", children: [
1682
1838
  /* @__PURE__ */ jsxs5(DialogHeader, { children: [
1683
1839
  /* @__PURE__ */ jsx5(DialogTitle, { children: "Create Secret" }),
1684
- /* @__PURE__ */ jsx5(DialogDescription, { children: "Secrets securely stored here will be automatically exposed as environment variables across all your new sandboxes." })
1840
+ /* @__PURE__ */ jsx5(DialogDescription, { children: "Secrets are automatically exposed as environment variables across all your new sandboxes." })
1685
1841
  ] }),
1686
1842
  /* @__PURE__ */ jsxs5("div", { className: "space-y-4", children: [
1687
1843
  /* @__PURE__ */ jsxs5("div", { children: [
1688
- /* @__PURE__ */ jsx5("label", { className: "block text-sm font-medium text-foreground mb-1.5", children: "Name" }),
1844
+ /* @__PURE__ */ jsx5("label", { className: "block text-xs font-bold uppercase tracking-widest text-muted-foreground mb-2", children: "Name" }),
1689
1845
  /* @__PURE__ */ jsx5(
1690
1846
  "input",
1691
1847
  {
@@ -1693,12 +1849,12 @@ function SecretsPage({ apiClient, className }) {
1693
1849
  value: newName,
1694
1850
  onChange: (e) => setNewName(e.target.value.toUpperCase().replace(/[^A-Z0-9_]/g, "_")),
1695
1851
  placeholder: "MY_SECRET_KEY",
1696
- className: "w-full rounded-lg border border-border bg-background px-3 py-2 text-sm font-mono text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring"
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"
1697
1853
  }
1698
1854
  )
1699
1855
  ] }),
1700
1856
  /* @__PURE__ */ jsxs5("div", { children: [
1701
- /* @__PURE__ */ jsx5("label", { className: "block text-sm font-medium text-foreground mb-1.5", children: "Value" }),
1857
+ /* @__PURE__ */ jsx5("label", { className: "block text-xs font-bold uppercase tracking-widest text-muted-foreground mb-2", children: "Value" }),
1702
1858
  /* @__PURE__ */ jsxs5("div", { className: "relative", children: [
1703
1859
  /* @__PURE__ */ jsx5(
1704
1860
  "input",
@@ -1707,7 +1863,7 @@ function SecretsPage({ apiClient, className }) {
1707
1863
  value: newValue,
1708
1864
  onChange: (e) => setNewValue(e.target.value),
1709
1865
  placeholder: "Enter secret value...",
1710
- className: "w-full rounded-lg border border-border bg-background px-3 py-2 pr-10 text-sm font-mono text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring"
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"
1711
1867
  }
1712
1868
  ),
1713
1869
  /* @__PURE__ */ jsx5(
@@ -1735,7 +1891,7 @@ function SecretsPage({ apiClient, className }) {
1735
1891
  setNewValue("");
1736
1892
  setCreateError(null);
1737
1893
  },
1738
- className: "rounded-lg border border-border bg-muted px-4 py-2 text-sm font-medium text-foreground hover:bg-muted/80 transition-colors",
1894
+ className: "rounded-md border border-border bg-card px-4 py-2 text-sm font-medium text-foreground hover:bg-muted transition-colors",
1739
1895
  children: "Cancel"
1740
1896
  }
1741
1897
  ),
@@ -1744,8 +1900,8 @@ function SecretsPage({ apiClient, className }) {
1744
1900
  {
1745
1901
  type: "button",
1746
1902
  onClick: handleCreate,
1747
- disabled: !newName.trim() || !newValue || isCreating,
1748
- className: "rounded-lg bg-primary px-4 py-2 text-sm font-bold text-primary-foreground hover:bg-primary/90 transition-colors disabled:opacity-50 active:scale-[0.97]",
1903
+ disabled: !newName.trim() || !newValue.trim() || isCreating,
1904
+ className: "rounded-md bg-[var(--btn-primary-bg)] px-4 py-2 text-sm font-bold text-[var(--btn-primary-text)] hover:bg-[var(--btn-primary-hover)] transition-colors disabled:opacity-50 active:scale-[0.97]",
1749
1905
  children: isCreating ? "Creating..." : "Create Secret"
1750
1906
  }
1751
1907
  )
@@ -1768,7 +1924,7 @@ function SecretsPage({ apiClient, className }) {
1768
1924
  {
1769
1925
  type: "button",
1770
1926
  onClick: () => setDeleteTarget(null),
1771
- className: "rounded-lg border border-border bg-muted px-4 py-2 text-sm font-medium text-foreground hover:bg-muted/80 transition-colors",
1927
+ className: "rounded-md border border-border bg-card px-4 py-2 text-sm font-medium text-foreground hover:bg-muted transition-colors",
1772
1928
  children: "Cancel"
1773
1929
  }
1774
1930
  ),
@@ -1778,60 +1934,79 @@ function SecretsPage({ apiClient, className }) {
1778
1934
  type: "button",
1779
1935
  onClick: () => deleteTarget && handleDelete(deleteTarget),
1780
1936
  disabled: isDeleting,
1781
- className: "rounded-lg bg-destructive px-4 py-2 text-sm font-bold text-destructive-foreground hover:bg-destructive/90 transition-colors disabled:opacity-50 active:scale-[0.97]",
1937
+ className: "rounded-md bg-destructive px-4 py-2 text-sm font-bold text-destructive-foreground hover:bg-destructive/90 transition-colors disabled:opacity-50 active:scale-[0.97]",
1782
1938
  children: isDeleting ? "Deleting..." : "Delete Secret"
1783
1939
  }
1784
1940
  )
1785
1941
  ] })
1786
1942
  ] }) }),
1787
- /* @__PURE__ */ jsxs5("div", { className: "rounded-xl border border-border bg-card shadow-sm overflow-hidden", children: [
1788
- /* @__PURE__ */ jsxs5("div", { className: "border-b border-border bg-muted/20 px-6 py-4 flex items-center justify-between", children: [
1789
- /* @__PURE__ */ jsx5("h2", { className: "text-sm font-bold text-foreground", children: "Your Secrets" }),
1943
+ /* @__PURE__ */ jsxs5("div", { className: "overflow-hidden rounded-lg border border-border bg-card shadow-[var(--shadow-card)]", children: [
1944
+ /* @__PURE__ */ jsxs5("div", { className: "border-b border-border px-6 py-4 flex items-center justify-between", children: [
1945
+ /* @__PURE__ */ jsx5("div", { className: "flex gap-6", children: /* @__PURE__ */ jsx5("button", { type: "button", className: "text-xs font-bold uppercase tracking-widest text-foreground border-b-2 border-foreground pb-1", children: "All Secrets" }) }),
1790
1946
  /* @__PURE__ */ jsxs5("span", { className: "text-xs text-muted-foreground font-mono", children: [
1791
1947
  secrets.length,
1792
1948
  " secret",
1793
1949
  secrets.length !== 1 ? "s" : ""
1794
1950
  ] })
1795
1951
  ] }),
1796
- loading ? /* @__PURE__ */ jsx5("div", { className: "p-12 text-center", children: /* @__PURE__ */ jsx5("div", { className: "mx-auto h-6 w-6 animate-spin rounded-full border-2 border-primary border-t-transparent" }) }) : secrets.length === 0 ? /* @__PURE__ */ jsxs5("div", { className: "p-12 text-center", children: [
1797
- /* @__PURE__ */ jsx5(Lock, { className: "mx-auto mb-3 h-10 w-10 text-muted-foreground" }),
1798
- /* @__PURE__ */ jsx5("p", { className: "text-sm font-medium text-foreground mb-1", children: "No secrets yet" }),
1799
- /* @__PURE__ */ jsx5("p", { className: "text-sm text-muted-foreground mb-4", children: "Create a secret to inject into your sandboxes." }),
1952
+ loading ? /* @__PURE__ */ jsx5("div", { className: "flex items-center justify-center py-16", children: /* @__PURE__ */ jsx5("div", { className: "h-6 w-6 animate-spin rounded-full border-2 border-muted-foreground border-t-transparent" }) }) : secrets.length === 0 ? /* @__PURE__ */ jsxs5("div", { className: "flex flex-col items-center justify-center py-16 text-center", children: [
1953
+ /* @__PURE__ */ jsx5(Lock, { className: "h-10 w-10 text-muted-foreground mb-4" }),
1954
+ /* @__PURE__ */ jsx5("h3", { className: "text-lg font-semibold text-foreground", children: "No secrets yet" }),
1955
+ /* @__PURE__ */ jsx5("p", { className: "mt-1 text-sm text-muted-foreground max-w-sm", children: "Create a secret to inject into your sandboxes." }),
1800
1956
  /* @__PURE__ */ jsxs5(
1801
1957
  "button",
1802
1958
  {
1803
1959
  type: "button",
1804
1960
  onClick: () => setIsCreateOpen(true),
1805
- className: "inline-flex items-center gap-2 rounded-lg bg-primary/20 border border-primary/30 px-4 py-2 text-sm font-bold text-primary hover:bg-primary hover:text-primary-foreground transition-colors",
1961
+ 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]",
1806
1962
  children: [
1807
1963
  /* @__PURE__ */ jsx5(Plus3, { className: "h-4 w-4" }),
1808
1964
  "Create Secret"
1809
1965
  ]
1810
1966
  }
1811
1967
  )
1812
- ] }) : /* @__PURE__ */ jsxs5("table", { className: "w-full text-sm", children: [
1813
- /* @__PURE__ */ jsx5("thead", { className: "bg-muted/10 border-b border-border", children: /* @__PURE__ */ jsxs5("tr", { children: [
1814
- /* @__PURE__ */ jsx5("th", { className: "px-6 py-2.5 text-left text-xs font-medium text-muted-foreground", children: "Name" }),
1815
- /* @__PURE__ */ jsx5("th", { className: "px-6 py-2.5 text-left text-xs font-medium text-muted-foreground", children: "Created" }),
1816
- /* @__PURE__ */ jsx5("th", { className: "px-6 py-2.5 text-right text-xs font-medium text-muted-foreground w-16" })
1968
+ ] }) : /* @__PURE__ */ jsxs5("table", { className: "w-full text-left border-collapse", children: [
1969
+ /* @__PURE__ */ jsx5("thead", { children: /* @__PURE__ */ jsxs5("tr", { className: "bg-muted/30 border-b border-border", children: [
1970
+ /* @__PURE__ */ jsx5("th", { className: "px-6 py-4 text-[10px] font-bold uppercase tracking-widest text-muted-foreground", children: "Secret Name" }),
1971
+ /* @__PURE__ */ jsx5("th", { className: "px-6 py-4 text-[10px] font-bold uppercase tracking-widest text-muted-foreground", children: "Encrypted Value" }),
1972
+ /* @__PURE__ */ jsx5("th", { className: "px-6 py-4 text-[10px] font-bold uppercase tracking-widest text-muted-foreground text-right", children: "Created" }),
1973
+ /* @__PURE__ */ jsx5("th", { className: "px-6 py-4 w-12" })
1817
1974
  ] }) }),
1818
- /* @__PURE__ */ jsx5("tbody", { className: "divide-y divide-border", children: secrets.map((secret) => /* @__PURE__ */ jsxs5("tr", { className: "hover:bg-muted/30 transition-colors", children: [
1819
- /* @__PURE__ */ jsx5("td", { className: "px-6 py-3", children: /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2", children: [
1820
- /* @__PURE__ */ jsx5(Lock, { className: "h-3.5 w-3.5 text-muted-foreground" }),
1821
- /* @__PURE__ */ jsx5("span", { className: "font-mono text-xs font-bold text-foreground", children: secret.name })
1975
+ /* @__PURE__ */ jsx5("tbody", { className: "divide-y divide-border", children: secrets.map((secret) => /* @__PURE__ */ jsxs5("tr", { className: "hover:bg-muted/20 transition-colors", children: [
1976
+ /* @__PURE__ */ jsx5("td", { className: "px-6 py-4", children: /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-3", children: [
1977
+ /* @__PURE__ */ jsx5(Key, { className: "h-4 w-4 text-muted-foreground" }),
1978
+ /* @__PURE__ */ jsx5("span", { className: "text-sm font-bold font-mono text-foreground", children: secret.name })
1822
1979
  ] }) }),
1823
- /* @__PURE__ */ jsx5("td", { className: "px-6 py-3 text-xs text-muted-foreground", children: formatDate(secret.createdAt) }),
1824
- /* @__PURE__ */ jsx5("td", { className: "px-6 py-3 text-right", children: /* @__PURE__ */ jsx5(
1980
+ /* @__PURE__ */ jsx5("td", { className: "px-6 py-4", children: /* @__PURE__ */ jsx5("code", { className: "text-xs font-mono text-muted-foreground bg-muted px-2 py-1 rounded", children: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" }) }),
1981
+ /* @__PURE__ */ jsx5("td", { className: "px-6 py-4 text-right", children: /* @__PURE__ */ jsx5("span", { className: "text-xs text-muted-foreground", children: formatDate(secret.createdAt) }) }),
1982
+ /* @__PURE__ */ jsx5("td", { className: "px-6 py-4", children: /* @__PURE__ */ jsx5(
1825
1983
  "button",
1826
1984
  {
1827
1985
  type: "button",
1828
1986
  onClick: () => setDeleteTarget(secret.name),
1829
- className: "p-1 text-muted-foreground hover:text-destructive transition-colors rounded",
1987
+ className: "p-1.5 rounded-md text-muted-foreground hover:text-destructive hover:bg-destructive/10 transition-colors",
1988
+ "aria-label": `Delete ${secret.name}`,
1830
1989
  children: /* @__PURE__ */ jsx5(Trash23, { className: "h-4 w-4" })
1831
1990
  }
1832
1991
  ) })
1833
1992
  ] }, secret.name)) })
1834
1993
  ] })
1994
+ ] }),
1995
+ /* @__PURE__ */ jsxs5("div", { className: "grid grid-cols-1 gap-6 md:grid-cols-2", children: [
1996
+ /* @__PURE__ */ jsxs5("div", { className: "rounded-lg border border-border bg-card p-6 shadow-[var(--shadow-card)]", children: [
1997
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-3 mb-3", children: [
1998
+ /* @__PURE__ */ jsx5("div", { className: "flex h-9 w-9 items-center justify-center rounded-md bg-[var(--brand-primary,hsl(var(--primary)))] text-[var(--btn-primary-text)]", children: /* @__PURE__ */ jsx5(Shield, { className: "h-5 w-5" }) }),
1999
+ /* @__PURE__ */ jsx5("h3", { className: "text-sm font-bold text-foreground", children: "Encryption Standard" })
2000
+ ] }),
2001
+ /* @__PURE__ */ jsx5("p", { className: "text-sm text-muted-foreground leading-relaxed", children: "Your secrets are encrypted using AES-256-GCM at rest and TLS 1.3 in transit. Hardware Security Modules manage all root keys." })
2002
+ ] }),
2003
+ /* @__PURE__ */ jsxs5("div", { className: "rounded-lg border border-border bg-card p-6 shadow-[var(--shadow-card)]", children: [
2004
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-3 mb-3", children: [
2005
+ /* @__PURE__ */ jsx5("div", { className: "flex h-9 w-9 items-center justify-center rounded-md bg-[var(--brand-primary,hsl(var(--primary)))] text-[var(--btn-primary-text)]", children: /* @__PURE__ */ jsx5(Lock, { className: "h-5 w-5" }) }),
2006
+ /* @__PURE__ */ jsx5("h3", { className: "text-sm font-bold text-foreground", children: "Access Policy" })
2007
+ ] }),
2008
+ /* @__PURE__ */ jsx5("p", { className: "text-sm text-muted-foreground leading-relaxed", children: "Secrets are injected at sandbox creation time and are never exposed in logs, API responses, or container metadata." })
2009
+ ] })
1835
2010
  ] })
1836
2011
  ] });
1837
2012
  }
@@ -1860,6 +2035,928 @@ function TemplatesPage({ templates, loading = false, onUseTemplate, className })
1860
2035
  ] });
1861
2036
  }
1862
2037
 
2038
+ // src/pages/startup-scripts-page.tsx
2039
+ import * as React6 from "react";
2040
+ import {
2041
+ Play,
2042
+ Plus as Plus4,
2043
+ Trash2 as Trash24,
2044
+ Pencil,
2045
+ AlertCircle as AlertCircle3,
2046
+ Power,
2047
+ PowerOff,
2048
+ Shield as Shield2,
2049
+ Terminal,
2050
+ Clock,
2051
+ ChevronDown as ChevronDown2,
2052
+ ChevronUp,
2053
+ GripVertical,
2054
+ Lock as Lock2,
2055
+ Cpu as Cpu2,
2056
+ MemoryStick,
2057
+ Layers as Layers3
2058
+ } from "lucide-react";
2059
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
2060
+ var SCRIPT_TYPE_META = {
2061
+ bash: {
2062
+ label: "Bash",
2063
+ ext: "sh",
2064
+ interpreter: "bash",
2065
+ template: "#!/bin/bash\nset -e\n\n# Your startup script here\n"
2066
+ },
2067
+ python: {
2068
+ label: "Python",
2069
+ ext: "py",
2070
+ interpreter: "python3",
2071
+ template: "#!/usr/bin/env python3\nimport os\nimport subprocess\n\n# Your startup script here\n"
2072
+ },
2073
+ node: {
2074
+ label: "Node.js",
2075
+ ext: "js",
2076
+ interpreter: "node",
2077
+ template: "#!/usr/bin/env node\nconst { execSync } = require('child_process');\n\n// Your startup script here\n"
2078
+ },
2079
+ ruby: {
2080
+ label: "Ruby",
2081
+ ext: "rb",
2082
+ interpreter: "ruby",
2083
+ template: "#!/usr/bin/env ruby\n\n# Your startup script here\n"
2084
+ },
2085
+ custom: {
2086
+ label: "Custom",
2087
+ ext: "sh",
2088
+ interpreter: "sh",
2089
+ template: "#!/bin/sh\n\n# Specify your interpreter in the shebang line above\n"
2090
+ }
2091
+ };
2092
+ var SCRIPT_TEMPLATES = [
2093
+ {
2094
+ name: "Install Claude Code",
2095
+ description: "Install and authenticate Claude Code CLI",
2096
+ scriptType: "bash",
2097
+ content: `#!/bin/bash
2098
+ set -e
2099
+
2100
+ # Install Claude Code
2101
+ npm install -g @anthropic-ai/claude-code
2102
+
2103
+ # Authenticate if API key secret is available
2104
+ if [ -n "$ANTHROPIC_API_KEY" ]; then
2105
+ echo "Claude Code installed and API key configured"
2106
+ fi`,
2107
+ injectSecrets: ["ANTHROPIC_API_KEY"]
2108
+ },
2109
+ {
2110
+ name: "Git Clone & Setup",
2111
+ description: "Clone a repo, install dependencies, and run setup",
2112
+ scriptType: "bash",
2113
+ content: `#!/bin/bash
2114
+ set -e
2115
+
2116
+ REPO_URL="\${GIT_REPO_URL:-https://github.com/your-org/your-repo.git}"
2117
+ BRANCH="\${GIT_BRANCH:-main}"
2118
+
2119
+ cd /home/agent
2120
+ git clone --depth 1 --branch "$BRANCH" "$REPO_URL" workspace
2121
+ cd workspace
2122
+
2123
+ # Detect and install dependencies
2124
+ if [ -f "package.json" ]; then
2125
+ npm install
2126
+ elif [ -f "requirements.txt" ]; then
2127
+ pip install -r requirements.txt
2128
+ elif [ -f "Cargo.toml" ]; then
2129
+ cargo fetch
2130
+ elif [ -f "go.mod" ]; then
2131
+ go mod download
2132
+ fi
2133
+
2134
+ echo "Repository cloned and dependencies installed"`,
2135
+ injectSecrets: ["GITHUB_TOKEN"]
2136
+ },
2137
+ {
2138
+ name: "Python Environment",
2139
+ description: "Create a virtual environment with common packages",
2140
+ scriptType: "python",
2141
+ content: `#!/usr/bin/env python3
2142
+ import subprocess
2143
+ import sys
2144
+
2145
+ # Create and activate virtual environment
2146
+ subprocess.run([sys.executable, "-m", "venv", "/home/agent/.venv"], check=True)
2147
+
2148
+ # Install common packages
2149
+ packages = [
2150
+ "requests",
2151
+ "httpx",
2152
+ "python-dotenv",
2153
+ "pydantic",
2154
+ ]
2155
+
2156
+ subprocess.run(
2157
+ ["/home/agent/.venv/bin/pip", "install", "--quiet"] + packages,
2158
+ check=True,
2159
+ )
2160
+
2161
+ print(f"Python venv created with {len(packages)} packages")`,
2162
+ injectSecrets: []
2163
+ },
2164
+ {
2165
+ name: "SSH Key Setup",
2166
+ description: "Configure SSH keys for Git access",
2167
+ scriptType: "bash",
2168
+ content: `#!/bin/bash
2169
+ set -e
2170
+
2171
+ mkdir -p ~/.ssh
2172
+ chmod 700 ~/.ssh
2173
+
2174
+ # Write deploy key if secret is available
2175
+ if [ -n "$SSH_PRIVATE_KEY" ]; then
2176
+ echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_ed25519
2177
+ chmod 600 ~/.ssh/id_ed25519
2178
+ ssh-keyscan github.com >> ~/.ssh/known_hosts 2>/dev/null
2179
+ echo "SSH key configured for Git access"
2180
+ else
2181
+ echo "No SSH_PRIVATE_KEY secret found, skipping"
2182
+ fi`,
2183
+ injectSecrets: ["SSH_PRIVATE_KEY"]
2184
+ },
2185
+ {
2186
+ name: "Node.js Project Bootstrap",
2187
+ description: "Initialize a new Node.js project with TypeScript",
2188
+ scriptType: "node",
2189
+ content: `#!/usr/bin/env node
2190
+ const { execSync } = require('child_process');
2191
+ const fs = require('fs');
2192
+ const path = require('path');
2193
+
2194
+ const dir = '/home/agent/workspace';
2195
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
2196
+ process.chdir(dir);
2197
+
2198
+ if (!fs.existsSync('package.json')) {
2199
+ execSync('npm init -y', { stdio: 'inherit' });
2200
+ execSync('npm install typescript tsx @types/node --save-dev', { stdio: 'inherit' });
2201
+ execSync('npx tsc --init --target ES2022 --module NodeNext --moduleResolution NodeNext --outDir dist', { stdio: 'inherit' });
2202
+ console.log('Node.js + TypeScript project initialized');
2203
+ } else {
2204
+ execSync('npm install', { stdio: 'inherit' });
2205
+ console.log('Dependencies installed');
2206
+ }`,
2207
+ injectSecrets: []
2208
+ }
2209
+ ];
2210
+ function makeDefaultFormData(scriptType = "bash") {
2211
+ return {
2212
+ name: "",
2213
+ description: "",
2214
+ scriptType,
2215
+ content: SCRIPT_TYPE_META[scriptType].template,
2216
+ environments: [],
2217
+ minCpuCores: null,
2218
+ minRamGB: null,
2219
+ runOrder: 100,
2220
+ timeoutSeconds: 300,
2221
+ continueOnFailure: false,
2222
+ runAsRoot: false,
2223
+ injectSecrets: [],
2224
+ enabled: true
2225
+ };
2226
+ }
2227
+ function StartupScriptsPage({ apiClient, className }) {
2228
+ const [scripts, setScripts] = React6.useState([]);
2229
+ const [secrets, setSecrets] = React6.useState([]);
2230
+ const [environments, setEnvironments] = React6.useState([]);
2231
+ const [loading, setLoading] = React6.useState(true);
2232
+ const [error, setError] = React6.useState(null);
2233
+ const [isDialogOpen, setIsDialogOpen] = React6.useState(false);
2234
+ const [dialogStep, setDialogStep] = React6.useState("picker");
2235
+ const [transitionDir, setTransitionDir] = React6.useState("forward");
2236
+ const [stepKey, setStepKey] = React6.useState(0);
2237
+ const [editingScript, setEditingScript] = React6.useState(null);
2238
+ const [formData, setFormData] = React6.useState(makeDefaultFormData());
2239
+ const [isSaving, setIsSaving] = React6.useState(false);
2240
+ const [formError, setFormError] = React6.useState(null);
2241
+ const [showConditions, setShowConditions] = React6.useState(false);
2242
+ const [deleteTarget, setDeleteTarget] = React6.useState(null);
2243
+ const [isDeleting, setIsDeleting] = React6.useState(false);
2244
+ const apiRef = React6.useRef(apiClient);
2245
+ apiRef.current = apiClient;
2246
+ const loadGenRef = React6.useRef(0);
2247
+ const loadData = React6.useCallback(async (showSpinner = true) => {
2248
+ const gen = ++loadGenRef.current;
2249
+ try {
2250
+ if (showSpinner) setLoading(true);
2251
+ setError(null);
2252
+ const [scriptData, secretData, envData] = await Promise.all([
2253
+ apiRef.current.listScripts(),
2254
+ apiRef.current.listSecrets?.() ?? Promise.resolve([]),
2255
+ apiRef.current.listEnvironments?.() ?? Promise.resolve([])
2256
+ ]);
2257
+ if (gen !== loadGenRef.current) return;
2258
+ setScripts(scriptData);
2259
+ setSecrets(secretData);
2260
+ setEnvironments(envData);
2261
+ } catch (err) {
2262
+ if (gen !== loadGenRef.current) return;
2263
+ setError(err instanceof Error ? err.message : "Failed to load startup scripts");
2264
+ } finally {
2265
+ if (gen === loadGenRef.current) setLoading(false);
2266
+ }
2267
+ }, []);
2268
+ React6.useEffect(() => {
2269
+ loadData();
2270
+ }, [loadData]);
2271
+ const openCreate = () => {
2272
+ setEditingScript(null);
2273
+ setFormData(makeDefaultFormData());
2274
+ setFormError(null);
2275
+ setShowConditions(false);
2276
+ setDialogStep("picker");
2277
+ setIsDialogOpen(true);
2278
+ };
2279
+ const goToStep = (step) => {
2280
+ setTransitionDir(step === "form" ? "forward" : "back");
2281
+ setStepKey((k) => k + 1);
2282
+ setDialogStep(step);
2283
+ };
2284
+ const openBlankScript = (scriptType = "bash") => {
2285
+ setEditingScript(null);
2286
+ setFormData(makeDefaultFormData(scriptType));
2287
+ setFormError(null);
2288
+ setShowConditions(false);
2289
+ goToStep("form");
2290
+ };
2291
+ const openFromTemplate = (template) => {
2292
+ setEditingScript(null);
2293
+ setFormData({
2294
+ ...makeDefaultFormData(template.scriptType),
2295
+ name: template.name,
2296
+ description: template.description,
2297
+ content: template.content,
2298
+ injectSecrets: template.injectSecrets.filter(
2299
+ (s) => secrets.some((sec) => sec.name === s)
2300
+ )
2301
+ });
2302
+ setFormError(null);
2303
+ setShowConditions(false);
2304
+ goToStep("form");
2305
+ };
2306
+ const openEdit = (script) => {
2307
+ setEditingScript(script);
2308
+ setFormData({
2309
+ name: script.name,
2310
+ description: script.description,
2311
+ scriptType: script.scriptType ?? "bash",
2312
+ content: script.content,
2313
+ environments: script.environments,
2314
+ minCpuCores: script.minCpuCores,
2315
+ minRamGB: script.minRamGB,
2316
+ runOrder: script.runOrder,
2317
+ timeoutSeconds: script.timeoutSeconds,
2318
+ continueOnFailure: script.continueOnFailure,
2319
+ runAsRoot: script.runAsRoot,
2320
+ injectSecrets: script.injectSecrets,
2321
+ enabled: script.enabled
2322
+ });
2323
+ setFormError(null);
2324
+ setShowConditions(
2325
+ script.environments.length > 0 || script.minCpuCores !== null || script.minRamGB !== null
2326
+ );
2327
+ setDialogStep("form");
2328
+ setIsDialogOpen(true);
2329
+ };
2330
+ const handleSave = async () => {
2331
+ setFormError(null);
2332
+ setIsSaving(true);
2333
+ try {
2334
+ if (editingScript) {
2335
+ await apiRef.current.updateScript(editingScript.id, formData);
2336
+ } else {
2337
+ await apiRef.current.createScript(formData);
2338
+ }
2339
+ setIsDialogOpen(false);
2340
+ await loadData(false);
2341
+ } catch (err) {
2342
+ setFormError(err instanceof Error ? err.message : "Failed to save script");
2343
+ } finally {
2344
+ setIsSaving(false);
2345
+ }
2346
+ };
2347
+ const handleToggle = async (script) => {
2348
+ try {
2349
+ await apiRef.current.toggleScript(script.id);
2350
+ await loadData(false);
2351
+ } catch (err) {
2352
+ setError(err instanceof Error ? err.message : "Failed to toggle script");
2353
+ }
2354
+ };
2355
+ const handleDelete = async () => {
2356
+ if (!deleteTarget) return;
2357
+ setIsDeleting(true);
2358
+ try {
2359
+ await apiRef.current.deleteScript(deleteTarget.id);
2360
+ setDeleteTarget(null);
2361
+ await loadData(false);
2362
+ } catch (err) {
2363
+ setError(err instanceof Error ? err.message : "Failed to delete script");
2364
+ } finally {
2365
+ setIsDeleting(false);
2366
+ }
2367
+ };
2368
+ const toggleSecret = (name) => {
2369
+ setFormData((prev) => ({
2370
+ ...prev,
2371
+ injectSecrets: prev.injectSecrets.includes(name) ? prev.injectSecrets.filter((s) => s !== name) : [...prev.injectSecrets, name]
2372
+ }));
2373
+ };
2374
+ const toggleEnvironment = (env) => {
2375
+ setFormData((prev) => ({
2376
+ ...prev,
2377
+ environments: prev.environments.includes(env) ? prev.environments.filter((e) => e !== env) : [...prev.environments, env]
2378
+ }));
2379
+ };
2380
+ const activeCount = scripts.filter((s) => s.enabled).length;
2381
+ return /* @__PURE__ */ jsxs7("div", { className: cn("space-y-6", className), children: [
2382
+ /* @__PURE__ */ jsxs7("div", { className: "flex flex-col gap-4 md:flex-row md:items-end md:justify-between", children: [
2383
+ /* @__PURE__ */ jsxs7("div", { children: [
2384
+ /* @__PURE__ */ jsx7("h1", { className: "font-display text-3xl font-extrabold tracking-tight text-foreground", children: "Startup Scripts" }),
2385
+ /* @__PURE__ */ jsx7("p", { className: "mt-1 text-sm text-muted-foreground", children: "Define scripts that run automatically when your sandboxes start. Scripts can access your encrypted secrets." })
2386
+ ] }),
2387
+ /* @__PURE__ */ jsxs7(
2388
+ "button",
2389
+ {
2390
+ type: "button",
2391
+ onClick: openCreate,
2392
+ className: "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)]",
2393
+ children: [
2394
+ /* @__PURE__ */ jsx7(Plus4, { className: "h-4 w-4" }),
2395
+ "New Script"
2396
+ ]
2397
+ }
2398
+ )
2399
+ ] }),
2400
+ /* @__PURE__ */ jsxs7("div", { className: "grid grid-cols-1 gap-6 md:grid-cols-4", children: [
2401
+ /* @__PURE__ */ jsxs7("div", { className: "rounded-lg border border-border bg-card p-5 shadow-[var(--shadow-card)]", children: [
2402
+ /* @__PURE__ */ jsx7("p", { className: "text-[10px] font-bold uppercase tracking-widest text-muted-foreground", children: "Total Scripts" }),
2403
+ /* @__PURE__ */ jsx7("div", { className: "mt-2 flex items-baseline gap-2", children: /* @__PURE__ */ jsx7("span", { className: "font-display text-2xl font-extrabold text-foreground", children: scripts.length }) })
2404
+ ] }),
2405
+ /* @__PURE__ */ jsxs7("div", { className: "rounded-lg border border-border bg-card p-5 shadow-[var(--shadow-card)]", children: [
2406
+ /* @__PURE__ */ jsx7("p", { className: "text-[10px] font-bold uppercase tracking-widest text-muted-foreground", children: "Active" }),
2407
+ /* @__PURE__ */ jsxs7("div", { className: "mt-2 flex items-baseline gap-2", children: [
2408
+ /* @__PURE__ */ jsx7("span", { className: "font-display text-2xl font-extrabold text-foreground", children: activeCount }),
2409
+ /* @__PURE__ */ jsxs7("span", { className: "text-xs text-muted-foreground", children: [
2410
+ "of ",
2411
+ scripts.length
2412
+ ] })
2413
+ ] })
2414
+ ] }),
2415
+ /* @__PURE__ */ jsx7(
2416
+ InfoPanel,
2417
+ {
2418
+ className: "md:col-span-2",
2419
+ label: "Execution",
2420
+ title: "Scripts run before your agent starts.",
2421
+ description: "They execute in order, with full access to injected secrets as environment variables."
2422
+ }
2423
+ )
2424
+ ] }),
2425
+ error && /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-3 rounded-lg border border-destructive/30 bg-destructive/10 p-4", children: [
2426
+ /* @__PURE__ */ jsx7(AlertCircle3, { className: "h-5 w-5 shrink-0 text-destructive" }),
2427
+ /* @__PURE__ */ jsx7("p", { className: "text-sm font-medium text-destructive", children: error })
2428
+ ] }),
2429
+ /* @__PURE__ */ jsx7(Dialog, { open: isDialogOpen, onOpenChange: setIsDialogOpen, children: /* @__PURE__ */ jsxs7(DialogContent, { className: "max-w-2xl max-h-[90vh] overflow-y-auto", children: [
2430
+ dialogStep === "picker" && /* @__PURE__ */ jsxs7(
2431
+ "div",
2432
+ {
2433
+ className: cn(
2434
+ "animate-in fade-in duration-300",
2435
+ transitionDir === "back" ? "slide-in-from-left-4" : "slide-in-from-right-4"
2436
+ ),
2437
+ children: [
2438
+ /* @__PURE__ */ jsxs7(DialogHeader, { children: [
2439
+ /* @__PURE__ */ jsx7(DialogTitle, { children: "New Startup Script" }),
2440
+ /* @__PURE__ */ jsx7(DialogDescription, { children: "Start from a template or create a blank script." })
2441
+ ] }),
2442
+ /* @__PURE__ */ jsxs7("div", { className: "space-y-4 py-2", children: [
2443
+ /* @__PURE__ */ jsxs7("div", { children: [
2444
+ /* @__PURE__ */ jsx7("p", { className: "text-xs font-bold uppercase tracking-widest text-muted-foreground mb-2", children: "Blank Script" }),
2445
+ /* @__PURE__ */ jsx7("div", { className: "flex flex-wrap gap-2", children: Object.entries(SCRIPT_TYPE_META).map(([type, meta]) => /* @__PURE__ */ jsx7(
2446
+ "button",
2447
+ {
2448
+ type: "button",
2449
+ onClick: () => openBlankScript(type),
2450
+ className: "rounded-lg border border-border bg-card px-4 py-2.5 text-sm font-medium text-foreground hover:border-primary/30 hover:bg-muted/50 transition-colors",
2451
+ children: meta.label
2452
+ },
2453
+ type
2454
+ )) })
2455
+ ] }),
2456
+ /* @__PURE__ */ jsxs7("div", { children: [
2457
+ /* @__PURE__ */ jsx7("p", { className: "text-xs font-bold uppercase tracking-widest text-muted-foreground mb-2", children: "Templates" }),
2458
+ /* @__PURE__ */ jsx7("div", { className: "space-y-2", children: SCRIPT_TEMPLATES.map((tmpl) => /* @__PURE__ */ jsxs7(
2459
+ "button",
2460
+ {
2461
+ type: "button",
2462
+ onClick: () => openFromTemplate(tmpl),
2463
+ className: "w-full text-left rounded-lg border border-border bg-card p-4 hover:border-primary/30 hover:bg-muted/30 transition-colors group",
2464
+ children: [
2465
+ /* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between", children: [
2466
+ /* @__PURE__ */ jsxs7("div", { children: [
2467
+ /* @__PURE__ */ jsx7("h4", { className: "text-sm font-bold text-foreground group-hover:text-primary transition-colors", children: tmpl.name }),
2468
+ /* @__PURE__ */ jsx7("p", { className: "mt-0.5 text-xs text-muted-foreground", children: tmpl.description })
2469
+ ] }),
2470
+ /* @__PURE__ */ jsx7("span", { className: "text-[10px] font-mono text-muted-foreground bg-muted rounded px-1.5 py-0.5", children: SCRIPT_TYPE_META[tmpl.scriptType].label })
2471
+ ] }),
2472
+ tmpl.injectSecrets.length > 0 && /* @__PURE__ */ jsx7("div", { className: "mt-2 flex flex-wrap gap-1", children: tmpl.injectSecrets.map((s) => /* @__PURE__ */ jsxs7("span", { className: "inline-flex items-center gap-0.5 rounded-full bg-muted px-2 py-0.5 text-[10px] text-muted-foreground", children: [
2473
+ /* @__PURE__ */ jsx7(Lock2, { className: "h-2.5 w-2.5" }),
2474
+ s
2475
+ ] }, s)) })
2476
+ ]
2477
+ },
2478
+ tmpl.name
2479
+ )) })
2480
+ ] })
2481
+ ] })
2482
+ ]
2483
+ },
2484
+ `picker-${stepKey}`
2485
+ ),
2486
+ dialogStep === "form" && /* @__PURE__ */ jsxs7(
2487
+ "div",
2488
+ {
2489
+ className: cn(
2490
+ "animate-in fade-in duration-300",
2491
+ transitionDir === "forward" ? "slide-in-from-right-4" : "slide-in-from-left-4"
2492
+ ),
2493
+ children: [
2494
+ /* @__PURE__ */ jsxs7(DialogHeader, { children: [
2495
+ /* @__PURE__ */ jsx7(DialogTitle, { children: editingScript ? "Edit Script" : "Create Startup Script" }),
2496
+ /* @__PURE__ */ jsx7(DialogDescription, { children: editingScript ? "Modify your startup script configuration." : "Define a shell script that runs when sandboxes start. Scripts execute before the AI agent." })
2497
+ ] }),
2498
+ /* @__PURE__ */ jsxs7("div", { className: "space-y-5 py-2", children: [
2499
+ /* @__PURE__ */ jsxs7("div", { children: [
2500
+ /* @__PURE__ */ jsx7("label", { className: "text-xs font-bold uppercase tracking-widest text-muted-foreground", children: "Name" }),
2501
+ /* @__PURE__ */ jsx7(
2502
+ "input",
2503
+ {
2504
+ type: "text",
2505
+ value: formData.name,
2506
+ onChange: (e) => setFormData((p) => ({ ...p, name: e.target.value })),
2507
+ placeholder: "Install Claude Code",
2508
+ maxLength: 64,
2509
+ className: "mt-1.5 w-full rounded-lg border border-border bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/30"
2510
+ }
2511
+ )
2512
+ ] }),
2513
+ /* @__PURE__ */ jsxs7("div", { children: [
2514
+ /* @__PURE__ */ jsx7("label", { className: "text-xs font-bold uppercase tracking-widest text-muted-foreground", children: "Description" }),
2515
+ /* @__PURE__ */ jsx7(
2516
+ "input",
2517
+ {
2518
+ type: "text",
2519
+ value: formData.description,
2520
+ onChange: (e) => setFormData((p) => ({ ...p, description: e.target.value })),
2521
+ placeholder: "Sets up Claude Code with authentication",
2522
+ maxLength: 256,
2523
+ className: "mt-1.5 w-full rounded-lg border border-border bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/30"
2524
+ }
2525
+ )
2526
+ ] }),
2527
+ /* @__PURE__ */ jsxs7("div", { children: [
2528
+ /* @__PURE__ */ jsx7("label", { className: "text-xs font-bold uppercase tracking-widest text-muted-foreground", children: "Language" }),
2529
+ /* @__PURE__ */ jsx7("div", { className: "mt-1.5 flex flex-wrap gap-2", children: Object.entries(SCRIPT_TYPE_META).map(([type, meta]) => /* @__PURE__ */ jsx7(
2530
+ "button",
2531
+ {
2532
+ type: "button",
2533
+ onClick: () => {
2534
+ const currentMeta = SCRIPT_TYPE_META[formData.scriptType];
2535
+ const isDefaultContent = formData.content === currentMeta.template;
2536
+ setFormData((p) => ({
2537
+ ...p,
2538
+ scriptType: type,
2539
+ content: isDefaultContent ? meta.template : p.content
2540
+ }));
2541
+ },
2542
+ className: cn(
2543
+ "rounded-lg px-3 py-1.5 text-xs font-medium border transition-colors",
2544
+ formData.scriptType === type ? "bg-primary/10 border-primary/30 text-primary" : "bg-background border-border text-muted-foreground hover:border-primary/20"
2545
+ ),
2546
+ children: meta.label
2547
+ },
2548
+ type
2549
+ )) })
2550
+ ] }),
2551
+ /* @__PURE__ */ jsxs7("div", { children: [
2552
+ /* @__PURE__ */ jsx7("label", { className: "text-xs font-bold uppercase tracking-widest text-muted-foreground", children: "Script" }),
2553
+ /* @__PURE__ */ jsx7(
2554
+ "textarea",
2555
+ {
2556
+ value: formData.content,
2557
+ onChange: (e) => setFormData((p) => ({ ...p, content: e.target.value })),
2558
+ rows: 12,
2559
+ spellCheck: false,
2560
+ className: "mt-1.5 w-full rounded-lg border border-border bg-[var(--depth-1,hsl(var(--muted)))] px-4 py-3 font-mono text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/30 resize-y"
2561
+ }
2562
+ ),
2563
+ /* @__PURE__ */ jsxs7("p", { className: "mt-1 text-xs text-muted-foreground", children: [
2564
+ SCRIPT_TYPE_META[formData.scriptType].label,
2565
+ " script. Injected secrets are available as environment variables (e.g. ",
2566
+ /* @__PURE__ */ jsx7("code", { className: "text-primary", children: "$GITHUB_TOKEN" }),
2567
+ ")."
2568
+ ] })
2569
+ ] }),
2570
+ secrets.length > 0 && /* @__PURE__ */ jsxs7("div", { children: [
2571
+ /* @__PURE__ */ jsx7("label", { className: "text-xs font-bold uppercase tracking-widest text-muted-foreground", children: "Inject Secrets" }),
2572
+ /* @__PURE__ */ jsx7("p", { className: "mt-0.5 text-xs text-muted-foreground", children: "Select secrets to make available as environment variables." }),
2573
+ /* @__PURE__ */ jsx7("div", { className: "mt-2 flex flex-wrap gap-2", children: secrets.map((secret) => {
2574
+ const selected = formData.injectSecrets.includes(secret.name);
2575
+ return /* @__PURE__ */ jsxs7(
2576
+ "button",
2577
+ {
2578
+ type: "button",
2579
+ onClick: () => toggleSecret(secret.name),
2580
+ className: cn(
2581
+ "inline-flex items-center gap-1.5 rounded-full px-3 py-1.5 text-xs font-medium border transition-colors",
2582
+ selected ? "bg-primary/10 border-primary/30 text-primary" : "bg-muted border-border text-muted-foreground hover:border-primary/20"
2583
+ ),
2584
+ children: [
2585
+ /* @__PURE__ */ jsx7(Lock2, { className: "h-3 w-3" }),
2586
+ secret.name
2587
+ ]
2588
+ },
2589
+ secret.name
2590
+ );
2591
+ }) })
2592
+ ] }),
2593
+ /* @__PURE__ */ jsxs7("div", { children: [
2594
+ /* @__PURE__ */ jsxs7(
2595
+ "button",
2596
+ {
2597
+ type: "button",
2598
+ onClick: () => setShowConditions(!showConditions),
2599
+ className: "flex items-center gap-2 text-xs font-bold uppercase tracking-widest text-muted-foreground hover:text-foreground transition-colors",
2600
+ children: [
2601
+ showConditions ? /* @__PURE__ */ jsx7(ChevronUp, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx7(ChevronDown2, { className: "h-3.5 w-3.5" }),
2602
+ "Conditions & Execution"
2603
+ ]
2604
+ }
2605
+ ),
2606
+ showConditions && /* @__PURE__ */ jsxs7("div", { className: "mt-3 space-y-4 rounded-lg border border-border bg-muted/30 p-4", children: [
2607
+ /* @__PURE__ */ jsxs7("div", { children: [
2608
+ /* @__PURE__ */ jsxs7("label", { className: "text-xs font-bold uppercase tracking-widest text-muted-foreground flex items-center gap-1.5", children: [
2609
+ /* @__PURE__ */ jsx7(Layers3, { className: "h-3.5 w-3.5" }),
2610
+ "Environments"
2611
+ ] }),
2612
+ /* @__PURE__ */ jsx7("p", { className: "mt-0.5 text-xs text-muted-foreground", children: environments.length > 0 ? "Only run for these environments. Leave empty to run for all." : "No templates configured. Script will run for all environments." }),
2613
+ environments.length > 0 && /* @__PURE__ */ jsx7("div", { className: "mt-2 flex flex-wrap gap-2", children: environments.map((env) => {
2614
+ const selected = formData.environments.includes(env.id);
2615
+ return /* @__PURE__ */ jsx7(
2616
+ "button",
2617
+ {
2618
+ type: "button",
2619
+ onClick: () => toggleEnvironment(env.id),
2620
+ className: cn(
2621
+ "rounded-full px-3 py-1 text-xs font-medium border transition-colors",
2622
+ selected ? "bg-primary/10 border-primary/30 text-primary" : "bg-background border-border text-muted-foreground hover:border-primary/20"
2623
+ ),
2624
+ children: env.name || env.id
2625
+ },
2626
+ env.id
2627
+ );
2628
+ }) })
2629
+ ] }),
2630
+ /* @__PURE__ */ jsxs7("div", { className: "grid grid-cols-2 gap-4", children: [
2631
+ /* @__PURE__ */ jsxs7("div", { children: [
2632
+ /* @__PURE__ */ jsxs7("label", { className: "text-xs font-bold uppercase tracking-widest text-muted-foreground flex items-center gap-1.5", children: [
2633
+ /* @__PURE__ */ jsx7(Cpu2, { className: "h-3.5 w-3.5" }),
2634
+ "Min CPU Cores"
2635
+ ] }),
2636
+ /* @__PURE__ */ jsx7(
2637
+ "input",
2638
+ {
2639
+ type: "number",
2640
+ value: formData.minCpuCores ?? "",
2641
+ onChange: (e) => setFormData((p) => ({
2642
+ ...p,
2643
+ minCpuCores: e.target.value ? Number(e.target.value) : null
2644
+ })),
2645
+ placeholder: "Any",
2646
+ min: 0.5,
2647
+ step: 0.5,
2648
+ className: "mt-1.5 w-full rounded-lg border border-border bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/30"
2649
+ }
2650
+ )
2651
+ ] }),
2652
+ /* @__PURE__ */ jsxs7("div", { children: [
2653
+ /* @__PURE__ */ jsxs7("label", { className: "text-xs font-bold uppercase tracking-widest text-muted-foreground flex items-center gap-1.5", children: [
2654
+ /* @__PURE__ */ jsx7(MemoryStick, { className: "h-3.5 w-3.5" }),
2655
+ "Min RAM (GB)"
2656
+ ] }),
2657
+ /* @__PURE__ */ jsx7(
2658
+ "input",
2659
+ {
2660
+ type: "number",
2661
+ value: formData.minRamGB ?? "",
2662
+ onChange: (e) => setFormData((p) => ({
2663
+ ...p,
2664
+ minRamGB: e.target.value ? Number(e.target.value) : null
2665
+ })),
2666
+ placeholder: "Any",
2667
+ min: 1,
2668
+ step: 1,
2669
+ className: "mt-1.5 w-full rounded-lg border border-border bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/30"
2670
+ }
2671
+ )
2672
+ ] })
2673
+ ] }),
2674
+ /* @__PURE__ */ jsxs7("div", { className: "grid grid-cols-2 gap-4", children: [
2675
+ /* @__PURE__ */ jsxs7("div", { children: [
2676
+ /* @__PURE__ */ jsxs7("label", { className: "text-xs font-bold uppercase tracking-widest text-muted-foreground flex items-center gap-1.5", children: [
2677
+ /* @__PURE__ */ jsx7(GripVertical, { className: "h-3.5 w-3.5" }),
2678
+ "Run Order"
2679
+ ] }),
2680
+ /* @__PURE__ */ jsx7(
2681
+ "input",
2682
+ {
2683
+ type: "number",
2684
+ value: formData.runOrder,
2685
+ onChange: (e) => setFormData((p) => ({ ...p, runOrder: e.target.value ? Math.max(0, Number(e.target.value)) : 0 })),
2686
+ min: 0,
2687
+ max: 9999,
2688
+ className: "mt-1.5 w-full rounded-lg border border-border bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/30"
2689
+ }
2690
+ ),
2691
+ /* @__PURE__ */ jsx7("p", { className: "mt-1 text-xs text-muted-foreground", children: "Lower runs first" })
2692
+ ] }),
2693
+ /* @__PURE__ */ jsxs7("div", { children: [
2694
+ /* @__PURE__ */ jsxs7("label", { className: "text-xs font-bold uppercase tracking-widest text-muted-foreground flex items-center gap-1.5", children: [
2695
+ /* @__PURE__ */ jsx7(Clock, { className: "h-3.5 w-3.5" }),
2696
+ "Timeout (seconds)"
2697
+ ] }),
2698
+ /* @__PURE__ */ jsx7(
2699
+ "input",
2700
+ {
2701
+ type: "number",
2702
+ value: formData.timeoutSeconds,
2703
+ onChange: (e) => setFormData((p) => ({ ...p, timeoutSeconds: e.target.value ? Math.max(5, Number(e.target.value)) : 300 })),
2704
+ min: 5,
2705
+ max: 3600,
2706
+ className: "mt-1.5 w-full rounded-lg border border-border bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/30"
2707
+ }
2708
+ )
2709
+ ] })
2710
+ ] }),
2711
+ /* @__PURE__ */ jsxs7("div", { className: "space-y-3", children: [
2712
+ /* @__PURE__ */ jsxs7("label", { className: "flex items-center justify-between cursor-pointer", children: [
2713
+ /* @__PURE__ */ jsxs7("div", { children: [
2714
+ /* @__PURE__ */ jsx7("span", { className: "text-sm font-medium text-foreground", children: "Continue on failure" }),
2715
+ /* @__PURE__ */ jsx7("p", { className: "text-xs text-muted-foreground", children: "If script fails, continue starting the sandbox" })
2716
+ ] }),
2717
+ /* @__PURE__ */ jsx7(
2718
+ "button",
2719
+ {
2720
+ type: "button",
2721
+ onClick: () => setFormData((p) => ({ ...p, continueOnFailure: !p.continueOnFailure })),
2722
+ className: cn(
2723
+ "relative h-6 w-11 rounded-full transition-colors",
2724
+ formData.continueOnFailure ? "bg-primary" : "bg-border"
2725
+ ),
2726
+ children: /* @__PURE__ */ jsx7(
2727
+ "span",
2728
+ {
2729
+ className: cn(
2730
+ "absolute top-0.5 left-0.5 h-5 w-5 rounded-full bg-white shadow transition-transform",
2731
+ formData.continueOnFailure && "translate-x-5"
2732
+ )
2733
+ }
2734
+ )
2735
+ }
2736
+ )
2737
+ ] }),
2738
+ /* @__PURE__ */ jsxs7("label", { className: "flex items-center justify-between cursor-pointer", children: [
2739
+ /* @__PURE__ */ jsxs7("div", { children: [
2740
+ /* @__PURE__ */ jsx7("span", { className: "text-sm font-medium text-foreground", children: "Run as root" }),
2741
+ /* @__PURE__ */ jsx7("p", { className: "text-xs text-muted-foreground", children: "Execute with root privileges instead of the agent user" })
2742
+ ] }),
2743
+ /* @__PURE__ */ jsx7(
2744
+ "button",
2745
+ {
2746
+ type: "button",
2747
+ onClick: () => setFormData((p) => ({ ...p, runAsRoot: !p.runAsRoot })),
2748
+ className: cn(
2749
+ "relative h-6 w-11 rounded-full transition-colors",
2750
+ formData.runAsRoot ? "bg-primary" : "bg-border"
2751
+ ),
2752
+ children: /* @__PURE__ */ jsx7(
2753
+ "span",
2754
+ {
2755
+ className: cn(
2756
+ "absolute top-0.5 left-0.5 h-5 w-5 rounded-full bg-white shadow transition-transform",
2757
+ formData.runAsRoot && "translate-x-5"
2758
+ )
2759
+ }
2760
+ )
2761
+ }
2762
+ )
2763
+ ] })
2764
+ ] })
2765
+ ] })
2766
+ ] }),
2767
+ formError && /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2 rounded-lg border border-destructive/30 bg-destructive/10 p-3", children: [
2768
+ /* @__PURE__ */ jsx7(AlertCircle3, { className: "h-4 w-4 text-destructive" }),
2769
+ /* @__PURE__ */ jsx7("p", { className: "text-sm text-destructive", children: formError })
2770
+ ] })
2771
+ ] }),
2772
+ /* @__PURE__ */ jsxs7(DialogFooter, { className: "flex items-center justify-between sm:justify-between", children: [
2773
+ /* @__PURE__ */ jsx7("div", { children: !editingScript && /* @__PURE__ */ jsxs7(
2774
+ "button",
2775
+ {
2776
+ type: "button",
2777
+ onClick: () => goToStep("picker"),
2778
+ className: "inline-flex items-center gap-1.5 rounded-lg border border-border px-4 py-2 text-sm font-medium text-muted-foreground hover:text-foreground hover:bg-muted transition-colors",
2779
+ children: [
2780
+ /* @__PURE__ */ jsx7("svg", { className: "h-4 w-4", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx7("path", { d: "m15 18-6-6 6-6" }) }),
2781
+ "Back"
2782
+ ]
2783
+ }
2784
+ ) }),
2785
+ /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2", children: [
2786
+ /* @__PURE__ */ jsx7(
2787
+ "button",
2788
+ {
2789
+ type: "button",
2790
+ onClick: () => setIsDialogOpen(false),
2791
+ className: "rounded-lg border border-border px-4 py-2 text-sm font-medium text-foreground hover:bg-muted transition-colors",
2792
+ children: "Cancel"
2793
+ }
2794
+ ),
2795
+ /* @__PURE__ */ jsx7(
2796
+ "button",
2797
+ {
2798
+ type: "button",
2799
+ onClick: handleSave,
2800
+ disabled: isSaving || !formData.name.trim() || !formData.content.trim(),
2801
+ className: "rounded-lg bg-[var(--btn-primary-bg)] px-4 py-2 text-sm font-bold text-[var(--btn-primary-text)] shadow-sm transition-colors hover:bg-[var(--btn-primary-hover)] disabled:opacity-50",
2802
+ children: isSaving ? "Saving..." : editingScript ? "Save Changes" : "Create Script"
2803
+ }
2804
+ )
2805
+ ] })
2806
+ ] })
2807
+ ]
2808
+ },
2809
+ `form-${stepKey}`
2810
+ )
2811
+ ] }) }),
2812
+ /* @__PURE__ */ jsx7(Dialog, { open: !!deleteTarget, onOpenChange: () => setDeleteTarget(null), children: /* @__PURE__ */ jsxs7(DialogContent, { className: "max-w-md", children: [
2813
+ /* @__PURE__ */ jsxs7(DialogHeader, { children: [
2814
+ /* @__PURE__ */ jsx7(DialogTitle, { children: "Delete Startup Script" }),
2815
+ /* @__PURE__ */ jsxs7(DialogDescription, { children: [
2816
+ "Are you sure you want to delete \u201C",
2817
+ deleteTarget?.name,
2818
+ "\u201D? This action cannot be undone."
2819
+ ] })
2820
+ ] }),
2821
+ /* @__PURE__ */ jsxs7(DialogFooter, { children: [
2822
+ /* @__PURE__ */ jsx7(
2823
+ "button",
2824
+ {
2825
+ type: "button",
2826
+ onClick: () => setDeleteTarget(null),
2827
+ className: "rounded-lg border border-border px-4 py-2 text-sm font-medium text-foreground hover:bg-muted transition-colors",
2828
+ children: "Cancel"
2829
+ }
2830
+ ),
2831
+ /* @__PURE__ */ jsx7(
2832
+ "button",
2833
+ {
2834
+ type: "button",
2835
+ onClick: handleDelete,
2836
+ disabled: isDeleting,
2837
+ className: "rounded-lg bg-destructive px-4 py-2 text-sm font-bold text-destructive-foreground shadow-sm transition-colors hover:bg-destructive/90 disabled:opacity-50",
2838
+ children: isDeleting ? "Deleting..." : "Delete"
2839
+ }
2840
+ )
2841
+ ] })
2842
+ ] }) }),
2843
+ /* @__PURE__ */ jsxs7("div", { className: "overflow-hidden rounded-lg border border-border bg-card shadow-[var(--shadow-card)]", children: [
2844
+ /* @__PURE__ */ jsxs7("div", { className: "border-b border-border px-6 py-4 flex items-center justify-between", children: [
2845
+ /* @__PURE__ */ jsx7("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsx7("button", { className: "text-xs font-bold uppercase tracking-widest text-foreground", children: "All Scripts" }) }),
2846
+ /* @__PURE__ */ jsxs7("span", { className: "text-xs text-muted-foreground font-mono", children: [
2847
+ scripts.length,
2848
+ " script",
2849
+ scripts.length !== 1 ? "s" : ""
2850
+ ] })
2851
+ ] }),
2852
+ loading ? /* @__PURE__ */ jsx7("div", { className: "flex items-center justify-center py-16", children: /* @__PURE__ */ jsx7("div", { className: "h-6 w-6 animate-spin rounded-full border-2 border-primary border-t-transparent" }) }) : scripts.length === 0 ? /* @__PURE__ */ jsxs7("div", { className: "flex flex-col items-center justify-center py-16 text-center", children: [
2853
+ /* @__PURE__ */ jsx7(Terminal, { className: "h-10 w-10 text-muted-foreground mb-4" }),
2854
+ /* @__PURE__ */ jsx7("h3", { className: "text-lg font-semibold text-foreground", children: "No startup scripts yet" }),
2855
+ /* @__PURE__ */ jsx7("p", { className: "mt-1 text-sm text-muted-foreground max-w-sm", children: "Create a script to run automatically when your sandboxes start." }),
2856
+ /* @__PURE__ */ jsxs7(
2857
+ "button",
2858
+ {
2859
+ type: "button",
2860
+ onClick: openCreate,
2861
+ 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
+ children: [
2863
+ /* @__PURE__ */ jsx7(Plus4, { className: "h-4 w-4" }),
2864
+ "Create Script"
2865
+ ]
2866
+ }
2867
+ )
2868
+ ] }) : /* @__PURE__ */ jsx7("div", { className: "divide-y divide-border", children: scripts.map((script) => /* @__PURE__ */ jsxs7(
2869
+ "div",
2870
+ {
2871
+ className: cn(
2872
+ "group flex items-center gap-4 px-6 py-4 transition-colors hover:bg-muted/30",
2873
+ !script.enabled && "opacity-60"
2874
+ ),
2875
+ children: [
2876
+ /* @__PURE__ */ jsx7(
2877
+ "button",
2878
+ {
2879
+ type: "button",
2880
+ onClick: () => handleToggle(script),
2881
+ title: script.enabled ? "Disable" : "Enable",
2882
+ className: cn(
2883
+ "flex h-8 w-8 shrink-0 items-center justify-center rounded-full transition-colors",
2884
+ script.enabled ? "bg-[var(--surface-success-bg,hsl(142 76% 90%))] text-[var(--surface-success-text,hsl(142 76% 36%))]" : "bg-muted text-muted-foreground"
2885
+ ),
2886
+ children: script.enabled ? /* @__PURE__ */ jsx7(Power, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx7(PowerOff, { className: "h-4 w-4" })
2887
+ }
2888
+ ),
2889
+ /* @__PURE__ */ jsxs7("div", { className: "flex-1 min-w-0", children: [
2890
+ /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2", children: [
2891
+ /* @__PURE__ */ jsx7("h4", { className: "text-sm font-bold text-foreground truncate", children: script.name }),
2892
+ /* @__PURE__ */ jsx7("span", { className: "text-[10px] font-mono text-muted-foreground bg-muted rounded px-1.5 py-0.5", children: SCRIPT_TYPE_META[script.scriptType ?? "bash"].label }),
2893
+ script.runOrder !== 100 && /* @__PURE__ */ jsxs7("span", { className: "text-[10px] font-mono text-muted-foreground bg-muted rounded px-1.5 py-0.5", children: [
2894
+ "#",
2895
+ script.runOrder
2896
+ ] })
2897
+ ] }),
2898
+ script.description && /* @__PURE__ */ jsx7("p", { className: "mt-0.5 text-xs text-muted-foreground truncate", children: script.description }),
2899
+ /* @__PURE__ */ jsxs7("div", { className: "mt-1.5 flex flex-wrap gap-1.5", children: [
2900
+ script.environments.map((env) => /* @__PURE__ */ jsx7("span", { className: "rounded-full bg-primary/10 px-2 py-0.5 text-[10px] font-medium text-primary", children: env }, env)),
2901
+ script.injectSecrets.map((s) => /* @__PURE__ */ jsxs7("span", { className: "rounded-full bg-muted px-2 py-0.5 text-[10px] font-medium text-muted-foreground flex items-center gap-0.5", children: [
2902
+ /* @__PURE__ */ jsx7(Lock2, { className: "h-2.5 w-2.5" }),
2903
+ s
2904
+ ] }, s)),
2905
+ script.continueOnFailure && /* @__PURE__ */ jsx7("span", { className: "rounded-full bg-amber-500/10 px-2 py-0.5 text-[10px] font-medium text-amber-600", children: "soft fail" }),
2906
+ script.runAsRoot && /* @__PURE__ */ jsx7("span", { className: "rounded-full bg-destructive/10 px-2 py-0.5 text-[10px] font-medium text-destructive", children: "root" })
2907
+ ] })
2908
+ ] }),
2909
+ /* @__PURE__ */ jsxs7("div", { className: "hidden md:flex items-center gap-1 text-xs text-muted-foreground", children: [
2910
+ /* @__PURE__ */ jsx7(Clock, { className: "h-3.5 w-3.5" }),
2911
+ script.timeoutSeconds,
2912
+ "s"
2913
+ ] }),
2914
+ /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-1 sm:opacity-0 sm:group-hover:opacity-100 sm:focus-within:opacity-100 transition-opacity", children: [
2915
+ /* @__PURE__ */ jsx7(
2916
+ "button",
2917
+ {
2918
+ type: "button",
2919
+ onClick: () => openEdit(script),
2920
+ className: "rounded-md p-2 text-muted-foreground hover:text-foreground hover:bg-muted transition-colors",
2921
+ "aria-label": `Edit ${script.name}`,
2922
+ children: /* @__PURE__ */ jsx7(Pencil, { className: "h-4 w-4" })
2923
+ }
2924
+ ),
2925
+ /* @__PURE__ */ jsx7(
2926
+ "button",
2927
+ {
2928
+ type: "button",
2929
+ onClick: () => setDeleteTarget(script),
2930
+ className: "rounded-md p-2 text-muted-foreground hover:text-destructive hover:bg-destructive/10 transition-colors",
2931
+ "aria-label": `Delete ${script.name}`,
2932
+ children: /* @__PURE__ */ jsx7(Trash24, { className: "h-4 w-4" })
2933
+ }
2934
+ )
2935
+ ] })
2936
+ ]
2937
+ },
2938
+ script.id
2939
+ )) })
2940
+ ] }),
2941
+ /* @__PURE__ */ jsxs7("div", { className: "grid grid-cols-1 gap-6 md:grid-cols-2", children: [
2942
+ /* @__PURE__ */ jsxs7("div", { className: "rounded-lg border border-border bg-card p-6 shadow-[var(--shadow-card)]", children: [
2943
+ /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-3 mb-3", children: [
2944
+ /* @__PURE__ */ jsx7("div", { className: "flex h-9 w-9 items-center justify-center rounded-md bg-[var(--brand-primary,hsl(var(--primary)))] text-[var(--btn-primary-text)]", children: /* @__PURE__ */ jsx7(Play, { className: "h-5 w-5" }) }),
2945
+ /* @__PURE__ */ jsx7("h3", { className: "text-sm font-bold text-foreground", children: "How Scripts Run" })
2946
+ ] }),
2947
+ /* @__PURE__ */ jsx7("p", { className: "text-sm text-muted-foreground", children: 'Scripts execute in order after the container starts but before the AI agent. They run as bash scripts with full access to mounted tools (Nix profile) and workspace. Failed scripts abort sandbox creation unless "continue on failure" is enabled.' })
2948
+ ] }),
2949
+ /* @__PURE__ */ jsxs7("div", { className: "rounded-lg border border-border bg-card p-6 shadow-[var(--shadow-card)]", children: [
2950
+ /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-3 mb-3", children: [
2951
+ /* @__PURE__ */ jsx7("div", { className: "flex h-9 w-9 items-center justify-center rounded-md bg-[var(--brand-primary,hsl(var(--primary)))] text-[var(--btn-primary-text)]", children: /* @__PURE__ */ jsx7(Shield2, { className: "h-5 w-5" }) }),
2952
+ /* @__PURE__ */ jsx7("h3", { className: "text-sm font-bold text-foreground", children: "Security & Secrets" })
2953
+ ] }),
2954
+ /* @__PURE__ */ jsx7("p", { className: "text-sm text-muted-foreground", children: "Selected secrets are injected as environment variables at execution time. Secret values are never stored in the script itself \u2014 they are decrypted and injected only when the sandbox starts. Scripts can use conditions to restrict execution to specific environments or resource tiers." })
2955
+ ] })
2956
+ ] })
2957
+ ] });
2958
+ }
2959
+
1863
2960
  // src/lib/template-presets.ts
1864
2961
  var BLOCKCHAIN_PATTERNS = [
1865
2962
  "ethereum",
@@ -2017,6 +3114,7 @@ export {
2017
3114
  ProvisioningWizard,
2018
3115
  SecretsPage,
2019
3116
  StandalonePricingPage,
3117
+ StartupScriptsPage,
2020
3118
  TemplatesPage,
2021
3119
  getPresetForTemplate,
2022
3120
  resolveEnvironment