@aiworkbench/vibe-cli 0.0.4 → 0.0.6

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
@@ -6496,6 +7287,33 @@ cleanups.forEach((fn) => fn());
6496
7287
  | "Unresolved external" build warning | Dependency not in Vite external config | Add to \`rollupOptions.external\` or bundle it |
6497
7288
  | HMR not reflecting changes | Custom element re-registration conflict | Hard refresh the page (Cmd+Shift+R / Ctrl+Shift+R) |
6498
7289
 
7290
+ ## Skills Reference
7291
+
7292
+ This project includes detailed skill guides for specific topics. Skills are **not loaded by
7293
+ default** to keep context focused. **Load the relevant skill before writing code** whenever the
7294
+ user's request touches that area. When in doubt, load the skill — it's better to have the
7295
+ reference than to guess.
7296
+
7297
+ | Skill | Load when… |
7298
+ |-------|-----------|
7299
+ | \`bridge-usage\` | Using any bridge capability (auth, api, storage, events, theme, toast, navigation) |
7300
+ | \`component-patterns\` | Creating or modifying components, lifecycle hooks, shadow DOM patterns |
7301
+ | \`manifest-guide\` | Editing manifest.json, adding or changing permissions |
7302
+ | \`security-rules\` | Reviewing code for policy violations, or unsure if a pattern is allowed |
7303
+ | \`vite-config\` | Changing build configuration, adding Vite plugins, optimizing bundles |
7304
+ | \`accessibility\` | Adding interactive elements, forms, ARIA attributes, keyboard navigation |
7305
+ | \`error-handling\` | Implementing try/catch, error boundaries, or graceful degradation |
7306
+ | \`performance\` | Optimizing bundle size, render performance, or memory usage |
7307
+ | \`debugging\` | Troubleshooting issues, tracing bridge calls, fixing HMR problems |
7308
+ | \`navigation-patterns\` | Building multi-view UIs, tabs, wizards, or steppers (no URL routing) |
7309
+ | \`asset-handling\` | Working with images, SVGs, fonts, or other static assets |
7310
+ | \`host-design-system\` | Styling components to match the host application's design tokens and visual language |
7311
+ ${testing ? `| \`testing-guide\` | Writing or modifying tests, mocking the bridge, test setup |
7312
+ ` : ""}
7313
+ **How to use skills:** Read the skill file for the relevant topic before implementing. Multiple
7314
+ skills can apply to a single task — for example, building a new form might involve
7315
+ \`component-patterns\`, \`accessibility\`, and \`bridge-usage\` (if the form submits via the bridge API).
7316
+
6499
7317
  ## Build & Dev
6500
7318
  - \`bun run dev\` — Start Vite dev server with hot reload
6501
7319
  - \`bun run build\` — Production build (ES module, < 50kb gzipped)
@@ -6530,7 +7348,8 @@ function generateEmbeddedSkills(config) {
6530
7348
  { name: "Performance", content: generatePerformance(config) },
6531
7349
  { name: "Debugging", content: generateDebugging(config) },
6532
7350
  { name: "Navigation Patterns", content: generateNavigationPatterns(config) },
6533
- { name: "Asset Handling", content: generateAssetHandling(config) }
7351
+ { name: "Asset Handling", content: generateAssetHandling(config) },
7352
+ { name: "Host Design System", content: generateHostDesignSystem(config) }
6534
7353
  ];
6535
7354
  if (config.testing) {
6536
7355
  skills.push({ name: "Testing Guide", content: generateTestingGuide(config) });
@@ -6867,6 +7686,11 @@ var SKILL_DEFS = [
6867
7686
  name: "asset-handling",
6868
7687
  description: "Inline SVGs, imported data URLs, and CDN patterns for production assets",
6869
7688
  generate: generateAssetHandling
7689
+ },
7690
+ {
7691
+ name: "host-design-system",
7692
+ description: "Host app design tokens, typography, spacing, colors, and component patterns",
7693
+ generate: generateHostDesignSystem
6870
7694
  }
6871
7695
  ];
6872
7696
  async function writeFolderSkill(baseDir, skill, content) {
@@ -6920,12 +7744,16 @@ function ciWorkflow(registryRepo, githubHost) {
6920
7744
  ` : "";
6921
7745
  return `# Auto-generated by vibe-kit — CI/CD for this mini-app.
6922
7746
  #
6923
- # Required repository secrets:
7747
+ # Required repository secrets (dev):
6924
7748
  # VIBE_STORAGE_ACCOUNT_NAME — Azure Storage account name
6925
7749
  # VIBE_STORAGE_ACCOUNT_KEY — Azure Storage account key
6926
7750
  # VIBE_STORAGE_CONTAINER_NAME — Azure container for bundles
6927
7751
  # VIBE_REGISTRY_REPO — e.g. "${registryRepo}"
6928
7752
  # VIBE_REGISTRY_TOKEN — PAT with repo write access to the registry
7753
+ #
7754
+ # For staging/prod, use environment-scoped secrets (e.g. VIBE_STORAGE_ACCOUNT_KEY_STAGING).
7755
+ # Each environment should use separate Azure Storage accounts so that dev credentials
7756
+ # cannot write to staging/prod. See vibe-publish docs for the full scoping model.
6929
7757
  ${gheComment}
6930
7758
  name: CI
6931
7759
 
@@ -7122,6 +7950,19 @@ async function createCommand(rawName) {
7122
7950
  }
7123
7951
  projectName = converted;
7124
7952
  }
7953
+ if (!isValidCustomElementName(projectName)) {
7954
+ const suggested = `vibe-${projectName}`;
7955
+ warn(`"${projectName}" is not a valid custom element name (must contain a hyphen).`);
7956
+ const ok = await dist_default3({
7957
+ message: `Use "${suggested}" instead?`,
7958
+ default: true
7959
+ });
7960
+ if (!ok) {
7961
+ error("Aborted.");
7962
+ process.exit(1);
7963
+ }
7964
+ projectName = suggested;
7965
+ }
7125
7966
  const outputDir = resolve2(process.cwd(), projectName);
7126
7967
  if (await dirExists(outputDir)) {
7127
7968
  const overwrite = await dist_default3({