@vitrine-kit/vitrine 0.3.0 → 0.3.1

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 (3) hide show
  1. package/README.md +18 -18
  2. package/dist/index.js +205 -205
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -1,34 +1,34 @@
1
1
  # @vitrine-kit/vitrine (CLI)
2
2
 
3
- Инструментарий Vitrine — агентского стартер-кита для интернет-магазинов и каталогов.
4
- Устанавливается глобально:
3
+ The Vitrine toolkit an agentic starter kit for e-commerce stores and catalogs.
4
+ Install it globally:
5
5
 
6
6
  ```bash
7
7
  npm i -g @vitrine-kit/vitrine
8
8
  ```
9
9
 
10
- ## С нуля до запущенного проекта
10
+ ## From zero to a running project
11
11
 
12
- 1. **Создать репозиторий клиента**визард (или флаги для CI):
12
+ 1. **Create the client repository** wizard (or flags for CI):
13
13
  ```bash
14
14
  vitrine init my-shop
15
- # без интерактива:
15
+ # non-interactive:
16
16
  vitrine init my-shop --yes --tier simple-store --backend payload --features catalog,product-page,seo,cart
17
17
  ```
18
- Визард спросит уровень (`catalog` / `simple-store` / `full-store`), backend
19
- (`payload` / `vendure`), набор фич и платёжного провайдера.
20
- 2. **Дальше через ИИ-агента.** В созданном репозитории есть `CLAUDE.md` (операционный
21
- гайд + справочник команд), `AGENTS.md` (для кросс-тул агентов) и слэш-команды Claude Code
22
- в `.claude/commands/`. Откройте проект в Claude Code и запустите `/setup` — агент поставит
23
- зависимости, заполнит `.env` и поднимет dev-сервер. Затем `/add-feature`, `/design`,
18
+ The wizard asks for the tier (`catalog` / `simple-store` / `full-store`), backend
19
+ (`payload` / `vendure`), the feature set, and the payment provider.
20
+ 2. **Then drive it with an AI agent.** The generated repo includes `CLAUDE.md` (operational
21
+ guide + command reference), `AGENTS.md` (for cross-tool agents), and Claude Code slash commands
22
+ in `.claude/commands/`. Open the project in Claude Code and run `/setup` — the agent installs
23
+ dependencies, fills in `.env`, and starts the dev server. Then `/add-feature`, `/design`,
24
24
  `/update`, `/doctor`.
25
25
 
26
- ## Команды
26
+ ## Commands
27
27
 
28
- В репозитории клиента: `init` (визард), `add`, `remove`, `update`, `diff`, `list`, `doctor`,
29
- `design apply`. Инфраструктура: `kit update` / `kit status` / `self-update`. Полный справочник
30
- с флагами в `CLAUDE.md` созданного проекта.
28
+ In the client repository: `init` (wizard), `add`, `remove`, `update`, `diff`, `list`, `doctor`,
29
+ `design apply`. Infrastructure: `kit update` / `kit status` / `self-update`. The full reference
30
+ with flags is in the generated project's `CLAUDE.md`.
31
31
 
32
- Ядро **примитив установки фичи**, общий для `init` и `add` (гарантия: «добавлено визардом»
33
- «добавлено позже»). `add`/`update` работают офлайн из кэша `~/.vitrine`; сеть нужна только
34
- `kit update`.
32
+ At its core is the **feature install primitive**, shared by `init` and `add` (guarantee: "added by
33
+ the wizard" "added later"). `add`/`update` work offline from the `~/.vitrine` cache; the network
34
+ is only needed for `kit update`.
package/dist/index.js CHANGED
@@ -18,7 +18,7 @@ function preflightNode(min = 20) {
18
18
  const major = Number(process.versions.node.split(".")[0]);
19
19
  if (Number.isFinite(major) && major < min) {
20
20
  throw new Error(
21
- `[vitrine] \u043D\u0443\u0436\u0435\u043D Node >= ${min} (\u0442\u0435\u043A\u0443\u0449\u0438\u0439 ${process.versions.node}). \u0423\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u0435 Node ${min} LTS: https://nodejs.org`
21
+ `[vitrine] Node >= ${min} required (current ${process.versions.node}). Install Node ${min} LTS: https://nodejs.org`
22
22
  );
23
23
  }
24
24
  }
@@ -46,7 +46,7 @@ function safeJoin(root, ...segs) {
46
46
  const target = resolve(base, ...segs);
47
47
  const rel = relative(base, target);
48
48
  if (rel.startsWith("..") || isAbsolute(rel)) {
49
- throw new Error(`[vitrine] \u043F\u0443\u0442\u044C "${join(...segs)}" \u0432\u044B\u0445\u043E\u0434\u0438\u0442 \u0437\u0430 \u043F\u0440\u0435\u0434\u0435\u043B\u044B "${root}"`);
49
+ throw new Error(`[vitrine] path "${join(...segs)}" escapes "${root}"`);
50
50
  }
51
51
  return target;
52
52
  }
@@ -84,7 +84,7 @@ function replaceBetween(content, startMarker, endMarker, replacement) {
84
84
  const si = content.indexOf(startMarker);
85
85
  const ei = content.indexOf(endMarker);
86
86
  if (si === -1 || ei === -1 || ei < si) {
87
- throw new Error(`[vitrine] \u043C\u0430\u0440\u043A\u0435\u0440\u044B "${startMarker}"/"${endMarker}" \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u044B`);
87
+ throw new Error(`[vitrine] markers "${startMarker}"/"${endMarker}" not found`);
88
88
  }
89
89
  const afterStartLine = content.indexOf("\n", si) + 1;
90
90
  const endLineStart = content.lastIndexOf("\n", ei) + 1;
@@ -131,7 +131,7 @@ import { join as join3, resolve as resolve3 } from "path";
131
131
  function vitrineHome() {
132
132
  if (process.env.VITRINE_HOME) return resolve3(process.env.VITRINE_HOME);
133
133
  const home = process.env.USERPROFILE ?? process.env.HOME;
134
- if (!home) throw new Error("[vitrine] \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u043F\u0440\u0435\u0434\u0435\u043B\u0438\u0442\u044C \u0434\u043E\u043C\u0430\u0448\u043D\u0438\u0439 \u043A\u0430\u0442\u0430\u043B\u043E\u0433 (HOME/USERPROFILE)");
134
+ if (!home) throw new Error("[vitrine] could not determine the home directory (HOME/USERPROFILE)");
135
135
  return join3(home, ".vitrine");
136
136
  }
137
137
  function cachePaths(home = vitrineHome()) {
@@ -171,7 +171,7 @@ function computeChangelog(oldIndex, newIndex) {
171
171
  return entries;
172
172
  }
173
173
  function formatChangelog(entries) {
174
- if (entries.length === 0) return "\u0431\u0435\u0437 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439 \u043D\u0430\u0431\u043E\u0440\u0430 \u0444\u0438\u0447";
174
+ if (entries.length === 0) return "no changes to the feature set";
175
175
  return entries.map((e) => {
176
176
  if (e.kind === "added") return `+ ${e.name}${e.to ? ` ${e.to}` : ""}`;
177
177
  if (e.kind === "removed") return `- ${e.name}`;
@@ -183,7 +183,7 @@ function populateCache(fromDir, opts = {}) {
183
183
  const srcRegistry = join3(fromDir, "registry");
184
184
  const srcTemplates = join3(fromDir, "templates");
185
185
  if (!existsSync3(join3(srcRegistry, "_index.json"))) {
186
- throw new Error(`[vitrine] \u0432 "${fromDir}" \u043D\u0435\u0442 registry/_index.json \u2014 \u044D\u0442\u043E \u043D\u0435 \u0434\u0435\u0440\u0435\u0432\u043E kit`);
186
+ throw new Error(`[vitrine] "${fromDir}" has no registry/_index.json \u2014 not a kit tree`);
187
187
  }
188
188
  const paths = cachePaths(home);
189
189
  const oldIndex = readIndex(paths.registry);
@@ -229,7 +229,7 @@ function resolveRegistryRoot(explicit) {
229
229
  }
230
230
  const dev = findUpRegistry(process.cwd());
231
231
  if (dev) return dev;
232
- throw new Error('[vitrine] \u0440\u0435\u0435\u0441\u0442\u0440 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D. \u0417\u0430\u043F\u0443\u0441\u0442\u0438 "vitrine kit update" \u0438\u043B\u0438 \u0443\u043A\u0430\u0436\u0438 --registry.');
232
+ throw new Error('[vitrine] registry not found. Run "vitrine kit update" or pass --registry.');
233
233
  }
234
234
  function createRegistrySource(explicitRoot) {
235
235
  const root = resolveRegistryRoot(explicitRoot);
@@ -240,7 +240,7 @@ function createRegistrySource(explicitRoot) {
240
240
  const cached = cache.get(name);
241
241
  if (cached) return cached;
242
242
  const file = join4(featureDir(name), "feature.json");
243
- if (!existsSync4(file)) throw new Error(`[vitrine] \u0444\u0438\u0447\u0430 "${name}" \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u0430 \u0432 \u0440\u0435\u0435\u0441\u0442\u0440\u0435`);
243
+ if (!existsSync4(file)) throw new Error(`[vitrine] feature "${name}" not found in the registry`);
244
244
  const manifest = featureManifestSchema.parse(JSON.parse(readText(file)));
245
245
  cache.set(name, manifest);
246
246
  return manifest;
@@ -326,7 +326,7 @@ function assertNoPascalCollisions(features) {
326
326
  const id = pascalCase(f.name);
327
327
  const prev = byPascal.get(id);
328
328
  if (prev && prev !== f.name) {
329
- throw new Error(`[vitrine] \u0444\u0438\u0447\u0438 "${prev}" \u0438 "${f.name}" \u0434\u0430\u044E\u0442 \u043E\u0434\u0438\u043D \u0438\u0434\u0435\u043D\u0442\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 "${id}" \u2014 \u043F\u0435\u0440\u0435\u0438\u043C\u0435\u043D\u0443\u0439\u0442\u0435 \u043E\u0434\u043D\u0443`);
329
+ throw new Error(`[vitrine] features "${prev}" and "${f.name}" produce the same identifier "${id}" \u2014 rename one`);
330
330
  }
331
331
  byPascal.set(id, f.name);
332
332
  }
@@ -339,11 +339,11 @@ function renderSlotsFile(features) {
339
339
  );
340
340
  const calls = withSlots.map((f) => ` register${pascalCase(f.name)}Slots();`);
341
341
  return [
342
- "// vitrine:generated \u2014 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044F \u0441\u043B\u043E\u0442\u043E\u0432 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u044B\u0445 \u0444\u0438\u0447. \u041D\u0435 \u0440\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0432\u0440\u0443\u0447\u043D\u0443\u044E.",
342
+ "// vitrine:generated \u2014 slot registration for installed features. Do not edit by hand.",
343
343
  ...imports,
344
344
  "",
345
345
  "export function registerSlots(): void {",
346
- ...calls.length ? calls : [" // \u043D\u0435\u0442 \u0444\u0438\u0447 \u0441\u043E \u0441\u043B\u043E\u0442\u0430\u043C\u0438"],
346
+ ...calls.length ? calls : [" // no slot features"],
347
347
  "}",
348
348
  ""
349
349
  ].join("\n");
@@ -356,11 +356,11 @@ function renderPaymentsFile(features) {
356
356
  );
357
357
  const calls = withPayment.map((f) => ` register${pascalCase(f.name)}Provider();`);
358
358
  return [
359
- "// vitrine:generated \u2014 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044F \u043F\u043B\u0430\u0442\u0451\u0436\u043D\u044B\u0445 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u043E\u0432 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u044B\u0445 \u0444\u0438\u0447. \u041D\u0435 \u0440\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0432\u0440\u0443\u0447\u043D\u0443\u044E.",
359
+ "// vitrine:generated \u2014 payment-provider registration for installed features. Do not edit by hand.",
360
360
  ...imports,
361
361
  "",
362
362
  "export function registerPayments(): void {",
363
- ...calls.length ? calls : [" // \u043D\u0435\u0442 \u043F\u043B\u0430\u0442\u0451\u0436\u043D\u044B\u0445 \u0444\u0438\u0447"],
363
+ ...calls.length ? calls : [" // no payment features"],
364
364
  "}",
365
365
  ""
366
366
  ].join("\n");
@@ -373,7 +373,7 @@ function renderBlueprintFile(features) {
373
373
  );
374
374
  const calls = withBp.map((f) => ` extend${pascalCase(f.name)}Blueprint(blueprint);`);
375
375
  return [
376
- "// vitrine:generated \u2014 blueprint \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u044B\u0445 \u0444\u0438\u0447. \u041D\u0435 \u0440\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0432\u0440\u0443\u0447\u043D\u0443\u044E.",
376
+ "// vitrine:generated \u2014 blueprint of installed features. Do not edit by hand.",
377
377
  "import { createBlueprint } from '@vitrine-kit/payload-blueprint';",
378
378
  ...imports,
379
379
  "",
@@ -385,9 +385,9 @@ function renderBlueprintFile(features) {
385
385
  ].join("\n");
386
386
  }
387
387
  function renderClaudeFeaturesTable(features) {
388
- if (features.length === 0) return "_\u0424\u0438\u0447\u0438 \u0435\u0449\u0451 \u043D\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u044B._";
388
+ if (features.length === 0) return "_No features installed yet._";
389
389
  const rows = features.slice().sort((a, b) => a.name.localeCompare(b.name)).map((f) => `| \`${f.name}\` | ${f.manifest.title} | ${f.version} |`).join("\n");
390
- return ["| \u0424\u0438\u0447\u0430 | \u041E\u043F\u0438\u0441\u0430\u043D\u0438\u0435 | \u0412\u0435\u0440\u0441\u0438\u044F |", "|---|---|---|", rows].join("\n");
390
+ return ["| Feature | Description | Version |", "|---|---|---|", rows].join("\n");
391
391
  }
392
392
  function mergeEnvExample(existing, features) {
393
393
  const present = parseEnvKeys(existing);
@@ -439,7 +439,7 @@ function renderNeutralTheme() {
439
439
  };
440
440
  const lines = TOKEN_CSS_VARS.map((v) => ` ${v}: ${neutral[v] ?? "initial"};`).join("\n");
441
441
  return [
442
- "/* vitrine: \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F \u0442\u043E\u043A\u0435\u043D\u043E\u0432 \u043A\u043B\u0438\u0435\u043D\u0442\u0430. \u0414\u0438\u0437\u0430\u0439\u043D-\u0448\u0430\u0433 (vitrine design apply) \u043F\u0435\u0440\u0435\u043F\u0438\u0441\u044B\u0432\u0430\u0435\u0442 \u0442\u043E\u043B\u044C\u043A\u043E \u044D\u0442\u043E. */",
442
+ "/* vitrine: client token values. The design step (vitrine design apply) rewrites only this. */",
443
443
  ":root {",
444
444
  lines,
445
445
  "}",
@@ -467,7 +467,7 @@ function resolveOrder(names, registry) {
467
467
  const visit = (name) => {
468
468
  if (seen.has(name)) return;
469
469
  if (!registry.hasFeature(name)) {
470
- throw new Error(`[vitrine] \u0444\u0438\u0447\u0430 "${name}" \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u0430 \u0432 \u0440\u0435\u0435\u0441\u0442\u0440\u0435`);
470
+ throw new Error(`[vitrine] feature "${name}" not found in the registry`);
471
471
  }
472
472
  seen.add(name);
473
473
  const manifest = registry.loadManifest(name);
@@ -480,12 +480,12 @@ function resolveOrder(names, registry) {
480
480
  function validate(name, manifest, project) {
481
481
  if (!manifest.tier.includes(project.lock.tier)) {
482
482
  throw new Error(
483
- `[vitrine] \u0444\u0438\u0447\u0430 "${name}" \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0443\u0440\u043E\u0432\u0435\u043D\u044C "${project.lock.tier}" (\u0442\u043E\u043B\u044C\u043A\u043E ${manifest.tier.join(", ")})`
483
+ `[vitrine] feature "${name}" does not support tier "${project.lock.tier}" (only ${manifest.tier.join(", ")})`
484
484
  );
485
485
  }
486
486
  for (const conflict of manifest.conflicts ?? []) {
487
487
  if (project.lock.features[conflict]) {
488
- throw new Error(`[vitrine] \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442: "${name}" \u043D\u0435\u0441\u043E\u0432\u043C\u0435\u0441\u0442\u0438\u043C\u0430 \u0441 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u043E\u0439 "${conflict}"`);
488
+ throw new Error(`[vitrine] conflict: "${name}" is incompatible with the installed "${conflict}"`);
489
489
  }
490
490
  }
491
491
  }
@@ -494,7 +494,7 @@ function copyFeatureFiles(project, name, manifest, registry, tx) {
494
494
  const originalsBase = join6(projectPaths(project.root).originals, `${name}@${manifest.kitVersion}`);
495
495
  for (const map of manifest.files) {
496
496
  if (!exists(join6(featDir, map.from))) {
497
- throw new Error(`[vitrine] \u0444\u0438\u0447\u0430 "${name}": \u043D\u0435\u0442 \u0438\u0441\u0442\u043E\u0447\u043D\u0438\u043A\u0430 "${map.from}"`);
497
+ throw new Error(`[vitrine] feature "${name}": no source "${map.from}"`);
498
498
  }
499
499
  for (const file of eachFeatureFile(featDir, map)) {
500
500
  const content = readText(file.srcAbs);
@@ -581,16 +581,16 @@ function installFeatures(project, names, registry) {
581
581
  }
582
582
  function removeFeature(project, name, registry) {
583
583
  const removed = project.lock.features[name];
584
- if (!removed) throw new Error(`[vitrine] \u0444\u0438\u0447\u0430 "${name}" \u043D\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0430`);
584
+ if (!removed) throw new Error(`[vitrine] feature "${name}" is not installed`);
585
585
  const manifest = registry.loadManifest(name);
586
586
  if (!manifest.removable) {
587
- throw new Error(`[vitrine] \u0444\u0438\u0447\u0430 "${name}" \u043D\u0435 \u0443\u0434\u0430\u043B\u044F\u0435\u043C\u0430 (removable: false)`);
587
+ throw new Error(`[vitrine] feature "${name}" is not removable (removable: false)`);
588
588
  }
589
589
  for (const other of Object.keys(project.lock.features)) {
590
590
  if (other === name) continue;
591
591
  const deps = registry.loadManifest(other).registryDependencies ?? [];
592
592
  if (deps.includes(name)) {
593
- throw new Error(`[vitrine] \u043D\u0435\u043B\u044C\u0437\u044F \u0443\u0434\u0430\u043B\u0438\u0442\u044C "${name}": \u043E\u0442 \u043D\u0435\u0451 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 "${other}"`);
593
+ throw new Error(`[vitrine] cannot remove "${name}": "${other}" depends on it`);
594
594
  }
595
595
  }
596
596
  const originalsDir = join6(projectPaths(project.root).originals, `${name}@${removed.version}`);
@@ -619,12 +619,12 @@ import { spawnSync } from "child_process";
619
619
  import { existsSync as existsSync6, readdirSync as readdirSync2 } from "fs";
620
620
  import { delimiter, join as join7 } from "path";
621
621
  import { TOKEN_CSS_VARS as TOKEN_CSS_VARS2 } from "@vitrine-kit/contracts";
622
- var DESIGN_HEADING = "## \u0418\u041D\u0421\u0422\u0420\u0423\u041A\u0426\u0418\u042F: \u043F\u0440\u0438\u043C\u0435\u043D\u0438\u0442\u044C \u0434\u0438\u0437\u0430\u0439\u043D \u0438\u0437 /design";
622
+ var DESIGN_HEADING = "## INSTRUCTION: apply the design from /design";
623
623
  function findClaudeBin(explicit) {
624
624
  const pinned = explicit ?? process.env.VITRINE_CLAUDE_BIN;
625
625
  if (pinned) {
626
626
  if (existsSync6(pinned)) return pinned;
627
- throw new Error(`[vitrine] Claude Code \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D \u043F\u043E \u043F\u0443\u0442\u0438 "${pinned}"`);
627
+ throw new Error(`[vitrine] Claude Code not found at path "${pinned}"`);
628
628
  }
629
629
  const names = process.platform === "win32" ? ["claude.cmd", "claude.exe", "claude.ps1", "claude"] : ["claude"];
630
630
  for (const dir of (process.env.PATH ?? "").split(delimiter).filter(Boolean)) {
@@ -634,7 +634,7 @@ function findClaudeBin(explicit) {
634
634
  }
635
635
  }
636
636
  throw new Error(
637
- "[vitrine] Claude Code CLI \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D \u0432 PATH. \u0423\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u0435 \u0435\u0433\u043E (npm i -g @anthropic-ai/claude-code) \u0438\u043B\u0438 \u0443\u043A\u0430\u0436\u0438\u0442\u0435 \u043F\u0443\u0442\u044C \u0447\u0435\u0440\u0435\u0437 --bin / VITRINE_CLAUDE_BIN. design apply \u2014 \u043E\u0431\u0451\u0440\u0442\u043A\u0430 \u043D\u0430\u0434 Claude Code, \u0441\u0432\u043E\u0435\u0433\u043E \u043A\u043B\u044E\u0447\u0430 Anthropic \u043D\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442."
637
+ "[vitrine] Claude Code CLI not found in PATH. Install it (npm i -g @anthropic-ai/claude-code) or pass the path via --bin / VITRINE_CLAUDE_BIN. design apply is a wrapper over Claude Code; it needs no Anthropic key of its own."
638
638
  );
639
639
  }
640
640
  function designHasInput(root) {
@@ -655,14 +655,14 @@ function buildDesignPrompt(project) {
655
655
  return [
656
656
  instruction,
657
657
  "",
658
- "\u041A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 \u043F\u0440\u0438\u043C\u0435\u043D\u0435\u043D\u0438\u044F:",
659
- "- \u0418\u0441\u0442\u043E\u0447\u043D\u0438\u043A \u0434\u0438\u0437\u0430\u0439\u043D\u0430: \u043F\u0430\u043F\u043A\u0430 `design/` (\u044D\u043A\u0441\u043F\u043E\u0440\u0442 \u0438\u0437 Claude Design).",
660
- "- \u0415\u0434\u0438\u043D\u0441\u0442\u0432\u0435\u043D\u043D\u044B\u0439 \u0440\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u0443\u0435\u043C\u044B\u0439 \u0444\u0430\u0439\u043B: `theme/client.css` \u2014 \u0437\u0430\u0434\u0430\u0439\u0442\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F CSS-\u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0445.",
661
- "- \u0417\u0430\u043C\u043A\u043D\u0443\u0442\u044B\u0439 \u043D\u0430\u0431\u043E\u0440 \u0438\u043C\u0451\u043D \u0442\u043E\u043A\u0435\u043D\u043E\u0432 (\u0434\u0440\u0443\u0433\u0438\u0445 \u043D\u0435 \u0432\u0432\u043E\u0434\u0438\u0442\u044C):",
658
+ "Application context:",
659
+ "- Design source: the `design/` folder (export from Claude Design).",
660
+ "- The only editable file: `theme/client.css` \u2014 set the CSS variable values.",
661
+ "- Closed set of token names (do not introduce others):",
662
662
  tokens,
663
663
  "",
664
- "\u041D\u0415 \u0440\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C: \u043A\u043E\u043C\u043F\u043E\u043D\u0435\u043D\u0442\u044B, \u0430\u0434\u0430\u043F\u0442\u0435\u0440\u044B, \u0440\u043E\u0443\u0442\u044B, site.config, lib/*. \u0422\u043E\u043B\u044C\u043A\u043E \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F",
665
- "\u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0445 \u0432 theme/client.css. \u0418\u0434\u0435\u043C\u043F\u043E\u0442\u0435\u043D\u0442\u043D\u043E\u0441\u0442\u044C: \u043F\u043E\u0432\u0442\u043E\u0440\u043D\u044B\u0439 \u043F\u0440\u043E\u0433\u043E\u043D \u043D\u0435 \u043D\u0430\u043A\u0430\u043F\u043B\u0438\u0432\u0430\u0435\u0442 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F."
664
+ "Do NOT edit: components, adapters, routes, site.config, lib/*. Only the variable",
665
+ "values in theme/client.css. Idempotency: re-running does not accumulate changes."
666
666
  ].join("\n");
667
667
  }
668
668
  var defaultRunner = ({ bin, args, cwd }) => {
@@ -679,12 +679,12 @@ function planDesignApply(project, opts = {}) {
679
679
  function designApply(project, opts = {}, runner = defaultRunner) {
680
680
  if (!designHasInput(project.root)) {
681
681
  throw new Error(
682
- "[vitrine] \u043F\u0430\u043F\u043A\u0430 design/ \u043F\u0443\u0441\u0442\u0430 \u2014 \u043F\u043E\u043B\u043E\u0436\u0438\u0442\u0435 \u044D\u043A\u0441\u043F\u043E\u0440\u0442 \u0438\u0437 Claude Design \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u0435."
682
+ "[vitrine] the design/ folder is empty \u2014 add an export from Claude Design and retry."
683
683
  );
684
684
  }
685
685
  const cmd = planDesignApply(project, opts);
686
686
  if (opts.dryRun) {
687
- console.log(`[vitrine] dry-run: ${cmd.bin} -p <\u043F\u0440\u043E\u043C\u043F\u0442 ${cmd.prompt.length} \u0441\u0438\u043C\u0432.> --permission-mode acceptEdits`);
687
+ console.log(`[vitrine] dry-run: ${cmd.bin} -p <prompt ${cmd.prompt.length} chars> --permission-mode acceptEdits`);
688
688
  return 0;
689
689
  }
690
690
  return runner(cmd);
@@ -704,21 +704,21 @@ function runDoctor(project, registry) {
704
704
  const paymentsText = exists(paths.payments) ? readText(paths.payments) : "";
705
705
  for (const core of ["@vitrine-kit/contracts", "@vitrine-kit/core"]) {
706
706
  if (!deps[core]) {
707
- add({ severity: "error", scope: "packages", message: `\u043D\u0435\u0442 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438 ${core}`, fix: "\u0434\u043E\u0431\u0430\u0432\u044C\u0442\u0435 \u0432 package.json" });
707
+ add({ severity: "error", scope: "packages", message: `missing dependency ${core}`, fix: "add it to package.json" });
708
708
  }
709
709
  }
710
- if (exists(paths.claude) && !readText(paths.claude).includes("\u0418\u041D\u0421\u0422\u0420\u0423\u041A\u0426\u0418\u042F: \u043F\u0440\u0438\u043C\u0435\u043D\u0438\u0442\u044C \u0434\u0438\u0437\u0430\u0439\u043D")) {
710
+ if (exists(paths.claude) && !readText(paths.claude).includes("INSTRUCTION: apply the design")) {
711
711
  add({
712
712
  severity: "warn",
713
713
  scope: "design",
714
- message: "\u0432 CLAUDE.md \u043D\u0435\u0442 \u0431\u043B\u043E\u043A\u0430 \u0434\u0438\u0437\u0430\u0439\u043D-\u0438\u043D\u0441\u0442\u0440\u0443\u043A\u0446\u0438\u0438",
715
- fix: "\u043E\u0431\u043D\u043E\u0432\u0438\u0442\u0435 CLAUDE.md (kit update \u043F\u0440\u0438\u043D\u043E\u0441\u0438\u0442 \u0441\u0432\u0435\u0436\u0443\u044E \u0438\u043D\u0441\u0442\u0440\u0443\u043A\u0446\u0438\u044E)"
714
+ message: "CLAUDE.md has no design-instruction block",
715
+ fix: "update CLAUDE.md (kit update brings a fresh instruction)"
716
716
  });
717
717
  }
718
718
  for (const [name, pin] of Object.entries(project.lock.features)) {
719
719
  const scope = `feature:${name}`;
720
720
  if (!registry.hasFeature(name)) {
721
- add({ severity: "error", scope, message: `\u0444\u0438\u0447\u0430 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u0430 \u0432 \u0440\u0435\u0435\u0441\u0442\u0440\u0435`, fix: "vitrine kit update" });
721
+ add({ severity: "error", scope, message: `feature not found in the registry`, fix: "vitrine kit update" });
722
722
  continue;
723
723
  }
724
724
  const manifest = registry.loadManifest(name);
@@ -726,7 +726,7 @@ function runDoctor(project, registry) {
726
726
  add({
727
727
  severity: "warn",
728
728
  scope,
729
- message: `\u0432\u0435\u0440\u0441\u0438\u044F \u0432 \u0440\u0435\u043F\u043E ${pin.version}, \u0440\u0435\u0435\u0441\u0442\u0440 \u043F\u0440\u0435\u0434\u043B\u0430\u0433\u0430\u0435\u0442 ${manifest.kitVersion}`,
729
+ message: `repo version ${pin.version}, registry offers ${manifest.kitVersion}`,
730
730
  fix: `vitrine update ${name}`
731
731
  });
732
732
  }
@@ -737,8 +737,8 @@ function runDoctor(project, registry) {
737
737
  add({
738
738
  severity: "error",
739
739
  scope,
740
- message: `\u043D\u0435\u0442 \u0444\u0430\u0439\u043B\u0430 "${file.toRel}"`,
741
- fix: `vitrine add ${name} (\u043F\u0435\u0440\u0435\u0443\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442)`
740
+ message: `missing file "${file.toRel}"`,
741
+ fix: `vitrine add ${name} (reinstalls)`
742
742
  });
743
743
  }
744
744
  }
@@ -748,33 +748,33 @@ function runDoctor(project, registry) {
748
748
  add({
749
749
  severity: e.required ? "error" : "warn",
750
750
  scope,
751
- message: `\u043D\u0435\u0442 \u043A\u043B\u044E\u0447\u0430 env "${e.key}"${e.required ? " (\u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D)" : ""}`,
752
- fix: "\u0434\u043E\u0431\u0430\u0432\u044C\u0442\u0435 \u0432 .env.example/.env"
751
+ message: `missing env key "${e.key}"${e.required ? " (required)" : ""}`,
752
+ fix: "add it to .env.example/.env"
753
753
  });
754
754
  }
755
755
  }
756
756
  const need = [...Object.keys(manifest.corePackages ?? {}), ...(manifest.npm ?? []).map((s) => parseNpmSpec(s).name)];
757
757
  for (const dep of need) {
758
758
  if (!deps[dep]) {
759
- add({ severity: "error", scope, message: `\u043D\u0435\u0442 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438 ${dep}`, fix: `vitrine add ${name} (\u0434\u043E\u043C\u0435\u0440\u0436\u0438\u0442 deps)` });
759
+ add({ severity: "error", scope, message: `missing dependency ${dep}`, fix: `vitrine add ${name} (merges deps)` });
760
760
  }
761
761
  }
762
762
  if ((manifest.slots?.length ?? 0) > 0) {
763
763
  const fn = `register${pascalCase(name)}Slots`;
764
764
  if (!slotsText.includes(fn)) {
765
- add({ severity: "error", scope, message: `lib/slots.ts \u043D\u0435 \u0432\u044B\u0437\u044B\u0432\u0430\u0435\u0442 ${fn}`, fix: `vitrine add ${name} (\u0440\u0435\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0435\u0442 slots)` });
765
+ add({ severity: "error", scope, message: `lib/slots.ts does not call ${fn}`, fix: `vitrine add ${name} (regenerates slots)` });
766
766
  }
767
767
  }
768
768
  if (!configText.includes(`"${name}": true`)) {
769
- add({ severity: "warn", scope, message: `\u0432 site.config \u043D\u0435\u0442 \u0444\u043B\u0430\u0433\u0430 features.${name}`, fix: `vitrine add ${name} (\u0440\u0435\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0435\u0442 \u0444\u043B\u0430\u0433\u0438)` });
769
+ add({ severity: "warn", scope, message: `site.config has no features.${name} flag`, fix: `vitrine add ${name} (regenerates flags)` });
770
770
  }
771
771
  if (manifest.payment) {
772
772
  const fn = `register${pascalCase(name)}Provider`;
773
773
  if (!paymentsText.includes(fn)) {
774
- add({ severity: "error", scope, message: `lib/payments.ts \u043D\u0435 \u0432\u044B\u0437\u044B\u0432\u0430\u0435\u0442 ${fn}`, fix: `vitrine add ${name} (\u0440\u0435\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0435\u0442 payments)` });
774
+ add({ severity: "error", scope, message: `lib/payments.ts does not call ${fn}`, fix: `vitrine add ${name} (regenerates payments)` });
775
775
  }
776
776
  if (!configText.includes(`payments: ${JSON.stringify(manifest.payment.provider)}`)) {
777
- add({ severity: "warn", scope, message: `\u0432 site.config integrations.payments \u2260 "${manifest.payment.provider}"`, fix: `vitrine add ${name} (\u0440\u0435\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0435\u0442 integrations)` });
777
+ add({ severity: "warn", scope, message: `site.config integrations.payments \u2260 "${manifest.payment.provider}"`, fix: `vitrine add ${name} (regenerates integrations)` });
778
778
  }
779
779
  }
780
780
  }
@@ -851,8 +851,8 @@ function merge3(base, ours, theirs, labels = {}) {
851
851
  const B = splitLines(base);
852
852
  const O = splitLines(ours);
853
853
  const T = splitLines(theirs);
854
- const ourLabel = labels.ours ?? "ours (\u0440\u0435\u043F\u043E \u043A\u043B\u0438\u0435\u043D\u0442\u0430)";
855
- const theirLabel = labels.theirs ?? "theirs (\u0440\u0435\u0435\u0441\u0442\u0440)";
854
+ const ourLabel = labels.ours ?? "ours (client repo)";
855
+ const theirLabel = labels.theirs ?? "theirs (registry)";
856
856
  if (B.length * Math.max(O.length, T.length) > MAX_LCS_CELLS) {
857
857
  if (ours === theirs || theirs === base) return { text: ours, clean: true, conflicts: 0 };
858
858
  if (ours === base) return { text: theirs, clean: true, conflicts: 0 };
@@ -921,8 +921,8 @@ function merge3(base, ours, theirs, labels = {}) {
921
921
  // src/update.ts
922
922
  function planUpdate(project, name, registry) {
923
923
  const pin = project.lock.features[name];
924
- if (!pin) throw new Error(`[vitrine] \u0444\u0438\u0447\u0430 "${name}" \u043D\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0430`);
925
- if (!registry.hasFeature(name)) throw new Error(`[vitrine] \u0444\u0438\u0447\u0430 "${name}" \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u0430 \u0432 \u0440\u0435\u0435\u0441\u0442\u0440\u0435`);
924
+ if (!pin) throw new Error(`[vitrine] feature "${name}" is not installed`);
925
+ if (!registry.hasFeature(name)) throw new Error(`[vitrine] feature "${name}" not found in the registry`);
926
926
  const manifest = registry.loadManifest(name);
927
927
  const fromVersion = pin.version;
928
928
  const toVersion = manifest.kitVersion;
@@ -987,17 +987,17 @@ function applyUpdate(project, plan, registry) {
987
987
  function renderPlan(plan) {
988
988
  const head = plan.fromVersion === plan.toVersion ? `${plan.feature} @ ${plan.toVersion}` : `${plan.feature} ${plan.fromVersion} \u2192 ${plan.toVersion}`;
989
989
  const lines = plan.files.filter((f) => f.status !== "unchanged").map((f) => {
990
- const mark = f.status === "conflict" ? "\u2717 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442" : f.status === "new" ? "+ \u043D\u043E\u0432\u044B\u0439 " : "~ \u0441\u043B\u0438\u044F\u043D\u0438\u0435";
991
- return ` ${mark} ${f.to}${f.conflicts ? ` (\u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u043E\u0432: ${f.conflicts})` : ""}`;
990
+ const mark = f.status === "conflict" ? "\u2717 conflict" : f.status === "new" ? "+ new " : "~ merge ";
991
+ return ` ${mark} ${f.to}${f.conflicts ? ` (conflicts: ${f.conflicts})` : ""}`;
992
992
  });
993
- return lines.length === 0 ? `${head}: \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439 \u043D\u0435\u0442` : [head, ...lines].join("\n");
993
+ return lines.length === 0 ? `${head}: no changes` : [head, ...lines].join("\n");
994
994
  }
995
995
 
996
996
  // src/commands.ts
997
997
  function requireProject() {
998
998
  const root = findProjectRoot();
999
999
  if (!root) {
1000
- throw new Error("[vitrine] \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D vitrine.json \u2014 \u044D\u0442\u043E \u043D\u0435 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439 \u043A\u043B\u0438\u0435\u043D\u0442\u0430 Vitrine");
1000
+ throw new Error("[vitrine] vitrine.json not found \u2014 not a Vitrine client repository");
1001
1001
  }
1002
1002
  return loadProject(root);
1003
1003
  }
@@ -1160,44 +1160,44 @@ function clientPackageJson(name, backend) {
1160
1160
  function clientEnvExample(backend) {
1161
1161
  if (backend === "vendure") {
1162
1162
  return [
1163
- "# \u041E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u043F\u0440\u043E\u0435\u043A\u0442\u0430.",
1163
+ "# Project environment.",
1164
1164
  "",
1165
- "# \u0411\u0414. \u041F\u0443\u0441\u0442\u043E \u0432 dev \u2014 \u0432\u0441\u0442\u0440\u043E\u0435\u043D\u043D\u044B\u0439 SQLite (.vitrine/vendure.sqlite).",
1165
+ "# DB. Empty in dev \u2014 built-in SQLite (.vitrine/vendure.sqlite).",
1166
1166
  "DATABASE_URL=",
1167
1167
  "",
1168
- "# Vendure Shop API (\u0432\u0438\u0442\u0440\u0438\u043D\u0430 \u2192 \u0441\u0435\u0440\u0432\u0435\u0440).",
1168
+ "# Vendure Shop API (storefront \u2192 server).",
1169
1169
  "VENDURE_SHOP_API_URL=http://localhost:3001/shop-api",
1170
1170
  "",
1171
- "# \u0421\u0443\u043F\u0435\u0440\u0430\u0434\u043C\u0438\u043D Vendure (dev-\u0434\u0435\u0444\u043E\u043B\u0442 superadmin/superadmin; \u0441\u043C\u0435\u043D\u0438\u0442\u0435 \u0434\u043B\u044F \u043F\u0440\u043E\u0434\u0430).",
1171
+ "# Vendure superadmin (dev default superadmin/superadmin; change it for prod).",
1172
1172
  "VENDURE_SUPERADMIN_USERNAME=",
1173
1173
  "VENDURE_SUPERADMIN_PASSWORD=",
1174
1174
  "VENDURE_COOKIE_SECRET=",
1175
1175
  "",
1176
- "# \u0411\u0430\u0437\u043E\u0432\u044B\u0439 URL \u0432\u0438\u0442\u0440\u0438\u043D\u044B.",
1176
+ "# Storefront base URL.",
1177
1177
  "NEXT_PUBLIC_SITE_URL=http://localhost:3000",
1178
1178
  ""
1179
1179
  ].join("\n");
1180
1180
  }
1181
1181
  if (backend !== "payload") {
1182
- return "# \u041E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u043F\u0440\u043E\u0435\u043A\u0442\u0430.\nDATABASE_URL=\n";
1182
+ return "# Project environment.\nDATABASE_URL=\n";
1183
1183
  }
1184
1184
  return [
1185
- "# \u041E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u043F\u0440\u043E\u0435\u043A\u0442\u0430.",
1185
+ "# Project environment.",
1186
1186
  "",
1187
- "# \u0411\u0414. \u0414\u043B\u044F \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E\u0433\u043E dev \u043C\u043E\u0436\u043D\u043E \u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u043F\u0443\u0441\u0442\u044B\u043C \u2014 \u0431\u0443\u0434\u0435\u0442 SQLite-fallback (.vitrine/dev.sqlite).",
1187
+ "# DB. For local dev you can leave it empty \u2014 a SQLite fallback is used (.vitrine/dev.sqlite).",
1188
1188
  "DATABASE_URL=",
1189
1189
  "",
1190
- "# \u0421\u0435\u043A\u0440\u0435\u0442 Payload (\u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D; \u0441\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0439\u0442\u0435 \u0441\u043B\u0443\u0447\u0430\u0439\u043D\u044B\u0439 \u0434\u043B\u044F \u043F\u0440\u043E\u0434\u0430).",
1190
+ "# Payload secret (required; generate a random one for prod).",
1191
1191
  "PAYLOAD_SECRET=",
1192
1192
  "",
1193
- "# Dev-\u0430\u0434\u043C\u0438\u043D (\u0441\u043E\u0437\u0434\u0430\u0451\u0442\u0441\u044F \u0442\u043E\u043B\u044C\u043A\u043E \u0432 dev \u043F\u0440\u0438 \u043F\u0443\u0441\u0442\u043E\u0439 \u0411\u0414; \u043F\u0430\u0440\u043E\u043B\u044C \u043F\u0435\u0447\u0430\u0442\u0430\u0435\u0442\u0441\u044F \u0432 \u043A\u043E\u043D\u0441\u043E\u043B\u044C).",
1193
+ "# Dev admin (created only in dev when the DB is empty; the password is printed to the console).",
1194
1194
  "DEV_ADMIN_EMAIL=",
1195
1195
  "DEV_ADMIN_PASSWORD=",
1196
1196
  "",
1197
- "# \u0411\u0430\u0437\u043E\u0432\u044B\u0439 URL \u0441\u0430\u0439\u0442\u0430 (canonical, OG).",
1197
+ "# Site base URL (canonical, OG).",
1198
1198
  "NEXT_PUBLIC_SITE_URL=http://localhost:3000",
1199
1199
  "",
1200
- "# \u041E\u0442\u043A\u043B\u044E\u0447\u0438\u0442\u044C SQLite-fallback \u0434\u0430\u0436\u0435 \u0432 dev (\u043B\u043E\u0432\u0438\u0442\u044C \u043E\u043F\u0435\u0447\u0430\u0442\u043A\u0438 \u043A\u043E\u043D\u0444\u0438\u0433\u0430):",
1200
+ "# Disable the SQLite fallback even in dev (to catch config typos):",
1201
1201
  "# VITRINE_DB_STRICT=1",
1202
1202
  ""
1203
1203
  ].join("\n");
@@ -1207,13 +1207,13 @@ function clientReadme(name, backend, tier) {
1207
1207
  "```bash",
1208
1208
  "pnpm install",
1209
1209
  "cp .env.example .env",
1210
- "pnpm vendure # Vendure-\u0441\u0435\u0440\u0432\u0435\u0440 (Shop API \u043D\u0430 :3001) \u2014 \u0432 \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u043E\u043C \u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B\u0435",
1211
- "pnpm dev # \u0432\u0438\u0442\u0440\u0438\u043D\u0430 \u043D\u0430 :3000",
1210
+ "pnpm vendure # Vendure server (Shop API on :3001) \u2014 in a separate terminal",
1211
+ "pnpm dev # storefront on :3000",
1212
1212
  "```",
1213
1213
  "",
1214
- "\u0411\u0435\u0437 Postgres dev \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u0442 \u0432\u0441\u0442\u0440\u043E\u0435\u043D\u043D\u044B\u0439 SQLite (`.vitrine/vendure.sqlite`) \u0438",
1215
- "populate-\u0441\u0438\u0434. \u0421\u0443\u043F\u0435\u0440\u0430\u0434\u043C\u0438\u043D \u2014 \u0438\u0437 `VENDURE_SUPERADMIN_*` (dev-\u0434\u0435\u0444\u043E\u043B\u0442",
1216
- "superadmin/superadmin; \u0441\u043C\u0435\u043D\u0438\u0442\u0435 \u0434\u043B\u044F \u043F\u0440\u043E\u0434\u0430)."
1214
+ "Without Postgres, dev starts a built-in SQLite (`.vitrine/vendure.sqlite`) and",
1215
+ "the populate seed. The superadmin comes from `VENDURE_SUPERADMIN_*` (dev default",
1216
+ "superadmin/superadmin; change it for prod)."
1217
1217
  ].join("\n") : [
1218
1218
  "```bash",
1219
1219
  "pnpm install",
@@ -1221,69 +1221,69 @@ function clientReadme(name, backend, tier) {
1221
1221
  "pnpm dev",
1222
1222
  "```",
1223
1223
  "",
1224
- "- \u0412\u0438\u0442\u0440\u0438\u043D\u0430: http://localhost:3000",
1225
- "- \u0410\u0434\u043C\u0438\u043D\u043A\u0430: http://localhost:3000/admin",
1224
+ "- Storefront: http://localhost:3000",
1225
+ "- Admin: http://localhost:3000/admin",
1226
1226
  "",
1227
- "\u0411\u0435\u0437 Postgres dev \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u0442 \u0432\u0441\u0442\u0440\u043E\u0435\u043D\u043D\u044B\u0439 SQLite (`.vitrine/dev.sqlite`), \u0437\u0430\u043F\u043E\u043B\u043D\u044F\u0435\u0442",
1228
- "\u0434\u0435\u043C\u043E-\u043A\u0430\u0442\u0430\u043B\u043E\u0433 (5 \u0442\u043E\u0432\u0430\u0440\u043E\u0432, 2 \u043A\u0430\u0442\u0435\u0433\u043E\u0440\u0438\u0438) \u0438 \u0437\u0430\u0432\u043E\u0434\u0438\u0442 dev-\u0430\u0434\u043C\u0438\u043D\u0430 (\u043B\u043E\u0433\u0438\u043D/\u043F\u0430\u0440\u043E\u043B\u044C \u043F\u0435\u0447\u0430\u0442\u0430\u044E\u0442\u0441\u044F",
1229
- "\u0432 \u043A\u043E\u043D\u0441\u043E\u043B\u044C \u043E\u0434\u0438\u043D \u0440\u0430\u0437). \u041E\u0442\u043A\u043B\u044E\u0447\u0438\u0442\u044C fallback \u0438 \u0432 dev \u2014 `VITRINE_DB_STRICT=1`."
1227
+ "Without Postgres, dev starts a built-in SQLite (`.vitrine/dev.sqlite`), seeds a",
1228
+ "demo catalog (5 products, 2 categories) and creates a dev admin (login/password printed",
1229
+ "to the console once). Disable the fallback in dev too with `VITRINE_DB_STRICT=1`."
1230
1230
  ].join("\n");
1231
- const deploySecret = backend === "vendure" ? "export VENDURE_COOKIE_SECRET=... # \u0441\u0435\u043A\u0440\u0435\u0442 cookie Vendure" : "export PAYLOAD_SECRET=... # \u0441\u043B\u0443\u0447\u0430\u0439\u043D\u044B\u0439 \u0441\u0435\u043A\u0440\u0435\u0442 Payload";
1231
+ const deploySecret = backend === "vendure" ? "export VENDURE_COOKIE_SECRET=... # Vendure cookie secret" : "export PAYLOAD_SECRET=... # random Payload secret";
1232
1232
  return `# ${name}
1233
1233
 
1234
- \u041A\u043B\u0438\u0435\u043D\u0442\u0441\u043A\u0438\u0439 \u043F\u0440\u043E\u0435\u043A\u0442 \u043D\u0430 Vitrine. Backend: \`${backend}\`, \u0443\u0440\u043E\u0432\u0435\u043D\u044C: \`${tier}\`.
1235
- Next.js + Tailwind; \u0444\u0438\u0447\u0438 \u0441\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u043D\u044B \u0438\u0437 \u0440\u0435\u0435\u0441\u0442\u0440\u0430 Vitrine \u2014 \u0432\u044B \u0432\u043B\u0430\u0434\u0435\u0435\u0442\u0435 \u043A\u043E\u0434\u043E\u043C \u0438
1236
- \u0441\u0442\u0438\u043B\u0438\u0437\u0443\u0435\u0442\u0435 \u0435\u0433\u043E \u0442\u043E\u043A\u0435\u043D\u0430\u043C\u0438 (\`theme/client.css\`), \u043D\u0435 \u043C\u0435\u043D\u044F\u044F \u043B\u043E\u0433\u0438\u043A\u0443.
1234
+ A client project on Vitrine. Backend: \`${backend}\`, tier: \`${tier}\`.
1235
+ Next.js + Tailwind; features are copied from the Vitrine registry \u2014 you own the code and
1236
+ style it with tokens (\`theme/client.css\`) without changing the logic.
1237
1237
 
1238
- ## 1. \u041F\u0440\u0435\u0434\u0443\u0441\u043B\u043E\u0432\u0438\u044F
1238
+ ## 1. Prerequisites
1239
1239
 
1240
- Node >= 20 (LTS) \u0438 \`pnpm\`. \u041F\u0430\u043A\u0435\u0442\u044B \`@vitrine-kit/*\` \u043F\u0443\u0431\u043B\u0438\u0447\u043D\u044B \u0432 npm \u2014
1241
- \u0442\u043E\u043A\u0435\u043D \u0434\u043B\u044F \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0438 \u043D\u0435 \u043D\u0443\u0436\u0435\u043D.
1240
+ Node >= 20 (LTS) and \`pnpm\`. The \`@vitrine-kit/*\` packages are public on npm \u2014
1241
+ no token is needed to install them.
1242
1242
 
1243
- ## 2. \u041B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u0437\u0430\u043F\u0443\u0441\u043A (zero-config)
1243
+ ## 2. Local run (zero-config)
1244
1244
 
1245
1245
  ${run}
1246
1246
 
1247
- ## 3. \u041F\u0440\u0438\u043C\u0435\u043D\u0438\u0442\u044C \u0434\u0438\u0437\u0430\u0439\u043D \u043A\u043B\u0438\u0435\u043D\u0442\u0430
1247
+ ## 3. Apply the client design
1248
1248
 
1249
- 1. \u041F\u043E\u043B\u043E\u0436\u0438\u0442\u0435 \u044D\u043A\u0441\u043F\u043E\u0440\u0442 \u0431\u0440\u0435\u043D\u0434\u0430 (Figma export, \u0441\u043A\u0440\u0438\u043D\u0448\u043E\u0442\u044B, \u0430\u0441\u0441\u0435\u0442\u044B) \u0432 \`/design\`.
1250
- 2. \`vitrine design apply\` \u2014 \u0418\u0418 \u0437\u0430\u0434\u0430\u0451\u0442 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F \u0442\u043E\u043A\u0435\u043D\u043E\u0432 \u0432 \`theme/client.css\`
1251
- (\u043B\u043E\u0433\u0438\u043A\u0443/\u0434\u0430\u043D\u043D\u044B\u0435/\u0440\u043E\u0443\u0442\u0438\u043D\u0433/a11y \u043D\u0435 \u0442\u0440\u043E\u0433\u0430\u0435\u0442). \u0428\u0430\u0433 \u0438\u0434\u0435\u043C\u043F\u043E\u0442\u0435\u043D\u0442\u0435\u043D.
1249
+ 1. Put the brand export (Figma export, screenshots, assets) in \`/design\`.
1250
+ 2. \`vitrine design apply\` \u2014 the AI sets token values in \`theme/client.css\`
1251
+ (doesn't touch logic/data/routing/a11y). The step is idempotent.
1252
1252
 
1253
- ## 4. \u0424\u0438\u0447\u0438: \u0434\u043E\u0431\u0430\u0432\u0438\u0442\u044C, \u0443\u0431\u0440\u0430\u0442\u044C, \u043F\u043E\u0441\u043C\u043E\u0442\u0440\u0435\u0442\u044C
1253
+ ## 4. Features: add, remove, view
1254
1254
 
1255
1255
  \`\`\`bash
1256
- vitrine list # \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u044B\u0435 + \u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B\u0435
1257
- vitrine add reviews # \u0441\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0444\u0438\u0447\u0443: \u0444\u043B\u0430\u0433, \u0441\u043B\u043E\u0442\u044B, blueprint, env
1258
- vitrine remove reviews # \u0443\u0431\u0440\u0430\u0442\u044C (\u0435\u0441\u043B\u0438 \u0444\u0438\u0447\u0430 removable)
1259
- vitrine design apply # \u0441\u0442\u0438\u043B\u0438\u0437\u043E\u0432\u0430\u0442\u044C \u043D\u043E\u0432\u0443\u044E \u0444\u0438\u0447\u0443
1256
+ vitrine list # installed + available
1257
+ vitrine add reviews # copy a feature: flag, slots, blueprint, env
1258
+ vitrine remove reviews # remove (if the feature is removable)
1259
+ vitrine design apply # style the new feature
1260
1260
  \`\`\`
1261
1261
 
1262
- \`add\` \u0438\u0434\u0435\u043C\u043F\u043E\u0442\u0435\u043D\u0442\u0435\u043D \u0438 \u0442\u0440\u0430\u043D\u0437\u0430\u043A\u0446\u0438\u043E\u043D\u0435\u043D (\u043E\u0442\u043A\u0430\u0442 \u043F\u0440\u0438 \u043E\u0448\u0438\u0431\u043A\u0435); \u043E\u0440\u0438\u0433\u0438\u043D\u0430\u043B\u044B \u0432\u0435\u0440\u0441\u0438\u0439 \u043F\u0438\u0448\u0443\u0442\u0441\u044F \u0432
1263
- \`.vitrine/originals/\` \u2014 \u043E\u0441\u043D\u043E\u0432\u0430 \u0434\u043B\u044F 3-way merge \u043F\u0440\u0438 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0438.
1262
+ \`add\` is idempotent and transactional (rollback on error); version originals are written to
1263
+ \`.vitrine/originals/\` \u2014 the basis for 3-way merge on update.
1264
1264
 
1265
- ## 5. \u041E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u044F \u0438 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430
1265
+ ## 5. Updates and checks
1266
1266
 
1267
1267
  \`\`\`bash
1268
- vitrine kit update # \u043E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u043A\u044D\u0448 \u0440\u0435\u0435\u0441\u0442\u0440\u0430/\u0448\u0430\u0431\u043B\u043E\u043D\u043E\u0432 \u0441 GitHub
1269
- vitrine diff <feature> # \u043F\u0440\u0435\u0434\u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u044F \u0444\u0438\u0447\u0438
1270
- vitrine update [feature] # 3-way merge \u043D\u043E\u0432\u043E\u0439 \u0432\u0435\u0440\u0441\u0438\u0438 \u0444\u0438\u0447\u0438 (\u0431\u0430\u0437\u0430 = \u0432\u0430\u0448 \u0441\u043D\u0430\u043F\u0448\u043E\u0442)
1271
- vitrine doctor # \u043A\u043E\u043D\u0441\u0438\u0441\u0442\u0435\u043D\u0442\u043D\u043E\u0441\u0442\u044C vitrine.json \u2194 \u0444\u0430\u0439\u043B\u044B \u2194 \u043F\u0430\u043A\u0435\u0442\u044B \u2194 env
1268
+ vitrine kit update # update the local registry/templates cache from GitHub
1269
+ vitrine diff <feature> # preview a feature update
1270
+ vitrine update [feature] # 3-way merge of the new feature version (base = your snapshot)
1271
+ vitrine doctor # consistency: vitrine.json \u2194 files \u2194 packages \u2194 env
1272
1272
  \`\`\`
1273
1273
 
1274
- \u041F\u0430\u043A\u0435\u0442\u044B \`@vitrine-kit/*\` \u0432\u0435\u0440\u0441\u0438\u043E\u043D\u0438\u0440\u0443\u044E\u0442\u0441\u044F \u043D\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E: \u0444\u0438\u043A\u0441 \u0432 \`core\` \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u0442 \u0442\u043E\u043B\u044C\u043A\u043E
1275
- \`@vitrine-kit/core\`, \u0430 \`@vitrine-kit/contracts\` \u043E\u0441\u0442\u0430\u0451\u0442\u0441\u044F \u043D\u0430 \u0441\u0432\u043E\u0435\u0439 \u0441\u0442\u0430\u0431\u0438\u043B\u044C\u043D\u043E\u0439 \u0432\u0435\u0440\u0441\u0438\u0438 \u2014
1276
- \u043E\u0431\u043D\u043E\u0432\u043B\u044F\u0439\u0442\u0435 \u0432\u0435\u0440\u0441\u0438\u0438 \u0432 \`package.json\` \u0442\u043E\u0447\u0435\u0447\u043D\u043E.
1274
+ The \`@vitrine-kit/*\` packages are versioned independently: a fix in \`core\` bumps only
1275
+ \`@vitrine-kit/core\`, while \`@vitrine-kit/contracts\` stays at its stable version \u2014
1276
+ update versions in \`package.json\` selectively.
1277
1277
 
1278
- ## 6. \u0414\u0435\u043F\u043B\u043E\u0439 (VPS + Docker)
1278
+ ## 6. Deploy (VPS + Docker)
1279
1279
 
1280
1280
  \`\`\`bash
1281
1281
  ${deploySecret}
1282
1282
  docker compose up --build
1283
1283
  \`\`\`
1284
1284
 
1285
- Production \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u0440\u0435\u0430\u043B\u044C\u043D\u044B\u0439 \`DATABASE_URL\` \u2014 \u0431\u0435\u0437 \u043D\u0435\u0433\u043E \u0441\u0442\u0430\u0440\u0442 \u043F\u0440\u0435\u0440\u044B\u0432\u0430\u0435\u0442\u0441\u044F
1286
- (SQLite-fallback \u0442\u043E\u043B\u044C\u043A\u043E \u0432 dev).
1285
+ Production requires a real \`DATABASE_URL\` \u2014 without it the start aborts
1286
+ (the SQLite fallback is dev-only).
1287
1287
  `;
1288
1288
  }
1289
1289
  function scaffoldBase(opts) {
@@ -1320,7 +1320,7 @@ export const siteConfig: SiteConfig = {
1320
1320
  // vitrine:integrations:start
1321
1321
  integrations: {},
1322
1322
  // vitrine:integrations:end
1323
- i18n: { defaultLocale: 'ru', locales: ['ru'], currency: 'RUB' },
1323
+ i18n: { defaultLocale: 'en', locales: ['en'], currency: 'USD' },
1324
1324
  };
1325
1325
 
1326
1326
  export default siteConfig;
@@ -1330,67 +1330,67 @@ export default siteConfig;
1330
1330
  join11(root, "CLAUDE.md"),
1331
1331
  `# ${name}
1332
1332
 
1333
- \u041F\u0440\u043E\u0435\u043A\u0442 \u043D\u0430 Vitrine. Backend: \`${backend}\`, \u0443\u0440\u043E\u0432\u0435\u043D\u044C: \`${tier}\`.
1334
- \u042D\u0442\u043E\u0442 \u0444\u0430\u0439\u043B \u2014 \u043E\u043F\u0435\u0440\u0430\u0446\u0438\u043E\u043D\u043D\u044B\u0439 \u0433\u0430\u0439\u0434 \u0434\u043B\u044F \u0418\u0418-\u0430\u0433\u0435\u043D\u0442\u0430 (Claude Code) \u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043E\u0442\u0447\u0438\u043A\u0430. \u0412\u0441\u0435 \u043E\u043F\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u043E
1335
- \u0441\u0442\u0430\u0440\u0442\u0435\u0440-\u043A\u0438\u0442\u043E\u043C \u0432\u044B\u043F\u043E\u043B\u043D\u044F\u044E\u0442\u0441\u044F \u0447\u0435\u0440\u0435\u0437 CLI \`vitrine\`; \u0433\u043E\u0442\u043E\u0432\u044B\u0435 \u043F\u043E\u0442\u043E\u043A\u0438 \u2014 \u0441\u043B\u044D\u0448-\u043A\u043E\u043C\u0430\u043D\u0434\u0430\u043C\u0438 \u0432 \`.claude/commands/\`.
1333
+ A Vitrine project. Backend: \`${backend}\`, tier: \`${tier}\`.
1334
+ This file is the operational guide for the AI agent (Claude Code) and the developer. All
1335
+ starter-kit operations go through the \`vitrine\` CLI; ready-made flows are the slash commands in \`.claude/commands/\`.
1336
1336
 
1337
- ## \u0423\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u044B\u0435 \u0444\u0438\u0447\u0438
1337
+ ## Installed features
1338
1338
  <!-- vitrine:features:start -->
1339
- _\u0424\u0438\u0447\u0438 \u0435\u0449\u0451 \u043D\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u044B._
1339
+ _No features installed yet._
1340
1340
  <!-- vitrine:features:end -->
1341
1341
 
1342
- ## \u041A\u043E\u043C\u0430\u043D\u0434\u044B vitrine CLI
1342
+ ## vitrine CLI commands
1343
1343
 
1344
- | \u041A\u043E\u043C\u0430\u043D\u0434\u0430 | \u041D\u0430\u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 | \u041A\u043E\u0433\u0434\u0430 \u0437\u0432\u0430\u0442\u044C | \u0424\u043B\u0430\u0433\u0438 |
1344
+ | Command | Purpose | When to use | Flags |
1345
1345
  |---|---|---|---|
1346
- | \`vitrine list\` | \u0423\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u044B\u0435 \u0438 \u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B\u0435 \u0444\u0438\u0447\u0438 | \u043F\u0435\u0440\u0435\u0434 \u0434\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u0438\u0435\u043C \u0444\u0438\u0447\u0438 | \u2014 |
1347
- | \`vitrine add <features\u2026>\` | \u0421\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0444\u0438\u0447\u0443(\u0438): \u0444\u0430\u0439\u043B\u044B, \u0444\u043B\u0430\u0433, \u0441\u043B\u043E\u0442\u044B, blueprint, env, deps | \xAB\u0434\u043E\u0431\u0430\u0432\u044C \u0444\u0438\u0447\u0443 X\xBB | \`--registry\` |
1348
- | \`vitrine remove <feature>\` | \u0423\u0431\u0440\u0430\u0442\u044C \u0444\u0438\u0447\u0443 (\u0435\u0441\u043B\u0438 \`removable\`) | \xAB\u0443\u0431\u0435\u0440\u0438 \u0444\u0438\u0447\u0443 X\xBB | \`--registry\` |
1349
- | \`vitrine update [features\u2026]\` | \u041E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0444\u0438\u0447\u0438 3-way merge (\u0431\u0435\u0437 \u0430\u0440\u0433\u0443\u043C\u0435\u043D\u0442\u043E\u0432 \u2014 \u0432\u0441\u0435) | \u043F\u043E\u0441\u043B\u0435 \`kit update\` | \`--dry-run\`, \`--registry\` |
1350
- | \`vitrine diff <feature>\` | \u041F\u0440\u0435\u0434\u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u044F (\u0431\u0435\u0437 \u0437\u0430\u043F\u0438\u0441\u0438) | \u043F\u0435\u0440\u0435\u0434 \`update\` | \`--registry\` |
1351
- | \`vitrine doctor\` | \u041A\u043E\u043D\u0441\u0438\u0441\u0442\u0435\u043D\u0442\u043D\u043E\u0441\u0442\u044C: \`vitrine.json\` \u2194 \u0444\u0430\u0439\u043B\u044B \u2194 \u043F\u0430\u043A\u0435\u0442\u044B \u2194 env | \u043F\u043E\u0441\u043B\u0435 \u043F\u0440\u0430\u0432\u043E\u043A, \u043F\u0440\u0438 \u0441\u043E\u043C\u043D\u0435\u043D\u0438\u044F\u0445 | \`--registry\` |
1352
- | \`vitrine design apply\` | \u041F\u0440\u0438\u043C\u0435\u043D\u0438\u0442\u044C \u0434\u0438\u0437\u0430\u0439\u043D \u0438\u0437 \`/design\` \u043A \u0442\u043E\u043A\u0435\u043D\u0430\u043C (\u0447\u0435\u0440\u0435\u0437 Claude Code) | \u043F\u043E\u0441\u043B\u0435 \`add\` \u0438\u043B\u0438 \u0441\u043C\u0435\u043D\u044B \u0431\u0440\u0435\u043D\u0434\u0430 | \`--bin\`, \`--dry-run\` |
1353
- | \`vitrine kit update\` | \u041E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u043A\u044D\u0448 \u0440\u0435\u0435\u0441\u0442\u0440\u0430/\u0448\u0430\u0431\u043B\u043E\u043D\u043E\u0432 \u0441 GitHub | \u043F\u0435\u0440\u0435\u0434 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435\u043C \u0444\u0438\u0447 | \`--from\`, \`--version\`, \`--channel\` |
1354
- | \`vitrine kit status\` | \u0412\u0435\u0440\u0441\u0438\u044F \u043A\u044D\u0448\u0430 vs \u043E\u0436\u0438\u0434\u0430\u0435\u043C\u0430\u044F CLI | \u0434\u0438\u0430\u0433\u043D\u043E\u0441\u0442\u0438\u043A\u0430 | \u2014 |
1355
- | \`vitrine self-update\` | \u041E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0441\u0430\u043C CLI | \u0440\u0435\u0434\u043A\u043E | \`--dry-run\` |
1346
+ | \`vitrine list\` | Installed and available features | before adding a feature | \u2014 |
1347
+ | \`vitrine add <features\u2026>\` | Copy feature(s): files, flag, slots, blueprint, env, deps | "add feature X" | \`--registry\` |
1348
+ | \`vitrine remove <feature>\` | Remove a feature (if \`removable\`) | "remove feature X" | \`--registry\` |
1349
+ | \`vitrine update [features\u2026]\` | Update features via 3-way merge (all if no arguments) | after \`kit update\` | \`--dry-run\`, \`--registry\` |
1350
+ | \`vitrine diff <feature>\` | Preview an update (without writing) | before \`update\` | \`--registry\` |
1351
+ | \`vitrine doctor\` | Consistency: \`vitrine.json\` \u2194 files \u2194 packages \u2194 env | after edits, when in doubt | \`--registry\` |
1352
+ | \`vitrine design apply\` | Apply the design from \`/design\` to tokens (via Claude Code) | after \`add\` or a rebrand | \`--bin\`, \`--dry-run\` |
1353
+ | \`vitrine kit update\` | Update the local registry/templates cache from GitHub | before updating features | \`--from\`, \`--version\`, \`--channel\` |
1354
+ | \`vitrine kit status\` | Cache version vs the CLI's expected one | diagnostics | \u2014 |
1355
+ | \`vitrine self-update\` | Update the CLI itself | rarely | \`--dry-run\` |
1356
1356
 
1357
- \`init\` \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u0435\u0442\u0441\u044F \u043E\u0434\u0438\u043D \u0440\u0430\u0437 \u043F\u0440\u0438 \u0441\u043E\u0437\u0434\u0430\u043D\u0438\u0438 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F (\u0432\u0438\u0437\u0430\u0440\u0434 \`vitrine init\`). \`add\`/\`update\`
1358
- \u0438\u0434\u0435\u043C\u043F\u043E\u0442\u0435\u043D\u0442\u043D\u044B \u0438 \u0442\u0440\u0430\u043D\u0437\u0430\u043A\u0446\u0438\u043E\u043D\u043D\u044B (\u043E\u0442\u043A\u0430\u0442 \u043F\u0440\u0438 \u043E\u0448\u0438\u0431\u043A\u0435); \u043E\u0440\u0438\u0433\u0438\u043D\u0430\u043B\u044B \u0432\u0435\u0440\u0441\u0438\u0439 \u043F\u0438\u0448\u0443\u0442\u0441\u044F \u0432 \`.vitrine/originals/\`
1359
- \u2014 \u0431\u0430\u0437\u0430 \u0434\u043B\u044F 3-way merge.
1357
+ \`init\` runs once when the repository is created (the \`vitrine init\` wizard). \`add\`/\`update\`
1358
+ are idempotent and transactional (rollback on error); version originals are written to \`.vitrine/originals/\`
1359
+ \u2014 the base for 3-way merge.
1360
1360
 
1361
- ## \u0422\u0438\u043F\u043E\u0432\u044B\u0435 \u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0438
1361
+ ## Common scenarios
1362
1362
 
1363
- - **\u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430 \u043F\u0440\u043E\u0435\u043A\u0442\u0430** \u2192 \`/setup\`: \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438, GitHub PAT, \`.env\`, \u0437\u0430\u043F\u0443\u0441\u043A dev-\u0441\u0435\u0440\u0432\u0435\u0440\u0430.
1364
- - **\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0438 \u0441\u0442\u0438\u043B\u0438\u0437\u043E\u0432\u0430\u0442\u044C \u0444\u0438\u0447\u0443** \u2192 \`/add-feature <\u0438\u043C\u044F>\`: \`list\` \u2192 \`add\` \u2192 \`design apply\` \u2192 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430.
1365
- - **\u041F\u0440\u0438\u043C\u0435\u043D\u0438\u0442\u044C/\u043E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0434\u0438\u0437\u0430\u0439\u043D** \u2192 \`/design\`: \u043F\u043E\u043B\u043E\u0436\u0438\u0442\u044C \u044D\u043A\u0441\u043F\u043E\u0440\u0442 \u0432 \`/design\`, \`design apply\`.
1366
- - **\u041E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0444\u0438\u0447\u0438** \u2192 \`/update\`: \`kit update\` \u2192 \`diff\` \u2192 \`update\` \u2192 \u0440\u0430\u0437\u0440\u0443\u043B\u0438\u0442\u044C \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u044B \u2192 \`doctor\`.
1367
- - **\u041F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u043A\u043E\u043D\u0441\u0438\u0441\u0442\u0435\u043D\u0442\u043D\u043E\u0441\u0442\u044C** \u2192 \`/doctor\`.
1363
+ - **Project setup** \u2192 \`/setup\`: dependencies, \`.env\`, start the dev server.
1364
+ - **Add and style a feature** \u2192 \`/add-feature <name>\`: \`list\` \u2192 \`add\` \u2192 \`design apply\` \u2192 check.
1365
+ - **Apply/update the design** \u2192 \`/design\`: put the export in \`/design\`, \`design apply\`.
1366
+ - **Update features** \u2192 \`/update\`: \`kit update\` \u2192 \`diff\` \u2192 \`update\` \u2192 resolve conflicts \u2192 \`doctor\`.
1367
+ - **Check consistency** \u2192 \`/doctor\`.
1368
1368
 
1369
- \u041F\u043E\u043B\u043D\u044B\u0439 \u0433\u0430\u0439\u0434 \u0434\u043B\u044F \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 \u2014 \u0432 \`README.md\`.
1369
+ The full human guide is in \`README.md\`.
1370
1370
 
1371
- ## \u0418\u041D\u0421\u0422\u0420\u0423\u041A\u0426\u0418\u042F: \u043F\u0440\u0438\u043C\u0435\u043D\u0438\u0442\u044C \u0434\u0438\u0437\u0430\u0439\u043D \u0438\u0437 /design
1372
- \u0412\u0445\u043E\u0434: \u0432\u0441\u0451 \u0432 \`/design\`.
1373
- \u0417\u0430\u0434\u0430\u0447\u0430: \u0438\u0437\u0432\u043B\u0435\u0447\u044C \u0432\u0438\u0437\u0443\u0430\u043B\u044C\u043D\u044B\u0439 \u044F\u0437\u044B\u043A (\u043F\u0430\u043B\u0438\u0442\u0440\u0430, \u0442\u0438\u043F\u043E\u0433\u0440\u0430\u0444\u0438\u043A\u0430, \u043E\u0442\u0441\u0442\u0443\u043F\u044B, \u0440\u0430\u0434\u0438\u0443\u0441\u044B, \u0442\u0435\u043D\u0438,
1374
- \u0432\u0438\u0434 \u043A\u043E\u043D\u043A\u0440\u0435\u0442\u043D\u044B\u0445 \u043A\u043E\u043C\u043F\u043E\u043D\u0435\u043D\u0442\u043E\u0432) \u0438 \u043F\u0440\u0438\u043C\u0435\u043D\u0438\u0442\u044C \u043A \u043F\u0440\u043E\u0435\u043A\u0442\u0443.
1375
- \u041F\u0440\u0438\u043C\u0435\u043D\u044F\u0442\u044C \u0442\u0430\u043A:
1376
- 1) \u0437\u0430\u0434\u0430\u0442\u044C \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F \u0442\u043E\u043A\u0435\u043D\u043E\u0432 \u0432 \`theme/client.css\` \u2014 \u043E\u0441\u043D\u043E\u0432\u043D\u043E\u0439 \u0440\u044B\u0447\u0430\u0433;
1377
- 2) \u0442\u043E\u043B\u044C\u043A\u043E \u0435\u0441\u043B\u0438 \u0442\u043E\u043A\u0435\u043D \u043D\u0435 \u0432\u044B\u0440\u0430\u0436\u0430\u0435\u0442 \u043D\u0443\u0436\u043D\u043E\u0435 \u2014 \u0434\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u043F\u0440\u0435\u0437\u0435\u043D\u0442\u0430\u0446\u0438\u043E\u043D\u043D\u044B\u0435 \u043A\u043B\u0430\u0441\u0441\u044B
1378
- \u043A\u043E\u043D\u043A\u0440\u0435\u0442\u043D\u043E\u043C\u0443 \u043A\u043E\u043C\u043F\u043E\u043D\u0435\u043D\u0442\u0443, \u041D\u0415 \u043C\u0435\u043D\u044F\u044F \u0435\u0433\u043E \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0443.
1379
- \u041D\u0415 \u043C\u0435\u043D\u044F\u0442\u044C: \u043B\u043E\u0433\u0438\u043A\u0443 \u043A\u043E\u043C\u043F\u043E\u043D\u0435\u043D\u0442\u043E\u0432, \u0432\u044B\u0431\u043E\u0440\u043A\u0443 \u0434\u0430\u043D\u043D\u044B\u0445, \u0432\u044B\u0437\u043E\u0432\u044B \u0430\u0434\u0430\u043F\u0442\u0435\u0440\u0430, \u0440\u043E\u0443\u0442\u0438\u043D\u0433,
1380
- a11y-\u0440\u043E\u043B\u0438/\u043B\u0435\u0439\u0431\u043B\u044B, \u043F\u0443\u0431\u043B\u0438\u0447\u043D\u044B\u0435 \u043F\u0440\u043E\u043F\u0441\u044B. \u0422\u043E\u043A\u0435\u043D\u044B \u2014 \u044D\u0442\u043E \u0438\u043D\u0442\u0435\u0440\u0444\u0435\u0439\u0441.
1381
- \u0415\u0441\u043B\u0438 \u0434\u0438\u0437\u0430\u0439\u043D \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u0438\u043D\u043E\u0439 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u044B \u0441\u0435\u043A\u0446\u0438\u0438 \u2014 \u0441\u043E\u0437\u0434\u0430\u0442\u044C override \u0441\u0435\u043A\u0446\u0438\u0438 \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438
1382
- (\u043A\u043E\u043C\u043F\u043E\u0437\u0438\u0446\u0438\u044F), \u0430 \u043D\u0435 \u043F\u0440\u0430\u0432\u0438\u0442\u044C \u043E\u0431\u0449\u0438\u0439 wireframe.
1383
- \u0428\u0430\u0433 \u0438\u0434\u0435\u043C\u043F\u043E\u0442\u0435\u043D\u0442\u0435\u043D: \u043F\u043E\u0432\u0442\u043E\u0440\u043D\u044B\u0439 \u043F\u0440\u043E\u0433\u043E\u043D \u0441\u0445\u043E\u0434\u0438\u0442\u0441\u044F, \u043D\u0435 \u043D\u0430\u043A\u0430\u043F\u043B\u0438\u0432\u0430\u0435\u0442 \u043C\u0443\u0441\u043E\u0440.
1371
+ ## INSTRUCTION: apply the design from /design
1372
+ Input: everything in \`/design\`.
1373
+ Task: extract the visual language (palette, typography, spacing, radii, shadows,
1374
+ the look of specific components) and apply it to the project.
1375
+ Apply it like this:
1376
+ 1) set token values in \`theme/client.css\` \u2014 the main lever;
1377
+ 2) only if a token can't express what's needed \u2014 add presentational classes
1378
+ to a specific component WITHOUT changing its structure.
1379
+ Do NOT change: component logic, data fetching, adapter calls, routing,
1380
+ a11y roles/labels, public props. Tokens are the interface.
1381
+ If the design requires a different section structure \u2014 create a section override in the repo
1382
+ (composition) rather than editing the shared wireframe.
1383
+ The step is idempotent: re-running converges and doesn't accumulate cruft.
1384
1384
 
1385
- ## \u0413\u0440\u0430\u043D\u0438\u0446\u044B (\u0447\u0442\u043E \u0430\u0433\u0435\u043D\u0442\u0443 \u043D\u0435\u043B\u044C\u0437\u044F \u0442\u0440\u043E\u0433\u0430\u0442\u044C)
1386
- - **\u0413\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0435\u043C\u044B\u0435/\u0443\u043F\u0440\u0430\u0432\u043B\u044F\u0435\u043C\u044B\u0435 \u0444\u0430\u0439\u043B\u044B \u2014 \u0440\u0443\u043A\u0430\u043C\u0438 \u043D\u0435 \u043F\u0440\u0430\u0432\u0438\u0442\u044C** (CLI \u043F\u0435\u0440\u0435\u0437\u0430\u0442\u0440\u0451\u0442 \u0438\u0437 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u044F):
1387
- \`lib/slots.ts\`, \`lib/payments.ts\`, \`lib/blueprint.ts\`, \u0443\u043F\u0440\u0430\u0432\u043B\u044F\u0435\u043C\u044B\u0435 \u0440\u0435\u0433\u0438\u043E\u043D\u044B \`site.config.ts\`
1388
- (\`features\`/\`integrations\`), \`vitrine.json\`, \u0442\u0430\u0431\u043B\u0438\u0446\u0430 \u0444\u0438\u0447 \u0432 \u044D\u0442\u043E\u043C \`CLAUDE.md\`, \`.env*\`.
1389
- \u041D\u0430\u0431\u043E\u0440 \u0444\u0438\u0447/\u0438\u043D\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0439 \u043C\u0435\u043D\u044F\u0435\u0442\u0441\u044F \u0447\u0435\u0440\u0435\u0437 \`vitrine add/remove\`, \u0430 \u043D\u0435 \u043F\u0440\u0430\u0432\u043A\u043E\u0439 \u0444\u0430\u0439\u043B\u043E\u0432.
1390
- - **\u0414\u0438\u0437\u0430\u0439\u043D \u2014 \u0442\u043E\u043B\u044C\u043A\u043E \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F \u0442\u043E\u043A\u0435\u043D\u043E\u0432** \u0432 \`theme/client.css\` (\u0447\u0435\u0440\u0435\u0437 \`vitrine design apply\`):
1391
- \u043B\u043E\u0433\u0438\u043A\u0443/\u0434\u0430\u043D\u043D\u044B\u0435/\u0440\u043E\u0443\u0442\u0438\u043D\u0433/a11y/\u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0443 \u043A\u043E\u043C\u043F\u043E\u043D\u0435\u043D\u0442\u043E\u0432 \u043D\u0435 \u043C\u0435\u043D\u044F\u0442\u044C.
1392
- - **\u041A\u043E\u043D\u0442\u0440\u0430\u043A\u0442\u044B \u0440\u0430\u0441\u0448\u0438\u0440\u044F\u044E\u0442\u0441\u044F \u0430\u0434\u0434\u0438\u0442\u0438\u0432\u043D\u043E** (\`@vitrine-kit/contracts\`): \u043B\u043E\u043C\u0430\u0442\u044C \u0444\u043E\u0440\u043C\u0443 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044E\u0449\u0438\u0445 \u043F\u043E\u043B\u0435\u0439 \u043D\u0435\u043B\u044C\u0437\u044F.
1393
- - **\u041A\u043E\u043C\u043C\u0438\u0442\u044B \u0434\u0435\u043B\u0430\u0435\u0442 \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044C** \u2014 \u043D\u0435 \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u0439 \`git commit\`/\`git push\` \u0431\u0435\u0437 \u044F\u0432\u043D\u043E\u0439 \u043F\u0440\u043E\u0441\u044C\u0431\u044B.
1385
+ ## Boundaries (what the agent must not touch)
1386
+ - **Generated/managed files \u2014 do not edit by hand** (the CLI overwrites them from state):
1387
+ \`lib/slots.ts\`, \`lib/payments.ts\`, \`lib/blueprint.ts\`, the managed regions of \`site.config.ts\`
1388
+ (\`features\`/\`integrations\`), \`vitrine.json\`, the feature table in this \`CLAUDE.md\`, \`.env*\`.
1389
+ The set of features/integrations is changed via \`vitrine add/remove\`, not by editing files.
1390
+ - **Design \u2014 token values only** in \`theme/client.css\` (via \`vitrine design apply\`):
1391
+ don't change logic/data/routing/a11y/component structure.
1392
+ - **Contracts are extended additively** (\`@vitrine-kit/contracts\`): you must not break the shape of existing fields.
1393
+ - **The user makes commits** \u2014 don't run \`git commit\`/\`git push\` without an explicit request.
1394
1394
  `
1395
1395
  );
1396
1396
  writeText(join11(root, "README.md"), clientReadme(name, backend, tier));
@@ -1403,7 +1403,7 @@ a11y-\u0440\u043E\u043B\u0438/\u043B\u0435\u0439\u0431\u043B\u044B, \u043F\u0443
1403
1403
  }
1404
1404
  function initProject(opts) {
1405
1405
  if (exists(opts.root) && readdirSync3(opts.root).length > 0) {
1406
- throw new Error(`[vitrine] \u043A\u0430\u0442\u0430\u043B\u043E\u0433 "${opts.root}" \u043D\u0435 \u043F\u0443\u0441\u0442\u043E\u0439`);
1406
+ throw new Error(`[vitrine] directory "${opts.root}" is not empty`);
1407
1407
  }
1408
1408
  scaffoldBase(opts);
1409
1409
  const project = loadProject(opts.root);
@@ -1422,10 +1422,10 @@ function hasBin(bin) {
1422
1422
  }
1423
1423
  function acquireFromGh(version) {
1424
1424
  if (!hasBin("gh")) {
1425
- throw new Error("[vitrine] \u0434\u043B\u044F \u0441\u0435\u0442\u0435\u0432\u043E\u0433\u043E \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u044F \u043D\u0443\u0436\u0435\u043D gh (GitHub CLI), \u043B\u0438\u0431\u043E \u0443\u043A\u0430\u0436\u0438\u0442\u0435 --from <dir>");
1425
+ throw new Error("[vitrine] network update needs gh (GitHub CLI), or pass --from <dir>");
1426
1426
  }
1427
1427
  if (!hasBin("tar")) {
1428
- throw new Error("[vitrine] \u0434\u043B\u044F \u0440\u0430\u0441\u043F\u0430\u043A\u043E\u0432\u043A\u0438 \u043D\u0443\u0436\u0435\u043D tar, \u043B\u0438\u0431\u043E \u0443\u043A\u0430\u0436\u0438\u0442\u0435 --from <dir>");
1428
+ throw new Error("[vitrine] unpacking needs tar, or pass --from <dir>");
1429
1429
  }
1430
1430
  const tmp = mkdtempSync(join12(tmpdir(), "vitrine-kit-"));
1431
1431
  const dl = spawnSync2(
@@ -1434,15 +1434,15 @@ function acquireFromGh(version) {
1434
1434
  { stdio: "inherit" }
1435
1435
  );
1436
1436
  if (dl.status !== 0) {
1437
- throw new Error("[vitrine] gh release download \u043D\u0435 \u0443\u0434\u0430\u043B\u0441\u044F (\u043F\u0440\u043E\u0432\u0435\u0440\u044C\u0442\u0435 \u0434\u043E\u0441\u0442\u0443\u043F/\u0430\u0432\u0442\u043E\u0440\u0438\u0437\u0430\u0446\u0438\u044E: gh auth status, \u043F\u0440\u0438 \u043D\u0435\u043E\u0431\u0445\u043E\u0434\u0438\u043C\u043E\u0441\u0442\u0438 gh auth login)");
1437
+ throw new Error("[vitrine] gh release download failed (check access/auth: gh auth status, and gh auth login if needed)");
1438
1438
  }
1439
1439
  const tarball = readdirSync4(tmp).find((f) => f.endsWith(".tar.gz"));
1440
- if (!tarball) throw new Error("[vitrine] release-tarball \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D \u043F\u043E\u0441\u043B\u0435 \u0437\u0430\u0433\u0440\u0443\u0437\u043A\u0438");
1440
+ if (!tarball) throw new Error("[vitrine] release tarball not found after download");
1441
1441
  if (spawnSync2("tar", ["-xzf", join12(tmp, tarball), "-C", tmp], { stdio: "inherit" }).status !== 0) {
1442
- throw new Error("[vitrine] \u0440\u0430\u0441\u043F\u0430\u043A\u043E\u0432\u043A\u0430 tarball \u043D\u0435 \u0443\u0434\u0430\u043B\u0430\u0441\u044C");
1442
+ throw new Error("[vitrine] tarball extraction failed");
1443
1443
  }
1444
1444
  const root = readdirSync4(tmp).map((f) => join12(tmp, f)).find((p2) => statSync2(p2).isDirectory() && existsSync8(join12(p2, "registry", "_index.json")));
1445
- if (!root) throw new Error("[vitrine] \u0432 \u0440\u0430\u0441\u043F\u0430\u043A\u043E\u0432\u0430\u043D\u043D\u043E\u043C tarball \u043D\u0435\u0442 registry/_index.json");
1445
+ if (!root) throw new Error("[vitrine] the unpacked tarball has no registry/_index.json");
1446
1446
  return root;
1447
1447
  }
1448
1448
  function kitUpdate(opts = {}) {
@@ -1477,93 +1477,93 @@ function selfUpdate(opts = {}) {
1477
1477
  var pkg = JSON.parse(readFileSync3(new URL("../package.json", import.meta.url), "utf8"));
1478
1478
  var program = new Command();
1479
1479
  program.name("vitrine").description("Vitrine CLI").version(pkg.version);
1480
- program.command("add").description("\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0444\u0438\u0447\u0443(\u0438) \u0432 \u0442\u0435\u043A\u0443\u0449\u0438\u0439 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439").argument("<features...>", "\u0438\u043C\u0435\u043D\u0430 \u0444\u0438\u0447").option("--registry <path>", "\u043F\u0443\u0442\u044C \u043A \u0440\u0435\u0435\u0441\u0442\u0440\u0443").action((features, opts) => {
1480
+ program.command("add").description("Add feature(s) to the current repository").argument("<features...>", "feature names").option("--registry <path>", "path to the registry").action((features, opts) => {
1481
1481
  const res = addFeatures(features, opts.registry);
1482
- console.log(res.installed.length ? `\u0423\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E: ${res.installed.join(", ")}` : "\u0423\u0436\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E \u2014 \u043D\u0435\u0442 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439.");
1482
+ console.log(res.installed.length ? `Installed: ${res.installed.join(", ")}` : "Already installed \u2014 no changes.");
1483
1483
  });
1484
- program.command("remove").description("\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0444\u0438\u0447\u0443 (\u0435\u0441\u043B\u0438 removable)").argument("<feature>", "\u0438\u043C\u044F \u0444\u0438\u0447\u0438").option("--registry <path>", "\u043F\u0443\u0442\u044C \u043A \u0440\u0435\u0435\u0441\u0442\u0440\u0443").action((feature, opts) => {
1484
+ program.command("remove").description("Remove a feature (if removable)").argument("<feature>", "feature name").option("--registry <path>", "path to the registry").action((feature, opts) => {
1485
1485
  removeFeatureCmd(feature, opts.registry);
1486
- console.log(`\u0423\u0434\u0430\u043B\u0435\u043D\u043E: ${feature}`);
1486
+ console.log(`Removed: ${feature}`);
1487
1487
  });
1488
- program.command("update").description("\u041E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u0443\u044E \u0444\u0438\u0447\u0443 (3-way merge); \u0431\u0435\u0437 \u0430\u0440\u0433\u0443\u043C\u0435\u043D\u0442\u043E\u0432 \u2014 \u0432\u0441\u0435").argument("[features...]", "\u0438\u043C\u0435\u043D\u0430 \u0444\u0438\u0447").option("--registry <path>", "\u043F\u0443\u0442\u044C \u043A \u0440\u0435\u0435\u0441\u0442\u0440\u0443").option("--dry-run", "\u043F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u043F\u043B\u0430\u043D \u0431\u0435\u0437 \u0437\u0430\u043F\u0438\u0441\u0438").action((features, opts) => {
1488
+ program.command("update").description("Update an installed feature (3-way merge); all if no arguments").argument("[features...]", "feature names").option("--registry <path>", "path to the registry").option("--dry-run", "show the plan without writing").action((features, opts) => {
1489
1489
  const outcomes = updateFeaturesCmd(features, opts.registry, { dryRun: opts.dryRun });
1490
1490
  let conflicts = 0;
1491
1491
  for (const { plan, applied } of outcomes) {
1492
1492
  console.log(renderPlan(plan));
1493
- if (applied) console.log(" \u2192 \u043F\u0440\u0438\u043C\u0435\u043D\u0435\u043D\u043E");
1493
+ if (applied) console.log(" \u2192 applied");
1494
1494
  conflicts += plan.files.reduce((n, f) => n + f.conflicts, 0);
1495
1495
  }
1496
1496
  if (conflicts > 0) {
1497
1497
  console.log(`
1498
- \u26A0 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u043E\u0432: ${conflicts}. \u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u0435 git-\u043C\u0430\u0440\u043A\u0435\u0440\u044B (<<<<<<< / >>>>>>>) \u0432\u0440\u0443\u0447\u043D\u0443\u044E.`);
1498
+ \u26A0 conflicts: ${conflicts}. Resolve the git markers (<<<<<<< / >>>>>>>) by hand.`);
1499
1499
  }
1500
1500
  });
1501
- program.command("diff").description("\u041F\u0440\u0435\u0434\u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439 update (\u0431\u0435\u0437 \u0437\u0430\u043F\u0438\u0441\u0438)").argument("<feature>", "\u0438\u043C\u044F \u0444\u0438\u0447\u0438").option("--registry <path>", "\u043F\u0443\u0442\u044C \u043A \u0440\u0435\u0435\u0441\u0442\u0440\u0443").action((feature, opts) => {
1501
+ program.command("diff").description("Preview update changes (without writing)").argument("<feature>", "feature name").option("--registry <path>", "path to the registry").action((feature, opts) => {
1502
1502
  console.log(renderPlan(diffFeatureCmd(feature, opts.registry)));
1503
1503
  });
1504
- program.command("list").description("\u0423\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u044B\u0435 \u0438 \u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B\u0435 \u0444\u0438\u0447\u0438").option("--registry <path>", "\u043F\u0443\u0442\u044C \u043A \u0440\u0435\u0435\u0441\u0442\u0440\u0443").action((opts) => {
1504
+ program.command("list").description("Installed and available features").option("--registry <path>", "path to the registry").action((opts) => {
1505
1505
  const { installed, available } = listFeatures(opts.registry);
1506
- console.log("\u0423\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u044B:", installed.join(", ") || "\u2014");
1507
- console.log("\u0414\u043E\u0441\u0442\u0443\u043F\u043D\u044B: ", available.join(", ") || "\u2014");
1506
+ console.log("Installed:", installed.join(", ") || "\u2014");
1507
+ console.log("Available:", available.join(", ") || "\u2014");
1508
1508
  });
1509
- var design = program.command("design").description("\u0418\u0418-\u0448\u0430\u0433 \u0434\u0438\u0437\u0430\u0439\u043D\u0430 (\u043E\u0431\u0451\u0440\u0442\u043A\u0430 \u043D\u0430\u0434 Claude Code)");
1510
- design.command("apply").description("\u041F\u0440\u0438\u043C\u0435\u043D\u0438\u0442\u044C \u0434\u0438\u0437\u0430\u0439\u043D \u0438\u0437 /design \u043A \u0442\u043E\u043A\u0435\u043D\u0430\u043C (\u0447\u0435\u0440\u0435\u0437 Claude Code)").option("--bin <path>", "\u043F\u0443\u0442\u044C \u043A Claude Code (\u0438\u043D\u0430\u0447\u0435 VITRINE_CLAUDE_BIN / PATH)").option("--dry-run", "\u043F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u043A\u043E\u043C\u0430\u043D\u0434\u0443 \u0431\u0435\u0437 \u0437\u0430\u043F\u0443\u0441\u043A\u0430").action((opts) => {
1509
+ var design = program.command("design").description("AI design step (wrapper over Claude Code)");
1510
+ design.command("apply").description("Apply the design from /design to the tokens (via Claude Code)").option("--bin <path>", "path to Claude Code (otherwise VITRINE_CLAUDE_BIN / PATH)").option("--dry-run", "show the command without running").action((opts) => {
1511
1511
  const code = designApplyCmd({ bin: opts.bin, dryRun: opts.dryRun });
1512
1512
  if (code !== 0) process.exit(code);
1513
1513
  });
1514
- var kit = program.command("kit").description("\u041B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0439 (\u043A\u044D\u0448 \u0440\u0435\u0435\u0441\u0442\u0440\u0430 ~/.vitrine)");
1515
- kit.command("update").description("\u041E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u043A\u044D\u0448 \u0440\u0435\u0435\u0441\u0442\u0440\u0430/\u0448\u0430\u0431\u043B\u043E\u043D\u043E\u0432 \u0441 GitHub (\u0438\u043B\u0438 --from <dir>)").option("--from <path>", "\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E\u0435 \u0434\u0435\u0440\u0435\u0432\u043E kit (\u043A\u043B\u043E\u043D / \u0440\u0430\u0441\u043F\u0430\u043A\u043E\u0432\u0430\u043D\u043D\u044B\u0439 tarball) \u0432\u043C\u0435\u0441\u0442\u043E \u0441\u0435\u0442\u0438").option("--version <tag>", "\u043A\u043E\u043D\u043A\u0440\u0435\u0442\u043D\u044B\u0439 \u0440\u0435\u043B\u0438\u0437").option("--channel <channel>", "stable | main", "stable").action((opts) => {
1514
+ var kit = program.command("kit").description("Local tooling (the ~/.vitrine registry cache)");
1515
+ kit.command("update").description("Update the local registry/templates cache from GitHub (or --from <dir>)").option("--from <path>", "local kit tree (clone / unpacked tarball) instead of the network").option("--version <tag>", "a specific release").option("--channel <channel>", "stable | main", "stable").action((opts) => {
1516
1516
  const res = kitUpdate({ from: opts.from, version: opts.version, channel: opts.channel });
1517
- console.log(`\u041A\u044D\u0448 \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D \u0434\u043E kit ${res.kitVersion}.`);
1517
+ console.log(`Cache updated to kit ${res.kitVersion}.`);
1518
1518
  console.log(formatChangelog(res.changelog));
1519
1519
  });
1520
- kit.command("status").description("\u0412\u0435\u0440\u0441\u0438\u044F \u043A\u044D\u0448\u0430 \u0438 \u0447\u0442\u043E \u043D\u043E\u0432\u0435\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u043E\u0433\u043E CLI").action(() => {
1520
+ kit.command("status").description("Cache version and what is newer than the installed CLI").action(() => {
1521
1521
  const s = kitStatus();
1522
1522
  if (!s.cached) {
1523
- console.log('\u041A\u044D\u0448 \u043F\u0443\u0441\u0442. \u0417\u0430\u043F\u0443\u0441\u0442\u0438 "vitrine kit update".');
1523
+ console.log('Cache is empty. Run "vitrine kit update".');
1524
1524
  return;
1525
1525
  }
1526
- console.log(`\u041A\u044D\u0448: kit ${s.kitVersion} (${s.channel}), \u0444\u0438\u0447: ${s.featureCount ?? "\u2014"}, \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D ${s.updatedAt}.`);
1527
- console.log(`CLI \u043E\u0436\u0438\u0434\u0430\u0435\u0442 kit ${s.cliKitVersion}.`);
1526
+ console.log(`Cache: kit ${s.kitVersion} (${s.channel}), features: ${s.featureCount ?? "\u2014"}, updated ${s.updatedAt}.`);
1527
+ console.log(`CLI expects kit ${s.cliKitVersion}.`);
1528
1528
  });
1529
- program.command("self-update").description("\u041E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0441\u0430\u043C CLI (@vitrine-kit/vitrine)").option("--dry-run", "\u043F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u043A\u043E\u043C\u0430\u043D\u0434\u0443 \u0431\u0435\u0437 \u0437\u0430\u043F\u0443\u0441\u043A\u0430").action((opts) => {
1529
+ program.command("self-update").description("Update the CLI itself (@vitrine-kit/vitrine)").option("--dry-run", "show the command without running").action((opts) => {
1530
1530
  const code = selfUpdate({ dryRun: opts.dryRun });
1531
1531
  if (code !== 0) process.exit(code);
1532
1532
  });
1533
- program.command("doctor").description("\u041F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u043A\u043E\u043D\u0441\u0438\u0441\u0442\u0435\u043D\u0442\u043D\u043E\u0441\u0442\u044C: vitrine.json \u2194 \u0444\u0430\u0439\u043B\u044B \u2194 \u043F\u0430\u043A\u0435\u0442\u044B \u2194 env").option("--registry <path>", "\u043F\u0443\u0442\u044C \u043A \u0440\u0435\u0435\u0441\u0442\u0440\u0443").action((opts) => {
1533
+ program.command("doctor").description("Check consistency: vitrine.json \u2194 files \u2194 packages \u2194 env").option("--registry <path>", "path to the registry").action((opts) => {
1534
1534
  const report = doctorCmd(opts.registry);
1535
1535
  if (report.issues.length === 0) {
1536
- console.log("\u2713 \u041F\u0440\u043E\u0431\u043B\u0435\u043C \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E.");
1536
+ console.log("\u2713 No issues found.");
1537
1537
  return;
1538
1538
  }
1539
1539
  for (const i of report.issues) {
1540
- const tag = i.severity === "error" ? "\u041E\u0428\u0418\u0411\u041A\u0410" : "\u043F\u0440\u0435\u0434\u0443\u043F\u0440.";
1540
+ const tag = i.severity === "error" ? "ERROR" : "warn";
1541
1541
  console.log(`[${tag}] ${i.scope}: ${i.message}${i.fix ? ` \u2192 ${i.fix}` : ""}`);
1542
1542
  }
1543
1543
  if (!report.ok) process.exit(1);
1544
1544
  });
1545
- program.command("init").description("\u0421\u043E\u0437\u0434\u0430\u0442\u044C \u043D\u043E\u0432\u044B\u0439 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439 \u043A\u043B\u0438\u0435\u043D\u0442\u0430").argument("[name]", "\u0438\u043C\u044F \u043F\u0440\u043E\u0435\u043A\u0442\u0430").option("--dir <path>", "\u0440\u043E\u0434\u0438\u0442\u0435\u043B\u044C\u0441\u043A\u0438\u0439 \u043A\u0430\u0442\u0430\u043B\u043E\u0433", process.cwd()).option("--tier <tier>", "catalog | simple-store | full-store").option("--backend <backend>", "payload | vendure").option("--features <list>", "\u0441\u043F\u0438\u0441\u043E\u043A \u0447\u0435\u0440\u0435\u0437 \u0437\u0430\u043F\u044F\u0442\u0443\u044E").option("--registry <path>", "\u043F\u0443\u0442\u044C \u043A \u0440\u0435\u0435\u0441\u0442\u0440\u0443").option("--yes", "\u0431\u0435\u0437 \u0438\u043D\u0442\u0435\u0440\u0430\u043A\u0442\u0438\u0432\u043D\u044B\u0445 \u0432\u043E\u043F\u0440\u043E\u0441\u043E\u0432").action(async (nameArg, opts) => {
1545
+ program.command("init").description("Create a new client repository").argument("[name]", "project name").option("--dir <path>", "parent directory", process.cwd()).option("--tier <tier>", "catalog | simple-store | full-store").option("--backend <backend>", "payload | vendure").option("--features <list>", "comma-separated list").option("--registry <path>", "path to the registry").option("--yes", "skip interactive questions").action(async (nameArg, opts) => {
1546
1546
  const registry = createRegistrySource(opts.registry);
1547
1547
  let name = nameArg;
1548
1548
  let tier = opts.tier;
1549
1549
  let features = opts.features ? String(opts.features).split(",") : void 0;
1550
1550
  if (!opts.yes && (!name || !tier)) {
1551
1551
  p.intro("vitrine init");
1552
- if (!name) name = String(await p.text({ message: "\u0418\u043C\u044F \u043F\u0440\u043E\u0435\u043A\u0442\u0430", placeholder: "my-shop" }));
1552
+ if (!name) name = String(await p.text({ message: "Project name", placeholder: "my-shop" }));
1553
1553
  if (!tier) {
1554
1554
  tier = await p.select({
1555
- message: "\u0423\u0440\u043E\u0432\u0435\u043D\u044C",
1555
+ message: "Tier",
1556
1556
  options: [
1557
- { value: "catalog", label: "\u041A\u0430\u0442\u0430\u043B\u043E\u0433" },
1558
- { value: "simple-store", label: "\u041F\u0440\u043E\u0441\u0442\u043E\u0439 \u043C\u0430\u0433\u0430\u0437\u0438\u043D" },
1559
- { value: "full-store", label: "\u041F\u043E\u043B\u043D\u044B\u0439 \u043C\u0430\u0433\u0430\u0437\u0438\u043D" }
1557
+ { value: "catalog", label: "Catalog" },
1558
+ { value: "simple-store", label: "Simple store" },
1559
+ { value: "full-store", label: "Full store" }
1560
1560
  ]
1561
1561
  });
1562
1562
  }
1563
1563
  const suggested = suggestFeatures(tier, registry);
1564
1564
  const baseline = suggested.filter((f) => !PAYMENT_PROVIDER_FEATURES.includes(f));
1565
1565
  const picked = await p.multiselect({
1566
- message: "\u0424\u0438\u0447\u0438",
1566
+ message: "Features",
1567
1567
  options: baseline.map((f) => ({ value: f, label: f })),
1568
1568
  initialValues: baseline,
1569
1569
  required: false
@@ -1574,14 +1574,14 @@ program.command("init").description("\u0421\u043E\u0437\u0434\u0430\u0442\u044C
1574
1574
  const labels = {
1575
1575
  "checkout-stripe": "Stripe",
1576
1576
  "checkout-paddle": "Paddle",
1577
- "checkout-yookassa": "\u042EKassa"
1577
+ "checkout-yookassa": "YooKassa"
1578
1578
  };
1579
1579
  const available = PAYMENT_PROVIDER_FEATURES.filter((f) => registry.hasFeature(f));
1580
1580
  if (available.length > 0) {
1581
1581
  const provider = await p.select({
1582
- message: "\u041F\u043B\u0430\u0442\u0451\u0436\u043D\u044B\u0439 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440",
1582
+ message: "Payment provider",
1583
1583
  options: [
1584
- { value: "none", label: "\u041D\u0435\u0442 (\u0434\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u043F\u043E\u0437\u0436\u0435: vitrine add checkout-<provider>)" },
1584
+ { value: "none", label: "None (add later: vitrine add checkout-<provider>)" },
1585
1585
  ...available.map((f) => ({ value: f, label: labels[f] ?? f }))
1586
1586
  ],
1587
1587
  initialValue: available[0]
@@ -1589,16 +1589,16 @@ program.command("init").description("\u0421\u043E\u0437\u0434\u0430\u0442\u044C
1589
1589
  if (typeof provider === "string" && provider !== "none") features.push(provider);
1590
1590
  }
1591
1591
  }
1592
- p.outro("\u041F\u043E\u0435\u0445\u0430\u043B\u0438");
1592
+ p.outro("Ready");
1593
1593
  }
1594
- if (!name) throw new Error("[vitrine] \u043D\u0443\u0436\u043D\u043E \u0438\u043C\u044F \u043F\u0440\u043E\u0435\u043A\u0442\u0430");
1594
+ if (!name) throw new Error("[vitrine] a project name is required");
1595
1595
  const finalTier = tier ?? "catalog";
1596
1596
  const backend = opts.backend ?? defaultBackend(finalTier);
1597
1597
  const finalFeatures = features ?? suggestFeatures(finalTier, registry, backend);
1598
1598
  const root = resolve6(String(opts.dir ?? process.cwd()), name);
1599
1599
  const res = initProject({ root, name, backend, tier: finalTier, features: finalFeatures, registry });
1600
- console.log(`\u0421\u043E\u0437\u0434\u0430\u043D \u043F\u0440\u043E\u0435\u043A\u0442 "${name}" \u2192 ${root}`);
1601
- console.log(`\u0423\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E: ${res.installed.join(", ") || "\u2014"}`);
1600
+ console.log(`Created project "${name}" \u2192 ${root}`);
1601
+ console.log(`Installed: ${res.installed.join(", ") || "\u2014"}`);
1602
1602
  });
1603
1603
  async function main() {
1604
1604
  preflightNode();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vitrine-kit/vitrine",
3
- "version": "0.3.0",
4
- "description": "Vitrine CLI — примитив установки фичи, init (визард), add, update, doctor, design apply.",
3
+ "version": "0.3.1",
4
+ "description": "Vitrine CLI — feature install primitive, init (wizard), add, update, doctor, design apply.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "bin": {
@@ -21,7 +21,7 @@
21
21
  "dependencies": {
22
22
  "@clack/prompts": "^0.8.2",
23
23
  "commander": "^12.1.0",
24
- "@vitrine-kit/contracts": "1.1.0"
24
+ "@vitrine-kit/contracts": "1.2.0"
25
25
  },
26
26
  "scripts": {
27
27
  "build": "tsup src/index.ts --format esm --clean",