@wix/himalaya-cli 0.1116.0 → 0.1118.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -13266,12 +13266,12 @@ function stripBrandFromIdiom(tokens) {
13266
13266
  for (const key2 of BRAND_COLOR_TOKENS) delete colors[key2];
13267
13267
  if (Object.keys(colors).length === 0) delete o.colors;
13268
13268
  }
13269
- const shadows = o.shadows;
13270
- const glow = shadows && typeof shadows === "object" && !Array.isArray(shadows) ? shadows[BRAND_GLOW_SHADOW] : void 0;
13269
+ const shadows2 = o.shadows;
13270
+ const glow = shadows2 && typeof shadows2 === "object" && !Array.isArray(shadows2) ? shadows2[BRAND_GLOW_SHADOW] : void 0;
13271
13271
  if (glow && typeof glow === "object" && !Array.isArray(glow) && "color" in glow) {
13272
13272
  delete glow.color;
13273
- if (Object.keys(glow).length === 0) delete shadows[BRAND_GLOW_SHADOW];
13274
- if (Object.keys(shadows).length === 0) delete o.shadows;
13273
+ if (Object.keys(glow).length === 0) delete shadows2[BRAND_GLOW_SHADOW];
13274
+ if (Object.keys(shadows2).length === 0) delete o.shadows;
13275
13275
  }
13276
13276
  }
13277
13277
  }
@@ -13363,9 +13363,9 @@ function tokenColorIssues(dir, tokensPathOverride) {
13363
13363
  error(`${tokensPath}: must be an object`);
13364
13364
  return issues;
13365
13365
  }
13366
- const shadows = raw.shadows;
13367
- if (shadows !== null && typeof shadows === "object" && !Array.isArray(shadows)) {
13368
- for (const [name, token] of Object.entries(shadows)) {
13366
+ const shadows2 = raw.shadows;
13367
+ if (shadows2 !== null && typeof shadows2 === "object" && !Array.isArray(shadows2)) {
13368
+ for (const [name, token] of Object.entries(shadows2)) {
13369
13369
  const value = token?.color;
13370
13370
  if (value === void 0) continue;
13371
13371
  if (typeof value === "string" && isCanonicalHex(value)) continue;
@@ -19946,6 +19946,7 @@ function deriveAppMeta(name, m, platforms = {}) {
19946
19946
  drawer: config.navigation?.drawer,
19947
19947
  navKind: config.navigation?.navKind,
19948
19948
  orientation: config.navigation?.orientation,
19949
+ cache: config.cache,
19949
19950
  sidebar: config.navigation?.sidebar,
19950
19951
  navLogicBundle: config.navigation?.navLogicBundle,
19951
19952
  appIconBadgeSources: config.navigation?.appIconBadgeSources,
@@ -24718,6 +24719,7 @@ function shouldRevealError(args) {
24718
24719
  var EMAIL_RE, URL_HOST_RE, INTEGER_RE, NUMBER_RE;
24719
24720
  var init_validation = __esm({
24720
24721
  "../../core/ts/src/validation.ts"() {
24722
+ "use strict";
24721
24723
  EMAIL_RE = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;
24722
24724
  URL_HOST_RE = /^[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*(:[0-9]+)?$/;
24723
24725
  INTEGER_RE = /^[+-]?[0-9]+$/;
@@ -29118,6 +29120,7 @@ function validateWorkerSubtree(raw, slotId, policy, actionCatalog) {
29118
29120
  var WORKER_SUBTREE_CEILINGS, ID_PART, ACTION_ID;
29119
29121
  var init_worker_subtree = __esm({
29120
29122
  "../../core/ts/src/worker-subtree.ts"() {
29123
+ "use strict";
29121
29124
  init_worker_subtree_safe_types_generated();
29122
29125
  WORKER_SUBTREE_CEILINGS = Object.freeze({
29123
29126
  maxEncodedBytes: 65536,
@@ -36087,24 +36090,172 @@ var init_render_fonts = __esm({
36087
36090
  }
36088
36091
  });
36089
36092
 
36093
+ // ../../core/dev-server/src/wix-cache-defaults.generated.ts
36094
+ var PLATFORM_DEFAULT_WIX_CACHE_RULES;
36095
+ var init_wix_cache_defaults_generated = __esm({
36096
+ "../../core/dev-server/src/wix-cache-defaults.generated.ts"() {
36097
+ PLATFORM_DEFAULT_WIX_CACHE_RULES = [
36098
+ { method: "POST", pathPrefix: "/_api/bookings/v2/services/search", cacheable: true, ttl: 60, swr: 86400, tag: "bookings/services" },
36099
+ { method: "POST", pathPrefix: "/blog/v3/categories/query", cacheable: true, ttl: 60, swr: 86400, tag: "blog" },
36100
+ { method: "GET", pathPrefix: "/blog/v3/posts/", cacheable: true, ttl: 60, swr: 86400, tag: "blog" },
36101
+ { method: "POST", pathPrefix: "/blog/v3/posts/query", cacheable: true, ttl: 60, swr: 86400, tag: "blog" },
36102
+ { method: "GET", pathPrefix: "/bookings/v2/services/", cacheable: true, ttl: 60, swr: 86400, tag: "bookings/services" },
36103
+ { method: "POST", pathPrefix: "/bookings/v2/services/query", cacheable: true, ttl: 60, swr: 86400, tag: "bookings/services" },
36104
+ { method: "GET", pathPrefix: "/donation-campaigns/v2/donation-campaigns/", cacheable: true, ttl: 60, swr: 86400, tag: "donations" },
36105
+ { method: "POST", pathPrefix: "/donation-campaigns/v2/donation-campaigns/query", cacheable: true, ttl: 60, swr: 86400, tag: "donations" },
36106
+ { method: "GET", pathPrefix: "/events/v3/events/", cacheable: true, ttl: 60, swr: 86400, tag: "events" },
36107
+ { method: "GET", pathPrefix: "/events/v3/events/category/", cacheable: true, ttl: 60, swr: 86400, tag: "events" },
36108
+ { method: "POST", pathPrefix: "/events/v3/events/query", cacheable: true, ttl: 60, swr: 86400, tag: "events" },
36109
+ { method: "GET", pathPrefix: "/events/v3/events/slug/", cacheable: true, ttl: 60, swr: 86400, tag: "events" },
36110
+ { method: "GET", pathPrefix: "/faq/v2/categories", cacheable: true, ttl: 60, swr: 86400, tag: "faq" },
36111
+ { method: "GET", pathPrefix: "/faq/v2/categories/", cacheable: true, ttl: 60, swr: 86400, tag: "faq" },
36112
+ { method: "POST", pathPrefix: "/faq/v2/categories/query", cacheable: true, ttl: 60, swr: 86400, tag: "faq" },
36113
+ { method: "GET", pathPrefix: "/faq/v2/question-entries", cacheable: true, ttl: 60, swr: 86400, tag: "faq" },
36114
+ { method: "GET", pathPrefix: "/faq/v2/question-entries/", cacheable: true, ttl: 60, swr: 86400, tag: "faq" },
36115
+ { method: "POST", pathPrefix: "/faq/v2/question-entries/query", cacheable: true, ttl: 60, swr: 86400, tag: "faq" },
36116
+ { method: "POST", pathPrefix: "/online-programs/v3/programs/query", cacheable: true, ttl: 60, swr: 86400, tag: "online-programs" },
36117
+ { method: "GET", pathPrefix: "/portfolio/v1/collections", cacheable: true, ttl: 60, swr: 86400, tag: "portfolio" },
36118
+ { method: "GET", pathPrefix: "/portfolio/v1/collections/", cacheable: true, ttl: 60, swr: 86400, tag: "portfolio" },
36119
+ { method: "POST", pathPrefix: "/portfolio/v1/collections/query", cacheable: true, ttl: 60, swr: 86400, tag: "portfolio" },
36120
+ { method: "GET", pathPrefix: "/portfolio/v1/projects", cacheable: true, ttl: 60, swr: 86400, tag: "portfolio" },
36121
+ { method: "GET", pathPrefix: "/portfolio/v1/projects/", cacheable: true, ttl: 60, swr: 86400, tag: "portfolio" },
36122
+ { method: "POST", pathPrefix: "/portfolio/v1/projects/collections/query", cacheable: true, ttl: 60, swr: 86400, tag: "portfolio" },
36123
+ { method: "POST", pathPrefix: "/portfolio/v1/projects/query", cacheable: true, ttl: 60, swr: 86400, tag: "portfolio" },
36124
+ { method: "GET", pathPrefix: "/pricing-plans/v2/plans/public", cacheable: true, ttl: 60, swr: 86400, tag: "pricing-plans" },
36125
+ { method: "POST", pathPrefix: "/pricing-plans/v2/plans/public/query", cacheable: true, ttl: 60, swr: 86400, tag: "pricing-plans" },
36126
+ { method: "GET", pathPrefix: "/restaurants/menus/v1/items/", cacheable: true, ttl: 60, swr: 86400, tag: "restaurants/menus" },
36127
+ { method: "POST", pathPrefix: "/restaurants/menus/v1/items/query", cacheable: true, ttl: 60, swr: 86400, tag: "restaurants/menus" },
36128
+ { method: "GET", pathPrefix: "/restaurants/menus/v1/menus/", cacheable: true, ttl: 60, swr: 86400, tag: "restaurants/menus" },
36129
+ { method: "POST", pathPrefix: "/restaurants/menus/v1/menus/query", cacheable: true, ttl: 60, swr: 86400, tag: "restaurants/menus" },
36130
+ { method: "POST", pathPrefix: "/restaurants/menus/v1/sections/query", cacheable: true, ttl: 60, swr: 86400, tag: "restaurants/menus" },
36131
+ { method: "GET", pathPrefix: "/reviews/v1/reviews/", cacheable: true, ttl: 60, swr: 86400, tag: "reviews" },
36132
+ { method: "POST", pathPrefix: "/reviews/v1/reviews/query", cacheable: true, ttl: 60, swr: 86400, tag: "reviews" },
36133
+ { method: "GET", pathPrefix: "/stores/v1/products/", cacheable: true, ttl: 60, swr: 86400, tag: "stores/products" },
36134
+ { method: "POST", pathPrefix: "/stores/v1/products/query", cacheable: true, ttl: 60, swr: 86400, tag: "stores/products" },
36135
+ { method: "POST", pathPrefix: "/stores/v3/categories/query", cacheable: true, ttl: 60, swr: 86400, tag: "stores/categories" },
36136
+ { method: "GET", pathPrefix: "/stores/v3/products/", cacheable: true, ttl: 60, swr: 86400, tag: "stores/products" },
36137
+ { method: "POST", pathPrefix: "/stores/v3/products/query", cacheable: true, ttl: 60, swr: 86400, tag: "stores/products" },
36138
+ { method: "POST", pathPrefix: "/stores/v3/products/search", cacheable: true, ttl: 60, swr: 86400, tag: "stores/products" },
36139
+ { method: "DELETE", pathPrefix: "/blog/v3/categories", cacheable: false, ttl: 0, swr: 0, tag: "blog" },
36140
+ { method: "PATCH", pathPrefix: "/blog/v3/categories", cacheable: false, ttl: 0, swr: 0, tag: "blog" },
36141
+ { method: "POST", pathPrefix: "/blog/v3/categories", cacheable: false, ttl: 0, swr: 0, tag: "blog" },
36142
+ { method: "DELETE", pathPrefix: "/blog/v3/posts", cacheable: false, ttl: 0, swr: 0, tag: "blog" },
36143
+ { method: "PATCH", pathPrefix: "/blog/v3/posts", cacheable: false, ttl: 0, swr: 0, tag: "blog" },
36144
+ { method: "POST", pathPrefix: "/blog/v3/posts", cacheable: false, ttl: 0, swr: 0, tag: "blog" },
36145
+ { method: "DELETE", pathPrefix: "/bookings/v1/services", cacheable: false, ttl: 0, swr: 0, tag: "bookings/services" },
36146
+ { method: "POST", pathPrefix: "/bookings/v1/services", cacheable: false, ttl: 0, swr: 0, tag: "bookings/services" },
36147
+ { method: "PUT", pathPrefix: "/bookings/v1/services", cacheable: false, ttl: 0, swr: 0, tag: "bookings/services" },
36148
+ { method: "DELETE", pathPrefix: "/bookings/v2/services", cacheable: false, ttl: 0, swr: 0, tag: "bookings/services" },
36149
+ { method: "PATCH", pathPrefix: "/bookings/v2/services", cacheable: false, ttl: 0, swr: 0, tag: "bookings/services" },
36150
+ { method: "POST", pathPrefix: "/bookings/v2/services", cacheable: false, ttl: 0, swr: 0, tag: "bookings/services" },
36151
+ { method: "PATCH", pathPrefix: "/donation-campaigns/v2/donation-campaigns", cacheable: false, ttl: 0, swr: 0, tag: "donations" },
36152
+ { method: "POST", pathPrefix: "/donation-campaigns/v2/donation-campaigns", cacheable: false, ttl: 0, swr: 0, tag: "donations" },
36153
+ { method: "PATCH", pathPrefix: "/events/v1/events", cacheable: false, ttl: 0, swr: 0, tag: "events" },
36154
+ { method: "POST", pathPrefix: "/events/v1/events", cacheable: false, ttl: 0, swr: 0, tag: "events" },
36155
+ { method: "POST", pathPrefix: "/events/v1/events/orders", cacheable: false, ttl: 0, swr: 0, tag: "events" },
36156
+ { method: "DELETE", pathPrefix: "/events/v3/events", cacheable: false, ttl: 0, swr: 0, tag: "events" },
36157
+ { method: "POST", pathPrefix: "/events/v3/events", cacheable: false, ttl: 0, swr: 0, tag: "events" },
36158
+ { method: "POST", pathPrefix: "/events/v3/events/count-by-status", cacheable: false, ttl: 0, swr: 0, tag: "events" },
36159
+ { method: "DELETE", pathPrefix: "/faq/v2/categories", cacheable: false, ttl: 0, swr: 0, tag: "faq" },
36160
+ { method: "PATCH", pathPrefix: "/faq/v2/categories", cacheable: false, ttl: 0, swr: 0, tag: "faq" },
36161
+ { method: "POST", pathPrefix: "/faq/v2/categories", cacheable: false, ttl: 0, swr: 0, tag: "faq" },
36162
+ { method: "DELETE", pathPrefix: "/faq/v2/question-entries", cacheable: false, ttl: 0, swr: 0, tag: "faq" },
36163
+ { method: "PATCH", pathPrefix: "/faq/v2/question-entries", cacheable: false, ttl: 0, swr: 0, tag: "faq" },
36164
+ { method: "POST", pathPrefix: "/faq/v2/question-entries", cacheable: false, ttl: 0, swr: 0, tag: "faq" },
36165
+ { method: "DELETE", pathPrefix: "/online-programs/v3/programs", cacheable: false, ttl: 0, swr: 0, tag: "online-programs" },
36166
+ { method: "PATCH", pathPrefix: "/online-programs/v3/programs", cacheable: false, ttl: 0, swr: 0, tag: "online-programs" },
36167
+ { method: "POST", pathPrefix: "/online-programs/v3/programs", cacheable: false, ttl: 0, swr: 0, tag: "online-programs" },
36168
+ { method: "DELETE", pathPrefix: "/portfolio/v1/collections", cacheable: false, ttl: 0, swr: 0, tag: "portfolio" },
36169
+ { method: "POST", pathPrefix: "/portfolio/v1/collections", cacheable: false, ttl: 0, swr: 0, tag: "portfolio" },
36170
+ { method: "DELETE", pathPrefix: "/portfolio/v1/projects", cacheable: false, ttl: 0, swr: 0, tag: "portfolio" },
36171
+ { method: "PATCH", pathPrefix: "/portfolio/v1/projects", cacheable: false, ttl: 0, swr: 0, tag: "portfolio" },
36172
+ { method: "DELETE", pathPrefix: "/pricing-plans/v2/plans", cacheable: false, ttl: 0, swr: 0, tag: "pricing-plans" },
36173
+ { method: "PATCH", pathPrefix: "/pricing-plans/v2/plans", cacheable: false, ttl: 0, swr: 0, tag: "pricing-plans" },
36174
+ { method: "POST", pathPrefix: "/pricing-plans/v2/plans", cacheable: false, ttl: 0, swr: 0, tag: "pricing-plans" },
36175
+ { method: "DELETE", pathPrefix: "/restaurants/menus/v1/sections", cacheable: false, ttl: 0, swr: 0, tag: "restaurants/menus" },
36176
+ { method: "PATCH", pathPrefix: "/restaurants/menus/v1/sections", cacheable: false, ttl: 0, swr: 0, tag: "restaurants/menus" },
36177
+ { method: "POST", pathPrefix: "/restaurants/menus/v1/sections", cacheable: false, ttl: 0, swr: 0, tag: "restaurants/menus" },
36178
+ { method: "DELETE", pathPrefix: "/reviews/v1/reviews", cacheable: false, ttl: 0, swr: 0, tag: "reviews" },
36179
+ { method: "PATCH", pathPrefix: "/reviews/v1/reviews", cacheable: false, ttl: 0, swr: 0, tag: "reviews" },
36180
+ { method: "POST", pathPrefix: "/reviews/v1/reviews", cacheable: false, ttl: 0, swr: 0, tag: "reviews" },
36181
+ { method: "POST", pathPrefix: "/reviews/v1/reviews/count", cacheable: false, ttl: 0, swr: 0, tag: "reviews" },
36182
+ { method: "DELETE", pathPrefix: "/stores/v1/products", cacheable: false, ttl: 0, swr: 0, tag: "stores/products" },
36183
+ { method: "PATCH", pathPrefix: "/stores/v1/products", cacheable: false, ttl: 0, swr: 0, tag: "stores/products" },
36184
+ { method: "POST", pathPrefix: "/stores/v1/products", cacheable: false, ttl: 0, swr: 0, tag: "stores/products" },
36185
+ { method: "DELETE", pathPrefix: "/stores/v3/categories", cacheable: false, ttl: 0, swr: 0, tag: "stores/categories" },
36186
+ { method: "PATCH", pathPrefix: "/stores/v3/categories", cacheable: false, ttl: 0, swr: 0, tag: "stores/categories" },
36187
+ { method: "POST", pathPrefix: "/stores/v3/categories", cacheable: false, ttl: 0, swr: 0, tag: "stores/categories" },
36188
+ { method: "DELETE", pathPrefix: "/stores/v3/products", cacheable: false, ttl: 0, swr: 0, tag: "stores/products" },
36189
+ { method: "PATCH", pathPrefix: "/stores/v3/products", cacheable: false, ttl: 0, swr: 0, tag: "stores/products" },
36190
+ { method: "POST", pathPrefix: "/stores/v3/products", cacheable: false, ttl: 0, swr: 0, tag: "stores/products" }
36191
+ ];
36192
+ }
36193
+ });
36194
+
36090
36195
  // ../../core/dev-server/src/wix-cache-policy.ts
36091
36196
  function clampRule(rule, denyPrefixes, maxTTL) {
36092
- const denied = denyPrefixes.some((p) => rule.pathPrefix.startsWith(p));
36197
+ const denied2 = denyPrefixes.some((p) => rule.pathPrefix.startsWith(p));
36093
36198
  return {
36094
36199
  ...rule,
36095
- cacheable: rule.cacheable && !denied,
36200
+ cacheable: rule.cacheable && !denied2,
36096
36201
  ttl: Math.min(rule.ttl, maxTTL)
36097
36202
  };
36098
36203
  }
36099
- function resolveWixCachePolicy(appName2) {
36100
- const appRules = CACHE_OVERRIDES[appName2] ?? [];
36101
- const rules = appRules.map((r) => clampRule(r, PLATFORM_DENY_PREFIXES, PLATFORM_MAX_TTL));
36102
- return { rules, defaultCacheable: false };
36204
+ function shadows(own3, dflt) {
36205
+ const methodCovers = own3.method === null || own3.method.toUpperCase() === (dflt.method ?? "").toUpperCase();
36206
+ return methodCovers && dflt.pathPrefix.startsWith(own3.pathPrefix);
36103
36207
  }
36104
- var PLATFORM_DENY_PREFIXES, PLATFORM_MAX_TTL, CACHE_OVERRIDES;
36208
+ function resolveWixCachePolicy(appName2, declared) {
36209
+ const own3 = (declared?.rules ?? []).map((r) => ({
36210
+ method: r.method ?? null,
36211
+ pathPrefix: r.pathPrefix,
36212
+ cacheable: r.cacheable,
36213
+ ttl: r.ttl,
36214
+ swr: r.swr ?? 0,
36215
+ tag: r.tag ?? null
36216
+ }));
36217
+ const legacy = CACHE_OVERRIDES[appName2] ?? [];
36218
+ const appLevel = [...own3, ...legacy];
36219
+ const defaults = declared?.platformDefaults === false ? [] : PLATFORM_DEFAULT_WIX_CACHE_RULES.filter((d) => !appLevel.some((a) => shadows(a, d)));
36220
+ const denies = [
36221
+ ...[...PLATFORM_DENY_PREFIXES, ...PERSONAL_DATA_PREFIXES].map((pathPrefix) => ({ method: null, pathPrefix, cacheable: false, ttl: 0, swr: 0, tag: null })),
36222
+ ...PERSONAL_DATA_READS.map(([method, pathPrefix]) => ({ method, pathPrefix, cacheable: false, ttl: 0, swr: 0, tag: null }))
36223
+ ];
36224
+ const rules = [...appLevel, ...defaults, ...denies].map((r) => clampRule(r, PLATFORM_DENY_PREFIXES, PLATFORM_MAX_TTL));
36225
+ return { rules, defaultCacheable: declared?.defaultCacheable ?? false };
36226
+ }
36227
+ var PLATFORM_DENY_PREFIXES, PERSONAL_DATA_PREFIXES, PERSONAL_DATA_READS, PLATFORM_MAX_TTL, CACHE_OVERRIDES;
36105
36228
  var init_wix_cache_policy = __esm({
36106
36229
  "../../core/dev-server/src/wix-cache-policy.ts"() {
36230
+ init_wix_cache_defaults_generated();
36107
36231
  PLATFORM_DENY_PREFIXES = ["/stores/v2/orders", "/stores/v3/orders"];
36232
+ PERSONAL_DATA_PREFIXES = [
36233
+ "/ecom/v1/carts",
36234
+ "/ecom/v1/checkouts",
36235
+ "/ecom/v1/orders",
36236
+ "/members/",
36237
+ "/contacts/",
36238
+ "/invoices/",
36239
+ "/bookings/v1/bookings",
36240
+ "/bookings/v2/bookings",
36241
+ "/events/v1/orders",
36242
+ "/events/v1/tickets",
36243
+ "/events/v2/rsvps",
36244
+ "/gift-cards/",
36245
+ "/loyalty/",
36246
+ "/inbox/",
36247
+ "/pricing-plans/v2/orders",
36248
+ // Every answer below depends on who is asking, like the ones above — the generator's
36249
+ // NEVER_CACHED set (codegen/wix/scripts/gen-cache-defaults.ts), which the platform-defaults
36250
+ // fixture pins. Denied HERE so an app opting into `defaultCacheable` cannot cache them either
36251
+ // (tests/wix-cache-policy.test.ts walks that fixture against the fallback).
36252
+ "/social-groups-proxy/",
36253
+ "/stores/v2/coupons",
36254
+ "/analytics/",
36255
+ "/site-list/",
36256
+ "/blog/v3/draft-posts"
36257
+ ];
36258
+ PERSONAL_DATA_READS = [["GET", "/events/v1/events/"]];
36108
36259
  PLATFORM_MAX_TTL = 3600;
36109
36260
  CACHE_OVERRIDES = {
36110
36261
  // The reference fixture exercises the cache-policy contract (products cacheable).
@@ -36255,7 +36406,7 @@ function deriveNavigationConfig(app, bundleCtx) {
36255
36406
  scheme: app.urlScheme,
36256
36407
  deepLinkPatterns: app.deepLinkPatterns ?? ov?.deepLinkPatterns ?? [],
36257
36408
  linkableScreens,
36258
- wixCachePolicy: resolveWixCachePolicy(appName2),
36409
+ wixCachePolicy: resolveWixCachePolicy(appName2, app.cache),
36259
36410
  // Fail-open baseline; tightened OTA via the admin app-config override. The
36260
36411
  // DESTINATION(s) are derived generically from the app's config (no app-name
36261
36412
  // special-casing) so the app chooses where telemetry goes via OTA:
@@ -53854,7 +54005,8 @@ var init_cli_bridge = __esm({
53854
54005
  });
53855
54006
 
53856
54007
  // src/mcp/file-tools.ts
53857
- import { lstatSync as lstatSync12, mkdirSync as mkdirSync26, readdirSync as readdirSync27, readFileSync as readFileSync59, realpathSync as realpathSync7, rmSync as rmSync12, statSync as statSync13, writeFileSync as writeFileSync21, existsSync as existsSync54 } from "node:fs";
54008
+ import { closeSync as closeSync4, constants, lstatSync as lstatSync12, mkdirSync as mkdirSync26, openSync as openSync3, readdirSync as readdirSync27, readFileSync as readFileSync59, realpathSync as realpathSync7, renameSync as renameSync6, rmSync as rmSync12, statSync as statSync13, writeSync, existsSync as existsSync54 } from "node:fs";
54009
+ import { randomBytes as randomBytes4 } from "node:crypto";
53858
54010
  import { dirname as dirname30, isAbsolute as isAbsolute5, join as join53, relative as relative11, resolve as resolve45, sep as sep14 } from "node:path";
53859
54011
  import * as z from "zod";
53860
54012
  function within(root, p) {
@@ -53871,37 +54023,99 @@ function realish(p) {
53871
54023
  }
53872
54024
  return join53(realpathSync7(cur), ...tail);
53873
54025
  }
54026
+ function symlinkComponent(base, abs) {
54027
+ let cur = base;
54028
+ for (const part of relative11(base, abs).split(sep14).filter(Boolean)) {
54029
+ cur = join53(cur, part);
54030
+ try {
54031
+ if (lstatSync12(cur).isSymbolicLink()) return relative11(base, cur);
54032
+ } catch {
54033
+ return void 0;
54034
+ }
54035
+ }
54036
+ return void 0;
54037
+ }
53874
54038
  async function confinedAppDir(args, deps) {
53875
54039
  const root = resolve45(await deps.defaultDir());
53876
54040
  const dir = typeof args.dir === "string" && args.dir ? resolve45(root, args.dir) : root;
53877
54041
  if (!existsSync54(root)) return refuse4("no_workspace", `the workspace folder ${root} does not exist`);
53878
- if (!within(realpathSync7(root), realish(dir))) return refuse4("outside_workspace", `"${String(args.dir)}" is outside the workspace folder`);
54042
+ if (!within(root, dir) || !within(realpathSync7(root), realish(dir))) return refuse4("outside_workspace", `"${String(args.dir)}" is outside the workspace folder`);
54043
+ const link = symlinkComponent(root, dir);
54044
+ if (link) return refuse4("outside_workspace", `"${link}" is a symlink \u2014 the app folder must be a real folder in the workspace`);
54045
+ const rel = relative11(root, dir);
54046
+ if (rel && (isDeniedSourcePath(`${rel}/x`) || isDeniedSourcePath(`${rel.toLowerCase()}/x`))) return refuse4("denied", `"${rel}" is a folder this tool does not touch`);
54047
+ if (!existsSync54(join53(dir, CONTENT_MARKER))) {
54048
+ return refuse4("not_an_app", `"${rel || "."}" is not an app folder (no ${CONTENT_MARKER}) \u2014 see himi_list_apps, or create one with himi_init`);
54049
+ }
53879
54050
  return dir;
53880
54051
  }
54052
+ function onDiskSpelling(abs) {
54053
+ let cur = abs;
54054
+ const tail = [];
54055
+ while (!existsSync54(cur)) {
54056
+ const up = dirname30(cur);
54057
+ if (up === cur) break;
54058
+ tail.unshift(cur.slice(up.length + (up.endsWith(sep14) ? 0 : 1)));
54059
+ cur = up;
54060
+ }
54061
+ return join53(realpathSync7.native(cur), ...tail);
54062
+ }
54063
+ function denied(appDir3, abs) {
54064
+ const rel = relative11(appDir3, abs);
54065
+ if (isDeniedSourcePath(rel) || isDeniedSourcePath(rel.toLowerCase())) return true;
54066
+ try {
54067
+ return isDeniedSourcePath(relative11(realpathSync7.native(appDir3), onDiskSpelling(abs)));
54068
+ } catch {
54069
+ return true;
54070
+ }
54071
+ }
53881
54072
  function confinedPath(appDir3, rel) {
53882
54073
  if (typeof rel !== "string" || !rel.trim()) return { error: "a path is required" };
53883
54074
  if (rel.includes("\0")) return { error: "a path may not contain NUL" };
53884
54075
  if (isAbsolute5(rel)) return { error: `"${rel}" is absolute \u2014 give a path inside the app folder` };
53885
54076
  const abs = resolve45(appDir3, rel);
54077
+ if (!within(appDir3, abs) || abs === appDir3) return { error: `"${rel}" is outside the app folder` };
53886
54078
  const real = realish(abs);
53887
54079
  const base = existsSync54(appDir3) ? realpathSync7(appDir3) : realish(appDir3);
53888
54080
  if (!within(base, real) || real === base) return { error: `"${rel}" is outside the app folder` };
53889
- const first = relative11(base, real).split(sep14)[0];
53890
- if (RESERVED.has(first)) return { error: `"${rel}" is inside ${first}/, which this tool does not touch` };
54081
+ const link = symlinkComponent(appDir3, abs);
54082
+ if (link) return { error: `"${link}" is a symlink, which this tool does not follow` };
54083
+ if (denied(appDir3, abs)) return { error: `"${rel}" is on the deny-list (secrets, credentials, build output), which this tool does not touch` };
53891
54084
  return abs;
53892
54085
  }
54086
+ function writeNoFollow(appDir3, abs, content) {
54087
+ mkdirSync26(dirname30(abs), { recursive: true });
54088
+ const before = symlinkComponent(appDir3, abs);
54089
+ if (before) throw new Error(`"${before}" became a symlink`);
54090
+ const tmp = join53(dirname30(abs), `.himi-write-${process.pid}-${randomBytes4(6).toString("hex")}.tmp`);
54091
+ const fd = openSync3(tmp, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0), 420);
54092
+ try {
54093
+ writeSync(fd, content);
54094
+ } finally {
54095
+ closeSync4(fd);
54096
+ }
54097
+ const link = symlinkComponent(appDir3, abs);
54098
+ if (link || !within(realpathSync7(appDir3), realpathSync7(dirname30(abs)))) {
54099
+ rmSync12(tmp, { force: true });
54100
+ throw new Error(`"${link ?? relative11(appDir3, dirname30(abs))}" changed into a link while writing`);
54101
+ }
54102
+ renameSync6(tmp, abs);
54103
+ }
53893
54104
  function listFiles(appDir3) {
53894
54105
  const files = [];
53895
54106
  const walk2 = (d) => {
54107
+ const parentRel = relative11(appDir3, d);
53896
54108
  for (const e of readdirSync27(d, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
53897
- if (RESERVED.has(e.name)) continue;
53898
54109
  if (e.isSymbolicLink()) continue;
53899
54110
  const p = join53(d, e.name);
53900
54111
  if (e.isDirectory()) {
54112
+ if (isDeniedSourceDir(e.name, parentRel) || isDeniedSourceDir(e.name.toLowerCase(), parentRel)) continue;
53901
54113
  if (!walk2(p)) return false;
53902
54114
  } else if (e.isFile()) {
54115
+ const rel = relative11(appDir3, p);
54116
+ if (isDeniedSourcePath(rel) || isDeniedSourcePath(rel.toLowerCase())) continue;
53903
54117
  if (files.length >= MAX_LISTED) return false;
53904
- files.push({ path: relative11(appDir3, p).split(sep14).join("/"), bytes: statSync13(p).size });
54118
+ files.push({ path: rel.split(sep14).join("/"), bytes: statSync13(p).size });
53905
54119
  }
53906
54120
  }
53907
54121
  return true;
@@ -53931,7 +54145,7 @@ function fileTools() {
53931
54145
  {
53932
54146
  name: "himi_list_files",
53933
54147
  title: "List app files",
53934
- description: `Every file in an app folder with its size (skips ${[...RESERVED].join(", ")}; at most ${MAX_LISTED}).`,
54148
+ description: `Every file in an app folder with its size \u2014 at most ${MAX_LISTED}, skipping symlinks and what the source deny-list keeps off the machine (secrets, credentials, build output).`,
53935
54149
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
53936
54150
  inputSchema: z.object({ dir: DIR }),
53937
54151
  handler: async (a, d) => {
@@ -53980,14 +54194,20 @@ function fileTools() {
53980
54194
  else if (typeof f.content !== "string") refused2.push({ path: f.path, error: "content must be text" });
53981
54195
  else if (Buffer.byteLength(f.content) > MAX_FILE_BYTES) refused2.push({ path: f.path, error: `over the ${MAX_FILE_BYTES}-byte limit` });
53982
54196
  else if (existsSync54(p) && !lstatSync12(p).isFile()) refused2.push({ path: f.path, error: "not a regular file" });
54197
+ else if (planned.some((q) => q.abs === p)) refused2.push({ path: f.path, error: "the same file twice in one call" });
53983
54198
  else planned.push({ abs: p, rel: String(f.path), content: f.content });
53984
54199
  }
53985
54200
  if (refused2.length) return { ...ok({ ok: false, error: "refused", written: [], refused: refused2 }), isError: true };
54201
+ const written = [];
53986
54202
  for (const f of planned) {
53987
- mkdirSync26(dirname30(f.abs), { recursive: true });
53988
- writeFileSync21(f.abs, f.content);
54203
+ try {
54204
+ writeNoFollow(app, f.abs, f.content);
54205
+ written.push(f.rel);
54206
+ } catch (e) {
54207
+ return { ...ok({ ok: false, error: "write_failed", message: e.message, written }), isError: true };
54208
+ }
53989
54209
  }
53990
- return ok({ ok: true, dir: app, written: planned.map((f) => f.rel) });
54210
+ return ok({ ok: true, dir: app, written });
53991
54211
  }
53992
54212
  },
53993
54213
  {
@@ -54001,28 +54221,32 @@ function fileTools() {
54001
54221
  if (typeof app !== "string") return app;
54002
54222
  const paths = Array.isArray(a.paths) ? a.paths : [];
54003
54223
  const planned = [];
54224
+ const deleted = [];
54004
54225
  const refused2 = [];
54005
54226
  for (const rel of paths) {
54006
54227
  const p = confinedPath(app, rel);
54007
54228
  if (typeof p !== "string") refused2.push({ path: rel, error: p.error });
54008
54229
  else if (!existsSync54(p)) refused2.push({ path: rel, error: "no such file" });
54009
54230
  else if (!lstatSync12(p).isFile()) refused2.push({ path: rel, error: "not a regular file" });
54010
- else planned.push(p);
54231
+ else if (!planned.includes(p)) {
54232
+ planned.push(p);
54233
+ deleted.push(rel);
54234
+ }
54011
54235
  }
54012
54236
  if (refused2.length) return { ...ok({ ok: false, error: "refused", deleted: [], refused: refused2 }), isError: true };
54013
54237
  for (const p of planned) rmSync12(p);
54014
- return ok({ ok: true, dir: app, deleted: paths });
54238
+ return ok({ ok: true, dir: app, deleted });
54015
54239
  }
54016
54240
  }
54017
54241
  ];
54018
54242
  }
54019
- var MAX_FILE_BYTES, MAX_FILES_PER_WRITE, MAX_LISTED, RESERVED, CONTENT_MARKER, ok, refuse4, DIR, WRITE_ANN;
54243
+ var MAX_FILE_BYTES, MAX_FILES_PER_WRITE, MAX_LISTED, CONTENT_MARKER, ok, refuse4, DIR, WRITE_ANN;
54020
54244
  var init_file_tools = __esm({
54021
54245
  "src/mcp/file-tools.ts"() {
54246
+ init_source_deny();
54022
54247
  MAX_FILE_BYTES = 1024 * 1024;
54023
54248
  MAX_FILES_PER_WRITE = 100;
54024
54249
  MAX_LISTED = 2e3;
54025
- RESERVED = /* @__PURE__ */ new Set([".git", "node_modules", "dist"]);
54026
54250
  CONTENT_MARKER = "himalaya.content.json";
54027
54251
  ok = (o) => ({ content: [{ type: "text", text: JSON.stringify(o, null, 2) }] });
54028
54252
  refuse4 = (error, message) => ({ content: [{ type: "text", text: JSON.stringify({ ok: false, error, message }, null, 2) }], isError: true });
@@ -54533,7 +54757,7 @@ __export(native_decode_exports, {
54533
54757
  runNativeDecode: () => runNativeDecode
54534
54758
  });
54535
54759
  import { execFileSync as execFileSync5, spawnSync as spawnSync2 } from "node:child_process";
54536
- import { existsSync as existsSync55, mkdtempSync as mkdtempSync2, readFileSync as readFileSync61, rmSync as rmSync13, writeFileSync as writeFileSync22 } from "node:fs";
54760
+ import { existsSync as existsSync55, mkdtempSync as mkdtempSync2, readFileSync as readFileSync61, rmSync as rmSync13, writeFileSync as writeFileSync21 } from "node:fs";
54537
54761
  import { tmpdir as tmpdir3 } from "node:os";
54538
54762
  import { join as join54 } from "node:path";
54539
54763
  import { fileURLToPath as fileURLToPath22 } from "node:url";
@@ -54667,7 +54891,7 @@ function runNativeDecode(options) {
54667
54891
  if (typeof descriptor.descriptorJson !== "string") {
54668
54892
  throw new Error(`native decode cannot judge ${descriptor.screenId}: the release builder did not return its exact baked JSON bytes`);
54669
54893
  }
54670
- writeFileSync22(join54(descriptorDir, `${encodeURIComponent(descriptor.screenId)}.json`), descriptor.descriptorJson);
54894
+ writeFileSync21(join54(descriptorDir, `${encodeURIComponent(descriptor.screenId)}.json`), descriptor.descriptorJson);
54671
54895
  }
54672
54896
  options.progress?.("building cached Swift oracle");
54673
54897
  execFileSync5("swift", ["build", "--package-path", options.packageDir, "--product", "himi-decode-oracle"], {
@@ -54705,7 +54929,7 @@ function runAndroidNativeDecode(options) {
54705
54929
  if (typeof descriptor.descriptorJson !== "string") {
54706
54930
  throw new Error(`native decode cannot judge ${descriptor.screenId}: the release builder did not return its exact baked JSON bytes`);
54707
54931
  }
54708
- writeFileSync22(join54(descriptorDir, `${encodeURIComponent(descriptor.screenId)}.json`), descriptor.descriptorJson);
54932
+ writeFileSync21(join54(descriptorDir, `${encodeURIComponent(descriptor.screenId)}.json`), descriptor.descriptorJson);
54709
54933
  }
54710
54934
  options.progress?.(`running real Kotlin decoder over ${options.descriptors.length} screen${options.descriptors.length === 1 ? "" : "s"}`);
54711
54935
  execFileSync5(options.gradlew, [
@@ -54789,7 +55013,7 @@ __export(browser_authoring_exports, {
54789
55013
  sdkSubpathReader: () => sdkSubpathReader
54790
55014
  });
54791
55015
  import { createServer as createServer5 } from "node:http";
54792
- import { createHash as createHash29, randomBytes as randomBytes4 } from "node:crypto";
55016
+ import { createHash as createHash29, randomBytes as randomBytes5 } from "node:crypto";
54793
55017
  import { existsSync as existsSync56, readdirSync as readdirSync28, readFileSync as readFileSync62, realpathSync as realpathSync8, statSync as statSync14 } from "node:fs";
54794
55018
  import { join as join55, posix as posix2, relative as relative12, sep as sep15 } from "node:path";
54795
55019
  function humanMs(ms) {
@@ -54887,7 +55111,7 @@ function sdkSubpathReader(resolve54) {
54887
55111
  function encodeJob(job) {
54888
55112
  return Buffer.from(JSON.stringify(job), "utf8").toString("base64");
54889
55113
  }
54890
- function mintToken2(payload, nonce = randomBytes4(16).toString("hex")) {
55114
+ function mintToken2(payload, nonce = randomBytes5(16).toString("hex")) {
54891
55115
  const digest2 = createHash29("sha256").update(payload, "utf8").digest("base64url");
54892
55116
  return `${nonce}.${digest2}`;
54893
55117
  }
@@ -55050,7 +55274,7 @@ __export(coverage_exports, {
55050
55274
  suppressedNativeKeys: () => suppressedNativeKeys,
55051
55275
  trendAndStore: () => trendAndStore
55052
55276
  });
55053
- import { existsSync as existsSync57, mkdirSync as mkdirSync27, readFileSync as readFileSync63, writeFileSync as writeFileSync23 } from "node:fs";
55277
+ import { existsSync as existsSync57, mkdirSync as mkdirSync27, readFileSync as readFileSync63, writeFileSync as writeFileSync22 } from "node:fs";
55054
55278
  import { dirname as dirname31, join as join56 } from "node:path";
55055
55279
  function suppressedNativeKeys(oracle) {
55056
55280
  return [...oracle.ios ? [] : ["native-decode|ios"], ...oracle.android ? [] : ["native-decode|android"]];
@@ -55244,7 +55468,7 @@ function trendAndStore(dir, entries, suppressed) {
55244
55468
  const stored = [.../* @__PURE__ */ new Set([...current, ...(previous ?? []).filter((key2) => (suppressed ?? []).includes(key2))])];
55245
55469
  try {
55246
55470
  mkdirSync27(dirname31(p), { recursive: true });
55247
- writeFileSync23(p, JSON.stringify({ keys: stored, ts: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
55471
+ writeFileSync22(p, JSON.stringify({ keys: stored, ts: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
55248
55472
  } catch {
55249
55473
  return null;
55250
55474
  }
@@ -55289,7 +55513,7 @@ function diffAndStoreObservations(dir, current) {
55289
55513
  }
55290
55514
  try {
55291
55515
  mkdirSync27(dirname31(p), { recursive: true });
55292
- writeFileSync23(p, JSON.stringify({ screens: current, ts: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
55516
+ writeFileSync22(p, JSON.stringify({ screens: current, ts: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
55293
55517
  } catch {
55294
55518
  return null;
55295
55519
  }
@@ -55900,8 +56124,8 @@ ${releaseId}`;
55900
56124
  const pin = resolvePin(request, url);
55901
56125
  let bytes2 = null;
55902
56126
  if (pin) {
55903
- const denied = await authorizePinnedRead(request, url, app, pin);
55904
- if (denied) return { ok: false, response: denied };
56127
+ const denied2 = await authorizePinnedRead(request, url, app, pin);
56128
+ if (denied2) return { ok: false, response: denied2 };
55905
56129
  const m = await pinnedManifest(app, pin);
55906
56130
  if (!m) return { ok: false, response: json4(404, { error: "release_not_found", app, releaseId: pin }) };
55907
56131
  bytes2 = m.appConfigHash ? await pinnedBody(app, m.appConfigHash, () => store.getAppConfig(app)) : null;
@@ -55924,7 +56148,7 @@ ${releaseId}`;
55924
56148
  return new Response(body, { status: 200, headers: { "content-type": JSON_CT2, etag, "cache-control": cache, ...vary } });
55925
56149
  }
55926
56150
  async function authorizePinnedRead(request, url, app, releaseId, screenId) {
55927
- const denied = await checkPinnedRead({
56151
+ const denied2 = await checkPinnedRead({
55928
56152
  secret: opts.previewTokenSecret,
55929
56153
  token: resolvePreviewToken(request, url),
55930
56154
  app,
@@ -55934,7 +56158,7 @@ ${releaseId}`;
55934
56158
  authorizePreview: opts.authorizePreview,
55935
56159
  authorizeFollow: opts.authorizeFollow
55936
56160
  });
55937
- return denied && json4(denied.status, denied.body);
56161
+ return denied2 && json4(denied2.status, denied2.body);
55938
56162
  }
55939
56163
  async function honourRingPin(request) {
55940
56164
  const kept = { request, dropped: false };
@@ -56385,8 +56609,8 @@ ${releaseId}`;
56385
56609
  const app = cfgMatch?.[1] ?? resolveApp(request, url);
56386
56610
  const pin = resolvePin(request, url);
56387
56611
  if (app && pin) {
56388
- const denied = await authorizePinnedRead(request, url, app, pin);
56389
- if (denied) return denied;
56612
+ const denied2 = await authorizePinnedRead(request, url, app, pin);
56613
+ if (denied2) return denied2;
56390
56614
  }
56391
56615
  if (path2 === "/v1/template-source") {
56392
56616
  if (!app) return json4(400, { error: "missing_app" });
@@ -56469,8 +56693,8 @@ ${releaseId}`;
56469
56693
  if (!app) return json4(400, { error: "missing_app" });
56470
56694
  let m;
56471
56695
  if (pin) {
56472
- const denied = await authorizePinnedRead(request, url, app, pin);
56473
- if (denied) return denied;
56696
+ const denied2 = await authorizePinnedRead(request, url, app, pin);
56697
+ if (denied2) return denied2;
56474
56698
  m = await pinnedManifest(app, pin);
56475
56699
  if (!m) return json4(404, { error: "release_not_found", app, releaseId: pin });
56476
56700
  } else {
@@ -56656,8 +56880,8 @@ ${releaseId}`;
56656
56880
  const screenId = screenMatch[1];
56657
56881
  if (!app || !isSafeSegment(screenId)) return json4(404, { error: "screen_not_found", screenId });
56658
56882
  if (pin) {
56659
- const denied = await authorizePinnedRead(request, url, app, pin, screenId);
56660
- if (denied) return denied;
56883
+ const denied2 = await authorizePinnedRead(request, url, app, pin, screenId);
56884
+ if (denied2) return denied2;
56661
56885
  const m2 = await pinnedManifest(app, pin);
56662
56886
  if (!m2) return json4(404, { error: "release_not_found", app, releaseId: pin });
56663
56887
  const meta2 = m2.screens[screenId];
@@ -57180,21 +57404,21 @@ var init_preview_assets_generated = __esm({
57180
57404
  "../../packages/serve/src/preview-assets.generated.ts"() {
57181
57405
  init_preview_assets_format();
57182
57406
  PREVIEW_ASSET_MANIFEST = [
57183
- { key: "assets/index-DRJmxLKY.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 1102618, sha256: "9976a79a9d6842770adfdc5605a486bef46bfe8a3570538b6f585fc1ef100f82" },
57184
- { key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset: 1102626, length: 4610, sha256: "994062dd7af4186a739de55dd9e151c63b6301f8e2cbafc608d35c2e87da0cd2" },
57185
- { key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset: 1107236, length: 488584, sha256: "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134" },
57186
- { key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 1595820, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
57187
- { key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 1686744, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
57188
- { key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 1777728, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
57189
- { key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 1868708, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
57190
- { key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 1959580, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
57191
- { key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 2050560, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
57192
- { key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 2159196, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
57193
- { key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 2268280, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
57194
- { key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 2377328, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
57195
- { key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 2485028, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
57196
- { key: "index.html", contentType: "text/html; charset=utf-8", offset: 2573136, length: 4947, sha256: "1217e0b9a45c53ee3c35fd8dfe751a50b2276661a3e699efde98c17152c1b960" },
57197
- { key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 2578083, length: 17209, sha256: "dffc9718e2faa63ae2cc919e0a842f585ed565aaaa7c53778e90379442c731b6" }
57407
+ { key: "assets/index-nG7b3MPu.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 1104209, sha256: "88839c5eb15058f706e50664752a75646d690de5148f8b504eb82c0f87a70a15" },
57408
+ { key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset: 1104217, length: 4610, sha256: "994062dd7af4186a739de55dd9e151c63b6301f8e2cbafc608d35c2e87da0cd2" },
57409
+ { key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset: 1108827, length: 488584, sha256: "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134" },
57410
+ { key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 1597411, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
57411
+ { key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 1688335, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
57412
+ { key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 1779319, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
57413
+ { key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 1870299, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
57414
+ { key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 1961171, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
57415
+ { key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 2052151, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
57416
+ { key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 2160787, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
57417
+ { key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 2269871, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
57418
+ { key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 2378919, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
57419
+ { key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 2486619, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
57420
+ { key: "index.html", contentType: "text/html; charset=utf-8", offset: 2574727, length: 4947, sha256: "5c0a3e1b1d5bda7b6bc8cc2a025321bf6d0aa74d1f14b073861f188fd7b13a8b" },
57421
+ { key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 2579674, length: 17209, sha256: "dffc9718e2faa63ae2cc919e0a842f585ed565aaaa7c53778e90379442c731b6" }
57198
57422
  ];
57199
57423
  }
57200
57424
  });
@@ -57450,7 +57674,7 @@ __export(run_watch_exports, {
57450
57674
  watchedContentDir: () => watchedContentDir,
57451
57675
  writeRunRecord: () => writeRunRecord
57452
57676
  });
57453
- import { closeSync as closeSync4, constants, existsSync as existsSync60, fstatSync, lstatSync as lstatSync13, statSync as statSync16, mkdirSync as mkdirSync29, openSync as openSync3, readFileSync as readFileSync67, realpathSync as realpathSync9, renameSync as renameSync6, rmSync as rmSync15, writeFileSync as writeFileSync24, watch as fsWatch } from "node:fs";
57677
+ import { closeSync as closeSync5, constants as constants2, existsSync as existsSync60, fstatSync, lstatSync as lstatSync13, statSync as statSync16, mkdirSync as mkdirSync29, openSync as openSync4, readFileSync as readFileSync67, realpathSync as realpathSync9, renameSync as renameSync7, rmSync as rmSync15, writeFileSync as writeFileSync23, watch as fsWatch } from "node:fs";
57454
57678
  import { randomUUID as randomUUID4 } from "node:crypto";
57455
57679
  import { extname as extname4, join as join59, resolve as resolve47, sep as sep16 } from "node:path";
57456
57680
  function diffReleases(prev, next) {
@@ -57585,9 +57809,9 @@ function writeRunRecord(dir, record2) {
57585
57809
  if (!root) return false;
57586
57810
  const tmp = join59(root, `.run.json.${process.pid}.${randomUUID4()}.tmp`);
57587
57811
  try {
57588
- writeFileSync24(tmp, `${JSON.stringify(record2, null, 2)}
57812
+ writeFileSync23(tmp, `${JSON.stringify(record2, null, 2)}
57589
57813
  `, { flag: "wx" });
57590
- renameSync6(tmp, join59(root, "run.json"));
57814
+ renameSync7(tmp, join59(root, "run.json"));
57591
57815
  } catch (e) {
57592
57816
  rmSync15(tmp, { force: true });
57593
57817
  throw e;
@@ -57644,12 +57868,12 @@ function readConfined(root, rel) {
57644
57868
  }
57645
57869
  const canonicalRoot = realpathSync9(root);
57646
57870
  if (!realpathSync9(at2).startsWith(canonicalRoot + sep16)) return null;
57647
- const fd = openSync3(at2, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
57871
+ const fd = openSync4(at2, constants2.O_RDONLY | (constants2.O_NOFOLLOW ?? 0));
57648
57872
  try {
57649
57873
  if (!fstatSync(fd).isFile()) return null;
57650
57874
  return readFileSync67(fd);
57651
57875
  } finally {
57652
- closeSync4(fd);
57876
+ closeSync5(fd);
57653
57877
  }
57654
57878
  } catch {
57655
57879
  return null;
@@ -58871,7 +59095,7 @@ __export(version_exports, {
58871
59095
  setAndroidVersion: () => setAndroidVersion,
58872
59096
  setIosVersion: () => setIosVersion
58873
59097
  });
58874
- import { readFileSync as readFileSync72, writeFileSync as writeFileSync25 } from "node:fs";
59098
+ import { readFileSync as readFileSync72, writeFileSync as writeFileSync24 } from "node:fs";
58875
59099
  function readIosVersion(projectYml) {
58876
59100
  const marketing = matchScalar(projectYml, "CFBundleShortVersionString");
58877
59101
  const build2 = matchScalar(projectYml, "CFBundleVersion");
@@ -58936,8 +59160,8 @@ function bumpVersion(app, opts) {
58936
59160
  marketing: opts.marketing ?? current.marketing,
58937
59161
  build: opts.build === "increment" || opts.build === void 0 ? current.build + (opts.build === "increment" ? 1 : 0) : opts.build
58938
59162
  };
58939
- writeFileSync25(iosPath, setIosVersion(iosYml, next));
58940
- writeFileSync25(gradlePath, setAndroidVersion(gradle, next));
59163
+ writeFileSync24(iosPath, setIosVersion(iosYml, next));
59164
+ writeFileSync24(gradlePath, setAndroidVersion(gradle, next));
58941
59165
  return next;
58942
59166
  }
58943
59167
  var init_version = __esm({
@@ -59080,7 +59304,7 @@ var init_asc_status = __esm({
59080
59304
  });
59081
59305
 
59082
59306
  // ../../core/functions-secrets/src/vault.ts
59083
- import { createCipheriv, createDecipheriv, randomBytes as randomBytes5 } from "node:crypto";
59307
+ import { createCipheriv, createDecipheriv, randomBytes as randomBytes6 } from "node:crypto";
59084
59308
  import { mkdir, readFile as readFile2, rename, writeFile as writeFile2 } from "node:fs/promises";
59085
59309
  import { dirname as dirname34 } from "node:path";
59086
59310
  function createJsonSecretStore(filePath) {
@@ -59138,7 +59362,7 @@ function createSecretVault(options) {
59138
59362
  environment: input.environment,
59139
59363
  name: input.name,
59140
59364
  version: input.version,
59141
- iv: randomBytes5(12).toString("base64url"),
59365
+ iv: randomBytes6(12).toString("base64url"),
59142
59366
  ciphertext: "",
59143
59367
  authTag: ""
59144
59368
  };
@@ -59339,7 +59563,7 @@ var init_push_send = __esm({
59339
59563
  });
59340
59564
 
59341
59565
  // ../../core/serve-control/src/push-send-service.ts
59342
- var PUSH_SEND_ERROR_CODES;
59566
+ var PUSH_SEND_ERROR_CODES, PushSendError;
59343
59567
  var init_push_send_service = __esm({
59344
59568
  "../../core/serve-control/src/push-send-service.ts"() {
59345
59569
  PUSH_SEND_ERROR_CODES = [
@@ -59381,6 +59605,160 @@ var init_push_send_service = __esm({
59381
59605
  "ping_rate_limited",
59382
59606
  "send_outcome_unknown"
59383
59607
  ];
59608
+ PushSendError = class extends Error {
59609
+ constructor(code, status, retryAfterSec) {
59610
+ super(code);
59611
+ this.code = code;
59612
+ this.status = status;
59613
+ this.retryAfterSec = retryAfterSec;
59614
+ }
59615
+ };
59616
+ }
59617
+ });
59618
+
59619
+ // ../../core/serve-control/src/site-member.ts
59620
+ async function assertOwnerSiteToken(input, fetchImpl = input.fetch ?? fetch) {
59621
+ const info2 = await upstreamJson(fetchImpl, {
59622
+ path: "/oauth2/token-info",
59623
+ body: { token: input.siteToken },
59624
+ unavailable: "site_token_unverifiable",
59625
+ // An expired, revoked or malformed token is answered with a 4xx: the caller's to fix, not an outage.
59626
+ rejected: { code: "site_token_invalid", when: (status) => status >= 400 && status < 500 }
59627
+ });
59628
+ const owned = info2.active === true && info2.subjectType === "USER" && info2.siteId === input.site && info2.accountId === input.ownerAccountId;
59629
+ if (!owned) throw new PushSendError("site_token_invalid", 403);
59630
+ }
59631
+ async function upstreamJson(fetchImpl, call) {
59632
+ let response;
59633
+ try {
59634
+ response = await fetchImpl(`${WIXAPIS}${call.path}`, {
59635
+ method: "POST",
59636
+ redirect: "error",
59637
+ signal: AbortSignal.timeout(TIMEOUT_MS2),
59638
+ headers: {
59639
+ "content-type": "application/json",
59640
+ accept: "application/json",
59641
+ ...call.authorization ? { Authorization: call.authorization } : {},
59642
+ ...call.siteId ? { "wix-site-id": call.siteId } : {}
59643
+ },
59644
+ body: JSON.stringify(call.body)
59645
+ });
59646
+ } catch {
59647
+ throw new PushSendError(call.unavailable, 503);
59648
+ }
59649
+ if (call.rejected?.when(response.status)) throw new PushSendError(call.rejected.code, 403);
59650
+ if (!response.ok) throw new PushSendError(call.unavailable, 503);
59651
+ try {
59652
+ return await response.json();
59653
+ } catch {
59654
+ throw new PushSendError(call.unavailable, 503);
59655
+ }
59656
+ }
59657
+ var WIXAPIS, TIMEOUT_MS2;
59658
+ var init_site_member = __esm({
59659
+ "../../core/serve-control/src/site-member.ts"() {
59660
+ init_push_send_service();
59661
+ WIXAPIS = "https://www.wixapis.com";
59662
+ TIMEOUT_MS2 = 1e4;
59663
+ }
59664
+ });
59665
+
59666
+ // src/notify-ping-all.ts
59667
+ import { createHash as createHash31 } from "node:crypto";
59668
+ async function listApprovedMembers(input) {
59669
+ const first = await scanMembers(input);
59670
+ const second = await scanMembers(input);
59671
+ if (JSON.stringify(first.fingerprint) !== JSON.stringify(second.fingerprint)) throw new Error("member_roster_unstable");
59672
+ return second.recipients;
59673
+ }
59674
+ async function scanMembers(input) {
59675
+ const recipients = [];
59676
+ const fingerprint = [];
59677
+ const seenIds = /* @__PURE__ */ new Set();
59678
+ const seenEmails = /* @__PURE__ */ new Set();
59679
+ const fetchImpl = input.fetchImpl ?? fetch;
59680
+ for (let offset = 0; ; ) {
59681
+ let response;
59682
+ try {
59683
+ response = await fetchImpl(`${WIXAPIS2}/members/v1/members/query`, {
59684
+ method: "POST",
59685
+ headers: { Authorization: input.siteToken, "wix-site-id": input.site, "content-type": "application/json", accept: "application/json" },
59686
+ redirect: "error",
59687
+ signal: AbortSignal.timeout(1e4),
59688
+ body: JSON.stringify({ query: { paging: { limit: PAGE_SIZE, offset } }, fieldsets: ["FULL"] })
59689
+ });
59690
+ } catch {
59691
+ throw new Error("member_lookup_unavailable");
59692
+ }
59693
+ if (!response.ok) throw new Error("member_lookup_unavailable");
59694
+ let raw;
59695
+ try {
59696
+ raw = await response.json();
59697
+ } catch {
59698
+ throw new Error("member_lookup_unavailable");
59699
+ }
59700
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) throw new Error("member_lookup_unavailable");
59701
+ const result = raw;
59702
+ if (!Array.isArray(result.members)) throw new Error("member_lookup_unavailable");
59703
+ if (result.members.length === 0) return { recipients, fingerprint };
59704
+ if (result.members.length > PAGE_SIZE) throw new Error("member_roster_unstable");
59705
+ for (const member of result.members) {
59706
+ if (member === null || typeof member !== "object" || Array.isArray(member)) throw new Error("member_lookup_unavailable");
59707
+ if (typeof member.id !== "string" || !member.id || seenIds.has(member.id)) throw new Error("member_roster_unstable");
59708
+ seenIds.add(member.id);
59709
+ fingerprint.push(JSON.stringify([member.id, member.loginEmail, member.contactId, member.status]));
59710
+ if (member.status !== "APPROVED") continue;
59711
+ if (typeof member.loginEmail !== "string" || !member.loginEmail || typeof member.contactId !== "string" || !member.contactId) throw new Error("member_roster_incomplete");
59712
+ const email = member.loginEmail.toLowerCase();
59713
+ if (seenEmails.has(email)) throw new Error("member_ambiguous");
59714
+ seenEmails.add(email);
59715
+ recipients.push({ id: member.id, email: member.loginEmail });
59716
+ }
59717
+ offset += result.members.length;
59718
+ }
59719
+ }
59720
+ function campaignRequestId(campaignId, memberId) {
59721
+ const bytes2 = createHash31("sha256").update(JSON.stringify(["himi-push-campaign", campaignId, memberId])).digest().subarray(0, 16);
59722
+ bytes2[6] = bytes2[6] & 15 | 80;
59723
+ bytes2[8] = bytes2[8] & 63 | 128;
59724
+ const hex2 = bytes2.toString("hex");
59725
+ return `${hex2.slice(0, 8)}-${hex2.slice(8, 12)}-${hex2.slice(12, 16)}-${hex2.slice(16, 20)}-${hex2.slice(20)}`;
59726
+ }
59727
+ async function sendCampaign(input) {
59728
+ let accepted = 0;
59729
+ let deduplicated = 0;
59730
+ for (const [index, recipient] of input.recipients.entries()) {
59731
+ if (index > 0) await input.wait(6100);
59732
+ const requestId2 = campaignRequestId(input.campaignId, recipient.id);
59733
+ let outcome = await input.send(recipient, requestId2);
59734
+ let retries = 0;
59735
+ while (outcome.state === "rate_limited" && outcome.retryAfterSec > 0 && retries < 3) {
59736
+ retries += 1;
59737
+ await input.wait(outcome.retryAfterSec * 1e3);
59738
+ outcome = await input.send(recipient, requestId2);
59739
+ }
59740
+ if (outcome.state !== "accepted") {
59741
+ throw new CampaignFailure(outcome.state === "failed" ? outcome.code : "rate_limited", index, recipient.email);
59742
+ }
59743
+ if (outcome.deduplicated) deduplicated += 1;
59744
+ else accepted += 1;
59745
+ input.onProgress?.(index + 1);
59746
+ }
59747
+ return { accepted, deduplicated };
59748
+ }
59749
+ var CampaignFailure, PAGE_SIZE, WIXAPIS2;
59750
+ var init_notify_ping_all = __esm({
59751
+ "src/notify-ping-all.ts"() {
59752
+ CampaignFailure = class extends Error {
59753
+ constructor(code, completed, memberEmail) {
59754
+ super(code);
59755
+ this.code = code;
59756
+ this.completed = completed;
59757
+ this.memberEmail = memberEmail;
59758
+ }
59759
+ };
59760
+ PAGE_SIZE = 100;
59761
+ WIXAPIS2 = "https://www.wixapis.com";
59384
59762
  }
59385
59763
  });
59386
59764
 
@@ -59398,8 +59776,11 @@ function refuse5(io, error, hint, exit = 2, extra = {}) {
59398
59776
  async function runNotifyPing(flags, io, deps = {}) {
59399
59777
  const flagError = flagRefusal(flags);
59400
59778
  if (flagError) return refuse5(io, flagError);
59779
+ const allMembers = flags["all-members"] === true;
59780
+ if (allMembers && text7(flags["member-email"])) return refuse5(io, "recipient_conflict", "Choose --all-members or --member-email.");
59781
+ if (allMembers && flags["dry-run"] !== true && flags.yes !== true) return refuse5(io, "confirmation_required", "Preview with --dry-run, then add --yes to send to every approved member.");
59401
59782
  const input = mergePushInputs(pushInputsFromContent(resolve50(text7(flags.content) ?? process.cwd())), flags);
59402
- const missing = [...input.app ? [] : ["app"], ...input.site ? [] : ["site"], ...REQUIRED_FLAGS.filter((f) => !text7(flags[f]))];
59783
+ const missing = [...input.app ? [] : ["app"], ...input.site ? [] : ["site"], ...REQUIRED_FLAGS.filter((f) => !(allMembers && f === "member-email") && !text7(flags[f]))];
59403
59784
  if (missing.length) return refuse5(io, "needs_input", "Name the app and site (or --content), the member's login email, a title and a link.", 2, { missing });
59404
59785
  const requestId2 = text7(flags["request-id"]) ?? (deps.newRequestId ?? randomUUID5)();
59405
59786
  if (!UUID_RE.test(requestId2)) return refuse5(io, "invalid_request_id", "Reuse the requestId a previous send printed.");
@@ -59414,6 +59795,7 @@ async function runNotifyPing(flags, io, deps = {}) {
59414
59795
  } catch {
59415
59796
  return refuse5(io, "wix_site_token_required", "Run wix login as the site owner; the site token reads this site's members.", 1);
59416
59797
  }
59798
+ if (allMembers) return runAllMembers({ input: { app: ownership.slug, site: input.site }, siteToken: siteToken2, session, requestId: requestId2, flags, io, deps });
59417
59799
  const body = {
59418
59800
  app: ownership.slug,
59419
59801
  site: input.site,
@@ -59427,6 +59809,88 @@ async function runNotifyPing(flags, io, deps = {}) {
59427
59809
  const http = await pushSendRequest({ target: session.target, wixToken: session.wixToken, siteToken: siteToken2, body, previewTag: session.previewTag, fetchImpl: deps.fetchImpl });
59428
59810
  return report2(http, body, flags, io);
59429
59811
  }
59812
+ async function runAllMembers(args) {
59813
+ const { input, siteToken: siteToken2, session, requestId: requestId2, flags, io, deps } = args;
59814
+ const ownerAccountId = decodeControlTokenClaims(session.controlToken)?.sub;
59815
+ if (!ownerAccountId) return refuse5(io, "owner_login_required");
59816
+ const readiness = await pushSetupRequest({
59817
+ target: session.target,
59818
+ wixToken: session.wixToken,
59819
+ operation: "status",
59820
+ query: input,
59821
+ previewTag: session.previewTag,
59822
+ fetchImpl: deps.fetchImpl
59823
+ });
59824
+ if (!readiness.ok) {
59825
+ const reported = String(readiness.body?.error ?? "");
59826
+ const code = KNOWN_CODES.has(reported) ? reported : "push_setup_unavailable";
59827
+ return refuse5(io, code, HINTS[code], 1);
59828
+ }
59829
+ if (readiness.body?.app !== input.app || readiness.body?.site !== input.site || readiness.body?.state !== "ready") {
59830
+ return refuse5(io, "push_not_ready", HINTS.push_not_ready, 1);
59831
+ }
59832
+ let recipients;
59833
+ try {
59834
+ await assertOwnerSiteToken({
59835
+ site: input.site,
59836
+ siteToken: siteToken2,
59837
+ ownerAccountId,
59838
+ fetch: deps.fetchImpl
59839
+ });
59840
+ recipients = await listApprovedMembers({ site: input.site, siteToken: siteToken2, fetchImpl: deps.fetchImpl });
59841
+ } catch (error) {
59842
+ const code = error instanceof PushSendError ? error.code : error instanceof Error && ROSTER_CODES.has(error.message) ? error.message : "member_lookup_unavailable";
59843
+ return refuse5(io, code, void 0, 1);
59844
+ }
59845
+ const base = { app: input.app, site: input.site, title: text7(flags.title), ...text7(flags.body) ? { body: text7(flags.body) } : {}, link: text7(flags.link) };
59846
+ const send2 = (recipient, memberRequestId, dryRun = false) => pushSendRequest({
59847
+ target: session.target,
59848
+ wixToken: session.wixToken,
59849
+ siteToken: siteToken2,
59850
+ previewTag: session.previewTag,
59851
+ fetchImpl: deps.fetchImpl,
59852
+ body: { ...base, memberEmail: recipient.email, requestId: memberRequestId, ...dryRun ? { dryRun: true } : {} }
59853
+ });
59854
+ if (flags["dry-run"] === true) {
59855
+ if (recipients.length) {
59856
+ const result = await send2(recipients[0], requestId2, true);
59857
+ const code = refusalCode(result);
59858
+ if (code) return refuse5(io, code, HINTS[code], 1);
59859
+ }
59860
+ io.log(JSON.stringify({ ok: true, state: "dry_run", campaignId: requestId2, recipients: recipients.length, sent: false }));
59861
+ return 0;
59862
+ }
59863
+ io.log(JSON.stringify({ ok: true, state: "started", campaignId: requestId2, recipients: recipients.length }));
59864
+ try {
59865
+ const result = await sendCampaign({
59866
+ recipients,
59867
+ campaignId: requestId2,
59868
+ wait: deps.wait ?? ((ms) => new Promise((resolve54) => setTimeout(resolve54, ms))),
59869
+ onProgress: (completed) => {
59870
+ if (completed % 10 === 0 || completed === recipients.length) {
59871
+ io.log(JSON.stringify({ ok: true, state: "progress", campaignId: requestId2, completed, recipients: recipients.length }));
59872
+ }
59873
+ },
59874
+ send: async (recipient, memberRequestId) => {
59875
+ const response = await send2(recipient, memberRequestId);
59876
+ const code = refusalCode(response);
59877
+ if (code === "rate_limited" && response.retryAfterSec) return { state: "rate_limited", retryAfterSec: response.retryAfterSec };
59878
+ if (code) return { state: "failed", code };
59879
+ return { state: "accepted", deduplicated: response.body?.deduplicated === true };
59880
+ }
59881
+ });
59882
+ io.log(JSON.stringify({ ok: true, state: "completed", campaignId: requestId2, recipients: recipients.length, ...result }));
59883
+ return 0;
59884
+ } catch (error) {
59885
+ if (!(error instanceof CampaignFailure)) return refuse5(io, "send_outcome_unknown", void 0, 1, { campaignId: requestId2 });
59886
+ const hint = error.code === "send_outcome_unknown" || error.code === "rate_limited" ? `Re-run the same command with --request-id ${requestId2} within Ping's 24-hour deduplication window.` : HINTS[error.code];
59887
+ return refuse5(io, error.code, hint, 1, {
59888
+ campaignId: requestId2,
59889
+ completed: error.completed,
59890
+ memberEmail: error.memberEmail
59891
+ });
59892
+ }
59893
+ }
59430
59894
  function flagRefusal(flags) {
59431
59895
  if (LOCAL_SENDER_FLAGS.some((f) => flags[f] !== void 0)) return "flag_not_supported_with_ping";
59432
59896
  if (SWITCHES.some((f) => flags[f] !== void 0 && typeof flags[f] !== "boolean")) return "invalid_boolean_flag";
@@ -59461,20 +59925,24 @@ function summary(result, sent) {
59461
59925
  }
59462
59926
  return `Ping accepted request ${sent.requestId} for ${sent.memberEmail}. Ping does not confirm delivery: it reaches the member's signed-in devices with notifications allowed.`;
59463
59927
  }
59464
- var LOCAL_SENDER_FLAGS, SWITCHES, REQUIRED_FLAGS, UUID_RE, KNOWN_CODES, HINTS, text7;
59928
+ var LOCAL_SENDER_FLAGS, SWITCHES, REQUIRED_FLAGS, UUID_RE, KNOWN_CODES, ROSTER_CODES, HINTS, text7;
59465
59929
  var init_notify_ping = __esm({
59466
59930
  "src/notify-ping.ts"() {
59467
59931
  init_push_send();
59932
+ init_push_setup();
59933
+ init_site_member();
59468
59934
  init_push_send_service();
59469
59935
  init_hosted_owner();
59936
+ init_notify_ping_all();
59470
59937
  init_owner_claim();
59471
59938
  init_push_onboarding();
59472
59939
  init_wix_owner();
59473
59940
  LOCAL_SENDER_FLAGS = ["token", "device", "all", "admin-url", "admin-session", "silent", "badge", "data", "category", "channel", "platform", "bundle-id"];
59474
- SWITCHES = ["json", "dry-run"];
59941
+ SWITCHES = ["json", "dry-run", "all-members"];
59475
59942
  REQUIRED_FLAGS = ["member-email", "title", "link"];
59476
59943
  UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
59477
59944
  KNOWN_CODES = new Set(PUSH_SEND_ERROR_CODES);
59945
+ ROSTER_CODES = /* @__PURE__ */ new Set(["member_lookup_unavailable", "member_roster_unstable", "member_roster_incomplete", "member_ambiguous"]);
59478
59946
  HINTS = {
59479
59947
  push_not_ready: "Run himi notify setup for this app and site first; status must be ready.",
59480
59948
  member_not_found: "Use the login email of an approved member of this site.",
@@ -59500,7 +59968,7 @@ __export(notify_exports, {
59500
59968
  runNotify: () => runNotify,
59501
59969
  saveAdminSession: () => saveAdminSession
59502
59970
  });
59503
- import { chmodSync as chmodSync3, mkdirSync as mkdirSync30, readFileSync as readFileSync76, writeFileSync as writeFileSync26 } from "node:fs";
59971
+ import { chmodSync as chmodSync3, mkdirSync as mkdirSync30, readFileSync as readFileSync76, writeFileSync as writeFileSync25 } from "node:fs";
59504
59972
  import { homedir as homedir6 } from "node:os";
59505
59973
  import { dirname as dirname35, join as join62 } from "node:path";
59506
59974
  function defaultSessionsPath() {
@@ -59517,7 +59985,7 @@ function saveAdminSession(adminUrl, session, path2 = defaultSessionsPath()) {
59517
59985
  const file = loadSessionsFile(path2);
59518
59986
  file.sessions = { ...file.sessions, [adminUrl]: session };
59519
59987
  mkdirSync30(dirname35(path2), { recursive: true, mode: 448 });
59520
- writeFileSync26(path2, JSON.stringify(file, null, 2) + "\n", { mode: 384 });
59988
+ writeFileSync25(path2, JSON.stringify(file, null, 2) + "\n", { mode: 384 });
59521
59989
  try {
59522
59990
  chmodSync3(dirname35(path2), 448);
59523
59991
  chmodSync3(path2, 384);
@@ -63276,7 +63744,7 @@ var init_brand_font = __esm({
63276
63744
  });
63277
63745
 
63278
63746
  // src/site-analyze/write-artifacts.ts
63279
- import { mkdirSync as mkdirSync31, writeFileSync as writeFileSync27 } from "node:fs";
63747
+ import { mkdirSync as mkdirSync31, writeFileSync as writeFileSync26 } from "node:fs";
63280
63748
  import { join as join63 } from "node:path";
63281
63749
  function attachVeloSources(book, sources, put) {
63282
63750
  const byPath = new Map((sources ?? []).map((s) => [s.path, s.text]));
@@ -63438,7 +63906,7 @@ async function writeBusinessBook(result, opts) {
63438
63906
  const put = (rel, body) => {
63439
63907
  const p = join63(out2, rel);
63440
63908
  mkdirSync31(join63(p, ".."), { recursive: true });
63441
- writeFileSync27(p, body);
63909
+ writeFileSync26(p, body);
63442
63910
  files.push(rel);
63443
63911
  };
63444
63912
  const book = result.book;
@@ -63531,7 +63999,7 @@ var init_customer_build = __esm({
63531
63999
  });
63532
64000
 
63533
64001
  // src/site-analyze/apply.ts
63534
- import { existsSync as existsSync65, readFileSync as readFileSync77, writeFileSync as writeFileSync28, copyFileSync as copyFileSync3, mkdirSync as mkdirSync32, realpathSync as realpathSync10, statSync as statSync18 } from "node:fs";
64002
+ import { existsSync as existsSync65, readFileSync as readFileSync77, writeFileSync as writeFileSync27, copyFileSync as copyFileSync3, mkdirSync as mkdirSync32, realpathSync as realpathSync10, statSync as statSync18 } from "node:fs";
63535
64003
  import { dirname as dirname36, isAbsolute as isAbsolute7, join as join64, resolve as resolve51, sep as sep17 } from "node:path";
63536
64004
  import { spawnSync as spawnSync4 } from "node:child_process";
63537
64005
  function unpairedRedirectNote(overlay) {
@@ -63683,10 +64151,10 @@ function buildMergedContract(options) {
63683
64151
  const body = `${JSON.stringify(merged, null, 2)}
63684
64152
  `;
63685
64153
  const path2 = join64(packageDir, "provisioning.contract.json");
63686
- writeFileSync28(path2, body);
64154
+ writeFileSync27(path2, body);
63687
64155
  const localPath = join64(appDir3, LOCAL_CONTRACT_REL);
63688
64156
  mkdirSync32(dirname36(localPath), { recursive: true });
63689
- writeFileSync28(localPath, body);
64157
+ writeFileSync27(localPath, body);
63690
64158
  staged.push(localPath);
63691
64159
  return { path: path2, staged };
63692
64160
  }
@@ -63785,10 +64253,10 @@ async function applyOverlays(input) {
63785
64253
  const cur = JSON.parse(readFileSync77(pkgProv, "utf8"));
63786
64254
  const merged = deepMerge4(cur, overlay);
63787
64255
  packageFaq = merged.faq;
63788
- writeFileSync28(pkgProv, JSON.stringify(merged, null, 2) + "\n");
64256
+ writeFileSync27(pkgProv, JSON.stringify(merged, null, 2) + "\n");
63789
64257
  wrote.push(pkgProv);
63790
64258
  } else {
63791
- writeFileSync28(pkgProv, JSON.stringify(overlay, null, 2) + "\n");
64259
+ writeFileSync27(pkgProv, JSON.stringify(overlay, null, 2) + "\n");
63792
64260
  wrote.push(pkgProv);
63793
64261
  }
63794
64262
  const pkgTokens = join64(cwd, "tokens.json");
@@ -63809,12 +64277,12 @@ async function applyOverlays(input) {
63809
64277
  if (bringsBrand) {
63810
64278
  const accent = tokens.colors?.accentPrimary;
63811
64279
  const primary = typeof accent === "string" ? accent : accent?.light;
63812
- const shadows = merged.shadows;
63813
- const glow = shadows && typeof shadows === "object" && !Array.isArray(shadows) ? shadows[BRAND_GLOW_SHADOW] : void 0;
64280
+ const shadows2 = merged.shadows;
64281
+ const glow = shadows2 && typeof shadows2 === "object" && !Array.isArray(shadows2) ? shadows2[BRAND_GLOW_SHADOW] : void 0;
63814
64282
  if (primary && glow && typeof glow.color === "string") glow.color = primary;
63815
64283
  stripBrandFromIdiom(merged);
63816
64284
  }
63817
- writeFileSync28(pkgTokens, JSON.stringify(merged, null, 2) + "\n");
64285
+ writeFileSync27(pkgTokens, JSON.stringify(merged, null, 2) + "\n");
63818
64286
  wrote.push(pkgTokens);
63819
64287
  }
63820
64288
  const homeAction = homeRefreshActionId(shell);
@@ -63844,7 +64312,7 @@ async function applyOverlays(input) {
63844
64312
  }
63845
64313
  }
63846
64314
  if (next !== prev) {
63847
- writeFileSync28(homeSrc, next);
64315
+ writeFileSync27(homeSrc, next);
63848
64316
  wrote.push(homeSrc);
63849
64317
  }
63850
64318
  if (patched.unparsed) {
@@ -68600,7 +69068,7 @@ __export(site_create_exports, {
68600
69068
  runSiteList: () => runSiteList,
68601
69069
  writeProvisioningBinding: () => writeProvisioningBinding
68602
69070
  });
68603
- import { existsSync as existsSync66, readFileSync as readFileSync79, writeFileSync as writeFileSync29 } from "node:fs";
69071
+ import { existsSync as existsSync66, readFileSync as readFileSync79, writeFileSync as writeFileSync28 } from "node:fs";
68604
69072
  import { resolve as resolvePath2 } from "node:path";
68605
69073
  function oauthClientName(projectName) {
68606
69074
  return `${projectName} client`;
@@ -68749,7 +69217,7 @@ async function runSiteCreate(opts) {
68749
69217
  if (!wantOauthApp) return { ...base, nextSteps: nextSteps(metaSiteId) };
68750
69218
  return { ...base, clientId, redirectUri, nextSteps: nextSteps(metaSiteId, clientId) };
68751
69219
  }
68752
- function writeProvisioningBinding(target, binding2, fs = { existsSync: existsSync66, readFileSync: readFileSync79, writeFileSync: writeFileSync29 }) {
69220
+ function writeProvisioningBinding(target, binding2, fs = { existsSync: existsSync66, readFileSync: readFileSync79, writeFileSync: writeFileSync28 }) {
68753
69221
  const msid = binding2.siteId;
68754
69222
  const path2 = resolvePath2(target);
68755
69223
  const committed = committedShellProvisioning(path2);
@@ -69422,10 +69890,10 @@ var init_context = __esm({
69422
69890
  });
69423
69891
 
69424
69892
  // src/cli.ts
69425
- import { mkdtempSync as mkdtempSync4, readFileSync as readFileSync80, mkdirSync as mkdirSync33, writeFileSync as writeFileSync30, existsSync as existsSync67, rmSync as rmSync16, readdirSync as readdirSync31, statSync as statSync19, lstatSync as lstatSync14, accessSync, constants as constants2, renameSync as renameSync7, cpSync as cpSync3 } from "node:fs";
69893
+ import { mkdtempSync as mkdtempSync4, readFileSync as readFileSync80, mkdirSync as mkdirSync33, writeFileSync as writeFileSync29, existsSync as existsSync67, rmSync as rmSync16, readdirSync as readdirSync31, statSync as statSync19, lstatSync as lstatSync14, accessSync, constants as constants3, renameSync as renameSync8, cpSync as cpSync3 } from "node:fs";
69426
69894
  import { execFile, execFileSync as execFileSync7, spawnSync as spawnSync5, spawn as spawn7 } from "node:child_process";
69427
69895
  import { AsyncLocalStorage } from "node:async_hooks";
69428
- import { createHash as createHash31, randomUUID as randomUUID7 } from "node:crypto";
69896
+ import { createHash as createHash32, randomUUID as randomUUID7 } from "node:crypto";
69429
69897
  import { tmpdir as tmpdir5, homedir as homedir7, hostname } from "node:os";
69430
69898
  import { join as join65, resolve as resolve53, relative as relative14, basename as basename17, dirname as dirname37 } from "node:path";
69431
69899
  import { fileURLToPath as fileURLToPath26 } from "node:url";
@@ -71447,6 +71915,7 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
71447
71915
  "allow-destructive",
71448
71916
  "allow-draft",
71449
71917
  "all",
71918
+ "all-members",
71450
71919
  "all-screens",
71451
71920
  "ai",
71452
71921
  "binary",
@@ -71481,6 +71950,7 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
71481
71950
  "wait",
71482
71951
  "wix",
71483
71952
  "silent",
71953
+ "with-files",
71484
71954
  "include-disabled",
71485
71955
  "skip-build",
71486
71956
  "refresh",
@@ -71872,9 +72342,12 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
71872
72342
  file is not the plane's per-release archive.
71873
72343
  himi share --file <package.himi> [--expires-at <ISO>] | revoke --app <app> <shareId>
71874
72344
  publish a content-addressed package link for a released app
71875
- himi mcp [plugin --out <dir>] the himi tools as a local MCP server over stdio, for coding
72345
+ himi mcp [plugin --out <dir>] [--with-files]
72346
+ the himi tools as a local MCP server over stdio, for coding
71876
72347
  agents (Claude Code, Cursor, Codex, VS Code). \`himi init\`
71877
- configures each; see \`himi docs himi-mcp\`.
72348
+ configures each; see \`himi docs himi-mcp\`. --with-files
72349
+ adds file tools confined to the workspace, for a host that
72350
+ cannot edit files (Claude Desktop chat).
71878
72351
  himi run [<package.himi|dir|url>] [--browser] [--watch [--idle-exit <min>]] [--lan] [--port <n>] [--chrome preview|app] [--screen <id>] [--surface <id>] [--no-open]
71879
72352
  serve a package on localhost + open it in a browser. No serve
71880
72353
  plane, no dev-server. With no argument it packs the current
@@ -72044,6 +72517,12 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
72044
72517
  wix login as the site owner, and a ready notify setup. Acceptance is not delivery.
72045
72518
  --dry-run checks ownership, member, link and readiness without sending. After an unknown
72046
72519
  outcome, re-run the same command with the printed --request-id; Ping never runs it twice.
72520
+ himi notify send --provider ping --target wix --app <a> --site <msid> --all-members
72521
+ --title <s> [--body <s>] --link <scheme>://screen/<id>[?\u2026] [--dry-run | --yes] [--request-id <uuid>]
72522
+ Sends to every approved site member with a login email. --dry-run counts recipients and
72523
+ checks one member's server gates; --yes is required to send. The CLI paces sends for the
72524
+ owner's 10/minute limit. Reuse the campaign ID with --request-id after an interrupted run
72525
+ within Ping's 24-hour deduplication window. Acceptance is not delivery.
72047
72526
  himi notify send --app <a> (--token <t> | --device <id> | --all) --title <s> [--body <s>] [--link <url>]
72048
72527
  deliver a REAL push notification to registered devices \u2014
72049
72528
  APNs (.p8) for iOS, FCM v1 for Android. --all fans out to
@@ -72236,7 +72715,7 @@ var COMMANDS = [
72236
72715
  { name: "icon", summary: "the app's LAUNCHER icon \u2014 the home-screen / store mark, which is BINARY-floor and never over-the-air (distinct from `himi icons`, the in-screen glyph vocabulary). `status`/`check` report what this app would actually ship and validate it against both stores: exact 1024x1024, no tRNS chunk, non-interlaced, and \u2014 when the optional image toolchain is installed \u2014 no transparent regions (Apple permits an alpha channel but not transparent pixels: ITMS-90717), no pre-masked corners, no letterboxed wordmark; they also print the store-policy checklist no checker can decide. `set <file.png>` installs supplied art, `from-site` installs the logo `himi site analyze` downloaded, and `generate` makes a gradient tile + glyph \u2014 all three trim, flatten onto the app's accent, write the 1024 opaque master plus the 512 Play listing icon (Play wants 32-bit WITH alpha, so it is a separate file), and DECLARE it in himalaya.content.json, without which the release bakes no icon at all", required: "[status|check] | set <file.png> | from-site [--book <dir>] | generate [--glyph <g>] [--accent <hex>] | glyphs [--content <dir>] [--json]" },
72237
72716
  { name: "login", summary: "obtain a scoped serve-control token from Wix identity and store it in ~/.himalaya/serve-targets.json (so push/promote need no hand-pasted token). Three modes: --browser (your own Wix account, no VPN/BO), --wix (HEADLESS \u2014 the machine's own Wix CLI credential; the mode a CI pod or agent container can satisfy), --dpx (Wix employee, reads ~/.dpx/session/token). RFC 0026 WS3.", required: "[--browser|--wix|--dpx] [--target <t>] [--issue-url <url>]" },
72238
72717
  { name: "logout", summary: "remove the stored control token for a target", required: "[--target <t>]" },
72239
- { name: "notify", summary: "`setup` optionally configures Branded/Ping from local customer key files; `status --refresh` reads live backend readiness. Setup may be deferred while building. These use serve-control and require owner plus Wix account authentication. `send`, `tokens`, `doctor`: send a REAL push notification to registered devices (RFC 0036 WS5) \u2014 APNs .p8 over HTTP/2 for iOS, FCM v1 for Android. `send` targets one device (--token / --device <registry id>) or fans out to the whole app (--all), reporting a per-device result; a token the provider says is uninstalled (APNs 410 Unregistered / FCM UNREGISTERED) is auto-disabled, while a merely wrong one (BadDeviceToken \u2014 usually a sandbox/production mismatch) is recorded and left enabled. `tokens` lists the registry. `doctor` reports which credentials resolve and which sender each platform picks. Talks to the admin-server via --admin-url/HIMALAYA_ADMIN_URL, authenticated with --admin-session/HIMALAYA_ADMIN_SESSION. `send --provider ping` is the hosted alternative: the serve plane sends one test notification to one site member through Ping as Himalaya, with owner login instead of provider keys, and reports acceptance, never delivery. NOT `himi push`, which publishes a release.", required: "setup|status [--content <dir>] [--target <t>] [--refresh] [--yes] [--no-push-setup] | send --provider ping [--content <dir>] [--app <a>] [--site <msid>] --member-email <email> --title <s> [--body <s>] --link <url> [--request-id <uuid>] [--dry-run] [--json] | send|tokens|doctor [--app <a>] [--token <t> | --device <id> | --all] [--title <s>] [--body <s>] [--link <url>] [--platform ios|android] [--badge <n>] [--channel <id>] [--silent] [--dry-run] [--json]" },
72718
+ { name: "notify", summary: "Hosted --all-members requires --yes for a live send; --dry-run checks ownership/readiness and counts approved recipients without sending. `setup` optionally configures Branded/Ping from local customer key files; `status --refresh` reads live backend readiness. Setup may be deferred while building. These use serve-control and require owner plus Wix account authentication. `send`, `tokens`, `doctor`: send a REAL push notification to registered devices (RFC 0036 WS5) \u2014 APNs .p8 over HTTP/2 for iOS, FCM v1 for Android. `send` targets one device (--token / --device <registry id>) or fans out to the whole app (--all), reporting a per-device result; a token the provider says is uninstalled (APNs 410 Unregistered / FCM UNREGISTERED) is auto-disabled, while a merely wrong one (BadDeviceToken \u2014 usually a sandbox/production mismatch) is recorded and left enabled. `tokens` lists the registry. `doctor` reports which credentials resolve and which sender each platform picks. Talks to the admin-server via --admin-url/HIMALAYA_ADMIN_URL, authenticated with --admin-session/HIMALAYA_ADMIN_SESSION. `send --provider ping` is the hosted alternative: the serve plane sends to one site member (--member-email) or all approved members (--all-members) through Ping as Himalaya, with owner login instead of provider keys, and reports acceptance, never delivery. NOT `himi push`, which publishes a release.", required: "setup|status [--content <dir>] [--target <t>] [--refresh] [--yes] [--no-push-setup] | send --provider ping [--content <dir>] [--app <a>] [--site <msid>] (--member-email <email> | --all-members (--dry-run | --yes)) --title <s> [--body <s>] --link <url> [--request-id <uuid>] [--dry-run] [--json] | send|tokens|doctor [--app <a>] [--token <t> | --device <id> | --all] [--title <s>] [--body <s>] [--link <url>] [--platform ios|android] [--badge <n>] [--channel <id>] [--silent] [--dry-run] [--json]" },
72240
72719
  { name: "site", summary: "PROVISION a Wix project, then analyze or extend it. `create` is the one door that makes a site instead of adopting one: POST /funnel/projects/v1/create (a WIX site, a HEADLESS backend, a BRANDED_APP, or a VIBE site), and for headless it also mints the OAuth app whose id IS the `WIX_CLIENT_ID` \u2014 with --redirect-uri already registered, because an unregistered redirect makes native signInDirect fail at redirect-session. `--verticals` REQUIRES `--template` and reports only what Wix confirmed: measured, installing onto a project created from the BLANK template returns 200 while the vertical's API answers 428 forever, because there is no site for it to attach to (the install asks for CREATE_SITE). A real template ships its verticals installed and populated, and installing further ones onto it then works. It MUTATES your Wix account and needs --yes; without it you get the payload and nothing is sent. `list` is the read-only site inventory (with each site's premium flag, which is what explains an installed-but-428 vertical). Auth is the account-scoped `wix token` (no --site), falling back to WIX_API_KEY + WIX_ACCOUNT_ID. `client` is the same OAuth step for a site that ALREADY exists \u2014 the one himi adopts rather than makes \u2014 so a clientId no longer means a manual trip to the dashboard: `client ensure` reconciles rather than mints, because a site can already carry a client with NO registered redirect and shipping that one fails signInDirect at runtime; it reuses a client that carries --redirect-uri, PATCHes one that matches --name but lacks it (unioning, so another app's redirect survives), and creates only when there is nothing to reuse (--new forces a fresh one). --name IS the idempotency key. `client` auth is SITE-scoped (`wix token --site <msid>`) because minting is owner-of-that-site, and --yes gates every mutation: a site that already has a usable client resolves WITHOUT it, but --write-provisioning needs --yes even then because it rewrites a manifest on disk (the dry run's planned.provisioning names the file). That query endpoint IGNORES paging (measured: query.paging, cursorPaging and a top-level paging all return the full set at offset 0), so when pagingMetadata reports more clients than came back, `ensure` REFUSES EVERY outcome \u2014 create, patch AND reuse. Reuse is refused too because pickClient breaks ties on the OLDEST createdDate, so an omitted older same-named MOBILE client with the same redirect would win a later complete read and the resolved clientId would MOVE between runs \u2014 and it gets written into provisioning.json; convergence is what makes a stray duplicate untidy rather than harmful. --name cannot narrow the query (no filter is sent; the name is applied to the rows after they arrive), so the recovery is the dashboard; --new skips the query entirely. A `patched` result is verified against what Wix STORED \u2014 the redirect list is read off the PATCH response, and re-read via GET when the response omits it, because falling back to the list himi just sent would make the guard pass vacuously on exactly the silent no-op it exists to catch. `client list` is the read-only inventory of a site's clients: act on its `shippable` column rather than `hasRedirect`, because shippable ALSO requires a MOBILE applicationType and `ensure` refuses a WEB client \u2014 a WEB one carrying your redirect looks right and is not a candidate \u2014 and it sets truncated:true rather than presenting a partial inventory as the whole one. `analyze --read-only` returns API-only generation context as JSON (identity, access probes, CMS permissions and selected redacted Velo source); it needs no URL/browser and never uploads or applies anything. `analyze` reads a site into a business-book folder (REST + Playwright, GET-only); `--url` alone is the visitor-only pass over the published pages, and `--client-id` adds the vertical probes and the app binding `--apply` requires; `site function` list/get/put/publish/invokes that site's Velo HTTP functions and is not `himi functions` (serve-plane jobs). `--apply` bakes into a brandable shell (a site shell needs no `--client-id`: it is visitor-only and binds no client); `--shell` takes the same names `himi dist --shell` does and defaults to branded-lite, and a shell that ships no prepare:customer is refused by name rather than half-built. `--shell brandy` (the site shell, RFC 0062) also derives the site's page map from its published pages and header menu \u2014 Home first, up to four tabs for the pages a visitor returns to, the rest in the drawer \u2014 into the overlay's `site` block, so it can be edited before `--apply`; only a site shell ever receives that block. It never overwrites any shell's committed apps/<shell>/provisioning.json.", required: "create <name> [--type wix|headless|branded-app|vibe] [--template <guid>] [--verticals stores,bookings,\u2026] [--folder <guid>] [--redirect-uri <uri>] [--no-oauth-app] [--write-provisioning [path]] [--site-url <url>] [--json] --yes | list | client ensure|list --site <msid> [--name <name>] [--redirect-uri <uri>] [--new] [--write-provisioning [path]] [--site-url <url>] [--json] --yes | analyze --read-only [--site <msid>] [--client-id <id>] [--json] | analyze --url <https> [--client-id <id>] [--site <msid>] [--out <dir>] [--redirect-uri <uri>] [--apply] [--shell " + SHELL_ENUM + "] | function list|get|put|publish|invoke [--site <msid>] [--url <https>] [--method GET|POST|PUT|DELETE] [--source <file>] [--body <json>] [--publish] [--invoke]" },
72241
72720
  { name: "doctor", summary: 'off-VPN preflight in one shot: probe the PUBLIC data plane (www.wixapis.com \u2014 reachable off-VPN), the INTERNAL serve/control plane for the selected target (VPN-gated), and report auth state (dpx session on disk + stored control token). --dpx additionally runs a live, EADDRINUSE-safe `dpx whoami`. Machine-readable with --json (an unreachable internal plane carries a stable code:"needs_vpn" so an agent branches on the code, not the prose); human-readable otherwise. Never loads the authoring runtime \u2014 works off-repo.', required: "[--target <t>] [--dpx] [--json] [--data-url <url>] [--timeout <ms>]" },
72242
72721
  { name: "upgrade", summary: "install the newest published CLI (`npm i -g @wix/himalaya-cli@latest`) and report what moved. This is the explicit door: himi also updates itself when the running build is more than a week old \u2014 unattended it just does it and re-runs your command, at a terminal it asks first \u2014 and `HIMI_NO_UPDATE_CHECK=1` turns that off, leaving this command as the way to catch up. Reports `unchanged` rather than claiming success when npm exits 0 without moving the version, which is what a registry mirror serving its own pinned copy looks like.", required: "[--json]" },
@@ -72475,7 +72954,7 @@ function readUpdateCache(path2) {
72475
72954
  function writeUpdateCache(path2, cache) {
72476
72955
  try {
72477
72956
  mkdirSync33(dirname37(path2), { recursive: true, mode: 448 });
72478
- writeFileSync30(path2, JSON.stringify(cache, null, 2) + "\n", { mode: 384 });
72957
+ writeFileSync29(path2, JSON.stringify(cache, null, 2) + "\n", { mode: 384 });
72479
72958
  } catch {
72480
72959
  }
72481
72960
  }
@@ -72506,7 +72985,7 @@ function globalPrefixWritable() {
72506
72985
  const root = globalNodeModulesRoot();
72507
72986
  if (!root) return false;
72508
72987
  try {
72509
- accessSync(root, constants2.W_OK);
72988
+ accessSync(root, constants3.W_OK);
72510
72989
  return true;
72511
72990
  } catch {
72512
72991
  return false;
@@ -72617,7 +73096,7 @@ function acquireUpdateLock(cachePath, now, hooks = {}) {
72617
73096
  };
72618
73097
  const tryCreate = (path2) => {
72619
73098
  try {
72620
- writeFileSync30(path2, mine, { flag: "wx", mode: 384 });
73099
+ writeFileSync29(path2, mine, { flag: "wx", mode: 384 });
72621
73100
  return true;
72622
73101
  } catch {
72623
73102
  return false;
@@ -73205,7 +73684,7 @@ function executableOnPath(name, env = process.env) {
73205
73684
  const candidate = join65(dir, `${name}${suffix}`);
73206
73685
  try {
73207
73686
  if (!statSync19(candidate).isFile()) continue;
73208
- if (process.platform !== "win32") accessSync(candidate, constants2.X_OK);
73687
+ if (process.platform !== "win32") accessSync(candidate, constants3.X_OK);
73209
73688
  return true;
73210
73689
  } catch {
73211
73690
  }
@@ -73332,7 +73811,7 @@ function readConfigSource(dir) {
73332
73811
  }
73333
73812
  function writePngFile(path2, base64) {
73334
73813
  mkdirSync33(dirname37(path2), { recursive: true });
73335
- writeFileSync30(path2, Buffer.from(base64, "base64"));
73814
+ writeFileSync29(path2, Buffer.from(base64, "base64"));
73336
73815
  return path2;
73337
73816
  }
73338
73817
  async function seedAgentFilesFor(dir, flags, mode = "init") {
@@ -74535,7 +75014,7 @@ ${doNow.map((l) => ` ${l}`).join("\n")}`);
74535
75014
  try {
74536
75015
  const bytes2 = readFileSync80(iconPath);
74537
75016
  payload.icon = bytes2.toString("base64");
74538
- iconHash = createHash31("sha256").update(bytes2).digest("hex");
75017
+ iconHash = createHash32("sha256").update(bytes2).digest("hex");
74539
75018
  } catch {
74540
75019
  }
74541
75020
  }
@@ -74955,7 +75434,7 @@ ${doNow.map((l) => ` ${l}`).join("\n")}`);
74955
75434
  const files = himiPluginFiles2(skills.skillMd);
74956
75435
  for (const f of files) {
74957
75436
  mkdirSync33(dirname37(join65(dir, f.path)), { recursive: true });
74958
- writeFileSync30(join65(dir, f.path), f.text);
75437
+ writeFileSync29(join65(dir, f.path), f.text);
74959
75438
  }
74960
75439
  print({ ok: true, dir, files: files.map((f) => f.path), next: [`claude plugin validate ${dir}`, `claude --plugin-dir ${dir}`] });
74961
75440
  return 0;
@@ -74981,7 +75460,7 @@ ${doNow.map((l) => ` ${l}`).join("\n")}`);
74981
75460
  const out2 = resolve53(str8(flags.out) ?? join65(dir, ".himi", "making-of.html"));
74982
75461
  const { html, plan } = buildReel2(dir);
74983
75462
  mkdirSync33(dirname37(out2), { recursive: true });
74984
- writeFileSync30(out2, html);
75463
+ writeFileSync29(out2, html);
74985
75464
  const { pathToFileURL: pathToFileURL9 } = await import("node:url");
74986
75465
  const url2 = pathToFileURL9(out2).href;
74987
75466
  const open2 = flags["no-open"] !== true;
@@ -76325,7 +76804,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
76325
76804
  let localSourceHash;
76326
76805
  try {
76327
76806
  const { collectAppSource: collectAppSource2, zipAppSource: zipAppSource2 } = await Promise.resolve().then(() => (init_app_source(), app_source_exports));
76328
- localSourceHash = createHash31("sha256").update(zipAppSource2(collectAppSource2(dir).entries).toString("base64")).digest("hex");
76807
+ localSourceHash = createHash32("sha256").update(zipAppSource2(collectAppSource2(dir).entries).toString("base64")).digest("hex");
76329
76808
  } catch {
76330
76809
  }
76331
76810
  return runReleasePatch({
@@ -77201,9 +77680,9 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
77201
77680
  const { describeChanges: describeChanges2, headline: headline2 } = await Promise.resolve().then(() => (init_build_changes(), build_changes_exports));
77202
77681
  const changes = describeChanges2(live2, join65(next, running.app), diff);
77203
77682
  const retired = `${live2}.retired-${Date.now()}`;
77204
- renameSync7(live2, retired);
77683
+ renameSync8(live2, retired);
77205
77684
  try {
77206
- renameSync7(join65(next, running.app), live2);
77685
+ renameSync8(join65(next, running.app), live2);
77207
77686
  } catch {
77208
77687
  cpSync3(join65(next, running.app), live2, { recursive: true });
77209
77688
  }
@@ -77941,7 +78420,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
77941
78420
  });
77942
78421
  }
77943
78422
  const bytes2 = readFileSync80(file);
77944
- const sha = createHash31("sha256").update(bytes2).digest("hex");
78423
+ const sha = createHash32("sha256").update(bytes2).digest("hex");
77945
78424
  const forScreens = (str8(flags["for-screen"]) ?? "").split(",").map((s) => s.trim()).filter(Boolean);
77946
78425
  if (flags["no-rebind"] !== void 0 && flags["no-rebind"] !== true) {
77947
78426
  io.error(JSON.stringify({ ok: false, error: "no_rebind_takes_no_value", message: "--no-rebind is a switch and takes no value; pass it bare.", usage: PATCH_USAGE }));