@aiworkbench/vibe-cli 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2789,6 +2789,9 @@ function isKebabCase(name) {
2789
2789
  function toKebabCase(name) {
2790
2790
  return name.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").replace(/[^a-z0-9-]/gi, "").toLowerCase().replace(/-+/g, "-").replace(/^-|-$/g, "");
2791
2791
  }
2792
+ function isValidCustomElementName(name) {
2793
+ return /^[a-z][a-z0-9]*-[a-z0-9-]*$/.test(name);
2794
+ }
2792
2795
  function toPascalCase(kebab) {
2793
2796
  return kebab.split("-").map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join("");
2794
2797
  }
@@ -2806,6 +2809,7 @@ function buildTemplateContext(projectName, framework) {
2806
2809
  var __dirname2 = dirname2(fileURLToPath(import.meta.url));
2807
2810
  async function templateDir(framework) {
2808
2811
  const candidates = [
2812
+ resolve(__dirname2, "..", "templates", framework),
2809
2813
  resolve(__dirname2, "..", "..", "..", "..", "templates", framework),
2810
2814
  resolve(__dirname2, "..", "..", "..", "templates", framework)
2811
2815
  ];
@@ -6300,6 +6304,793 @@ this._shadow.appendChild(img);
6300
6304
  }
6301
6305
  }
6302
6306
 
6307
+ // src/skills/host-design-system.ts
6308
+ function generateHostDesignSystem(config) {
6309
+ const { framework } = config;
6310
+ const stylingApproach = getStylingApproach(framework);
6311
+ return `# Host Design System — USECASE-EDITOR
6312
+
6313
+ Your mini-app renders inside the host's content area via \`<VibeHost>\`. The host provides
6314
+ the header, sidebar, and scroll container. Your job is to fill the content region with UI
6315
+ that looks native to the platform. This guide contains every design token and pattern you
6316
+ need.
6317
+
6318
+ ## Important: Shadow DOM Isolation
6319
+
6320
+ Your mini-app lives inside a Shadow DOM boundary. This means:
6321
+ - Host Tailwind classes are **NOT available** inside your component
6322
+ - CSS custom properties (variables) **DO inherit** through Shadow DOM
6323
+ - You must write your own CSS using the values below, not Tailwind utility classes
6324
+ - Use \`adoptedStyleSheets\` or \`?inline\` CSS imports to inject styles into the shadow root
6325
+
6326
+ ## Color System (OKLCH)
6327
+
6328
+ The host uses oklch color space exclusively. Use these hardcoded values in your CSS.
6329
+ CSS custom properties like \`var(--foreground)\` will inherit from the host through the
6330
+ shadow boundary, but having the literal values ensures correct rendering in dev sandbox.
6331
+
6332
+ ### Light Mode
6333
+
6334
+ | Token | Value | Usage |
6335
+ |-------|-------|-------|
6336
+ | background | \`oklch(1 0 0)\` | Page background |
6337
+ | foreground | \`oklch(0.145 0 0)\` | Primary text |
6338
+ | card | \`oklch(1 0 0)\` | Card backgrounds |
6339
+ | card-foreground | \`oklch(0.145 0 0)\` | Card text |
6340
+ | primary | \`oklch(0.205 0 0)\` | Buttons, emphasis |
6341
+ | primary-foreground | \`oklch(0.985 0 0)\` | Text on primary |
6342
+ | secondary | \`oklch(0.97 0 0)\` | Secondary backgrounds |
6343
+ | secondary-foreground | \`oklch(0.205 0 0)\` | Text on secondary |
6344
+ | tertiary | \`oklch(0.8544 0.1746 90.88)\` | Accent highlight (yellow-green) |
6345
+ | tertiary-foreground | \`oklch(0.145 0 0)\` | Text on tertiary |
6346
+ | muted | \`oklch(0.97 0 0)\` | Subdued backgrounds |
6347
+ | muted-foreground | \`oklch(0.556 0 0)\` | Subdued text, descriptions |
6348
+ | destructive | \`oklch(0.577 0.245 27.325)\` | Error, danger |
6349
+ | border | \`oklch(0.922 0 0)\` | Borders, dividers |
6350
+ | input | \`oklch(0.922 0 0)\` | Input borders |
6351
+ | ring | \`oklch(0.708 0 0)\` | Focus rings |
6352
+
6353
+ ### Dark Mode
6354
+
6355
+ | Token | Value | Usage |
6356
+ |-------|-------|-------|
6357
+ | background | \`oklch(0.145 0 0)\` | Page background |
6358
+ | foreground | \`oklch(0.985 0 0)\` | Primary text |
6359
+ | card | \`oklch(0.205 0 0)\` | Card backgrounds |
6360
+ | card-foreground | \`oklch(0.985 0 0)\` | Card text |
6361
+ | primary | \`oklch(0.922 0 0)\` | Buttons, emphasis |
6362
+ | primary-foreground | \`oklch(0.205 0 0)\` | Text on primary |
6363
+ | secondary | \`oklch(0.269 0 0)\` | Secondary backgrounds |
6364
+ | secondary-foreground | \`oklch(0.985 0 0)\` | Text on secondary |
6365
+ | tertiary | \`oklch(0.8544 0.1746 90.88)\` | Same accent (unchanged) |
6366
+ | tertiary-foreground | \`oklch(0.145 0 0)\` | Text on tertiary |
6367
+ | muted | \`oklch(0.269 0 0)\` | Subdued backgrounds |
6368
+ | muted-foreground | \`oklch(0.708 0 0)\` | Subdued text |
6369
+ | destructive | \`oklch(0.704 0.191 22.216)\` | Error (lighter in dark) |
6370
+ | border | \`oklch(1 0 0 / 10%)\` | Borders (semi-transparent) |
6371
+ | input | \`oklch(1 0 0 / 15%)\` | Input borders |
6372
+ | ring | \`oklch(0.556 0 0)\` | Focus rings |
6373
+
6374
+ ### Using Host CSS Variables
6375
+
6376
+ CSS custom properties inherit through Shadow DOM. Prefer variables when possible:
6377
+
6378
+ \`\`\`css
6379
+ .my-card {
6380
+ background: var(--card, oklch(1 0 0));
6381
+ color: var(--card-foreground, oklch(0.145 0 0));
6382
+ border: 1px solid var(--border, oklch(0.922 0 0));
6383
+ }
6384
+ \`\`\`
6385
+
6386
+ The fallback values ensure correct rendering in the dev sandbox where host variables
6387
+ are not present.
6388
+
6389
+ ## Typography
6390
+
6391
+ ### Fonts
6392
+
6393
+ The host loads **Geist** (sans) and **Geist Mono** via Google Fonts, exposed as CSS variables:
6394
+
6395
+ \`\`\`css
6396
+ font-family: var(--font-geist-sans, system-ui, -apple-system, sans-serif);
6397
+ font-family: var(--font-geist-mono, ui-monospace, monospace);
6398
+ \`\`\`
6399
+
6400
+ These variables inherit through Shadow DOM. Always include system font fallbacks.
6401
+
6402
+ ### Type Scale
6403
+
6404
+ | Element | Size | Weight | Extra |
6405
+ |---------|------|--------|-------|
6406
+ | Page heading | \`1.125rem\` (text-lg) | \`700\` (bold) | — |
6407
+ | Card title | \`1.125rem\` (text-lg) | \`600\` (semibold) | — |
6408
+ | Section label | \`0.6875rem\` | \`600\` (semibold) | \`text-transform: uppercase; letter-spacing: 0.1em\` |
6409
+ | Body text | \`0.875rem\` (text-sm) | \`400\` (regular) | — |
6410
+ | Description | \`0.875rem\` (text-sm) | \`400\` | Color: muted-foreground |
6411
+ | Small label | \`0.75rem\` (text-xs) | \`500\` (medium) | — |
6412
+ | Badge / pill | \`0.625rem\` (10px) | \`600\` | — |
6413
+
6414
+ ### Heading Pattern (Section Labels)
6415
+
6416
+ The host uses uppercase, letter-spaced, muted headings to label sections:
6417
+
6418
+ \`\`\`css
6419
+ .section-label {
6420
+ font-size: 0.6875rem;
6421
+ font-weight: 600;
6422
+ text-transform: uppercase;
6423
+ letter-spacing: 0.1em;
6424
+ color: oklch(0.556 0 0); /* muted-foreground */
6425
+ }
6426
+ \`\`\`
6427
+
6428
+ ## Border Radius
6429
+
6430
+ | Token | Value |
6431
+ |-------|-------|
6432
+ | radius-sm | \`4px\` |
6433
+ | radius-md | \`6px\` |
6434
+ | radius-lg | \`8px\` (base) |
6435
+ | radius-xl | \`12px\` |
6436
+
6437
+ - **Cards**: \`border-radius: 12px\` (rounded-xl) for marketplace-style cards, \`8px\` (rounded-lg) for standard cards
6438
+ - **Buttons**: \`6px\` (rounded-md)
6439
+ - **Badges**: \`6px\` (rounded-md)
6440
+ - **Inputs**: \`6px\` (rounded-md)
6441
+
6442
+ ## Card Patterns
6443
+
6444
+ ### Standard Card
6445
+
6446
+ \`\`\`css
6447
+ .card {
6448
+ background: oklch(1 0 0);
6449
+ border: 1px solid oklch(0.922 0 0);
6450
+ border-radius: 8px;
6451
+ padding: 1.5rem;
6452
+ box-shadow: 0 1px 2px oklch(0.145 0 0 / 0.04);
6453
+ }
6454
+ \`\`\`
6455
+
6456
+ ### Marketplace Agent Card (Interactive)
6457
+
6458
+ \`\`\`css
6459
+ .agent-card {
6460
+ display: flex;
6461
+ flex-direction: column;
6462
+ background: oklch(1 0 0);
6463
+ border: 1px solid oklch(0.922 0 0);
6464
+ border-radius: 12px;
6465
+ padding: 1rem;
6466
+ cursor: pointer;
6467
+ transition: box-shadow 180ms ease;
6468
+ overflow: hidden;
6469
+ }
6470
+
6471
+ .agent-card:hover {
6472
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
6473
+ }
6474
+ \`\`\`
6475
+
6476
+ ### Builder Card
6477
+
6478
+ \`\`\`css
6479
+ .builder-card {
6480
+ padding: 1rem;
6481
+ border-radius: 8px;
6482
+ border: 1px solid oklch(0.75 0 0); /* gray-300 equivalent */
6483
+ cursor: pointer;
6484
+ transition: background-color 180ms ease;
6485
+ }
6486
+
6487
+ .builder-card:hover {
6488
+ background: oklch(0.97 0 0); /* gray-50 equivalent */
6489
+ }
6490
+ \`\`\`
6491
+
6492
+ ### Accent-Highlighted Card
6493
+
6494
+ Use the tertiary color for feature emphasis:
6495
+
6496
+ \`\`\`css
6497
+ .card--accent {
6498
+ background: linear-gradient(
6499
+ 135deg,
6500
+ oklch(0.8544 0.1746 90.88 / 0.06),
6501
+ oklch(0.8544 0.1746 90.88 / 0.02)
6502
+ );
6503
+ border-color: oklch(0.8544 0.1746 90.88 / 0.3);
6504
+ }
6505
+ \`\`\`
6506
+
6507
+ ## Button Patterns
6508
+
6509
+ ### Primary Button
6510
+
6511
+ \`\`\`css
6512
+ .btn-primary {
6513
+ display: inline-flex;
6514
+ align-items: center;
6515
+ justify-content: center;
6516
+ gap: 0.5rem;
6517
+ height: 2.25rem;
6518
+ padding: 0 1rem;
6519
+ border-radius: 6px;
6520
+ font-size: 0.875rem;
6521
+ font-weight: 500;
6522
+ background: oklch(0.205 0 0);
6523
+ color: oklch(0.985 0 0);
6524
+ border: none;
6525
+ cursor: pointer;
6526
+ transition: opacity 150ms ease;
6527
+ }
6528
+
6529
+ .btn-primary:hover {
6530
+ opacity: 0.9;
6531
+ }
6532
+ \`\`\`
6533
+
6534
+ ### Button Sizes
6535
+
6536
+ | Size | Height | Padding | Font |
6537
+ |------|--------|---------|------|
6538
+ | xs | \`1.5rem\` | \`0 0.75rem\` | \`0.75rem\` |
6539
+ | sm | \`2rem\` | \`0 0.75rem\` | \`0.875rem\` |
6540
+ | default | \`2.25rem\` | \`0 1rem\` | \`0.875rem\` |
6541
+ | lg | \`2.5rem\` | \`0 1.5rem\` | \`0.875rem\` |
6542
+
6543
+ ### Button Variants
6544
+
6545
+ | Variant | Background | Text | Border |
6546
+ |---------|-----------|------|--------|
6547
+ | default | \`oklch(0.205 0 0)\` | \`oklch(0.985 0 0)\` | none |
6548
+ | outline | \`oklch(1 0 0)\` | \`oklch(0.205 0 0)\` | \`1px solid oklch(0.922 0 0)\` |
6549
+ | secondary | \`oklch(0.97 0 0)\` | \`oklch(0.205 0 0)\` | none |
6550
+ | ghost | \`transparent\` | inherit | none (hover: \`oklch(0.97 0 0)\`) |
6551
+ | destructive | \`oklch(0.577 0.245 27.325)\` | \`white\` | none |
6552
+ | highlight | \`oklch(0.85 0.18 95)\` | \`black\` | none |
6553
+
6554
+ ### Disabled State
6555
+
6556
+ \`\`\`css
6557
+ .btn:disabled {
6558
+ pointer-events: none;
6559
+ background: oklch(0.9 0 0);
6560
+ color: oklch(0.556 0 0);
6561
+ }
6562
+ \`\`\`
6563
+
6564
+ ## Input Patterns
6565
+
6566
+ \`\`\`css
6567
+ .input {
6568
+ height: 2.25rem;
6569
+ width: 100%;
6570
+ border-radius: 6px;
6571
+ border: 1px solid oklch(0.922 0 0);
6572
+ background: transparent;
6573
+ padding: 0.25rem 0.75rem;
6574
+ font-size: 0.875rem;
6575
+ transition: box-shadow 150ms ease, border-color 150ms ease;
6576
+ }
6577
+
6578
+ .input::placeholder {
6579
+ color: oklch(0.556 0 0);
6580
+ }
6581
+
6582
+ .input:focus-visible {
6583
+ border-color: oklch(0.708 0 0);
6584
+ outline: none;
6585
+ box-shadow: 0 0 0 3px oklch(0.708 0 0 / 0.5);
6586
+ }
6587
+ \`\`\`
6588
+
6589
+ ## Badge Patterns
6590
+
6591
+ \`\`\`css
6592
+ .badge {
6593
+ display: inline-flex;
6594
+ align-items: center;
6595
+ border-radius: 6px;
6596
+ padding: 0.125rem 0.625rem;
6597
+ font-size: 0.75rem;
6598
+ font-weight: 600;
6599
+ border: 1px solid transparent;
6600
+ }
6601
+
6602
+ .badge--default {
6603
+ background: oklch(0.205 0 0);
6604
+ color: oklch(0.985 0 0);
6605
+ }
6606
+
6607
+ .badge--secondary {
6608
+ background: oklch(0.97 0 0);
6609
+ color: oklch(0.205 0 0);
6610
+ }
6611
+
6612
+ .badge--outline {
6613
+ background: transparent;
6614
+ border-color: oklch(0.922 0 0);
6615
+ color: oklch(0.145 0 0);
6616
+ }
6617
+ \`\`\`
6618
+
6619
+ ### Version Badge (Small)
6620
+
6621
+ Used on marketplace cards for version labels:
6622
+
6623
+ \`\`\`css
6624
+ .version-badge {
6625
+ font-size: 0.625rem;
6626
+ line-height: 1;
6627
+ padding: 0.125rem 0.375rem;
6628
+ border-radius: 4px;
6629
+ background: oklch(0.97 0 0);
6630
+ color: oklch(0.45 0 0);
6631
+ border: 1px solid oklch(0.922 0 0);
6632
+ }
6633
+ \`\`\`
6634
+
6635
+ ## Layout
6636
+
6637
+ ### Content Container
6638
+
6639
+ The host's content area uses responsive horizontal padding and a max-width:
6640
+
6641
+ \`\`\`css
6642
+ .container {
6643
+ margin: 0 auto;
6644
+ width: 100%;
6645
+ padding-left: 1rem;
6646
+ padding-right: 1rem;
6647
+ }
6648
+
6649
+ @media (min-width: 1024px) {
6650
+ .container { padding-left: 1.5rem; padding-right: 1.5rem; }
6651
+ }
6652
+
6653
+ @media (min-width: 1280px) {
6654
+ .container { padding-left: 2rem; padding-right: 2rem; }
6655
+ }
6656
+ \`\`\`
6657
+
6658
+ | Container Size | Max Width |
6659
+ |---------------|-----------|
6660
+ | sm | \`768px\` |
6661
+ | md | \`1440px\` |
6662
+ | lg | \`1600px\` |
6663
+ | xl | none (full width) |
6664
+
6665
+ ### Spacing Scale
6666
+
6667
+ | Name | Value | Common Usage |
6668
+ |------|-------|------|
6669
+ | xs | \`0.25rem\` (4px) | Tight gaps |
6670
+ | sm | \`0.5rem\` (8px) | Icon gaps, badge padding |
6671
+ | md | \`0.75rem\` (12px) | Grid gaps, small card gaps |
6672
+ | base | \`1rem\` (16px) | Card padding, component spacing |
6673
+ | lg | \`1.5rem\` (24px) | Card content padding, section gaps |
6674
+ | xl | \`2rem\` (32px) | Page padding, section margin |
6675
+ | 2xl | \`3rem\` (48px) | Large vertical sections |
6676
+
6677
+ ### Common Layouts
6678
+
6679
+ **Card Grid (3 columns)**
6680
+ \`\`\`css
6681
+ .card-grid {
6682
+ display: flex;
6683
+ flex-wrap: wrap;
6684
+ gap: 1.5rem;
6685
+ }
6686
+
6687
+ .card-grid > * {
6688
+ flex: 0 0 calc(33.333% - 1rem);
6689
+ }
6690
+ \`\`\`
6691
+
6692
+ **Sidebar + Content**
6693
+ \`\`\`css
6694
+ .layout-with-sidebar {
6695
+ display: flex;
6696
+ gap: 2rem;
6697
+ }
6698
+
6699
+ .sidebar {
6700
+ width: 200px;
6701
+ flex-shrink: 0;
6702
+ }
6703
+
6704
+ .content {
6705
+ flex: 1;
6706
+ min-width: 0;
6707
+ }
6708
+ \`\`\`
6709
+
6710
+ ## Shadows
6711
+
6712
+ | Level | Value | Usage |
6713
+ |-------|-------|-------|
6714
+ | xs | \`0 1px 2px oklch(0.145 0 0 / 0.04)\` | Buttons, inputs |
6715
+ | sm | \`0 1px 3px oklch(0.145 0 0 / 0.06)\` | Static cards |
6716
+ | card-hover | \`0 2px 6px rgba(0, 0, 0, 0.15)\` | Interactive card hover |
6717
+ | lg | \`0 10px 15px oklch(0.145 0 0 / 0.1)\` | Dialogs, popovers |
6718
+
6719
+ ## Focus States
6720
+
6721
+ All interactive elements must show a visible focus ring for accessibility:
6722
+
6723
+ \`\`\`css
6724
+ .interactive:focus-visible {
6725
+ border-color: oklch(0.708 0 0);
6726
+ outline: none;
6727
+ box-shadow: 0 0 0 3px oklch(0.708 0 0 / 0.5);
6728
+ }
6729
+ \`\`\`
6730
+
6731
+ ## Transitions
6732
+
6733
+ Consistent timing across all interactive elements:
6734
+
6735
+ \`\`\`css
6736
+ transition: 180ms ease; /* default for hover/shadow/transform */
6737
+ transition: 150ms ease; /* faster for opacity/color */
6738
+ \`\`\`
6739
+
6740
+ ## Navigation Routes
6741
+
6742
+ The host application has these routes. Use \`bridge.navigation.navigate(path)\` to link:
6743
+
6744
+ | Route | Page |
6745
+ |-------|------|
6746
+ | \`/chat\` | Chat with agents |
6747
+ | \`/marketplace\` | Marketplace landing |
6748
+ | \`/marketplace/agents?state=PUBLISHED\` | Published agents |
6749
+ | \`/playground\` | Prompt playground |
6750
+ | \`/builder\` | Agent builder |
6751
+ | \`/documents\` | Document library |
6752
+ | \`/administration\` | Admin panel (admins only) |
6753
+
6754
+ ## Dark Mode Support
6755
+
6756
+ Dark mode is toggled via the \`.dark\` class on the \`<html>\` element (class-based, not media query).
6757
+ Use \`bridge.theme.current()\` to detect and \`bridge.events.on("theme-changed", ...)\` to react.
6758
+
6759
+ For CSS-only dark mode in your shadow DOM, use the host's CSS variables with fallbacks:
6760
+
6761
+ \`\`\`css
6762
+ .card {
6763
+ background: var(--card, oklch(1 0 0));
6764
+ color: var(--card-foreground, oklch(0.145 0 0));
6765
+ border-color: var(--border, oklch(0.922 0 0));
6766
+ }
6767
+ \`\`\`
6768
+
6769
+ When the host toggles dark mode, its CSS variables change automatically and your component
6770
+ inherits the new values through the Shadow DOM. No JavaScript needed if you use variables.
6771
+
6772
+ For bridge-driven dark mode with separate value sets:
6773
+
6774
+ ${getDarkModeExample(framework)}
6775
+
6776
+ ## Icons
6777
+
6778
+ The host uses **Lucide React** icons. Mini-apps should NOT depend on Lucide — it adds
6779
+ unnecessary bundle size. Instead:
6780
+
6781
+ 1. **Inline SVG** (preferred) — Copy the SVG paths from [lucide.dev](https://lucide.dev).
6782
+ Use \`viewBox="0 0 24 24"\`, \`fill="none"\`, \`stroke="currentColor"\`, \`stroke-width="2"\`.
6783
+ 2. **Single icon import** — If you must use Lucide, import individual icons:
6784
+ \`import { Search } from "lucide-react"\` (NOT the barrel \`lucide-react\` import).
6785
+
6786
+ ## Anti-Patterns
6787
+
6788
+ These break visual consistency with the host:
6789
+
6790
+ - **Don't use oklch hue for primary UI.** The host is grayscale-first. Color is reserved for the
6791
+ tertiary accent (\`oklch(0.8544 0.1746 90.88)\`), destructive actions, and chart data.
6792
+ - **Don't use colored backgrounds for sections.** The host uses white/near-white backgrounds with
6793
+ subtle borders to separate content. Colored section backgrounds look foreign.
6794
+ - **Don't use rounded-full on cards.** Cards use \`8px\` or \`12px\` radius, never pill shapes.
6795
+ - **Don't add drop shadows to static elements.** The host uses flat design with \`shadow-sm\` at most.
6796
+ Shadows are interactive cues (hover state), not decoration.
6797
+ - **Don't use custom fonts.** Stick to the Geist font family inherited from the host.
6798
+ - **Don't use thick borders.** The host uses \`1px\` borders exclusively.
6799
+ - **Don't use bright/saturated colors for UI chrome.** The grayscale palette is intentional.
6800
+
6801
+ ${stylingApproach}
6802
+
6803
+ ## Complete Example: Minimal Card Component
6804
+
6805
+ ${getCardExample(framework)}
6806
+ `;
6807
+ }
6808
+ function getStylingApproach(framework) {
6809
+ switch (framework) {
6810
+ case "react":
6811
+ return `## Styling Approach (React)
6812
+
6813
+ Use the \`?inline\` import pattern with \`adoptedStyleSheets\`:
6814
+
6815
+ \`\`\`tsx
6816
+ // src/main.tsx
6817
+ import styles from "./styles.css?inline";
6818
+
6819
+ connectedCallback() {
6820
+ const shadow = this.attachShadow({ mode: "open" });
6821
+ const sheet = new CSSStyleSheet();
6822
+ sheet.replaceSync(styles);
6823
+ shadow.adoptedStyleSheets = [sheet];
6824
+ // ... create container and React root
6825
+ }
6826
+ \`\`\`
6827
+
6828
+ Add a TypeScript declaration for the \`?inline\` import:
6829
+
6830
+ \`\`\`ts
6831
+ // src/vite-env.d.ts
6832
+ declare module "*.css?inline" {
6833
+ const css: string;
6834
+ export default css;
6835
+ }
6836
+ \`\`\`
6837
+
6838
+ Then write plain CSS in \`src/styles.css\` using the token values from this guide.`;
6839
+ case "vue":
6840
+ return `## Styling Approach (Vue)
6841
+
6842
+ Vue SFC \`<style>\` blocks are automatically injected into the shadow DOM when using
6843
+ \`defineCustomElement\`:
6844
+
6845
+ \`\`\`vue
6846
+ <style scoped>
6847
+ .card {
6848
+ background: var(--card, oklch(1 0 0));
6849
+ border: 1px solid var(--border, oklch(0.922 0 0));
6850
+ border-radius: 8px;
6851
+ padding: 1.5rem;
6852
+ }
6853
+ </style>
6854
+ \`\`\`
6855
+
6856
+ Use the oklch token values from this guide in your scoped styles.`;
6857
+ case "vanilla":
6858
+ return `## Styling Approach (Vanilla)
6859
+
6860
+ Use adopted stylesheets for best performance:
6861
+
6862
+ \`\`\`ts
6863
+ const sheet = new CSSStyleSheet();
6864
+ sheet.replaceSync(\`
6865
+ .card {
6866
+ background: oklch(1 0 0);
6867
+ border: 1px solid oklch(0.922 0 0);
6868
+ border-radius: 8px;
6869
+ padding: 1.5rem;
6870
+ }
6871
+ \`);
6872
+ this._shadow.adoptedStyleSheets = [sheet];
6873
+ \`\`\`
6874
+
6875
+ Or use a separate CSS file with Vite's \`?inline\` import.`;
6876
+ default:
6877
+ return "";
6878
+ }
6879
+ }
6880
+ function getDarkModeExample(framework) {
6881
+ switch (framework) {
6882
+ case "react":
6883
+ return `\`\`\`tsx
6884
+ function App({ bridge }: VibeProps) {
6885
+ const [theme, setTheme] = useState(bridge.theme?.current() ?? "light");
6886
+
6887
+ useEffect(() => {
6888
+ const unsub = bridge.events?.on("theme-changed", (payload) => {
6889
+ setTheme((payload as { theme: string }).theme);
6890
+ });
6891
+ return unsub;
6892
+ }, [bridge]);
6893
+
6894
+ const isDark = theme === "dark";
6895
+ // Use isDark to toggle class names or inline styles
6896
+ }
6897
+ \`\`\``;
6898
+ case "vue":
6899
+ return `\`\`\`vue
6900
+ <script setup lang="ts">
6901
+ import { ref, onMounted, onUnmounted } from "vue";
6902
+ import type { Bridge } from "@aiworkbench/vibe-types";
6903
+
6904
+ const props = defineProps<{ bridge: Bridge }>();
6905
+ const theme = ref(props.bridge.theme?.current() ?? "light");
6906
+ let unsub: (() => void) | undefined;
6907
+
6908
+ onMounted(() => {
6909
+ unsub = props.bridge.events?.on("theme-changed", (payload) => {
6910
+ theme.value = (payload as { theme: string }).theme;
6911
+ });
6912
+ });
6913
+ onUnmounted(() => unsub?.());
6914
+ </script>
6915
+ \`\`\``;
6916
+ case "vanilla":
6917
+ return `\`\`\`ts
6918
+ set bridge(b: Bridge) {
6919
+ this._bridge = b;
6920
+ this._theme = b.theme?.current() ?? "light";
6921
+
6922
+ this._cleanups.push(
6923
+ b.events?.on("theme-changed", (payload) => {
6924
+ this._theme = (payload as { theme: string }).theme;
6925
+ this.updateTheme();
6926
+ }) ?? (() => {})
6927
+ );
6928
+
6929
+ this.render();
6930
+ }
6931
+ \`\`\``;
6932
+ default:
6933
+ return "";
6934
+ }
6935
+ }
6936
+ function getCardExample(framework) {
6937
+ switch (framework) {
6938
+ case "react":
6939
+ return `\`\`\`tsx
6940
+ interface CardProps {
6941
+ title: string;
6942
+ description: string;
6943
+ onClick?: () => void;
6944
+ }
6945
+
6946
+ function Card({ title, description, onClick }: CardProps) {
6947
+ return (
6948
+ <div
6949
+ className="card"
6950
+ role={onClick ? "button" : undefined}
6951
+ tabIndex={onClick ? 0 : undefined}
6952
+ onClick={onClick}
6953
+ onKeyDown={(e) => {
6954
+ if (onClick && (e.key === "Enter" || e.key === " ")) {
6955
+ e.preventDefault();
6956
+ onClick();
6957
+ }
6958
+ }}
6959
+ >
6960
+ <div className="card-title">{title}</div>
6961
+ <div className="card-desc">{description}</div>
6962
+ </div>
6963
+ );
6964
+ }
6965
+ \`\`\`
6966
+
6967
+ \`\`\`css
6968
+ /* styles.css */
6969
+ .card {
6970
+ background: var(--card, oklch(1 0 0));
6971
+ color: var(--card-foreground, oklch(0.145 0 0));
6972
+ border: 1px solid var(--border, oklch(0.922 0 0));
6973
+ border-radius: 12px;
6974
+ padding: 1rem;
6975
+ cursor: pointer;
6976
+ transition: box-shadow 180ms ease;
6977
+ user-select: none;
6978
+ }
6979
+
6980
+ .card:hover {
6981
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
6982
+ }
6983
+
6984
+ .card-title {
6985
+ font-size: 1.125rem;
6986
+ font-weight: 600;
6987
+ color: var(--foreground, oklch(0.205 0 0));
6988
+ }
6989
+
6990
+ .card-desc {
6991
+ margin-top: 0.5rem;
6992
+ font-size: 0.875rem;
6993
+ color: var(--muted-foreground, oklch(0.556 0 0));
6994
+ line-height: 1.5;
6995
+ }
6996
+ \`\`\``;
6997
+ case "vue":
6998
+ return `\`\`\`vue
6999
+ <template>
7000
+ <div
7001
+ class="card"
7002
+ :role="onClick ? 'button' : undefined"
7003
+ :tabindex="onClick ? 0 : undefined"
7004
+ @click="onClick?.()"
7005
+ @keydown.enter.space.prevent="onClick?.()"
7006
+ >
7007
+ <div class="card-title">{{ title }}</div>
7008
+ <div class="card-desc">{{ description }}</div>
7009
+ </div>
7010
+ </template>
7011
+
7012
+ <script setup lang="ts">
7013
+ defineProps<{
7014
+ title: string;
7015
+ description: string;
7016
+ onClick?: () => void;
7017
+ }>();
7018
+ </script>
7019
+
7020
+ <style scoped>
7021
+ .card {
7022
+ background: var(--card, oklch(1 0 0));
7023
+ color: var(--card-foreground, oklch(0.145 0 0));
7024
+ border: 1px solid var(--border, oklch(0.922 0 0));
7025
+ border-radius: 12px;
7026
+ padding: 1rem;
7027
+ cursor: pointer;
7028
+ transition: box-shadow 180ms ease;
7029
+ user-select: none;
7030
+ }
7031
+
7032
+ .card:hover {
7033
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
7034
+ }
7035
+
7036
+ .card-title {
7037
+ font-size: 1.125rem;
7038
+ font-weight: 600;
7039
+ }
7040
+
7041
+ .card-desc {
7042
+ margin-top: 0.5rem;
7043
+ font-size: 0.875rem;
7044
+ color: var(--muted-foreground, oklch(0.556 0 0));
7045
+ line-height: 1.5;
7046
+ }
7047
+ </style>
7048
+ \`\`\``;
7049
+ case "vanilla":
7050
+ return `\`\`\`ts
7051
+ private render() {
7052
+ const sheet = new CSSStyleSheet();
7053
+ sheet.replaceSync(\`
7054
+ .card {
7055
+ background: var(--card, oklch(1 0 0));
7056
+ color: var(--card-foreground, oklch(0.145 0 0));
7057
+ border: 1px solid var(--border, oklch(0.922 0 0));
7058
+ border-radius: 12px;
7059
+ padding: 1rem;
7060
+ cursor: pointer;
7061
+ transition: box-shadow 180ms ease;
7062
+ user-select: none;
7063
+ }
7064
+ .card:hover {
7065
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
7066
+ }
7067
+ .card-title {
7068
+ font-size: 1.125rem;
7069
+ font-weight: 600;
7070
+ }
7071
+ .card-desc {
7072
+ margin-top: 0.5rem;
7073
+ font-size: 0.875rem;
7074
+ color: var(--muted-foreground, oklch(0.556 0 0));
7075
+ line-height: 1.5;
7076
+ }
7077
+ \`);
7078
+ this._shadow.adoptedStyleSheets = [sheet];
7079
+
7080
+ const card = document.createElement("div");
7081
+ card.className = "card";
7082
+ card.innerHTML = \`
7083
+ <div class="card-title">\${this.escapeHtml(this.title)}</div>
7084
+ <div class="card-desc">\${this.escapeHtml(this.description)}</div>
7085
+ \`;
7086
+ this._shadow.appendChild(card);
7087
+ }
7088
+ \`\`\``;
7089
+ default:
7090
+ return "";
7091
+ }
7092
+ }
7093
+
6303
7094
  // src/scaffold/write-agent-docs.ts
6304
7095
  var PERMISSION_DOCS = {
6305
7096
  auth: `### auth
@@ -6530,7 +7321,8 @@ function generateEmbeddedSkills(config) {
6530
7321
  { name: "Performance", content: generatePerformance(config) },
6531
7322
  { name: "Debugging", content: generateDebugging(config) },
6532
7323
  { name: "Navigation Patterns", content: generateNavigationPatterns(config) },
6533
- { name: "Asset Handling", content: generateAssetHandling(config) }
7324
+ { name: "Asset Handling", content: generateAssetHandling(config) },
7325
+ { name: "Host Design System", content: generateHostDesignSystem(config) }
6534
7326
  ];
6535
7327
  if (config.testing) {
6536
7328
  skills.push({ name: "Testing Guide", content: generateTestingGuide(config) });
@@ -6920,12 +7712,16 @@ function ciWorkflow(registryRepo, githubHost) {
6920
7712
  ` : "";
6921
7713
  return `# Auto-generated by vibe-kit — CI/CD for this mini-app.
6922
7714
  #
6923
- # Required repository secrets:
7715
+ # Required repository secrets (dev):
6924
7716
  # VIBE_STORAGE_ACCOUNT_NAME — Azure Storage account name
6925
7717
  # VIBE_STORAGE_ACCOUNT_KEY — Azure Storage account key
6926
7718
  # VIBE_STORAGE_CONTAINER_NAME — Azure container for bundles
6927
7719
  # VIBE_REGISTRY_REPO — e.g. "${registryRepo}"
6928
7720
  # VIBE_REGISTRY_TOKEN — PAT with repo write access to the registry
7721
+ #
7722
+ # For staging/prod, use environment-scoped secrets (e.g. VIBE_STORAGE_ACCOUNT_KEY_STAGING).
7723
+ # Each environment should use separate Azure Storage accounts so that dev credentials
7724
+ # cannot write to staging/prod. See vibe-publish docs for the full scoping model.
6929
7725
  ${gheComment}
6930
7726
  name: CI
6931
7727
 
@@ -7122,6 +7918,19 @@ async function createCommand(rawName) {
7122
7918
  }
7123
7919
  projectName = converted;
7124
7920
  }
7921
+ if (!isValidCustomElementName(projectName)) {
7922
+ const suggested = `vibe-${projectName}`;
7923
+ warn(`"${projectName}" is not a valid custom element name (must contain a hyphen).`);
7924
+ const ok = await dist_default3({
7925
+ message: `Use "${suggested}" instead?`,
7926
+ default: true
7927
+ });
7928
+ if (!ok) {
7929
+ error("Aborted.");
7930
+ process.exit(1);
7931
+ }
7932
+ projectName = suggested;
7933
+ }
7125
7934
  const outputDir = resolve2(process.cwd(), projectName);
7126
7935
  if (await dirExists(outputDir)) {
7127
7936
  const overwrite = await dist_default3({