@wix/himalaya-cli 0.1115.0 → 0.1117.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 +162 -79
- package/guides/himi-mcp.md +7 -2
- package/guides/wix-business-context.md +4 -2
- package/package.json +2 -2
- package/template-icons/brandy.png +0 -0
- package/templates/brandy/README.md +60 -0
- package/templates/brandy/config.ts +76 -0
- package/templates/brandy/dev/index.ts +25 -0
- package/templates/brandy/himalaya.content.json +5 -0
- package/templates/brandy/himalaya.template.json +6 -0
- package/templates/brandy/icon.png +0 -0
- package/templates/brandy/platforms.json +1 -0
- package/templates/brandy/provisioning.json +12 -0
- package/templates/brandy/tokens.json +128 -0
package/dist/cli.mjs
CHANGED
|
@@ -13140,7 +13140,6 @@ function resolveIdiom(tokens, ctx = {}) {
|
|
|
13140
13140
|
var IDIOM_CONTEXTS, UNSAFE_KEYS;
|
|
13141
13141
|
var init_idiom = __esm({
|
|
13142
13142
|
"../../core/ts/src/idiom.ts"() {
|
|
13143
|
-
"use strict";
|
|
13144
13143
|
IDIOM_CONTEXTS = {
|
|
13145
13144
|
ios: { native: ["ios"] },
|
|
13146
13145
|
android: { native: ["material"] },
|
|
@@ -24719,7 +24718,6 @@ function shouldRevealError(args) {
|
|
|
24719
24718
|
var EMAIL_RE, URL_HOST_RE, INTEGER_RE, NUMBER_RE;
|
|
24720
24719
|
var init_validation = __esm({
|
|
24721
24720
|
"../../core/ts/src/validation.ts"() {
|
|
24722
|
-
"use strict";
|
|
24723
24721
|
EMAIL_RE = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;
|
|
24724
24722
|
URL_HOST_RE = /^[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*(:[0-9]+)?$/;
|
|
24725
24723
|
INTEGER_RE = /^[+-]?[0-9]+$/;
|
|
@@ -29120,7 +29118,6 @@ function validateWorkerSubtree(raw, slotId, policy, actionCatalog) {
|
|
|
29120
29118
|
var WORKER_SUBTREE_CEILINGS, ID_PART, ACTION_ID;
|
|
29121
29119
|
var init_worker_subtree = __esm({
|
|
29122
29120
|
"../../core/ts/src/worker-subtree.ts"() {
|
|
29123
|
-
"use strict";
|
|
29124
29121
|
init_worker_subtree_safe_types_generated();
|
|
29125
29122
|
WORKER_SUBTREE_CEILINGS = Object.freeze({
|
|
29126
29123
|
maxEncodedBytes: 65536,
|
|
@@ -36092,10 +36089,10 @@ var init_render_fonts = __esm({
|
|
|
36092
36089
|
|
|
36093
36090
|
// ../../core/dev-server/src/wix-cache-policy.ts
|
|
36094
36091
|
function clampRule(rule, denyPrefixes, maxTTL) {
|
|
36095
|
-
const
|
|
36092
|
+
const denied2 = denyPrefixes.some((p) => rule.pathPrefix.startsWith(p));
|
|
36096
36093
|
return {
|
|
36097
36094
|
...rule,
|
|
36098
|
-
cacheable: rule.cacheable && !
|
|
36095
|
+
cacheable: rule.cacheable && !denied2,
|
|
36099
36096
|
ttl: Math.min(rule.ttl, maxTTL)
|
|
36100
36097
|
};
|
|
36101
36098
|
}
|
|
@@ -53857,7 +53854,8 @@ var init_cli_bridge = __esm({
|
|
|
53857
53854
|
});
|
|
53858
53855
|
|
|
53859
53856
|
// src/mcp/file-tools.ts
|
|
53860
|
-
import { lstatSync as lstatSync12, mkdirSync as mkdirSync26, readdirSync as readdirSync27, readFileSync as readFileSync59, realpathSync as realpathSync7, rmSync as rmSync12, statSync as statSync13,
|
|
53857
|
+
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";
|
|
53858
|
+
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
53861
53859
|
import { dirname as dirname30, isAbsolute as isAbsolute5, join as join53, relative as relative11, resolve as resolve45, sep as sep14 } from "node:path";
|
|
53862
53860
|
import * as z from "zod";
|
|
53863
53861
|
function within(root, p) {
|
|
@@ -53874,37 +53872,99 @@ function realish(p) {
|
|
|
53874
53872
|
}
|
|
53875
53873
|
return join53(realpathSync7(cur), ...tail);
|
|
53876
53874
|
}
|
|
53875
|
+
function symlinkComponent(base, abs) {
|
|
53876
|
+
let cur = base;
|
|
53877
|
+
for (const part of relative11(base, abs).split(sep14).filter(Boolean)) {
|
|
53878
|
+
cur = join53(cur, part);
|
|
53879
|
+
try {
|
|
53880
|
+
if (lstatSync12(cur).isSymbolicLink()) return relative11(base, cur);
|
|
53881
|
+
} catch {
|
|
53882
|
+
return void 0;
|
|
53883
|
+
}
|
|
53884
|
+
}
|
|
53885
|
+
return void 0;
|
|
53886
|
+
}
|
|
53877
53887
|
async function confinedAppDir(args, deps) {
|
|
53878
53888
|
const root = resolve45(await deps.defaultDir());
|
|
53879
53889
|
const dir = typeof args.dir === "string" && args.dir ? resolve45(root, args.dir) : root;
|
|
53880
53890
|
if (!existsSync54(root)) return refuse4("no_workspace", `the workspace folder ${root} does not exist`);
|
|
53881
|
-
if (!within(realpathSync7(root), realish(dir))) return refuse4("outside_workspace", `"${String(args.dir)}" is outside the workspace folder`);
|
|
53891
|
+
if (!within(root, dir) || !within(realpathSync7(root), realish(dir))) return refuse4("outside_workspace", `"${String(args.dir)}" is outside the workspace folder`);
|
|
53892
|
+
const link = symlinkComponent(root, dir);
|
|
53893
|
+
if (link) return refuse4("outside_workspace", `"${link}" is a symlink \u2014 the app folder must be a real folder in the workspace`);
|
|
53894
|
+
const rel = relative11(root, dir);
|
|
53895
|
+
if (rel && (isDeniedSourcePath(`${rel}/x`) || isDeniedSourcePath(`${rel.toLowerCase()}/x`))) return refuse4("denied", `"${rel}" is a folder this tool does not touch`);
|
|
53896
|
+
if (!existsSync54(join53(dir, CONTENT_MARKER))) {
|
|
53897
|
+
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`);
|
|
53898
|
+
}
|
|
53882
53899
|
return dir;
|
|
53883
53900
|
}
|
|
53901
|
+
function onDiskSpelling(abs) {
|
|
53902
|
+
let cur = abs;
|
|
53903
|
+
const tail = [];
|
|
53904
|
+
while (!existsSync54(cur)) {
|
|
53905
|
+
const up = dirname30(cur);
|
|
53906
|
+
if (up === cur) break;
|
|
53907
|
+
tail.unshift(cur.slice(up.length + (up.endsWith(sep14) ? 0 : 1)));
|
|
53908
|
+
cur = up;
|
|
53909
|
+
}
|
|
53910
|
+
return join53(realpathSync7.native(cur), ...tail);
|
|
53911
|
+
}
|
|
53912
|
+
function denied(appDir3, abs) {
|
|
53913
|
+
const rel = relative11(appDir3, abs);
|
|
53914
|
+
if (isDeniedSourcePath(rel) || isDeniedSourcePath(rel.toLowerCase())) return true;
|
|
53915
|
+
try {
|
|
53916
|
+
return isDeniedSourcePath(relative11(realpathSync7.native(appDir3), onDiskSpelling(abs)));
|
|
53917
|
+
} catch {
|
|
53918
|
+
return true;
|
|
53919
|
+
}
|
|
53920
|
+
}
|
|
53884
53921
|
function confinedPath(appDir3, rel) {
|
|
53885
53922
|
if (typeof rel !== "string" || !rel.trim()) return { error: "a path is required" };
|
|
53886
53923
|
if (rel.includes("\0")) return { error: "a path may not contain NUL" };
|
|
53887
53924
|
if (isAbsolute5(rel)) return { error: `"${rel}" is absolute \u2014 give a path inside the app folder` };
|
|
53888
53925
|
const abs = resolve45(appDir3, rel);
|
|
53926
|
+
if (!within(appDir3, abs) || abs === appDir3) return { error: `"${rel}" is outside the app folder` };
|
|
53889
53927
|
const real = realish(abs);
|
|
53890
53928
|
const base = existsSync54(appDir3) ? realpathSync7(appDir3) : realish(appDir3);
|
|
53891
53929
|
if (!within(base, real) || real === base) return { error: `"${rel}" is outside the app folder` };
|
|
53892
|
-
const
|
|
53893
|
-
if (
|
|
53930
|
+
const link = symlinkComponent(appDir3, abs);
|
|
53931
|
+
if (link) return { error: `"${link}" is a symlink, which this tool does not follow` };
|
|
53932
|
+
if (denied(appDir3, abs)) return { error: `"${rel}" is on the deny-list (secrets, credentials, build output), which this tool does not touch` };
|
|
53894
53933
|
return abs;
|
|
53895
53934
|
}
|
|
53935
|
+
function writeNoFollow(appDir3, abs, content) {
|
|
53936
|
+
mkdirSync26(dirname30(abs), { recursive: true });
|
|
53937
|
+
const before = symlinkComponent(appDir3, abs);
|
|
53938
|
+
if (before) throw new Error(`"${before}" became a symlink`);
|
|
53939
|
+
const tmp = join53(dirname30(abs), `.himi-write-${process.pid}-${randomBytes4(6).toString("hex")}.tmp`);
|
|
53940
|
+
const fd = openSync3(tmp, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0), 420);
|
|
53941
|
+
try {
|
|
53942
|
+
writeSync(fd, content);
|
|
53943
|
+
} finally {
|
|
53944
|
+
closeSync4(fd);
|
|
53945
|
+
}
|
|
53946
|
+
const link = symlinkComponent(appDir3, abs);
|
|
53947
|
+
if (link || !within(realpathSync7(appDir3), realpathSync7(dirname30(abs)))) {
|
|
53948
|
+
rmSync12(tmp, { force: true });
|
|
53949
|
+
throw new Error(`"${link ?? relative11(appDir3, dirname30(abs))}" changed into a link while writing`);
|
|
53950
|
+
}
|
|
53951
|
+
renameSync6(tmp, abs);
|
|
53952
|
+
}
|
|
53896
53953
|
function listFiles(appDir3) {
|
|
53897
53954
|
const files = [];
|
|
53898
53955
|
const walk2 = (d) => {
|
|
53956
|
+
const parentRel = relative11(appDir3, d);
|
|
53899
53957
|
for (const e of readdirSync27(d, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
53900
|
-
if (RESERVED.has(e.name)) continue;
|
|
53901
53958
|
if (e.isSymbolicLink()) continue;
|
|
53902
53959
|
const p = join53(d, e.name);
|
|
53903
53960
|
if (e.isDirectory()) {
|
|
53961
|
+
if (isDeniedSourceDir(e.name, parentRel) || isDeniedSourceDir(e.name.toLowerCase(), parentRel)) continue;
|
|
53904
53962
|
if (!walk2(p)) return false;
|
|
53905
53963
|
} else if (e.isFile()) {
|
|
53964
|
+
const rel = relative11(appDir3, p);
|
|
53965
|
+
if (isDeniedSourcePath(rel) || isDeniedSourcePath(rel.toLowerCase())) continue;
|
|
53906
53966
|
if (files.length >= MAX_LISTED) return false;
|
|
53907
|
-
files.push({ path:
|
|
53967
|
+
files.push({ path: rel.split(sep14).join("/"), bytes: statSync13(p).size });
|
|
53908
53968
|
}
|
|
53909
53969
|
}
|
|
53910
53970
|
return true;
|
|
@@ -53934,7 +53994,7 @@ function fileTools() {
|
|
|
53934
53994
|
{
|
|
53935
53995
|
name: "himi_list_files",
|
|
53936
53996
|
title: "List app files",
|
|
53937
|
-
description: `Every file in an app folder with its size
|
|
53997
|
+
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).`,
|
|
53938
53998
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
53939
53999
|
inputSchema: z.object({ dir: DIR }),
|
|
53940
54000
|
handler: async (a, d) => {
|
|
@@ -53983,14 +54043,20 @@ function fileTools() {
|
|
|
53983
54043
|
else if (typeof f.content !== "string") refused2.push({ path: f.path, error: "content must be text" });
|
|
53984
54044
|
else if (Buffer.byteLength(f.content) > MAX_FILE_BYTES) refused2.push({ path: f.path, error: `over the ${MAX_FILE_BYTES}-byte limit` });
|
|
53985
54045
|
else if (existsSync54(p) && !lstatSync12(p).isFile()) refused2.push({ path: f.path, error: "not a regular file" });
|
|
54046
|
+
else if (planned.some((q) => q.abs === p)) refused2.push({ path: f.path, error: "the same file twice in one call" });
|
|
53986
54047
|
else planned.push({ abs: p, rel: String(f.path), content: f.content });
|
|
53987
54048
|
}
|
|
53988
54049
|
if (refused2.length) return { ...ok({ ok: false, error: "refused", written: [], refused: refused2 }), isError: true };
|
|
54050
|
+
const written = [];
|
|
53989
54051
|
for (const f of planned) {
|
|
53990
|
-
|
|
53991
|
-
|
|
54052
|
+
try {
|
|
54053
|
+
writeNoFollow(app, f.abs, f.content);
|
|
54054
|
+
written.push(f.rel);
|
|
54055
|
+
} catch (e) {
|
|
54056
|
+
return { ...ok({ ok: false, error: "write_failed", message: e.message, written }), isError: true };
|
|
54057
|
+
}
|
|
53992
54058
|
}
|
|
53993
|
-
return ok({ ok: true, dir: app, written
|
|
54059
|
+
return ok({ ok: true, dir: app, written });
|
|
53994
54060
|
}
|
|
53995
54061
|
},
|
|
53996
54062
|
{
|
|
@@ -54004,28 +54070,32 @@ function fileTools() {
|
|
|
54004
54070
|
if (typeof app !== "string") return app;
|
|
54005
54071
|
const paths = Array.isArray(a.paths) ? a.paths : [];
|
|
54006
54072
|
const planned = [];
|
|
54073
|
+
const deleted = [];
|
|
54007
54074
|
const refused2 = [];
|
|
54008
54075
|
for (const rel of paths) {
|
|
54009
54076
|
const p = confinedPath(app, rel);
|
|
54010
54077
|
if (typeof p !== "string") refused2.push({ path: rel, error: p.error });
|
|
54011
54078
|
else if (!existsSync54(p)) refused2.push({ path: rel, error: "no such file" });
|
|
54012
54079
|
else if (!lstatSync12(p).isFile()) refused2.push({ path: rel, error: "not a regular file" });
|
|
54013
|
-
else planned.
|
|
54080
|
+
else if (!planned.includes(p)) {
|
|
54081
|
+
planned.push(p);
|
|
54082
|
+
deleted.push(rel);
|
|
54083
|
+
}
|
|
54014
54084
|
}
|
|
54015
54085
|
if (refused2.length) return { ...ok({ ok: false, error: "refused", deleted: [], refused: refused2 }), isError: true };
|
|
54016
54086
|
for (const p of planned) rmSync12(p);
|
|
54017
|
-
return ok({ ok: true, dir: app, deleted
|
|
54087
|
+
return ok({ ok: true, dir: app, deleted });
|
|
54018
54088
|
}
|
|
54019
54089
|
}
|
|
54020
54090
|
];
|
|
54021
54091
|
}
|
|
54022
|
-
var MAX_FILE_BYTES, MAX_FILES_PER_WRITE, MAX_LISTED,
|
|
54092
|
+
var MAX_FILE_BYTES, MAX_FILES_PER_WRITE, MAX_LISTED, CONTENT_MARKER, ok, refuse4, DIR, WRITE_ANN;
|
|
54023
54093
|
var init_file_tools = __esm({
|
|
54024
54094
|
"src/mcp/file-tools.ts"() {
|
|
54095
|
+
init_source_deny();
|
|
54025
54096
|
MAX_FILE_BYTES = 1024 * 1024;
|
|
54026
54097
|
MAX_FILES_PER_WRITE = 100;
|
|
54027
54098
|
MAX_LISTED = 2e3;
|
|
54028
|
-
RESERVED = /* @__PURE__ */ new Set([".git", "node_modules", "dist"]);
|
|
54029
54099
|
CONTENT_MARKER = "himalaya.content.json";
|
|
54030
54100
|
ok = (o) => ({ content: [{ type: "text", text: JSON.stringify(o, null, 2) }] });
|
|
54031
54101
|
refuse4 = (error, message) => ({ content: [{ type: "text", text: JSON.stringify({ ok: false, error, message }, null, 2) }], isError: true });
|
|
@@ -54536,7 +54606,7 @@ __export(native_decode_exports, {
|
|
|
54536
54606
|
runNativeDecode: () => runNativeDecode
|
|
54537
54607
|
});
|
|
54538
54608
|
import { execFileSync as execFileSync5, spawnSync as spawnSync2 } from "node:child_process";
|
|
54539
|
-
import { existsSync as existsSync55, mkdtempSync as mkdtempSync2, readFileSync as readFileSync61, rmSync as rmSync13, writeFileSync as
|
|
54609
|
+
import { existsSync as existsSync55, mkdtempSync as mkdtempSync2, readFileSync as readFileSync61, rmSync as rmSync13, writeFileSync as writeFileSync21 } from "node:fs";
|
|
54540
54610
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
54541
54611
|
import { join as join54 } from "node:path";
|
|
54542
54612
|
import { fileURLToPath as fileURLToPath22 } from "node:url";
|
|
@@ -54670,7 +54740,7 @@ function runNativeDecode(options) {
|
|
|
54670
54740
|
if (typeof descriptor.descriptorJson !== "string") {
|
|
54671
54741
|
throw new Error(`native decode cannot judge ${descriptor.screenId}: the release builder did not return its exact baked JSON bytes`);
|
|
54672
54742
|
}
|
|
54673
|
-
|
|
54743
|
+
writeFileSync21(join54(descriptorDir, `${encodeURIComponent(descriptor.screenId)}.json`), descriptor.descriptorJson);
|
|
54674
54744
|
}
|
|
54675
54745
|
options.progress?.("building cached Swift oracle");
|
|
54676
54746
|
execFileSync5("swift", ["build", "--package-path", options.packageDir, "--product", "himi-decode-oracle"], {
|
|
@@ -54708,7 +54778,7 @@ function runAndroidNativeDecode(options) {
|
|
|
54708
54778
|
if (typeof descriptor.descriptorJson !== "string") {
|
|
54709
54779
|
throw new Error(`native decode cannot judge ${descriptor.screenId}: the release builder did not return its exact baked JSON bytes`);
|
|
54710
54780
|
}
|
|
54711
|
-
|
|
54781
|
+
writeFileSync21(join54(descriptorDir, `${encodeURIComponent(descriptor.screenId)}.json`), descriptor.descriptorJson);
|
|
54712
54782
|
}
|
|
54713
54783
|
options.progress?.(`running real Kotlin decoder over ${options.descriptors.length} screen${options.descriptors.length === 1 ? "" : "s"}`);
|
|
54714
54784
|
execFileSync5(options.gradlew, [
|
|
@@ -54792,7 +54862,7 @@ __export(browser_authoring_exports, {
|
|
|
54792
54862
|
sdkSubpathReader: () => sdkSubpathReader
|
|
54793
54863
|
});
|
|
54794
54864
|
import { createServer as createServer5 } from "node:http";
|
|
54795
|
-
import { createHash as createHash29, randomBytes as
|
|
54865
|
+
import { createHash as createHash29, randomBytes as randomBytes5 } from "node:crypto";
|
|
54796
54866
|
import { existsSync as existsSync56, readdirSync as readdirSync28, readFileSync as readFileSync62, realpathSync as realpathSync8, statSync as statSync14 } from "node:fs";
|
|
54797
54867
|
import { join as join55, posix as posix2, relative as relative12, sep as sep15 } from "node:path";
|
|
54798
54868
|
function humanMs(ms) {
|
|
@@ -54890,7 +54960,7 @@ function sdkSubpathReader(resolve54) {
|
|
|
54890
54960
|
function encodeJob(job) {
|
|
54891
54961
|
return Buffer.from(JSON.stringify(job), "utf8").toString("base64");
|
|
54892
54962
|
}
|
|
54893
|
-
function mintToken2(payload, nonce =
|
|
54963
|
+
function mintToken2(payload, nonce = randomBytes5(16).toString("hex")) {
|
|
54894
54964
|
const digest2 = createHash29("sha256").update(payload, "utf8").digest("base64url");
|
|
54895
54965
|
return `${nonce}.${digest2}`;
|
|
54896
54966
|
}
|
|
@@ -55053,7 +55123,7 @@ __export(coverage_exports, {
|
|
|
55053
55123
|
suppressedNativeKeys: () => suppressedNativeKeys,
|
|
55054
55124
|
trendAndStore: () => trendAndStore
|
|
55055
55125
|
});
|
|
55056
|
-
import { existsSync as existsSync57, mkdirSync as mkdirSync27, readFileSync as readFileSync63, writeFileSync as
|
|
55126
|
+
import { existsSync as existsSync57, mkdirSync as mkdirSync27, readFileSync as readFileSync63, writeFileSync as writeFileSync22 } from "node:fs";
|
|
55057
55127
|
import { dirname as dirname31, join as join56 } from "node:path";
|
|
55058
55128
|
function suppressedNativeKeys(oracle) {
|
|
55059
55129
|
return [...oracle.ios ? [] : ["native-decode|ios"], ...oracle.android ? [] : ["native-decode|android"]];
|
|
@@ -55247,7 +55317,7 @@ function trendAndStore(dir, entries, suppressed) {
|
|
|
55247
55317
|
const stored = [.../* @__PURE__ */ new Set([...current, ...(previous ?? []).filter((key2) => (suppressed ?? []).includes(key2))])];
|
|
55248
55318
|
try {
|
|
55249
55319
|
mkdirSync27(dirname31(p), { recursive: true });
|
|
55250
|
-
|
|
55320
|
+
writeFileSync22(p, JSON.stringify({ keys: stored, ts: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
|
|
55251
55321
|
} catch {
|
|
55252
55322
|
return null;
|
|
55253
55323
|
}
|
|
@@ -55292,7 +55362,7 @@ function diffAndStoreObservations(dir, current) {
|
|
|
55292
55362
|
}
|
|
55293
55363
|
try {
|
|
55294
55364
|
mkdirSync27(dirname31(p), { recursive: true });
|
|
55295
|
-
|
|
55365
|
+
writeFileSync22(p, JSON.stringify({ screens: current, ts: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
|
|
55296
55366
|
} catch {
|
|
55297
55367
|
return null;
|
|
55298
55368
|
}
|
|
@@ -55903,8 +55973,8 @@ ${releaseId}`;
|
|
|
55903
55973
|
const pin = resolvePin(request, url);
|
|
55904
55974
|
let bytes2 = null;
|
|
55905
55975
|
if (pin) {
|
|
55906
|
-
const
|
|
55907
|
-
if (
|
|
55976
|
+
const denied2 = await authorizePinnedRead(request, url, app, pin);
|
|
55977
|
+
if (denied2) return { ok: false, response: denied2 };
|
|
55908
55978
|
const m = await pinnedManifest(app, pin);
|
|
55909
55979
|
if (!m) return { ok: false, response: json4(404, { error: "release_not_found", app, releaseId: pin }) };
|
|
55910
55980
|
bytes2 = m.appConfigHash ? await pinnedBody(app, m.appConfigHash, () => store.getAppConfig(app)) : null;
|
|
@@ -55927,7 +55997,7 @@ ${releaseId}`;
|
|
|
55927
55997
|
return new Response(body, { status: 200, headers: { "content-type": JSON_CT2, etag, "cache-control": cache, ...vary } });
|
|
55928
55998
|
}
|
|
55929
55999
|
async function authorizePinnedRead(request, url, app, releaseId, screenId) {
|
|
55930
|
-
const
|
|
56000
|
+
const denied2 = await checkPinnedRead({
|
|
55931
56001
|
secret: opts.previewTokenSecret,
|
|
55932
56002
|
token: resolvePreviewToken(request, url),
|
|
55933
56003
|
app,
|
|
@@ -55937,7 +56007,7 @@ ${releaseId}`;
|
|
|
55937
56007
|
authorizePreview: opts.authorizePreview,
|
|
55938
56008
|
authorizeFollow: opts.authorizeFollow
|
|
55939
56009
|
});
|
|
55940
|
-
return
|
|
56010
|
+
return denied2 && json4(denied2.status, denied2.body);
|
|
55941
56011
|
}
|
|
55942
56012
|
async function honourRingPin(request) {
|
|
55943
56013
|
const kept = { request, dropped: false };
|
|
@@ -56388,8 +56458,8 @@ ${releaseId}`;
|
|
|
56388
56458
|
const app = cfgMatch?.[1] ?? resolveApp(request, url);
|
|
56389
56459
|
const pin = resolvePin(request, url);
|
|
56390
56460
|
if (app && pin) {
|
|
56391
|
-
const
|
|
56392
|
-
if (
|
|
56461
|
+
const denied2 = await authorizePinnedRead(request, url, app, pin);
|
|
56462
|
+
if (denied2) return denied2;
|
|
56393
56463
|
}
|
|
56394
56464
|
if (path2 === "/v1/template-source") {
|
|
56395
56465
|
if (!app) return json4(400, { error: "missing_app" });
|
|
@@ -56472,8 +56542,8 @@ ${releaseId}`;
|
|
|
56472
56542
|
if (!app) return json4(400, { error: "missing_app" });
|
|
56473
56543
|
let m;
|
|
56474
56544
|
if (pin) {
|
|
56475
|
-
const
|
|
56476
|
-
if (
|
|
56545
|
+
const denied2 = await authorizePinnedRead(request, url, app, pin);
|
|
56546
|
+
if (denied2) return denied2;
|
|
56477
56547
|
m = await pinnedManifest(app, pin);
|
|
56478
56548
|
if (!m) return json4(404, { error: "release_not_found", app, releaseId: pin });
|
|
56479
56549
|
} else {
|
|
@@ -56659,8 +56729,8 @@ ${releaseId}`;
|
|
|
56659
56729
|
const screenId = screenMatch[1];
|
|
56660
56730
|
if (!app || !isSafeSegment(screenId)) return json4(404, { error: "screen_not_found", screenId });
|
|
56661
56731
|
if (pin) {
|
|
56662
|
-
const
|
|
56663
|
-
if (
|
|
56732
|
+
const denied2 = await authorizePinnedRead(request, url, app, pin, screenId);
|
|
56733
|
+
if (denied2) return denied2;
|
|
56664
56734
|
const m2 = await pinnedManifest(app, pin);
|
|
56665
56735
|
if (!m2) return json4(404, { error: "release_not_found", app, releaseId: pin });
|
|
56666
56736
|
const meta2 = m2.screens[screenId];
|
|
@@ -57453,7 +57523,7 @@ __export(run_watch_exports, {
|
|
|
57453
57523
|
watchedContentDir: () => watchedContentDir,
|
|
57454
57524
|
writeRunRecord: () => writeRunRecord
|
|
57455
57525
|
});
|
|
57456
|
-
import { closeSync as
|
|
57526
|
+
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";
|
|
57457
57527
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
57458
57528
|
import { extname as extname4, join as join59, resolve as resolve47, sep as sep16 } from "node:path";
|
|
57459
57529
|
function diffReleases(prev, next) {
|
|
@@ -57588,9 +57658,9 @@ function writeRunRecord(dir, record2) {
|
|
|
57588
57658
|
if (!root) return false;
|
|
57589
57659
|
const tmp = join59(root, `.run.json.${process.pid}.${randomUUID4()}.tmp`);
|
|
57590
57660
|
try {
|
|
57591
|
-
|
|
57661
|
+
writeFileSync23(tmp, `${JSON.stringify(record2, null, 2)}
|
|
57592
57662
|
`, { flag: "wx" });
|
|
57593
|
-
|
|
57663
|
+
renameSync7(tmp, join59(root, "run.json"));
|
|
57594
57664
|
} catch (e) {
|
|
57595
57665
|
rmSync15(tmp, { force: true });
|
|
57596
57666
|
throw e;
|
|
@@ -57647,12 +57717,12 @@ function readConfined(root, rel) {
|
|
|
57647
57717
|
}
|
|
57648
57718
|
const canonicalRoot = realpathSync9(root);
|
|
57649
57719
|
if (!realpathSync9(at2).startsWith(canonicalRoot + sep16)) return null;
|
|
57650
|
-
const fd =
|
|
57720
|
+
const fd = openSync4(at2, constants2.O_RDONLY | (constants2.O_NOFOLLOW ?? 0));
|
|
57651
57721
|
try {
|
|
57652
57722
|
if (!fstatSync(fd).isFile()) return null;
|
|
57653
57723
|
return readFileSync67(fd);
|
|
57654
57724
|
} finally {
|
|
57655
|
-
|
|
57725
|
+
closeSync5(fd);
|
|
57656
57726
|
}
|
|
57657
57727
|
} catch {
|
|
57658
57728
|
return null;
|
|
@@ -58874,7 +58944,7 @@ __export(version_exports, {
|
|
|
58874
58944
|
setAndroidVersion: () => setAndroidVersion,
|
|
58875
58945
|
setIosVersion: () => setIosVersion
|
|
58876
58946
|
});
|
|
58877
|
-
import { readFileSync as readFileSync72, writeFileSync as
|
|
58947
|
+
import { readFileSync as readFileSync72, writeFileSync as writeFileSync24 } from "node:fs";
|
|
58878
58948
|
function readIosVersion(projectYml) {
|
|
58879
58949
|
const marketing = matchScalar(projectYml, "CFBundleShortVersionString");
|
|
58880
58950
|
const build2 = matchScalar(projectYml, "CFBundleVersion");
|
|
@@ -58939,8 +59009,8 @@ function bumpVersion(app, opts) {
|
|
|
58939
59009
|
marketing: opts.marketing ?? current.marketing,
|
|
58940
59010
|
build: opts.build === "increment" || opts.build === void 0 ? current.build + (opts.build === "increment" ? 1 : 0) : opts.build
|
|
58941
59011
|
};
|
|
58942
|
-
|
|
58943
|
-
|
|
59012
|
+
writeFileSync24(iosPath, setIosVersion(iosYml, next));
|
|
59013
|
+
writeFileSync24(gradlePath, setAndroidVersion(gradle, next));
|
|
58944
59014
|
return next;
|
|
58945
59015
|
}
|
|
58946
59016
|
var init_version = __esm({
|
|
@@ -59083,7 +59153,7 @@ var init_asc_status = __esm({
|
|
|
59083
59153
|
});
|
|
59084
59154
|
|
|
59085
59155
|
// ../../core/functions-secrets/src/vault.ts
|
|
59086
|
-
import { createCipheriv, createDecipheriv, randomBytes as
|
|
59156
|
+
import { createCipheriv, createDecipheriv, randomBytes as randomBytes6 } from "node:crypto";
|
|
59087
59157
|
import { mkdir, readFile as readFile2, rename, writeFile as writeFile2 } from "node:fs/promises";
|
|
59088
59158
|
import { dirname as dirname34 } from "node:path";
|
|
59089
59159
|
function createJsonSecretStore(filePath) {
|
|
@@ -59141,7 +59211,7 @@ function createSecretVault(options) {
|
|
|
59141
59211
|
environment: input.environment,
|
|
59142
59212
|
name: input.name,
|
|
59143
59213
|
version: input.version,
|
|
59144
|
-
iv:
|
|
59214
|
+
iv: randomBytes6(12).toString("base64url"),
|
|
59145
59215
|
ciphertext: "",
|
|
59146
59216
|
authTag: ""
|
|
59147
59217
|
};
|
|
@@ -59503,7 +59573,7 @@ __export(notify_exports, {
|
|
|
59503
59573
|
runNotify: () => runNotify,
|
|
59504
59574
|
saveAdminSession: () => saveAdminSession
|
|
59505
59575
|
});
|
|
59506
|
-
import { chmodSync as chmodSync3, mkdirSync as mkdirSync30, readFileSync as readFileSync76, writeFileSync as
|
|
59576
|
+
import { chmodSync as chmodSync3, mkdirSync as mkdirSync30, readFileSync as readFileSync76, writeFileSync as writeFileSync25 } from "node:fs";
|
|
59507
59577
|
import { homedir as homedir6 } from "node:os";
|
|
59508
59578
|
import { dirname as dirname35, join as join62 } from "node:path";
|
|
59509
59579
|
function defaultSessionsPath() {
|
|
@@ -59520,7 +59590,7 @@ function saveAdminSession(adminUrl, session, path2 = defaultSessionsPath()) {
|
|
|
59520
59590
|
const file = loadSessionsFile(path2);
|
|
59521
59591
|
file.sessions = { ...file.sessions, [adminUrl]: session };
|
|
59522
59592
|
mkdirSync30(dirname35(path2), { recursive: true, mode: 448 });
|
|
59523
|
-
|
|
59593
|
+
writeFileSync25(path2, JSON.stringify(file, null, 2) + "\n", { mode: 384 });
|
|
59524
59594
|
try {
|
|
59525
59595
|
chmodSync3(dirname35(path2), 448);
|
|
59526
59596
|
chmodSync3(path2, 384);
|
|
@@ -63279,7 +63349,7 @@ var init_brand_font = __esm({
|
|
|
63279
63349
|
});
|
|
63280
63350
|
|
|
63281
63351
|
// src/site-analyze/write-artifacts.ts
|
|
63282
|
-
import { mkdirSync as mkdirSync31, writeFileSync as
|
|
63352
|
+
import { mkdirSync as mkdirSync31, writeFileSync as writeFileSync26 } from "node:fs";
|
|
63283
63353
|
import { join as join63 } from "node:path";
|
|
63284
63354
|
function attachVeloSources(book, sources, put) {
|
|
63285
63355
|
const byPath = new Map((sources ?? []).map((s) => [s.path, s.text]));
|
|
@@ -63441,7 +63511,7 @@ async function writeBusinessBook(result, opts) {
|
|
|
63441
63511
|
const put = (rel, body) => {
|
|
63442
63512
|
const p = join63(out2, rel);
|
|
63443
63513
|
mkdirSync31(join63(p, ".."), { recursive: true });
|
|
63444
|
-
|
|
63514
|
+
writeFileSync26(p, body);
|
|
63445
63515
|
files.push(rel);
|
|
63446
63516
|
};
|
|
63447
63517
|
const book = result.book;
|
|
@@ -63534,7 +63604,7 @@ var init_customer_build = __esm({
|
|
|
63534
63604
|
});
|
|
63535
63605
|
|
|
63536
63606
|
// src/site-analyze/apply.ts
|
|
63537
|
-
import { existsSync as existsSync65, readFileSync as readFileSync77, writeFileSync as
|
|
63607
|
+
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";
|
|
63538
63608
|
import { dirname as dirname36, isAbsolute as isAbsolute7, join as join64, resolve as resolve51, sep as sep17 } from "node:path";
|
|
63539
63609
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
63540
63610
|
function unpairedRedirectNote(overlay) {
|
|
@@ -63686,10 +63756,10 @@ function buildMergedContract(options) {
|
|
|
63686
63756
|
const body = `${JSON.stringify(merged, null, 2)}
|
|
63687
63757
|
`;
|
|
63688
63758
|
const path2 = join64(packageDir, "provisioning.contract.json");
|
|
63689
|
-
|
|
63759
|
+
writeFileSync27(path2, body);
|
|
63690
63760
|
const localPath = join64(appDir3, LOCAL_CONTRACT_REL);
|
|
63691
63761
|
mkdirSync32(dirname36(localPath), { recursive: true });
|
|
63692
|
-
|
|
63762
|
+
writeFileSync27(localPath, body);
|
|
63693
63763
|
staged.push(localPath);
|
|
63694
63764
|
return { path: path2, staged };
|
|
63695
63765
|
}
|
|
@@ -63788,10 +63858,10 @@ async function applyOverlays(input) {
|
|
|
63788
63858
|
const cur = JSON.parse(readFileSync77(pkgProv, "utf8"));
|
|
63789
63859
|
const merged = deepMerge4(cur, overlay);
|
|
63790
63860
|
packageFaq = merged.faq;
|
|
63791
|
-
|
|
63861
|
+
writeFileSync27(pkgProv, JSON.stringify(merged, null, 2) + "\n");
|
|
63792
63862
|
wrote.push(pkgProv);
|
|
63793
63863
|
} else {
|
|
63794
|
-
|
|
63864
|
+
writeFileSync27(pkgProv, JSON.stringify(overlay, null, 2) + "\n");
|
|
63795
63865
|
wrote.push(pkgProv);
|
|
63796
63866
|
}
|
|
63797
63867
|
const pkgTokens = join64(cwd, "tokens.json");
|
|
@@ -63817,7 +63887,7 @@ async function applyOverlays(input) {
|
|
|
63817
63887
|
if (primary && glow && typeof glow.color === "string") glow.color = primary;
|
|
63818
63888
|
stripBrandFromIdiom(merged);
|
|
63819
63889
|
}
|
|
63820
|
-
|
|
63890
|
+
writeFileSync27(pkgTokens, JSON.stringify(merged, null, 2) + "\n");
|
|
63821
63891
|
wrote.push(pkgTokens);
|
|
63822
63892
|
}
|
|
63823
63893
|
const homeAction = homeRefreshActionId(shell);
|
|
@@ -63847,7 +63917,7 @@ async function applyOverlays(input) {
|
|
|
63847
63917
|
}
|
|
63848
63918
|
}
|
|
63849
63919
|
if (next !== prev) {
|
|
63850
|
-
|
|
63920
|
+
writeFileSync27(homeSrc, next);
|
|
63851
63921
|
wrote.push(homeSrc);
|
|
63852
63922
|
}
|
|
63853
63923
|
if (patched.unparsed) {
|
|
@@ -68216,6 +68286,7 @@ __export(cli_exports, {
|
|
|
68216
68286
|
APPLY_NEEDS_CLIENT: () => APPLY_NEEDS_CLIENT,
|
|
68217
68287
|
REDIRECT_NEEDS_CLIENT: () => REDIRECT_NEEDS_CLIENT,
|
|
68218
68288
|
SITE_FUNCTION_USAGE: () => SITE_FUNCTION_USAGE,
|
|
68289
|
+
applyNeedsClient: () => applyNeedsClient,
|
|
68219
68290
|
assetFetchFor: () => assetFetchFor,
|
|
68220
68291
|
noteWithWarnings: () => noteWithWarnings,
|
|
68221
68292
|
ownerSiteFor: () => ownerSiteFor,
|
|
@@ -68224,6 +68295,10 @@ __export(cli_exports, {
|
|
|
68224
68295
|
});
|
|
68225
68296
|
import { readFileSync as readFileSync78 } from "node:fs";
|
|
68226
68297
|
import { resolve as resolve52 } from "node:path";
|
|
68298
|
+
function applyNeedsClient(shell) {
|
|
68299
|
+
if (shell === void 0 || !SHELL_NAMES2.includes(shell)) return true;
|
|
68300
|
+
return !isSiteShell(shell);
|
|
68301
|
+
}
|
|
68227
68302
|
function assetFetchFor(_opts) {
|
|
68228
68303
|
return pinnedFetch2;
|
|
68229
68304
|
}
|
|
@@ -68236,7 +68311,7 @@ function ownerSiteFor(opts, env = process.env) {
|
|
|
68236
68311
|
async function runSiteAnalyze(opts) {
|
|
68237
68312
|
if (opts.shell !== void 0) assertKnownShell(opts.shell);
|
|
68238
68313
|
if (!opts.clientId?.trim()) {
|
|
68239
|
-
if (opts.apply) throw Object.assign(new Error(APPLY_NEEDS_CLIENT), { exit: 2 });
|
|
68314
|
+
if (opts.apply && applyNeedsClient(opts.shell)) throw Object.assign(new Error(APPLY_NEEDS_CLIENT), { exit: 2 });
|
|
68240
68315
|
if (opts.redirectUri?.trim()) throw Object.assign(new Error(REDIRECT_NEEDS_CLIENT), { exit: 2 });
|
|
68241
68316
|
}
|
|
68242
68317
|
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
@@ -68271,7 +68346,10 @@ async function runSiteAnalyze(opts) {
|
|
|
68271
68346
|
try {
|
|
68272
68347
|
const derived = deriveSiteShell({ siteUrl: result.book.input.siteUrl, look: result.book.look });
|
|
68273
68348
|
result.provisioningOverlay.site = derived.site;
|
|
68274
|
-
|
|
68349
|
+
const warnings2 = (result.warnings ?? []).map(
|
|
68350
|
+
(w) => !opts.clientId?.trim() && w.startsWith("no --client-id:") ? `no --client-id: fine for --shell ${opts.shell} \u2014 a site shell binds no client, so --apply works without one. Only the vertical probes were skipped, so the catalog has no counts.` : w
|
|
68351
|
+
);
|
|
68352
|
+
result.warnings = [...warnings2, ...derived.notes];
|
|
68275
68353
|
} catch (e) {
|
|
68276
68354
|
result.warnings = [
|
|
68277
68355
|
...result.warnings ?? [],
|
|
@@ -68595,7 +68673,7 @@ __export(site_create_exports, {
|
|
|
68595
68673
|
runSiteList: () => runSiteList,
|
|
68596
68674
|
writeProvisioningBinding: () => writeProvisioningBinding
|
|
68597
68675
|
});
|
|
68598
|
-
import { existsSync as existsSync66, readFileSync as readFileSync79, writeFileSync as
|
|
68676
|
+
import { existsSync as existsSync66, readFileSync as readFileSync79, writeFileSync as writeFileSync28 } from "node:fs";
|
|
68599
68677
|
import { resolve as resolvePath2 } from "node:path";
|
|
68600
68678
|
function oauthClientName(projectName) {
|
|
68601
68679
|
return `${projectName} client`;
|
|
@@ -68744,7 +68822,7 @@ async function runSiteCreate(opts) {
|
|
|
68744
68822
|
if (!wantOauthApp) return { ...base, nextSteps: nextSteps(metaSiteId) };
|
|
68745
68823
|
return { ...base, clientId, redirectUri, nextSteps: nextSteps(metaSiteId, clientId) };
|
|
68746
68824
|
}
|
|
68747
|
-
function writeProvisioningBinding(target, binding2, fs = { existsSync: existsSync66, readFileSync: readFileSync79, writeFileSync:
|
|
68825
|
+
function writeProvisioningBinding(target, binding2, fs = { existsSync: existsSync66, readFileSync: readFileSync79, writeFileSync: writeFileSync28 }) {
|
|
68748
68826
|
const msid = binding2.siteId;
|
|
68749
68827
|
const path2 = resolvePath2(target);
|
|
68750
68828
|
const committed = committedShellProvisioning(path2);
|
|
@@ -69417,7 +69495,7 @@ var init_context = __esm({
|
|
|
69417
69495
|
});
|
|
69418
69496
|
|
|
69419
69497
|
// src/cli.ts
|
|
69420
|
-
import { mkdtempSync as mkdtempSync4, readFileSync as readFileSync80, mkdirSync as mkdirSync33, writeFileSync as
|
|
69498
|
+
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";
|
|
69421
69499
|
import { execFile, execFileSync as execFileSync7, spawnSync as spawnSync5, spawn as spawn7 } from "node:child_process";
|
|
69422
69500
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
69423
69501
|
import { createHash as createHash31, randomUUID as randomUUID7 } from "node:crypto";
|
|
@@ -71867,9 +71945,12 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
|
|
|
71867
71945
|
file is not the plane's per-release archive.
|
|
71868
71946
|
himi share --file <package.himi> [--expires-at <ISO>] | revoke --app <app> <shareId>
|
|
71869
71947
|
publish a content-addressed package link for a released app
|
|
71870
|
-
himi mcp [plugin --out <dir>]
|
|
71948
|
+
himi mcp [plugin --out <dir>] [--with-files]
|
|
71949
|
+
the himi tools as a local MCP server over stdio, for coding
|
|
71871
71950
|
agents (Claude Code, Cursor, Codex, VS Code). \`himi init\`
|
|
71872
|
-
configures each; see \`himi docs himi-mcp\`.
|
|
71951
|
+
configures each; see \`himi docs himi-mcp\`. --with-files
|
|
71952
|
+
adds file tools confined to the workspace, for a host that
|
|
71953
|
+
cannot edit files (Claude Desktop chat).
|
|
71873
71954
|
himi run [<package.himi|dir|url>] [--browser] [--watch [--idle-exit <min>]] [--lan] [--port <n>] [--chrome preview|app] [--screen <id>] [--surface <id>] [--no-open]
|
|
71874
71955
|
serve a package on localhost + open it in a browser. No serve
|
|
71875
71956
|
plane, no dev-server. With no argument it packs the current
|
|
@@ -72116,7 +72197,8 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
|
|
|
72116
72197
|
a brandable shell's content package. --url alone is the
|
|
72117
72198
|
visitor-only pass (published pages, look, copy); --client-id
|
|
72118
72199
|
adds the vertical catalog counts and the app binding, and
|
|
72119
|
-
--apply requires it
|
|
72200
|
+
--apply requires it (not for a site shell, which binds no
|
|
72201
|
+
client). --shell takes the same
|
|
72120
72202
|
names 'himi dist --shell' does; default branded-lite.
|
|
72121
72203
|
A SITE shell (brandy) also derives the site's page map
|
|
72122
72204
|
(tabs/drawer from its menu) into the overlay's \`site\`
|
|
@@ -72231,7 +72313,7 @@ var COMMANDS = [
|
|
|
72231
72313
|
{ 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>]" },
|
|
72232
72314
|
{ name: "logout", summary: "remove the stored control token for a target", required: "[--target <t>]" },
|
|
72233
72315
|
{ 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]" },
|
|
72234
|
-
{ 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; `--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]" },
|
|
72316
|
+
{ 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]" },
|
|
72235
72317
|
{ 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>]" },
|
|
72236
72318
|
{ 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]" },
|
|
72237
72319
|
{ name: "push", summary: "lint + build + publish a release (default --draft: servable via ?release= but NOT live). REFUSES to publish on any error unless --force; --strict promotes warnings. Prints preview + agent-context links. A successful draft push also rebuilds the making-of (.himi/making-of.html, local and offline) and names it as `reel` \u2014 the file to hand the owner beside the preview link. Its postcard carries the home shot only when that shot is newer than the last source edit; otherwise the postcard says why and names `himi render <home> --png`, and the envelope's `postcardPicture: {skipped, render}` leads next[] with that render (the push never renders it itself). The published slug is NAMESPACED per owner \u2014 `<ownerPrefix>.<name>`, composed offline from the control token's ownerPrefix claim, so the CLI and the server derive the identical string and himalaya.content.json is NOT rewritten (a legacy token with no ownerPrefix publishes the declared name unchanged). An app claimed BEFORE namespacing is stored under its FLAT slug, so the claim route answers `alreadyYours` naming that slug and the push adopts it \u2014 reported as `adoptedSlug`, since it is the one case where the published `app` differs from what the declared name composes to. `--no-claim` skips the route that knows this, so a pre-namespacing app pushed with it still fails 403. --visibility public|unlisted decides whether /v1/apps + the Himi Player picker LIST the app (also declarable as `visibility` in himalaya.content.json; omitted, a brand-new app defaults to unlisted \u2014 share by link \u2014 and an existing setting is kept). --account-id names which account owns the app when your Wix session resolves to several (the auto-claim of a brand-new slug cannot guess \u2014 without it a multi-account caller is told to name one). An unforced push of a release that carries SCREENS also refuses when `.himi/ux-audit.json` holds no mobile-UX drive recorded against that release id, or holds one that FAILED (#2955) \u2014 a published draft IS the owner-private preview a customer is handed; `himi ux record` clears it and `--force` overrides it, which the success envelope then says out loud. `--only <selector>` publishes ONLY the named items as a release derived from the app's live release (or `--from-release <id>`), running the identical build + lint gate and simply publishing less; it requires an explicit `--draft` or `--live` because a partial publish has no safe default, never claims a slug, and carries neither attestation nor a source archive (both named in the output). Selectors are comma-separated in ONE `--only`: `changed` (every item whose bytes differ from the base), `screen:<id|glob>`, `bundle:<name>`, `tokens`, `app-config` \u2014 a selected screen drags its logic bundle and a selected bundle drags the screens that reference it, or the bytes land under a key nothing reads.", required: "[--content <dir>] [--draft] [--live] [--force] [--strict] [--label] [--notes] [--visibility public|unlisted] [--no-claim] [--account-id <id>] [--only <selector>] [--from-release <id>] [--accept-regressions] [--accept-partial] [--app <slug>] (--control-token)" },
|
|
@@ -72469,7 +72551,7 @@ function readUpdateCache(path2) {
|
|
|
72469
72551
|
function writeUpdateCache(path2, cache) {
|
|
72470
72552
|
try {
|
|
72471
72553
|
mkdirSync33(dirname37(path2), { recursive: true, mode: 448 });
|
|
72472
|
-
|
|
72554
|
+
writeFileSync29(path2, JSON.stringify(cache, null, 2) + "\n", { mode: 384 });
|
|
72473
72555
|
} catch {
|
|
72474
72556
|
}
|
|
72475
72557
|
}
|
|
@@ -72500,7 +72582,7 @@ function globalPrefixWritable() {
|
|
|
72500
72582
|
const root = globalNodeModulesRoot();
|
|
72501
72583
|
if (!root) return false;
|
|
72502
72584
|
try {
|
|
72503
|
-
accessSync(root,
|
|
72585
|
+
accessSync(root, constants3.W_OK);
|
|
72504
72586
|
return true;
|
|
72505
72587
|
} catch {
|
|
72506
72588
|
return false;
|
|
@@ -72611,7 +72693,7 @@ function acquireUpdateLock(cachePath, now, hooks = {}) {
|
|
|
72611
72693
|
};
|
|
72612
72694
|
const tryCreate = (path2) => {
|
|
72613
72695
|
try {
|
|
72614
|
-
|
|
72696
|
+
writeFileSync29(path2, mine, { flag: "wx", mode: 384 });
|
|
72615
72697
|
return true;
|
|
72616
72698
|
} catch {
|
|
72617
72699
|
return false;
|
|
@@ -73199,7 +73281,7 @@ function executableOnPath(name, env = process.env) {
|
|
|
73199
73281
|
const candidate = join65(dir, `${name}${suffix}`);
|
|
73200
73282
|
try {
|
|
73201
73283
|
if (!statSync19(candidate).isFile()) continue;
|
|
73202
|
-
if (process.platform !== "win32") accessSync(candidate,
|
|
73284
|
+
if (process.platform !== "win32") accessSync(candidate, constants3.X_OK);
|
|
73203
73285
|
return true;
|
|
73204
73286
|
} catch {
|
|
73205
73287
|
}
|
|
@@ -73326,7 +73408,7 @@ function readConfigSource(dir) {
|
|
|
73326
73408
|
}
|
|
73327
73409
|
function writePngFile(path2, base64) {
|
|
73328
73410
|
mkdirSync33(dirname37(path2), { recursive: true });
|
|
73329
|
-
|
|
73411
|
+
writeFileSync29(path2, Buffer.from(base64, "base64"));
|
|
73330
73412
|
return path2;
|
|
73331
73413
|
}
|
|
73332
73414
|
async function seedAgentFilesFor(dir, flags, mode = "init") {
|
|
@@ -74949,7 +75031,7 @@ ${doNow.map((l) => ` ${l}`).join("\n")}`);
|
|
|
74949
75031
|
const files = himiPluginFiles2(skills.skillMd);
|
|
74950
75032
|
for (const f of files) {
|
|
74951
75033
|
mkdirSync33(dirname37(join65(dir, f.path)), { recursive: true });
|
|
74952
|
-
|
|
75034
|
+
writeFileSync29(join65(dir, f.path), f.text);
|
|
74953
75035
|
}
|
|
74954
75036
|
print({ ok: true, dir, files: files.map((f) => f.path), next: [`claude plugin validate ${dir}`, `claude --plugin-dir ${dir}`] });
|
|
74955
75037
|
return 0;
|
|
@@ -74975,7 +75057,7 @@ ${doNow.map((l) => ` ${l}`).join("\n")}`);
|
|
|
74975
75057
|
const out2 = resolve53(str8(flags.out) ?? join65(dir, ".himi", "making-of.html"));
|
|
74976
75058
|
const { html, plan } = buildReel2(dir);
|
|
74977
75059
|
mkdirSync33(dirname37(out2), { recursive: true });
|
|
74978
|
-
|
|
75060
|
+
writeFileSync29(out2, html);
|
|
74979
75061
|
const { pathToFileURL: pathToFileURL9 } = await import("node:url");
|
|
74980
75062
|
const url2 = pathToFileURL9(out2).href;
|
|
74981
75063
|
const open2 = flags["no-open"] !== true;
|
|
@@ -77195,9 +77277,9 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
|
|
|
77195
77277
|
const { describeChanges: describeChanges2, headline: headline2 } = await Promise.resolve().then(() => (init_build_changes(), build_changes_exports));
|
|
77196
77278
|
const changes = describeChanges2(live2, join65(next, running.app), diff);
|
|
77197
77279
|
const retired = `${live2}.retired-${Date.now()}`;
|
|
77198
|
-
|
|
77280
|
+
renameSync8(live2, retired);
|
|
77199
77281
|
try {
|
|
77200
|
-
|
|
77282
|
+
renameSync8(join65(next, running.app), live2);
|
|
77201
77283
|
} catch {
|
|
77202
77284
|
cpSync3(join65(next, running.app), live2, { recursive: true });
|
|
77203
77285
|
}
|
|
@@ -79552,10 +79634,11 @@ ${report3.ok ? "OK \u2014 data + serve plane reachable." : typeof report3.hint =
|
|
|
79552
79634
|
return 2;
|
|
79553
79635
|
}
|
|
79554
79636
|
const redirectFlag = str8(flags["redirect-uri"]);
|
|
79555
|
-
|
|
79556
|
-
|
|
79637
|
+
const { APPLY_NEEDS_CLIENT: APPLY_NEEDS_CLIENT2, REDIRECT_NEEDS_CLIENT: REDIRECT_NEEDS_CLIENT2, applyNeedsClient: applyNeedsClient2 } = await Promise.resolve().then(() => (init_cli(), cli_exports));
|
|
79638
|
+
const applyWantsClient = flags.apply === true && applyNeedsClient2(str8(flags.shell));
|
|
79639
|
+
if (!clientId && (applyWantsClient || redirectFlag)) {
|
|
79557
79640
|
io.error(JSON.stringify({
|
|
79558
|
-
error:
|
|
79641
|
+
error: applyWantsClient ? APPLY_NEEDS_CLIENT2 : REDIRECT_NEEDS_CLIENT2,
|
|
79559
79642
|
usage: analyzeUsage
|
|
79560
79643
|
}));
|
|
79561
79644
|
return 2;
|
package/guides/himi-mcp.md
CHANGED
|
@@ -83,8 +83,13 @@ Each tool acts on the agent's workspace folder unless it is given a `dir`.
|
|
|
83
83
|
**File tools, for hosts that cannot edit files.** `himi mcp --with-files` adds `himi_list_apps`,
|
|
84
84
|
`himi_list_files`, `himi_read_file`, `himi_write_files` and `himi_delete_files`. The Claude Desktop
|
|
85
85
|
Extension uses them, because Claude Desktop chat has no file editing of its own. They follow the
|
|
86
|
-
hosted connector's shapes
|
|
87
|
-
|
|
86
|
+
hosted connector's shapes and act only inside the workspace folder, on an app root (a folder with
|
|
87
|
+
`himalaya.content.json`). They refuse any path through a symlink, dangling ones included, and write
|
|
88
|
+
without following links. They judge the deny-list in every spelling, because macOS and Windows open
|
|
89
|
+
`.ENV` as `.env`. They never list, read,
|
|
90
|
+
write or delete anything the source deny-list keeps off the machine: `.env` files, the `.himi/`
|
|
91
|
+
secret store, MCP host configs with their keys, key files, and `node_modules`, `dist` and `.git`
|
|
92
|
+
at any depth. When the host names
|
|
88
93
|
no workspace, `HIMI_WORKSPACE` sets the folder. Coding agents edit files themselves, so the flag is
|
|
89
94
|
off by default.
|
|
90
95
|
|
|
@@ -161,11 +161,13 @@ The plane has no headless browser (`look.unavailable: no_browser_on_plane`). Own
|
|
|
161
161
|
`himi-brandy`, the site shell, opens the customer's live website as native-framed tabs, so its contract is a `site` block: which website, and which of its pages are tabs, drawer rows or overflow rows. `--shell brandy` derives that block into `provisioning.overlay.json`:
|
|
162
162
|
|
|
163
163
|
```bash
|
|
164
|
-
himi site analyze --url https://owner.wixsite.com/site --
|
|
164
|
+
himi site analyze --url https://owner.wixsite.com/site --shell brandy # writes site.pages
|
|
165
165
|
# edit site.pages in .himi/business-book/provisioning.overlay.json if the map needs it
|
|
166
|
-
himi site analyze --url https://owner.wixsite.com/site --
|
|
166
|
+
himi site analyze --url https://owner.wixsite.com/site --shell brandy --apply
|
|
167
167
|
```
|
|
168
168
|
|
|
169
|
+
No `--client-id` is needed here: a site shell is visitor-only, its pages are the site itself, and its contract binds no Headless client. For an off-repo brandy app (`himi init --template brandy`), run the `--apply` inside the app folder: it writes the derived `site` block into the package's own `provisioning.json`, which the template's `config.ts` builds every tab and screen from.
|
|
170
|
+
|
|
169
171
|
The map comes from what the analysis already reads without owner credentials, because Wix has no public menu API: the published page list (`look.sitePages`), the header menu the crawl saw (`look.navLinks`, label and href), and each page's role.
|
|
170
172
|
|
|
171
173
|
- **Order and labels** follow the site's own menu. Links to other hosts, and repeat links to the same page, are dropped.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/himalaya-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1117.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": [
|
|
@@ -67,5 +67,5 @@
|
|
|
67
67
|
"artifactId": "himalaya-cli"
|
|
68
68
|
}
|
|
69
69
|
},
|
|
70
|
-
"falconPackageHash": "
|
|
70
|
+
"falconPackageHash": "fc5b0cacba000b4c6e6a4a5002e19ab4d37d0560e6bd98aed8b49b13"
|
|
71
71
|
}
|
|
Binary file
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# __HIMI_APP_NAME__ — your website as an app
|
|
2
|
+
|
|
3
|
+
A **site shell**: your live Wix website, framed as a native app. Every tab and drawer row is one of
|
|
4
|
+
your site's own pages, shown in a WebView under a native tab bar, drawer and splash in your brand
|
|
5
|
+
colours. The site's own header, footer and Wix ads are hidden. It is the fastest way to ship an
|
|
6
|
+
existing website as an app, and each tab can later be replaced by a native screen.
|
|
7
|
+
|
|
8
|
+
## Point it at your site
|
|
9
|
+
|
|
10
|
+
The whole app is the `site` block in `provisioning.json`:
|
|
11
|
+
|
|
12
|
+
```json
|
|
13
|
+
{
|
|
14
|
+
"site": {
|
|
15
|
+
"origin": "https://your-account.wixsite.com/your-site",
|
|
16
|
+
"kind": "wix",
|
|
17
|
+
"pages": [
|
|
18
|
+
{ "id": "home", "title": "Home", "path": "/", "icon": "home" },
|
|
19
|
+
{ "id": "blog", "title": "Blog", "path": "/blog", "icon": "newspaper" },
|
|
20
|
+
{ "id": "contact", "title": "Contact", "path": "/contact", "icon": "mail", "placement": "drawer" }
|
|
21
|
+
],
|
|
22
|
+
"chrome": { "hideSiteHeader": true, "hideSiteFooter": true, "hideWixAds": true }
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The placeholder origin above is not a real site. Replace it by deriving the block from your site's
|
|
28
|
+
own menu, from inside this folder:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
himi site analyze --url https://your-account.wixsite.com/your-site --shell brandy --apply
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
That writes the derived `site` block into this `provisioning.json` and your site's colours into
|
|
35
|
+
`tokens.json`. No `--client-id` is needed, because a site shell reads nothing through a Wix client.
|
|
36
|
+
To review the map first, leave out `--apply`: the block is then only written to
|
|
37
|
+
`.himi/business-book/provisioning.overlay.json`. You can always edit the block by hand.
|
|
38
|
+
|
|
39
|
+
- **`origin`** is your published site, `https` only. A free site's path (`/your-site`) is part of it.
|
|
40
|
+
- **`pages`** are paths relative to the origin. Each needs a unique `id` (`a-z`, `0-9`, `_`) and must
|
|
41
|
+
not use `page`, which is reserved.
|
|
42
|
+
- **`placement`** is `tab` (the default), `drawer` or `overflow`. At most four tabs are shown, and
|
|
43
|
+
`home` must be the first of them: the app opens on its first tab.
|
|
44
|
+
- **`icon`** is a design-system icon name (`home`, `bag`, `calendar`, `ticket`, `newspaper`, `mail`,
|
|
45
|
+
`info`, `tag`, `photo`, `person`, `globe`, …).
|
|
46
|
+
- **`chrome`** hides the site's own header (on inner pages), its footer and the Wix ads banner.
|
|
47
|
+
Every flag defaults to hidden.
|
|
48
|
+
|
|
49
|
+
`config.ts` validates the block whenever the app loads, so an `http` origin, a duplicate id or a map
|
|
50
|
+
with no tab fails `himi validate`, `himi render` and `himi push` rather than shipping.
|
|
51
|
+
|
|
52
|
+
## Loop
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
himi build && himi validate --strict # derive the screens and check them
|
|
56
|
+
himi render site_home # the home tab (a WebView: headless render reports it, it does not paint it)
|
|
57
|
+
himi push --draft # publish a draft release and open the web preview
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Your colours live in `tokens.json` and your name in `config.ts`.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Your app's configuration — a SITE SHELL. Your live website is the app: every tab and drawer
|
|
2
|
+
// row is one of its pages, shown in a native-framed WebView under a native tab bar and drawer.
|
|
3
|
+
//
|
|
4
|
+
// Nothing here names a page. The pages come from the `site` block of ./provisioning.json, so you
|
|
5
|
+
// change the app by changing that block — by hand, or with
|
|
6
|
+
// himi site analyze --url https://<your-site> --shell brandy
|
|
7
|
+
// which derives it from your site's own menu. Inspect the live values any time with
|
|
8
|
+
// `himi context`; see `himi docs app-config`.
|
|
9
|
+
import {
|
|
10
|
+
assertSiteShellConfig,
|
|
11
|
+
defineApp,
|
|
12
|
+
siteLinkableScreens,
|
|
13
|
+
siteNav,
|
|
14
|
+
siteNavConfig,
|
|
15
|
+
type SiteShellConfig,
|
|
16
|
+
} from "@himalaya/core";
|
|
17
|
+
import { ConsoleTelemetrySink } from "@himalaya/telemetry-console";
|
|
18
|
+
import provisioning from "./provisioning.json" with { type: "json" };
|
|
19
|
+
|
|
20
|
+
/** Your website and its page map. Validated here, so a mistake in provisioning.json (an http
|
|
21
|
+
* origin, a duplicate page id, no tab) fails `himi validate`, `render` and `push` instead of
|
|
22
|
+
* shipping a broken app. */
|
|
23
|
+
export const site = provisioning.site as SiteShellConfig;
|
|
24
|
+
assertSiteShellConfig(site);
|
|
25
|
+
const nav = siteNav(site);
|
|
26
|
+
// The app opens on its first tab, and everything that names the home screen (`himi init`,
|
|
27
|
+
// `himi render`, agent tooling) names `site_home` — so the `home` page must BE the first tab.
|
|
28
|
+
if (nav.tabs[0]?.screenId !== "site_home") {
|
|
29
|
+
throw new Error('provisioning.json: the "home" page must be the first tab — list it first in site.pages');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default defineApp({
|
|
33
|
+
id: "__HIMI_APP_DOTID__",
|
|
34
|
+
name: "__HIMI_APP_NAME__",
|
|
35
|
+
bundleId: "__HIMI_APP_DOTID__",
|
|
36
|
+
applicationId: "__HIMI_APP_DOTID__",
|
|
37
|
+
// Placeholders — an over-the-air app never consumes these. Leave as-is.
|
|
38
|
+
schemaPackage: "@example/__HIMI_APP_SLUG__-schema",
|
|
39
|
+
schemaPackageRange: { min: 9900, max: 9909 },
|
|
40
|
+
// Placeholders — only a local server would read these. Leave as-is.
|
|
41
|
+
ports: { devServer: 8190, web: 5390 },
|
|
42
|
+
// Visitor-only: your site's own pages handle anything a visitor does.
|
|
43
|
+
auth: { providerId: "none", config: {} },
|
|
44
|
+
telemetry: ConsoleTelemetrySink(),
|
|
45
|
+
serverUrls: {
|
|
46
|
+
screen: "http://localhost:8190/v1",
|
|
47
|
+
actions: "http://localhost:8190/v1/actions",
|
|
48
|
+
sync: "http://localhost:8190/v1/sync",
|
|
49
|
+
},
|
|
50
|
+
capabilities: [],
|
|
51
|
+
// Your brand colours paint the NATIVE chrome (tabs, drawer, splash); the site paints its pages.
|
|
52
|
+
designTokensPath: "./tokens.json",
|
|
53
|
+
// "Ready" means the first page has painted, not just the app shell — otherwise the splash
|
|
54
|
+
// fades into a blank WebView. It still dismisses after 5s on a slow link.
|
|
55
|
+
splash: {
|
|
56
|
+
enabled: true,
|
|
57
|
+
backgroundColor: "accentPrimary",
|
|
58
|
+
caption: "__HIMI_APP_NAME__",
|
|
59
|
+
minVisibleMs: 400,
|
|
60
|
+
maxHoldMs: 5000,
|
|
61
|
+
readySignal: "first_content",
|
|
62
|
+
},
|
|
63
|
+
navigation: {
|
|
64
|
+
// Bottom tabs on a phone, a sidebar on a larger screen; the drawer lists every page that is
|
|
65
|
+
// not `overflow`. At most four tabs — each keeps its page loaded while you switch.
|
|
66
|
+
navKind: "responsive",
|
|
67
|
+
scheme: "himalaya-__HIMI_APP_SLUG__",
|
|
68
|
+
deepLinkPatterns: [],
|
|
69
|
+
linkableScreens: siteLinkableScreens(site),
|
|
70
|
+
// A notification or app link carrying one of your site's URLs opens that page's tab.
|
|
71
|
+
site: siteNavConfig(site),
|
|
72
|
+
tabBar: { items: nav.tabs },
|
|
73
|
+
drawer: { items: nav.drawer },
|
|
74
|
+
},
|
|
75
|
+
entities: {},
|
|
76
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Your app's screens. Each one is a page of your website — a WebView screen built from the
|
|
2
|
+
// `site` block of ../provisioning.json — so this file holds no screen of its own. Change the
|
|
3
|
+
// pages by changing that block, never this file.
|
|
4
|
+
import { siteScreens } from "@himalaya/core";
|
|
5
|
+
import config, { site } from "../config.js";
|
|
6
|
+
|
|
7
|
+
const SCREENS = siteScreens(site);
|
|
8
|
+
// The home screen is always the `home` page's: `site_home`. A literal on purpose — `himi init`
|
|
9
|
+
// and the agent tooling read `homeScreenId` from this file as written; config.ts refuses a map
|
|
10
|
+
// whose first tab is anything else, so the literal and the tab bar cannot disagree.
|
|
11
|
+
|
|
12
|
+
export default {
|
|
13
|
+
config,
|
|
14
|
+
homeScreenId: "site_home",
|
|
15
|
+
screens: {
|
|
16
|
+
// A fresh copy per build: descriptors are pure functions of the site block.
|
|
17
|
+
exact: Object.fromEntries(Object.keys(SCREENS).map((id) => [id, () => structuredClone(SCREENS[id])])),
|
|
18
|
+
templated: [],
|
|
19
|
+
},
|
|
20
|
+
actions: [],
|
|
21
|
+
createStore: () => ({}),
|
|
22
|
+
// Every page is seeded, so the whole tab bar and drawer work on a cold offline start (each
|
|
23
|
+
// WebView then shows its own offline state until the network returns).
|
|
24
|
+
seed: { screens: SCREENS, data: {} },
|
|
25
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"title": "Brandy — your website as an app",
|
|
3
|
+
"description": "A site shell: the customer's live Wix website as a native app. Every tab and drawer row is one of the site's own pages in a native-framed WebView, with a native tab bar, drawer, splash and brand colours; the site's header, footer and Wix ads are hidden. The page map lives in provisioning.json (`site`), and `himi site analyze --url <site> --shell brandy` derives it from the site's own menu. Start here to ship an existing website as an app today and move tabs to native screens later.",
|
|
4
|
+
"tags": ["wix", "website", "site-shell", "webview", "tabs", "drawer", "no-code", "day-zero", "consumer"],
|
|
5
|
+
"category": "consumer"
|
|
6
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"macos":false}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"site": {
|
|
3
|
+
"origin": "https://your-account.wixsite.com/your-site",
|
|
4
|
+
"kind": "wix",
|
|
5
|
+
"pages": [
|
|
6
|
+
{ "id": "home", "title": "Home", "path": "/", "icon": "home" },
|
|
7
|
+
{ "id": "blog", "title": "Blog", "path": "/blog", "icon": "newspaper" },
|
|
8
|
+
{ "id": "contact", "title": "Contact", "path": "/contact", "icon": "mail", "placement": "drawer" }
|
|
9
|
+
],
|
|
10
|
+
"chrome": { "hideSiteHeader": true, "hideSiteFooter": true, "hideWixAds": true }
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
{
|
|
2
|
+
"colors": {
|
|
3
|
+
"surfacePrimary": {
|
|
4
|
+
"light": "#FFFFFF",
|
|
5
|
+
"dark": "#0D1117",
|
|
6
|
+
"highContrast": "#000000"
|
|
7
|
+
},
|
|
8
|
+
"surfaceSecondary": {
|
|
9
|
+
"light": "#F4F7F8",
|
|
10
|
+
"dark": "#161B22",
|
|
11
|
+
"highContrast": "#111111"
|
|
12
|
+
},
|
|
13
|
+
"surfaceElevated": {
|
|
14
|
+
"light": "#FFFFFF",
|
|
15
|
+
"dark": "#1D2228",
|
|
16
|
+
"highContrast": "#111111"
|
|
17
|
+
},
|
|
18
|
+
"surfaceSubtle": {
|
|
19
|
+
"light": "#F8FAF9",
|
|
20
|
+
"dark": "#121821",
|
|
21
|
+
"highContrast": "#111111"
|
|
22
|
+
},
|
|
23
|
+
"surfaceTertiary": {
|
|
24
|
+
"light": "#E9EEF0",
|
|
25
|
+
"dark": "#232A32",
|
|
26
|
+
"highContrast": "#1A1A1A"
|
|
27
|
+
},
|
|
28
|
+
"textPrimary": {
|
|
29
|
+
"light": "#1F2328",
|
|
30
|
+
"dark": "#E6EDF3",
|
|
31
|
+
"highContrast": "#FFFFFF"
|
|
32
|
+
},
|
|
33
|
+
"textSecondary": {
|
|
34
|
+
"light": "#59636E",
|
|
35
|
+
"dark": "#848D98",
|
|
36
|
+
"highContrast": "#CCCCCC"
|
|
37
|
+
},
|
|
38
|
+
"textTertiary": {
|
|
39
|
+
"light": "#7D8791",
|
|
40
|
+
"dark": "#6E7681",
|
|
41
|
+
"highContrast": "#CCCCCC"
|
|
42
|
+
},
|
|
43
|
+
"textOnAccent": {
|
|
44
|
+
"light": "#FFFFFF",
|
|
45
|
+
"dark": "#FFFFFF",
|
|
46
|
+
"highContrast": "#000000"
|
|
47
|
+
},
|
|
48
|
+
"accentPrimary": {
|
|
49
|
+
"light": "#0969DA",
|
|
50
|
+
"dark": "#1E72C7",
|
|
51
|
+
"highContrast": "#58A6FF"
|
|
52
|
+
},
|
|
53
|
+
"accentSecondary": {
|
|
54
|
+
"light": "#087F5B",
|
|
55
|
+
"dark": "#1C8968",
|
|
56
|
+
"highContrast": "#FFFFFF"
|
|
57
|
+
},
|
|
58
|
+
"accentSurface": {
|
|
59
|
+
"light": "#E2EDF8",
|
|
60
|
+
"dark": "#0C2540",
|
|
61
|
+
"highContrast": "#FFFFFF"
|
|
62
|
+
},
|
|
63
|
+
"success": {
|
|
64
|
+
"light": "#1A7F37",
|
|
65
|
+
"dark": "#3FB950",
|
|
66
|
+
"highContrast": "#3FB950"
|
|
67
|
+
},
|
|
68
|
+
"successSurface": {
|
|
69
|
+
"light": "#E9F8EC",
|
|
70
|
+
"dark": "#12331B",
|
|
71
|
+
"highContrast": "#FFFFFF"
|
|
72
|
+
},
|
|
73
|
+
"warning": {
|
|
74
|
+
"light": "#9A6700",
|
|
75
|
+
"dark": "#D29922",
|
|
76
|
+
"highContrast": "#D29922"
|
|
77
|
+
},
|
|
78
|
+
"warningSurface": {
|
|
79
|
+
"light": "#FFF5D8",
|
|
80
|
+
"dark": "#3A2C0A",
|
|
81
|
+
"highContrast": "#FFFFFF"
|
|
82
|
+
},
|
|
83
|
+
"danger": {
|
|
84
|
+
"light": "#CF222E",
|
|
85
|
+
"dark": "#F85149",
|
|
86
|
+
"highContrast": "#FF5555"
|
|
87
|
+
},
|
|
88
|
+
"dangerSurface": {
|
|
89
|
+
"light": "#FFEDEA",
|
|
90
|
+
"dark": "#3A1513",
|
|
91
|
+
"highContrast": "#FFFFFF"
|
|
92
|
+
},
|
|
93
|
+
"border": {
|
|
94
|
+
"light": "#D8DEE4",
|
|
95
|
+
"dark": "#30363D",
|
|
96
|
+
"highContrast": "#FFFFFF"
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
"spacing": {
|
|
100
|
+
"xs": 4,
|
|
101
|
+
"sm": 8,
|
|
102
|
+
"md": 12,
|
|
103
|
+
"lg": 16,
|
|
104
|
+
"xl": 24
|
|
105
|
+
},
|
|
106
|
+
"typography": {
|
|
107
|
+
"title1": {
|
|
108
|
+
"size": 28,
|
|
109
|
+
"weight": 700,
|
|
110
|
+
"line": 1.2
|
|
111
|
+
},
|
|
112
|
+
"body": {
|
|
113
|
+
"size": 16,
|
|
114
|
+
"weight": 400,
|
|
115
|
+
"line": 1.5
|
|
116
|
+
},
|
|
117
|
+
"caption": {
|
|
118
|
+
"size": 13,
|
|
119
|
+
"weight": 400,
|
|
120
|
+
"line": 1.4
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
"radius": {
|
|
124
|
+
"sm": 4,
|
|
125
|
+
"md": 8,
|
|
126
|
+
"lg": 12
|
|
127
|
+
}
|
|
128
|
+
}
|