@mesh-tech/mesh-cli 0.43.3 → 0.44.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/bin/mesh.js CHANGED
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  createProgram
4
- } from "./mesh.chunk-VCQKLVZJ.js";
4
+ } from "./mesh.chunk-XOCERV6F.js";
5
5
  import "./mesh.chunk-QUKWWFZI.js";
6
6
  import {
7
7
  formatVersionLine,
8
8
  resolveCliRuntime
9
- } from "./mesh.chunk-OOWKJR4H.js";
9
+ } from "./mesh.chunk-OQA2UBOD.js";
10
10
  import "./mesh.chunk-QE5LUIHN.js";
11
11
  import "./mesh.chunk-ZNM3DDAM.js";
12
12
  import "./mesh.chunk-PANTMVC3.js";
@@ -2,9 +2,9 @@ import {
2
2
  CLI_DESCRIPTION,
3
3
  CLI_NAME,
4
4
  createProgram
5
- } from "./mesh.chunk-VCQKLVZJ.js";
5
+ } from "./mesh.chunk-XOCERV6F.js";
6
6
  import "./mesh.chunk-QUKWWFZI.js";
7
- import "./mesh.chunk-OOWKJR4H.js";
7
+ import "./mesh.chunk-OQA2UBOD.js";
8
8
  import "./mesh.chunk-QE5LUIHN.js";
9
9
  import "./mesh.chunk-ZNM3DDAM.js";
10
10
  import "./mesh.chunk-PANTMVC3.js";
@@ -4,7 +4,7 @@ import {
4
4
  reportAssembly,
5
5
  resolveCliRuntime,
6
6
  runAssemble
7
- } from "./mesh.chunk-OOWKJR4H.js";
7
+ } from "./mesh.chunk-OQA2UBOD.js";
8
8
  import {
9
9
  logError,
10
10
  logInfo,
@@ -1,4 +1,4 @@
1
1
  {
2
- "commit": "67750469b",
3
- "builtAt": "2026-11-09T14:05:29.095Z"
2
+ "commit": "d86991f76",
3
+ "builtAt": "2026-11-12T18:37:32.328Z"
4
4
  }
@@ -1,2 +1,16 @@
1
1
  import { Command } from "commander";
2
+ interface ExtractedWorkflow {
3
+ workflowType: string;
4
+ ir: {
5
+ name: string;
6
+ nodes: unknown[];
7
+ edges: unknown[];
8
+ };
9
+ sourceFile: string;
10
+ }
11
+ export declare function workflowIrFiles(appName: string, workflows: ExtractedWorkflow[], root: string): {
12
+ name: string;
13
+ body: string;
14
+ }[];
2
15
  export declare function registerWorkflowCommands(program: Command): void;
16
+ export {};
@@ -123,7 +123,7 @@ const target = ${JSON.stringify(targetPath)};
123
123
  const explicitProcessArtifact = ${JSON.stringify(explicitProcessArtifact ?? null)};
124
124
  const stat = statSync(target);
125
125
  const files = stat.isDirectory()
126
- ? readdirSync(target).filter(f => f.endsWith(".ts") && f !== "index.ts").map(f => join(target, f))
126
+ ? readdirSync(target).filter(f => f.endsWith(".ts") && f !== "index.ts" && !/\\.(test|spec|d)\\.ts$/.test(f)).map(f => join(target, f))
127
127
  : [target];
128
128
 
129
129
  const results = [];
@@ -168,7 +168,7 @@ ${DISCOVERY_HELPERS_SRC}
168
168
  const target = ${JSON.stringify(targetPath)};
169
169
  const stat = statSync(target);
170
170
  const files = stat.isDirectory()
171
- ? readdirSync(target).filter(f => f.endsWith(".ts") && f !== "index.ts").map(f => join(target, f))
171
+ ? readdirSync(target).filter(f => f.endsWith(".ts") && f !== "index.ts" && !/\\.(test|spec|d)\\.ts$/.test(f)).map(f => join(target, f))
172
172
  : [target];
173
173
 
174
174
  const results = [];
@@ -220,7 +220,7 @@ const target = ${JSON.stringify(targetPath)};
220
220
  const explicitProcessArtifact = ${JSON.stringify(explicitProcessArtifact ?? null)};
221
221
  const stat = statSync(target);
222
222
  const files = stat.isDirectory()
223
- ? readdirSync(target).filter(f => f.endsWith(".ts") && f !== "index.ts").map(f => join(target, f))
223
+ ? readdirSync(target).filter(f => f.endsWith(".ts") && f !== "index.ts" && !/\\.(test|spec|d)\\.ts$/.test(f)).map(f => join(target, f))
224
224
  : [target];
225
225
 
226
226
  const results = [];
@@ -253,40 +253,37 @@ process.stdout.write(JSON.stringify(results));
253
253
  return null;
254
254
  }
255
255
  }
256
- async function uploadToS3(bucket, appName, workflows) {
257
- const { S3Client, PutObjectCommand } = await import("@aws-sdk/client-s3");
258
- const s3 = new S3Client({ region: process.env.AWS_REGION ?? "us-east-2" });
259
- for (const wf of workflows) {
260
- const key = `workflow-ir/${appName}/${wf.workflowType}.json`;
261
- const body = JSON.stringify(wf.ir, null, 2);
262
- logInfo(` Uploading s3://${bucket}/${key} (${body.length} bytes)`);
263
- await s3.send(new PutObjectCommand({
264
- Bucket: bucket,
265
- Key: key,
266
- Body: body,
267
- ContentType: "application/json",
268
- }));
269
- }
256
+ export function workflowIrFiles(appName, workflows, root) {
270
257
  const manifest = {
271
258
  extractedAt: new Date().toISOString(),
272
259
  app: appName,
273
260
  workflows: Object.fromEntries(workflows.map((wf) => [
274
261
  wf.workflowType,
275
262
  {
276
- sourceFile: wf.sourceFile,
263
+ sourceFile: path.relative(root, wf.sourceFile) || path.basename(wf.sourceFile),
277
264
  nodeCount: wf.ir.nodes.length,
278
265
  edgeCount: wf.ir.edges.length,
279
266
  },
280
267
  ])),
281
268
  };
282
- const manifestKey = `workflow-ir/${appName}/_manifest.json`;
283
- logInfo(` Uploading s3://${bucket}/${manifestKey}`);
284
- await s3.send(new PutObjectCommand({
285
- Bucket: bucket,
286
- Key: manifestKey,
287
- Body: JSON.stringify(manifest, null, 2),
288
- ContentType: "application/json",
289
- }));
269
+ return [
270
+ ...workflows.map((wf) => ({ name: `${wf.workflowType}.json`, body: JSON.stringify(wf.ir, null, 2) })),
271
+ { name: "_manifest.json", body: JSON.stringify(manifest, null, 2) },
272
+ ];
273
+ }
274
+ async function uploadToS3(bucket, appName, files) {
275
+ const { S3Client, PutObjectCommand } = await import("@aws-sdk/client-s3");
276
+ const s3 = new S3Client({ region: process.env.AWS_REGION ?? "us-east-2", followRegionRedirects: true });
277
+ for (const file of files) {
278
+ const key = `workflow-ir/${appName}/${file.name}`;
279
+ logInfo(` Uploading s3://${bucket}/${key} (${file.body.length} bytes)`);
280
+ await s3.send(new PutObjectCommand({
281
+ Bucket: bucket,
282
+ Key: key,
283
+ Body: file.body,
284
+ ContentType: "application/json",
285
+ }));
286
+ }
290
287
  }
291
288
  function parseNameList(value) {
292
289
  return value
@@ -307,6 +304,7 @@ export function registerWorkflowCommands(program) {
307
304
  "Supports single files or directories.")
308
305
  .option("--app <name>", "Application name (used as S3 key prefix)")
309
306
  .option("--upload <bucket>", "S3 bucket to upload extracted IR (requires --app)")
307
+ .option("--out <dir>", "Write the files the upload would write (one JSON per workflow type, plus _manifest.json) to a directory (requires --app)")
310
308
  .option("--process <path>", "Path to a process artifact JSON file (docs/plans/2026-07-05-process-artifact-design.md §1); " +
311
309
  "renders substeps/actors/outcomes in place of a BUSINESS_STAGES map. Absent this flag, a sibling " +
312
310
  "process/*.process.json is auto-discovered per workflow file.")
@@ -346,15 +344,29 @@ export function registerWorkflowCommands(program) {
346
344
  for (const wf of results) {
347
345
  logSuccess(` ${wf.workflowType} (${wf.ir.nodes.length} nodes, ${wf.ir.edges.length} edges)`);
348
346
  }
349
- if (opts.upload && opts.app) {
347
+ if ((opts.upload || opts.out) && !opts.app) {
348
+ logError("--upload and --out need --app (the IR's key prefix)");
349
+ process.exitCode = 1;
350
+ return;
351
+ }
352
+ const root = fs.statSync(resolvedPath).isDirectory() ? resolvedPath : path.dirname(resolvedPath);
353
+ const files = opts.app ? workflowIrFiles(opts.app, results, root) : [];
354
+ if (opts.out) {
355
+ const outDir = path.resolve(opts.out);
356
+ fs.mkdirSync(outDir, { recursive: true });
357
+ for (const file of files)
358
+ fs.writeFileSync(path.join(outDir, file.name), file.body);
359
+ logSuccess(`Wrote ${files.length} file(s) to ${outDir}`);
360
+ }
361
+ if (opts.upload) {
350
362
  logInfo(`Uploading ${results.length} workflow IR(s) to s3://${opts.upload}/workflow-ir/${opts.app}/`);
351
- await uploadToS3(opts.upload, opts.app, results);
363
+ await uploadToS3(opts.upload, opts.app, files);
352
364
  logSuccess("Upload complete");
353
365
  }
354
- else if (opts.upload || opts.app) {
355
- logWarn("Both --app and --upload required for S3 upload");
366
+ else if (opts.app && !opts.out) {
367
+ logWarn("--app does nothing without --upload or --out");
356
368
  }
357
- else {
369
+ if (!opts.upload && !opts.out) {
358
370
  console.log(JSON.stringify(results.map((r) => r.ir), null, 2));
359
371
  }
360
372
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mesh-tech/mesh-cli",
3
- "version": "0.43.3",
3
+ "version": "0.44.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.7.0",
45
- "@mesh-tech/tsconfig": "2.61.1",
45
+ "@mesh-tech/tsconfig": "2.62.0",
46
46
  "@mesh-tech/workflow-viz": "0.3.4",
47
47
  "@tanstack/intent": "^0.3.6",
48
48
  "@types/node": "^20.0.0",
@@ -191,7 +191,7 @@ Top-level commands (each `register*` is wired in `src/program.ts`, which `bin/me
191
191
  | `mesh artifacts` | `get <ref>` | Download workflow artifact files from an AI-agent conversation. Ref `<conversationId>:<artifactId>` (artifactId defaults to `workflow-artifact`). | `-o/--output <dir>`, `--target <name>`, `--api-url <url>`, `--context <ctx>` (default `mesh.dev`) |
192
192
  | `mesh conversations` (alias `conv`) | `list`, `show <id>`, `artifacts <id>` | Read a deployed Mesh agent's conversations: list the caller's conversations, render a transcript, or list a conversation's artifacts. Resolves against the same `agent-targets` registry the MCP `agent_*` tools use. | all three: `--target <name>`, `--api-url <url>` (overrides `--target`), `--context <ctx>` (used with `--api-url`, default `mesh.dev`), `--json` (default when stdout isn't a TTY) |
193
193
  | `mesh vpn` | `connect <context>`, `disconnect`, `status`, `tunnel <up\|down\|status>`, `api-key`, `pre-auth-key`, `users` | Headscale VPN mgmt via Tailscale + kubectl. **`connect` now defaults to the userspace `tailscaled`** (brew formula, NOT the GUI Tailscale.app) — the same daemon `mesh dev --transport tailscale` / `mesh vpn tunnel` use, so it **runs headless / under the sandbox** and exposes a local SOCKS5 proxy (route a tool via `ALL_PROXY=socks5://127.0.0.1:<port>`). It relays a one-time browser-register URL for Zitadel auth. `--system` opts into the whole-machine GUI Tailscale.app (system TUN) — needs the app installed and **cannot run headless/sandboxed** (Sparkle.framework abort). For auto-forwarded VPC services (Temporal/RDS) prefer **`mesh vpn tunnel up`** or `mesh dev`. `tunnel status` reports runner **ownership** (`Runner: owned (verified)` vs `NOT OWNED (<reason>)` — a port answering a dial is not proof it's mesh's); `--json` adds `owned` (bool) + `ownership` (reason string). Admin subcommands `exec` into the `headscale-0` pod. | root: `-t/--tenant` (default `mesh`), `-e/--env` (default `dev`), `-n/--namespace`; `connect`/`disconnect`: `--system`; `tunnel up`: `--context <ctx>`; `tunnel down`: `--stop` (stop daemon, keep login), `--logout`; `tunnel status`: `--json`; `pre-auth-key`: `-u/--user` (**required**), `--expiration <24h>`, `--reusable`, `--ephemeral` |
194
- | `mesh workflow` | `extract-ir <path>`, `inventory <path>`, `lint-process <path>` | `extract-ir`: parse Temporal workflow TS source → WorkflowIR graph (nodes+edges); prints JSON or uploads to S3. `inventory`: the as-built `{workflowType, commands, queries, activities}` the process lint diffs against. `lint-process`: parse + conformance-lint a process artifact against that inventory; exits 1 on any error-severity finding. | `extract-ir`: `--app <name>`, `--upload <bucket>` (both required together for S3); `extract-ir`/`lint-process`: `--process <path>` (else a sibling `process/*.process.json` is discovered); `lint-process`: `--predicates <csv>`, `--selectors <csv>` — the CLI cannot execute the worker's registries, so without a flag those findings are skipped (a note is printed) while every other rule still runs |
194
+ | `mesh workflow` | `extract-ir <path>`, `inventory <path>`, `lint-process <path>` | `extract-ir`: parse Temporal workflow TS source → WorkflowIR graph (nodes+edges); prints JSON, uploads to S3, or writes the same files to a directory (so a package can ship IR it extracted at build time). A directory's `*.test.ts`/`*.spec.ts`/`*.d.ts` files are skipped. `inventory`: the as-built `{workflowType, commands, queries, activities}` the process lint diffs against. `lint-process`: parse + conformance-lint a process artifact against that inventory; exits 1 on any error-severity finding. | `extract-ir`: `--app <name>`, `--upload <bucket>`, `--out <dir>` (`--upload` and `--out` each need `--app`); `extract-ir`/`lint-process`: `--process <path>` (else a sibling `process/*.process.json` is discovered); `lint-process`: `--predicates <csv>`, `--selectors <csv>` — the CLI cannot execute the worker's registries, so without a flag those findings are skipped (a note is printed) while every other rule still runs |
195
195
  | `mesh site` | `publish <name> <dir>`, `versions <name>`, `rollback <name> <version>` | Publish a built directory as an OAuth-protected static site served by Studio, content-addressed and incremental: `publish` walks and hashes the tree, uploads only the blobs the site does not already hold, then commits a version and points the site at it; `versions` lists what has been published, newest first; `rollback` points the site back at an earlier version. Three stateless calls against the agent-api, so a run that dies partway leaves nothing to clean up. | all: `--target <name>`, `--api-url <url>`, `--context <ctx>`; `publish`: `--version-id <id>`, `--no-activate` (publish without pointing the site at it) |
196
196
  | `mesh vcs` | `clone <repo> [dest]`, `get <repo> <path>`, `propose`, `rm <repo> <paths...>`, `proposals`/`show`/`diff`/`approve`/`reject`/`comment`/`request-changes`, `drafts <list\|show\|create\|submit\|discard>` | Versioned content repos (`mesh.vcs`). Repos are real git repos over smart HTTP but **read-only** — every change goes through a proposal. `clone` wires the bearer auth header into the clone; `propose` submits the working tree (binary content is base64-encoded from the bytes, not the extension, so images survive intact); **`rm` proposes deletions of files or folders with no clone at all**, and `--dry-run` lists what would go without proposing anything. | all: `--url <base>`, `--token <t>`, `--context <ctx>`; `propose`: `-m/--message`, `--revise <id>`, `--merge-parent <sha>`; `rm`: `-m/--message`, `--dry-run` |
197
197
  | `mesh temporal` | `describe <wfId> [runId]`, `history <wfId> [runId]`, `recover-conversation <wfId> [runId]`, `capture-history <wfId> [runId]` | Inspect Temporal workflows for the current app/stack; reconstruct an agent conversation transcript from durable history when the worker can't replay it; capture a full history to a local replay fixture. | shared: `--stack`, `--address`, `--namespace`; `history`: `-n/--limit <200>`, `-f/--follow`, `--no-compact`, `-p/--show-payloads` (decrypts via `TEMPORAL_ENCODING_KEY` from the K8s secret); `recover-conversation`: `--out <path>`, `--json`, `--snapshot` (structured `{conversationId,messages,artifacts,focus}` blob); `capture-history`: `--out <path>` (default `~/.mesh/replay-histories/<wfId>.json`, do not commit) | <!-- skill-lint-disable-next-line — row documents the mesh-owned kubectl wrappers; the backticked `aws eks get-token` describes what the CLI runs internally, not an operator step -->
@@ -483,7 +483,7 @@ the program (…); run `mesh deploy up --stack <s>` to refresh the dev env.").
483
483
  | 401 installing a **public** package (`npm view lodash version` fails) | an unscoped `registry=<codeartifact>` line in `~/.npmrc` makes CodeArtifact the DEFAULT registry, so public packages resolve through it and 401 at the 12h token expiry | `mesh registry login` — it removes the line after backing `~/.npmrc` up; confirm with `npm config get registry` (expect `https://registry.npmjs.org/`) |
484
484
  | `--export` prints nothing | no role resolved | pass `--role <arn>` or set `MESH_AWS_ROLE` |
485
485
  | `--export` / `EPERM … ~/.aws` in a sandbox | `~/.aws` not writable | it now auto-falls back to a mesh-managed config (`~/.config/mesh/aws-config`) and also exports `AWS_CONFIG_FILE`; or prefer `mesh deploy <op>` (no shell profile needed) |
486
- | `workflow extract-ir` no S3 upload | only one of `--app`/`--upload` given | pass both, or omit both to print JSON |
486
+ | `workflow extract-ir` exits 1: `--upload and --out need --app` | `--upload` or `--out` without `--app` | add `--app <name>`; `--app` alone only prints JSON |
487
487
 
488
488
  ## Programmatic API
489
489