@wix/himalaya-cli 0.1116.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 CHANGED
@@ -36089,10 +36089,10 @@ var init_render_fonts = __esm({
36089
36089
 
36090
36090
  // ../../core/dev-server/src/wix-cache-policy.ts
36091
36091
  function clampRule(rule, denyPrefixes, maxTTL) {
36092
- const denied = denyPrefixes.some((p) => rule.pathPrefix.startsWith(p));
36092
+ const denied2 = denyPrefixes.some((p) => rule.pathPrefix.startsWith(p));
36093
36093
  return {
36094
36094
  ...rule,
36095
- cacheable: rule.cacheable && !denied,
36095
+ cacheable: rule.cacheable && !denied2,
36096
36096
  ttl: Math.min(rule.ttl, maxTTL)
36097
36097
  };
36098
36098
  }
@@ -53854,7 +53854,8 @@ var init_cli_bridge = __esm({
53854
53854
  });
53855
53855
 
53856
53856
  // src/mcp/file-tools.ts
53857
- import { lstatSync as lstatSync12, mkdirSync as mkdirSync26, readdirSync as readdirSync27, readFileSync as readFileSync59, realpathSync as realpathSync7, rmSync as rmSync12, statSync as statSync13, writeFileSync as writeFileSync21, existsSync as existsSync54 } from "node:fs";
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";
53858
53859
  import { dirname as dirname30, isAbsolute as isAbsolute5, join as join53, relative as relative11, resolve as resolve45, sep as sep14 } from "node:path";
53859
53860
  import * as z from "zod";
53860
53861
  function within(root, p) {
@@ -53871,37 +53872,99 @@ function realish(p) {
53871
53872
  }
53872
53873
  return join53(realpathSync7(cur), ...tail);
53873
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
+ }
53874
53887
  async function confinedAppDir(args, deps) {
53875
53888
  const root = resolve45(await deps.defaultDir());
53876
53889
  const dir = typeof args.dir === "string" && args.dir ? resolve45(root, args.dir) : root;
53877
53890
  if (!existsSync54(root)) return refuse4("no_workspace", `the workspace folder ${root} does not exist`);
53878
- if (!within(realpathSync7(root), realish(dir))) return refuse4("outside_workspace", `"${String(args.dir)}" is outside the workspace folder`);
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
+ }
53879
53899
  return dir;
53880
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
+ }
53881
53921
  function confinedPath(appDir3, rel) {
53882
53922
  if (typeof rel !== "string" || !rel.trim()) return { error: "a path is required" };
53883
53923
  if (rel.includes("\0")) return { error: "a path may not contain NUL" };
53884
53924
  if (isAbsolute5(rel)) return { error: `"${rel}" is absolute \u2014 give a path inside the app folder` };
53885
53925
  const abs = resolve45(appDir3, rel);
53926
+ if (!within(appDir3, abs) || abs === appDir3) return { error: `"${rel}" is outside the app folder` };
53886
53927
  const real = realish(abs);
53887
53928
  const base = existsSync54(appDir3) ? realpathSync7(appDir3) : realish(appDir3);
53888
53929
  if (!within(base, real) || real === base) return { error: `"${rel}" is outside the app folder` };
53889
- const first = relative11(base, real).split(sep14)[0];
53890
- if (RESERVED.has(first)) return { error: `"${rel}" is inside ${first}/, which this tool does not touch` };
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` };
53891
53933
  return abs;
53892
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
+ }
53893
53953
  function listFiles(appDir3) {
53894
53954
  const files = [];
53895
53955
  const walk2 = (d) => {
53956
+ const parentRel = relative11(appDir3, d);
53896
53957
  for (const e of readdirSync27(d, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
53897
- if (RESERVED.has(e.name)) continue;
53898
53958
  if (e.isSymbolicLink()) continue;
53899
53959
  const p = join53(d, e.name);
53900
53960
  if (e.isDirectory()) {
53961
+ if (isDeniedSourceDir(e.name, parentRel) || isDeniedSourceDir(e.name.toLowerCase(), parentRel)) continue;
53901
53962
  if (!walk2(p)) return false;
53902
53963
  } else if (e.isFile()) {
53964
+ const rel = relative11(appDir3, p);
53965
+ if (isDeniedSourcePath(rel) || isDeniedSourcePath(rel.toLowerCase())) continue;
53903
53966
  if (files.length >= MAX_LISTED) return false;
53904
- files.push({ path: relative11(appDir3, p).split(sep14).join("/"), bytes: statSync13(p).size });
53967
+ files.push({ path: rel.split(sep14).join("/"), bytes: statSync13(p).size });
53905
53968
  }
53906
53969
  }
53907
53970
  return true;
@@ -53931,7 +53994,7 @@ function fileTools() {
53931
53994
  {
53932
53995
  name: "himi_list_files",
53933
53996
  title: "List app files",
53934
- description: `Every file in an app folder with its size (skips ${[...RESERVED].join(", ")}; at most ${MAX_LISTED}).`,
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).`,
53935
53998
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
53936
53999
  inputSchema: z.object({ dir: DIR }),
53937
54000
  handler: async (a, d) => {
@@ -53980,14 +54043,20 @@ function fileTools() {
53980
54043
  else if (typeof f.content !== "string") refused2.push({ path: f.path, error: "content must be text" });
53981
54044
  else if (Buffer.byteLength(f.content) > MAX_FILE_BYTES) refused2.push({ path: f.path, error: `over the ${MAX_FILE_BYTES}-byte limit` });
53982
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" });
53983
54047
  else planned.push({ abs: p, rel: String(f.path), content: f.content });
53984
54048
  }
53985
54049
  if (refused2.length) return { ...ok({ ok: false, error: "refused", written: [], refused: refused2 }), isError: true };
54050
+ const written = [];
53986
54051
  for (const f of planned) {
53987
- mkdirSync26(dirname30(f.abs), { recursive: true });
53988
- writeFileSync21(f.abs, f.content);
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
+ }
53989
54058
  }
53990
- return ok({ ok: true, dir: app, written: planned.map((f) => f.rel) });
54059
+ return ok({ ok: true, dir: app, written });
53991
54060
  }
53992
54061
  },
53993
54062
  {
@@ -54001,28 +54070,32 @@ function fileTools() {
54001
54070
  if (typeof app !== "string") return app;
54002
54071
  const paths = Array.isArray(a.paths) ? a.paths : [];
54003
54072
  const planned = [];
54073
+ const deleted = [];
54004
54074
  const refused2 = [];
54005
54075
  for (const rel of paths) {
54006
54076
  const p = confinedPath(app, rel);
54007
54077
  if (typeof p !== "string") refused2.push({ path: rel, error: p.error });
54008
54078
  else if (!existsSync54(p)) refused2.push({ path: rel, error: "no such file" });
54009
54079
  else if (!lstatSync12(p).isFile()) refused2.push({ path: rel, error: "not a regular file" });
54010
- else planned.push(p);
54080
+ else if (!planned.includes(p)) {
54081
+ planned.push(p);
54082
+ deleted.push(rel);
54083
+ }
54011
54084
  }
54012
54085
  if (refused2.length) return { ...ok({ ok: false, error: "refused", deleted: [], refused: refused2 }), isError: true };
54013
54086
  for (const p of planned) rmSync12(p);
54014
- return ok({ ok: true, dir: app, deleted: paths });
54087
+ return ok({ ok: true, dir: app, deleted });
54015
54088
  }
54016
54089
  }
54017
54090
  ];
54018
54091
  }
54019
- var MAX_FILE_BYTES, MAX_FILES_PER_WRITE, MAX_LISTED, RESERVED, CONTENT_MARKER, ok, refuse4, DIR, WRITE_ANN;
54092
+ var MAX_FILE_BYTES, MAX_FILES_PER_WRITE, MAX_LISTED, CONTENT_MARKER, ok, refuse4, DIR, WRITE_ANN;
54020
54093
  var init_file_tools = __esm({
54021
54094
  "src/mcp/file-tools.ts"() {
54095
+ init_source_deny();
54022
54096
  MAX_FILE_BYTES = 1024 * 1024;
54023
54097
  MAX_FILES_PER_WRITE = 100;
54024
54098
  MAX_LISTED = 2e3;
54025
- RESERVED = /* @__PURE__ */ new Set([".git", "node_modules", "dist"]);
54026
54099
  CONTENT_MARKER = "himalaya.content.json";
54027
54100
  ok = (o) => ({ content: [{ type: "text", text: JSON.stringify(o, null, 2) }] });
54028
54101
  refuse4 = (error, message) => ({ content: [{ type: "text", text: JSON.stringify({ ok: false, error, message }, null, 2) }], isError: true });
@@ -54533,7 +54606,7 @@ __export(native_decode_exports, {
54533
54606
  runNativeDecode: () => runNativeDecode
54534
54607
  });
54535
54608
  import { execFileSync as execFileSync5, spawnSync as spawnSync2 } from "node:child_process";
54536
- import { existsSync as existsSync55, mkdtempSync as mkdtempSync2, readFileSync as readFileSync61, rmSync as rmSync13, writeFileSync as writeFileSync22 } from "node:fs";
54609
+ import { existsSync as existsSync55, mkdtempSync as mkdtempSync2, readFileSync as readFileSync61, rmSync as rmSync13, writeFileSync as writeFileSync21 } from "node:fs";
54537
54610
  import { tmpdir as tmpdir3 } from "node:os";
54538
54611
  import { join as join54 } from "node:path";
54539
54612
  import { fileURLToPath as fileURLToPath22 } from "node:url";
@@ -54667,7 +54740,7 @@ function runNativeDecode(options) {
54667
54740
  if (typeof descriptor.descriptorJson !== "string") {
54668
54741
  throw new Error(`native decode cannot judge ${descriptor.screenId}: the release builder did not return its exact baked JSON bytes`);
54669
54742
  }
54670
- writeFileSync22(join54(descriptorDir, `${encodeURIComponent(descriptor.screenId)}.json`), descriptor.descriptorJson);
54743
+ writeFileSync21(join54(descriptorDir, `${encodeURIComponent(descriptor.screenId)}.json`), descriptor.descriptorJson);
54671
54744
  }
54672
54745
  options.progress?.("building cached Swift oracle");
54673
54746
  execFileSync5("swift", ["build", "--package-path", options.packageDir, "--product", "himi-decode-oracle"], {
@@ -54705,7 +54778,7 @@ function runAndroidNativeDecode(options) {
54705
54778
  if (typeof descriptor.descriptorJson !== "string") {
54706
54779
  throw new Error(`native decode cannot judge ${descriptor.screenId}: the release builder did not return its exact baked JSON bytes`);
54707
54780
  }
54708
- writeFileSync22(join54(descriptorDir, `${encodeURIComponent(descriptor.screenId)}.json`), descriptor.descriptorJson);
54781
+ writeFileSync21(join54(descriptorDir, `${encodeURIComponent(descriptor.screenId)}.json`), descriptor.descriptorJson);
54709
54782
  }
54710
54783
  options.progress?.(`running real Kotlin decoder over ${options.descriptors.length} screen${options.descriptors.length === 1 ? "" : "s"}`);
54711
54784
  execFileSync5(options.gradlew, [
@@ -54789,7 +54862,7 @@ __export(browser_authoring_exports, {
54789
54862
  sdkSubpathReader: () => sdkSubpathReader
54790
54863
  });
54791
54864
  import { createServer as createServer5 } from "node:http";
54792
- import { createHash as createHash29, randomBytes as randomBytes4 } from "node:crypto";
54865
+ import { createHash as createHash29, randomBytes as randomBytes5 } from "node:crypto";
54793
54866
  import { existsSync as existsSync56, readdirSync as readdirSync28, readFileSync as readFileSync62, realpathSync as realpathSync8, statSync as statSync14 } from "node:fs";
54794
54867
  import { join as join55, posix as posix2, relative as relative12, sep as sep15 } from "node:path";
54795
54868
  function humanMs(ms) {
@@ -54887,7 +54960,7 @@ function sdkSubpathReader(resolve54) {
54887
54960
  function encodeJob(job) {
54888
54961
  return Buffer.from(JSON.stringify(job), "utf8").toString("base64");
54889
54962
  }
54890
- function mintToken2(payload, nonce = randomBytes4(16).toString("hex")) {
54963
+ function mintToken2(payload, nonce = randomBytes5(16).toString("hex")) {
54891
54964
  const digest2 = createHash29("sha256").update(payload, "utf8").digest("base64url");
54892
54965
  return `${nonce}.${digest2}`;
54893
54966
  }
@@ -55050,7 +55123,7 @@ __export(coverage_exports, {
55050
55123
  suppressedNativeKeys: () => suppressedNativeKeys,
55051
55124
  trendAndStore: () => trendAndStore
55052
55125
  });
55053
- import { existsSync as existsSync57, mkdirSync as mkdirSync27, readFileSync as readFileSync63, writeFileSync as writeFileSync23 } from "node:fs";
55126
+ import { existsSync as existsSync57, mkdirSync as mkdirSync27, readFileSync as readFileSync63, writeFileSync as writeFileSync22 } from "node:fs";
55054
55127
  import { dirname as dirname31, join as join56 } from "node:path";
55055
55128
  function suppressedNativeKeys(oracle) {
55056
55129
  return [...oracle.ios ? [] : ["native-decode|ios"], ...oracle.android ? [] : ["native-decode|android"]];
@@ -55244,7 +55317,7 @@ function trendAndStore(dir, entries, suppressed) {
55244
55317
  const stored = [.../* @__PURE__ */ new Set([...current, ...(previous ?? []).filter((key2) => (suppressed ?? []).includes(key2))])];
55245
55318
  try {
55246
55319
  mkdirSync27(dirname31(p), { recursive: true });
55247
- writeFileSync23(p, JSON.stringify({ keys: stored, ts: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
55320
+ writeFileSync22(p, JSON.stringify({ keys: stored, ts: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
55248
55321
  } catch {
55249
55322
  return null;
55250
55323
  }
@@ -55289,7 +55362,7 @@ function diffAndStoreObservations(dir, current) {
55289
55362
  }
55290
55363
  try {
55291
55364
  mkdirSync27(dirname31(p), { recursive: true });
55292
- writeFileSync23(p, JSON.stringify({ screens: current, ts: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
55365
+ writeFileSync22(p, JSON.stringify({ screens: current, ts: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
55293
55366
  } catch {
55294
55367
  return null;
55295
55368
  }
@@ -55900,8 +55973,8 @@ ${releaseId}`;
55900
55973
  const pin = resolvePin(request, url);
55901
55974
  let bytes2 = null;
55902
55975
  if (pin) {
55903
- const denied = await authorizePinnedRead(request, url, app, pin);
55904
- if (denied) return { ok: false, response: denied };
55976
+ const denied2 = await authorizePinnedRead(request, url, app, pin);
55977
+ if (denied2) return { ok: false, response: denied2 };
55905
55978
  const m = await pinnedManifest(app, pin);
55906
55979
  if (!m) return { ok: false, response: json4(404, { error: "release_not_found", app, releaseId: pin }) };
55907
55980
  bytes2 = m.appConfigHash ? await pinnedBody(app, m.appConfigHash, () => store.getAppConfig(app)) : null;
@@ -55924,7 +55997,7 @@ ${releaseId}`;
55924
55997
  return new Response(body, { status: 200, headers: { "content-type": JSON_CT2, etag, "cache-control": cache, ...vary } });
55925
55998
  }
55926
55999
  async function authorizePinnedRead(request, url, app, releaseId, screenId) {
55927
- const denied = await checkPinnedRead({
56000
+ const denied2 = await checkPinnedRead({
55928
56001
  secret: opts.previewTokenSecret,
55929
56002
  token: resolvePreviewToken(request, url),
55930
56003
  app,
@@ -55934,7 +56007,7 @@ ${releaseId}`;
55934
56007
  authorizePreview: opts.authorizePreview,
55935
56008
  authorizeFollow: opts.authorizeFollow
55936
56009
  });
55937
- return denied && json4(denied.status, denied.body);
56010
+ return denied2 && json4(denied2.status, denied2.body);
55938
56011
  }
55939
56012
  async function honourRingPin(request) {
55940
56013
  const kept = { request, dropped: false };
@@ -56385,8 +56458,8 @@ ${releaseId}`;
56385
56458
  const app = cfgMatch?.[1] ?? resolveApp(request, url);
56386
56459
  const pin = resolvePin(request, url);
56387
56460
  if (app && pin) {
56388
- const denied = await authorizePinnedRead(request, url, app, pin);
56389
- if (denied) return denied;
56461
+ const denied2 = await authorizePinnedRead(request, url, app, pin);
56462
+ if (denied2) return denied2;
56390
56463
  }
56391
56464
  if (path2 === "/v1/template-source") {
56392
56465
  if (!app) return json4(400, { error: "missing_app" });
@@ -56469,8 +56542,8 @@ ${releaseId}`;
56469
56542
  if (!app) return json4(400, { error: "missing_app" });
56470
56543
  let m;
56471
56544
  if (pin) {
56472
- const denied = await authorizePinnedRead(request, url, app, pin);
56473
- if (denied) return denied;
56545
+ const denied2 = await authorizePinnedRead(request, url, app, pin);
56546
+ if (denied2) return denied2;
56474
56547
  m = await pinnedManifest(app, pin);
56475
56548
  if (!m) return json4(404, { error: "release_not_found", app, releaseId: pin });
56476
56549
  } else {
@@ -56656,8 +56729,8 @@ ${releaseId}`;
56656
56729
  const screenId = screenMatch[1];
56657
56730
  if (!app || !isSafeSegment(screenId)) return json4(404, { error: "screen_not_found", screenId });
56658
56731
  if (pin) {
56659
- const denied = await authorizePinnedRead(request, url, app, pin, screenId);
56660
- if (denied) return denied;
56732
+ const denied2 = await authorizePinnedRead(request, url, app, pin, screenId);
56733
+ if (denied2) return denied2;
56661
56734
  const m2 = await pinnedManifest(app, pin);
56662
56735
  if (!m2) return json4(404, { error: "release_not_found", app, releaseId: pin });
56663
56736
  const meta2 = m2.screens[screenId];
@@ -57450,7 +57523,7 @@ __export(run_watch_exports, {
57450
57523
  watchedContentDir: () => watchedContentDir,
57451
57524
  writeRunRecord: () => writeRunRecord
57452
57525
  });
57453
- import { closeSync as closeSync4, constants, existsSync as existsSync60, fstatSync, lstatSync as lstatSync13, statSync as statSync16, mkdirSync as mkdirSync29, openSync as openSync3, readFileSync as readFileSync67, realpathSync as realpathSync9, renameSync as renameSync6, rmSync as rmSync15, writeFileSync as writeFileSync24, watch as fsWatch } from "node:fs";
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";
57454
57527
  import { randomUUID as randomUUID4 } from "node:crypto";
57455
57528
  import { extname as extname4, join as join59, resolve as resolve47, sep as sep16 } from "node:path";
57456
57529
  function diffReleases(prev, next) {
@@ -57585,9 +57658,9 @@ function writeRunRecord(dir, record2) {
57585
57658
  if (!root) return false;
57586
57659
  const tmp = join59(root, `.run.json.${process.pid}.${randomUUID4()}.tmp`);
57587
57660
  try {
57588
- writeFileSync24(tmp, `${JSON.stringify(record2, null, 2)}
57661
+ writeFileSync23(tmp, `${JSON.stringify(record2, null, 2)}
57589
57662
  `, { flag: "wx" });
57590
- renameSync6(tmp, join59(root, "run.json"));
57663
+ renameSync7(tmp, join59(root, "run.json"));
57591
57664
  } catch (e) {
57592
57665
  rmSync15(tmp, { force: true });
57593
57666
  throw e;
@@ -57644,12 +57717,12 @@ function readConfined(root, rel) {
57644
57717
  }
57645
57718
  const canonicalRoot = realpathSync9(root);
57646
57719
  if (!realpathSync9(at2).startsWith(canonicalRoot + sep16)) return null;
57647
- const fd = openSync3(at2, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
57720
+ const fd = openSync4(at2, constants2.O_RDONLY | (constants2.O_NOFOLLOW ?? 0));
57648
57721
  try {
57649
57722
  if (!fstatSync(fd).isFile()) return null;
57650
57723
  return readFileSync67(fd);
57651
57724
  } finally {
57652
- closeSync4(fd);
57725
+ closeSync5(fd);
57653
57726
  }
57654
57727
  } catch {
57655
57728
  return null;
@@ -58871,7 +58944,7 @@ __export(version_exports, {
58871
58944
  setAndroidVersion: () => setAndroidVersion,
58872
58945
  setIosVersion: () => setIosVersion
58873
58946
  });
58874
- import { readFileSync as readFileSync72, writeFileSync as writeFileSync25 } from "node:fs";
58947
+ import { readFileSync as readFileSync72, writeFileSync as writeFileSync24 } from "node:fs";
58875
58948
  function readIosVersion(projectYml) {
58876
58949
  const marketing = matchScalar(projectYml, "CFBundleShortVersionString");
58877
58950
  const build2 = matchScalar(projectYml, "CFBundleVersion");
@@ -58936,8 +59009,8 @@ function bumpVersion(app, opts) {
58936
59009
  marketing: opts.marketing ?? current.marketing,
58937
59010
  build: opts.build === "increment" || opts.build === void 0 ? current.build + (opts.build === "increment" ? 1 : 0) : opts.build
58938
59011
  };
58939
- writeFileSync25(iosPath, setIosVersion(iosYml, next));
58940
- writeFileSync25(gradlePath, setAndroidVersion(gradle, next));
59012
+ writeFileSync24(iosPath, setIosVersion(iosYml, next));
59013
+ writeFileSync24(gradlePath, setAndroidVersion(gradle, next));
58941
59014
  return next;
58942
59015
  }
58943
59016
  var init_version = __esm({
@@ -59080,7 +59153,7 @@ var init_asc_status = __esm({
59080
59153
  });
59081
59154
 
59082
59155
  // ../../core/functions-secrets/src/vault.ts
59083
- import { createCipheriv, createDecipheriv, randomBytes as randomBytes5 } from "node:crypto";
59156
+ import { createCipheriv, createDecipheriv, randomBytes as randomBytes6 } from "node:crypto";
59084
59157
  import { mkdir, readFile as readFile2, rename, writeFile as writeFile2 } from "node:fs/promises";
59085
59158
  import { dirname as dirname34 } from "node:path";
59086
59159
  function createJsonSecretStore(filePath) {
@@ -59138,7 +59211,7 @@ function createSecretVault(options) {
59138
59211
  environment: input.environment,
59139
59212
  name: input.name,
59140
59213
  version: input.version,
59141
- iv: randomBytes5(12).toString("base64url"),
59214
+ iv: randomBytes6(12).toString("base64url"),
59142
59215
  ciphertext: "",
59143
59216
  authTag: ""
59144
59217
  };
@@ -59500,7 +59573,7 @@ __export(notify_exports, {
59500
59573
  runNotify: () => runNotify,
59501
59574
  saveAdminSession: () => saveAdminSession
59502
59575
  });
59503
- import { chmodSync as chmodSync3, mkdirSync as mkdirSync30, readFileSync as readFileSync76, writeFileSync as writeFileSync26 } from "node:fs";
59576
+ import { chmodSync as chmodSync3, mkdirSync as mkdirSync30, readFileSync as readFileSync76, writeFileSync as writeFileSync25 } from "node:fs";
59504
59577
  import { homedir as homedir6 } from "node:os";
59505
59578
  import { dirname as dirname35, join as join62 } from "node:path";
59506
59579
  function defaultSessionsPath() {
@@ -59517,7 +59590,7 @@ function saveAdminSession(adminUrl, session, path2 = defaultSessionsPath()) {
59517
59590
  const file = loadSessionsFile(path2);
59518
59591
  file.sessions = { ...file.sessions, [adminUrl]: session };
59519
59592
  mkdirSync30(dirname35(path2), { recursive: true, mode: 448 });
59520
- writeFileSync26(path2, JSON.stringify(file, null, 2) + "\n", { mode: 384 });
59593
+ writeFileSync25(path2, JSON.stringify(file, null, 2) + "\n", { mode: 384 });
59521
59594
  try {
59522
59595
  chmodSync3(dirname35(path2), 448);
59523
59596
  chmodSync3(path2, 384);
@@ -63276,7 +63349,7 @@ var init_brand_font = __esm({
63276
63349
  });
63277
63350
 
63278
63351
  // src/site-analyze/write-artifacts.ts
63279
- import { mkdirSync as mkdirSync31, writeFileSync as writeFileSync27 } from "node:fs";
63352
+ import { mkdirSync as mkdirSync31, writeFileSync as writeFileSync26 } from "node:fs";
63280
63353
  import { join as join63 } from "node:path";
63281
63354
  function attachVeloSources(book, sources, put) {
63282
63355
  const byPath = new Map((sources ?? []).map((s) => [s.path, s.text]));
@@ -63438,7 +63511,7 @@ async function writeBusinessBook(result, opts) {
63438
63511
  const put = (rel, body) => {
63439
63512
  const p = join63(out2, rel);
63440
63513
  mkdirSync31(join63(p, ".."), { recursive: true });
63441
- writeFileSync27(p, body);
63514
+ writeFileSync26(p, body);
63442
63515
  files.push(rel);
63443
63516
  };
63444
63517
  const book = result.book;
@@ -63531,7 +63604,7 @@ var init_customer_build = __esm({
63531
63604
  });
63532
63605
 
63533
63606
  // src/site-analyze/apply.ts
63534
- import { existsSync as existsSync65, readFileSync as readFileSync77, writeFileSync as writeFileSync28, copyFileSync as copyFileSync3, mkdirSync as mkdirSync32, realpathSync as realpathSync10, statSync as statSync18 } from "node:fs";
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";
63535
63608
  import { dirname as dirname36, isAbsolute as isAbsolute7, join as join64, resolve as resolve51, sep as sep17 } from "node:path";
63536
63609
  import { spawnSync as spawnSync4 } from "node:child_process";
63537
63610
  function unpairedRedirectNote(overlay) {
@@ -63683,10 +63756,10 @@ function buildMergedContract(options) {
63683
63756
  const body = `${JSON.stringify(merged, null, 2)}
63684
63757
  `;
63685
63758
  const path2 = join64(packageDir, "provisioning.contract.json");
63686
- writeFileSync28(path2, body);
63759
+ writeFileSync27(path2, body);
63687
63760
  const localPath = join64(appDir3, LOCAL_CONTRACT_REL);
63688
63761
  mkdirSync32(dirname36(localPath), { recursive: true });
63689
- writeFileSync28(localPath, body);
63762
+ writeFileSync27(localPath, body);
63690
63763
  staged.push(localPath);
63691
63764
  return { path: path2, staged };
63692
63765
  }
@@ -63785,10 +63858,10 @@ async function applyOverlays(input) {
63785
63858
  const cur = JSON.parse(readFileSync77(pkgProv, "utf8"));
63786
63859
  const merged = deepMerge4(cur, overlay);
63787
63860
  packageFaq = merged.faq;
63788
- writeFileSync28(pkgProv, JSON.stringify(merged, null, 2) + "\n");
63861
+ writeFileSync27(pkgProv, JSON.stringify(merged, null, 2) + "\n");
63789
63862
  wrote.push(pkgProv);
63790
63863
  } else {
63791
- writeFileSync28(pkgProv, JSON.stringify(overlay, null, 2) + "\n");
63864
+ writeFileSync27(pkgProv, JSON.stringify(overlay, null, 2) + "\n");
63792
63865
  wrote.push(pkgProv);
63793
63866
  }
63794
63867
  const pkgTokens = join64(cwd, "tokens.json");
@@ -63814,7 +63887,7 @@ async function applyOverlays(input) {
63814
63887
  if (primary && glow && typeof glow.color === "string") glow.color = primary;
63815
63888
  stripBrandFromIdiom(merged);
63816
63889
  }
63817
- writeFileSync28(pkgTokens, JSON.stringify(merged, null, 2) + "\n");
63890
+ writeFileSync27(pkgTokens, JSON.stringify(merged, null, 2) + "\n");
63818
63891
  wrote.push(pkgTokens);
63819
63892
  }
63820
63893
  const homeAction = homeRefreshActionId(shell);
@@ -63844,7 +63917,7 @@ async function applyOverlays(input) {
63844
63917
  }
63845
63918
  }
63846
63919
  if (next !== prev) {
63847
- writeFileSync28(homeSrc, next);
63920
+ writeFileSync27(homeSrc, next);
63848
63921
  wrote.push(homeSrc);
63849
63922
  }
63850
63923
  if (patched.unparsed) {
@@ -68600,7 +68673,7 @@ __export(site_create_exports, {
68600
68673
  runSiteList: () => runSiteList,
68601
68674
  writeProvisioningBinding: () => writeProvisioningBinding
68602
68675
  });
68603
- import { existsSync as existsSync66, readFileSync as readFileSync79, writeFileSync as writeFileSync29 } from "node:fs";
68676
+ import { existsSync as existsSync66, readFileSync as readFileSync79, writeFileSync as writeFileSync28 } from "node:fs";
68604
68677
  import { resolve as resolvePath2 } from "node:path";
68605
68678
  function oauthClientName(projectName) {
68606
68679
  return `${projectName} client`;
@@ -68749,7 +68822,7 @@ async function runSiteCreate(opts) {
68749
68822
  if (!wantOauthApp) return { ...base, nextSteps: nextSteps(metaSiteId) };
68750
68823
  return { ...base, clientId, redirectUri, nextSteps: nextSteps(metaSiteId, clientId) };
68751
68824
  }
68752
- function writeProvisioningBinding(target, binding2, fs = { existsSync: existsSync66, readFileSync: readFileSync79, writeFileSync: writeFileSync29 }) {
68825
+ function writeProvisioningBinding(target, binding2, fs = { existsSync: existsSync66, readFileSync: readFileSync79, writeFileSync: writeFileSync28 }) {
68753
68826
  const msid = binding2.siteId;
68754
68827
  const path2 = resolvePath2(target);
68755
68828
  const committed = committedShellProvisioning(path2);
@@ -69422,7 +69495,7 @@ var init_context = __esm({
69422
69495
  });
69423
69496
 
69424
69497
  // src/cli.ts
69425
- import { mkdtempSync as mkdtempSync4, readFileSync as readFileSync80, mkdirSync as mkdirSync33, writeFileSync as writeFileSync30, existsSync as existsSync67, rmSync as rmSync16, readdirSync as readdirSync31, statSync as statSync19, lstatSync as lstatSync14, accessSync, constants as constants2, renameSync as renameSync7, cpSync as cpSync3 } from "node:fs";
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";
69426
69499
  import { execFile, execFileSync as execFileSync7, spawnSync as spawnSync5, spawn as spawn7 } from "node:child_process";
69427
69500
  import { AsyncLocalStorage } from "node:async_hooks";
69428
69501
  import { createHash as createHash31, randomUUID as randomUUID7 } from "node:crypto";
@@ -71872,9 +71945,12 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
71872
71945
  file is not the plane's per-release archive.
71873
71946
  himi share --file <package.himi> [--expires-at <ISO>] | revoke --app <app> <shareId>
71874
71947
  publish a content-addressed package link for a released app
71875
- himi mcp [plugin --out <dir>] the himi tools as a local MCP server over stdio, for coding
71948
+ himi mcp [plugin --out <dir>] [--with-files]
71949
+ the himi tools as a local MCP server over stdio, for coding
71876
71950
  agents (Claude Code, Cursor, Codex, VS Code). \`himi init\`
71877
- 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).
71878
71954
  himi run [<package.himi|dir|url>] [--browser] [--watch [--idle-exit <min>]] [--lan] [--port <n>] [--chrome preview|app] [--screen <id>] [--surface <id>] [--no-open]
71879
71955
  serve a package on localhost + open it in a browser. No serve
71880
71956
  plane, no dev-server. With no argument it packs the current
@@ -72475,7 +72551,7 @@ function readUpdateCache(path2) {
72475
72551
  function writeUpdateCache(path2, cache) {
72476
72552
  try {
72477
72553
  mkdirSync33(dirname37(path2), { recursive: true, mode: 448 });
72478
- writeFileSync30(path2, JSON.stringify(cache, null, 2) + "\n", { mode: 384 });
72554
+ writeFileSync29(path2, JSON.stringify(cache, null, 2) + "\n", { mode: 384 });
72479
72555
  } catch {
72480
72556
  }
72481
72557
  }
@@ -72506,7 +72582,7 @@ function globalPrefixWritable() {
72506
72582
  const root = globalNodeModulesRoot();
72507
72583
  if (!root) return false;
72508
72584
  try {
72509
- accessSync(root, constants2.W_OK);
72585
+ accessSync(root, constants3.W_OK);
72510
72586
  return true;
72511
72587
  } catch {
72512
72588
  return false;
@@ -72617,7 +72693,7 @@ function acquireUpdateLock(cachePath, now, hooks = {}) {
72617
72693
  };
72618
72694
  const tryCreate = (path2) => {
72619
72695
  try {
72620
- writeFileSync30(path2, mine, { flag: "wx", mode: 384 });
72696
+ writeFileSync29(path2, mine, { flag: "wx", mode: 384 });
72621
72697
  return true;
72622
72698
  } catch {
72623
72699
  return false;
@@ -73205,7 +73281,7 @@ function executableOnPath(name, env = process.env) {
73205
73281
  const candidate = join65(dir, `${name}${suffix}`);
73206
73282
  try {
73207
73283
  if (!statSync19(candidate).isFile()) continue;
73208
- if (process.platform !== "win32") accessSync(candidate, constants2.X_OK);
73284
+ if (process.platform !== "win32") accessSync(candidate, constants3.X_OK);
73209
73285
  return true;
73210
73286
  } catch {
73211
73287
  }
@@ -73332,7 +73408,7 @@ function readConfigSource(dir) {
73332
73408
  }
73333
73409
  function writePngFile(path2, base64) {
73334
73410
  mkdirSync33(dirname37(path2), { recursive: true });
73335
- writeFileSync30(path2, Buffer.from(base64, "base64"));
73411
+ writeFileSync29(path2, Buffer.from(base64, "base64"));
73336
73412
  return path2;
73337
73413
  }
73338
73414
  async function seedAgentFilesFor(dir, flags, mode = "init") {
@@ -74955,7 +75031,7 @@ ${doNow.map((l) => ` ${l}`).join("\n")}`);
74955
75031
  const files = himiPluginFiles2(skills.skillMd);
74956
75032
  for (const f of files) {
74957
75033
  mkdirSync33(dirname37(join65(dir, f.path)), { recursive: true });
74958
- writeFileSync30(join65(dir, f.path), f.text);
75034
+ writeFileSync29(join65(dir, f.path), f.text);
74959
75035
  }
74960
75036
  print({ ok: true, dir, files: files.map((f) => f.path), next: [`claude plugin validate ${dir}`, `claude --plugin-dir ${dir}`] });
74961
75037
  return 0;
@@ -74981,7 +75057,7 @@ ${doNow.map((l) => ` ${l}`).join("\n")}`);
74981
75057
  const out2 = resolve53(str8(flags.out) ?? join65(dir, ".himi", "making-of.html"));
74982
75058
  const { html, plan } = buildReel2(dir);
74983
75059
  mkdirSync33(dirname37(out2), { recursive: true });
74984
- writeFileSync30(out2, html);
75060
+ writeFileSync29(out2, html);
74985
75061
  const { pathToFileURL: pathToFileURL9 } = await import("node:url");
74986
75062
  const url2 = pathToFileURL9(out2).href;
74987
75063
  const open2 = flags["no-open"] !== true;
@@ -77201,9 +77277,9 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
77201
77277
  const { describeChanges: describeChanges2, headline: headline2 } = await Promise.resolve().then(() => (init_build_changes(), build_changes_exports));
77202
77278
  const changes = describeChanges2(live2, join65(next, running.app), diff);
77203
77279
  const retired = `${live2}.retired-${Date.now()}`;
77204
- renameSync7(live2, retired);
77280
+ renameSync8(live2, retired);
77205
77281
  try {
77206
- renameSync7(join65(next, running.app), live2);
77282
+ renameSync8(join65(next, running.app), live2);
77207
77283
  } catch {
77208
77284
  cpSync3(join65(next, running.app), live2, { recursive: true });
77209
77285
  }
@@ -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, act only inside the workspace folder (checked on the real path, so a
87
- symlink cannot lead out of it), and never touch `.git`, `node_modules` or `dist`. When the host names
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.1116.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": "6d8f58d4b9f65728efd1d2728c9c1567bbe8ea1cb014bda9ee1afc50"
70
+ "falconPackageHash": "fc5b0cacba000b4c6e6a4a5002e19ab4d37d0560e6bd98aed8b49b13"
71
71
  }