@askexenow/exe-os 0.9.82 → 0.9.84

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "latest": "0.9.5",
3
+ "latest": "0.9.7",
4
4
  "stacks": {
5
5
  "0.9.0": {
6
6
  "version": "0.9.0",
@@ -361,6 +361,142 @@
361
361
  },
362
362
  "date": "2026-05-12",
363
363
  "sourceRef": "stack-v0.9.5"
364
+ },
365
+ "0.9.6": {
366
+ "version": "0.9.6",
367
+ "releasedAt": "2026-05-12T00:00:00Z",
368
+ "notes": "Customer stack lifecycle manager: exe-os stack-update now detects cold-start/partial/existing host state, creates compose/env templates, hydrates generated secrets/license/domain, checks/installs Docker on Ubuntu/Debian with --yes, and then plans/applies the pinned stack. Other services remain v0.9.3.",
369
+ "breakingChanges": [
370
+ {
371
+ "id": "whatsapp_relink_required",
372
+ "title": "WhatsApp QR re-link required for Baileys v7",
373
+ "description": "exe-gateway uses Baileys v7. Existing WhatsApp 6.x linked-device auth state must be backed up and re-linked once with a new QR code.",
374
+ "requiredAction": "Open https://<gateway-domain>/pair/default?token=<admin-token> and scan from WhatsApp → Linked Devices after the update.",
375
+ "expectedDowntimeMinutes": "2-5",
376
+ "requiresConfirmation": true
377
+ }
378
+ ],
379
+ "services": {
380
+ "crm": {
381
+ "env": "CRM_IMAGE_TAG",
382
+ "image": "registry.askexe.com/askexe/exe-crm:v0.9.3",
383
+ "healthUrl": "http://127.0.0.1:3000/healthz",
384
+ "deploymentScope": "customer"
385
+ },
386
+ "wiki": {
387
+ "env": "WIKI_IMAGE_TAG",
388
+ "image": "registry.askexe.com/askexe/exe-wiki:v0.9.3",
389
+ "healthUrl": "http://127.0.0.1:3001/api/ping",
390
+ "deploymentScope": "customer"
391
+ },
392
+ "exed": {
393
+ "env": "EXED_IMAGE_TAG",
394
+ "image": "registry.askexe.com/askexe/exed:v0.9.6",
395
+ "healthUrl": "http://127.0.0.1:8765/health",
396
+ "deploymentScope": "customer"
397
+ },
398
+ "gateway": {
399
+ "env": "GATEWAY_IMAGE_TAG",
400
+ "image": "registry.askexe.com/askexe/exe-gateway:v0.9.3",
401
+ "healthUrl": "http://127.0.0.1:3100/health",
402
+ "deploymentScope": "customer"
403
+ },
404
+ "monitorAgent": {
405
+ "env": "MONITOR_AGENT_IMAGE_TAG",
406
+ "image": "registry.askexe.com/askexe/exe-monitor-agent:v0.9.3",
407
+ "deploymentScope": "customer"
408
+ }
409
+ },
410
+ "releaseDescriptors": {
411
+ "exed": "AskExe/exe-os@stack-v0.9.4:stack.release.json",
412
+ "crm": "AskExe/exe-crm@0.9.3:stack.release.json",
413
+ "wiki": "AskExe/exe-wiki@0.9.3:stack.release.json",
414
+ "gateway": "AskExe/exe-gateway@0.9.3:stack.release.json",
415
+ "db": "AskExe/exe-db@0.9.3:stack.release.json",
416
+ "monitorAgent": "AskExe/exe-monitor@0.9.3:stack.release.json"
417
+ },
418
+ "componentVersions": {
419
+ "exe-os": {
420
+ "repo": "AskExe/exe-os",
421
+ "npmPackage": "@askexenow/exe-os",
422
+ "npmVersion": "0.9.83",
423
+ "image": "registry.askexe.com/askexe/exed:v0.9.4",
424
+ "imageVersion": "0.9.6",
425
+ "note": "Customer stack image tag is 0.9.4; bundled npm package is @askexenow/exe-os@0.9.81. Hygo pulls through registry.askexe.com, which proxies the AskExe-owned GHCR artifact server-side.",
426
+ "sourceRef": "stack-v0.9.4",
427
+ "commit": "main@stack-v0.9.6"
428
+ }
429
+ },
430
+ "date": "2026-05-12",
431
+ "sourceRef": "stack-v0.9.6"
432
+ },
433
+ "0.9.7": {
434
+ "version": "0.9.7",
435
+ "releasedAt": "2026-05-12T00:00:00Z",
436
+ "notes": "Stack update auth unblock: default manifest now uses the packaged signed/customer manifest when available, so cold-start installs do not require update.askexe.com entitlement provisioning. Remote update service remains opt-in via EXE_STACK_MANIFEST.",
437
+ "breakingChanges": [
438
+ {
439
+ "id": "whatsapp_relink_required",
440
+ "title": "WhatsApp QR re-link required for Baileys v7",
441
+ "description": "exe-gateway uses Baileys v7. Existing WhatsApp 6.x linked-device auth state must be backed up and re-linked once with a new QR code.",
442
+ "requiredAction": "Open https://<gateway-domain>/pair/default?token=<admin-token> and scan from WhatsApp → Linked Devices after the update.",
443
+ "expectedDowntimeMinutes": "2-5",
444
+ "requiresConfirmation": true
445
+ }
446
+ ],
447
+ "services": {
448
+ "crm": {
449
+ "env": "CRM_IMAGE_TAG",
450
+ "image": "registry.askexe.com/askexe/exe-crm:v0.9.3",
451
+ "healthUrl": "http://127.0.0.1:3000/healthz",
452
+ "deploymentScope": "customer"
453
+ },
454
+ "wiki": {
455
+ "env": "WIKI_IMAGE_TAG",
456
+ "image": "registry.askexe.com/askexe/exe-wiki:v0.9.3",
457
+ "healthUrl": "http://127.0.0.1:3001/api/ping",
458
+ "deploymentScope": "customer"
459
+ },
460
+ "exed": {
461
+ "env": "EXED_IMAGE_TAG",
462
+ "image": "registry.askexe.com/askexe/exed:v0.9.7",
463
+ "healthUrl": "http://127.0.0.1:8765/health",
464
+ "deploymentScope": "customer"
465
+ },
466
+ "gateway": {
467
+ "env": "GATEWAY_IMAGE_TAG",
468
+ "image": "registry.askexe.com/askexe/exe-gateway:v0.9.3",
469
+ "healthUrl": "http://127.0.0.1:3100/health",
470
+ "deploymentScope": "customer"
471
+ },
472
+ "monitorAgent": {
473
+ "env": "MONITOR_AGENT_IMAGE_TAG",
474
+ "image": "registry.askexe.com/askexe/exe-monitor-agent:v0.9.3",
475
+ "deploymentScope": "customer"
476
+ }
477
+ },
478
+ "releaseDescriptors": {
479
+ "exed": "AskExe/exe-os@stack-v0.9.4:stack.release.json",
480
+ "crm": "AskExe/exe-crm@0.9.3:stack.release.json",
481
+ "wiki": "AskExe/exe-wiki@0.9.3:stack.release.json",
482
+ "gateway": "AskExe/exe-gateway@0.9.3:stack.release.json",
483
+ "db": "AskExe/exe-db@0.9.3:stack.release.json",
484
+ "monitorAgent": "AskExe/exe-monitor@0.9.3:stack.release.json"
485
+ },
486
+ "componentVersions": {
487
+ "exe-os": {
488
+ "repo": "AskExe/exe-os",
489
+ "npmPackage": "@askexenow/exe-os",
490
+ "npmVersion": "0.9.84",
491
+ "image": "registry.askexe.com/askexe/exed:v0.9.4",
492
+ "imageVersion": "0.9.7",
493
+ "note": "Customer stack image tag is 0.9.4; bundled npm package is @askexenow/exe-os@0.9.81. Hygo pulls through registry.askexe.com, which proxies the AskExe-owned GHCR artifact server-side.",
494
+ "sourceRef": "stack-v0.9.4",
495
+ "commit": "main@stack-v0.9.7"
496
+ }
497
+ },
498
+ "date": "2026-05-12",
499
+ "sourceRef": "stack-v0.9.7"
364
500
  }
365
501
  }
366
502
  }
package/dist/bin/cli.js CHANGED
@@ -19745,12 +19745,13 @@ var init_update = __esm({
19745
19745
  });
19746
19746
 
19747
19747
  // src/lib/stack-update.ts
19748
- import { execFileSync as execFileSync4 } from "child_process";
19749
- import { createVerify, verify as verifySignature } from "crypto";
19750
- import { existsSync as existsSync34, mkdirSync as mkdirSync24, readdirSync as readdirSync10, readFileSync as readFileSync29, renameSync as renameSync8, writeFileSync as writeFileSync25 } from "fs";
19748
+ import { execFileSync as execFileSync4, spawnSync } from "child_process";
19749
+ import { createVerify, randomBytes as randomBytes2, verify as verifySignature } from "crypto";
19750
+ import { copyFileSync as copyFileSync5, existsSync as existsSync34, mkdirSync as mkdirSync24, readdirSync as readdirSync10, readFileSync as readFileSync29, renameSync as renameSync8, writeFileSync as writeFileSync25 } from "fs";
19751
19751
  import http from "http";
19752
19752
  import https from "https";
19753
19753
  import path42 from "path";
19754
+ import { fileURLToPath as fileURLToPath5 } from "url";
19754
19755
  function isSignedEnvelope(value) {
19755
19756
  return !!value && typeof value === "object" && "manifest" in value && "signature" in value;
19756
19757
  }
@@ -19985,10 +19986,139 @@ Re-run with --allow-breaking ${missing.map((c) => c.id).join(",")}`
19985
19986
  );
19986
19987
  }
19987
19988
  }
19989
+ function commandSucceeds(cmd, args2 = []) {
19990
+ const res = spawnSync(cmd, args2, { stdio: "ignore" });
19991
+ return res.status === 0;
19992
+ }
19993
+ function shellSucceeds(command) {
19994
+ const res = spawnSync("sh", ["-lc", command], { stdio: "ignore" });
19995
+ return res.status === 0;
19996
+ }
19997
+ function resolvePackageRoot2() {
19998
+ const here = path42.dirname(fileURLToPath5(import.meta.url));
19999
+ const candidates = [
20000
+ path42.resolve(here, "..", ".."),
20001
+ path42.resolve(here, ".."),
20002
+ process.cwd()
20003
+ ];
20004
+ for (const c of candidates) {
20005
+ if (existsSync34(path42.join(c, "package.json")) && existsSync34(path42.join(c, "deploy", "compose", "docker-compose.yml"))) return c;
20006
+ }
20007
+ return process.cwd();
20008
+ }
20009
+ function copyTemplateIfMissing(srcRel, dest, created) {
20010
+ if (existsSync34(dest)) return;
20011
+ const src = path42.join(resolvePackageRoot2(), srcRel);
20012
+ if (!existsSync34(src)) throw new Error(`Missing packaged stack template: ${srcRel}. Reinstall/update exe-os and retry.`);
20013
+ mkdirSync24(path42.dirname(dest), { recursive: true });
20014
+ copyFileSync5(src, dest);
20015
+ created.push(dest);
20016
+ }
20017
+ function installDockerUbuntu(exec2) {
20018
+ if (process.platform !== "linux") throw new Error("Docker auto-install is only supported on Linux. Install Docker manually, then retry.");
20019
+ if (!existsSync34("/etc/os-release")) throw new Error("Cannot detect Linux distro; install Docker manually, then retry.");
20020
+ const osRelease = readFileSync29("/etc/os-release", "utf8");
20021
+ if (!/ID=(ubuntu|debian)|ID_LIKE=.*debian/.test(osRelease)) {
20022
+ throw new Error("Docker auto-install currently supports Ubuntu/Debian only. Install Docker manually, then retry.");
20023
+ }
20024
+ const script = [
20025
+ "set -e",
20026
+ "sudo apt-get update",
20027
+ "sudo apt-get install -y ca-certificates curl gnupg",
20028
+ "sudo install -m 0755 -d /etc/apt/keyrings",
20029
+ "if [ ! -f /etc/apt/keyrings/docker.gpg ]; then curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg; sudo chmod a+r /etc/apt/keyrings/docker.gpg; fi",
20030
+ ". /etc/os-release",
20031
+ 'CODENAME="${VERSION_CODENAME:-bookworm}"',
20032
+ 'if [ "${ID:-}" = "debian" ]; then DOCKER_DISTRO=debian; else DOCKER_DISTRO=ubuntu; fi',
20033
+ `printf 'deb [arch=%s signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/%s %s stable\\n' "$(dpkg --print-architecture)" "$DOCKER_DISTRO" "$CODENAME" | sudo tee /etc/apt/sources.list.d/docker.list >/dev/null`,
20034
+ "sudo apt-get update",
20035
+ "sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin",
20036
+ "sudo systemctl enable --now docker",
20037
+ "sudo docker version >/dev/null",
20038
+ "sudo docker compose version >/dev/null"
20039
+ ].join("\n");
20040
+ exec2("sh", ["-lc", script]);
20041
+ }
20042
+ function randomSecret(bytes = 32) {
20043
+ return randomBytes2(bytes).toString("base64url");
20044
+ }
20045
+ function hydrateEnv(raw, opts) {
20046
+ let next = raw;
20047
+ const license = opts.licenseKey || process.env.EXE_LICENSE_KEY || loadLicense() || "";
20048
+ const domain = opts.domain || process.env.EXE_STACK_DOMAIN || process.env.CUSTOMER_DOMAIN || "";
20049
+ const replacements = {};
20050
+ const env = parseEnv(raw);
20051
+ for (const [key, value] of env.entries()) {
20052
+ if (!/CHANGEME/.test(value)) continue;
20053
+ if (key === "EXE_LICENSE_KEY" && license) replacements[key] = license;
20054
+ else if (key.endsWith("_PASSWORD")) replacements[key] = randomSecret(24);
20055
+ else if (key.endsWith("_SECRET") || key.endsWith("_TOKEN") || key.endsWith("_KEY") || key.endsWith("_SALT")) replacements[key] = value.replace(/CHANGEME[A-Z0-9_]*/g, randomSecret(32));
20056
+ else if (key === "EXED_MCP_TOKEN") replacements[key] = randomSecret(32);
20057
+ }
20058
+ if (domain) next = next.replaceAll("CHANGEME_DOMAIN", domain);
20059
+ next = patchEnv(next, replacements);
20060
+ const remaining = [...parseEnv(next).entries()].filter(([, value]) => /CHANGEME/.test(value)).map(([key, value]) => `${key}=${value}`);
20061
+ return { raw: next, hadPlaceholders: /CHANGEME/.test(raw), remaining: [...new Set(remaining)] };
20062
+ }
20063
+ function bootstrapStackHost(options) {
20064
+ const exec2 = options.exec ?? defaultExec;
20065
+ const createdFiles = [];
20066
+ const actions = [];
20067
+ let dockerInstalled = commandSucceeds("docker", ["version"]);
20068
+ let dockerComposeInstalled = shellSucceeds("docker compose version");
20069
+ if ((!dockerInstalled || !dockerComposeInstalled) && options.installDocker) {
20070
+ actions.push("install_docker");
20071
+ installDockerUbuntu(exec2);
20072
+ dockerInstalled = commandSucceeds("docker", ["version"]);
20073
+ dockerComposeInstalled = shellSucceeds("docker compose version");
20074
+ }
20075
+ copyTemplateIfMissing("deploy/compose/docker-compose.yml", options.composeFile, createdFiles);
20076
+ copyTemplateIfMissing("deploy/compose/.env.customer.example", options.envFile, createdFiles);
20077
+ const brandingDest = path42.join(path42.dirname(options.envFile), "branding.json");
20078
+ copyTemplateIfMissing("deploy/compose/gateway.json", path42.join(path42.dirname(options.envFile), "gateway.json"), createdFiles);
20079
+ if (!existsSync34(brandingDest)) writeFileSync25(brandingDest, JSON.stringify({ brandName: "Hygo", productName: "Hygo OS" }, null, 2) + "\n", { mode: 384 });
20080
+ let envHadPlaceholders = false;
20081
+ let envRemainingPlaceholders = [];
20082
+ if (existsSync34(options.envFile)) {
20083
+ const envRaw = readFileSync29(options.envFile, "utf8");
20084
+ const hydrated = hydrateEnv(envRaw, options);
20085
+ envHadPlaceholders = hydrated.hadPlaceholders;
20086
+ envRemainingPlaceholders = hydrated.remaining;
20087
+ if (hydrated.raw !== envRaw) {
20088
+ writeFileSync25(options.envFile, hydrated.raw, { mode: 384 });
20089
+ actions.push("hydrate_env_secrets");
20090
+ }
20091
+ }
20092
+ return {
20093
+ dockerInstalled,
20094
+ dockerComposeInstalled,
20095
+ composeFileExists: existsSync34(options.composeFile),
20096
+ envFileExists: existsSync34(options.envFile),
20097
+ envHadPlaceholders,
20098
+ envRemainingPlaceholders,
20099
+ licensePresent: !!(options.licenseKey || process.env.EXE_LICENSE_KEY || loadLicense()),
20100
+ createdFiles,
20101
+ actions
20102
+ };
20103
+ }
20104
+ function assertHostReadyForApply(report) {
20105
+ const blockers = [];
20106
+ if (!report.dockerInstalled) blockers.push("Docker is not installed/running. Re-run with --yes to auto-install on Ubuntu/Debian, or install Docker manually.");
20107
+ if (!report.dockerComposeInstalled) blockers.push("Docker Compose plugin is missing. Re-run with --yes to auto-install on Ubuntu/Debian, or install docker-compose-plugin manually.");
20108
+ if (!report.composeFileExists) blockers.push("docker-compose.yml is missing and could not be created.");
20109
+ if (!report.envFileExists) blockers.push(".env is missing and could not be created.");
20110
+ if (!report.licensePresent) blockers.push("Exe OS license key is missing. Run `exe-os setup`, `exe-os --activate <key>`, or pass --license-key.");
20111
+ const hardPlaceholders = report.envRemainingPlaceholders.filter((p) => !/WHATSAPP|API_ROUTER|MONITOR_AGENT/.test(p));
20112
+ if (hardPlaceholders.length > 0) blockers.push(`Required .env placeholders remain: ${hardPlaceholders.join(", ")}`);
20113
+ if (blockers.length > 0) throw new Error(`Stack host is not ready:
20114
+ - ${blockers.join("\n- ")}`);
20115
+ }
19988
20116
  async function runStackUpdate(options) {
19989
20117
  const exec2 = options.exec ?? defaultExec;
19990
20118
  const now2 = options.now ?? (() => /* @__PURE__ */ new Date());
19991
20119
  if (options.rollback) return rollbackStackUpdate(options);
20120
+ const report = bootstrapStackHost({ ...options, installDocker: options.installDocker ?? !!options.yes });
20121
+ if (!options.dryRun) assertHostReadyForApply(report);
19992
20122
  const manifest = await loadStackManifest(options.manifestRef, options.fetchText, options.manifestPublicKey, options.manifestAuthToken);
19993
20123
  const envRaw = readFileSync29(options.envFile, "utf8");
19994
20124
  const plan = createStackUpdatePlan(manifest, envRaw, options.targetVersion);
@@ -20156,12 +20286,16 @@ async function defaultPostJson(url, body, authToken) {
20156
20286
  function defaultStackPaths() {
20157
20287
  const cwdCompose = path42.resolve("docker-compose.yml");
20158
20288
  const cwdEnv = path42.resolve(".env");
20289
+ const packagedManifest = path42.join(resolvePackageRoot2(), "deploy", "stack-manifests", "v0.9.json");
20290
+ const manifestRef = process.env.EXE_STACK_MANIFEST || (existsSync34(packagedManifest) ? packagedManifest : "https://update.askexe.com/stack-manifest.json");
20159
20291
  return {
20160
20292
  composeFile: process.env.EXE_STACK_COMPOSE_FILE || (existsSync34(cwdCompose) ? cwdCompose : "/opt/exe-stack/docker-compose.yml"),
20161
20293
  envFile: process.env.EXE_STACK_ENV_FILE || (existsSync34(cwdEnv) ? cwdEnv : "/opt/exe-stack/.env"),
20162
- manifestRef: process.env.EXE_STACK_MANIFEST || "https://update.askexe.com/stack-manifest.json",
20163
- auditUrl: process.env.EXE_STACK_AUDIT_URL || "https://update.askexe.com/v1/deploy-audits",
20164
- imageCredentialsUrl: process.env.EXE_STACK_IMAGE_CREDENTIALS_URL || "https://update.askexe.com/v1/image-credentials",
20294
+ manifestRef,
20295
+ // Only call update.askexe.com if explicitly configured or if a remote manifest was requested.
20296
+ // Packaged manifests keep cold-start installs unblocked even before update-service entitlements are provisioned.
20297
+ auditUrl: process.env.EXE_STACK_AUDIT_URL || (/^https?:\/\//.test(manifestRef) ? "https://update.askexe.com/v1/deploy-audits" : void 0),
20298
+ imageCredentialsUrl: process.env.EXE_STACK_IMAGE_CREDENTIALS_URL || (/^https?:\/\//.test(manifestRef) ? "https://update.askexe.com/v1/image-credentials" : void 0),
20165
20299
  manifestAuthToken: process.env.EXE_STACK_UPDATE_TOKEN || process.env.EXE_LICENSE_KEY || loadLicense() || void 0,
20166
20300
  manifestPublicKey: loadDefaultPublicKey()
20167
20301
  };
@@ -20205,7 +20339,8 @@ function parseArgs4(args2) {
20205
20339
  rollback: false,
20206
20340
  deploymentPersona: process.env.EXE_STACK_DEPLOYMENT_PERSONA === "askexe-control-plane" ? "askexe-control-plane" : "customer",
20207
20341
  yes: false,
20208
- allowedBreakingChangeIds: []
20342
+ allowedBreakingChangeIds: [],
20343
+ noBootstrap: false
20209
20344
  };
20210
20345
  for (let i = 0; i < args2.length; i++) {
20211
20346
  const arg = args2[i];
@@ -20216,8 +20351,8 @@ function parseArgs4(args2) {
20216
20351
  else if (arg.startsWith("--target=")) opts.targetVersion = arg.split("=")[1];
20217
20352
  else if (arg === "--compose-file") opts.composeFile = next();
20218
20353
  else if (arg.startsWith("--compose-file=")) opts.composeFile = arg.split("=").slice(1).join("=");
20219
- else if (arg === "--env-file") opts.envFile = next();
20220
- else if (arg.startsWith("--env-file=")) opts.envFile = arg.split("=").slice(1).join("=");
20354
+ else if (arg === "--env-file" || arg === "--stack-env-file") opts.envFile = next();
20355
+ else if (arg.startsWith("--env-file=") || arg.startsWith("--stack-env-file=")) opts.envFile = arg.split("=").slice(1).join("=");
20221
20356
  else if (arg === "--lock-file") opts.lockFile = next();
20222
20357
  else if (arg === "--public-key") opts.manifestPublicKey = readFileSync30(next(), "utf8");
20223
20358
  else if (arg.startsWith("--public-key=")) opts.manifestPublicKey = readFileSync30(arg.split("=").slice(1).join("="), "utf8");
@@ -20246,6 +20381,9 @@ function parseArgs4(args2) {
20246
20381
  else if (arg.startsWith("--break-glass=")) opts.breakGlassReason = arg.split("=").slice(1).join("=");
20247
20382
  else if (arg === "--break-glass-audit-file") opts.breakGlassAuditFile = next();
20248
20383
  else if (arg.startsWith("--break-glass-audit-file=")) opts.breakGlassAuditFile = arg.split("=").slice(1).join("=");
20384
+ else if (arg === "--domain") opts.domain = next();
20385
+ else if (arg.startsWith("--domain=")) opts.domain = arg.split("=").slice(1).join("=");
20386
+ else if (arg === "--no-bootstrap") opts.noBootstrap = true;
20249
20387
  else if (arg === "--dry-run") opts.dryRun = true;
20250
20388
  else if (arg === "--check") opts.check = true;
20251
20389
  else if (arg === "--yes" || arg === "-y") opts.yes = true;
@@ -20270,10 +20408,13 @@ Options:
20270
20408
  --manifest <ref> Stack manifest JSON path or URL (default: update.askexe.com)
20271
20409
  --target <version> Stack version to install (default: manifest.latest)
20272
20410
  --compose-file <path> docker-compose.yml path (default: ./docker-compose.yml or /opt/exe-stack/docker-compose.yml)
20273
- --env-file <path> .env path (default: ./.env or /opt/exe-stack/.env)
20411
+ --stack-env-file <path> .env path (default: ./.env or /opt/exe-stack/.env)
20412
+ --env-file <path> Alias; prefer --stack-env-file because Node 22 reserves --env-file
20274
20413
  --lock-file <path> Lock file path (default: beside .env)
20275
20414
  --check Print available changes only
20276
20415
  --dry-run Plan only; do not run Docker
20416
+ --domain <domain> Fill CHANGEME_DOMAIN in new stack .env
20417
+ --no-bootstrap Do not create templates/hydrate .env/check host prereqs
20277
20418
  --public-key <path> PEM public key for signed manifest verification
20278
20419
  --auth-token <token> Bearer token for private update manifest/audit API
20279
20420
  --auth-token-env <name> Read bearer token from an environment variable
@@ -20311,6 +20452,18 @@ function printBreaking(changes) {
20311
20452
  if (c.expectedDowntimeMinutes) console.log(` Expected downtime: ${c.expectedDowntimeMinutes} minutes`);
20312
20453
  }
20313
20454
  }
20455
+ function printHostReport(report) {
20456
+ console.log("Host preflight:");
20457
+ console.log(` Docker: ${report.dockerInstalled ? "\u2705" : "\u274C"}`);
20458
+ console.log(` Docker Compose: ${report.dockerComposeInstalled ? "\u2705" : "\u274C"}`);
20459
+ console.log(` Compose file: ${report.composeFileExists ? "\u2705" : "\u274C"}`);
20460
+ console.log(` Env file: ${report.envFileExists ? "\u2705" : "\u274C"}`);
20461
+ console.log(` License: ${report.licensePresent ? "\u2705" : "\u274C"}`);
20462
+ if (report.createdFiles.length > 0) console.log(` Created: ${report.createdFiles.join(", ")}`);
20463
+ if (report.actions.length > 0) console.log(` Actions: ${report.actions.join(", ")}`);
20464
+ if (report.envRemainingPlaceholders.length > 0) console.log(` Remaining placeholders: ${report.envRemainingPlaceholders.join(", ")}`);
20465
+ console.log("");
20466
+ }
20314
20467
  async function main7(args2 = process.argv.slice(2)) {
20315
20468
  const opts = parseArgs4(args2);
20316
20469
  if (opts.rollback) {
@@ -20322,6 +20475,12 @@ async function main7(args2 = process.argv.slice(2)) {
20322
20475
  console.log(`\u2705 Stack rollback attempted using backup: ${result2.backupEnvFile ?? "latest backup"}`);
20323
20476
  return;
20324
20477
  }
20478
+ let hostReport;
20479
+ if (!opts.noBootstrap) {
20480
+ hostReport = bootstrapStackHost({ ...opts, installDocker: opts.yes, domain: opts.domain });
20481
+ printHostReport(hostReport);
20482
+ if (!opts.check && !opts.dryRun) assertHostReadyForApply(hostReport);
20483
+ }
20325
20484
  const manifest = await loadStackManifest(opts.manifestRef, void 0, opts.manifestPublicKey, opts.manifestAuthToken);
20326
20485
  const envRaw = readFileSync30(opts.envFile, "utf8");
20327
20486
  const plan = createStackUpdatePlan(manifest, envRaw, opts.targetVersion);
@@ -34902,7 +35061,7 @@ __export(code_context_index_exports, {
34902
35061
  import crypto14 from "crypto";
34903
35062
  import path51 from "path";
34904
35063
  import { existsSync as existsSync36, mkdirSync as mkdirSync25, readFileSync as readFileSync32, readdirSync as readdirSync11, statSync as statSync7, writeFileSync as writeFileSync26 } from "fs";
34905
- import { spawnSync } from "child_process";
35064
+ import { spawnSync as spawnSync2 } from "child_process";
34906
35065
  function normalizeProjectRoot(projectRoot) {
34907
35066
  return path51.resolve(projectRoot || process.cwd());
34908
35067
  }
@@ -34920,7 +35079,7 @@ function getCodeContextIndexPath(projectRoot) {
34920
35079
  return path51.join(indexDir(), `${rootHash}.json`);
34921
35080
  }
34922
35081
  function currentBranch(projectRoot) {
34923
- const result = spawnSync("git", ["branch", "--show-current"], { cwd: projectRoot, encoding: "utf8", timeout: 2e3 });
35082
+ const result = spawnSync2("git", ["branch", "--show-current"], { cwd: projectRoot, encoding: "utf8", timeout: 2e3 });
34924
35083
  const branch = result.status === 0 ? result.stdout.trim() : "";
34925
35084
  return branch || "detached-or-unknown";
34926
35085
  }
@@ -34939,12 +35098,12 @@ function listRecursive(projectRoot, dir = projectRoot, out = []) {
34939
35098
  return out;
34940
35099
  }
34941
35100
  function listCodeFiles(projectRoot, maxFiles) {
34942
- const git = spawnSync("git", ["ls-files"], { cwd: projectRoot, encoding: "utf8", timeout: 5e3, maxBuffer: 1024 * 1024 * 16 });
35101
+ const git = spawnSync2("git", ["ls-files"], { cwd: projectRoot, encoding: "utf8", timeout: 5e3, maxBuffer: 1024 * 1024 * 16 });
34943
35102
  let files = [];
34944
35103
  if (git.status === 0 && git.stdout.trim()) {
34945
35104
  files = git.stdout.split("\n").map((s) => s.trim()).filter(Boolean);
34946
35105
  } else {
34947
- const rg = spawnSync("rg", ["--files"], { cwd: projectRoot, encoding: "utf8", timeout: 5e3, maxBuffer: 1024 * 1024 * 16 });
35106
+ const rg = spawnSync2("rg", ["--files"], { cwd: projectRoot, encoding: "utf8", timeout: 5e3, maxBuffer: 1024 * 1024 * 16 });
34948
35107
  files = rg.status === 0 && rg.stdout.trim() ? rg.stdout.split("\n").map((s) => s.trim()).filter(Boolean) : listRecursive(projectRoot);
34949
35108
  }
34950
35109
  return files.map((file) => file.replaceAll(path51.sep, "/")).filter((file) => isChunkable(file) && !shouldIgnore(file)).slice(0, maxFiles).sort();
@@ -36146,9 +36305,9 @@ if (args.includes("--global")) {
36146
36305
  );
36147
36306
  await runClaudeInstall();
36148
36307
  } else if (args.includes("--commands-only")) {
36149
- const { copySlashCommands: copySlashCommands2, resolvePackageRoot: resolvePackageRoot2 } = await Promise.resolve().then(() => (init_installer(), installer_exports));
36308
+ const { copySlashCommands: copySlashCommands2, resolvePackageRoot: resolvePackageRoot3 } = await Promise.resolve().then(() => (init_installer(), installer_exports));
36150
36309
  try {
36151
- const packageRoot = resolvePackageRoot2();
36310
+ const packageRoot = resolvePackageRoot3();
36152
36311
  const result = await copySlashCommands2(packageRoot);
36153
36312
  if (result.copied > 0) {
36154
36313
  process.stderr.write(