@mesh-tech/mesh-cli 0.27.0 → 0.28.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": "5b8435e57",
3
- "builtAt": "2026-09-17T20:26:45.360Z"
2
+ "commit": "dd1a85628",
3
+ "builtAt": "2026-09-17T22:16:24.040Z"
4
4
  }
@@ -23,7 +23,8 @@ export const APP_CHECK_RULES = {
23
23
  gate: "0.2",
24
24
  title: "App registers with the Hub",
25
25
  severity: "block",
26
- remediation: "end index.ts with `export const app = env.register({ description })`",
26
+ remediation: "end index.ts with `export const app = env.register({ displayName, description })` — " +
27
+ "one sentence a non-technical operator can act on; put the caveats in `operatorNote`",
27
28
  scope: "app",
28
29
  },
29
30
  CUSTOM_SESSION_AUTH: {
@@ -19,7 +19,9 @@ export interface HubDevEnvOptions {
19
19
  tenants?: string;
20
20
  uiPort: number;
21
21
  apiPort: number;
22
+ audience?: string;
22
23
  }
24
+ export declare function resolveHubAudience(raw: string | undefined): "operator" | "tenant";
23
25
  export interface AssembledHubEnv {
24
26
  apiEnv: Record<string, string>;
25
27
  uiEnv: Record<string, string>;
@@ -99,6 +99,11 @@ export function forwardedSessionVars(sessionEnv) {
99
99
  }
100
100
  return out;
101
101
  }
102
+ export function resolveHubAudience(raw) {
103
+ if (raw === undefined || raw === "")
104
+ return "operator";
105
+ return raw.trim().toLowerCase() === "operator" ? "operator" : "tenant";
106
+ }
102
107
  export function assembleHubEnv(session, sessionEnv, opts) {
103
108
  const warnings = [];
104
109
  const notes = [];
@@ -141,6 +146,7 @@ export function assembleHubEnv(session, sessionEnv, opts) {
141
146
  if (!temporalAddress) {
142
147
  throw new MeshCliError(`Dev session '${session.name}' exposes no Temporal tunnel — the Hub can't reach the stack's Temporal.`, { remediation: { command: "mesh dev # relaunch; the session records tunnels.temporal in its state" } });
143
148
  }
149
+ const audienceEnv = { HUB_AUDIENCE: resolveHubAudience(opts.audience) };
144
150
  const apiEnv = {
145
151
  ...forwarded,
146
152
  PORT: String(opts.apiPort),
@@ -148,6 +154,7 @@ export function assembleHubEnv(session, sessionEnv, opts) {
148
154
  HUB_TENANT: hubTenant,
149
155
  HUB_SCOPE_ENV: scopeEnv,
150
156
  HUB_SCOPE_TENANTS: scopeTenants.join(","),
157
+ ...audienceEnv,
151
158
  ...(isLocalPlatform ? localPlatformEnv(sessionEnv) : {}),
152
159
  };
153
160
  const uiEnv = {
@@ -157,6 +164,7 @@ export function assembleHubEnv(session, sessionEnv, opts) {
157
164
  HUB_TENANT: hubTenant,
158
165
  DEFAULT_ORG: scopeTenants[0],
159
166
  DEFAULT_ENV: scopeEnv,
167
+ ...audienceEnv,
160
168
  };
161
169
  return { apiEnv, uiEnv, scope: { hubTenant, scopeEnv, scopeTenants }, temporalAddress, warnings, notes };
162
170
  }
@@ -282,7 +290,12 @@ async function hubDevAction(opts) {
282
290
  throw new MeshCliError(`Dev session '${session.name}' has a state file but no live tmux session — its tunnels and token-server are gone.`, { remediation: { command: `mesh dev # from ${session.state.appRoot}` } });
283
291
  }
284
292
  const sessionEnv = readTmuxSessionEnv(session.name);
285
- const assembled = assembleHubEnv(session, sessionEnv, { tenants: opts.tenants, uiPort, apiPort });
293
+ const assembled = assembleHubEnv(session, sessionEnv, {
294
+ tenants: opts.tenants,
295
+ uiPort,
296
+ apiPort,
297
+ audience: opts.audience ?? process.env.HUB_AUDIENCE,
298
+ });
286
299
  if (opts.printEnv) {
287
300
  console.log(chalk.bold("\nhub-api env:"));
288
301
  for (const [k, v] of Object.entries(redactEnv(assembled.apiEnv)))
@@ -308,6 +321,7 @@ async function hubDevAction(opts) {
308
321
  logInfo(`Dev session : ${session.name} (${session.state.appRoot})`);
309
322
  logInfo(`Hub source : ${platformDir}`);
310
323
  logInfo(`Scope : HUB_TENANT=${assembled.scope.hubTenant} HUB_SCOPE_ENV=${assembled.scope.scopeEnv} HUB_SCOPE_TENANTS=${assembled.scope.scopeTenants.join(",")}`);
324
+ logInfo(`Audience : ${assembled.apiEnv.HUB_AUDIENCE} (--audience tenant for a client's view)`);
311
325
  logInfo(`Temporal : ${assembled.temporalAddress}`);
312
326
  for (const note of assembled.notes)
313
327
  logInfo(note);
@@ -372,6 +386,7 @@ export function registerHubCommands(program) {
372
386
  .description("Launch the current-code Hub (api + ui) against a running `mesh dev` session — env auto-assembled, zero hand-set vars")
373
387
  .option("--session <name>", "dev session to observe (default: auto-detect from cwd)")
374
388
  .option("--tenants <list>", "comma-separated HUB_SCOPE_TENANTS override (default: the session's tenant)")
389
+ .option("--audience <who>", "who the Hub renders for: `operator` (platform staff — the default here) or `tenant` (a client's operators). Anything else reads as `tenant`")
375
390
  .option("--port <port>", `Hub UI host port (default: $MESH_HUB_DEV_PORT or ${DEFAULT_HUB_UI_PORT})`)
376
391
  .option("--api-port <port>", `Hub API port (default: ${DEFAULT_HUB_API_PORT})`)
377
392
  .option("--platform-dir <dir>", "mesh-platform checkout to run the Hub from (default: $MESH_PLATFORM_DIR or walk up from cwd)")
@@ -145,7 +145,19 @@ new mesh.apps.ApiDocs("docs", {
145
145
  // =============================================================================
146
146
 
147
147
  export const app = env.register({
148
- description: "{{titleCase name}}",
148
+ // The line under this app's name on the Hub's Apps list, read by the
149
+ // client's own operators more often than by anyone on the team. One
150
+ // sentence in THEIR words — what it does, not how. No file paths, no ticket
151
+ // ids, no platform vocabulary; `operatorNote` below is where those go.
152
+ displayName: "{{titleCase name}}",
153
+ description: "{{titleCase name}}.",
154
+ // Engineer to engineer. A Hub deployed for a client never serializes this —
155
+ // the key is dropped from the response — so it is the right home for the
156
+ // caveat, the runbook pointer and the ticket.
157
+ // operatorNote: "Shares a database with the importer; see the runbook.",
158
+ //
159
+ // `visibility: "platform"` would hide this app from a client's Hub entirely.
160
+ // That is for platform components, not for an app a client operates.
149
161
  });
150
162
 
151
163
  // Exported so the bindings above are used, and because a stack's URLs are the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mesh-tech/mesh-cli",
3
- "version": "0.27.0",
3
+ "version": "0.28.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.17.0",
45
+ "@mesh-tech/tsconfig": "2.18.0",
46
46
  "@mesh-tech/workflow-viz": "0.3.0",
47
47
  "@tanstack/intent": "^0.3.6",
48
48
  "@types/node": "^20.0.0",
@@ -115,7 +115,11 @@ new mesh.auth.AppAuthzPointer("authz", { env, mode: "policy-engine", opsHubMetad
115
115
  // --- Outputs ---
116
116
 
117
117
  export const app = env.register({
118
- description: "{{titleCase name}} — platform-authenticated API (authn + authz)",
118
+ displayName: "{{titleCase name}}",
119
+ // What a client operator reads on the Apps list. One sentence, their words.
120
+ description: "Serves the {{titleCase name}} API to signed-in callers.",
121
+ // Engineer to engineer; a Hub deployed for a client never serializes it.
122
+ operatorNote: "Platform-authenticated API — authn (Zitadel JWT) plus authz (SpiceDB policy engine).",
119
123
  });
120
124
 
121
125
  /** The app's own Zitadel project id (grant people and API keys against this). */
@@ -107,7 +107,11 @@ new mesh.auth.AppAuthzPointer("authz", {
107
107
  });
108
108
 
109
109
  export const app = env.register({
110
- description: "{{titleCase name}} — role-gating API (roles enforced in-process from the Zitadel JWT)",
110
+ displayName: "{{titleCase name}}",
111
+ // What a client operator reads on the Apps list. One sentence, their words.
112
+ description: "Serves the {{titleCase name}} API, with access decided by each caller's role.",
113
+ // Engineer to engineer; a Hub deployed for a client never serializes it.
114
+ operatorNote: "Role-gating mode — roles are enforced in-process from the Zitadel JWT, no SpiceDB.",
111
115
  });
112
116
 
113
117
  /** The app's own Zitadel project id (grant API keys against this). */
@@ -42,7 +42,11 @@ const vendorpay = new mesh.apps.ExternalService("vendorpay", {
42
42
  env,
43
43
  type: "payment",
44
44
  kind: "connection",
45
- description: "Example payments vendor (quotes API) — the external-service pattern",
45
+ // One sentence a client operator can act on, in their words. This renders on
46
+ // the Hub's Integrations row, which a credit union's own staff read.
47
+ description: "Gets price quotes from our payments vendor.",
48
+ // Engineer to engineer: never serialized by a Hub deployed for a client.
49
+ operatorNote: "Example vendor wired the external-service way — credentials in Secrets Manager, no runtime to probe.",
46
50
  credentials: {
47
51
  fields: {
48
52
  baseUrl: { name: "Base URL", description: "Vendor API endpoint" },
@@ -3,11 +3,27 @@
3
3
  - Invoke the CLI as `pnpm mesh <cmd>` or (cwd-independent) `pnpm exec mesh <cmd>` —
4
4
  a root `"mesh"` script + the `@mesh-tech/mesh-cli` devDependency make both work
5
5
  after `pnpm install`. For a bare `mesh` on PATH, run once: `pnpm exec mesh install-shim`.
6
- - Operations are `mesh` commands, never raw `aws`/`pulumi`/`kubectl`:
7
- `mesh login {{tenant}}.{{env}}`, `mesh deploy preview|up` (run inside
8
- `core/` or `platform/`), `mesh init app-tenant` (doctor).
6
+ - Operations are `mesh` commands, never raw `pulumi`/`kubectl`: `mesh deploy
7
+ preview|up` (run inside `core/` or `platform/`), `mesh init app-tenant`
8
+ (doctor). **One exception:** a platform stack carries no `mesh:deployerRole`
9
+ — only app stacks do — so `mesh deploy` reads credentials from the
10
+ environment, on the first deploy and on every day-2 run:
11
+ `aws sso login --profile {{profile}}`, then
12
+ `eval "$(aws configure export-credentials --profile {{profile}} --format env)"`.
13
+ - **`mesh login {{tenant}}.{{env}}` comes LAST, after the first deploy.** The
14
+ Zitadel it signs in to is what the `platform/` layer creates; run earlier it
15
+ fails with `No configuration found for "{{tenant}}.{{env}}"`. Package access
16
+ is a separate, global sign-in (`mesh registry login`) and is not needed in
17
+ this repo at all.
9
18
  - Deploy order matters: `core/` before `platform/` (platform imports core
10
- from the SSM registry).
19
+ from the SSM registry), and the NS delegation of the public zone is a manual
20
+ step **in the middle of the `core/` deploy** — that deploy blocks on its ACM
21
+ certificate until you delegate the new zone's NS records from the parent, so
22
+ a `core/` deploy that looks hung is waiting on you. README.md holds the full
23
+ first-deploy sequence, the one `mesh init` printed when it scaffolded this
24
+ repo. Do not invent a different one.
25
+ - `platform/` builds service images, so it needs Docker; `core/` does not.
26
+ That is why a Docker-less container can deploy `core/` and stop there.
11
27
  - Capabilities are config, not code: enable/disable platform services in
12
28
  `platform/Pulumi.{{tenant}}-{{env}}.yaml` (temporal/zitadel/hub/tenants/…);
13
29
  the `index.ts` layer programs should rarely change.
@@ -64,5 +64,9 @@ const worker = new mesh.apps.TemporalWorker("worker", {
64
64
  // --- Outputs ---
65
65
 
66
66
  export const app = env.register({
67
- description: "{{titleCase name}} — Temporal workflow app",
67
+ displayName: "{{titleCase name}}",
68
+ // What a client operator reads on the Apps list. One sentence, their words.
69
+ description: "Runs {{titleCase name}}'s scheduled and long-running jobs.",
70
+ // Engineer to engineer; a Hub deployed for a client never serializes it.
71
+ operatorNote: "Temporal workflow app scaffolded by `mesh create-app --template workflow`.",
68
72
  });