@wix/himalaya-cli 0.1113.0 → 0.1114.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
@@ -24718,7 +24718,6 @@ function shouldRevealError(args) {
24718
24718
  var EMAIL_RE, URL_HOST_RE, INTEGER_RE, NUMBER_RE;
24719
24719
  var init_validation = __esm({
24720
24720
  "../../core/ts/src/validation.ts"() {
24721
- "use strict";
24722
24721
  EMAIL_RE = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;
24723
24722
  URL_HOST_RE = /^[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*(:[0-9]+)?$/;
24724
24723
  INTEGER_RE = /^[+-]?[0-9]+$/;
@@ -29119,7 +29118,6 @@ function validateWorkerSubtree(raw, slotId, policy, actionCatalog) {
29119
29118
  var WORKER_SUBTREE_CEILINGS, ID_PART, ACTION_ID;
29120
29119
  var init_worker_subtree = __esm({
29121
29120
  "../../core/ts/src/worker-subtree.ts"() {
29122
- "use strict";
29123
29121
  init_worker_subtree_safe_types_generated();
29124
29122
  WORKER_SUBTREE_CEILINGS = Object.freeze({
29125
29123
  maxEncodedBytes: 65536,
@@ -53871,8 +53869,25 @@ async function appDir2(args, deps) {
53871
53869
  const base = await deps.defaultDir();
53872
53870
  return typeof args.dir === "string" && args.dir ? resolve45(base, args.dir) : base;
53873
53871
  }
53872
+ function loginResult(s) {
53873
+ switch (s.state) {
53874
+ case "pending":
53875
+ return json3({
53876
+ ok: true,
53877
+ state: "pending",
53878
+ ...s.loginUrl ? { loginUrl: s.loginUrl } : {},
53879
+ message: "A browser window opened for the user to sign in to Wix. If it did not open, give them `loginUrl`. Once they have signed in, call himi_login again to confirm, or just retry the push."
53880
+ });
53881
+ case "signed_in":
53882
+ return json3({ ok: true, state: "signed_in", account: s.account, scope: s.scope, expiresAt: s.expiresAt });
53883
+ case "failed":
53884
+ return { ...json3({ ok: false, state: "failed", error: s.error, hint: "Call himi_login again to retry, or ask the user to run `himi login --browser` in a terminal." }), isError: true };
53885
+ default:
53886
+ return json3({ ok: true, state: "idle" });
53887
+ }
53888
+ }
53874
53889
  async function requireLogin(deps) {
53875
- const who = await deps.runCli(["whoami"]);
53890
+ const who = await deps.runCli(["whoami", ...opt("--target", deps.login.target())]);
53876
53891
  if (who.code === 0) return null;
53877
53892
  return { content: [{ type: "text", text: LOGIN_HINT }, ...who.stderr.trim() ? [{ type: "text", text: who.stderr.trim() }] : []], isError: true };
53878
53893
  }
@@ -54068,12 +54083,34 @@ ${log}` : ""}`, true);
54068
54083
  description: "The identity the stored serve-plane credential carries (account, scope, expiry). Local \u2014 decodes the stored token, no network.",
54069
54084
  annotations: LEARN,
54070
54085
  inputSchema: z.object({ target: z.string().optional().describe("Serve target. Default: the CLI's default.") }),
54071
- handler: async (a, d) => fromCli(await d.runCli(["whoami", ...opt("--target", a.target)]))
54086
+ handler: async (a, d) => fromCli(await d.runCli(["whoami", ...opt("--target", a.target ?? d.login.target())]))
54087
+ },
54088
+ {
54089
+ name: "himi_login",
54090
+ title: "Sign in to Wix",
54091
+ description: "Open a browser window where the USER signs in to Wix, so himi_push and himi_share can publish. Returns at once with the sign-in link; call again to see whether the sign-in finished. Already signed in: says who, and opens nothing unless `switchAccount` is true. This server never sees the credential \u2014 the sign-in stores a scoped token on this machine.",
54092
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
54093
+ inputSchema: z.object({
54094
+ target: z.string().optional().describe("Serve target. Default: the CLI's default."),
54095
+ switchAccount: z.boolean().optional().describe("Sign in again even though a credential is stored (e.g. to use another Wix account).")
54096
+ }),
54097
+ handler: async (a, d) => {
54098
+ const s = d.login.status();
54099
+ if (s.state === "pending") return loginResult(s);
54100
+ if (!a.switchAccount) {
54101
+ const who = await d.runCli(["whoami", ...opt("--target", a.target)]);
54102
+ if (who.code === 0) {
54103
+ d.login.adopt(typeof a.target === "string" && a.target ? a.target : void 0);
54104
+ return json3({ ok: true, state: "signed_in", ...who.json && typeof who.json === "object" ? who.json : {} });
54105
+ }
54106
+ }
54107
+ return loginResult(await d.login.start(typeof a.target === "string" && a.target ? a.target : void 0));
54108
+ }
54072
54109
  },
54073
54110
  {
54074
54111
  name: "himi_push",
54075
54112
  title: "Publish a draft",
54076
- description: "Lint, build and publish the app as a DRAFT release: owner-private, servable by preview link and in Himi Player, NOT live. Returns the preview links and the universal link that opens it in Himi Player. Never promotes \u2014 going live is a human command. Refuses on any validation error. Needs `himi login --browser` first.",
54113
+ description: "Lint, build and publish the app as a DRAFT release: owner-private, servable by preview link and in Himi Player, NOT live. Returns the preview links and the universal link that opens it in Himi Player. Never promotes \u2014 going live is a human command. Refuses on any validation error. Needs a sign-in first (himi_login).",
54077
54114
  annotations: PUBLISH,
54078
54115
  inputSchema: z.object({
54079
54116
  dir: DIR,
@@ -54083,13 +54120,13 @@ ${log}` : ""}`, true);
54083
54120
  handler: async (a, d) => {
54084
54121
  const gate = await requireLogin(d);
54085
54122
  if (gate) return gate;
54086
- return fromCli(await d.runCli(["push", "--content", await appDir2(a, d), "--draft", ...opt("--label", a.label), ...opt("--notes", a.notes)]));
54123
+ return fromCli(await d.runCli(["push", "--content", await appDir2(a, d), "--draft", ...opt("--label", a.label), ...opt("--notes", a.notes), ...opt("--target", d.login.target())]));
54087
54124
  }
54088
54125
  },
54089
54126
  {
54090
54127
  name: "himi_share",
54091
54128
  title: "Share a .himi package link",
54092
- description: "Publish a content-addressed share link for a validated `.himi` package of an already-published release. Needs `himi login --browser` first.",
54129
+ description: "Publish a content-addressed share link for a validated `.himi` package of an already-published release. Needs a sign-in first (himi_login).",
54093
54130
  annotations: PUBLISH,
54094
54131
  inputSchema: z.object({
54095
54132
  file: z.string().min(1).describe("Path to the .himi package (see `himi pack`)."),
@@ -54098,7 +54135,7 @@ ${log}` : ""}`, true);
54098
54135
  handler: async (a, d) => {
54099
54136
  const gate = await requireLogin(d);
54100
54137
  if (gate) return gate;
54101
- return fromCli(await d.runCli(["share", "--file", resolve45(await d.defaultDir(), String(a.file)), ...opt("--expires-at", a.expiresAt)]));
54138
+ return fromCli(await d.runCli(["share", "--file", resolve45(await d.defaultDir(), String(a.file)), ...opt("--expires-at", a.expiresAt), ...opt("--target", d.login.target())]));
54102
54139
  }
54103
54140
  },
54104
54141
  {
@@ -54114,7 +54151,7 @@ ${log}` : ""}`, true);
54114
54151
  screen: z.string().optional(),
54115
54152
  surface: z.string().optional()
54116
54153
  }),
54117
- handler: async (a, d) => fromCli(await d.runCli(["preview", "--app", String(a.app), "--release", String(a.release), ...opt("--screen", a.screen), ...opt("--surface", a.surface)]))
54154
+ handler: async (a, d) => fromCli(await d.runCli(["preview", "--app", String(a.app), "--release", String(a.release), ...opt("--screen", a.screen), ...opt("--surface", a.surface), ...opt("--target", d.login.target())]))
54118
54155
  }
54119
54156
  ];
54120
54157
  }
@@ -54134,7 +54171,82 @@ var init_tools = __esm({
54134
54171
  LOCAL_WRITE = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false };
54135
54172
  PUBLISH = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true };
54136
54173
  PREVIEW_HINT = "The preview is already running: open `url` in your built-in browser \u2014 Claude Code desktop's browser pane, Cursor's Browser tool, the Browser in Codex inside the ChatGPT desktop app, VS Code's integrated browser. Do not also start the `himi-preview` launch configuration: it serves the same port, and is the way to start a preview only when none is running. A terminal-only agent should show the owner `himi_render` PNGs instead. `board` (when present) is the build board on the same origin.";
54137
- LOGIN_HINT = "Not signed in to the himi serve plane. Ask the user to run `himi login --browser` in a terminal (this server never handles credentials), then retry.";
54174
+ LOGIN_HINT = "Not signed in to the himi serve plane. Call himi_login: it opens a browser window where the user signs in to Wix (this server never sees the credential). A user with a terminal can run `himi login --browser` instead. Then retry.";
54175
+ }
54176
+ });
54177
+
54178
+ // src/mcp/login.ts
54179
+ import { spawn as nodeSpawn2 } from "node:child_process";
54180
+ function createLoginSession(deps) {
54181
+ let current = { state: "idle" };
54182
+ let signedInTarget;
54183
+ return {
54184
+ status: () => current,
54185
+ target: () => signedInTarget,
54186
+ adopt: (target) => {
54187
+ signedInTarget = target;
54188
+ },
54189
+ start(target) {
54190
+ if (current.state === "pending") return Promise.resolve(current);
54191
+ const self = deps.selfCommand();
54192
+ const args = [...self.args, "login", "--browser", "--skip-version-check", ...target ? ["--target", target] : []];
54193
+ const pending = { state: "pending", startedAt: deps.now() };
54194
+ current = pending;
54195
+ const out2 = [];
54196
+ const err = [];
54197
+ return new Promise((resolve53) => {
54198
+ let answered = false;
54199
+ const answer = () => {
54200
+ if (answered) return;
54201
+ answered = true;
54202
+ resolve53(current);
54203
+ };
54204
+ let child;
54205
+ try {
54206
+ child = deps.spawn(self.cmd, args, { stdio: ["ignore", "pipe", "pipe"], env: { ...process.env, HIMI_NO_UPDATE_CHECK: "1" } });
54207
+ } catch (e) {
54208
+ current = { state: "failed", error: `could not start himi login: ${e.message}` };
54209
+ answer();
54210
+ return;
54211
+ }
54212
+ child.stdout?.on("data", (c) => out2.push(String(c)));
54213
+ child.stderr?.on("data", (c) => {
54214
+ err.push(String(c));
54215
+ const link = LINK2.exec(err.join(""))?.[0];
54216
+ if (link && !pending.loginUrl && current === pending) {
54217
+ pending.loginUrl = link;
54218
+ answer();
54219
+ }
54220
+ });
54221
+ child.once("error", (e) => {
54222
+ current = { state: "failed", error: `could not start himi login: ${e.message}` };
54223
+ answer();
54224
+ });
54225
+ child.once("exit", (code) => {
54226
+ if (code === 0) {
54227
+ const r = lastJson([out2.join("")]) ?? {};
54228
+ current = { state: "signed_in", account: r.sub, scope: r.scope, expiresAt: r.exp };
54229
+ signedInTarget = target;
54230
+ } else {
54231
+ const e = lastJson(err.join("").split("\n"));
54232
+ current = { state: "failed", error: e?.error ?? (err.join("").trim().slice(-500) || `himi login exited ${code}`) };
54233
+ }
54234
+ answer();
54235
+ });
54236
+ setTimeout(answer, deps.linkWaitMs ?? 5e3).unref?.();
54237
+ });
54238
+ }
54239
+ };
54240
+ }
54241
+ var defaultLoginDeps, LINK2;
54242
+ var init_login = __esm({
54243
+ "src/mcp/login.ts"() {
54244
+ init_cli_bridge();
54245
+ defaultLoginDeps = {
54246
+ spawn: (cmd, args, opts) => nodeSpawn2(cmd, args, opts),
54247
+ now: () => Date.now()
54248
+ };
54249
+ LINK2 = /https?:\/\/[^\s)]+/;
54138
54250
  }
54139
54251
  });
54140
54252
 
@@ -54199,7 +54311,8 @@ async function startMcpServer(opts) {
54199
54311
  status: (dir) => previewStatus(dir, previewDeps),
54200
54312
  stop: (dir) => stopPreview(dir, previewDeps)
54201
54313
  },
54202
- readFile: (p) => readFileSync59(p)
54314
+ readFile: (p) => readFileSync59(p),
54315
+ login: createLoginSession({ ...defaultLoginDeps, selfCommand: previewDeps.selfCommand, ...opts.login })
54203
54316
  };
54204
54317
  const closed = new Promise((resolve53) => {
54205
54318
  stdin.once("end", resolve53);
@@ -54221,6 +54334,7 @@ var init_server = __esm({
54221
54334
  init_cli_bridge();
54222
54335
  init_tools();
54223
54336
  init_preview();
54337
+ init_login();
54224
54338
  SERVER_INSTRUCTIONS = "himi builds native mobile apps (iOS + Android + web) from a content package: SDUI descriptors plus Tier-5 JS workers. The loop: himi_docs (read `headless-loop` first) \u2192 edit files \u2192 himi_validate \u2192 himi_test \u2192 himi_render (show the owner the PNG) \u2192 himi_preview_start (live preview in your IDE's browser) \u2192 himi_push (a DRAFT the owner opens in Himi Player; never live). Tools act on the workspace root unless given `dir`.";
54225
54339
  }
54226
54340
  });
@@ -71944,7 +72058,7 @@ var COMMANDS = [
71944
72058
  { name: "pull", summary: "restore an app's SOURCE from the serve plane into an editable content package \u2014 the other half of `himi push`, which archives the content package on every publish. Owner or editor only: a viewer may download the RUNNING app (`/v1/owner/package`), not the codebase it was built from. A PAST release is restorable, not only the live one \u2014 the archive is content-addressed per release, so it has none of the live-single-slot problem that makes `himi pack` current-release-only. Two limits: pruning keeps the current release plus the newest ~20 ledger entries, and a release id identifies OTA content rather than source, so a source-only change republishes under the same id and replaces that id's archive. A release published before source archiving shipped, or pushed with `--no-source`, answers `app_source_not_found` and says to push again. `--name` re-substitutes the app identity through the same machinery `himi init --template` uses, so the restored tree is a NEW app rather than a second copy of the same one, and a name that also appears inside a screen id (`feed` in `feed_home`) is left alone. A NON-EMPTY destination is refused unless you say which you mean: `--force` merges and reports \u2014 it writes the archive over what is there, keeps files the release does not carry, and NAMES every one of them as `left: [...]` so a tree that matches no release is never silent (#2437); `--replace` restores exactly what was archived, removing those files first (`removed: [...]`) and implying `--force`. `--replace` refuses a directory holding no `himalaya.content.json`, so a mistyped `--out` cannot clear an unrelated tree, and the only paths it can remove are the ones a push would have archived \u2014 `.git/`, `node_modules/`, `dist/`, `.env*` and key material are out of its reach by construction.", required: "<app> [--release <id>] [--out <dir>] [--name <new>] [--force|--replace] [--target <t>]" },
71945
72059
  { name: "share", summary: "publish or revoke a content-addressed `.himi` share link. The package must be a validated archive for an already-published release; the resulting https URL is safe to QR, copy, or open in Himi Player.", required: "--file <package.himi> [--expires-at <ISO>] | revoke --app <app> <shareId>" },
71946
72060
  { name: "run", summary: "serve a .himi package on localhost and open it in a browser \u2014 no serve plane, no dev-server, no setup. Given no argument it builds the current content package first, so `himi run` is the inner loop: edit, run, look at it. The SPA and /v1 are served from ONE origin because Tier-5 bundle fetches on web are same-origin-only. --chrome preview frames the app in a device chassis; --chrome app serves it as a plain web app. Blocks until Ctrl-C, like any dev server. --browser runs the whole plane INSIDE the tab (service worker + in-memory store) so no app code executes in this process or on any pod. --lan also serves it to the local network and prints a QR, so a phone on the same Wi-Fi opens it as the real app, full screen. --watch keeps the preview LIVE: every save in the content package rebuilds it and repaints the open tab with only what changed (the dev-server's hot-reload events); a build that fails keeps the last good preview on screen and says why. --idle-exit <min> (with --watch only) stops the watch once that many minutes pass with no source save, printing `{\"watch\":\"idle_exit\"}` and removing its .himi/run.json on the way out \u2014 the watch `himi init` starts passes 30; one started by hand runs until Ctrl-C. Stopping (Ctrl-C, SIGTERM, SIGHUP or the idle exit) removes the run's own .himi/run.json. Any `himi run` of a content package (plain, --watch or --browser) frames its preview on the build board (`himi studio`) while it serves, and all but --browser also serve that board at /__himi/studio/ on the preview's own origin. Its .himi/run.json (pid, watching) is also how `himi render` and `himi test` tell a live --watch is running: while none is, they add a `reminder` naming `himi run --watch --content <dir> &`, and while one is (the one `himi init` started, usually, whose .himi/watch.json vouches for it until it serves) the reminder names its url and board instead (either at most once every ten minutes per app) \u2014 first in `next[]` and again as the envelope's LAST key (the last line of `himi test`'s plain report), so an output read with `tail` still carries it.", required: "[<package.himi|dir|url>] [--browser] [--watch] [--idle-exit <min>] [--lan] [--content <dir>] [--port <n>] [--chrome preview|app] [--screen <id>] [--surface <id>] [--no-open] [--spa <url>]" },
71947
- { name: "mcp", summary: "run the himi tools as a local Model Context Protocol server over stdio, for coding agents \u2014 Claude Code, Cursor, Codex, VS Code. Tools: docs/components/sdk/icons, init, build, validate, test (hermetic), render (returns the PNG), progress, a live preview (start/status/stop a `himi run --watch`), whoami, and DRAFT-only push/share/preview links \u2014 never promote, login, dist or secrets. Each tool runs the same command in-process on the content package on disk (default: the host's workspace root). `himi init` and `himi agent-init` write the config for every host (.mcp.json, .cursor/mcp.json, .vscode/mcp.json, .codex/config.toml when absent) plus a `himi-preview` launch configuration (.claude/launch.json) that opens the live preview in Claude Code desktop's browser pane. Never self-updates (npx already runs the current CLI). Blocks until the host closes stdin. `himi mcp plugin --out <dir>` writes the Claude Code plugin (the customer skill + this server) as a folder \u2014 the same plugin the serve plane's marketplace hands out at /.claude-plugin/marketplace.json.", required: "[plugin --out <dir>]" },
72061
+ { name: "mcp", summary: "run the himi tools as a local Model Context Protocol server over stdio, for coding agents \u2014 Claude Code, Cursor, Codex, VS Code. Tools: docs/components/sdk/icons, init, build, validate, test (hermetic), render (returns the PNG), progress, a live preview (start/status/stop a `himi run --watch`), whoami, login (opens the browser sign-in for the user; the credential goes to a child `himi login` and onto disk, never through the server), and DRAFT-only push/share/preview links \u2014 never promote, dist or secrets. A sign-in's target is the one push/share then use. Each tool runs the same command in-process on the content package on disk (default: the host's workspace root). `himi init` and `himi agent-init` write the config for every host (.mcp.json, .cursor/mcp.json, .vscode/mcp.json, .codex/config.toml when absent) plus a `himi-preview` launch configuration (.claude/launch.json) that opens the live preview in Claude Code desktop's browser pane. Never self-updates (npx already runs the current CLI). Blocks until the host closes stdin. `himi mcp plugin --out <dir>` writes the Claude Code plugin (the customer skill + this server) as a folder \u2014 the same plugin the serve plane's marketplace hands out at /.claude-plugin/marketplace.json.", required: "[plugin --out <dir>]" },
71948
72062
  { name: "preview", summary: "print the shareable web URL + himi:// deep link + agent /v1/preview JSON URLs for an app+release; --surface frames the web preview as any catalog surface (?surface=)", required: "--app --release [--screen] [--surface]" },
71949
72063
  { name: "surfaces", summary: "the surface catalog vs this app's envelope (platforms.json): targeted surfaces, the opt-in key for the rest, per-surface preview links with --app --release, and which widget families the package produces with --measure-widgets (which runs its built bundles)", required: "[--content <dir>] [--app --release] [--measure-widgets]" },
71950
72064
  { name: "context", summary: "fetch the agent preview envelope (GET /v1/preview/a/<app>) \u2014 ok/issues/screens/links; exit 1 if ok:false", required: "--app --release [--screen] [--include telemetry]" },
@@ -74,16 +74,19 @@ runs a session with it.
74
74
  | `himi_progress` | Read or add to the build journal the owner follows. |
75
75
  | `himi_preview_start` / `_status` / `_stop` | The live preview — see below. |
76
76
  | `himi_whoami` | Which account the stored sign-in belongs to. |
77
+ | `himi_login` | Open a browser window where the user signs in to Wix, so publishing works with no terminal. It returns the sign-in link at once; call it again to see whether the sign-in finished. |
77
78
  | `himi_push` | Publish a **draft**: private to the owner, viewable by preview link and in Himi Player, not live. |
78
79
  | `himi_share`, `himi_preview_links` | Share links for a published release. |
79
80
 
80
81
  Each tool acts on the agent's workspace folder unless it is given a `dir`.
81
82
 
82
- **What the tools will not do:** sign in, make a release live, build store binaries, touch secrets,
83
- write to a site's CMS, or create a Wix site. Those spend money, handle credentials, or change what
84
- real users see, so they stay commands a person runs. Publishing needs a sign-in first — run
85
- `himi login --browser` in a terminal; the server never asks for or handles a credential. A tool
86
- input that starts with `-` is refused, so nothing can be passed through to a flag no tool offers.
83
+ **What the tools will not do:** make a release live, build store binaries, touch secrets, write to a
84
+ site's CMS, or create a Wix site. Those spend money, handle credentials, or change what real users
85
+ see, so they stay commands a person runs. Publishing needs a sign-in first: `himi_login` opens the
86
+ browser sign-in for the user, or run `himi login --browser` in a terminal. Either way the server
87
+ never asks for or sees a credential — the browser hands a scoped token to a separate `himi login`
88
+ process, which stores it on this machine. A tool input that starts with `-` is refused, so nothing
89
+ can be passed through to a flag no tool offers.
87
90
 
88
91
  ## See the app inside your IDE
89
92
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.1113.0",
3
+ "version": "0.1114.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": "3271c06d75a812e01e41d371fafbd76ca27b6b97a36f6bb255c56d91"
70
+ "falconPackageHash": "0577b42d47b1e8019d8e91a63ef760878990ad8500fc4c7deec84f83"
71
71
  }