@wix/himalaya-cli 0.1341.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.
- package/dist/cli.mjs +267 -108
- 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, {
|
|
@@ -160935,6 +161050,105 @@ var init_asc_status = __esm({
|
|
|
160935
161050
|
}
|
|
160936
161051
|
});
|
|
160937
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
|
+
|
|
160938
161152
|
// ../../core/functions-secrets/src/vault.ts
|
|
160939
161153
|
import { createCipheriv, createDecipheriv as createDecipheriv2, randomBytes as randomBytes6 } from "node:crypto";
|
|
160940
161154
|
import { mkdir, readFile as readFile2, rename, writeFile as writeFile2 } from "node:fs/promises";
|
|
@@ -164210,10 +164424,10 @@ function familyFromFontShorthand(value) {
|
|
|
164210
164424
|
const WEIGHT = String.raw`bold|bolder|lighter|\d{3}`;
|
|
164211
164425
|
const STRETCH = String.raw`(ultra-|extra-|semi-)?(condensed|expanded)`;
|
|
164212
164426
|
const FUNC = String.raw`[a-z-]+\(.*\)${LINE}`;
|
|
164213
|
-
const
|
|
164427
|
+
const PREFIX2 = new RegExp(`^(${STYLE}|${VARIANT}|${WEIGHT}|${STRETCH}|${SIZE2}|${FUNC})$`, "i");
|
|
164214
164428
|
const parts = splitTopLevel(first, IS_SPACE).map((t2) => t2.trim()).filter(Boolean);
|
|
164215
164429
|
let i2 = 0;
|
|
164216
|
-
while (i2 < parts.length - 1 &&
|
|
164430
|
+
while (i2 < parts.length - 1 && PREFIX2.test(parts[i2])) i2 += 1;
|
|
164217
164431
|
const raw = parts.slice(i2).join(" ").trim();
|
|
164218
164432
|
const quoted = /^(['"])([\s\S]*)\1$/.exec(raw);
|
|
164219
164433
|
const family = (quoted ? quoted[2] : raw).replace(/['"]/g, "").trim();
|
|
@@ -173751,112 +173965,8 @@ ${res.error?.message ?? ""}`;
|
|
|
173751
173965
|
return { ok: false, session, message: `dpx auth status failed${firstLine ? `: ${firstLine}` : ""}` };
|
|
173752
173966
|
}
|
|
173753
173967
|
|
|
173754
|
-
// src/
|
|
173755
|
-
|
|
173756
|
-
import { createServer } from "node:http";
|
|
173757
|
-
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
173758
|
-
var DEFAULT_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
173759
|
-
function defaultOpenBrowser(url) {
|
|
173760
|
-
openUrl(url);
|
|
173761
|
-
}
|
|
173762
|
-
var CLOSE_PAGE = `<!doctype html><meta charset="utf-8"><title>himi login</title>
|
|
173763
|
-
<body style="font-family:system-ui,sans-serif;text-align:center;padding:56px;color:#111">
|
|
173764
|
-
<h1 style="font-size:20px;margin:0 0 8px">Signed in to himi</h1>
|
|
173765
|
-
<p style="color:#555;margin:0">You can close this tab and return to your terminal.</p>
|
|
173766
|
-
<script>setTimeout(function(){window.close()},1500)</script></body>`;
|
|
173767
|
-
async function browserLogin(opts) {
|
|
173768
|
-
const state = randomBytes2(16).toString("hex");
|
|
173769
|
-
const log = opts.log ?? (() => {
|
|
173770
|
-
});
|
|
173771
|
-
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
173772
|
-
return await new Promise((resolve61, reject) => {
|
|
173773
|
-
let settled = false;
|
|
173774
|
-
let loginUrl = "";
|
|
173775
|
-
const server = createServer();
|
|
173776
|
-
const finish2 = (fn2) => {
|
|
173777
|
-
if (settled) return;
|
|
173778
|
-
settled = true;
|
|
173779
|
-
clearTimeout(timer);
|
|
173780
|
-
server.close(() => fn2());
|
|
173781
|
-
server.closeAllConnections?.();
|
|
173782
|
-
};
|
|
173783
|
-
const timer = setTimeout(() => {
|
|
173784
|
-
finish2(() => reject(new Error(`login timed out after ${Math.round(timeoutMs / 1e3)}s waiting for the browser callback`)));
|
|
173785
|
-
}, timeoutMs);
|
|
173786
|
-
if (typeof timer.unref === "function") timer.unref();
|
|
173787
|
-
const MAX_CALLBACK_BODY = 1024 * 1024;
|
|
173788
|
-
const readCallback = (req, url, whenReady) => {
|
|
173789
|
-
if (req.method !== "POST") {
|
|
173790
|
-
whenReady(url.searchParams);
|
|
173791
|
-
return;
|
|
173792
|
-
}
|
|
173793
|
-
const chunks = [];
|
|
173794
|
-
let size = 0;
|
|
173795
|
-
let tooLarge = false;
|
|
173796
|
-
req.on("data", (c2) => {
|
|
173797
|
-
size += c2.length;
|
|
173798
|
-
if (size > MAX_CALLBACK_BODY) tooLarge = true;
|
|
173799
|
-
else chunks.push(c2);
|
|
173800
|
-
});
|
|
173801
|
-
req.on("end", () => whenReady(tooLarge ? null : new URLSearchParams(Buffer.concat(chunks).toString("utf8"))));
|
|
173802
|
-
req.on("error", () => whenReady(null));
|
|
173803
|
-
};
|
|
173804
|
-
server.on("request", (req, res) => {
|
|
173805
|
-
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
173806
|
-
if (url.pathname !== "/callback") {
|
|
173807
|
-
res.writeHead(404).end();
|
|
173808
|
-
return;
|
|
173809
|
-
}
|
|
173810
|
-
readCallback(req, url, (params) => {
|
|
173811
|
-
if (!params) {
|
|
173812
|
-
res.writeHead(413, { "content-type": "text/plain" }).end("callback body too large");
|
|
173813
|
-
finish2(() => reject(new Error("the login callback body was too large to be a valid token")));
|
|
173814
|
-
return;
|
|
173815
|
-
}
|
|
173816
|
-
const token = params.get("token");
|
|
173817
|
-
const gotState = params.get("state");
|
|
173818
|
-
const error = params.get("error");
|
|
173819
|
-
if (error) {
|
|
173820
|
-
const safe = error.replace(/[^\x20-\x7e]/g, "").slice(0, 200);
|
|
173821
|
-
res.writeHead(400, { "content-type": "text/plain" }).end(`login failed: ${safe}`);
|
|
173822
|
-
finish2(() => reject(new Error(`issuer reported: ${safe}`)));
|
|
173823
|
-
return;
|
|
173824
|
-
}
|
|
173825
|
-
if (gotState !== state) {
|
|
173826
|
-
res.writeHead(403, { "content-type": "text/plain" }).end("state mismatch \u2014 possible callback hijack");
|
|
173827
|
-
finish2(() => reject(new Error("state mismatch on the login callback \u2014 refusing the token")));
|
|
173828
|
-
return;
|
|
173829
|
-
}
|
|
173830
|
-
if (!token) {
|
|
173831
|
-
res.writeHead(400, { "content-type": "text/plain" }).end("missing token");
|
|
173832
|
-
finish2(() => reject(new Error("the login callback carried no token")));
|
|
173833
|
-
return;
|
|
173834
|
-
}
|
|
173835
|
-
res.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store", connection: "close" }).end(CLOSE_PAGE);
|
|
173836
|
-
finish2(() => resolve61({ token, loginUrl }));
|
|
173837
|
-
});
|
|
173838
|
-
});
|
|
173839
|
-
server.on("error", (err) => finish2(() => reject(err)));
|
|
173840
|
-
server.listen(opts.port ?? 0, "127.0.0.1", () => {
|
|
173841
|
-
const addr = server.address();
|
|
173842
|
-
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
173843
|
-
if (!port) {
|
|
173844
|
-
finish2(() => reject(new Error("could not bind a loopback port for the login callback")));
|
|
173845
|
-
return;
|
|
173846
|
-
}
|
|
173847
|
-
const base = opts.ownerIssue.replace(/\/$/, "");
|
|
173848
|
-
loginUrl = `${base}/v1/owner/cli-login?callback_port=${port}&state=${state}&response_mode=form_post`;
|
|
173849
|
-
if (opts.noOpen) {
|
|
173850
|
-
log(`Open this URL to sign in:
|
|
173851
|
-
${loginUrl}`);
|
|
173852
|
-
} else {
|
|
173853
|
-
log(`Opening your browser to sign in to Wix\u2026
|
|
173854
|
-
(if it doesn't open: ${loginUrl})`);
|
|
173855
|
-
(opts.openBrowser ?? defaultOpenBrowser)(loginUrl);
|
|
173856
|
-
}
|
|
173857
|
-
});
|
|
173858
|
-
});
|
|
173859
|
-
}
|
|
173968
|
+
// src/cli.ts
|
|
173969
|
+
init_browser_login();
|
|
173860
173970
|
|
|
173861
173971
|
// src/cms.ts
|
|
173862
173972
|
init_cms_schema();
|
|
@@ -175426,6 +175536,12 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
|
|
|
175426
175536
|
Auth: the \`himi login --browser\` token, or
|
|
175427
175537
|
--cookie/HIMI_WIX_COOKIE for a scripted call (a claim needs
|
|
175428
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.
|
|
175429
175545
|
himi whoami [--target <t>] print the stored control token's identity for a target \u2014
|
|
175430
175546
|
sub, credential kind, role, app scope, expiry. Purely LOCAL
|
|
175431
175547
|
(decodes the token; no round trip), so it works offline.
|
|
@@ -176003,6 +176119,7 @@ var COMMANDS2 = [
|
|
|
176003
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]" },
|
|
176004
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>]" },
|
|
176005
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>]" },
|
|
176006
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]" },
|
|
176007
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]" },
|
|
176008
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]" },
|
|
@@ -183345,6 +183462,48 @@ ${label2}`);
|
|
|
183345
183462
|
return 1;
|
|
183346
183463
|
}
|
|
183347
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
|
+
}
|
|
183348
183507
|
case "logout": {
|
|
183349
183508
|
const target = targetName();
|
|
183350
183509
|
let revoked;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/himalaya-cli",
|
|
3
|
-
"version": "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": "
|
|
74
|
+
"falconPackageHash": "5025968d49cb54fee85788f23bacdd1fc4e38f6346a06dc2a2bd81c4"
|
|
75
75
|
}
|