@wix/himalaya-cli 0.1340.0 → 0.1342.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.
Files changed (2) hide show
  1. package/dist/cli.mjs +268 -108
  2. package/package.json +2 -2
package/dist/cli.mjs CHANGED
@@ -3727,6 +3727,121 @@ var init_open_url = __esm({
3727
3727
  }
3728
3728
  });
3729
3729
 
3730
+ // src/browser-login.ts
3731
+ import { createServer } from "node:http";
3732
+ import { randomBytes as randomBytes2 } from "node:crypto";
3733
+ function defaultOpenBrowser(url) {
3734
+ openUrl(url);
3735
+ }
3736
+ async function browserLogin(opts) {
3737
+ const state = randomBytes2(16).toString("hex");
3738
+ const log = opts.log ?? (() => {
3739
+ });
3740
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
3741
+ return await new Promise((resolve61, reject) => {
3742
+ let settled = false;
3743
+ let loginUrl = "";
3744
+ const server = createServer();
3745
+ const finish2 = (fn2) => {
3746
+ if (settled) return;
3747
+ settled = true;
3748
+ clearTimeout(timer);
3749
+ server.close(() => fn2());
3750
+ server.closeAllConnections?.();
3751
+ };
3752
+ const timer = setTimeout(() => {
3753
+ finish2(() => reject(new Error(`login timed out after ${Math.round(timeoutMs / 1e3)}s waiting for the browser callback`)));
3754
+ }, timeoutMs);
3755
+ if (typeof timer.unref === "function") timer.unref();
3756
+ const MAX_CALLBACK_BODY = 1024 * 1024;
3757
+ const readCallback = (req, url, whenReady) => {
3758
+ if (req.method !== "POST") {
3759
+ whenReady(url.searchParams);
3760
+ return;
3761
+ }
3762
+ const chunks = [];
3763
+ let size = 0;
3764
+ let tooLarge = false;
3765
+ req.on("data", (c2) => {
3766
+ size += c2.length;
3767
+ if (size > MAX_CALLBACK_BODY) tooLarge = true;
3768
+ else chunks.push(c2);
3769
+ });
3770
+ req.on("end", () => whenReady(tooLarge ? null : new URLSearchParams(Buffer.concat(chunks).toString("utf8"))));
3771
+ req.on("error", () => whenReady(null));
3772
+ };
3773
+ server.on("request", (req, res) => {
3774
+ const url = new URL(req.url ?? "/", "http://127.0.0.1");
3775
+ if (url.pathname !== "/callback") {
3776
+ res.writeHead(404).end();
3777
+ return;
3778
+ }
3779
+ readCallback(req, url, (params) => {
3780
+ if (!params) {
3781
+ res.writeHead(413, { "content-type": "text/plain" }).end("callback body too large");
3782
+ finish2(() => reject(new Error("the login callback body was too large to be a valid token")));
3783
+ return;
3784
+ }
3785
+ const token = params.get("token");
3786
+ const gotState = params.get("state");
3787
+ const error = params.get("error");
3788
+ if (error) {
3789
+ const safe = error.replace(/[^\x20-\x7e]/g, "").slice(0, 200);
3790
+ res.writeHead(400, { "content-type": "text/plain" }).end(`login failed: ${safe}`);
3791
+ finish2(() => reject(new Error(`issuer reported: ${safe}`)));
3792
+ return;
3793
+ }
3794
+ if (gotState !== state) {
3795
+ res.writeHead(403, { "content-type": "text/plain" }).end("state mismatch \u2014 possible callback hijack");
3796
+ finish2(() => reject(new Error("state mismatch on the login callback \u2014 refusing the token")));
3797
+ return;
3798
+ }
3799
+ if (!token) {
3800
+ res.writeHead(400, { "content-type": "text/plain" }).end("missing token");
3801
+ finish2(() => reject(new Error("the login callback carried no token")));
3802
+ return;
3803
+ }
3804
+ res.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store", connection: "close" }).end(closePage(opts.doneTitle ?? "Signed in to himi"));
3805
+ const extra = {};
3806
+ for (const [k2, v2] of params) if (k2 !== "token" && k2 !== "state") extra[k2] = v2;
3807
+ finish2(() => resolve61({ token, loginUrl, extra }));
3808
+ });
3809
+ });
3810
+ server.on("error", (err) => finish2(() => reject(err)));
3811
+ server.listen(opts.port ?? 0, "127.0.0.1", () => {
3812
+ const addr = server.address();
3813
+ const port = typeof addr === "object" && addr ? addr.port : 0;
3814
+ if (!port) {
3815
+ finish2(() => reject(new Error("could not bind a loopback port for the login callback")));
3816
+ return;
3817
+ }
3818
+ const base = opts.ownerIssue.replace(/\/$/, "");
3819
+ const q2 = new URLSearchParams({ ...opts.query ?? {}, callback_port: String(port), state, response_mode: "form_post" });
3820
+ loginUrl = `${base}${opts.path ?? "/v1/owner/cli-login"}?${q2}`;
3821
+ if (opts.noOpen) {
3822
+ log(`Open this URL to sign in:
3823
+ ${loginUrl}`);
3824
+ } else {
3825
+ log(`Opening your browser to sign in to Wix\u2026
3826
+ (if it doesn't open: ${loginUrl})`);
3827
+ (opts.openBrowser ?? defaultOpenBrowser)(loginUrl);
3828
+ }
3829
+ });
3830
+ });
3831
+ }
3832
+ var DEFAULT_TIMEOUT_MS, closePage;
3833
+ var init_browser_login = __esm({
3834
+ "src/browser-login.ts"() {
3835
+ init_open_url();
3836
+ DEFAULT_TIMEOUT_MS = 5 * 60 * 1e3;
3837
+ closePage = (title) => `<!doctype html><meta charset="utf-8"><title>himi</title>
3838
+ <body style="font-family:system-ui,sans-serif;text-align:center;padding:56px;color:#111">
3839
+ <h1 style="font-size:20px;margin:0 0 8px">${title.replace(/[<>&"]/g, "")}</h1>
3840
+ <p style="color:#555;margin:0">You can close this tab and return to your terminal.</p>
3841
+ <script>setTimeout(function(){window.close()},1500)</script></body>`;
3842
+ }
3843
+ });
3844
+
3730
3845
  // src/cms-schema.ts
3731
3846
  var cms_schema_exports = {};
3732
3847
  __export(cms_schema_exports, {
@@ -7591,6 +7706,7 @@ function resolveTranslation(value, catalog3, sourceCatalog) {
7591
7706
  var BINDING_SIGILS, BINDING_SIGIL_RE, TRANSLATION_PREFIX, TRANSLATION_KEY_RE;
7592
7707
  var init_bindings = __esm({
7593
7708
  "../../core/ts/src/bindings.ts"() {
7709
+ "use strict";
7594
7710
  BINDING_SIGILS = ["service", "flag", "item", "t"];
7595
7711
  BINDING_SIGIL_RE = new RegExp(
7596
7712
  `^\\$(?:${BINDING_SIGILS.filter((s2) => s2 !== "t").join("|")})\\b|^\\$t:`
@@ -160934,6 +161050,105 @@ var init_asc_status = __esm({
160934
161050
  }
160935
161051
  });
160936
161052
 
161053
+ // src/connector-token.ts
161054
+ var connector_token_exports = {};
161055
+ __export(connector_token_exports, {
161056
+ runConnectorToken: () => runConnectorToken
161057
+ });
161058
+ async function runConnectorToken(sub, args, ctx) {
161059
+ const base = ctx.ownerIssue.replace(/\/$/, "");
161060
+ const doFetch = ctx.fetchImpl ?? fetch;
161061
+ const managePage = `${base}${PREFIX}personal-token`;
161062
+ if (sub === "create") {
161063
+ if (args.days !== void 0 && !/^[0-9]{1,3}$/.test(args.days)) {
161064
+ ctx.error(JSON.stringify({ ok: false, code: "invalid_days", error: "--days must be a whole number of days (1-90)" }));
161065
+ return 2;
161066
+ }
161067
+ let result;
161068
+ try {
161069
+ result = await browserLogin({
161070
+ ownerIssue: base,
161071
+ path: `${PREFIX}personal-token`,
161072
+ query: { ...args.name ? { name: args.name } : {}, ...args.days ? { days: args.days } : {} },
161073
+ doneTitle: "Token created \u2014 return to your terminal",
161074
+ log: ctx.log,
161075
+ ...args.noOpen ? { noOpen: true } : {},
161076
+ ...ctx.openBrowser ? { openBrowser: ctx.openBrowser } : {},
161077
+ ...ctx.port ? { port: ctx.port } : {}
161078
+ });
161079
+ } catch (e2) {
161080
+ ctx.error(JSON.stringify({
161081
+ ok: false,
161082
+ code: "token_create_failed",
161083
+ error: e2.message,
161084
+ hint: "sign in to wix.com in the browser that opened; with no browser here, pass --no-open and open the printed URL yourself"
161085
+ }));
161086
+ return 1;
161087
+ }
161088
+ const expiresAt = Number(result.extra.expires_at);
161089
+ ctx.print({
161090
+ ok: true,
161091
+ id: result.extra.id,
161092
+ ...Number.isFinite(expiresAt) ? { expiresAt: new Date(expiresAt).toISOString() } : {},
161093
+ token: result.token,
161094
+ mcpUrl: ctx.mcpUrl,
161095
+ header: { name: "Authorization", value: `Bearer ${result.token}` },
161096
+ howTo: "Add a custom MCP server in your tool: the URL above, authentication none / not required, and this header. Shown once \u2014 it is not stored.",
161097
+ revoke: `himi connector token revoke ${result.extra.id ?? "<id>"} --target ${ctx.target}`
161098
+ });
161099
+ return 0;
161100
+ }
161101
+ if (sub === "list" || sub === "revoke") {
161102
+ if (!ctx.controlToken) {
161103
+ ctx.error(JSON.stringify({ ok: false, code: "not_signed_in", error: "not signed in", hint: `run \`himi login --browser --target ${ctx.target}\` first` }));
161104
+ return 2;
161105
+ }
161106
+ if (sub === "revoke" && !args.id) {
161107
+ ctx.error(JSON.stringify({ ok: false, code: "usage", error: "usage: himi connector token revoke <id>" }));
161108
+ return 2;
161109
+ }
161110
+ const res = sub === "list" ? await doFetch(`${base}${PREFIX}personal-tokens`, { redirect: "error", headers: { "x-control-token": ctx.controlToken, accept: "application/json" } }) : await doFetch(`${base}${PREFIX}personal-tokens/revoke`, {
161111
+ method: "POST",
161112
+ redirect: "error",
161113
+ headers: { "x-control-token": ctx.controlToken, "content-type": "application/json", accept: "application/json" },
161114
+ body: JSON.stringify({ id: args.id })
161115
+ });
161116
+ const body = await res.json().catch(() => ({}));
161117
+ if (!res.ok) {
161118
+ const code = res.status === 404 && sub === "revoke" ? "token_not_found" : res.status === 404 ? "personal_tokens_unavailable" : res.status === 401 ? "not_signed_in" : "request_failed";
161119
+ ctx.error(JSON.stringify({
161120
+ ok: false,
161121
+ code,
161122
+ status: res.status,
161123
+ ...body,
161124
+ ...code === "token_not_found" ? { hint: `a token made for another of your Wix accounts is managed on ${managePage}` } : {}
161125
+ }));
161126
+ return 1;
161127
+ }
161128
+ if (sub === "list") {
161129
+ const tokens2 = (body.tokens ?? []).map((t2) => ({
161130
+ id: t2.id,
161131
+ name: t2.name,
161132
+ createdAt: new Date(t2.createdAt).toISOString(),
161133
+ expiresAt: new Date(t2.expiresAt).toISOString()
161134
+ }));
161135
+ ctx.print({ ok: true, tokens: tokens2, note: `tokens of the account your \`himi login\` is for; every account's are on ${managePage}` });
161136
+ } else {
161137
+ ctx.print({ ok: true, revoked: args.id });
161138
+ }
161139
+ return 0;
161140
+ }
161141
+ ctx.error(JSON.stringify({ ok: false, code: "usage", error: "usage: himi connector token create [--name <n>] [--days <1-90>] [--no-open] | list | revoke <id>" }));
161142
+ return 2;
161143
+ }
161144
+ var PREFIX;
161145
+ var init_connector_token = __esm({
161146
+ "src/connector-token.ts"() {
161147
+ init_browser_login();
161148
+ PREFIX = "/v1/connector/oauth/";
161149
+ }
161150
+ });
161151
+
160937
161152
  // ../../core/functions-secrets/src/vault.ts
160938
161153
  import { createCipheriv, createDecipheriv as createDecipheriv2, randomBytes as randomBytes6 } from "node:crypto";
160939
161154
  import { mkdir, readFile as readFile2, rename, writeFile as writeFile2 } from "node:fs/promises";
@@ -164209,10 +164424,10 @@ function familyFromFontShorthand(value) {
164209
164424
  const WEIGHT = String.raw`bold|bolder|lighter|\d{3}`;
164210
164425
  const STRETCH = String.raw`(ultra-|extra-|semi-)?(condensed|expanded)`;
164211
164426
  const FUNC = String.raw`[a-z-]+\(.*\)${LINE}`;
164212
- const PREFIX = new RegExp(`^(${STYLE}|${VARIANT}|${WEIGHT}|${STRETCH}|${SIZE2}|${FUNC})$`, "i");
164427
+ const PREFIX2 = new RegExp(`^(${STYLE}|${VARIANT}|${WEIGHT}|${STRETCH}|${SIZE2}|${FUNC})$`, "i");
164213
164428
  const parts = splitTopLevel(first, IS_SPACE).map((t2) => t2.trim()).filter(Boolean);
164214
164429
  let i2 = 0;
164215
- while (i2 < parts.length - 1 && PREFIX.test(parts[i2])) i2 += 1;
164430
+ while (i2 < parts.length - 1 && PREFIX2.test(parts[i2])) i2 += 1;
164216
164431
  const raw = parts.slice(i2).join(" ").trim();
164217
164432
  const quoted = /^(['"])([\s\S]*)\1$/.exec(raw);
164218
164433
  const family = (quoted ? quoted[2] : raw).replace(/['"]/g, "").trim();
@@ -173750,112 +173965,8 @@ ${res.error?.message ?? ""}`;
173750
173965
  return { ok: false, session, message: `dpx auth status failed${firstLine ? `: ${firstLine}` : ""}` };
173751
173966
  }
173752
173967
 
173753
- // src/browser-login.ts
173754
- init_open_url();
173755
- import { createServer } from "node:http";
173756
- import { randomBytes as randomBytes2 } from "node:crypto";
173757
- var DEFAULT_TIMEOUT_MS = 5 * 60 * 1e3;
173758
- function defaultOpenBrowser(url) {
173759
- openUrl(url);
173760
- }
173761
- var CLOSE_PAGE = `<!doctype html><meta charset="utf-8"><title>himi login</title>
173762
- <body style="font-family:system-ui,sans-serif;text-align:center;padding:56px;color:#111">
173763
- <h1 style="font-size:20px;margin:0 0 8px">Signed in to himi</h1>
173764
- <p style="color:#555;margin:0">You can close this tab and return to your terminal.</p>
173765
- <script>setTimeout(function(){window.close()},1500)</script></body>`;
173766
- async function browserLogin(opts) {
173767
- const state = randomBytes2(16).toString("hex");
173768
- const log = opts.log ?? (() => {
173769
- });
173770
- const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
173771
- return await new Promise((resolve61, reject) => {
173772
- let settled = false;
173773
- let loginUrl = "";
173774
- const server = createServer();
173775
- const finish2 = (fn2) => {
173776
- if (settled) return;
173777
- settled = true;
173778
- clearTimeout(timer);
173779
- server.close(() => fn2());
173780
- server.closeAllConnections?.();
173781
- };
173782
- const timer = setTimeout(() => {
173783
- finish2(() => reject(new Error(`login timed out after ${Math.round(timeoutMs / 1e3)}s waiting for the browser callback`)));
173784
- }, timeoutMs);
173785
- if (typeof timer.unref === "function") timer.unref();
173786
- const MAX_CALLBACK_BODY = 1024 * 1024;
173787
- const readCallback = (req, url, whenReady) => {
173788
- if (req.method !== "POST") {
173789
- whenReady(url.searchParams);
173790
- return;
173791
- }
173792
- const chunks = [];
173793
- let size = 0;
173794
- let tooLarge = false;
173795
- req.on("data", (c2) => {
173796
- size += c2.length;
173797
- if (size > MAX_CALLBACK_BODY) tooLarge = true;
173798
- else chunks.push(c2);
173799
- });
173800
- req.on("end", () => whenReady(tooLarge ? null : new URLSearchParams(Buffer.concat(chunks).toString("utf8"))));
173801
- req.on("error", () => whenReady(null));
173802
- };
173803
- server.on("request", (req, res) => {
173804
- const url = new URL(req.url ?? "/", "http://127.0.0.1");
173805
- if (url.pathname !== "/callback") {
173806
- res.writeHead(404).end();
173807
- return;
173808
- }
173809
- readCallback(req, url, (params) => {
173810
- if (!params) {
173811
- res.writeHead(413, { "content-type": "text/plain" }).end("callback body too large");
173812
- finish2(() => reject(new Error("the login callback body was too large to be a valid token")));
173813
- return;
173814
- }
173815
- const token = params.get("token");
173816
- const gotState = params.get("state");
173817
- const error = params.get("error");
173818
- if (error) {
173819
- const safe = error.replace(/[^\x20-\x7e]/g, "").slice(0, 200);
173820
- res.writeHead(400, { "content-type": "text/plain" }).end(`login failed: ${safe}`);
173821
- finish2(() => reject(new Error(`issuer reported: ${safe}`)));
173822
- return;
173823
- }
173824
- if (gotState !== state) {
173825
- res.writeHead(403, { "content-type": "text/plain" }).end("state mismatch \u2014 possible callback hijack");
173826
- finish2(() => reject(new Error("state mismatch on the login callback \u2014 refusing the token")));
173827
- return;
173828
- }
173829
- if (!token) {
173830
- res.writeHead(400, { "content-type": "text/plain" }).end("missing token");
173831
- finish2(() => reject(new Error("the login callback carried no token")));
173832
- return;
173833
- }
173834
- res.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store", connection: "close" }).end(CLOSE_PAGE);
173835
- finish2(() => resolve61({ token, loginUrl }));
173836
- });
173837
- });
173838
- server.on("error", (err) => finish2(() => reject(err)));
173839
- server.listen(opts.port ?? 0, "127.0.0.1", () => {
173840
- const addr = server.address();
173841
- const port = typeof addr === "object" && addr ? addr.port : 0;
173842
- if (!port) {
173843
- finish2(() => reject(new Error("could not bind a loopback port for the login callback")));
173844
- return;
173845
- }
173846
- const base = opts.ownerIssue.replace(/\/$/, "");
173847
- loginUrl = `${base}/v1/owner/cli-login?callback_port=${port}&state=${state}&response_mode=form_post`;
173848
- if (opts.noOpen) {
173849
- log(`Open this URL to sign in:
173850
- ${loginUrl}`);
173851
- } else {
173852
- log(`Opening your browser to sign in to Wix\u2026
173853
- (if it doesn't open: ${loginUrl})`);
173854
- (opts.openBrowser ?? defaultOpenBrowser)(loginUrl);
173855
- }
173856
- });
173857
- });
173858
- }
173968
+ // src/cli.ts
173969
+ init_browser_login();
173859
173970
 
173860
173971
  // src/cms.ts
173861
173972
  init_cms_schema();
@@ -175425,6 +175536,12 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
175425
175536
  Auth: the \`himi login --browser\` token, or
175426
175537
  --cookie/HIMI_WIX_COOKIE for a scripted call (a claim needs
175427
175538
  the XSRF-TOKEN cookie too).
175539
+ himi connector token create [--name <n>] [--days <1-90>] [--no-open] | list | revoke <id> [--target <t>]
175540
+ a personal access token for the hosted MCP connector, for a
175541
+ tool that cannot sign in with OAuth (e.g. Base44's builder):
175542
+ paste it there as the header Authorization: Bearer <token>.
175543
+ create opens a Wix sign-in page and prints the token ONCE;
175544
+ list/revoke use the \`himi login --browser\` token. Drafts only.
175428
175545
  himi whoami [--target <t>] print the stored control token's identity for a target \u2014
175429
175546
  sub, credential kind, role, app scope, expiry. Purely LOCAL
175430
175547
  (decodes the token; no round trip), so it works offline.
@@ -176002,6 +176119,7 @@ var COMMANDS2 = [
176002
176119
  { 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]" },
176003
176120
  { 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>]" },
176004
176121
  { name: "logout", summary: "remove the stored control token for a target", required: "[--target <t>]" },
176122
+ { name: "connector", summary: "personal access tokens for the hosted MCP connector (Wix Native App Builder), for an AI tool that cannot sign in with OAuth (e.g. Base44's builder): `token create` opens a Wix sign-in page and prints a long-lived token once, to paste into the tool as `Authorization: Bearer <token>`; `token list` / `token revoke <id>` manage them with the `himi login --browser` token. The token can only build and publish DRAFTS.", required: "token create [--name <n>] [--days <1-90>] [--no-open] | token list | token revoke <id> [--target <t>]" },
176005
176123
  { 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]" },
176006
176124
  { name: "signing", summary: "READ-ONLY App Store Connect inventory for customer BYO TestFlight readiness. `inspect` lists whether the bundle id, distribution certificate, IOS_APP_STORE profile, and ASC app record exist for --bundle-id / --team-id using a local ASC .p8 (flags or ASC_KEY_* env). Never uploads or mutates Apple resources. Optional --cert-file/--profile-file classify on-disk material.", required: "inspect --bundle-id <id> --team-id <id> --asc-key-file <path> --asc-key-id <id> --asc-issuer-id <id> [--cert-file <p12> --cert-password <pw> --profile-file <mobileprovision>] [--json]" },
176007
176125
  { 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 browser and never uploads or applies anything. A URL is optional there: `--url` lets it read the published page's Velo code-app ids (credential-free, pinned transport) so the backend source is found on sites where the owner code-app probe 404s. `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>] [--url <https>] [--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]" },
@@ -183344,6 +183462,48 @@ ${label2}`);
183344
183462
  return 1;
183345
183463
  }
183346
183464
  }
183465
+ case "connector": {
183466
+ if (pos[1] !== "token") {
183467
+ io2.error(JSON.stringify({ ok: false, code: "usage", error: "usage: himi connector token create|list|revoke" }));
183468
+ return 2;
183469
+ }
183470
+ const target = targetName();
183471
+ let ownerIssue;
183472
+ let controlToken;
183473
+ try {
183474
+ const t2 = resolveTarget({ target, ownerIssueUrl: str9(flags["owner-issue-url"]), controlToken: str9(flags["control-token"]) });
183475
+ ownerIssue = t2.ownerIssue;
183476
+ controlToken = t2.controlToken;
183477
+ } catch (e2) {
183478
+ io2.error(JSON.stringify({ ok: false, code: "target_unresolved", error: e2.message }));
183479
+ return 2;
183480
+ }
183481
+ if (!ownerIssue) {
183482
+ io2.error(JSON.stringify({ ok: false, code: "no_owner_issuer", error: `no owner issuer for target "${target}"`, hint: "use --target wix, or set target.ownerIssue" }));
183483
+ return 2;
183484
+ }
183485
+ const refusal = ownerIssuerRefusal(ownerIssue);
183486
+ if (refusal) {
183487
+ io2.error(JSON.stringify(refusal));
183488
+ return 2;
183489
+ }
183490
+ const { runConnectorToken: runConnectorToken2 } = await Promise.resolve().then(() => (init_connector_token(), connector_token_exports));
183491
+ const issuerHost = new URL(ownerIssue).hostname;
183492
+ const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(issuerHost);
183493
+ return await runConnectorToken2(
183494
+ pos[2],
183495
+ { id: pos[3], name: str9(flags.name), days: str9(flags.days), noOpen: flags["no-open"] === true },
183496
+ {
183497
+ ownerIssue,
183498
+ ...controlToken ? { controlToken } : {},
183499
+ target,
183500
+ mcpUrl: str9(flags["mcp-url"]) ?? `${loopback ? ownerIssue.replace(/\/$/, "") : SERVE_ORIGIN_PUBLIC_APPS}/v1/connector/mcp`,
183501
+ print,
183502
+ error: (l2) => io2.error(l2),
183503
+ log: (l2) => io2.error(l2)
183504
+ }
183505
+ );
183506
+ }
183347
183507
  case "logout": {
183348
183508
  const target = targetName();
183349
183509
  let revoked;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.1340.0",
3
+ "version": "0.1342.0",
4
4
  "type": "module",
5
5
  "description": "himi — the Himalaya authoring CLI. Build a Tier-5 content package on your own machine, push it to the production serve as a draft, preview it on web + Himi Player, and read worker logs back — the remote dev loop. Wraps @himalaya/serve-cli for transport.",
6
6
  "keywords": [
@@ -71,5 +71,5 @@
71
71
  "artifactId": "himalaya-cli"
72
72
  }
73
73
  },
74
- "falconPackageHash": "4fcbb25c4038ec78082bd82a8360b9f7956f2ffa8c376442dfdeb91d"
74
+ "falconPackageHash": "5025968d49cb54fee85788f23bacdd1fc4e38f6346a06dc2a2bd81c4"
75
75
  }