@wix/himalaya-cli 0.1112.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
@@ -53869,8 +53869,25 @@ async function appDir2(args, deps) {
53869
53869
  const base = await deps.defaultDir();
53870
53870
  return typeof args.dir === "string" && args.dir ? resolve45(base, args.dir) : base;
53871
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
+ }
53872
53889
  async function requireLogin(deps) {
53873
- const who = await deps.runCli(["whoami"]);
53890
+ const who = await deps.runCli(["whoami", ...opt("--target", deps.login.target())]);
53874
53891
  if (who.code === 0) return null;
53875
53892
  return { content: [{ type: "text", text: LOGIN_HINT }, ...who.stderr.trim() ? [{ type: "text", text: who.stderr.trim() }] : []], isError: true };
53876
53893
  }
@@ -54066,12 +54083,34 @@ ${log}` : ""}`, true);
54066
54083
  description: "The identity the stored serve-plane credential carries (account, scope, expiry). Local \u2014 decodes the stored token, no network.",
54067
54084
  annotations: LEARN,
54068
54085
  inputSchema: z.object({ target: z.string().optional().describe("Serve target. Default: the CLI's default.") }),
54069
- 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
+ }
54070
54109
  },
54071
54110
  {
54072
54111
  name: "himi_push",
54073
54112
  title: "Publish a draft",
54074
- 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).",
54075
54114
  annotations: PUBLISH,
54076
54115
  inputSchema: z.object({
54077
54116
  dir: DIR,
@@ -54081,13 +54120,13 @@ ${log}` : ""}`, true);
54081
54120
  handler: async (a, d) => {
54082
54121
  const gate = await requireLogin(d);
54083
54122
  if (gate) return gate;
54084
- 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())]));
54085
54124
  }
54086
54125
  },
54087
54126
  {
54088
54127
  name: "himi_share",
54089
54128
  title: "Share a .himi package link",
54090
- 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).",
54091
54130
  annotations: PUBLISH,
54092
54131
  inputSchema: z.object({
54093
54132
  file: z.string().min(1).describe("Path to the .himi package (see `himi pack`)."),
@@ -54096,7 +54135,7 @@ ${log}` : ""}`, true);
54096
54135
  handler: async (a, d) => {
54097
54136
  const gate = await requireLogin(d);
54098
54137
  if (gate) return gate;
54099
- 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())]));
54100
54139
  }
54101
54140
  },
54102
54141
  {
@@ -54112,7 +54151,7 @@ ${log}` : ""}`, true);
54112
54151
  screen: z.string().optional(),
54113
54152
  surface: z.string().optional()
54114
54153
  }),
54115
- 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())]))
54116
54155
  }
54117
54156
  ];
54118
54157
  }
@@ -54132,7 +54171,82 @@ var init_tools = __esm({
54132
54171
  LOCAL_WRITE = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false };
54133
54172
  PUBLISH = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true };
54134
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.";
54135
- 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)]+/;
54136
54250
  }
54137
54251
  });
54138
54252
 
@@ -54197,7 +54311,8 @@ async function startMcpServer(opts) {
54197
54311
  status: (dir) => previewStatus(dir, previewDeps),
54198
54312
  stop: (dir) => stopPreview(dir, previewDeps)
54199
54313
  },
54200
- readFile: (p) => readFileSync59(p)
54314
+ readFile: (p) => readFileSync59(p),
54315
+ login: createLoginSession({ ...defaultLoginDeps, selfCommand: previewDeps.selfCommand, ...opts.login })
54201
54316
  };
54202
54317
  const closed = new Promise((resolve53) => {
54203
54318
  stdin.once("end", resolve53);
@@ -54219,6 +54334,7 @@ var init_server = __esm({
54219
54334
  init_cli_bridge();
54220
54335
  init_tools();
54221
54336
  init_preview();
54337
+ init_login();
54222
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`.";
54223
54339
  }
54224
54340
  });
@@ -71942,7 +72058,7 @@ var COMMANDS = [
71942
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>]" },
71943
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>" },
71944
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>]" },
71945
- { 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>]" },
71946
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]" },
71947
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]" },
71948
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.1112.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": "eddb135db9560051303f23b17115c9f9952f4d120580b7e776f0928d"
70
+ "falconPackageHash": "0577b42d47b1e8019d8e91a63ef760878990ad8500fc4c7deec84f83"
71
71
  }