create-skybridge 0.0.0-dev.994f77c → 0.0.0-dev.995023d

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.
Files changed (42) hide show
  1. package/dist/index.js +107 -17
  2. package/package.json +1 -1
  3. package/templates/blank/README.md +2 -1
  4. package/templates/blank/node_modules/.bin/alpic +2 -2
  5. package/templates/blank/package.json +5 -4
  6. package/templates/blank/src/index.ts +3 -0
  7. package/templates/blank/src/server.ts +7 -11
  8. package/templates/blank/vite.config.ts +1 -1
  9. package/templates/demo/README.md +3 -1
  10. package/templates/demo/evals/start.eval.ts +11 -0
  11. package/templates/demo/node_modules/.bin/alpic +2 -2
  12. package/templates/demo/node_modules/.bin/vitest +21 -0
  13. package/templates/demo/package.json +11 -5
  14. package/templates/demo/src/index.ts +3 -0
  15. package/templates/demo/src/server.ts +86 -88
  16. package/templates/demo/src/views/onboarding.tsx +3 -3
  17. package/templates/demo/tsconfig.evals.json +11 -0
  18. package/templates/demo/vite.config.ts +3 -3
  19. package/templates/ecom/.env.template +5 -0
  20. package/templates/ecom/README.md +2 -1
  21. package/templates/ecom/node_modules/.bin/alpic +2 -2
  22. package/templates/ecom/node_modules/.bin/ladle +2 -2
  23. package/templates/ecom/node_modules/.bin/vite +2 -2
  24. package/templates/ecom/package.json +3 -2
  25. package/templates/ecom/src/catalog/index.ts +19 -0
  26. package/templates/ecom/src/catalog/mock.ts +134 -0
  27. package/templates/ecom/src/catalog/shopify.ts +264 -0
  28. package/templates/ecom/src/components/product-card.tsx +2 -2
  29. package/templates/ecom/src/components/variant-picker.stories.tsx +1 -1
  30. package/templates/ecom/src/components/variant-picker.tsx +1 -1
  31. package/templates/ecom/src/components/view-frame.tsx +4 -4
  32. package/templates/ecom/src/index.ts +3 -0
  33. package/templates/ecom/src/lib/variants.ts +1 -1
  34. package/templates/ecom/src/server.ts +17 -21
  35. package/templates/ecom/src/tools/render-carousel.ts +5 -89
  36. package/templates/ecom/src/tools/search-products.ts +32 -21
  37. package/templates/ecom/src/types.ts +72 -0
  38. package/templates/ecom/src/views/carousel/detail/detail.css.ts +2 -0
  39. package/templates/ecom/src/views/carousel/detail/detail.stories.tsx +8 -1
  40. package/templates/ecom/src/views/carousel/detail/index.tsx +1 -1
  41. package/templates/ecom/src/views/carousel/index.tsx +1 -2
  42. package/templates/ecom/vite.config.ts +1 -1
@@ -1,75 +1,11 @@
1
1
  import { z } from "zod";
2
+ import { getProducts } from "../catalog/index.js";
2
3
  import { CAROUSEL_MAX_SIZE, CAROUSEL_RANGE } from "../config.js";
3
- import { type Price, PriceSchema, type Spec, SpecSchema } from "../types.js";
4
+ import { PriceSchema, type Product, SpecSchema } from "../types.js";
4
5
 
5
6
  // The `render-carousel` tool: takes the IDs the model curated and returns the
6
- // matching products for the carousel view to render.
7
- // Everything this tool needs lives in this file.
8
-
9
- // ---------------------------------------------------------------------------
10
- // Product model
11
- // ---------------------------------------------------------------------------
12
- // Model: variant-as-full-product. Each `Variant` is a complete, buyable product
13
- // (its own title, price, media). A `Product` ties sibling variants together and
14
- // declares the axes (`Option`s) they vary on. A product with no variations is
15
- // just a product with a single variant and no options.
16
-
17
- // One selectable value on an axis, e.g. the "Black" choice on the "Color" axis.
18
- type OptionValue = {
19
- id: string; // stable key referenced by Variant.selection, e.g. "black"
20
- label: string; // shown to the user, e.g. "Black"
21
- media?: string; // optional swatch / image representing this value
22
- };
23
-
24
- // A variation axis the variants differ on, e.g. Color or Size.
25
- type Option = {
26
- id: string; // stable key, used as a key in Variant.selection, e.g. "color"
27
- label: string; // shown to the user, e.g. "Color"
28
- values: OptionValue[]; // in display order
29
- };
30
-
31
- // Display fields shared by a Variant and by a product's `card`.
32
- type Meta = {
33
- title: string;
34
- description?: string;
35
- price?: Price;
36
- media: string[]; // images for this item; media[0] is the primary/cover
37
- url?: string; // link to this item's external product page
38
- outOfStock?: boolean; // true = not purchasable
39
- // Objective, product-specific facts (material, dimensions, capacity, care…),
40
- // rendered as-is. Each fact's label is optional.
41
- specs: Spec[];
42
-
43
- // @todo: Add whatever custom fields the carousel should render as real types
44
- // (e.g. `rating` → stars, `discountPct` → badge, `badges` → chips).
45
- };
46
-
47
- // One buyable product: full display Meta plus which value it takes on each axis.
48
- export type Variant = Meta & {
49
- id: string; // SKU / article number; unique within the catalog
50
- // The chosen value per axis: keys are Option.id, values are OptionValue.id.
51
- // e.g. { color: "black", size: "40" }
52
- selection: Record<string, string>;
53
- };
54
-
55
- // A product: one carousel card backed by one or more variants and the axes they
56
- // vary on (none for a single-variant product).
57
- export type Product = {
58
- id: string; // stable product key
59
- // The axes the variants vary on, in display order. Order is semantic: the
60
- // detail picker narrows availability top-down (each axis constrained by the
61
- // ones before it), so put the imagery-driving axis (usually color) first.
62
- options: Option[];
63
- // Only the variants that actually exist. A missing combination (e.g. no
64
- // { color: "black", size: "40" }) is simply absent from this list — that is how
65
- // contingent variations are expressed. Derive the selectable values for an axis
66
- // by filtering this list on the choices already made.
67
- variants: Variant[];
68
- // The product's carousel card. Surfaced both in the carousel (the view
69
- // renders it) and to the model (structuredContent is projected from it).
70
- // How you build it depends on your mapping strategy: see getProducts.
71
- card: Meta;
72
- };
7
+ // matching products for the carousel view to render. Data access lives in
8
+ // `src/catalog/`; everything else this tool needs lives in this file.
73
9
 
74
10
  // ---------------------------------------------------------------------------
75
11
  // Input
@@ -115,26 +51,6 @@ const outputSchema = {
115
51
 
116
52
  type RenderOutput = z.infer<z.ZodObject<typeof outputSchema>>;
117
53
 
118
- // ---------------------------------------------------------------------------
119
- // Data access
120
- // ---------------------------------------------------------------------------
121
-
122
- async function getProducts(_ids: string[]): Promise<Product[]> {
123
- // @todo: fetch each id from your product API / DB and map the results into
124
- // `Product`s (rename `_ids` -> `ids` once you use it).
125
- //
126
- // First decide your mapping strategy — it depends on your catalog:
127
- // - no variants (simple products): one `Product`, a single variant, card = that variant, options: [],
128
- // - grouped: one `Product` per product; `card` = union of its variants, one picture per requested variant
129
- // - one card per requested variant: `card` = that variant
130
- // Either way, set `variants` to ALL variants the source returns for the product;
131
- // the detail view reads them so the client can switch variant. Order `options`
132
- // with the imagery-driving axis first (see the Product type: order is semantic).
133
- //
134
- // Returns [] for now, so the carousel is empty.
135
- return [];
136
- }
137
-
138
54
  // ---------------------------------------------------------------------------
139
55
  // Mapping: trim each product's `card` and `options` into the model-facing
140
56
  // grounding (outputSchema), dropping presentational fields (media, url). The
@@ -185,7 +101,7 @@ export const renderCarouselDefinition = {
185
101
  Display the products you curated as an inline carousel for the client.
186
102
 
187
103
  ## When to call
188
- Call this AFTER searching and curating, and BEFORE writing your recommendation. Do not describe the products in text first; the carousel shows them. Stay silent until it has rendered, then speak.
104
+ Call this AFTER searching and curating, and BEFORE writing your recommendation. Avoid describing the products in text first since the carousel shows them.
189
105
 
190
106
  ## What to pass
191
107
  Pass the IDs of the ${CAROUSEL_RANGE} products you chose, in display order (most relevant first). Order is significant: the carousel shows them in this exact order and your recommendation must follow the same sequence. Pass distinct products, not several variants of the same one; the detail view lets the client explore a product's variants (colors, sizes, and so on).
@@ -1,15 +1,17 @@
1
1
  import { z } from "zod";
2
+ import { search } from "../catalog/index.js";
2
3
  import {
3
4
  CAROUSEL_MAX_SIZE,
4
5
  CAROUSEL_RANGE,
5
6
  MIN_SEARCH_ITERATIONS,
6
7
  } from "../config.js";
7
- import { PriceSchema, SpecSchema } from "../types.js";
8
+ import { PriceSchema, type SearchResult, SpecSchema } from "../types.js";
8
9
 
9
10
  // The `search-products` tool: keyword + filters in, matching products out as
10
11
  // structured output for the model. It has NO view — include only what the model
11
12
  // needs to curate (ids + properties), never presentational data (images, media);
12
- // render-carousel handles that. Everything this tool needs lives in this file.
13
+ // render-carousel handles that. Data access lives in `src/catalog/`; everything
14
+ // else this tool needs lives in this file.
13
15
 
14
16
  // ---------------------------------------------------------------------------
15
17
  // Input
@@ -21,10 +23,8 @@ const inputSchema = {
21
23
  keyword: z.string().describe(
22
24
  `\
23
25
  Short noun phrases extracted from conversational input. Never pass full sentences. \
24
- ALWAYS use the singular form, not the plural. \
25
26
  Include color, material, and style descriptors for accuracy. \
26
- For vague gift or occasion queries without a clear product type, use broad category terms. \
27
- Preserve the existing keyword across filter refinements; only replace when the user makes a completely different request.`,
27
+ For vague gift or occasion queries without a clear product type, use broad category terms.`,
28
28
  ),
29
29
 
30
30
  // @todo: set the sort options your backend supports (or remove).
@@ -41,7 +41,7 @@ Preserve the existing keyword across filter refinements; only replace when the u
41
41
  ),
42
42
  };
43
43
 
44
- type SearchInput = z.infer<z.ZodObject<typeof inputSchema>>;
44
+ export type SearchInput = z.infer<z.ZodObject<typeof inputSchema>>;
45
45
 
46
46
  // ---------------------------------------------------------------------------
47
47
  // Output — model-facing grounding, returned in structuredContent.
@@ -82,17 +82,29 @@ const outputSchema = {
82
82
  type SearchOutput = z.infer<z.ZodObject<typeof outputSchema>>;
83
83
 
84
84
  // ---------------------------------------------------------------------------
85
- // Data access
85
+ // Mapping: project each product's `card` into the model-facing grounding
86
+ // (outputSchema), dropping presentational fields (media, url).
87
+ // @todo: choose what the model sees per product. Grounding only: the model
88
+ // curates on facts, so presentational data never belongs here.
86
89
  // ---------------------------------------------------------------------------
87
90
 
88
- function search(_input: SearchInput): SearchOutput {
89
- // @todo: plug in your product API / DB. Query it with the input params and map
90
- // each result into `products` below. `pages` and `totalHits` are optional.
91
- return {
92
- products: [], // { id: string; properties: { name: PropertyName; value: string[] }[] }[]
93
- pages: { current: 1, total: 1 },
94
- totalHits: 0,
95
- };
91
+ function toStructuredContent({
92
+ products,
93
+ pages,
94
+ totalHits,
95
+ }: SearchResult): SearchOutput {
96
+ const results: SearchOutput["products"] = [];
97
+ for (const { id, card } of products) {
98
+ results.push({
99
+ id,
100
+ title: card.title,
101
+ description: card.description,
102
+ price: card.price,
103
+ outOfStock: card.outOfStock,
104
+ specs: card.specs,
105
+ });
106
+ }
107
+ return { products: results, pages, totalHits };
96
108
  }
97
109
 
98
110
  // ---------------------------------------------------------------------------
@@ -147,13 +159,12 @@ Never assume a category is unavailable: always search before responding.
147
159
 
148
160
  The response is data only: a list of matching products (name, ID, price, description, and any product-specific facts) plus pagination and the available filters. The raw results are for your eyes only: the client never sees them.
149
161
 
150
- NEVER describe or characterize the raw results to the client: do not mention how many there are, what categories they fall into, or that they look off-topic. While searching, stay silent: do not narrate what you are doing, announce searches, or report progress between tool calls. Speak only once the carousel is rendered.
162
+ NEVER describe or characterize the raw results to the client: do not mention how many there are, what categories they fall into, or that they look off-topic.
151
163
 
152
164
  Act on the response as follows:
153
165
 
154
- - FIRST SEARCH: use the keyword only (plus sort if needed). Read the available filters in the response; these are the only valid filter options for follow-ups.
155
- - REFINEMENT: if the user's intent matches an available filter, apply it. If it doesn't match any filter, start a new keyword search.
156
- - ITERATE: a single search is never enough. Run several searches (${MIN_SEARCH_ITERATIONS} minimum) before rendering: vary the keyword, apply a filter, or page deeper, and compare results across them. Quality comes from this multi-call exploration; one call then render gives shallow results.
166
+ - FIRST SEARCH: use keywords only (plus sort if needed).
167
+ - ITERATE: a single search is not always enough. Run several searches (${MIN_SEARCH_ITERATIONS} minimum) before rendering: vary the keyword, apply a filter, or page deeper, and compare results across them. Quality comes from this multi-call exploration; one call then render gives shallow results.
157
168
  - CURATION: read results and pick the best matches for the client's intent, grounding your choice in each product's description. If zero results, broaden the keyword or relax filters and search again: do NOT call render-carousel. If fewer than 3 results and the user didn't ask for a specific product, search again with broader terms. Only after exploring across several searches, call render-carousel with the selected IDs.
158
169
  - PRESENT: Once you have ${CAROUSEL_RANGE} distinct, relevant products, call render-carousel with their IDs. Recommend ONLY AFTER the carousel displays, in carousel order.
159
170
 
@@ -175,8 +186,8 @@ The sweet spot is ${CAROUSEL_RANGE} products.
175
186
  outputSchema,
176
187
  };
177
188
 
178
- export function searchProductsHandler(input: SearchInput) {
179
- const results = search(input);
189
+ export async function searchProductsHandler(input: SearchInput) {
190
+ const results = toStructuredContent(await search(input));
180
191
  return {
181
192
  structuredContent: results,
182
193
  content: [{ type: "text" as const, text: narrate(results) }],
@@ -13,3 +13,75 @@ export const SpecSchema = z.object({
13
13
  value: z.string(),
14
14
  });
15
15
  export type Spec = z.infer<typeof SpecSchema>;
16
+
17
+ // ---------------------------------------------------------------------------
18
+ // Product model — what the catalog providers in `src/catalog/` return and the
19
+ // views render. Model: variant-as-full-product. Each `Variant` is a complete,
20
+ // buyable product (its own title, price, media). A `Product` ties sibling
21
+ // variants together and declares the axes (`Option`s) they vary on. A product
22
+ // with no variations is just a product with a single variant and no options.
23
+ // ---------------------------------------------------------------------------
24
+
25
+ // One selectable value on an axis, e.g. the "Black" choice on the "Color" axis.
26
+ type OptionValue = {
27
+ id: string; // stable key referenced by Variant.selection, e.g. "black"
28
+ label: string; // shown to the user, e.g. "Black"
29
+ media?: string; // optional swatch / image representing this value
30
+ };
31
+
32
+ // A variation axis the variants differ on, e.g. Color or Size.
33
+ export type Option = {
34
+ id: string; // stable key, used as a key in Variant.selection, e.g. "color"
35
+ label: string; // shown to the user, e.g. "Color"
36
+ values: OptionValue[]; // in display order
37
+ };
38
+
39
+ // Display fields shared by a Variant and by a product's `card`.
40
+ type Meta = {
41
+ title: string;
42
+ description?: string;
43
+ price?: Price;
44
+ media: string[]; // images for this item; media[0] is the primary/cover
45
+ url?: string; // link to this item's external product page
46
+ outOfStock?: boolean; // true = not purchasable
47
+ // Objective, product-specific facts (material, dimensions, capacity, care…),
48
+ // rendered as-is. Each fact's label is optional.
49
+ specs: Spec[];
50
+
51
+ // @todo: Add whatever custom fields the carousel should render as real types
52
+ // (e.g. `rating` → stars, `discountPct` → badge, `badges` → chips).
53
+ };
54
+
55
+ // One buyable product: full display Meta plus which value it takes on each axis.
56
+ export type Variant = Meta & {
57
+ id: string; // SKU / article number; unique within the catalog
58
+ // The chosen value per axis: keys are Option.id, values are OptionValue.id.
59
+ // e.g. { color: "black", size: "40" }
60
+ selection: Record<string, string>;
61
+ };
62
+
63
+ // A product: one carousel card backed by one or more variants and the axes they
64
+ // vary on (none for a single-variant product).
65
+ export type Product = {
66
+ id: string; // stable product key
67
+ // The axes the variants vary on, in display order. Order is semantic: the
68
+ // detail picker narrows availability top-down (each axis constrained by the
69
+ // ones before it), so put the imagery-driving axis (usually color) first.
70
+ options: Option[];
71
+ // Only the variants that actually exist. A missing combination (e.g. no
72
+ // { color: "black", size: "40" }) is simply absent from this list — that is how
73
+ // contingent variations are expressed. Derive the selectable values for an axis
74
+ // by filtering this list on the choices already made.
75
+ variants: Variant[];
76
+ // The product's carousel card. Surfaced both in the carousel (the view
77
+ // renders it) and to the model (structuredContent is projected from it).
78
+ card: Meta;
79
+ };
80
+
81
+ // What a catalog provider's `search` returns. `pages` and `totalHits` are
82
+ // optional: not every backend reports them.
83
+ export type SearchResult = {
84
+ products: Product[];
85
+ pages?: { current: number; total: number };
86
+ totalHits?: number;
87
+ };
@@ -7,6 +7,8 @@ const TWO_COLUMN_MIN_WIDTH = "560px";
7
7
 
8
8
  export const detail = style({
9
9
  containerType: "inline-size",
10
+ maxHeight: "100dvh",
11
+ overflowY: "auto",
10
12
  padding: primitives.space.s,
11
13
  // Clear the notch / home indicator when the page bleeds to the screen edge.
12
14
  paddingBottom: `calc(${primitives.space.s} + env(safe-area-inset-bottom))`,
@@ -1,4 +1,4 @@
1
- import type { Product } from "../../../tools/render-carousel.js";
1
+ import type { Product } from "../../../types.js";
2
2
  import { DetailView } from "./index";
3
3
 
4
4
  function shot(fill: string) {
@@ -56,7 +56,14 @@ function jacket(
56
56
  outOfStock,
57
57
  specs: [
58
58
  { label: "Material", value: "Water-repellent cotton" },
59
+ { label: "Lining", value: "Recycled polyester" },
59
60
  { label: "Fit", value: "Relaxed" },
61
+ { label: "Closure", value: "Two-way zip" },
62
+ { label: "Pockets", value: "Two zip front, one inside" },
63
+ { label: "Collar", value: "Stand-up" },
64
+ { label: "Cuffs", value: "Ribbed" },
65
+ { label: "Weight", value: "780 g" },
66
+ { label: "Origin", value: "Made in Portugal" },
60
67
  { value: "Machine washable" }, // label-less fact (renders value only)
61
68
  ],
62
69
  };
@@ -12,7 +12,7 @@ import {
12
12
  resolveVariant,
13
13
  type Selection,
14
14
  } from "../../../lib/variants.js";
15
- import type { Product, Variant } from "../../../tools/render-carousel.js";
15
+ import type { Product, Variant } from "../../../types.js";
16
16
  import * as styles from "./detail.css";
17
17
 
18
18
  // Price to show: the resolved variant's price, else the range across variants
@@ -20,8 +20,7 @@ import { sprinkles } from "../../design/tokens";
20
20
  import { useToolInfo } from "../../helpers.js";
21
21
  import { useLabels } from "../../i18n";
22
22
  import { formatPrice } from "../../lib/format";
23
- import type { Product } from "../../tools/render-carousel.js";
24
- import type { Price, Spec } from "../../types.js";
23
+ import type { Price, Product, Spec } from "../../types.js";
25
24
  import { DetailView } from "./detail";
26
25
 
27
26
  const SKELETON_COUNT = 4;
@@ -1,6 +1,6 @@
1
+ import { skybridge } from "@skybridge/vite-plugin";
1
2
  import { vanillaExtractPlugin } from "@vanilla-extract/vite-plugin";
2
3
  import react from "@vitejs/plugin-react";
3
- import { skybridge } from "skybridge/vite";
4
4
  import { defineConfig, type PluginOption } from "vite";
5
5
 
6
6
  export default defineConfig({