@wix/agent-skills 1.31.0 → 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 (115) hide show
  1. package/package.json +1 -1
  2. package/skills/wix-app/SKILL.md +7 -8
  3. package/skills/wix-app/references/DASHBOARD_PAGE.md +1 -1
  4. package/skills/wix-app/references/STORES_VERSIONING.md +1 -1
  5. package/skills/wix-app/references/WIX_PATTERNS_DOCS.md +11 -50
  6. package/skills/wix-app/references/dashboard-page/DATA_SOURCES.md +14 -10
  7. package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE.md +5 -8
  8. package/skills/wix-app/references/dashboard-page/TABLE_STATE.md +5 -4
  9. package/skills/wix-headless-kit/SKILL.md +31 -14
  10. package/skills/wix-headless-kit/guides/api-run.md +116 -31
  11. package/skills/wix-headless-kit/guides/existing-site.md +9 -2
  12. package/skills/wix-headless-kit/guides/reference-mode.md +9 -1
  13. package/skills/wix-headless-kit/install/AGENTS.md +10 -7
  14. package/skills/wix-headless-kit/install/attach.mjs +35 -30
  15. package/skills/wix-headless-kit/install/background.mjs +32 -0
  16. package/skills/wix-headless-kit/install/bootstrap.mjs +28 -11
  17. package/skills/wix-headless-kit/install/check.mjs +2 -2
  18. package/skills/wix-headless-kit/install/context.mjs +62 -8
  19. package/skills/wix-headless-kit/install/deploy.mjs +36 -11
  20. package/skills/wix-headless-kit/install/env-ignore.mjs +16 -0
  21. package/skills/wix-headless-kit/install/install.mjs +95 -0
  22. package/skills/wix-headless-kit/install/pins.json +1 -1
  23. package/skills/wix-headless-kit/install/proc.mjs +46 -0
  24. package/skills/wix-headless-kit/install/setup.mjs +18 -27
  25. package/skills/wix-headless-kit/install/templates.mjs +4 -3
  26. package/skills/wix-headless-templates/blog/app/components/blog/RichContent.tsx +3 -2
  27. package/skills/wix-headless-templates/blog/project/package-lock.json +1287 -1299
  28. package/skills/wix-headless-templates/blog/project/package.json +1 -1
  29. package/skills/wix-headless-templates/blog/project/src/components/blog/RichContent.tsx +3 -2
  30. package/skills/wix-headless-templates/blog/rest/authors.ts +5 -5
  31. package/skills/wix-headless-templates/blog/rest/comments.ts +4 -4
  32. package/skills/wix-headless-templates/blog/rest/post-likes.ts +3 -3
  33. package/skills/wix-headless-templates/blog/rest/posts.ts +4 -4
  34. package/skills/wix-headless-templates/blog/rest/taxonomy.ts +5 -5
  35. package/skills/wix-headless-templates/blog/seed/SEED.md +1 -1
  36. package/skills/wix-headless-templates/blog/seed/seed-blog.mjs +4 -3
  37. package/skills/wix-headless-templates/bookings/rest/booking.ts +4 -4
  38. package/skills/wix-headless-templates/bookings/rest/services.ts +4 -4
  39. package/skills/wix-headless-templates/bookings/seed/SEED.md +1 -1
  40. package/skills/wix-headless-templates/bookings/seed/seed-bookings.mjs +4 -2
  41. package/skills/wix-headless-templates/cms/rest/collections.ts +3 -3
  42. package/skills/wix-headless-templates/cms/rest/items.ts +4 -4
  43. package/skills/wix-headless-templates/cms/seed/seed-cms.mjs +2 -1
  44. package/skills/wix-headless-templates/donations/rest/campaigns.ts +4 -4
  45. package/skills/wix-headless-templates/donations/rest/donate.ts +3 -3
  46. package/skills/wix-headless-templates/donations/seed/SEED.md +1 -1
  47. package/skills/wix-headless-templates/donations/seed/seed-donations.mjs +5 -3
  48. package/skills/wix-headless-templates/events/rest/events.ts +4 -4
  49. package/skills/wix-headless-templates/events/rest/orders.ts +4 -4
  50. package/skills/wix-headless-templates/events/rest/registration.ts +4 -4
  51. package/skills/wix-headless-templates/events/seed/SEED.md +1 -1
  52. package/skills/wix-headless-templates/events/seed/seed-events.mjs +5 -3
  53. package/skills/wix-headless-templates/faq/rest/faq.ts +4 -4
  54. package/skills/wix-headless-templates/faq/seed/seed-faq.mjs +2 -1
  55. package/skills/wix-headless-templates/forms/rest/forms.ts +4 -4
  56. package/skills/wix-headless-templates/forms/rest/submissions.ts +4 -4
  57. package/skills/wix-headless-templates/forms/seed/seed-forms.mjs +2 -1
  58. package/skills/wix-headless-templates/members/rest/auth.ts +3 -3
  59. package/skills/wix-headless-templates/members/rest/members.ts +4 -4
  60. package/skills/wix-headless-templates/members/seed/seed-members.mjs +2 -1
  61. package/skills/wix-headless-templates/portfolio/rest/portfolio.ts +4 -4
  62. package/skills/wix-headless-templates/portfolio/seed/SEED.md +4 -2
  63. package/skills/wix-headless-templates/portfolio/seed/seed-portfolio.mjs +8 -7
  64. package/skills/wix-headless-templates/pricing-plans/rest/plan-access.ts +3 -3
  65. package/skills/wix-headless-templates/pricing-plans/rest/plans.ts +4 -4
  66. package/skills/wix-headless-templates/pricing-plans/rest/purchase.ts +3 -3
  67. package/skills/wix-headless-templates/pricing-plans/seed/seed-pricing-plans.mjs +2 -1
  68. package/skills/wix-headless-templates/rentals/rest/rentals.ts +4 -4
  69. package/skills/wix-headless-templates/rentals/seed/SEED.md +1 -1
  70. package/skills/wix-headless-templates/rentals/seed/seed-rentals.mjs +4 -2
  71. package/skills/wix-headless-templates/restaurants/rest/menu.ts +4 -4
  72. package/skills/wix-headless-templates/restaurants/rest/ordering.ts +5 -5
  73. package/skills/wix-headless-templates/restaurants/rest/reservations.ts +4 -4
  74. package/skills/wix-headless-templates/restaurants/seed/SEED.md +1 -1
  75. package/skills/wix-headless-templates/restaurants/seed/seed-restaurants.mjs +6 -4
  76. package/skills/wix-headless-templates/shared/capabilities/site-search/rest/search.ts +4 -4
  77. package/skills/wix-headless-templates/shared/capabilities/site-search/seed/install.mjs +6 -4
  78. package/skills/wix-headless-templates/shared/seed/images.mjs +41 -5
  79. package/skills/wix-headless-templates/shared/seed/main.mjs +15 -0
  80. package/skills/wix-headless-templates/shared/seed/wix-cli.mjs +43 -11
  81. package/skills/wix-headless-templates/storefront/INSTRUCTIONS.md +1 -1
  82. package/skills/wix-headless-templates/storefront/rest/cart.ts +5 -5
  83. package/skills/wix-headless-templates/storefront/rest/catalog.ts +5 -5
  84. package/skills/wix-headless-templates/storefront/seed/SEED.md +44 -14
  85. package/skills/wix-headless-templates/storefront/seed/seed-store.mjs +23 -15
  86. package/skills/wix-manage/SKILL.md +28 -18
  87. package/skills/wix-manage/references/app-installation/install-wix-apps.md +5 -1
  88. package/skills/wix-manage/references/app-installation/list-installed-apps.md +19 -9
  89. package/skills/wix-manage/references/automations/activation-status.md +52 -0
  90. package/skills/wix-manage/references/automations/api-catalog.md +81 -0
  91. package/skills/wix-manage/references/automations/build-and-manage-automations.md +34 -0
  92. package/skills/wix-manage/references/bookings/booking-service-policy-setup.md +3 -0
  93. package/skills/wix-manage/references/bookings/create-and-update-booking-services.md +277 -363
  94. package/skills/wix-manage/references/bookings/external-calendar-integration.md +3 -0
  95. package/skills/wix-manage/references/bookings/multi-resource-service-creation.md +3 -0
  96. package/skills/wix-manage/references/cms/cms-data-items-crud.md +11 -2
  97. package/skills/wix-manage/references/cms/cms-publishing-flow.md +63 -16
  98. package/skills/wix-manage/references/custom-apps/use-app-skills-and-app-tools.md +3 -1
  99. package/skills/wix-manage/references/forms/update-form.md +3 -0
  100. package/skills/wix-manage/references/pricing-plans/create-and-update-pricing-plans.md +3 -0
  101. package/skills/wix-manage/references/restaurants/wix-restaurants-setup.md +3 -0
  102. package/skills/wix-manage/references/sites/create-headless-site.md +7 -50
  103. package/skills/wix-manage/references/sites/manage-oauth-apps.md +4 -4
  104. package/skills/wix-manage/references/sites/read-site-context.md +4 -0
  105. package/skills/wix-manage/references/sites/send-feedback-to-wix.md +92 -0
  106. package/skills/wix-manage/references/sites/upload-static-site.md +298 -283
  107. package/skills/wix-manage/references/stores/add-store-pages-to-site.md +3 -0
  108. package/skills/wix-manage/references/stores/change-store-currency.md +3 -0
  109. package/skills/wix-manage/references/stores/create-product-catalog-v3.md +3 -0
  110. package/skills/wix-manage/references/stores/find-products-query-and-search-catalog-v3.md +3 -0
  111. package/skills/wix-manage/references/stores/update-product-with-options.md +192 -22
  112. package/skills/wix-manage/references/bookings/create-appointment-service.md +0 -243
  113. package/skills/wix-manage/references/bookings/create-booking-service-from-prompt.md +0 -31
  114. package/skills/wix-manage/references/bookings/create-class-service.md +0 -222
  115. package/skills/wix-manage/references/bookings/create-course-service.md +0 -251
@@ -1,10 +1,18 @@
1
1
  # The kit's run as Wix API calls
2
2
 
3
- Read when the commands this skill is built on cannot run where you are, or stopped: no shell, no
4
- file system, a sandbox that blocks the install, the CLI or the login, or a host that only talks to
5
- Wix through the Wix MCP. It walks the run of SKILL.md step by step and names, for each, the Wix
6
- API call the CLI or the script performs and the file beside this skill that carries the contract;
7
- where a script can run where you are, it says so.
3
+ Read when the commands this skill is built on cannot run where you are, or stopped: your shell
4
+ can't reach Wix, there is no shell, or the install, the
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. 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
9
+ SKILL.md step by step and names, for each, the Wix API call the CLI or the script performs and the
10
+ file beside this skill that carries the contract; where a script can run where you are, it says so.
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.
8
16
 
9
17
  ## Where these skills are
10
18
 
@@ -13,20 +21,40 @@ This skill is one of a set, and the guide reads from three of them:
13
21
  - `{SKILL_ROOT}` — this skill, `wix-headless-kit`: the run, its guides, the Verticals table.
14
22
  - `{TEMPLATES}` — `wix-headless-templates`: the shipped, verified code per Wix Business Solution,
15
23
  one folder each, with its `INSTRUCTIONS.md` (the contracts), `seed/` (the seed script and
16
- `SEED.md`) and `rest/` (the data layer over plain `fetch`). Installed beside this skill, or
17
- fetched to `{SKILL_ROOT}/templates/`.
24
+ `SEED.md`) and `rest/` (the data layer over plain `fetch`). Not every install carries it (a
25
+ plugin may leave it out): it is installed beside this skill, or fetched to
26
+ `{SKILL_ROOT}/templates/` by `node {SKILL_ROOT}/install/templates.mjs` (a git clone), or installed
27
+ with `npx skills@latest add wix/skills --skill wix-headless-templates`; with none of those, read
28
+ online at `https://www.wix.com/skills/wix-headless-templates/{path}`.
18
29
  - `{MANAGE}` — `wix-manage`: REST recipes to configure and manage a site's business solutions, one
19
30
  recipe per operation with the exact endpoint and payload; its `SKILL.md` is the index.
20
31
  - `{DOCS}` — `wix-docs`: how to look up the Wix API and SDK documentation and confirm a method's
21
32
  exact shape before writing a call.
22
33
 
23
- Installed, they sit beside each other under `.agents/skills/`. Online, they are published alongside
24
- each other under the same names, each with a manifest of its files, and in the `wix/skills`
25
- repository on GitHub under `skills/{skill}/`; a sandbox that reaches GitHub has the whole set on
26
- disk from a clone.
34
+ Installed, they sit beside each other wherever your client keeps them: under `.agents/skills/` after
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
41
+ `wix/skills` repository on GitHub under `skills/{skill}/`; a sandbox that reaches GitHub has the
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.
27
54
 
28
55
  What such a run does not produce: the Astro build and its release. Those need a machine with the
29
- CLI. Everything else the kit does to a site, it does through calls you can make.
56
+ CLI. Everything else the kit does to a site, it does through calls you can make, the frontend
57
+ included: step 5 ships it as static files.
30
58
 
31
59
  ## 0. Identity
32
60
 
@@ -42,6 +70,9 @@ Unchanged from SKILL.md step 2 and its Verticals table: the brief names the busi
42
70
  names the solutions it needs. Read each solution's `{TEMPLATES}/{solution}/INSTRUCTIONS.md` for
43
71
  what it covers and `{TEMPLATES}/{solution}/seed/SEED.md` for what a seeded site holds.
44
72
 
73
+ A brief that needs no solution (a game, a landing page, a tool) is a run of two steps: the site
74
+ (step 2, with no `seedOptions`) and the frontend (step 5). No apps, no seed.
75
+
45
76
  ## 2. The site
46
77
 
47
78
  The CLI's `npm create @wix/new` is one call: `POST /headless-business-setup/v1/headless-business/provision`,
@@ -63,23 +94,45 @@ Restaurants, Donations, FAQ), goes on with the Apps Installer:
63
94
  ## 4. The seed
64
95
 
65
96
  `seed-{solution}.mjs` is a sequence of REST calls, and `SEED.md` is its contract: the plan shape
66
- (what to create, how many, which images), the order the entities need, and the traps the script
67
- encodes (a Bookings service needs a category to be visible and takes resource ids, not staff ids;
68
- a Forms field is registered by its `validation` block; a product's choice photos are linked after
69
- the gallery holds them). Read `SEED.md`, read the script for any call `SEED.md` only names, and make the same
70
- calls through `{MANAGE}`'s recipe for each (services, products, posts, events, collections). Keep
71
- the script's rules: create, never delete, and report what the site already held. Image prompts
72
- need the Media Manager; without it, products and services stay text-only, say so.
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.
73
116
 
74
117
  ## 5. The frontend
75
118
 
119
+ The run is not done until the pages use what the backend holds: every control the page shows for
120
+ it (add to cart, the cart, book, submit) works on the live site. A backend with untouched pages is
121
+ half a run.
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
+
76
126
  The Astro pages cannot be released here: that is a run with a machine and a CLI login, which
77
127
  attaches to this site (`guides/existing-site.md`) rather than making a new one. What ships from here
78
128
  is static files, dropped onto the site from step 2 through
79
- `{MANAGE}/references/sites/upload-static-site.md`. Which files depends on whether the compose can
80
- run.
129
+ `{MANAGE}/references/sites/upload-static-site.md`: the Wix MCP's upload tool, or a script that
130
+ downloads what the site serves, edits it and drops it back, so only what changed is written. Which
131
+ files depends on whether the compose can run, and on what writing them costs: when files reach the
132
+ upload tool as files (its `attachments`), anything on your disk travels as it is; when they don't,
133
+ every file you send is text you write, and the smallest set wins.
81
134
 
82
- **When the compose can run** (the skills on disk and Node), the frontend is the kit's own shipped code. The compose
135
+ **When the compose can run** (the skills on disk, Node, and npm reachable, for the TypeScript compiler it fetches), the frontend is the kit's own shipped code. The compose
83
136
  `{SKILL_ROOT}/install/deploy.mjs {solution} --stack static --out site --client-id {appId}` fetches the
84
137
  templates and writes the solution's REST data layer and stores to `site/js/wix/` as plain ES modules
85
138
  (`guides/reference-mode.md`, the static site): the transport, the cores, the state machines, verified,
@@ -89,9 +142,11 @@ takes the complete file set in one call and a response has a hard size limit; th
89
142
  later" section downloads what the site serves, so a later drop adds files to a live set without
90
143
  resending what is already there.
91
144
 
92
- **When it cannot**, a page that loads the Wix SDK from a package CDN and talks to the site as a
93
- visitor. A few files at most, each written in full before it is dropped; the same size limit and
94
- the same way to add files to a live set apply. The
145
+ **When it cannot**, or when its output would all have to be written out by hand, a page that loads
146
+ the Wix SDK from a package CDN and talks to the site as a visitor. One file per call, a few hundred lines, written in full inside that call: a response has a
147
+ hard size limit, and a call that carries the whole frontend at once exceeds it. A second file joins
148
+ the first the recipe's way, "Change it later": the same call downloads the live set and drops the
149
+ union. The
95
150
  SDK comes from a pinned package URL, `https://esm.sh/@wix/sdk@{version}` and the solution's package
96
151
  (`@wix/bookings`, `@wix/stores`, `@wix/blog`, …); the client is
97
152
  `createClient({ modules: { … }, auth: OAuthStrategy({ clientId }) })` with the `appId` the provision
@@ -102,9 +157,17 @@ holds; nothing invented, as in SKILL.md step 4.
102
157
  **The calls**, on that second path, take their shape from the kit's own transport for the solution,
103
158
  `{TEMPLATES}/{solution}/app/wix/{solution}/*.ts` (`services.ts`, `booking.ts`, `catalog.ts`, …,
104
159
  with `{TEMPLATES}/shared/app/wix/sdk.ts` for the client wiring): those files call the same SDK
105
- modules the page imports, and they are verified. Copy the form of the call, not the file. The
106
- cores beside them (`*-core.ts`) hold the rules the page needs and nothing else does: which fields
107
- to read, how a price or a duration is formatted, which slot is bookable, how an id is read.
160
+ modules the page imports, and they are verified. Copy the form of the call, not the file. A page
161
+ that talks to Wix over plain `fetch`, with no SDK, takes them from the REST twins instead:
162
+ `{TEMPLATES}/shared/rest/client.ts` (the visitor token and the request) and
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.
108
171
 
109
172
  **A call those files don't cover** is confirmed before it is written, the way `{DOCS}` describes:
110
173
  the method's SDK page, and when the page and the package disagree, the package's own type
@@ -112,9 +175,31 @@ declarations (the `@wix/auto_sdk_{solution}_<module>` package the solution packa
112
175
  its `index.d.ts`) are the truth. Two shapes that recur, as examples of what the types settle and
113
176
  the kit's files already encode: SDK query methods return a query builder finished with `.find()`;
114
177
  and an entity's id arrives as `_id` on some objects and `id` on others (the kit reads both,
115
- `rawId`). Dates travel as local wall-clock strings in the business time zone. A page cannot be run
116
- here, so a wrong shape fails silently in the browser; the file beside the skill is the check that is
117
- available.
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.
118
203
 
119
204
  ## 6. Closing
120
205
 
@@ -7,7 +7,8 @@ is the create flow's job on a site made for the run — a folder holding only a
7
7
  is that case too: `init` made the site; SKILL.md step 3). (A project downloaded from Wix whose `.env.local` declares a
8
8
  migration is the other way to arrive at an existing site: `guides/migration.md`; the reading
9
9
  part below applies there too, against the migrated site.) `<SKILL_ROOT>` is the installed skill folder; the shipped code is
10
- under `<SKILL_ROOT>/templates/` (SKILL.md, "The run").
10
+ in the folder `node <SKILL_ROOT>/install/templates.mjs` prints (`<templates>` below; SKILL.md, "The
11
+ model").
11
12
 
12
13
  **Read the site, then run attach.** First, one call tells you what
13
14
  the site is — its name, currency, and the Wix apps installed on it:
@@ -26,6 +27,12 @@ shape as setup (SKILL.md step 3):
26
27
  node <SKILL_ROOT>/install/attach.mjs --site <siteId> --business-name "<site name>" --vertical <vertical>[,<vertical>] [--plan plan.json]
27
28
  ```
28
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
+
29
36
  `init`/`wix create` always create a site, so they are not used here. attach does what they do
30
37
  after creating one — the site's OAuth app, Wix hosting, `wix.config.json` — against the site
31
38
  given, copies the first vertical's composed template, deploys the rest, and starts the install
@@ -46,7 +53,7 @@ for: what the chosen verticals will render, roughly how much of it, and what it
46
53
  six items in three groups design differently from six hundred. Run the vertical's reader:
47
54
 
48
55
  ```bash
49
- node <SKILL_ROOT>/templates/<vertical>/seed/read-site.mjs --site <siteId> [--limit <n>]
56
+ node <templates>/<vertical>/seed/read-site.mjs --site <siteId> [--limit <n>]
50
57
  ```
51
58
 
52
59
  It prints one JSON: whether the vertical's app is installed, counts, one page of each entity
@@ -1,7 +1,9 @@
1
1
  # Reference mode: a static site, or a server-rendered app in another language
2
2
 
3
3
  Read when SKILL.md step 1 resolves to a stack that cannot run `app/`. `<SKILL_ROOT>` is the
4
- installed skill folder; the shipped code is under `<SKILL_ROOT>/templates/`. Steps 2 to 5 of
4
+ installed skill folder; the shipped code is in the folder `node <SKILL_ROOT>/install/templates.mjs`
5
+ prints (the `wix-headless-templates` skill beside this one, or a copy fetched into
6
+ `<SKILL_ROOT>/templates/`), and every `templates/…` path here is relative to it. Steps 2 to 5 of
5
7
  SKILL.md still apply; this file is the mechanics that differ.
6
8
 
7
9
  The data layer ships a second time as a **REST layer**: `templates/shared/rest/` (the auth seam
@@ -31,6 +33,12 @@ two transports: with `--plan` its REST twin and stores land in `js/wix/` beside
31
33
  to it. Seed per the vertical's `SEED.md` (Node + the CLI token, no project dependencies).
32
34
  Release with `npx @wix/cli@latest release` — no build. Item-page tags come from the entity's
33
35
  `seoData`, set after the fetch (`document.title`, the meta description).
36
+ - **A dropped site on its Wix Dev Machine** (shape `static-in-astro`). The machine holds the
37
+ dropped pages in `public/` of an Astro starter with no pages of its own, and its Astro config
38
+ redirects `/` to `/index.html`. The pages stay where they are: run
39
+ `deploy.mjs <vertical> --stack static --out public`, which writes `public/js/wix/`, and wire the
40
+ pages in place as above. The config is left as it is, so the release goes through Astro:
41
+ `npx @wix/cli@latest build && npx @wix/cli@latest release`, to the same URL.
34
42
  - **Server-rendered, another language (Flask, Laravel, Rails, …).** The same shape as managed
35
43
  Astro — pages rendered on the server, the interactive surfaces in the browser — with hosting and
36
44
  SEO plumbing theirs. `init` still runs in the project folder; run `deploy.mjs <vertical> --stack
@@ -17,15 +17,18 @@ 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
- - `{{SKILL}}` — the code in this app and how to extend it. The shipped code sits in
24
+ - `{{SKILL}}` — the code in this app and how to extend it. The shipped code is the
25
+ `wix-headless-templates` skill: `node .agents/skills/{{SKILL}}/install/templates.mjs` prints where it
26
+ is (`.agents/skills/wix-headless-templates/` when installed beside this skill, else
24
27
  `.agents/skills/{{SKILL}}/templates/`, fetched once from the skill's repository and kept with this
25
- project at the commit it was built from (`templates/.source`); if that folder is missing,
26
- `node .agents/skills/{{SKILL}}/install/templates.mjs` fetches it. Each business solution has a playbook at
27
- `templates/<solution>/INSTRUCTIONS.md`; `node .agents/skills/{{SKILL}}/install/deploy.mjs <solution> --stack {{STACK}}`
28
- adds one; `node .agents/skills/{{SKILL}}/templates/<solution>/seed/read-site.mjs` reads what the
28
+ project at the commit it was built from, `templates/.source`) and fetches it when neither exists;
29
+ `<templates>` below is that folder. Each business solution has a playbook at
30
+ `<templates>/<solution>/INSTRUCTIONS.md`; `node .agents/skills/{{SKILL}}/install/deploy.mjs <solution> --stack {{STACK}}`
31
+ adds one; `node <templates>/<solution>/seed/read-site.mjs` reads what the
29
32
  site holds for it, with the documentation URL of every call it made. A code change ends with a release; the live URL shows it, the dev server does not count.
30
33
  `node .agents/skills/{{SKILL}}/install/check.mjs` says whether the skill or its templates have a newer
31
34
  version and prints the update commands; an update touches the skill folder or `templates/`, never `src/`.
@@ -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 }));
@@ -304,7 +315,7 @@ emit("agent_configs", writeAgentsMd(projectDir, { skill: basename(SKILL_ROOT), s
304
315
  if (mode !== "link" && (stack !== "astro" || mode === "config-only")) {
305
316
  emit("ready", { projectDir, siteId, appId, baseUrl, hosting, frontend, stack, dashboardUrl: `https://manage.wix.com/dashboard/${siteId}`,
306
317
  next: `scaffold the ${stack} project in this folder per SKILL.md, then deploy.mjs <vertical…> --stack ${stack}${planPath ? ` --plan ${planPath}` : ""} (the client id is read from wix.config.json); ` +
307
- (planPath ? `then seed with the plan: node <SKILL_ROOT>/templates/${verticals[0]}/seed/seed-<vertical>.mjs ${planPath}` : `no plan given, so nothing seeds — the site owns its content`) +
318
+ (planPath ? `then seed with the plan: node ${join(TEMPLATES, verticals[0], "seed", `seed-${verticals[0]}.mjs`)} ${planPath}` : `no plan given, so nothing seeds — the site owns its content`) +
308
319
  (hosting === "self" ? `; you host it: origins on the OAuth app allow-list now: ${origins.join(", ") || "none — add them before the first checkout test"}` : "") });
309
320
  process.exit(0);
310
321
  }
@@ -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,20 +349,15 @@ 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 {
351
356
  if (siteOrigin === "init") {
352
- emit("note", { step: "seed", detail: `this site was made for this run (init) and is empty: draft a plan per templates/${verticals[0]}/seed/SEED.md — the brief's content, else demo content — and run node <SKILL_ROOT>/templates/${verticals[0]}/seed/seed-<vertical>.mjs plan.json from the project root` });
357
+ emit("note", { step: "seed", detail: `this site was made for this run (init) and is empty: draft a plan per ${join(TEMPLATES, verticals[0], "seed", "SEED.md")} — the brief's content, else demo content — and run node <SKILL_ROOT>/templates/${verticals[0]}/seed/seed-<vertical>.mjs plan.json from the project root` });
353
358
  }
354
359
  if (verticals.includes("members")) {
355
- emit("note", { step: "seed", detail: "members: the Members Area app (the profile layer, no content) is installed by templates/members/seed/seed-members.mjs — run it unless the site already has the app" });
360
+ emit("note", { step: "seed", detail: `members: the Members Area app (the profile layer, no content) is installed by ${join(TEMPLATES, "members", "seed", "seed-members.mjs")} — run it unless the site already has the app` });
356
361
  }
357
362
  }
358
363
 
@@ -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: {} };