@mesh-tech/mesh-cli
Version:
CLI for Mesh platform development utilities
2,565 lines • 88.9 kB
JavaScript
var __defProp = Object.defineProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __esm = (fn, res) => function __init() {
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
// libs/mesh-cli/src/utils/log.ts
import chalk from "chalk";
function logPrefix(opts) {
const enabled = opts.envFlag === "1" || opts.envFlag !== "0" && !opts.isTTY;
return enabled ? `[${opts.now.toISOString().slice(11, 19)}] ` : "";
}
function prefix() {
return chalk.dim(
logPrefix({
isTTY: !!process.stderr.isTTY,
envFlag: process.env.MESH_LOG_TIMESTAMPS,
now: /* @__PURE__ */ new Date()
})
);
}
function logInfo(message) {
console.error(prefix() + chalk.blue("\u2139"), message);
}
function logSuccess(message) {
console.error(prefix() + chalk.green("\u2713"), message);
}
function logWarn(message) {
console.error(prefix() + chalk.yellow("\u26A0"), message);
}
function logError(message) {
console.error(prefix() + chalk.red("\u2717"), message);
}
var init_log = __esm({
"libs/mesh-cli/src/utils/log.ts"() {
"use strict";
}
});
// libs/mesh-cli/src/utils/pulumi.ts
import { execFileSync } from "child_process";
import * as path from "path";
import * as fs from "fs";
function findAppRoot(startDir) {
let dir = startDir;
while (true) {
if (fs.existsSync(path.join(dir, "Pulumi.yaml"))) return dir;
const parent = path.dirname(dir);
if (parent === dir) return null;
dir = parent;
}
}
function findStackConfigs(appRoot) {
return fs.readdirSync(appRoot).filter((f) => /^Pulumi\..+\.yaml$/.test(f) && f !== "Pulumi.yaml").map((f) => f.replace(/^Pulumi\./, "").replace(/\.yaml$/, ""));
}
function getCurrentStack(appRoot) {
try {
const result = execFileSync("pulumi", ["stack", "--show-name"], {
encoding: "utf-8",
cwd: appRoot,
stdio: ["pipe", "pipe", "pipe"]
});
return result.trim() || null;
} catch {
return null;
}
}
function readStackConfig(appRoot, stack, key) {
const configFile = path.join(appRoot, `Pulumi.${stack}.yaml`);
if (!fs.existsSync(configFile)) return null;
const content = fs.readFileSync(configFile, "utf-8");
const pattern = new RegExp(`^\\s+${key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}:\\s*(.+)$`, "m");
const match = content.match(pattern);
if (!match) return null;
return match[1].trim().replace(/^["']|["']$/g, "");
}
function pulumiStackOutput(appRoot, key, extraArgs, env) {
const execEnv = env ? { ...process.env, ...env } : void 0;
try {
const result = execFileSync(
"pulumi",
["stack", "output", key, "--json", ...extraArgs],
{ cwd: appRoot, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], env: execEnv }
);
if (result.includes('"[secret]"')) {
return execFileSync(
"pulumi",
["stack", "output", key, "--json", "--show-secrets", ...extraArgs],
{ cwd: appRoot, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], env: execEnv }
);
}
return result;
} catch (err) {
const errMsg = err?.stderr ?? "";
if (errMsg.includes("kms:") || errMsg.includes("KMS") || errMsg.includes("secrets manager")) {
throw err;
}
return execFileSync(
"pulumi",
["stack", "output", key, "--json", "--show-secrets", ...extraArgs],
{ cwd: appRoot, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], env: execEnv }
);
}
}
var init_pulumi = __esm({
"libs/mesh-cli/src/utils/pulumi.ts"() {
"use strict";
}
});
// libs/mesh-cli/src/utils/auth-preflight.ts
function isExpiredAwsTokenMessage(message) {
return /expired|invalid.*(security )?token|token.*(is )?invalid|no credential|could not load credentials|could not be found|sso session/i.test(
message
);
}
var SSO_LOGIN_FIX;
var init_auth_preflight = __esm({
"libs/mesh-cli/src/utils/auth-preflight.ts"() {
"use strict";
SSO_LOGIN_FIX = "aws sso login --sso-session=mesh # or: pnpm sso";
}
});
// libs/mesh-cli/src/utils/errors.ts
import chalk2 from "chalk";
var init_errors = __esm({
"libs/mesh-cli/src/utils/errors.ts"() {
"use strict";
}
});
// libs/mesh-cli/src/utils/first-party-contexts.ts
function firstPartyDomainFor(context) {
if (!Object.hasOwn(FIRST_PARTY_CONTEXTS, context)) return null;
const domain = FIRST_PARTY_CONTEXTS[context];
return typeof domain === "string" && domain ? domain : null;
}
var FIRST_PARTY_CONTEXTS;
var init_first_party_contexts = __esm({
"libs/mesh-cli/src/utils/first-party-contexts.ts"() {
"use strict";
FIRST_PARTY_CONTEXTS = Object.freeze({
// Verified 2026-08-14: https://cli.dev.platform.meshtech.io/.well-known/mesh.json
// answers with the platform's issuer + CLI client id + registryBroker,
// anonymously. (Repointed from dev.trabian.meshtech.io — the trabian tenant
// platform lives in a different AWS account with no registry; the
// platform-dev stack in the registry-owning account is the developer front
// door. mesh-sandbox#32 / #3587.)
"mesh.dev": "dev.platform.meshtech.io"
});
}
});
// libs/mesh-cli/src/utils/context.ts
var init_context = __esm({
"libs/mesh-cli/src/utils/context.ts"() {
"use strict";
init_log();
}
});
// libs/mesh-cli/src/utils/bastion.ts
import { SSMClient, GetParameterCommand } from "@aws-sdk/client-ssm";
var init_bastion = __esm({
"libs/mesh-cli/src/utils/bastion.ts"() {
"use strict";
init_log();
}
});
// libs/mesh-cli/src/utils/credentials.ts
import {
SecretsManagerClient,
GetSecretValueCommand
} from "@aws-sdk/client-secrets-manager";
var init_credentials = __esm({
"libs/mesh-cli/src/utils/credentials.ts"() {
"use strict";
init_log();
}
});
// libs/mesh-cli/src/utils/vpn.ts
var init_vpn = __esm({
"libs/mesh-cli/src/utils/vpn.ts"() {
"use strict";
}
});
// libs/mesh-cli/src/utils/index.ts
var init_utils = __esm({
"libs/mesh-cli/src/utils/index.ts"() {
"use strict";
init_errors();
init_log();
init_context();
init_bastion();
init_credentials();
init_pulumi();
init_vpn();
}
});
// libs/mesh-cli/src/utils/pid.ts
var init_pid = __esm({
"libs/mesh-cli/src/utils/pid.ts"() {
"use strict";
init_errors();
}
});
// libs/mesh-cli/src/utils/vpn-join.ts
var init_vpn_join = __esm({
"libs/mesh-cli/src/utils/vpn-join.ts"() {
"use strict";
init_login();
}
});
// libs/mesh-cli/src/utils/pulumi-run.ts
var init_pulumi_run = __esm({
"libs/mesh-cli/src/utils/pulumi-run.ts"() {
"use strict";
init_log();
init_pulumi();
init_aws_auth();
init_login();
}
});
// libs/mesh-cli/src/utils/kubeconfig.ts
var init_kubeconfig = __esm({
"libs/mesh-cli/src/utils/kubeconfig.ts"() {
"use strict";
}
});
// libs/mesh-cli/src/utils/temporal-auth.ts
import { execFileSync as execFileSync2 } from "node:child_process";
async function resolveTemporalAuth(tenant, env, platformName = tenant) {
const { SSMClient: SSMClient2, GetParameterCommand: GetParameterCommand2 } = await import("@aws-sdk/client-ssm");
const ssm = new SSMClient2({ region: process.env.AWS_REGION || "us-east-2" });
const results = {};
async function trySSM(name) {
try {
const resp = await ssm.send(new GetParameterCommand2({ Name: name, WithDecryption: true }));
return resp.Parameter?.Value ?? void 0;
} catch {
return void 0;
}
}
try {
const workerPath = `/mesh-platform/${tenant}/${env}/temporal/worker-service-account-key`;
const exchangePath = `/mesh-platform/${tenant}/${env}/temporal/token-exchange-app`;
const workerJson = await trySSM(workerPath);
if (workerJson) {
const worker = JSON.parse(workerJson);
results.TEMPORAL_AUTH_CLIENT_ID = worker.clientId;
results.TEMPORAL_AUTH_CLIENT_SECRET = worker.clientSecret;
}
const exchangeJson = await trySSM(exchangePath);
if (exchangeJson) {
const exchange = JSON.parse(exchangeJson);
results.ZITADEL_TOKEN_EXCHANGE_CLIENT_ID = exchange.clientId;
results.ZITADEL_TOKEN_EXCHANGE_CLIENT_SECRET = exchange.clientSecret;
}
const platformJson = await trySSM(`/mesh-platform/${platformName}/${env}/platform/zitadel`);
if (platformJson) {
const platform = JSON.parse(platformJson);
if (platform.endpoint) results.ZITADEL_ISSUER = platform.endpoint;
if (platform.projectId) results.ZITADEL_PROJECT_ID = platform.projectId;
}
if (!results.TEMPORAL_AUTH_CLIENT_ID) {
const legacyClientId = await trySSM("/platform/temporal/auth/CLIENT_ID");
const legacyClientSecret = await trySSM("/platform/temporal/auth/CLIENT_SECRET");
if (legacyClientId && legacyClientSecret) {
results.TEMPORAL_AUTH_CLIENT_ID = legacyClientId;
results.TEMPORAL_AUTH_CLIENT_SECRET = legacyClientSecret;
}
}
if (!results.ZITADEL_ISSUER) {
try {
const podEnv = execFileSync2(
"kubectl",
[
"get",
"pods",
"-n",
`temporal-${env}`,
"-l",
"app.kubernetes.io/component=frontend",
"-o",
"jsonpath={.items[0].spec.containers[0].env}"
],
{ encoding: "utf-8", timeout: 1e4, stdio: ["pipe", "pipe", "pipe"] }
).trim();
if (podEnv) {
const envVars = JSON.parse(podEnv);
for (const v of envVars) {
if (v.name === "ZITADEL_ISSUER_URL" && v.value && !results.ZITADEL_ISSUER) {
results.ZITADEL_ISSUER = v.value;
}
if (v.name === "ZITADEL_PROJECT_ID" && v.value && !results.ZITADEL_PROJECT_ID) {
results.ZITADEL_PROJECT_ID = v.value;
}
}
}
} catch (err) {
const detail = err instanceof Error ? err.message : String(err);
const stderr = String(err?.stderr ?? "");
if (/\.kube[/\\]config|error loading config file/i.test(`${detail} ${stderr}`)) {
logWarn(
"kubectl could not read a kubeconfig and fell back to ~/.kube/config (macOS blocks it for shells without Full Disk Access). Run inside the mesh dev tmux session, or export KUBECONFIG=$TMPDIR/mesh-dev-sessions/<session>.kubeconfig."
);
}
}
}
if (results.TEMPORAL_AUTH_CLIENT_ID && results.ZITADEL_ISSUER) {
logSuccess(`Temporal auth resolved (client: ${results.TEMPORAL_AUTH_CLIENT_ID})`);
} else if (Object.keys(results).length > 0) {
logWarn("Partial Temporal auth resolved (some credentials missing)");
}
} catch (err) {
logWarn(
`Could not resolve Temporal auth from SSM: ${err instanceof Error ? err.message : String(err)}`
);
}
return results;
}
async function acquireTemporalBearer(vars = {}) {
const issuerUrl = vars.ZITADEL_ISSUER ?? process.env.ZITADEL_ISSUER;
const projectId = vars.ZITADEL_PROJECT_ID ?? process.env.ZITADEL_PROJECT_ID;
const clientId = vars.TEMPORAL_AUTH_CLIENT_ID ?? process.env.TEMPORAL_AUTH_CLIENT_ID;
const clientSecret = vars.TEMPORAL_AUTH_CLIENT_SECRET ?? process.env.TEMPORAL_AUTH_CLIENT_SECRET;
if (!issuerUrl || !clientId || !clientSecret) {
return void 0;
}
const scopes = ["openid"];
if (projectId) {
scopes.push(`urn:zitadel:iam:org:project:id:${projectId}:aud`);
if (process.env.TEMPORAL_M2M_SKIP_ROLES_SCOPE !== "true") {
scopes.push("urn:zitadel:iam:org:projects:roles");
}
}
const response = await fetch(`${issuerUrl}/oauth/v2/token`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "client_credentials",
client_id: clientId,
client_secret: clientSecret,
scope: scopes.join(" ")
})
});
if (!response.ok) {
const text = await response.text();
throw new Error(
`Temporal token acquisition failed: ${response.status} ${response.statusText} \u2014 ${text}`
);
}
const data = await response.json();
return data.access_token;
}
var init_temporal_auth = __esm({
"libs/mesh-cli/src/utils/temporal-auth.ts"() {
"use strict";
init_log();
}
});
// libs/mesh-cli/src/utils/reachability.ts
var init_reachability = __esm({
"libs/mesh-cli/src/utils/reachability.ts"() {
"use strict";
}
});
// libs/mesh-cli/src/utils/workflow-fingerprint.ts
var init_workflow_fingerprint = __esm({
"libs/mesh-cli/src/utils/workflow-fingerprint.ts"() {
"use strict";
}
});
// libs/mesh-cli/src/commands/dev-launch.ts
var init_dev_launch = __esm({
"libs/mesh-cli/src/commands/dev-launch.ts"() {
"use strict";
}
});
// libs/mesh-cli/src/commands/local/seed.ts
var LOCAL_TENANT, LOCAL_ENV, APP_TENANTS_PARAM, DATA_BUCKET_SEEDS, FABRIC_CHECK_PATH, TEMPORAL_NAMESPACE;
var init_seed = __esm({
"libs/mesh-cli/src/commands/local/seed.ts"() {
"use strict";
init_log();
init_errors();
LOCAL_TENANT = "local";
LOCAL_ENV = "dev";
APP_TENANTS_PARAM = `/mesh-platform/${LOCAL_TENANT}/${LOCAL_ENV}/app-tenants`;
DATA_BUCKET_SEEDS = [
{
key: "samples/pipes/fragment-0001.json",
body: JSON.stringify(
{
id: "sample-0001",
kind: "pipes-fragment",
account: "0001",
asOf: "2026-01-01",
lines: [{ amount: "125.00", currency: "USD", description: "Sample credit" }]
},
null,
2
)
}
];
FABRIC_CHECK_PATH = `/mesh-platform/${LOCAL_TENANT}/${LOCAL_ENV}/.fabric-check`;
TEMPORAL_NAMESPACE = `${LOCAL_TENANT}-${LOCAL_ENV}`;
}
});
// libs/mesh-cli/src/commands/local/helpers.ts
var init_helpers = __esm({
"libs/mesh-cli/src/commands/local/helpers.ts"() {
"use strict";
init_seed();
}
});
// libs/mesh-cli/src/utils/cache-home.ts
var init_cache_home = __esm({
"libs/mesh-cli/src/utils/cache-home.ts"() {
"use strict";
}
});
// libs/mesh-cli/src/commands/local/stack.ts
import { parse as parseYaml } from "yaml";
var DEFAULT_HUB_PORT;
var init_stack = __esm({
"libs/mesh-cli/src/commands/local/stack.ts"() {
"use strict";
init_helpers();
init_cache_home();
init_errors();
init_log();
DEFAULT_HUB_PORT = "9000";
}
});
// libs/mesh-cli/src/commands/local/mocks.ts
var init_mocks = __esm({
"libs/mesh-cli/src/commands/local/mocks.ts"() {
"use strict";
init_log();
init_errors();
init_seed();
init_helpers();
init_stack();
}
});
// libs/api-registry/src/hub-roles.ts
function hubOperatorRoleKeys(tenants) {
const cleaned = [...new Set(tenants.filter((t) => t !== ""))].sort();
for (const tenant of cleaned) {
for (const sep of [HUB_ROLE_KEY_SEPARATOR, HUB_ROLE_APP_SEPARATOR]) {
if (tenant.includes(sep)) {
throw new Error(
`hubOperatorRoleKeys: tenant name "${tenant}" contains the role-key separator "${sep}" \u2014 it would parse as a different tenant's or app's grant`
);
}
}
}
return [
...HUB_BASE_ROLES,
...cleaned.flatMap(
(tenant) => HUB_RESTRICTED_ROLES.map((role) => `${role}${HUB_ROLE_KEY_SEPARATOR}${tenant}`)
)
];
}
var HUB_STAFF_ROLES, HUB_RESTRICTED_ROLES, HUB_BASE_ROLES, HUB_ROLE_KEY_SEPARATOR, HUB_ROLE_APP_SEPARATOR, RESTRICTED;
var init_hub_roles = __esm({
"libs/api-registry/src/hub-roles.ts"() {
"use strict";
HUB_STAFF_ROLES = ["ops", "admin"];
HUB_RESTRICTED_ROLES = ["developer", "auditor", "vendor"];
HUB_BASE_ROLES = [
...HUB_STAFF_ROLES,
...HUB_RESTRICTED_ROLES
];
HUB_ROLE_KEY_SEPARATOR = ":";
HUB_ROLE_APP_SEPARATOR = "/";
RESTRICTED = new Set(HUB_RESTRICTED_ROLES);
}
});
// libs/api-registry/src/index.ts
import { z } from "zod";
var rateLimitSpecSchema, rateLimitDefaultsSchema, integrationHealthSchema, apiSurfaceKindSchema, apiSurfaceSchema, apiRegistryEntrySchema, integrationStatusSchema;
var init_src = __esm({
"libs/api-registry/src/index.ts"() {
"use strict";
init_hub_roles();
rateLimitSpecSchema = z.object({
rps: z.number().int().min(1),
burst: z.number().int().min(1)
});
rateLimitDefaultsSchema = z.record(z.string().min(1), rateLimitSpecSchema);
integrationHealthSchema = z.union([
z.object({ op: z.string().trim().min(1) }),
z.object({ unavailable: z.string().trim().min(1) })
]);
apiSurfaceKindSchema = z.enum(["app", "vendor"]);
apiSurfaceSchema = z.object({
http: z.object({ url: z.string().min(1) }).optional(),
nexus: z.object({ endpoint: z.string().min(1), taskQueue: z.string().min(1) }).optional()
});
apiRegistryEntrySchema = z.object({
schemaVersion: z.number().int().positive().default(1),
name: z.string().min(1),
kind: apiSurfaceKindSchema.optional(),
provider: z.string().optional(),
version: z.string().optional(),
title: z.string().optional(),
description: z.string().optional(),
surfaces: apiSurfaceSchema,
credentials: z.object({ keyedBy: z.string().nullish() }).optional(),
docs: z.object({
url: z.string().min(1),
contentHash: z.string().min(1),
siteUrl: z.string().min(1).optional(),
siteInternalUrl: z.string().min(1).optional()
}),
enabledOps: z.array(z.string()),
// The consumer schema strips unknown keys, so a producer-side field that is
// not mirrored here never reaches a consumer — mirror every addition.
status: z.object({ url: z.string().min(1) }).optional(),
appVersion: z.string().min(1).optional(),
rateLimits: rateLimitDefaultsSchema.optional(),
health: integrationHealthSchema.optional(),
producedRateClasses: z.array(z.string().min(1)).optional()
});
integrationStatusSchema = z.object({
contract: z.literal("v1"),
name: z.string().min(1),
provider: z.string().optional(),
type: z.string().optional(),
title: z.string().optional(),
definitionVersion: z.string().optional(),
packageVersion: z.string().min(1).optional(),
tenant: z.string().min(1),
instanceKey: z.string().optional(),
surfaces: z.array(z.enum(["http", "nexus"])),
coreMode: z.enum(["mock", "live"]),
coreModeSource: z.string().optional(),
enabledOps: z.array(z.string()),
credentialsWired: z.boolean(),
rateLimits: rateLimitDefaultsSchema.optional(),
health: integrationHealthSchema.optional(),
startedAt: z.string().min(1)
});
}
});
// libs/mesh-cli/src/commands/local/seed-zitadel.ts
var HUB_DEFAULT_REDIRECT_URI, HUB_ROLES, ZITADEL_SSM_PARAM, TEST_USERS_SSM_PREFIX;
var init_seed_zitadel = __esm({
"libs/mesh-cli/src/commands/local/seed-zitadel.ts"() {
"use strict";
init_src();
init_log();
init_errors();
init_login();
init_stack();
init_seed();
init_cache_home();
HUB_DEFAULT_REDIRECT_URI = `http://localhost:${DEFAULT_HUB_PORT}/oauth2/callback`;
HUB_ROLES = hubOperatorRoleKeys([LOCAL_TENANT]);
ZITADEL_SSM_PARAM = `/mesh-platform/${LOCAL_TENANT}/${LOCAL_ENV}/platform/zitadel`;
TEST_USERS_SSM_PREFIX = `/mesh-platform/${LOCAL_TENANT}/${LOCAL_ENV}/temporal/test-users`;
}
});
// libs/mesh-cli/src/commands/local/auth-provision.ts
var init_auth_provision = __esm({
"libs/mesh-cli/src/commands/local/auth-provision.ts"() {
"use strict";
init_log();
init_seed();
init_seed_zitadel();
init_helpers();
}
});
// libs/mesh-cli/src/utils/mesh-json.ts
var init_mesh_json = __esm({
"libs/mesh-cli/src/utils/mesh-json.ts"() {
"use strict";
}
});
// libs/mesh-cli/src/commands/local/dev-local.ts
var init_dev_local = __esm({
"libs/mesh-cli/src/commands/local/dev-local.ts"() {
"use strict";
init_mocks();
init_helpers();
init_errors();
init_seed();
init_seed();
init_seed_zitadel();
init_auth_provision();
init_stack();
init_login();
init_mesh_json();
}
});
// libs/mesh-cli/src/commands/local/docker-runner.ts
var init_docker_runner = __esm({
"libs/mesh-cli/src/commands/local/docker-runner.ts"() {
"use strict";
init_dev_local();
init_cache_home();
}
});
// libs/mesh-cli/src/utils/stack-flag.ts
var init_stack_flag = __esm({
"libs/mesh-cli/src/utils/stack-flag.ts"() {
"use strict";
init_log();
}
});
// libs/mesh-cli/src/commands/peer-addressing.ts
var init_peer_addressing = __esm({
"libs/mesh-cli/src/commands/peer-addressing.ts"() {
"use strict";
}
});
// libs/mesh-cli/src/commands/dev-doctor.ts
var init_dev_doctor = __esm({
"libs/mesh-cli/src/commands/dev-doctor.ts"() {
"use strict";
init_dev();
init_auth_preflight();
init_login();
}
});
// libs/mesh-cli/src/utils/worktree-identity.ts
var init_worktree_identity = __esm({
"libs/mesh-cli/src/utils/worktree-identity.ts"() {
"use strict";
}
});
// libs/mesh-cli/src/commands/dev.ts
import { Option } from "commander";
import { SecretsManagerClient as SecretsManagerClient2, GetSecretValueCommand as GetSecretValueCommand2 } from "@aws-sdk/client-secrets-manager";
var init_dev = __esm({
"libs/mesh-cli/src/commands/dev.ts"() {
"use strict";
init_utils();
init_aws_auth();
init_login();
init_vpn_join();
init_pulumi_run();
init_tailscale();
init_pulumi();
init_kubeconfig();
init_temporal_auth();
init_reachability();
init_workflow_fingerprint();
init_dev_launch();
init_dev_local();
init_auth_provision();
init_seed();
init_mocks();
init_dev_local();
init_stack();
init_docker_runner();
init_errors();
init_stack_flag();
init_peer_addressing();
init_dev_doctor();
init_worktree_identity();
}
});
// libs/mesh-cli/src/utils/tailscale-targets.ts
var init_tailscale_targets = __esm({
"libs/mesh-cli/src/utils/tailscale-targets.ts"() {
"use strict";
init_dev();
init_login();
}
});
// libs/mesh-cli/src/utils/socks-forward.ts
var init_socks_forward = __esm({
"libs/mesh-cli/src/utils/socks-forward.ts"() {
"use strict";
}
});
// libs/mesh-cli/src/utils/tunnel-ownership.ts
var init_tunnel_ownership = __esm({
"libs/mesh-cli/src/utils/tunnel-ownership.ts"() {
"use strict";
}
});
// libs/mesh-cli/src/utils/tailscale.ts
var init_tailscale = __esm({
"libs/mesh-cli/src/utils/tailscale.ts"() {
"use strict";
init_login();
init_aws_auth();
init_log();
init_errors();
init_pid();
init_bastion();
init_tailscale_targets();
init_socks_forward();
init_tunnel_ownership();
}
});
// libs/mesh-cli/src/commands/vpn/tunnel.ts
var init_tunnel = __esm({
"libs/mesh-cli/src/commands/vpn/tunnel.ts"() {
"use strict";
init_log();
init_login();
init_tailscale();
}
});
// libs/mesh-cli/src/commands/vpn/index.ts
import { execFileSync as execFileSync3 } from "child_process";
function tailscaleStatus() {
try {
const json = execFileSync3("tailscale", ["status", "--json"], {
encoding: "utf-8",
stdio: ["pipe", "pipe", "pipe"]
});
return JSON.parse(json);
} catch {
return null;
}
}
function tailscaleBackendState() {
return tailscaleStatus()?.BackendState ?? null;
}
var init_vpn2 = __esm({
"libs/mesh-cli/src/commands/vpn/index.ts"() {
"use strict";
init_utils();
init_login();
init_tunnel();
init_tailscale();
}
});
// libs/mesh-cli/src/commands/login.ts
import * as http from "http";
import * as crypto from "crypto";
import * as fs2 from "fs";
import * as path2 from "path";
import { execFileSync as execFileSync4 } from "child_process";
function readConfig() {
if (!fs2.existsSync(CONFIG_FILE)) return {};
try {
return JSON.parse(fs2.readFileSync(CONFIG_FILE, "utf-8"));
} catch {
return {};
}
}
function writeContextConfig(context, config) {
const existing = readConfig();
existing[context] = config;
fs2.mkdirSync(CONFIG_DIR, { recursive: true });
fs2.writeFileSync(CONFIG_FILE, JSON.stringify(existing, null, 2));
}
function getContextConfig(context) {
const config = readConfig();
return config[context] ?? null;
}
function parseTenantEnv(context) {
const parts = context.split(".");
if (parts.length !== 2 || !parts[0] || !parts[1]) return null;
return { tenant: parts[0], env: parts[1] };
}
async function discoverConfigFromSsm(context) {
const parsed = parseTenantEnv(context);
if (!parsed) return null;
const { tenant, env } = parsed;
const ssmPath = `/mesh-platform/${tenant}/${env}/platform/zitadel`;
logInfo(`Attempting SSM discovery from ${ssmPath}...`);
try {
const { SSMClient: SSMClient2, GetParameterCommand: GetParameterCommand2 } = await import("@aws-sdk/client-ssm");
const region = process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION ?? "us-east-2";
const ssm = new SSMClient2({ region });
const resp = await ssm.send(new GetParameterCommand2({ Name: ssmPath }));
const raw = resp.Parameter?.Value;
if (!raw) {
logWarn(`SSM parameter ${ssmPath} has no value`);
return null;
}
const data = JSON.parse(raw);
const issuer = data.endpoint;
const clientId = data.cliClientId;
if (typeof issuer !== "string" || !issuer) {
logWarn("SSM zitadel entry missing 'endpoint' field");
return null;
}
if (typeof clientId !== "string" || !clientId) {
logWarn(
"SSM zitadel entry missing 'cliClientId' field.\n The platform needs to be deployed with an updated ZitadelPlatformIdentity\n that exports cliClientId. Until then, contact your platform admin."
);
return null;
}
const config = { issuer, clientId };
if (typeof data.vpn === "string") config.vpn = data.vpn;
if (typeof data.vpnJoinBroker === "string") config.vpnJoinBroker = data.vpnJoinBroker;
if (typeof data.registryBroker === "string") config.registryBroker = data.registryBroker;
writeContextConfig(context, config);
logSuccess(`Discovered platform configuration for ${context} via SSM`);
return config;
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
if (message.includes("ExpiredToken") || message.includes("credentials")) {
logWarn(`SSM discovery failed: AWS credentials expired or unavailable.`);
if (firstPartyDomainFor(context) || context.split(".").length - 1 >= 2) {
logInfo(` Falling back to anonymous HTTPS discovery \u2014 no AWS needed.`);
} else {
logInfo(` If you have an AWS account: aws sso login --profile <profile>`);
logInfo(` Otherwise use the platform's full domain, e.g. mesh login dev.<tenant>.meshtech.io`);
}
} else if (message.includes("ParameterNotFound")) {
logWarn(`SSM parameter not found: ${ssmPath}`);
logInfo(" This platform context may not be deployed.");
} else {
logWarn(`SSM discovery failed: ${message}`);
}
return null;
}
}
async function discoverConfig(domain, contextKey = domain, opts = {}) {
const url = `https://cli.${domain}/.well-known/mesh.json`;
if (!opts.quiet) logInfo(`Attempting discovery from ${url}...`);
try {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5e3);
const resp = await fetch(url, { signal: controller.signal });
clearTimeout(timeout);
if (!resp.ok) {
logWarn(`Discovery endpoint returned ${resp.status}`);
return null;
}
const data = await resp.json();
const issuer = data.issuer;
const clientId = data.clientId;
if (typeof issuer !== "string" || !issuer || typeof clientId !== "string" || !clientId) {
logWarn("Discovery endpoint returned invalid config (missing issuer or clientId)");
return null;
}
const config = { issuer, clientId };
if (typeof data.vpn === "string") config.vpn = data.vpn;
if (typeof data.vpnJoinBroker === "string") config.vpnJoinBroker = data.vpnJoinBroker;
if (typeof data.registryBroker === "string") config.registryBroker = data.registryBroker;
if (!opts.quiet) {
writeContextConfig(contextKey, config);
logSuccess(`Discovered platform configuration for ${contextKey}`);
}
return config;
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
if (message.includes("abort")) {
logWarn("Discovery timed out");
} else {
logWarn(`Discovery failed: ${message}`);
}
return null;
}
}
function readAllCredentials() {
if (!fs2.existsSync(CREDENTIALS_FILE)) return {};
try {
return JSON.parse(fs2.readFileSync(CREDENTIALS_FILE, "utf-8"));
} catch {
return {};
}
}
function readCredentials(context) {
return readAllCredentials()[context] ?? null;
}
function atomicWriteFileSync(path4, data, mode) {
const tmpPath = `${path4}.${process.pid}.${atomicWriteCounter++}.tmp`;
try {
fs2.writeFileSync(tmpPath, data, { mode });
fs2.renameSync(tmpPath, path4);
} catch (err) {
try {
fs2.unlinkSync(tmpPath);
} catch {
}
throw err;
}
}
function writeCredentials(context, creds) {
fs2.mkdirSync(CONFIG_DIR, { recursive: true });
const all = readAllCredentials();
all[context] = creds;
atomicWriteFileSync(CREDENTIALS_FILE, JSON.stringify(all, null, 2), 384);
}
function base64url(buffer) {
return buffer.toString("base64url");
}
function generateCodeVerifier() {
return base64url(crypto.randomBytes(32));
}
function generateCodeChallenge(verifier) {
return base64url(crypto.createHash("sha256").update(verifier).digest());
}
function decodeJwtPayload(token) {
const parts = token.split(".");
if (parts.length !== 3) throw new Error("Invalid JWT");
return JSON.parse(Buffer.from(parts[1], "base64url").toString());
}
async function exchangeCode(issuer, clientId, code, codeVerifier) {
const body = new URLSearchParams({
grant_type: "authorization_code",
code,
redirect_uri: REDIRECT_URI,
client_id: clientId,
code_verifier: codeVerifier
});
const resp = await fetch(`${issuer}/oauth/v2/token`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: body.toString()
});
if (!resp.ok) {
const text = await resp.text();
throw new Error(`Token exchange failed (${resp.status}): ${text}`);
}
return resp.json();
}
async function refreshTokens(issuer, clientId, refreshToken) {
const body = new URLSearchParams({
grant_type: "refresh_token",
refresh_token: refreshToken,
client_id: clientId
});
const resp = await fetch(`${issuer}/oauth/v2/token`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: body.toString()
});
if (!resp.ok) {
throw new Error(`Token refresh failed (${resp.status})`);
}
return resp.json();
}
function login(context, config) {
return new Promise((resolve3, reject) => {
const codeVerifier = generateCodeVerifier();
const codeChallenge = generateCodeChallenge(codeVerifier);
const state = base64url(crypto.randomBytes(16));
const authUrl = new URL(`${config.issuer}/oauth/v2/authorize`);
authUrl.searchParams.set("client_id", config.clientId);
authUrl.searchParams.set("redirect_uri", REDIRECT_URI);
authUrl.searchParams.set("response_type", "code");
authUrl.searchParams.set("scope", SCOPES);
authUrl.searchParams.set("code_challenge", codeChallenge);
authUrl.searchParams.set("code_challenge_method", "S256");
authUrl.searchParams.set("state", state);
let timeoutId;
const server = http.createServer(async (req, res) => {
try {
const url = new URL(req.url ?? "/", `http://localhost:${REDIRECT_PORT}`);
if (url.pathname !== "/callback") {
res.writeHead(404);
res.end("Not found");
return;
}
const error = url.searchParams.get("error");
if (error) {
const desc = url.searchParams.get("error_description") ?? error;
const safeDesc = desc.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
res.end(
`<html><body><h2>Login failed</h2><p>${safeDesc}</p></body></html>`
);
teardown();
reject(new Error(desc));
return;
}
const returnedState = url.searchParams.get("state");
if (returnedState !== state) {
res.writeHead(400);
res.end("State mismatch");
teardown();
reject(new Error("State mismatch"));
return;
}
const code = url.searchParams.get("code");
if (!code) {
res.writeHead(400);
res.end("No code");
teardown();
reject(new Error("No authorization code received"));
return;
}
const tokens = await exchangeCode(
config.issuer,
config.clientId,
code,
codeVerifier
);
const idPayload = decodeJwtPayload(tokens.id_token);
const email = idPayload.email ?? idPayload.preferred_username ?? "unknown";
const sub = idPayload.sub;
let tenants = [];
try {
const accessPayload = decodeJwtPayload(tokens.access_token);
tenants = accessPayload["urn:mesh:tenants"] ?? [];
} catch {
}
writeCredentials(context, {
idToken: tokens.id_token,
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token,
expiresAt: new Date(
Date.now() + tokens.expires_in * 1e3
).toISOString(),
email,
sub
});
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
res.end(
`<html><body><h2>✅ Logged in to ${context}!</h2><p>You can close this tab.</p></body></html>`
);
logSuccess(`Logged in as ${email} (${context})`);
if (tenants.length > 0) {
logInfo(`Deployable tenants: ${tenants.join(", ")}`);
}
hintVpnIfDisconnected(context);
teardown();
resolve3();
} catch (err) {
teardown();
reject(err);
}
});
const teardown = () => {
if (timeoutId) clearTimeout(timeoutId);
server.closeAllConnections?.();
server.close();
};
server.listen(REDIRECT_PORT, () => {
logInfo(`Opening browser for authentication (${context})...`);
const url = authUrl.toString();
try {
if (process.platform === "darwin") {
execFileSync4("open", [url], { stdio: "ignore" });
} else if (process.platform === "linux") {
execFileSync4("xdg-open", [url], { stdio: "ignore" });
} else {
logInfo(`Open this URL in your browser:
${url}`);
}
} catch {
logInfo(`Open this URL in your browser:
${url}`);
}
});
timeoutId = setTimeout(() => {
teardown();
reject(new Error(loginTimeoutMessage(context)));
}, 12e4);
});
}
async function deviceLoginWithReissue(attempt, maxCodes = MAX_DEVICE_CODES) {
for (let n = 1; n <= maxCodes; n++) {
if (n > 1) logWarn(`That code expired \u2014 issuing a fresh one (${n}/${maxCodes})\u2026`);
if (await attempt(n) === "success") return;
}
throw new Error(
`Device login not completed after ${maxCodes} codes. Re-run the command when you're ready to authorize.`
);
}
async function deviceCodeLogin(context, config) {
await deviceLoginWithReissue(() => attemptDeviceCode(context, config));
}
async function attemptDeviceCode(context, config) {
const resp = await fetch(`${config.issuer}/oauth/v2/device_authorization`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
client_id: config.clientId,
scope: SCOPES
}).toString()
});
if (!resp.ok) {
const text = await resp.text();
throw new Error(`Device authorization request failed (${resp.status}): ${text}`);
}
const deviceAuth = await resp.json();
const openUrl = deviceAuth.verification_uri_complete ?? deviceAuth.verification_uri;
console.log();
logInfo(`Open this URL in your browser:
`);
logInfo(` ${openUrl}
`);
logInfo(`Code: ${deviceAuth.user_code}`);
console.log();
logInfo("Waiting for authorization...");
try {
if (process.platform === "darwin") {
execFileSync4("open", [openUrl], { stdio: "ignore" });
} else if (process.platform === "linux") {
execFileSync4("xdg-open", [openUrl], { stdio: "ignore" });
}
} catch {
}
const deadline = Date.now() + deviceAuth.expires_in * 1e3;
let interval = deviceAuth.interval * 1e3;
while (Date.now() < deadline) {
await new Promise((r) => setTimeout(r, interval));
const tokenResp = await fetch(`${config.issuer}/oauth/v2/token`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
client_id: config.clientId,
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
device_code: deviceAuth.device_code
}).toString()
});
if (tokenResp.ok) {
const tokens = await tokenResp.json();
const idPayload = decodeJwtPayload(tokens.id_token);
const email = idPayload.email ?? idPayload.preferred_username ?? "unknown";
const sub = idPayload.sub;
let tenants = [];
try {
const accessPayload = decodeJwtPayload(tokens.access_token);
tenants = accessPayload["urn:mesh:tenants"] ?? [];
} catch {
}
writeCredentials(context, {
idToken: tokens.id_token,
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token,
expiresAt: new Date(Date.now() + tokens.expires_in * 1e3).toISOString(),
email,
sub
});
logSuccess(`Logged in as ${email} (${context})`);
if (tenants.length > 0) {
logInfo(`Deployable tenants: ${tenants.join(", ")}`);
}
hintVpnIfDisconnected(context);
return "success";
}
const error = await tokenResp.json();
switch (error.error) {
case "authorization_pending":
continue;
case "slow_down":
interval += 1e3;
continue;
case "expired_token":
return "expired";
// caller re-issues a fresh code
case "access_denied":
throw new Error("Authorization denied by user.");
default:
throw new Error(`Token exchange failed: ${error.error} \u2014 ${error.error_description ?? ""}`);
}
}
return "expired";
}
function loginTimeoutMessage(context) {
const base = "Login timed out (2 minutes)";
if (context !== "local") return base;
return `${base}. The local platform already has a signed-up user \u2014 sign in as dev@local.mesh / LocalDev1! instead of registering. If you did register, its confirmation email is in the local mailbox at http://localhost:8025; confirm it there and run the command again.`;
}
function isRemoteEnvironment() {
if (process.env.REMOTE_CONTAINERS || process.env.CODESPACES) return true;
if (fs2.existsSync("/.dockerenv")) return true;
if (process.env.SSH_CLIENT || process.env.SSH_TTY) return true;
if (!process.stdout.isTTY || !process.stdin.isTTY) return true;
return false;
}
function hintVpnIfDisconnected(context) {
if (context === RESERVED_REGISTRY_CONTEXT) return;
try {
const state = tailscaleBackendState();
if (state !== "Running") {
console.log("");
logInfo(`VPN not connected. To access dev services, run:`);
logInfo(` mesh vpn connect ${context}`);
}
} catch {
}
}
function tokenStillValid(expiresAt, marginMs = 0, now = Date.now()) {
return new Date(expiresAt).getTime() - marginMs > now;
}
async function getValidToken(context, opts = {}) {
const config = getContextConfig(context);
if (!config) return null;
const creds = readCredentials(context);
if (!creds) return null;
if (tokenStillValid(creds.expiresAt, opts.marginMs ?? 0)) {
return creds.idToken;
}
return remintToken(context, config, creds);
}
async function remintToken(context, config, creds) {
if (!creds.refreshToken) return null;
let tokens;
try {
tokens = await refreshTokens(config.issuer, config.clientId, creds.refreshToken);
} catch {
return null;
}
let email = creds.email;
try {
email = decodeJwtPayload(tokens.id_token).email ?? creds.email;
} catch {
}
try {
writeCredentials(context, {
...creds,
idToken: tokens.id_token,
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token ?? creds.refreshToken,
expiresAt: new Date(Date.now() + tokens.expires_in * 1e3).toISOString(),
email
});
} catch (err) {
logWarn(
`The refreshed ${context} session could not be cached (${err instanceof Error ? err.message : String(err)}) \u2014 this run continues on the new token, but the next one may need a fresh sign-in.`
);
}
return tokens.id_token;
}
async function runLoginFlow(context, config, opts = {}) {
const useDevice = opts.device || isRemoteEnvironment();
if (useDevice) {
logInfo("Using device code flow");
await deviceCodeLogin(context, config);
} else {
await login(context, config);
}
}
async function ensureValidToken(context, config, opts) {
let token = await getValidToken(context);
if (token) return token;
logInfo(`No valid Zitadel session for ${context} \u2014 running login flow`);
try {
await runLoginFlow(context, config, opts);
} catch (err) {
logError(`Login failed: ${err.message}`);
return null;
}
return await getValidToken(context);
}
async function ensureLogin(context, opts = {}) {
const existing = readCredentials(context);
if (existing && new Date(existing.expiresAt) > /* @__PURE__ */ new Date()) return existing;
if (opts.interactive === false) return null;
const config = getContextConfig(context) ?? await discoverConfigGuarded(context);
if (!config) {
logWarn(`No login config for "${context}". Run: mesh login ${context}`);
return null;
}
const token = await ensureValidToken(context, config, opts);
if (!token) return null;
return readCredentials(context);
}
async function discoverConfigGuarded(context) {
let config = null;
if (parseTenantEnv(context)) {
config = await discoverConfigFromSsm(context);
}
if (!config) {
const pinned = firstPartyDomainFor(context);
if (pinned) {
logInfo(`"${context}" is a known Mesh platform \u2014 resolving via ${pinned} (no AWS needed)`);
config = await discoverConfig(pinned, context);
}
}
if (!config && context.split(".").length - 1 >= 2) {
config = await discoverConfig(context);
}
return config;
}
var CONFIG_DIR, CONFIG_FILE, CREDENTIALS_FILE, REDIRECT_PORT, REDIRECT_URI, SCOPES, atomicWriteCounter, MAX_DEVICE_CODES, RESERVED_REGISTRY_CONTEXT;
var init_login = __esm({
"libs/mesh-cli/src/commands/login.ts"() {
"use strict";
init_log();
init_errors();
init_aws_auth();
init_first_party_contexts();
init_vpn2();
CONFIG_DIR = path2.join(
process.env.XDG_CONFIG_HOME ?? path2.join(process.env.HOME ?? "~", ".config"),
"mesh"
);
CONFIG_FILE = path2.join(CONFIG_DIR, "config.json");
CREDENTIALS_FILE = path2.join(CONFIG_DIR, "credentials.json");
REDIRECT_PORT = 9876;
REDIRECT_URI = `http://localhost:${REDIRECT_PORT}/callback`;
SCOPES = "openid email profile offline_access urn:zitadel:iam:org:project:id:zitadel:aud";
atomicWriteCounter = 0;
MAX_DEVICE_CODES = 3;
RESERVED_REGISTRY_CONTEXT = "registry";
}
});
// libs/mesh-cli/src/utils/aws-auth.ts
import * as fs3 from "fs";
import * as path3 from "path";
import {
AssumeRoleCommand,
AssumeRoleWithWebIdentityCommand,
GetCallerIdentityCommand,
STSClient
} from "@aws-sdk/client-sts";
function derivePlatformContext(appRoot, stack) {
const configFile = path3.join(appRoot, `Pulumi.${stack}.yaml`);
if (!fs3.existsSync(configFile)) return null;
const content = fs3.readFileSync(configFile, "utf-8");
const jsonMatch = content.match(/mesh:platform:\s*'(\{[^']+\})'/);
if (jsonMatch) {
try {
const parsed = JSON.parse(jsonMatch[1]);
if (parsed.name && parsed.env) return `${parsed.name}.${parsed.env}`;
} catch {
}
}
const nameMatch = content.match(/mesh:platform:[\s\S]*?name:\s*(\S+)/);
const envMatch = content.match(/mesh:platform:[\s\S]*?env:\s*(\S+)/);
if (nameMatch && envMatch) {
return `${nameMatch[1]}.${envMatch[1]}`;
}
return null;
}
function newStsClient() {
return new STSClient({ region: DEFAULT_REGION });
}
function toEnv(credentials) {
const { AccessKeyId, SecretAccessKey, SessionToken } = credentials;
if (!AccessKeyId || !SecretAccessKey || !SessionToken) return null;
return {
AWS_ACCESS_KEY_ID: AccessKeyId,
AWS_SECRET_ACCESS_KEY: SecretAccessKey,
AWS_SESSION_TOKEN: SessionToken
};
}
function isMaxSessionDurationError(err) {
if (!(err instanceof Error)) return false;
const message = err.message ?? "";
return message.includes("MaxSessionDuration") || message.includes("DurationSeconds");
}
async function assumeRole(roleArn, sessionName = "mesh-cli", durationSeconds = 43200) {
const sts = newStsClient();
for (const duration of [durationSeconds, 3600]) {
try {
const resp = await sts.send(
new AssumeRoleCommand({
RoleArn: roleArn,
RoleSessionName: sessionName,
DurationSeconds: duration
})
);
if (!resp.Credentials) return null;
return toEnv(resp.Credentials);
} catch (err) {
if (!isMaxSessionDurationError(err) || duration === 3600) {
const message = err instanceof Error ? err.message : String(err);
logWarn(`AssumeRole failed: ${message}`);
if (isExpiredAwsTokenMessage(message)) {
logWarn(` Your AWS SSO session looks expired or missing \u2014 refresh with: ${SSO_LOGIN_FIX}`);
}
return null;
}
}
}
return null;
}
async function assumeRoleCredentials(roleArn, idToken, sessionName, durationSeconds = 43200) {
const sts = newStsClient();
for (const duration of [durationSeconds, 3600]) {
try {
const resp = await sts.send(
new AssumeRoleWithWebIdentityCommand({
RoleArn: roleArn,
RoleSessionName: sessionName,
WebIdentityToken: idToken,
DurationSeconds: duration
})
);
if (!resp.Credentials) return null;
const env = toEnv(resp.Credentials);
if (!env) return null;
return {
// `env` is a `Record<string, string>` (index-signature typed), so
// `noUncheckedIndexedAccess` widens these to `string | undefined`
// even though `toEnv` guarantees they're set — assert accordingly.
AccessKeyId: env.AWS_ACCESS_KEY_ID,
SecretAccessKey: env.AWS_SECRET_ACCESS_KEY,
SessionToken: env.AWS_SESSION_TOKEN,
Expiration: resp.Credentials.Expiration.toISOString()
};
} catch (err) {
if (!isMaxSessionDurationError(err) || duration === 3600) {
logWarn(
`AssumeRoleWithWebIdentity failed: ${err instanceof Error ? err.message : String(err)}`
);
return null;
}
}
}
return null;
}
async function assumeRoleWithWebIdentity(roleArn, idToken, sessionName, durationSeconds = 43200) {
const creds = await assumeRoleCredentials(roleArn, idToken, sessionName, durationSeconds);
if (!creds) return null;
return toEnv(creds);
}
async function resolveAwsCredentials(roleArn, appRoot, stack) {
const roleName = roleArn.split("/").pop() ?? roleArn;
if (process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SESSION_TOKEN) {
logInfo("Using existing AWS credentials from environment");
return {
env: {
AWS_ACCESS_KEY_ID: process.env.AWS_ACCESS_KEY_ID,
AWS_SECRET_ACCESS_KEY: process.env.AWS_SECRET_ACCESS_KEY ?? "",
AWS_SESSION_TOKEN: process.env.AWS_SESSION_TOKEN
},
method: "environment"
};
}
const platformContext = derivePlatformContext(appRoot, stack);
if (platformContext) {
const meshCreds = await ensureLogin(platformContext, { interactive: false });
if (meshCreds) {
const sessionName = (meshCreds.email ?? "mesh-cli").replace(/[^a-zA-Z0-9=,.@-]/g, "_").slice(0, 64);
logInfo(`Assuming ${roleName} via Zitadel (${meshCreds.email ?? platformContext})`);
const creds2 = await assumeRoleWithWebIdentity(roleArn, meshCreds.idToken, sessionName);
if (creds2) {
logSuccess(`Assumed ${roleName} via Zitadel`);
return { env: creds2, method: "zitadel" };
}
logWarn(
`Zitadel JWT auth failed \u2014 falling back to AWS SSO.
(Try: mesh login ${platformContext})`
);
}
}
logInfo(`Assuming ${roleName} via AWS SSO`);
const creds = await assumeRole(roleArn, "mesh-cli");
if (creds) {
logSuccess(`Assumed ${roleName}`);
return { env: creds, method: "sso" };
}
if (platformContext && process.stdout.isTTY) {
const meshCreds = await ensureLogin(platformContext);
if (meshCreds) {
const sessionName = (meshCreds.email ?? "mesh-cli").replace(/[^a-zA-Z0-9=,.@-]/g, "_").slice(0, 64);
logInfo(`Assuming ${roleName} via Zitadel (${meshCreds.email ?? platformContext})`);
const zCreds = await assumeRoleWithWebIdentity(roleArn, meshCreds.idToken, sessionName);
if (zCreds) {
logSuccess(`Assumed ${roleName} via Zitadel`);
return { env: zCreds, method: "zitadel" };
}
}
}
return null;
}
var DEFAULT_REGION;
var init_aws_auth = __esm({
"libs/mesh-cli/src/utils/aws-auth.ts"() {
"use strict";
init_log();
init_auth_preflight();
init_login();
DEFAULT_REGION = process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION ?? "us-east-2";
}
});
// libs/mesh-cli/src/utils/capture-history.ts
var capture_history_exports = {};
__export(capture_history_exports, {
decryptHistoryPayloads: () => decryptHistoryPayloads,
serializeHistoryToFixture: () => serializeHistoryToFixture
});
import { historyToJSON } from "@temporalio/common/lib/proto-utils.js";
function isPayload(value) {
return value != null && typeof value === "object" && "metadata" in value && "data" in value;
}
async function decryptHistoryPayloads(node, decrypt) {
if (node == null || typeof node !== "object") return 0;
if (isPayload(node)) {
const inner = await decrypt(node);
if (inner === null) return 0;
node.metadata = inner.metadata ?? {};
node.data = inner.data ?? new Uint8Array(0);
return 1;
}
let count = 0;
if (Array.isArray(node)) {
for (const item of node) count += await decryptHistoryPayloads(item, decrypt);
} else {
for (const value of Object.values(node)) {
count += await decryptHistoryPayloads(value, decrypt);
}
}
return count;
}
async function serializeHistoryToFixture(events, decrypter) {
let decryptedPayloads = 0;
if (decrypter) {
for (const event of events) {
decryptedPayloads += await decryptHistoryPayloads(event, decrypter);
}
}
const fixture = JSON.parse(historyToJSON({ events }));
return { fixture, eventCount: events.length, decryptedPayloads };
}
var init_capture_history = __esm({
"libs/mesh-cli/src/utils/capture-history.ts"() {
"use strict";
}
});
// libs/mesh-cli/src/commands/temporal.ts
init_log();
import { spawnSync } from "node:child_process";
import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "node:fs";
import { homedir } from "node:os";
import { dirname as dirname4, join as join4, resolve as resolve2 } from "node:path";
// libs/mesh-cli/src/utils/recover-conversation.ts
async function classifyHistoryEvents(rawEvents, decodePayload) {
const activityTypeByScheduledId = /* @__PURE__ */ new Map();
const events = [];
for (const event of rawEvents) {
const eventId = String(event.eventId);
const scheduled = event.activityTaskScheduledEventAttributes;
if (scheduled) {
const name = scheduled.activityType?.name;
if (name) activityTypeByScheduledId.set(eventId, name);
continue;
}
const signal = event.workflowExecutionSignaledEventAttributes;
if (signal?.signalName === "submit_user_message") {
events.push({
eventId,
kind: "userSignal",
payload: await decodePayload(signal.input?.payloads)
});
continue;
}
const completed = event.activityTaskCompletedEventAttributes;
if (completed) {
const scheduledId = String(completed.scheduledEventId);
if (activityTypeByScheduledId.get(scheduledId) === "callLLM") {
events.push({
eventId,
kind: "callLLMResult",
payload: await decodePayload(completed.result?.payloads)
});
}
}
}
return events;
}
async function extractSnapshotMessages(rawEvents, decodePayload) {
let lastCallLLMScheduledId;
let lastCallLLMInputPayloads;
const activityTypeByScheduledId = /* @__PURE__ */ new Map();
let lastCallLLMResultPayloads;
for (const event of rawEvents) {
const eventId = String(event.eventId);
const scheduled = event.activityTaskScheduledEventAttributes;
if (scheduled) {
const name = scheduled.activityType?.name;
if (name) activityTypeByScheduledId.set(eventId, name);
if (name === "callLLM") {
lastCallLLMScheduledId = eventId;
lastCallLLMInputPayloads = scheduled.input?.payloads;
lastCallLLMResultPayloads = void 0;
}
continue;
}
const completed = event.activityTaskCompletedEventAttributes;
if (completed) {
const scheduledId = String(completed.scheduledEventId);
if (activityTypeByScheduledId.get(scheduledId) === "callLLM") {
lastCallLLMResultPayloads = completed.result?.payloads;
}
}
}
if (lastCallLLMScheduledId === void 0) return [];
const inputArgs = await decodePayload(lastCallLLMInputPayloads);
const inputMessages = messagesFrom(inputArgs);
const result = await decodePayload(lastCallLLMResultPayloads);
const resultMessages = messagesFrom(result);
return [...inputMessages, ...resultMessages];
}
function messagesFrom(payload) {
if (isRecord(payload) && Array.isArray(payload.messages)) {
return payload.messages;
}
return [];
}
function extractText(content) {
if (typeof content === "string") return content;
if (Array.isArray(content)) {
return content.filter(
(p) => !!p && typeof p === "object" && p.type === "text"
).map((p) => typeof p.text === "string" ? p.text : "").join("");
}
return "";
}
function summarizeArgs(args) {
try {
const str = JSON.stringify(args ?? {});
return str.length > 100 ? str.substring(0, 97) + "..." : str;
} catch {
return "...";
}
}
function formatToolCall(part) {
const name = typeof part.toolName === "string" ? part.toolName : "?";
const args = "input" in part ? part.input : part.args;
return `${name}(${summarizeArgs(args)})`;
}
function isRecord(v) {
return !!v && typeof v === "object";
}
function extractUserText(payload) {
if (!isRecord(payload)) return null;
const messages = payload.messages;
if (Array.isArray(messages)) {
const parts = [];
for (const m of messages) {
if (isRecord(m) && m.role === "user") {
const t = extractText(m.content);
if (t) parts.push(t);
}
}
if (parts.length > 0) return parts.join("\n");
}
for (const field of ["content", "text", "message"]) {
const v = payload[field];
if (typeof v === "string" && v) return v;
const t = extractText(v);
if (t) return t;
}
return null;
}
function extractAssistantTurn(payload) {
if (!isRecord(payload)) return null;
const messages = payload.messages;
if (!Array.isArray(messages)) {
const t = extractText(payload.content) || extractText(payload.text);
return t ? { text: t, toolCalls: [] } : null;
}
let text = "";
const toolCalls = [];
for (const m of messages) {
if (!isRecord(m) || m.role !== "assistant") continue;
text += extractText(m.content);
if (Array.isArray(m.content)) {
for (const p of m.content) {
if (isRecord(p) && p.type === "tool-call") {
toolCalls.push(formatToolCall(p));
}
}
}
}
if (!text && toolCalls.length === 0) return null;
return { text, toolCalls };
}
function reconstructTranscript(events) {
const turns = [];
for (const event of events) {
if (event.kind === "userSignal") {
const text = extractUserText(event.payload);
turns.push(
text !== null ? { role: "user", text, eventId: event.eventId } : { role: "user", text: placeholder(event.eventId), eventId: event.eventId }
);
} else if (event.kind === "callLLMResult") {
const turn = extractAssistantTurn(event.payload);
if (turn) {
turns.push({
role: "assistant",
text: turn.text,
eventId: event.eventId,
...turn.toolCalls.length > 0 ? { toolCalls: turn.toolCalls } : {}
});
} else {
turns.push({
role: "assistant",
text: placeholder(event.eventId),
eventId: event.eventId
});
}
}
}
return turns;
}
function placeholder(eventId) {
return `_[unparseable turn: ${eventId}]_`;
}
function renderTranscriptMarkdown(turns) {
const blocks = [];
for (const turn of turns) {
const heading = turn.role === "user" ? "## User" : "## Assistant";
const lines = [heading, ""];
if (turn.text) lines.push(turn.text);
if (turn.toolCalls && turn.toolCalls.length > 0) {
if (turn.text) lines.push("");
for (const tc of turn.toolCalls) lines.push(`\u2192 tool: ${tc}`);
}
blocks.push(lines.join("\n"));
}
return blocks.join("\n\n") + "\n";
}
// libs/mesh-cli/src/commands/temporal.ts
init_pulumi();
init_aws_auth();
init_temporal_auth();
// libs/mesh-cli/src/utils/temporal-codec.ts
init_log();
import { execFileSync as execFileSync5 } from "child_process";
import { webcrypto as crypto2 } from "node:crypto";
function resolveTemporalEncodingKeyFromK8s(namespace) {
const secretName = `${namespace}-temporal-encoding-key`;
try {
const b64 = execFileSync5(
"kubectl",
[
"get",
"secret",
secretName,
"-n",
namespace,
"-o",
"jsonpath={.data.TEMPORAL_ENCODING_KEY}"
],
{ encoding: "utf-8", timeout: 1e4, stdio: ["pipe", "pipe", "pipe"] }
).trim();
if (!b64) return void 0;
return Buffer.from(b64, "base64").toString("utf-8");
} catch {
return void 0;
}
}
var ENCRYPTED_ENCODING = "binary/encrypted";
var IV_LENGTH = 12;
async function deriveKey(encodingKey) {
const keyData = await crypto2.subtle.digest(
"SHA-256",
new TextEncoder().encode(encodingKey)
);
return crypto2.subtle.importKey(
"raw",
keyData,
{ name: "AES-GCM" },
false,
["decrypt"]
);
}
async function aesGcmDecrypt(data, key) {
const iv = data.slice(0, IV_LENGTH);
const ciphertextWithTag = data.slice(IV_LENGTH);
const decrypted = await crypto2.subtle.decrypt(
{ name: "AES-GCM", iv },
key,
ciphertextWithTag
);
return new Uint8Array(decrypted);
}
async function createPayloadDecrypter(encodingKey) {
const key = await deriveKey(encodingKey);
return async (payload) => {
if (!payload?.metadata) return null;
const encodingBytes = payload.metadata["encoding"];
if (!encodingBytes) return null;
const encoding = new TextDecoder().decode(encodingBytes);
if (encoding !== ENCRYPTED_ENCODING) return null;
if (!payload.data) return "[encrypted payload missing data]";
let inner;
try {
const decrypted = await aesGcmDecrypt(payload.data, key);
inner = decodePayloadProtobuf(decrypted);
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
return `[undecryptable: ${msg}]`;
}
return decodeInnerPayload(inner);
};
}
async function createRawPayloadDecrypter(encodingKey) {
const key = await deriveKey(encodingKey);
return async (payload) => {
if (!payload?.metadata) return null;
const encodingBytes = payload.metadata["encoding"];
if (!encodingBytes) return null;
const encoding = new TextDecoder().decode(encodingBytes);
if (encoding !== ENCRYPTED_ENCODING) return null;
if (!payload.data) {
throw new Error("encrypted payload has no data to decrypt");
}
const decrypted = await aesGcmDecrypt(payload.data, key);
return decodePayloadProtobuf(decrypted);
};
}
function readVarint(buf, pos) {
let value = 0;
let shift = 0;
let next = pos;
while (next < buf.length) {
const byte = buf[next];
next += 1;
value |= (byte & 127) << shift;
if ((byte & 128) === 0) return { value, next };
shift += 7;
if (shift >= 32) throw new Error("varint too large");
}
throw new Error("truncated varint");
}
function readLengthDelimited(buf, pos) {
const { value: length, next: afterLen } = readVarint(buf, pos);
const end = afterLen + length;
if (end > buf.length) throw new Error("truncated length-delimited field");
return { bytes: buf.slice(afterLen, end), next: end };
}
function decodePayloadProtobuf(buf) {
const metadata = {};
let data;
let pos = 0;
while (pos < buf.length) {
const { value: tag, next: afterTag } = readVarint(buf, pos);
const fieldNumber = tag >>> 3;
const wireType = tag & 7;
pos = afterTag;
if (wireType === 2) {
const { bytes, next } = readLengthDelimited(buf, pos);
pos = next;
if (fieldNumber === 1) {
const entry = decodeMetadataEntry(bytes);
if (entry) metadata[entry.key] = entry.value;
} else if (fieldNumber === 2) {
data = bytes;
}
} else if (wireType === 0) {
pos = readVarint(buf, pos).next;
} else if (wireType === 1) {
pos += 8;
} else if (wireType === 5) {
pos += 4;
} else {
throw new Error(`unsupported wire type ${wireType} at pos ${pos}`);
}
}
return { metadata, data: data ?? new Uint8Array(0) };
}
function decodeMetadataEntry(buf) {
let key;
let value;
let pos = 0;
while (pos < buf.length) {
const { value: tag, next: afterTag } = readVarint(buf, pos);
const fieldNumber = tag >>> 3;
const wireType = tag & 7;
pos = afterTag;
if (wireType !== 2) {
if (wireType === 0) pos = readVarint(buf, pos).next;
else if (wireType === 1) pos += 8;
else if (wireType === 5) pos += 4;
else throw new Error(`unsupported metadata wire type ${wireType}`);
continue;
}
const { bytes, next } = readLengthDelimited(buf, pos);
pos = next;
if (fieldNumber === 1) key = new TextDecoder().decode(bytes);
else if (fieldNumber === 2) value = bytes;
}
if (key === void 0 || value === void 0) return null;
return { key, value };
}
function decodeInnerPayload(payload) {
const meta = payload.metadata ?? {};
const encodingBytes = meta["encoding"];
const encoding = encodingBytes ? new TextDecoder().decode(encodingBytes) : "unknown";
const data = payload.data ?? new Uint8Array(0);
if (encoding === "binary/null") return "null";
if (encoding === "json/plain") {
const text = new TextDecoder().decode(data);
try {
return JSON.stringify(JSON.parse(text), null, 2);
} catch {
return text;
}
}
if (encoding === "binary/plain") return `[binary, ${data.length} bytes]`;
return `[encoding=${encoding}, ${data.length} bytes]`;
}
function resolveEncodingKey(namespace) {
const fromK8s = resolveTemporalEncodingKeyFromK8s(namespace);
const key = fromK8s ?? process.env.TEMPORAL_ENCODING_KEY;
if (!key) {
logWarn(
`Could not read TEMPORAL_ENCODING_KEY from secret ${namespace}/${namespace}-temporal-encoding-key, and TEMPORAL_ENCODING_KEY is not set. Configure kubectl (namespace read access), or export TEMPORAL_ENCODING_KEY (e.g. from a \`mesh dev\` worker env).`
);
return void 0;
}
logSuccess(
fromK8s ? `Resolved encoding key from ${namespace}-temporal-encoding-key` : "Resolved encoding key from TEMPORAL_ENCODING_KEY env"
);
return key;
}
async function buildDecrypterForNamespace(namespace) {
const key = resolveEncodingKey(namespace);
if (!key) return void 0;
return createPayloadDecrypter(key);
}
async function buildRawDecrypterForNamespace(namespace) {
const key = resolveEncodingKey(namespace);
if (!key) return void 0;
return createRawPayloadDecrypter(key);
}
// libs/mesh-cli/src/commands/temporal.ts
async function resolveConnection(options) {
if (options.address && options.namespace) {
return { address: options.address, namespace: options.namespace };
}
if (!options.address && process.env.TEMPORAL_ADDRESS) {
const address = process.env.TEMPORAL_ADDRESS;
const namespace2 = options.namespace ?? process.env.TEMPORAL_NAMESPACE;
if (namespace2) return { address, namespace: namespace2 };
}
const appRoot = findAppRoot(process.cwd());
if (!appRoot) {
throw new Error(
"Not in a Mesh app directory (no Pulumi.yaml found).\nRun from an app directory, or provide --address and --namespace."
);
}
const stack = options.stack ?? getCurrentStack(appRoot);
if (!stack) {
const stacks = findStackConfigs(appRoot);
throw new Error(
`No Pulumi stack selected.
` + (stacks.length > 0 ? `Available: ${stacks.join(", ")}
Use: mesh temporal --stack <name> ...` : "No stack configs found in this directory.")
);
}
const sa = stack ? ["--stack", stack] : [];
const deployerRole = readStackConfig(appRoot, stack, "mesh:deployerRole");
const awsCreds = deployerRole ? (await resolveAwsCredentials(deployerRole, appRoot, stack))?.env : void 0;
let devOutput;
let appOutput;
try {
const result = pulumiStackOutput(appRoot, "app", sa, awsCreds);
appOutput = JSON.parse(result);
devOutput = appOutput.dev ?? appOutput;
} catch {
try {
const result = pulumiStackOutput(appRoot, "dev", sa, awsCreds);
devOutput = JSON.parse(result);
appOutput = devOutput;
} catch {
throw new Error(
`Could not read stack output for '${stack}'.
Ensure you've run: mesh deploy up
Or provide --address and --namespace explicitly.`
);
}
}
const tunnel = devOutput?.tunnels?.temporal;
const namespace = options.namespace ?? appOutput?.namespace ?? devOutput?.namespace;
if (!tunnel) {
throw new Error("No temporal tunnel found in stack output.");
}
if (!namespace) {
throw new Error("No temporal namespace found in stack output.");
}
const platform = devOutput?.platform ?? appOutput?.platform;
const auth = platform ? {
tenant: platform.tenant ?? "mesh",
env: platform.env ?? "dev",
platformName: platform.name ?? platform.tenant ?? "mesh"
} : void 0;
return {
address: options.address ?? `${tunnel.host}:${tunnel.port}`,
namespace,
auth
};
}
async function resolveBearerToken(auth) {
try {
const haveEnvCreds = !!process.env.TEMPORAL_AUTH_CLIENT_ID && !!process.env.ZITADEL_ISSUER;
const vars = !haveEnvCreds && auth ? await resolveTemporalAuth(auth.tenant, auth.env, auth.platformName) : {};
return await acquireTemporalBearer(vars);
} catch (err) {
logWarn(
`Temporal auth unavailable (${err instanceof Error ? err.message : String(err)}); connecting unauthenticated`
);
return void 0;
}
}
var EVENT_TYPE_NAMES = {
1: "WorkflowExecutionStarted",
2: "WorkflowExecutionCompleted",
3: "WorkflowExecutionFailed",
4: "WorkflowExecutionTimedOut",
5: "WorkflowTaskScheduled",
6: "WorkflowTaskStarted",
7: "WorkflowTaskCompleted",
8: "WorkflowTaskTimedOut",
9: "WorkflowTaskFailed",
10: "ActivityTaskScheduled",
11: "ActivityTaskStarted",
12: "ActivityTaskCompleted",
13: "ActivityTaskFailed",
14: "ActivityTaskTimedOut",
15: "ActivityTaskCancelRequested",
16: "ActivityTaskCanceled",
17: "TimerStarted",
18: "TimerFired",
19: "TimerCanceled",
20: "WorkflowExecutionCancelRequested",
21: "WorkflowExecutionCanceled",
24: "WorkflowExecutionSignaled",
// not 22/23 which were removed
25: "WorkflowExecutionTerminated",
26: "WorkflowExecutionSignaled",
29: "WorkflowPropertiesModified",
40: "ChildWorkflowExecutionStarted",
41: "ChildWorkflowExecutionCompleted",
42: "ChildWorkflowExecutionFailed",
43: "ChildWorkflowExecutionCanceled",
44: "ChildWorkflowExecutionTimedOut",
45: "ChildWorkflowExecutionTerminated",
46: "WorkflowPropertiesModified",
48: "NexusOperationScheduled",
49: "NexusOperationStarted",
50: "NexusOperationCompleted",
51: "NexusOperationFailed",
52: "NexusOperationCanceled",
53: "NexusOperationTimedOut"
};
var STATUS_NAMES = {
0: "UNSPECIFIED",
1: "RUNNING",
2: "COMPLETED",
3: "FAILED",
4: "CANCELED",
5: "TERMINATED",
6: "CONTINUED_AS_NEW",
7: "TIMED_OUT"
};
async function describeWorkflow(workflowId, runId, options) {
const conn = await connect(options);
try {
const desc = await conn.connection.workflowService.describeWorkflowExecution({
namespace: conn.namespace,
execution: { workflowId, runId }
});
const info = desc.workflowExecutionInfo;
if (!info) {
logError("No workflow execution info returned.");
return;
}
console.log(`Workflow: ${workflowId}`);
console.log(`Run ID: ${info.execution?.runId ?? runId ?? "?"}`);
console.log(`Type: ${info.type?.name ?? "?"}`);
console.log(`Status: ${STATUS_NAMES[info.status ?? 0] ?? info.status}`);
console.log(`Task Queue: ${info.taskQueue ?? "?"}`);
console.log(`Namespace: ${conn.namespace}`);
const pending = desc.pendingActivities ?? [];
if (pending.length > 0) {
console.log(`
Pending Activities (${pending.length}):`);
for (const pa of pending) {
console.log(` - ${pa.activityType?.name ?? "?"} (attempt ${pa.attempt}, state: ${pa.state})`);
}
}
const pendingNexus = desc.pendingNexusOperations ?? [];
if (pendingNexus.length > 0) {
console.log(`
Pending Nexus Operations (${pendingNexus.length}):`);
for (const pn of pendingNexus) {
console.log(` - ${pn.operation ?? "?"} \u2192 ${pn.endpoint ?? "?"} (state: ${pn.state})`);
}
}
} finally {
conn.connection.close();
}
}
async function* iterHistoryEvents(workflowService, namespace, workflowId, runId, maximumPageSize = 100) {
let nextPageToken;
do {
const resp = await workflowService.getWorkflowExecutionHistory({
namespace,
execution: { workflowId, runId },
maximumPageSize,
nextPageToken
});
for (const event of resp.history?.events ?? []) {
yield event;
}
nextPageToken = resp.nextPageToken?.length ? resp.nextPageToken : void 0;
} while (nextPageToken);
}
async function showHistory(workflowId, runId, options) {
const conn = await connect(options);
const maxEvents = options.follow ? Infinity : parseInt(options.limit ?? "200", 10);
const skipTypes = /* @__PURE__ */ new Set([5, 6, 7, 8, 9]);
const decrypter = options.showPayloads ? await buildDecrypterForNamespace(conn.namespace) : void 0;
try {
let totalShown = 0;
let lastEventId = 0;
console.log(`History for ${workflowId} (namespace: ${conn.namespace})${options.follow ? " [following]" : ""}
`);
for await (const event of iterHistoryEvents(
conn.connection.workflowService,
conn.namespace,
workflowId,
runId
)) {
lastEventId = Number(event.eventId);
const eventType = event.eventType ?? 0;
if (options.compact !== false && skipTypes.has(eventType)) continue;
await printEvent(event, decrypter);
totalShown++;
if (totalShown >= maxEvents) break;
}
if (!options.follow) {
console.log(`
${totalShown} events shown.`);
return;
}
const terminalTypes = /* @__PURE__ */ new Set([2, 3, 4, 21, 27, 28]);
let done = false;
while (!done) {
try {
const resp = await conn.connection.workflowService.getWorkflowExecutionHistory({
namespace: conn.namespace,
execution: { workflowId, runId },
maximumPageSize: 100,
waitNewEvent: true,
// Start after the last event we've seen
...lastEventId > 0 ? { skipArchival: true } : {}
});
for (const event of resp.history?.events ?? []) {
const eid = Number(event.eventId);
if (eid <= lastEventId) continue;
lastEventId = eid;
const eventType = event.eventType ?? 0;
if (options.compact !== false && skipTypes.has(eventType)) continue;
await printEvent(event, decrypter);
totalShown++;
if (terminalTypes.has(eventType)) {
done = true;
}
}
} catch (err) {
if (err?.code === 5 || err?.message?.includes("not found")) {
console.log("\nWorkflow completed or not found.");
done = true;
} else {
throw err;
}
}
}
console.log(`
${totalShown} events shown (workflow ended).`);
} finally {
conn.connection.close();
}
}
async function printEvent(event, decrypter) {
const eventType = event.eventType ?? 0;
const typeName = EVENT_TYPE_NAMES[eventType] ?? `Unknown(${eventType})`;
const id = String(event.eventId).padStart(4);
const detail = extractEventDetail(event);
const detailStr = detail ? ` \u2014 ${detail}` : "";
console.log(`${id} ${typeName}${detailStr}`);
if (!decrypter) return;
for (const group of extractEventPayloads(event)) {
const rendered = await renderPayloads(group.payloads, decrypter);
if (rendered === null) continue;
console.log(` ${group.label}:`);
for (const line of rendered.split("\n")) {
console.log(` ${line}`);
}
}
}
function extractEventPayloads(event) {
const groups = [];
const pushIfAny = (label, payloads) => {
if (payloads && payloads.length > 0) groups.push({ label, payloads });
};
if (event.workflowExecutionStartedEventAttributes) {
pushIfAny("input", event.workflowExecutionStartedEventAttributes.input?.payloads);
}
if (event.workflowExecutionCompletedEventAttributes) {
pushIfAny("result", event.workflowExecutionCompletedEventAttributes.result?.payloads);
}
if (event.workflowExecutionFailedEventAttributes) {
const failure = event.workflowExecutionFailedEventAttributes.failure;
if (failure?.message) groups.push({ label: "failure", payloads: [{ __failure: failure }] });
}
if (event.workflowExecutionSignaledEventAttributes) {
pushIfAny("signal input", event.workflowExecutionSignaledEventAttributes.input?.payloads);
}
if (event.activityTaskScheduledEventAttributes) {
pushIfAny("input", event.activityTaskScheduledEventAttributes.input?.payloads);
}
if (event.activityTaskCompletedEventAttributes) {
pushIfAny("result", event.activityTaskCompletedEventAttributes.result?.payloads);
}
if (event.activityTaskFailedEventAttributes) {
const failure = event.activityTaskFailedEventAttributes.failure;
if (failure?.message) groups.push({ label: "failure", payloads: [{ __failure: failure }] });
}
if (event.nexusOperationScheduledEventAttributes) {
const input = event.nexusOperationScheduledEventAttributes.input;
if (input) pushIfAny("input", [input]);
}
if (event.nexusOperationCompletedEventAttributes) {
const result = event.nexusOperationCompletedEventAttributes.result;
if (result) pushIfAny("result", [result]);
}
if (event.nexusOperationFailedEventAttributes) {
const failure = event.nexusOperationFailedEventAttributes.failure;
if (failure?.message) groups.push({ label: "failure", payloads: [{ __failure: failure }] });
}
return groups;
}
async function renderPayloads(payloads, decrypter) {
const parts = [];
for (const payload of payloads) {
if (payload && typeof payload === "object" && "__failure" in payload) {
const failure = payload.__failure;
parts.push(renderFailure(failure));
continue;
}
const decoded = await decrypter(payload);
if (decoded === null) {
parts.push("[non-encrypted payload]");
} else {
parts.push(decoded);
}
}
return parts.length > 0 ? parts.join("\n---\n") : null;
}
function renderFailure(failure) {
const lines = [failure.message ?? "(no message)"];
if (failure.stackTrace) lines.push(failure.stackTrace);
return lines.join("\n");
}
function extractEventDetail(event) {
if (event.activityTaskScheduledEventAttributes) {
const a = event.activityTaskScheduledEventAttributes;
return `activity: ${a.activityType?.name ?? "?"}, queue: ${a.taskQueue?.name ?? "?"}`;
}
if (event.activityTaskCompletedEventAttributes) {
return `scheduledId: ${event.activityTaskCompletedEventAttributes.scheduledEventId}`;
}
if (event.activityTaskFailedEventAttributes) {
const a = event.activityTaskFailedEventAttributes;
const msg = a.failure?.message ?? "";
return `scheduledId: ${a.scheduledEventId}${msg ? `, error: ${msg.slice(0, 120)}` : ""}`;
}
if (event.activityTaskTimedOutEventAttributes) {
return `scheduledId: ${event.activityTaskTimedOutEventAttributes.scheduledEventId}`;
}
if (event.nexusOperationScheduledEventAttributes) {
const a = event.nexusOperationScheduledEventAttributes;
return `endpoint: ${a.endpoint ?? "?"}, operation: ${a.operation ?? "?"}, service: ${a.service ?? "?"}`;
}
if (event.nexusOperationCompletedEventAttributes) {
return `scheduledId: ${event.nexusOperationCompletedEventAttributes.scheduledEventId}`;
}
if (event.nexusOperationFailedEventAttributes) {
const a = event.nexusOperationFailedEventAttributes;
return `scheduledId: ${a.scheduledEventId}, error: ${a.failure?.message?.slice(0, 120) ?? "?"}`;
}
if (event.nexusOperationTimedOutEventAttributes) {
return `scheduledId: ${event.nexusOperationTimedOutEventAttributes.scheduledEventId}`;
}
if (event.workflowExecutionSignaledEventAttributes) {
return `signal: ${event.workflowExecutionSignaledEventAttributes.signalName ?? "?"}`;
}
if (event.workflowExecutionStartedEventAttributes) {
const a = event.workflowExecutionStartedEventAttributes;
return `type: ${a.workflowType?.name ?? "?"}, queue: ${a.taskQueue?.name ?? "?"}`;
}
if (event.workflowExecutionCompletedEventAttributes) return null;
if (event.workflowExecutionFailedEventAttributes) {
return `error: ${event.workflowExecutionFailedEventAttributes.failure?.message?.slice(0, 120) ?? "?"}`;
}
if (event.timerStartedEventAttributes) {
const dur = event.timerStartedEventAttributes.startToFireTimeout;
return `timerId: ${event.timerStartedEventAttributes.timerId}, duration: ${dur?.seconds ?? "?"}s`;
}
if (event.timerFiredEventAttributes) {
return `timerId: ${event.timerFiredEventAttributes.timerId}`;
}
return null;
}
async function recoverConversation(workflowId, runId, options) {
const conn = await connect(options);
const decrypter = await buildDecrypterForNamespace(conn.namespace);
if (!decrypter) {
conn.connection.close();
throw new Error(
"Cannot decrypt conversation payloads: no encoding key available.\nSet TEMPORAL_ENCODING_KEY (e.g. from a `mesh dev` worker env) or configure kubectl namespace read access, then retry."
);
}
try {
const decodePayload = async (payloads) => {
const first = payloads?.[0];
if (first === void 0) return void 0;
const decoded = await decrypter(first);
if (decoded === null) return void 0;
try {
return JSON.parse(decoded);
} catch {
return void 0;
}
};
const rawEvents = [];
for await (const event of iterHistoryEvents(
conn.connection.workflowService,
conn.namespace,
workflowId,
runId
)) {
rawEvents.push(event);
}
if (options.snapshot) {
const messages = await extractSnapshotMessages(rawEvents, decodePayload);
const blob = {
conversationId: workflowId,
messages,
artifacts: [],
focus: null
};
const output2 = JSON.stringify(blob, null, 2) + "\n";
if (options.out) {
writeFileSync2(options.out, output2);
logSuccess(
`Wrote snapshot (${messages.length} messages) to ${options.out}`
);
} else {
process.stdout.write(output2);
}
return;
}
const events = await classifyHistoryEvents(rawEvents, decodePayload);
const turns = reconstructTranscript(events);
const output = options.json ? JSON.stringify(
{
turns: turns.map((t) => ({
role: t.role,
text: t.text,
...t.toolCalls ? { toolCalls: t.toolCalls } : {}
}))
},
null,
2
) + "\n" : renderTranscriptMarkdown(turns);
if (options.out) {
writeFileSync2(options.out, output);
logSuccess(`Wrote ${turns.length} turns to ${options.out}`);
} else {
process.stdout.write(output);
}
} finally {
conn.connection.close();
}
}
async function captureHistory(workflowId, runId, options) {
const conn = await connect(options);
const decrypter = await buildRawDecrypterForNamespace(conn.namespace);
if (!decrypter) {
conn.connection.close();
throw new Error(
"Cannot decrypt history payloads: no encoding key available.\nA replay fixture needs decrypted payloads. Set TEMPORAL_ENCODING_KEY (e.g. from a `mesh dev` worker env) or configure kubectl namespace read access, then retry."
);
}
try {
const events = [];
for await (const event of iterHistoryEvents(
conn.connection.workflowService,
conn.namespace,
workflowId,
runId
)) {
events.push(event);
}
if (events.length === 0) {
throw new Error(
`No history events found for workflow '${workflowId}'` + (runId ? ` (run ${runId})` : "") + `. Check the workflow id and namespace (${conn.namespace}).`
);
}
const { serializeHistoryToFixture: serializeHistoryToFixture2 } = await Promise.resolve().then(() => (init_capture_history(), capture_history_exports));
const { fixture, eventCount, decryptedPayloads } = await serializeHistoryToFixture2(events, decrypter);
const outPath = options.out ?? join4(homedir(), ".mesh", "replay-histories", `${sanitizeFileId(workflowId)}.json`);
mkdirSync2(dirname4(outPath), { recursive: true });
if (options.out) warnIfNotGitIgnored(options.out);
writeFileSync2(outPath, JSON.stringify(fixture, null, 2) + "\n");
logSuccess(
`Wrote replay history (${eventCount} events, ${decryptedPayloads} payloads decrypted) to ${outPath}`
);
logWarn(
"\u26A0 This file contains DECRYPTED payloads \u2014 real customer/conversation data.\n For LOCAL replay debugging ONLY. Do NOT commit it. (CI golden fixtures are synthetic; this is the debugging counterpart.)"
);
} finally {
conn.connection.close();
}
}
function sanitizeFileId(id) {
return id.replace(/[^A-Za-z0-9._-]/g, "_");
}
function warnIfNotGitIgnored(outPath) {
const abs = resolve2(outPath);
try {
const res = spawnSync("git", ["-C", dirname4(abs), "check-ignore", "-q", abs], {
stdio: "ignore"
});
if (res.status !== 1) return;
} catch {
return;
}
logWarn(
`\u26A0\u26A0\u26A0 OUTPUT PATH IS INSIDE A GIT REPO AND IS *NOT* GITIGNORED \u26A0\u26A0\u26A0
${abs}
This fixture contains DECRYPTED production payloads. Add it to .gitignore or move it outside the repo \u2014 do NOT commit it.`
);
}
async function terminateWorkflow(workflowId, runId, options) {
const conn = await connect(options);
try {
await conn.connection.workflowService.terminateWorkflowExecution({
namespace: conn.namespace,
workflowExecution: { workflowId, runId },
reason: options.reason ?? "terminated via mesh temporal",
identity: "mesh-cli"
});
logSuccess(
`Terminated ${workflowId}${runId ? ` (run ${runId})` : ""} in namespace ${conn.namespace}`
);
} finally {
conn.connection.close();
}
}
async function connect(options) {
const info = await resolveConnection(options);
logInfo(`Connecting to ${info.address} (namespace: ${info.namespace})`);
const { Connection } = await import("@temporalio/client");
const token = await resolveBearerToken(info.auth);
const metadata = token ? { authorization: `Bearer ${token}` } : void 0;
const connection = await Connection.connect({
address: info.address,
metadata,
channelArgs: { "grpc.max_receive_message_length": 256 * 1024 * 1024 }
});
return { connection, namespace: info.namespace };
}
function registerTemporalCommands(program) {
const temporal = program.command("temporal").description("Inspect Temporal workflows for the current app/stack");
const sharedOpts = (cmd) => cmd.option("--stack <stack>", "Pulumi stack name (auto-detected from current directory)").option("--address <addr>", "Temporal server address (default: from stack output)").option("--namespace <ns>", "Temporal namespace (default: from stack output)");
sharedOpts(
temporal.command("describe <workflowId> [runId]").description("Show workflow status and metadata")
).action(async (workflowId, runId, opts) => {
try {
await describeWorkflow(workflowId, runId, opts);
} catch (err) {
logError(err instanceof Error ? err.message : String(err));
process.exitCode = 1;
}
});
sharedOpts(
temporal.command("terminate <workflowId> [runId]").description("Terminate a running workflow (e.g. a wedged/looping conversation)")
).option("--reason <reason>", "Termination reason (recorded in workflow history)").action(async (workflowId, runId, opts) => {
try {
await terminateWorkflow(workflowId, runId, opts);
} catch (err) {
logError(err instanceof Error ? err.message : String(err));
process.exitCode = 1;
}
});
sharedOpts(
temporal.command("history <workflowId> [runId]").description("Show workflow event history").option("-n, --limit <n>", "Maximum events to show (default: 200)").option("-f, --follow", "Tail the history, waiting for new events until the workflow completes").option("--no-compact", "Show all events including WorkflowTask scheduling noise").option("-p, --show-payloads", "Decrypt and print activity inputs/outputs (reads TEMPORAL_ENCODING_KEY from the app's K8s secret via kubectl)")
).action(async (workflowId, runId, opts) => {
try {
await showHistory(workflowId, runId, opts);
} catch (err) {
logError(err instanceof Error ? err.message : String(err));
process.exitCode = 1;
}
});
sharedOpts(
temporal.command("recover-conversation <workflowId> [runId]").description(
"Reconstruct an agent conversation transcript from durable Temporal history (for when the worker can no longer replay it). Requires the encoding key \u2014 reads TEMPORAL_ENCODING_KEY (or the app's K8s secret)."
).option("--out <path>", "Write the transcript to a file instead of stdout").option("--json", "Emit a structured { turns: [...] } JSON array instead of markdown").option(
"--snapshot",
"Emit a store-ready conversation blob { conversationId, messages, artifacts, focus } with STRUCTURED ModelMessage[] (tool-call parts intact) for a full-fidelity backfill"
)
).action(async (workflowId, runId, opts) => {
try {
await recoverConversation(workflowId, runId, opts);
} catch (err) {
logError(err instanceof Error ? err.message : String(err));
process.exitCode = 1;
}
});
sharedOpts(
temporal.command("capture-history <workflowId> [runId]").description(
"Capture a workflow's full history to a replay fixture JSON (proto3-JSON, DECRYPTED payloads) for LOCAL replay debugging. Requires the encoding key. Defaults to ~/.mesh/replay-histories/<workflowId>.json (outside any repo) \u2014 do NOT commit the output."
).option(
"--out <path>",
"Output path (default: ~/.mesh/replay-histories/<workflowId>.json; a non-gitignored path inside a git repo triggers a loud warning)"
)
).action(async (workflowId, runId, opts) => {
try {
await captureHistory(workflowId, runId, opts);
} catch (err) {
logError(err instanceof Error ? err.message : String(err));
process.exitCode = 1;
}
});
}
export {
connect,
registerTemporalCommands
};