@tablation/crew 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +56 -10
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -578,7 +578,8 @@ var SHIP_KEYS = /* @__PURE__ */ new Set([
578
578
  "relayHost",
579
579
  "relayPort",
580
580
  "relayHttpPort",
581
- "relayPublicDomain"
581
+ "relayPublicDomain",
582
+ "passengerImage"
582
583
  ]);
583
584
  var ROUTE_KEYS = /* @__PURE__ */ new Set([
584
585
  "route",
@@ -912,7 +913,8 @@ Create the first of those:
912
913
  // 2222 matches the relay's own RELAY_SSH_PORT default (crew-relay README).
913
914
  relayPort: shipRaw.relayPort !== void 0 ? Number(shipRaw.relayPort) : 2222,
914
915
  relayHttpPort: shipRaw.relayHttpPort !== void 0 ? Number(shipRaw.relayHttpPort) : 443,
915
- relayPublicDomain: shipRaw.relayPublicDomain ? String(shipRaw.relayPublicDomain) : void 0
916
+ relayPublicDomain: shipRaw.relayPublicDomain ? String(shipRaw.relayPublicDomain) : void 0,
917
+ passengerImage: shipRaw.passengerImage ? String(shipRaw.passengerImage) : void 0
916
918
  },
917
919
  routes,
918
920
  crewHome,
@@ -7775,7 +7777,21 @@ function dockerAvailable(exec = realExec) {
7775
7777
  return false;
7776
7778
  }
7777
7779
  }
7778
- var IMAGE_TAG = "crew-passenger-mcp:latest";
7780
+ var PASSENGER_IMAGE_REPO = "tablation/crew-passenger-mcp";
7781
+ function passengerImageRef(cfg) {
7782
+ return process.env.CREW_PASSENGER_IMAGE || cfg.ship.passengerImage || `${PASSENGER_IMAGE_REPO}:${crewVersion(cfg.crewHome)}`;
7783
+ }
7784
+ function imagePresent(image, exec = realExec) {
7785
+ try {
7786
+ exec("docker", ["image", "inspect", image]);
7787
+ return true;
7788
+ } catch {
7789
+ return false;
7790
+ }
7791
+ }
7792
+ function ensureImage(image, exec = realExec) {
7793
+ if (!imagePresent(image, exec)) exec("docker", ["pull", image]);
7794
+ }
7779
7795
  function containerNameFor(workspaceId) {
7780
7796
  return `crew-passenger-${workspaceId}`;
7781
7797
  }
@@ -7783,12 +7799,13 @@ function portFor(workspaceId, base = 28765, span = 1e3) {
7783
7799
  const digest = createHash4("sha256").update(workspaceId).digest();
7784
7800
  return base + digest.readUInt32BE(0) % span;
7785
7801
  }
7786
- function mountsDigest(mounts) {
7802
+ function mountsDigest(mounts, image) {
7787
7803
  const sorted2 = [...mounts].sort((a, b) => a.containerPath.localeCompare(b.containerPath));
7788
- return createHash4("sha256").update(JSON.stringify(sorted2)).digest("hex").slice(0, 16);
7804
+ return createHash4("sha256").update(JSON.stringify({ mounts: sorted2, image })).digest("hex").slice(0, 16);
7789
7805
  }
7790
7806
  function planContainers(cfg) {
7791
7807
  const byWorkspace = /* @__PURE__ */ new Map();
7808
+ const image = passengerImageRef(cfg);
7792
7809
  for (const route of cfg.routes) {
7793
7810
  if (!route.enabled || !route.hostPassengers || !route.resolved?.workspaceId) continue;
7794
7811
  const workspaceId = route.resolved.workspaceId;
@@ -7804,8 +7821,9 @@ function planContainers(cfg) {
7804
7821
  workspaceId,
7805
7822
  containerName: containerNameFor(workspaceId),
7806
7823
  port: portFor(workspaceId),
7824
+ image,
7807
7825
  mounts: mountList,
7808
- mountsHash: mountsDigest(mountList)
7826
+ mountsHash: mountsDigest(mountList, image)
7809
7827
  };
7810
7828
  });
7811
7829
  }
@@ -7861,7 +7879,7 @@ function startContainer(plan, tablationApiBaseUrl, sync, exec = realExec) {
7861
7879
  );
7862
7880
  }
7863
7881
  for (const m of plan.mounts) args.push("-v", `${m.hostPath}:${m.containerPath}:ro`);
7864
- args.push(IMAGE_TAG);
7882
+ args.push(plan.image);
7865
7883
  exec("docker", args);
7866
7884
  }
7867
7885
  function syncPassengerContainers(cfg, tablationApiBaseUrl, exec = realExec, syncEndpoints = /* @__PURE__ */ new Map()) {
@@ -7872,19 +7890,27 @@ function syncPassengerContainers(cfg, tablationApiBaseUrl, exec = realExec, sync
7872
7890
  const started = [];
7873
7891
  const recreated = [];
7874
7892
  const stopped = [];
7893
+ const ensured = /* @__PURE__ */ new Set();
7894
+ const start = (plan, sync) => {
7895
+ if (!ensured.has(plan.image)) {
7896
+ ensureImage(plan.image, exec);
7897
+ ensured.add(plan.image);
7898
+ }
7899
+ startContainer(plan, tablationApiBaseUrl, sync, exec);
7900
+ };
7875
7901
  for (const plan of plans) {
7876
7902
  const sync = syncEndpoints.get(plan.workspaceId);
7877
7903
  const current = existingByName.get(plan.containerName);
7878
7904
  if (!current) {
7879
- startContainer(plan, tablationApiBaseUrl, sync, exec);
7905
+ start(plan, sync);
7880
7906
  started.push(plan.containerName);
7881
7907
  } else if (!current.running) {
7882
7908
  stopContainer(plan.containerName, exec);
7883
- startContainer(plan, tablationApiBaseUrl, sync, exec);
7909
+ start(plan, sync);
7884
7910
  started.push(plan.containerName);
7885
7911
  } else if (currentMountsHash(plan.containerName, exec) !== plan.mountsHash) {
7886
7912
  stopContainer(plan.containerName, exec);
7887
- startContainer(plan, tablationApiBaseUrl, sync, exec);
7913
+ start(plan, sync);
7888
7914
  recreated.push(plan.containerName);
7889
7915
  }
7890
7916
  }
@@ -9129,6 +9155,23 @@ if (command === "passenger-sync-daemon") {
9129
9155
  process.stderr.write("crew install: no scheduler support yet for Windows (Task Scheduler is planned, not built)\n");
9130
9156
  process.exit(2);
9131
9157
  }
9158
+ if (!dryRun && cfg.routes.some((r) => r.enabled && r.hostPassengers) && dockerAvailable()) {
9159
+ const image = passengerImageRef(cfg);
9160
+ if (imagePresent(image)) {
9161
+ process.stdout.write(`Host Passengers image ${image} is already present
9162
+ `);
9163
+ } else {
9164
+ process.stdout.write(`pulling Host Passengers image ${image}
9165
+ `);
9166
+ try {
9167
+ ensureImage(image);
9168
+ } catch (e) {
9169
+ process.stderr.write(`crew install: could not pull Host Passengers image ${image}: ${e.message}
9170
+ `);
9171
+ process.exitCode = 1;
9172
+ }
9173
+ }
9174
+ }
9132
9175
  const hasSystemd = detectSystemd();
9133
9176
  for (const job of ["run", "release", "passengers"]) {
9134
9177
  const plan = planInstall(cfg.ship, CREW_HOME, host, hasSystemd, job);
@@ -11253,10 +11296,13 @@ git: ${gitPath ? `OK (${gitPath})` : "NOT FOUND on the scheduler P
11253
11296
  } else {
11254
11297
  const dockerOk = dockerAvailable();
11255
11298
  const keygenOk = sshKeygenAvailable();
11299
+ const image = passengerImageRef(cfg);
11300
+ const imageOk = dockerOk && imagePresent(image);
11256
11301
  process.stdout.write(
11257
11302
  `host passengers: ON for ${passengerRoutes.map((r) => r.route).join(", ")}
11258
11303
  docker: ${dockerOk ? "OK" : "NOT AVAILABLE"}
11259
11304
  ssh-keygen: ${keygenOk ? "OK" : "NOT AVAILABLE"}
11305
+ image: ${image} (${imageOk ? "present" : "missing"})
11260
11306
  relay host: ${cfg.ship.relayHost ? `${cfg.ship.relayHost}:${cfg.ship.relayPort}` : "not set (ship.relayHost) \u2014 containers will run with no tunnel"}
11261
11307
  `
11262
11308
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tablation/crew",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "A standing team of headless agents that picks work off a Tablation board, does it on a machine you control, and reports back.",
5
5
  "type": "module",
6
6
  "license": "MIT",