@mesh-tech/mesh-cli 0.30.0 → 0.31.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.
@@ -1,4 +1,4 @@
1
1
  {
2
- "commit": "a5fdbfed6",
3
- "builtAt": "2026-09-18T19:56:52.850Z"
2
+ "commit": "68b5be22a",
3
+ "builtAt": "2026-09-21T18:16:49.857Z"
4
4
  }
@@ -108,9 +108,9 @@ export const APP_CHECK_RULES = {
108
108
  },
109
109
  DOCS_SITE_PUBLIC: {
110
110
  gate: "0.3",
111
- title: "Docs site read through the Hub, not on a hostname of its own",
111
+ title: "Docs site published through the Hub docs door",
112
112
  severity: "advisory",
113
- remediation: "drop the docs hostname — a docs site is read inside the Hub through the docs door, which signs every reader in: on an API surface remove docs.ingress (docs.auth, docs.basePath and docs.hubDoor are retired with it); on a mesh.apps.ApiDocs remove the ingress (and its auth / authentication) and declare the API as a mesh.apps.AppApiSurface whose docs block builds the site",
113
+ remediation: "publish the site through the Hub docs door — declare the API as a mesh.apps.AppApiSurface whose docs block builds this same site (docs: { spec, pages }), declare its readers' role on the ZitadelAppIdentity and grant it from the Hub's Access tab; then drop the hostname (docs.ingress on an API surface, where docs.auth, docs.basePath and docs.hubDoor do nothing either; the ingress with its auth and authentication on a mesh.apps.ApiDocs). The public shape keeps deploying until you do",
114
114
  scope: "app",
115
115
  },
116
116
  DOCS_AUDIENCE_IMPLICIT: {
@@ -509,7 +509,7 @@ export function docsSiteFindings(text) {
509
509
  findings.push({
510
510
  code: "DOCS_SITE_PUBLIC",
511
511
  line: decl.line,
512
- why: `mesh.apps.${decl.ctor} declares a docs site with ${named} — retired: a docs site has no hostname, no sign-in and no door switch of its own; it is read inside the Hub through the docs door, and the primitive ignores ${retired.length > 1 ? "them" : "it"}`,
512
+ why: `mesh.apps.${decl.ctor} declares a docs site with ${named} — deprecated: the Hub docs door is the path this site is published on, and it is already built for it (no hostname, no sign-in and no door switch of its own), so the primitive ignores ${retired.length > 1 ? "them" : "it"}`,
513
513
  });
514
514
  }
515
515
  const kindStated = decl.ctor === "AppApiSurface" || decl.ctor === "VendorApiSurface" || args.has("kind");
@@ -545,7 +545,7 @@ export function docsSiteFindings(text) {
545
545
  findings.push({
546
546
  code: "DOCS_SITE_PUBLIC",
547
547
  line: decl.line,
548
- why: `mesh.apps.ApiDocs is published on an ingress — a docs site has no hostname of its own; it is read inside the Hub through the docs door of the API surface that declares it, and the ingress is retired (it still deploys, with a preview warning, until a coming minor release refuses it)`,
548
+ why: `mesh.apps.ApiDocs is published on an ingress — deprecated: the Hub docs door is the path a docs site is published on, and it opens for the API surface that declares the site. The hostname still deploys, with a preview warning, as the compatibility path`,
549
549
  });
550
550
  }
551
551
  return findings;
@@ -20,10 +20,14 @@ Every mesh app follows 5 steps in `index.ts`:
20
20
 
21
21
  `api/openapi.json` is the API's OpenAPI document and `docs/pages/` holds its
22
22
  guides; `index.ts` declares a `mesh.apps.ApiDocs` site from both, reachable
23
- under `mesh dev`. A docs site has no hostname of its own — it is read inside
24
- the Hub. To publish it, declare the API as a `mesh.apps.AppApiSurface` whose
25
- `docs` block builds the site; the Hub serves it through its docs door to the
26
- readers the app's `docs` role admits. A site declared on a
23
+ under `mesh dev`. **The Hub docs door is the path a docs site is published
24
+ on**: declare the API as a `mesh.apps.AppApiSurface` whose `docs` block builds
25
+ the site, and the Hub serves it through that door to the readers the app's
26
+ `docs` role admits. Giving the site a hostname of its own instead — `ingress`,
27
+ with the `auth` proxy and the in-page `authentication` sign-in — is the
28
+ deprecated public shape: it still deploys, as the compatibility path for the
29
+ sites that are public today, and warns at every `pulumi preview`. Nothing here
30
+ writes one, and a new app has no reason to. A site declared on a
27
31
  `mesh.apps.VendorApiSurface` (a third-party core's raw reference) is read
28
32
  through the same door by holders of the Hub's `vendor-docs` add-on only —
29
33
  never by a partner — and declaring `audience: { roles: [] }` on one fails
@@ -107,16 +107,20 @@ const worker = new mesh.apps.TemporalWorker("worker", {
107
107
  // Docs — the API's reference site, built by the platform from the spec and
108
108
  // the guides in docs/pages/ (this README is its landing page — `landingPage`
109
109
  // names another file, or `false` — and every guide is listed after it, in
110
- // path order). A docs site has no hostname of its own: here it
111
- // is reachable under `mesh dev`, and it is published by being read inside
112
- // the Hub. To publish it, declare the API as a `mesh.apps.AppApiSurface`
113
- // whose `docs` block builds this same site (`docs: { spec, pages }`) — the
114
- // Hub then serves it through its docs door. Its readers hold this app's
115
- // `docs` project role: declare the role on the ZitadelAppIdentity and grant
116
- // it from Access → People; that same role is what the Hub reads when a
117
- // partner holding the Hub's `docs:<tenant>` role opens this app's Docs tab,
118
- // and the app then appears in their Apps list. `mesh app check` flags a docs
119
- // site put on an ingress instead (`DOCS_SITE_PUBLIC`). Declaring `actAs: true`
110
+ // path order). The Hub docs door is the path a docs site is published on:
111
+ // here the site is reachable under `mesh dev`, and to publish it you declare
112
+ // the API as a `mesh.apps.AppApiSurface` whose `docs` block builds this same
113
+ // site (`docs: { spec, pages }`) — the Hub then serves it through that door.
114
+ // A site given a hostname of its own instead (`ingress`, with the `auth`
115
+ // proxy and the in-page `authentication` sign-in) is the deprecated public
116
+ // shape: it still deploys, for the sites that are public today, and warns at
117
+ // every preview. This declaration names none of it, and a new app needs
118
+ // none. The site's readers hold this app's `docs` project role: declare the
119
+ // role on the ZitadelAppIdentity and grant it from Access → People; that same
120
+ // role is what the Hub reads when a partner holding the Hub's `docs:<tenant>`
121
+ // role opens this app's Docs tab, and the app then appears in their Apps list.
122
+ // `mesh app check` flags a docs site put on an ingress instead
123
+ // (`DOCS_SITE_PUBLIC`). Declaring `actAs: true`
120
124
  // on that same surface opens the Hub's act-as playground: an operator picks
121
125
  // one of this app's API keys on the Docs tab and the Hub executes the request
122
126
  // against the deployed API as that key, over the cluster network, with a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mesh-tech/mesh-cli",
3
- "version": "0.30.0",
3
+ "version": "0.31.0",
4
4
  "description": "CLI for Mesh platform development utilities",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -42,7 +42,7 @@
42
42
  },
43
43
  "devDependencies": {
44
44
  "@mesh-tech/agent-contracts": "0.2.0",
45
- "@mesh-tech/tsconfig": "2.21.0",
45
+ "@mesh-tech/tsconfig": "2.23.0",
46
46
  "@mesh-tech/workflow-viz": "0.3.0",
47
47
  "@tanstack/intent": "^0.3.6",
48
48
  "@types/node": "^20.0.0",
@@ -97,7 +97,7 @@ Top-level commands (each `register*` is wired in `src/program.ts`, which `bin/me
97
97
  | `mesh start` / `mesh stop` / `mesh status` | — | Full-local Mesh platform via docker compose (project `mesh-local`, assets ship in the CLI package): Postgres, Temporal (+UI), Zitadel, SpiceDB, ministack (local AWS fabric: SSM/Secrets/S3 registry; also seeds a general-purpose `mesh-local-data` bucket with sample data — see `docs/guides/local-dagster.md`), memcached, OpenSearch, a **local mailbox** (Mailpit — SMTP :1025 wired into Zitadel, inbox at http://localhost:8025, so user-activation and password-reset mail is testable locally instead of vanishing), **and the hosted observability backends** (OTel collector + Loki + Tempo + Prometheus — the Hub's logs/traces/metrics views run unchanged). Seeds tenant `local`/env `dev` on first boot and reconciles every Zitadel-provisioned app tenant into the registry. **No AWS creds, VPN, or Pulumi state needed — Docker only.** The **Hub is included by default** — three overlay services, `hub-api` + `hub-ui` + `hub-auth` (an oauth2-proxy; it is what binds the front-door port and proxies to `hub-ui`, so front-door 401s/redirect loops are in ITS logs, not `hub-api`'s) — the front door at http://localhost:9000 — `MESH_HUB_PORT=<port>` publishes it elsewhere when 9000 is taken; export it for `mesh status` too (images build once from the published @mesh-tech/hub tarball; needs `mesh registry login` the first time; `--no-hub` opts out). Auth contract: `--with-hub` refreshes to the latest published hub after preflighting the CodeArtifact token — with an expired/missing token it falls back to the already-built local images (warn + fixing command), or fails fast naming `mesh registry login --profile mesh-dev` when none exist; the default (no-flag) path with an expired/missing token starts hub-less and names the same fix. `--hub-from-source` sidesteps the registry entirely — it builds the Hub images from THIS checkout's `apps/hub`, which is the only way to run an unpublished Hub change in the containerized stack. `--with-idps` adds two mock identity providers (SAML + OIDC) and federates the local Zitadel with BOTH, which is how enterprise SSO gets exercised without a cloud tenant — the two-provider shape is deliberate, since the instance login policy carries a LIST of IdPs and the failure worth catching is a second federation evicting the first (`docs/guides/local-federation.md`). Its two host ports (4569 SAML, 5556 OIDC — 5556 is Dex's own default) join the start-up port preflight only when the flag is passed, and `mesh status` lists both mocks once they are running. Non-TTY runs (CI, agents, piped output) get an `[HH:MM:SS]` prefix on every step line plus a heartbeat during long fetches/builds — `MESH_LOG_TIMESTAMPS=1/0` forces the prefix on/off (default: on when stderr is not a TTY). | `start`: `--no-seed`, `--no-hub`, `--with-hub`, `--hub-from-source`, `--with-idps`, `--takeover`, `--skip-port-check`; `stop`: `--destroy` (drops volumes/seeds), `--force`; `status`: `--json` |
98
98
  | `mesh hub` | `dev` | One-command local Hub over a running dev-local stack. Launches the CURRENT-CODE Hub (apps/hub api+ui from a mesh-platform checkout) pointed at an existing `mesh dev` session — auto-assembles `HUB_TENANT`/`HUB_SCOPE_ENV`/`HUB_SCOPE_TENANTS` (session platform context), `TEMPORAL_ADDRESS` (session tunnel), `AWS_*` (session scratch profile), `DEV_USER_*`+`DEV_USER_TOKEN_URL` (session dev identity; starts its own token-server if the session predates it, so tokens stay fresh past ~1h). Refuses to launch with an empty tenant scope (which the Hub renders as silently blank). Runs in tmux session `mesh-hub-dev`. | `dev`: `--session <name>`, `--tenants <csv>`, `--port` (or `MESH_HUB_DEV_PORT`, default 9000; the containerized `mesh start` Hub uses the separate `MESH_HUB_PORT`), `--api-port <3002>`, `--platform-dir <dir>` (or `MESH_PLATFORM_DIR`), `--print-env`, `--kill` |
99
99
  | `mesh create-app` | — | Scaffold a tenant app. Composable mode (primitives) or legacy template mode. Interactive when TTY + missing flags. Run at the root of an **empty** `{tenant}-mesh-apps` git clone (a `.git` with no `package.json`/`apps/`/`tenants/` and no enclosing pnpm workspace), it first generates the repo-level shape — `pnpm-workspace.yaml` over `apps/*`, private root `package.json`, scoped-registry `.npmrc`, base `tsconfig.json`, `.gitignore`, `README.md`, `apps/` — then scaffolds the app. Never overwrites an existing file. | `--tenant`, `--name`, `--primitives <csv>` (`service,database,temporal,bucket`), `--template <workflow\|api-auth\|api-role-gating\|external-service>`, `--test` (writes to `tests/tenants/`) |
100
- | `mesh app` | `check [apps...]` | Checks tenant apps against the **Mesh app contract** (`@mesh-tech/app-kit#apps` → `references/app-contract.md`): a UI is its own Service (`UI_IN_API`, `NO_UI_SERVICE`), the app registers with the Hub (`NO_REGISTER`), sign-in goes through the platform proxy (`CUSTOM_SESSION_AUTH`), logs go through `@mesh-tech/logger` (`NON_PLATFORM_LOGGER`), and people/roles/keys are surfaced through the Hub (`NO_AUTHZ_POINTER`, `METADATA_UNPUBLISHED`, `IAC_GRANTS`, `PASSWORD_STORE`, `EMAIL_ALLOWLIST`, `USERS_TABLE`), and a docs site is read through the Hub rather than on a hostname of its own, with its readers stated and grantable (`DOCS_SITE_PUBLIC` — advisory while the hostname is deprecated — `DOCS_AUDIENCE_IMPLICIT`, `DOCS_AUDIENCE_UNGRANTABLE`, `DOCS_VENDOR_AUDIENCE_PARTNER` — a vendor surface's reference is never partner-readable); a tenant-local auth lib is advisory (`LOCAL_AUTH_LIB`), as is a pointer that names its own Zitadel project instead of binding the env with `zitadelAppProjectId` (`POINTER_PROJECT_OVERRIDE`). Gate 0.6 additionally resolves the Pulumi program's imports against the installed tree (`UNRESOLVABLE_IMPORT`) — an optional peer nothing declares installs silently and `skipLibCheck` hides it, so the break otherwise waits for `mesh deploy`; with no `node_modules` the gate reports itself not checked rather than met. One check per code on the `mesh dev doctor` engine; exit 1 on any BLOCK. The SAME gate `mesh create-app` runs after scaffolding, tenant CI runs beside `mesh skills sync --check`, and the platform reviewer runs on a PR. Default: every `apps/*` with a Pulumi program. | `[apps...]` (repo-relative dirs), `--root <dir>`, `--json`, `--verbose` |
100
+ | `mesh app` | `check [apps...]` | Checks tenant apps against the **Mesh app contract** (`@mesh-tech/app-kit#apps` → `references/app-contract.md`): a UI is its own Service (`UI_IN_API`, `NO_UI_SERVICE`), the app registers with the Hub (`NO_REGISTER`), sign-in goes through the platform proxy (`CUSTOM_SESSION_AUTH`), logs go through `@mesh-tech/logger` (`NON_PLATFORM_LOGGER`), and people/roles/keys are surfaced through the Hub (`NO_AUTHZ_POINTER`, `METADATA_UNPUBLISHED`, `IAC_GRANTS`, `PASSWORD_STORE`, `EMAIL_ALLOWLIST`, `USERS_TABLE`), and a docs site is published through the Hub docs door rather than on a hostname of its own, with its readers stated and grantable (`DOCS_SITE_PUBLIC` — advisory: the public hostname is the deprecated compatibility path and keeps deploying, so the finding names the door instead — `DOCS_AUDIENCE_IMPLICIT`, `DOCS_AUDIENCE_UNGRANTABLE`, `DOCS_VENDOR_AUDIENCE_PARTNER` — a vendor surface's reference is never partner-readable); a tenant-local auth lib is advisory (`LOCAL_AUTH_LIB`), as is a pointer that names its own Zitadel project instead of binding the env with `zitadelAppProjectId` (`POINTER_PROJECT_OVERRIDE`). Gate 0.6 additionally resolves the Pulumi program's imports against the installed tree (`UNRESOLVABLE_IMPORT`) — an optional peer nothing declares installs silently and `skipLibCheck` hides it, so the break otherwise waits for `mesh deploy`; with no `node_modules` the gate reports itself not checked rather than met. One check per code on the `mesh dev doctor` engine; exit 1 on any BLOCK. The SAME gate `mesh create-app` runs after scaffolding, tenant CI runs beside `mesh skills sync --check`, and the platform reviewer runs on a PR. Default: every `apps/*` with a Pulumi program. | `[apps...]` (repo-relative dirs), `--root <dir>`, `--json`, `--verbose` |
101
101
  | `mesh init` | (bare = wizard), `app-tenant`, `platform <tenant>` | **Bare `mesh init` is the guided setup wizard**: tenant (pre-filled from `mesh.json` / Pulumi `mesh:tenant`), **where the tenant runs — local-only, a deployed platform, or NOTHING YET (scaffold one; §1c)**, registry access (in-process `mesh registry login`; a 403 marks the step ✘ and the wizard continues), platform sign-in on the deployed branch only, repo bootstrap (empty clone, or an empty non-git folder the wizard offers to `git init`) or shape check, skills sync; writes `mesh.json` (`{tenant, platform}`) so `create-app` / `mesh dev` default `--tenant`; flags `--tenant --local\|--platform <env>\|--new-platform [env] --device --profile --skip-repo --yes --json`, plus the scaffold's own under `--new-platform` (`--region --domain --state-bucket --create-state-bucket/--no-create-state-bucket --dir`); exit 1 if any step failed, 130 on Ctrl-C. `platform <tenant>` **prompts** on a TTY for env/region/profile/state bucket (defaults on each) and for `--domain`, which has **no** default because a placeholder scaffolds an unvalidatable zone; it offers to create the Pulumi state bucket (versioned) and prints the deploy sequence including the manual NS delegation. `app-tenant` is the repo doctor (A1–A3): read-only check pipeline — CLI auth, registry access, public npm not hijacked, platform reachable (Hub API), app-tenant registration, deployer role, repo shape, agent skills — each failure names the exact `mesh` fix. **Registry access** is a live probe of the CodeArtifact token, not a grep of `~/.npmrc`: an expired token `fail`s (it used to `pass`), an unreachable registry `warn`s (a network fault is not an auth fault). **Public npm not hijacked** fails when `~/.npmrc` carries an unscoped `registry=<codeartifact>` line, which makes CodeArtifact your default registry — one `mesh registry login` fixes both. Exit 0 iff all pass; re-run any time. `--fix` applies developer-scope fixes (registry token, skills sync); operator actions stay remediations — and `--profile <p>` is the SSO profile `--fix` logs into CodeArtifact with, echoed back in the `fix:` command each check prints so the advertised command is the one that runs. | `app-tenant`: `--tenant <name>`, `--context <ctx>` (default `local`), `--hub-url <url>`, `--fix`, `--profile <sso>`, `--json` |
102
102
  | `mesh skills` | `sync` | Agent-skill distribution (D1/D2): installs the base building-with-Mesh skills into the repo's `.claude/skills/mesh-*/` (managed-marker files) and wires TanStack-Intent discovery for the pattern skills shipping inside `@mesh-tech/*` packages (`.intent/hooks/` gate + `.claude/settings.json` SessionStart hook + AGENTS.md fence — the PR #2356 mechanism, vendored). Idempotent; runs automatically after `mesh create-app`. | `sync`: `--check` (CI/doctor: exit 1 when missing/stale), `--root <path>` |
103
103
  | `mesh docs` | `build`, `portal`, `cli-reference` | `build` builds/validates an **app's** OpenAPI specs from `docs/docs.config.json` (file copy or SymXchange generation). `start`/`stop`/`list` are the simple front door: `start` serves the docs on loopback — DETACHED in a tmux session named `mesh-docs` by default (prints the bare URL once live; `stop` kills the session; `--foreground` or any non-TTY runs in the foreground) — serving the working tree in a mesh-platform checkout, or the published `@mesh-tech/docs` artifact fetched from the role-gated CodeArtifact registry anywhere else (docs version == the @mesh-tech/* baseline it describes; `-v` pins, default latest; cached under `~/.cache/mesh/docs/`). `list` shows the published docs versions. `portal` assembles the **Mesh developer portal** (docs.meshtech.io) from every `docs.json`-opted-in doc root in the repo — a directory publishes its markdown iff it holds a `docs.json`; route/title/order/nav derive from the tree and each file, so adding a doc is adding one file — then hands the assembled tree to Zudoku (`apps/docs/`): default runs `zudoku build`, `--serve` runs `zudoku dev`, `--assemble-only`/`-o` stop after assembly. Links between published files are rewritten to routes; links to unpublished repo files become blob links. Reserved directory names (`plans/`, `designs/`, `incidents/`, … the frozen list in `src/docs/schema.ts`) are excluded at any depth. `cli-reference` regenerates `docs/portal/generated/cli-reference.md` from THIS command tree, so it can't document a flag the binary lacks. `portal --check` is the CI gate (`pnpm check:docs-portal`): docs.json schema validation, reserved-segment fail-close, broken links, and CLI-reference staleness; `--diff-base <ref>` prints the publish-set diff for PR job summaries. Hidden commands and `--version` are excluded from the reference. | `build`: `-c/--config <path>` (default `docs/docs.config.json`); `portal`: `-o/--out <dir>`, `--assemble-only`, `--serve`, `-p/--port <n>` (default `3000`), `--check`, `--print-manifest`, `--diff-base <ref>`, `--manifest-out <path>`; `cli-reference`: `-o/--out <path>`, `--check`; `start`: `-v/--version <v>` (default: latest), `-p/--port <n>` (default `4400`; `0` is foreground-only), `--dev` (foreground HMR), `--foreground`; `stop`: no flags; `list`: no flags |
@@ -19,6 +19,10 @@ export const schema = defineSchema({
19
19
  namespace: "{{name}}",
20
20
  version: "1.0.0",
21
21
  subjects: {
22
+ // `identity` marks `user` as a caller type: a request resolves into it
23
+ // (`resolveSubject` in src/index.ts), so the compiled `ops_*` relations
24
+ // admit it and the Hub can assign a person a role under it. A subject
25
+ // declared without `identity` is grant-handle vocabulary only.
22
26
  user: { coarseRoles: ["user"], identity: { claim: "sub" } },
23
27
  },
24
28
  resources: {