@wix/agent-skills 1.31.1 → 1.31.2

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 (90) hide show
  1. package/package.json +1 -1
  2. package/skills/wix-app/references/STORES_VERSIONING.md +1 -1
  3. package/skills/wix-headless-kit/SKILL.md +16 -7
  4. package/skills/wix-headless-kit/guides/api-run.md +81 -51
  5. package/skills/wix-headless-kit/guides/existing-site.md +6 -0
  6. package/skills/wix-headless-kit/install/AGENTS.md +3 -2
  7. package/skills/wix-headless-kit/install/attach.mjs +32 -27
  8. package/skills/wix-headless-kit/install/background.mjs +32 -0
  9. package/skills/wix-headless-kit/install/bootstrap.mjs +28 -11
  10. package/skills/wix-headless-kit/install/check.mjs +2 -2
  11. package/skills/wix-headless-kit/install/context.mjs +49 -5
  12. package/skills/wix-headless-kit/install/deploy.mjs +33 -8
  13. package/skills/wix-headless-kit/install/env-ignore.mjs +16 -0
  14. package/skills/wix-headless-kit/install/install.mjs +95 -0
  15. package/skills/wix-headless-kit/install/pins.json +1 -1
  16. package/skills/wix-headless-kit/install/proc.mjs +46 -0
  17. package/skills/wix-headless-kit/install/setup.mjs +16 -25
  18. package/skills/wix-headless-kit/install/templates.mjs +2 -2
  19. package/skills/wix-headless-templates/blog/app/components/blog/RichContent.tsx +3 -2
  20. package/skills/wix-headless-templates/blog/project/package-lock.json +1287 -1299
  21. package/skills/wix-headless-templates/blog/project/package.json +1 -1
  22. package/skills/wix-headless-templates/blog/project/src/components/blog/RichContent.tsx +3 -2
  23. package/skills/wix-headless-templates/blog/rest/authors.ts +5 -5
  24. package/skills/wix-headless-templates/blog/rest/comments.ts +4 -4
  25. package/skills/wix-headless-templates/blog/rest/post-likes.ts +3 -3
  26. package/skills/wix-headless-templates/blog/rest/posts.ts +4 -4
  27. package/skills/wix-headless-templates/blog/rest/taxonomy.ts +5 -5
  28. package/skills/wix-headless-templates/blog/seed/seed-blog.mjs +2 -1
  29. package/skills/wix-headless-templates/bookings/rest/booking.ts +4 -4
  30. package/skills/wix-headless-templates/bookings/rest/services.ts +4 -4
  31. package/skills/wix-headless-templates/bookings/seed/seed-bookings.mjs +2 -1
  32. package/skills/wix-headless-templates/cms/rest/collections.ts +3 -3
  33. package/skills/wix-headless-templates/cms/rest/items.ts +4 -4
  34. package/skills/wix-headless-templates/cms/seed/seed-cms.mjs +2 -1
  35. package/skills/wix-headless-templates/donations/rest/campaigns.ts +4 -4
  36. package/skills/wix-headless-templates/donations/rest/donate.ts +3 -3
  37. package/skills/wix-headless-templates/donations/seed/seed-donations.mjs +2 -1
  38. package/skills/wix-headless-templates/events/rest/events.ts +4 -4
  39. package/skills/wix-headless-templates/events/rest/orders.ts +4 -4
  40. package/skills/wix-headless-templates/events/rest/registration.ts +4 -4
  41. package/skills/wix-headless-templates/events/seed/seed-events.mjs +2 -1
  42. package/skills/wix-headless-templates/faq/rest/faq.ts +4 -4
  43. package/skills/wix-headless-templates/faq/seed/seed-faq.mjs +2 -1
  44. package/skills/wix-headless-templates/forms/rest/forms.ts +4 -4
  45. package/skills/wix-headless-templates/forms/rest/submissions.ts +4 -4
  46. package/skills/wix-headless-templates/forms/seed/seed-forms.mjs +2 -1
  47. package/skills/wix-headless-templates/members/rest/auth.ts +3 -3
  48. package/skills/wix-headless-templates/members/rest/members.ts +4 -4
  49. package/skills/wix-headless-templates/members/seed/seed-members.mjs +2 -1
  50. package/skills/wix-headless-templates/portfolio/rest/portfolio.ts +4 -4
  51. package/skills/wix-headless-templates/portfolio/seed/seed-portfolio.mjs +2 -1
  52. package/skills/wix-headless-templates/pricing-plans/rest/plan-access.ts +3 -3
  53. package/skills/wix-headless-templates/pricing-plans/rest/plans.ts +4 -4
  54. package/skills/wix-headless-templates/pricing-plans/rest/purchase.ts +3 -3
  55. package/skills/wix-headless-templates/pricing-plans/seed/seed-pricing-plans.mjs +2 -1
  56. package/skills/wix-headless-templates/rentals/rest/rentals.ts +4 -4
  57. package/skills/wix-headless-templates/rentals/seed/seed-rentals.mjs +2 -1
  58. package/skills/wix-headless-templates/restaurants/rest/menu.ts +4 -4
  59. package/skills/wix-headless-templates/restaurants/rest/ordering.ts +5 -5
  60. package/skills/wix-headless-templates/restaurants/rest/reservations.ts +4 -4
  61. package/skills/wix-headless-templates/restaurants/seed/seed-restaurants.mjs +2 -1
  62. package/skills/wix-headless-templates/shared/capabilities/site-search/rest/search.ts +4 -4
  63. package/skills/wix-headless-templates/shared/capabilities/site-search/seed/install.mjs +6 -4
  64. package/skills/wix-headless-templates/shared/seed/main.mjs +15 -0
  65. package/skills/wix-headless-templates/shared/seed/wix-cli.mjs +43 -11
  66. package/skills/wix-headless-templates/storefront/INSTRUCTIONS.md +1 -1
  67. package/skills/wix-headless-templates/storefront/rest/cart.ts +5 -5
  68. package/skills/wix-headless-templates/storefront/rest/catalog.ts +5 -5
  69. package/skills/wix-headless-templates/storefront/seed/SEED.md +27 -9
  70. package/skills/wix-headless-templates/storefront/seed/seed-store.mjs +2 -1
  71. package/skills/wix-manage/SKILL.md +3 -3
  72. package/skills/wix-manage/references/app-installation/install-wix-apps.md +5 -1
  73. package/skills/wix-manage/references/app-installation/list-installed-apps.md +19 -9
  74. package/skills/wix-manage/references/bookings/booking-service-policy-setup.md +3 -0
  75. package/skills/wix-manage/references/bookings/external-calendar-integration.md +3 -0
  76. package/skills/wix-manage/references/bookings/multi-resource-service-creation.md +3 -0
  77. package/skills/wix-manage/references/cms/cms-data-items-crud.md +11 -2
  78. package/skills/wix-manage/references/cms/cms-publishing-flow.md +63 -16
  79. package/skills/wix-manage/references/forms/update-form.md +3 -0
  80. package/skills/wix-manage/references/pricing-plans/create-and-update-pricing-plans.md +3 -0
  81. package/skills/wix-manage/references/restaurants/wix-restaurants-setup.md +3 -0
  82. package/skills/wix-manage/references/sites/create-headless-site.md +7 -50
  83. package/skills/wix-manage/references/sites/manage-oauth-apps.md +4 -4
  84. package/skills/wix-manage/references/sites/read-site-context.md +4 -0
  85. package/skills/wix-manage/references/sites/upload-static-site.md +67 -31
  86. package/skills/wix-manage/references/stores/add-store-pages-to-site.md +3 -0
  87. package/skills/wix-manage/references/stores/change-store-currency.md +3 -0
  88. package/skills/wix-manage/references/stores/create-product-catalog-v3.md +3 -0
  89. package/skills/wix-manage/references/stores/find-products-query-and-search-catalog-v3.md +3 -0
  90. package/skills/wix-manage/references/stores/update-product-with-options.md +192 -22
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/agent-skills",
3
- "version": "1.31.1",
3
+ "version": "1.31.2",
4
4
  "description": "Wix Skills for AI coding agents — versioned npm distribution of the wix/skills content.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -130,4 +130,4 @@ Payload changes: V1 `changedFields` → V3 `modifiedFields`. Top-level `entityId
130
130
  - [stores/INVENTORY.md](stores/INVENTORY.md) — inventory read and write operations
131
131
  - [stores/CATEGORIES.md](stores/CATEGORIES.md) — collections (V1) ↔ categories (V3)
132
132
 
133
- For methods not listed here, use `SearchWixSDKDocumentation` then `ReadFullDocsArticle`. Always return the required permission scopes to the user.
133
+ For methods not listed here, use `SearchWixSDKDocumentation` then `ReadFullDocsMethodSchema` (use `ReadFullDocsArticle` only for prose). Always return the required permission scopes to the user.
@@ -106,6 +106,11 @@ paths below are relative to that folder, never assumed under `<SKILL_ROOT>`.
106
106
  `node <SKILL_ROOT>/install/check.mjs` says whether the skill or its templates have a newer version
107
107
  and prints the update commands; it changes nothing.
108
108
 
109
+ Throughout any run, keep the site working at every step so the user can try it as it grows. Steps
110
+ only the site owner can take never hold a flow back: drafted content is live, and anything that
111
+ takes money reaches Wix's checkout before payments are connected. The owner's steps go in the
112
+ closing with their links, as next steps the user can take or ask for.
113
+
109
114
  Throughout any run: if the user asks to send feedback to Wix, complains or gets frustrated, or the
110
115
  run hits friction of any kind: anything that cost more turns than it should have, whether or not
111
116
  it ended in an error (a confusing error, a doc gap, a seed that had to be re-run, a shipped file
@@ -143,8 +148,9 @@ only after an explicit yes, never automatically. Step 5 ends with the same self-
143
148
  `astro.config.mjs` `integrations: [wix(), react()]`, `adapter: wixHostingAdapter()`,
144
149
  `output: "server"`, `security: { checkOrigin: false }`, `image.domains` with
145
150
  `static.wixstatic.com`. An Astro project made without the CLI has none of that; add it
146
- before deploying, and the site serves every route. The integration supports **Astro 5**: a
147
- project on another major is pinned to 5 first, or connected as a React host.
151
+ before deploying, and the site serves every route. The integration's current major
152
+ (`@wix/astro` 3, as in this skill's templates) requires **Astro 7**: a project on an earlier
153
+ major moves to 7 first, or is connected as a React host.
148
154
  - **React and other bundlers** release their own build as files. So routes are hash routes,
149
155
  or one emitted HTML file per route linked by its file name — decided before the first route
150
156
  is written; any URL handed to Wix as a return target must be one the host serves. Verify by
@@ -189,10 +195,13 @@ only after an explicit yes, never automatically. Step 5 ends with the same self-
189
195
  | a frontend, no config (a `package.json`, or `index.html` at the root: someone's Astro, Vite, Next, plain HTML) | **project** → adopt | `init` in place gives it a new, empty site, then deploys; `--stack` from step 1 is required; make the project what that stack needs on Wix hosting (step 1) before or right after | **yes**, from `--plan` |
190
196
  | a config whose `.env.local` declares an active editor migration (`EDITOR_MIGRATION_STATUS=ACTIVE`), with or without the blank Astro starter the download carries | **migration** → migrate | the shipped code into the starter (or the composed template around a bare config), deployed with the migrated site's app as the client, the install starts; `ready_for_brand_layer` says `mode: "migrate"`, the parent as `siteId`, the child as `deploySiteId` (`guides/migration.md`) | no, ever |
191
197
  | a config, no frontend | **config-only** → refuses | the site exists and has no frontend yet: `attach.mjs` (below) takes the site from the config, reuses its hosting, scaffolds and deploys. That config is what `init` leaves behind, and `init` always creates a site: this site was made for this run and is empty | **yes**: draft the plan as for create, then `attach.mjs --plan` |
192
- | a config and a frontend (a `package.json`, or `index.html` inside the folder `site.outputDirectory` names) | **wix-project** → refuses | iterate: never scaffold, `init` or reseed. `deploy.mjs <vertical…> --stack <stack>` adds a solution (the client id comes from `.env.local`, the config as the fallback), then ONE `npm install`; a change is file edits; then release | no |
198
+ | a config and a frontend (a `package.json`, or `index.html` inside the folder `site.outputDirectory` names) | **wix-project** → refuses | iterate: never scaffold, `init` or reseed. `deploy.mjs <vertical…> --stack <stack>` adds a solution (the client id comes from `.env.local`, the config as the fallback), then one install, `node <SKILL_ROOT>/install/install.mjs` (nothing when `node_modules` already satisfies the lock); a change is file edits; then release | no |
193
199
  | a config, a `package.json`, `public/index.html` and no `src/pages/` (a site published through the drop flow, on its Wix Dev Machine) | **static-in-astro** → refuses | the pages stay plain files in `public/`: `deploy.mjs <vertical…> --stack static --out public` puts the REST layer in `public/js/wix/`, the pages are wired in place (reference mode), and no Astro pages are added; release with `build` then `release`, the URL stays | no |
200
+ | a config and the CLI's blank Astro starter with no work of its own (`wix-astro-blank`, only the blank template's packages: a Dev Machine's never-released site, a bare `wix create`) | **blank-starter** → refuses | `attach.mjs --business-name <brand> --vertical <v…> [--plan plan.json]` on the config's site: the vertical's composed template takes the starter's place (its lockfile with it), the install and the seed start detached; build the brand layer while they run | **yes**, from `--plan`: the site was made for this run and is empty, unless `read-site.mjs` shows content |
194
201
  | a config, `index.html` at the root, no `package.json` (a site published through the drop flow and downloaded) | **published-static** | the config's site, no `init`: `site/` becomes the upload, the REST layer deploys into `site/js/wix/`; the `next` says to move the pages, styles and assets in; release keeps the URL | no |
195
202
 
203
+ A folder no row names is **wix-project**: the fill-only path — never scaffold, `init`, reseed or delete; deploy, edit, release. A wrong guess costs a slower run, never the user's work.
204
+
196
205
  **Who decides the seed: where the site came from, never the brief's wording.** A site made
197
206
  for this run is empty by construction, so it is always seeded — with the brief's content when
198
207
  it supplies or describes any, otherwise with demo content the agent drafts per the vertical's
@@ -232,7 +241,7 @@ only after an explicit yes, never automatically. Step 5 ends with the same self-
232
241
  It emits one JSON event per line and returns in **~35s**: **scaffolds** the project from the
233
242
  vertical's composed template (`wix create` copies it: the code and its lockfile arrive with
234
243
  the scaffold), **deploys** whatever the folder still lacks (patching `package.json` with every
235
- dependency the code imports), then **starts two detached background jobs** — the dependency install (`npm ci --ignore-scripts || npm install --ignore-scripts`)
244
+ dependency the code imports), then **starts two detached background jobs** — the dependency install (`install/install.mjs`: nothing when `node_modules` already satisfies the lock, `npm install --ignore-scripts` over a tree that is there, else `npm ci --ignore-scripts || npm install --ignore-scripts`)
236
245
  and the **seed** — whose logs and completion markers are in the events. The final
237
246
  `ready_for_brand_layer` event carries the project dir, siteId, ready-made dashboard links,
238
247
  and both markers. Relay notable events. On an `error` event, recover just that step via the
@@ -240,8 +249,8 @@ only after an explicit yes, never automatically. Step 5 ends with the same self-
240
249
 
241
250
  **Recovering one step, or adding a solution later:** the pieces run on their own from the
242
251
  project root — `node <SKILL_ROOT>/install/deploy.mjs <vertical…> --stack <stack>` (the client
243
- id is read from `wix.config.json`), ONE `npm ci --ignore-scripts || npm install
244
- --ignore-scripts` (**never a second npm install concurrently**: two npms in one
252
+ id is read from `wix.config.json`), ONE `node <SKILL_ROOT>/install/install.mjs`
253
+ (**never a second npm install concurrently**: two npms in one
245
254
  `node_modules` race and redo each other's work; setup already started one — wait on its
246
255
  marker), the vertical's seed module per its `seed/SEED.md`.
247
256
  A code change on an existing project is done when it is **released** (step 5) and the live
@@ -266,7 +275,7 @@ only after an explicit yes, never automatically. Step 5 ends with the same self-
266
275
  `templates/shared/CUSTOM_OPERATIONS.md` before writing it. Use one documented path and
267
276
  implement it; do not reverse-engineer SDK internals.
268
277
  5. **When both background jobs have completed** — the install's marker
269
- (`node_modules/.package-lock.json`) and the seed's (`.seed-exit`) both exist — **verify the
278
+ (`.install-exit`, containing `0`) and the seed's (`.seed-exit`) both exist — **verify the
270
279
  seed succeeded** (`.seed-exit` contains `0`; `seed-result.json` has the created counts for
271
280
  your summary — if non-zero, read `seed.log` and re-run the seed module manually). Those two
272
281
  seed files exist **only when setup started the seed** (attach runs none: only the install
@@ -3,10 +3,17 @@
3
3
  Read when the commands this skill is built on cannot run where you are, or stopped: your shell
4
4
  can't reach Wix, there is no shell, or the install, the
5
5
  CLI or the login is blocked. Wix is then reachable through the Wix MCP: every call below is an
6
- `ExecuteWixAPI` call, and files reach the site through the upload recipe. It walks the run of
6
+ `ExecuteWixAPI` call, and files reach the site through the upload recipe. With no Wix MCP tools in
7
+ your tool list (a plugin's skills can be on disk while its Wix connector is not connected), ask the
8
+ user to connect Wix to this chat first. It walks the run of
7
9
  SKILL.md step by step and names, for each, the Wix API call the CLI or the script performs and the
8
10
  file beside this skill that carries the contract; where a script can run where you are, it says so.
9
11
 
12
+ As in SKILL.md's run, keep the site working at every step so the user can try it as it grows.
13
+ Steps only the site owner can take never hold a flow back: drafted content is live, and anything
14
+ that takes money reaches Wix's checkout before payments are connected. The owner's steps go in the
15
+ closing with their links, as next steps the user can take or ask for.
16
+
10
17
  ## Where these skills are
11
18
 
12
19
  This skill is one of a set, and the guide reads from three of them:
@@ -25,13 +32,25 @@ This skill is one of a set, and the guide reads from three of them:
25
32
  exact shape before writing a call.
26
33
 
27
34
  Installed, they sit beside each other wherever your client keeps them: under `.agents/skills/` after
28
- `npx skills add`, or in a plugin's folder. Read them from disk when they are there. Online, they are
29
- published alongside each other under the same names (the registry also serves each without its
30
- `wix-` prefix, `headless-templates` for `wix-headless-templates`, which is the `base` its manifest
31
- shows; both forms resolve), each with a manifest of its files, and in the
35
+ `npx skills add` or a similar skills folder, or in a plugin's folder. A skill your client offers you
36
+ by name is read from there, or through the client's skill tool when it is served rather than a file,
37
+ with no install, clone or online read. List them first, through that tool or the folder: a catalog
38
+ shown at the start can leave skills out. Online, they are published alongside each other under the
39
+ same names (the registry also serves each without its `wix-` prefix, `headless-templates` for
40
+ `wix-headless-templates`, which is the `base` its manifest shows; both forms resolve), and in the
32
41
  `wix/skills` repository on GitHub under `skills/{skill}/`; a sandbox that reaches GitHub has the
33
- whole set on disk from a clone. Online pages are read with the Wix MCP's `ReadFullDocsArticle`
34
- when it is connected: a client's web fetch may open only URLs the user gave or a search returned.
42
+ whole set on disk from a clone. Online pages are read with the Wix MCP's `ReadFullDocsArticle` when
43
+ it is connected: a client's web fetch may open only URLs the user gave or a search returned.
44
+
45
+ Online, a skill's files are found through its manifest,
46
+ `https://www.wix.com/skills/{skill}.manifest.json` (for the templates,
47
+ `headless-templates.manifest.json`): every file it holds, by path, with its size in bytes. A
48
+ folder's URL lists its files, and `<folder>/_content` returns them in one response. Open it once
49
+ before the first file and take each path from it: the files this guide names by pattern
50
+ (`{solution}/rest/*.ts`, `*-core.ts`), and the files an import points to. A path the manifest
51
+ doesn't list is a wrong path; find the file there before writing anything in its place. A copy you
52
+ hold, served or on disk, is current when its size in bytes matches the manifest's; a file whose size
53
+ differs is read online.
35
54
 
36
55
  What such a run does not produce: the Astro build and its release. Those need a machine with the
37
56
  CLI. Everything else the kit does to a site, it does through calls you can make, the frontend
@@ -75,32 +94,25 @@ Restaurants, Donations, FAQ), goes on with the Apps Installer:
75
94
  ## 4. The seed
76
95
 
77
96
  `seed-{solution}.mjs` is a sequence of REST calls, and `SEED.md` is its contract: the plan shape
78
- (what to create, how many, which images), the order the entities need, and the traps the script
79
- encodes (a Bookings service needs a category to be visible and takes resource ids, not staff ids;
80
- a Forms field is registered by its `validation` block; a product's choice photos are linked after
81
- the gallery holds them; right after a bulk create a product's own stock summary can still read
82
- `OUT_OF_STOCK` while its inventory items are already stocked, so check the items (Query Inventory
83
- Items) and create only the missing ones, as `SEED.md` says, where `ALREADY_EXISTS` means stocked; stock that isn't counted, such as "made to order", is
84
- `inStock: true`, never a missing quantity, which leaves the product unbuyable). Read `SEED.md`
85
- before the first create call: it maps the brief's words to the fields. Those fields are the
86
- script's input, not the request: the body each create sends is built in the script (for products,
87
- `bulkCreateProducts` in `seed-store.mjs`: the description as rich-text nodes from `mkDesc`,
88
- `productType` with `physicalProperties`, the stock as `inventoryItem`), so read that function before
89
- the first create. On a site this run created, the store's currency is `SEED.md`'s `currency` rule,
90
- the one the brief or the user's own pages state (a page priced "$68" is USD), set before the
91
- products and without asking; a confirmation before a currency change is for a store the user
92
- already runs. Then read the script for any call `SEED.md` only names, and make the same
93
- calls through `{MANAGE}`'s recipe for each (services, products, posts, events, collections). Keep
94
- the script's rules: create, never delete, and report what the site already held. A photo already in
95
- the site's Media Manager, such as one the user uploaded, goes in by its file id (`imageMediaId` in
96
- `SEED.md`), as it is: an import by URL would copy it. A photo on a site the user dropped is served
97
- with the drop, not from the Media Manager: it goes in by its live URL, imported once through
98
- `{MANAGE}/references/media/upload-media-to-wix.md`, and the file id the import returns is what the
99
- product or service takes. Image prompts need the Media Manager; without
100
- it, products and services stay text-only, say so. The scripts read
101
- a response body directly; `ExecuteWixAPI`'s `wix.request` returns `{ status, data }`, and the body
102
- is `data`. A generated image is billed when it is generated, so a read of the wrong level discards
103
- a paid image.
97
+ (what to create, how many, which images), the order the entities need, the request bodies and the
98
+ traps the script encodes. Read `SEED.md` before the first create: it maps the brief's words to the
99
+ fields, and the script's builder functions turn those fields into each request, so the bodies you
100
+ send are the ones they build. A call `SEED.md` only names is made through `{MANAGE}`'s recipe for
101
+ it. Keep the script's rules: create, never delete, and report what the site already held. The closing
102
+ names entities you drafted as placeholders.
103
+
104
+ A photo already in the site's Media Manager, such as one the user uploaded, goes in by its file id
105
+ (`imageMediaId` in `SEED.md`), as it is: an import by URL would copy it. A photo on a site the user
106
+ dropped is served with the drop, not from the Media Manager: it goes in by its live URL, imported
107
+ once through `{MANAGE}/references/media/upload-media-to-wix.md`, and the file id the import returns
108
+ is what the entity takes. An `imagePrompt` is generated through
109
+ `{MANAGE}/references/media/generate-image-with-ai.md` (`POST /runwareschemaless/v1/request`, the
110
+ body `generateImage` in `{TEMPLATES}/shared/seed/images.mjs` builds), then imported through
111
+ `upload-media-to-wix.md`, and the file id goes on the entity, as for a dropped photo; this is the
112
+ seed's default for every entity that shows a picture. An entity stays text-only only when generation
113
+ or the import fails; say which. The scripts read a response body directly; `ExecuteWixAPI`'s
114
+ `wix.request` returns `{ status, data }`, and the body is `data`. A generated image is billed when
115
+ it is generated, so a read of the wrong level discards a paid image.
104
116
 
105
117
  ## 5. The frontend
106
118
 
@@ -108,6 +120,9 @@ The run is not done until the pages use what the backend holds: every control th
108
120
  it (add to cart, the cart, book, submit) works on the live site. A backend with untouched pages is
109
121
  half a run.
110
122
 
123
+ A payment (a cart, a paid booking, a ticket, a plan, a donation) ends in Wix's hosted checkout,
124
+ reached through a redirect session, and is wired on the first pass, payments connected or not.
125
+
111
126
  The Astro pages cannot be released here: that is a run with a machine and a CLI login, which
112
127
  attaches to this site (`guides/existing-site.md`) rather than making a new one. What ships from here
113
128
  is static files, dropped onto the site from step 2 through
@@ -145,16 +160,14 @@ with `{TEMPLATES}/shared/app/wix/sdk.ts` for the client wiring): those files cal
145
160
  modules the page imports, and they are verified. Copy the form of the call, not the file. A page
146
161
  that talks to Wix over plain `fetch`, with no SDK, takes them from the REST twins instead:
147
162
  `{TEMPLATES}/shared/rest/client.ts` (the visitor token and the request) and
148
- `{TEMPLATES}/{solution}/rest/*.ts` (the calls; for a store, `storefront/rest/cart.ts` and
149
- `storefront/rest/catalog.ts`). Read those files by name; the manifest and the solution's whole
150
- `INSTRUCTIONS.md` are not needed to find them. The
151
- cores beside them (`*-core.ts`) hold the rules the page needs and nothing else does: which fields
152
- to read, how a price or a duration is formatted, which slot is bookable, how an id is read. Copy
153
- those core functions into the page as they are, with only the types removed (`cart-core.ts`'s
154
- `toLine` and `toCart`, the price formatter, `rawId`), and run every API response through them: a
155
- raw field read by hand is where a page breaks (a product name is a translatable object, a cart
156
- line's quantity is `quantityInfo.confirmedQuantity`). What you write is the markup, the rendering
157
- of what the cores return, and the event handlers.
163
+ `{TEMPLATES}/{solution}/rest/*.ts` (the calls), each importing its helpers by their real paths.
164
+ The cores,
165
+ `{TEMPLATES}/{solution}/app/wix/{solution}/*-core.ts`, hold the rules the page needs and nothing else
166
+ does: which fields to read, how a price or a duration is formatted, which slot is bookable, how an
167
+ id is read. Copy those core functions into the page as they are, with only the types removed, and
168
+ run every API response through them: a raw field read by hand is where a page breaks (a name can be
169
+ a translatable object, a quantity a nested one). What you write is the markup, the rendering of what
170
+ the cores return, and the event handlers.
158
171
 
159
172
  **A call those files don't cover** is confirmed before it is written, the way `{DOCS}` describes:
160
173
  the method's SDK page, and when the page and the package disagree, the package's own type
@@ -162,14 +175,31 @@ declarations (the `@wix/auto_sdk_{solution}_<module>` package the solution packa
162
175
  its `index.d.ts`) are the truth. Two shapes that recur, as examples of what the types settle and
163
176
  the kit's files already encode: SDK query methods return a query builder finished with `.find()`;
164
177
  and an entity's id arrives as `_id` on some objects and `id` on others (the kit reads both,
165
- `rawId`). Dates travel as local wall-clock strings in the business time zone. A page cannot be
166
- opened here, so a wrong shape fails silently in the browser. Check the data code before the upload:
167
- get a real response through `ExecuteWixAPI` for each call the page makes, run the page's data code
168
- on it in your sandbox's Node, and look for names, prices and quantities in the output, not
169
- `[object Object]` or `undefined`. The cart's calls run as a visitor, which `wix.request` (the
170
- owner) is not: in the script, mint a visitor token with the site's public client id as
171
- `shared/rest/client.ts` does (`POST https://www.wixapis.com/oauth2/token`, `grantType: "anonymous"`),
172
- send the cart calls with it through `fetch`, add one product and read the cart back.
178
+ `rawId`). Dates travel as local wall-clock strings in the business time zone.
179
+
180
+ **The check**, before every upload: a page cannot be opened here, so a wrong shape fails silently in
181
+ the browser. In one `ExecuteWixAPI` script, make each control's calls the way the page makes them,
182
+ as a visitor, with the bodies from `{solution}/rest/*.ts`. Then read the solution's cores,
183
+ `{TEMPLATES}/{solution}/app/wix/{solution}/*-core.ts` (online, that folder's `_content` returns them
184
+ in one response), and run each response through them in your sandbox's Node, as the page does:
185
+
186
+ ```js
187
+ async function () {
188
+ const api = "https://www.wixapis.com", clientId = "<appId>";
189
+ const post = (path, body, token) => fetch(api + path, { method: "POST",
190
+ headers: { "Content-Type": "application/json", ...(token && { Authorization: token }) },
191
+ body: JSON.stringify(body) }).then(async (r) => ({ path, status: r.status, data: await r.json() }));
192
+ const { data: { access_token: token } } = await post("/oauth2/token", { clientId, grantType: "anonymous" });
193
+ const results = [];
194
+ // each call the control makes, in order: results.push(await post(path, body, token));
195
+ // a payment's last call is the redirect session the solution's rest/*.ts creates
196
+ return results;
197
+ }
198
+ ```
199
+
200
+ Done when every call returns 200, the cores turn each response into the names, prices, quantities or
201
+ times the page shows, and a payment's redirect session returns a URL. The upload waits for that: a
202
+ failing call is fixed first, on the site or in the page. The closing reports the check.
173
203
 
174
204
  ## 6. Closing
175
205
 
@@ -27,6 +27,12 @@ shape as setup (SKILL.md step 3):
27
27
  node <SKILL_ROOT>/install/attach.mjs --site <siteId> --business-name "<site name>" --vertical <vertical>[,<vertical>] [--plan plan.json]
28
28
  ```
29
29
 
30
+ The same call takes a folder that already holds the site's config and the CLI's blank Astro
31
+ starter with no work of its own (`context.mjs` says `blank-starter`: a Wix Dev Machine for a
32
+ never-released site, or a bare `wix create`): `--site` comes from the config, the vertical's
33
+ composed template takes the starter's place, its lockfile with it, and the install and the seed
34
+ run detached as below. Do not install by hand there; the brand layer is yours while they run.
35
+
30
36
  `init`/`wix create` always create a site, so they are not used here. attach does what they do
31
37
  after creating one — the site's OAuth app, Wix hosting, `wix.config.json` — against the site
32
38
  given, copies the first vertical's composed template, deploys the rest, and starts the install
@@ -17,8 +17,9 @@ call is memory, and an empty or error reply to it says nothing about the site.
17
17
 
18
18
  ## Skills
19
19
 
20
- Installed at `.agents/skills/` (by the Wix plugin, or by
21
- `npx skills add wix/skills --skill {{SKILL}} --skill wix-docs --skill wix-manage`).
20
+ Installed at `.agents/skills/` by
21
+ `npx skills add wix/skills --skill {{SKILL}} --skill wix-docs --skill wix-manage`; a Wix plugin keeps
22
+ its copy, under the same names, in the client's own skills folder.
22
23
 
23
24
  - `{{SKILL}}` — the code in this app and how to extend it. The shipped code is the
24
25
  `wix-headless-templates` skill: `node .agents/skills/{{SKILL}}/install/templates.mjs` prints where it
@@ -46,13 +46,16 @@
46
46
  // Emits ONE JSON event per line (attached, scaffolded, deployed, install_started,
47
47
  // ready_for_brand_layer, or error). Requires a logged-in Wix CLI (`npx @wix/cli@<pinned> whoami`, the version in pins.json)
48
48
  // whose account owns or co-manages the site.
49
- import { spawn, spawnSync, execFileSync } from "node:child_process";
50
- import { cpSync, existsSync, mkdirSync, openSync, readFileSync, writeFileSync, readdirSync } from "node:fs";
49
+ import { spawnSync } from "node:child_process";
50
+ import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from "node:fs";
51
51
  import { basename, dirname, join, resolve } from "node:path";
52
52
  import { fileURLToPath } from "node:url";
53
53
  import { writeAgentsMd } from "./agents-md.mjs";
54
+ import { ignoreEnvLocal } from "./env-ignore.mjs";
54
55
  import { frontendPresent, siteContext } from "./context.mjs";
55
56
  import { listVerticals, templatesDir } from "./templates.mjs";
57
+ import { failure, npmSync, startBackground } from "./proc.mjs";
58
+ import { blankStarter } from "./context.mjs";
56
59
  const PINS = JSON.parse(readFileSync(new URL("./pins.json", import.meta.url), "utf8"));
57
60
  const WIX_CLI = `@wix/cli@${PINS["@wix/cli"]}`;
58
61
 
@@ -90,6 +93,10 @@ const cwdConfig = has("wix.config.json") ? JSON.parse(readFileSync(join(cwd, "wi
90
93
  // config's site.outputDirectory names (a static site laid out for release) — install/context.mjs.
91
94
  const fp = frontendPresent(cwd);
92
95
  const hasProject = fp.packageJson || fp.rootIndex || fp.outputIndex;
96
+ // The CLI's blank starter with no work of its own (a Dev Machine's never-released site, a bare
97
+ // `wix create`): not a project to link, a scaffold to complete — the vertical's composed template
98
+ // takes its place (deploy.mjs), the install and the seed run detached as for a new site.
99
+ const starter = !!cwdConfig && fp.packageJson && !(fp.publicIndex && !fp.astroPages) && blankStarter(cwd);
93
100
  // A migration preview (wix.config.json names a deploy-only site, .env.local names the site being
94
101
  // migrated) is already provisioned: its app, hosting and credentials came with the folder. Nothing
95
102
  // here applies — setup.mjs takes it (SKILL.md step 3).
@@ -122,24 +129,24 @@ if (hosting0 === "self" && stack === "astro" && !hasProject && !stackFlag) {
122
129
  fail("args", "--hosting self scaffolds nothing: pass --stack for the frontend you will host (react|lib|static), or attach from inside the project on disk");
123
130
  }
124
131
  if (!subfolder) {
125
- if (hasProject && cwdConfig) {
126
- fail("place", "this folder is already a Wix project with a frontend (wix.config.json and a project): nothing to attach. deploy.mjs <vertical> adds this skill's code or a solution to it; then ONE npm install");
132
+ if (hasProject && cwdConfig && !starter) {
133
+ fail("place", "this folder is already a Wix project with a frontend (wix.config.json and a project): nothing to attach. deploy.mjs <vertical> adds this skill's code or a solution to it; then node <SKILL_ROOT>/install/install.mjs");
127
134
  }
128
135
  if (cwdConfig && (cwdConfig.siteId ?? cwdConfig.projectId) !== siteId) {
129
136
  fail("place", `this folder is attached to site ${cwdConfig.siteId ?? cwdConfig.projectId}, not ${siteId} — a folder is never re-pointed; attach the other site with --subfolder`);
130
137
  }
131
- if (hasProject && !stackFlag) {
138
+ if (hasProject && !starter && !stackFlag) {
132
139
  fail("args", "attaching a project on disk needs --stack astro|react|lib|static — the stack you resolved in SKILL.md step 1 for this project");
133
140
  }
134
141
  }
135
- const mode = !subfolder && hasProject ? "link" : hosting0 === "self" ? "config-only" : "scaffold";
136
- emit("folder", { mode, stack, hosting: hosting0, project: hasProject, config: cwdConfig ? "same site" : null, siteOrigin });
142
+ const mode = !subfolder && hasProject && !starter ? "link" : hosting0 === "self" ? "config-only" : "scaffold";
143
+ emit("folder", { mode, stack, hosting: hosting0, project: hasProject, starter, config: cwdConfig ? "same site" : null, siteOrigin });
137
144
 
138
145
  // ---- http ---------------------------------------------------------------------------------------
139
146
  const cliToken = (site) => {
140
- const r = spawnSync("npx", ["-y", WIX_CLI, "token", ...(site ? ["--site", site] : [])], { encoding: "utf8", timeout: 120_000 });
147
+ const r = npmSync("npx", ["-y", WIX_CLI, "token", ...(site ? ["--site", site] : [])], { encoding: "utf8", timeout: 120_000 });
141
148
  const t = (r.stdout || "").trim();
142
- if (r.status !== 0 || !t) fail("auth", (r.stderr || r.stdout || `no token — is the Wix CLI logged in? (npx ${WIX_CLI} whoami)`).slice(-400));
149
+ if (r.status !== 0 || !t) fail("auth", failure(r, `no token — is the Wix CLI logged in? (npx ${WIX_CLI} whoami)`).slice(-400));
143
150
  return t;
144
151
  };
145
152
  async function call(base, path, { method = "POST", token, site, body, query } = {}) {
@@ -163,6 +170,13 @@ async function call(base, path, { method = "POST", token, site, body, query } =
163
170
  }
164
171
 
165
172
  const folderName = flag("folder-name") ?? (businessName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "site");
173
+ const renamePackage = (dir, name) => {
174
+ const pkgPath = join(dir, "package.json");
175
+ if (!existsSync(pkgPath)) return;
176
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
177
+ pkg.name = name;
178
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
179
+ };
166
180
  const projectDir = subfolder ? resolve(cwd, folderName) : cwd;
167
181
  if (subfolder && existsSync(join(projectDir, "wix.config.json"))) fail("args", `${folderName}/ is already a Wix project — pick --folder-name, or run deploy.mjs there`);
168
182
 
@@ -277,13 +291,9 @@ if (stack === "astro" && mode === "scaffold") {
277
291
  // here because the CLI's create makes a new site and this folder gets an existing one. Further
278
292
  // verticals are deployed below.
279
293
  cpSync(join(templatesDir({ need: `${verticals[0]}/project` }), verticals[0], "project"), projectDir, { recursive: true, force: false, errorOnExist: false });
280
- const pkgPath = join(projectDir, "package.json");
281
- const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
282
- pkg.name = folderName;
283
- writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
284
- const gi = join(projectDir, ".gitignore");
285
- const cur = existsSync(gi) ? readFileSync(gi, "utf8") : "";
286
- if (!/\.env/.test(cur)) writeFileSync(gi, cur + "\n# local env (pulled from Wix)\n.env.local\n.env\n");
294
+ // The blank starter keeps its name through deploy, which recognizes it by name and gives it the
295
+ // composed package.json and lockfile; the project takes the folder's name after that (below).
296
+ if (!starter) renamePackage(projectDir, folderName);
287
297
  }
288
298
  writeFileSync(join(projectDir, "wix.config.json"), JSON.stringify({ appId, siteId }, null, 2) + "\n");
289
299
  // The env the CLI's build reads. Same content `wix env pull` writes; written here so the build
@@ -297,6 +307,7 @@ writeFileSync(join(projectDir, ".env.local"), [
297
307
  `WIX_CLIENT_SECRET=${quote(secrets.appSecret)}`,
298
308
  "",
299
309
  ].join("\n"));
310
+ ignoreEnvLocal(projectDir); // in every mode: a linked project's own .gitignore may not cover it
300
311
  emit(mode === "link" ? "linked" : mode === "config-only" ? "configured" : "scaffolded", { folder: folderName, stack, template: mode === "scaffold" && stack === "astro" ? join(TEMPLATES, verticals[0], "project") : null });
301
312
  // the agent config files `wix create` writes (attach never runs the CLI's scaffold at all); fill-only
302
313
  emit("agent_configs", writeAgentsMd(projectDir, { skill: basename(SKILL_ROOT), stack }));
@@ -313,20 +324,19 @@ if (mode !== "link" && (stack !== "astro" || mode === "config-only")) {
313
324
  let deployResult = {};
314
325
  {
315
326
  const deploy = spawnSync("node", [join(SKILL_ROOT, "install", "deploy.mjs"), ...verticals, "--stack", stack, ...(planPath ? ["--plan", resolve(planPath)] : [])], { cwd: projectDir, encoding: "utf8", timeout: 60_000 });
316
- if (deploy.status !== 0) fail("deploy", deploy.stderr || deploy.stdout);
327
+ if (deploy.status !== 0) fail("deploy", failure(deploy));
317
328
  try { deployResult = JSON.parse(deploy.stdout); } catch { /* keep going */ }
318
329
  if (deployResult.error) fail("deploy", deployResult.error);
319
330
  emit("deployed", deployResult);
331
+ if (starter) renamePackage(projectDir, folderName);
320
332
  }
321
333
 
322
334
  // ---- 4 · dependency install, detached (any project with a package.json) --------------------------
323
335
  let install = null;
324
336
  if (existsSync(join(projectDir, "package.json"))) {
325
337
  const installLog = join(projectDir, "npm-install.log");
326
- const logFd = openSync(installLog, "a");
327
- const child = spawn("sh", ["-c", existsSync(join(projectDir, "package-lock.json")) ? "npm ci --ignore-scripts || npm install --ignore-scripts" : "npm install --ignore-scripts"], { cwd: projectDir, detached: true, stdio: ["ignore", logFd, logFd] });
328
- child.unref();
329
- install = { log: installLog, doneMarker: "node_modules/.package-lock.json" };
338
+ startBackground("install", [], { cwd: projectDir, log: installLog });
339
+ install = { log: installLog, doneMarker: ".install-exit", success: "file contains 0" };
330
340
  emit("install_started", install);
331
341
  }
332
342
 
@@ -339,12 +349,7 @@ if (planPath) {
339
349
  const seedDir = join(TEMPLATES, verticals[0], "seed");
340
350
  const seedName = existsSync(seedDir) ? readdirSync(seedDir).find((f) => f.startsWith("seed-") && f.endsWith(".mjs")) : undefined;
341
351
  if (!seedName) fail("seed", `no seed module found under ${seedDir}`);
342
- const seedChild = spawn(
343
- "sh",
344
- ["-c", `node "${join(seedDir, seedName)}" "${resolve(planPath)}" > seed-result.json 2> seed.log; echo $? > .seed-exit`],
345
- { cwd: projectDir, detached: true, stdio: "ignore" },
346
- );
347
- seedChild.unref();
352
+ startBackground("seed", [join(seedDir, seedName), resolve(planPath)], { cwd: projectDir });
348
353
  seed = { resultFile: "seed-result.json", log: "seed.log", doneMarker: ".seed-exit", success: "file contains 0" };
349
354
  emit("seeding_started", { vertical: verticals[0], ...seed, ...(verticals.length > 1 ? { note: `the plan seeds ${verticals[0]}; the other verticals' seeds run afterwards, each with its own plan` } : {}) });
350
355
  } else {
@@ -0,0 +1,32 @@
1
+ // The detached jobs setup.mjs and attach.mjs start (through proc.mjs startBackground), run by node
2
+ // so they need no shell on any OS. Each leaves the files the caller syncs on:
3
+ //
4
+ // node background.mjs install install.mjs: nothing when node_modules already satisfies
5
+ // the lock, npm install when a tree is there, else npm ci
6
+ // (falling back to npm install); --ignore-scripts, output
7
+ // inherited (the install log); the exit code to .install-exit
8
+ // node background.mjs seed <seed.mjs> <plan> the seed's stdout to seed-result.json, its stderr to
9
+ // seed.log, then its exit code to .seed-exit
10
+ import { spawnSync } from "node:child_process";
11
+ import { closeSync, openSync, writeFileSync } from "node:fs";
12
+ import { runInstall } from "./install.mjs";
13
+
14
+ const [job, ...args] = process.argv.slice(2);
15
+
16
+ if (job === "install") {
17
+ const r = runInstall(process.cwd());
18
+ console.log(`install: ${r.decision}${r.lock ? ` (${r.lock.missing} of ${r.lock.total} lock entries were missing)` : ""}`);
19
+ process.exit(r.status);
20
+ } else if (job === "seed") {
21
+ const [seedFile, plan] = args;
22
+ const out = openSync("seed-result.json", "w");
23
+ const err = openSync("seed.log", "w");
24
+ const r = spawnSync(process.execPath, [seedFile, plan], { stdio: ["ignore", out, err], windowsHide: true });
25
+ closeSync(out);
26
+ closeSync(err);
27
+ if (r.error) writeFileSync("seed.log", `the seed could not start: ${r.error.message}\n`, { flag: "a" });
28
+ writeFileSync(".seed-exit", `${r.status ?? 1}\n`);
29
+ } else {
30
+ console.error("usage: background.mjs install <ci|install> | seed <seed.mjs> <plan.json>");
31
+ process.exit(2);
32
+ }
@@ -2,7 +2,7 @@
2
2
  import { spawn, spawnSync } from 'node:child_process';
3
3
  import { existsSync, mkdirSync, openSync, readFileSync, writeFileSync } from 'node:fs';
4
4
  import { tmpdir } from 'node:os';
5
- import { join } from 'node:path';
5
+ import { dirname, join } from 'node:path';
6
6
  const PINS = JSON.parse(readFileSync(new URL("./pins.json", import.meta.url), "utf8"));
7
7
  const WIX_CLI = `@wix/cli@${PINS["@wix/cli"]}`;
8
8
 
@@ -14,10 +14,17 @@ const fail = (event, extra = {}) => {
14
14
  process.exit(1);
15
15
  };
16
16
 
17
- // ── platform-safe binary names (npm/npx are .cmd on Windows) ─────────────────
17
+ // ── npx on every platform ────────────────────────────────────────────────────
18
+ // On Windows npx is a .cmd shim, which Node starts only through a shell; a detached shell opens its
19
+ // own console, and the login's JSON events never reach the log. npm ships npx's JS entry beside
20
+ // node.exe, and that starts directly with node: no shell, no window, and the pid is the CLI's.
21
+ // A Node without npm beside it (a version manager's shim) keeps the .cmd through the shell.
18
22
  const isWin = process.platform === 'win32';
19
- const bin = (name) => (isWin ? `${name}.cmd` : name);
20
- const WIX = [bin('npx'), '-y', WIX_CLI]; // run the CLI via npx — no global install/mutation
23
+ const NPX_JS = isWin ? join(dirname(process.execPath), 'node_modules', 'npm', 'bin', 'npx-cli.js') : null;
24
+ const NPX = NPX_JS && existsSync(NPX_JS)
25
+ ? { file: process.execPath, pre: [NPX_JS], shell: false }
26
+ : { file: isWin ? 'npx.cmd' : 'npx', pre: [], shell: isWin };
27
+ const WIX = [NPX.file, ...NPX.pre, '-y', WIX_CLI]; // run the CLI via npx — no global install/mutation
21
28
 
22
29
  // Force the CLI into non-interactive "agent" mode. Without an agent signal in
23
30
  // the env, `wix login` renders an interactive Ink TUI (device code + keypress)
@@ -36,17 +43,26 @@ const PIDFILE = join(STATE_DIR, 'login.pid');
36
43
 
37
44
  // run a command, capture stdout+stderr (combined), return {status, out}
38
45
  function capture(cmd, args, opts = {}) {
39
- const r = spawnSync(cmd, args, { encoding: 'utf8', shell: isWin, env: AGENT_ENV, ...opts });
46
+ const r = spawnSync(cmd, args, { encoding: 'utf8', shell: NPX.shell, windowsHide: true, env: AGENT_ENV, ...opts });
40
47
  return { status: r.status ?? 1, out: `${r.stdout || ''}${r.stderr || ''}`, error: r.error };
41
48
  }
42
49
 
43
50
  // ── 1. CLI reachable (via npx — no install) ──────────────────────────────────
51
+ // A line holding only a version is the CLI's answer ("npm notice" lines carry versions too).
52
+ const VERSION_LINE = /^\s*(\d+\.\d+\.\d+\S*)\s*$/m;
53
+ const answered = (r) => r.status === 0 || VERSION_LINE.test(r.out);
54
+
44
55
  function checkCli() {
45
- const r = capture(WIX[0], [...WIX.slice(1), '--version']);
46
- if (r.status !== 0) fail('cli_unreachable', { detail: r.out.trim().slice(0, 400) });
56
+ let r = capture(WIX[0], [...WIX.slice(1), '--version']);
57
+ // On a cold npm cache npx can exit non-zero after the version, the install's warnings around
58
+ // it; a second run finds the cache warm.
59
+ if (!answered(r)) r = capture(WIX[0], [...WIX.slice(1), '--version']);
60
+ if (!answered(r)) {
61
+ fail('cli_unreachable', { detail: (r.out.trim() || String(r.error?.message || `exit ${r.status}`)).slice(0, 400) });
62
+ }
47
63
  // npx interleaves "npm notice …" lines with the version, so don't just take the
48
- // last line — pick the first semver-looking token, falling back to the last line.
49
- const version = (r.out.match(/\d+\.\d+\.\d+[^\s]*/) || [])[0] || r.out.trim().split('\n').pop();
64
+ // last line — pick the version line, else the first semver-looking token.
65
+ const version = r.out.match(VERSION_LINE)?.[1] || (r.out.match(/\d+\.\d+\.\d+[^\s]*/) || [])[0] || r.out.trim().split('\n').pop();
50
66
  emit('cli_ok', { version });
51
67
  }
52
68
 
@@ -108,7 +124,8 @@ function startLogin() {
108
124
  detached: true,
109
125
  stdio: ['ignore', out, out],
110
126
  env: AGENT_ENV,
111
- shell: isWin,
127
+ shell: NPX.shell,
128
+ windowsHide: true,
112
129
  });
113
130
  child.on('error', (e) => fail('login_failed', { detail: String(e) }));
114
131
  writeFileSync(PIDFILE, JSON.stringify({ pid: child.pid, startedAt: Date.now() }));
@@ -127,7 +144,7 @@ async function waitForCode(timeoutMs = 60000) {
127
144
  }
128
145
  const detail = existsSync(LOG) ? readFileSync(LOG, 'utf8').trim().slice(-1500) : '';
129
146
  fail('login_failed', {
130
- detail: detail || `wix login produced no device code within ${timeoutMs / 1000}s.`,
147
+ detail: detail || `wix login wrote nothing within ${timeoutMs / 1000}s; \`${['npx', '-y', WIX.at(-1), 'login'].join(' ')}\` run in the foreground prints the code.`,
131
148
  });
132
149
  }
133
150
 
@@ -13,12 +13,12 @@
13
13
  // what future deploys copy and what the agent reads, not `src/`. Nothing here touches `src/`.
14
14
  import { mkdtempSync, readdirSync, rmSync } from "node:fs";
15
15
  import { tmpdir } from "node:os";
16
- import { join, relative } from "node:path";
16
+ import { basename, join, relative } from "node:path";
17
17
  import { spawnSync } from "node:child_process";
18
18
  import { SKILL_ROOT, TEMPLATES_PATH, installSource, templatesDir, templatesSource } from "./templates.mjs";
19
19
 
20
20
  const git = (args, opts = {}) => spawnSync("git", args, { encoding: "utf8", timeout: 120_000, ...opts });
21
- const skillName = SKILL_ROOT.split("/").pop();
21
+ const skillName = basename(SKILL_ROOT);
22
22
  const { repo } = installSource();
23
23
 
24
24
  const out = { skill: {}, templates: {}, update: {} };