@tablation/crew 0.100.3 → 0.101.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 +32 -2
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -19973,6 +19973,7 @@ async function execute(h, args, streaming, after) {
19973
19973
  var isPeriscopePath = (p) => p === "/periscopes" || p.startsWith("/periscopes/");
19974
19974
  async function handlePeriscopes(h, q) {
19975
19975
  if (!h || !h.routes.some((r) => r.hostPeriscopes)) return err(404, "not found");
19976
+ if (!q.token) return err(401, "a bearer token is required");
19976
19977
  const down = /^\/periscopes\/([^/]+)\/down$/.exec(q.path);
19977
19978
  const isUp = q.path === "/periscopes" && q.method === "POST";
19978
19979
  const isList = q.path === "/periscopes" && q.method === "GET";
@@ -19989,6 +19990,7 @@ async function handlePeriscopes(h, q) {
19989
19990
  const named = q.query.get("route") ?? void 0;
19990
19991
  const candidates2 = named === void 0 ? h.routes.filter((r) => r.hostPeriscopes) : [pickRoute(h, named)];
19991
19992
  const rows = [];
19993
+ const hosting = [];
19992
19994
  for (const c of candidates2) {
19993
19995
  if ("status" in c) return c;
19994
19996
  const denied2 = await member(c.workspaceId);
@@ -20001,12 +20003,21 @@ async function handlePeriscopes(h, q) {
20001
20003
  if (r.code !== 0) return err(failureStatus(r), cliMessage(r));
20002
20004
  try {
20003
20005
  const list = JSON.parse(r.stdout);
20004
- if (Array.isArray(list)) for (const e of list) rows.push({ route: c.route, ...e });
20006
+ const mine = Array.isArray(list) ? list : [];
20007
+ for (const e of mine) rows.push({ route: c.route, ...e });
20008
+ hosting.push({
20009
+ route: c.route,
20010
+ workspaceId: c.workspaceId,
20011
+ previewHostSet: !!h.previewHost,
20012
+ maxPeriscopes: h.maxPeriscopes,
20013
+ live: mine.length,
20014
+ repos: h.previewRepos?.(c.route) ?? []
20015
+ });
20005
20016
  } catch {
20006
20017
  return err(502, "the list command did not answer JSON");
20007
20018
  }
20008
20019
  }
20009
- return reply(200, { periscopes: rows });
20020
+ return reply(200, { periscopes: rows, hosting });
20010
20021
  }
20011
20022
  const b = q.body;
20012
20023
  if (!b) return err(400, "body must be a small JSON object");
@@ -20136,6 +20147,7 @@ var ControlAuth = class {
20136
20147
  async verifyIn(token, workspaceId) {
20137
20148
  if (!token) return "unauthorized";
20138
20149
  for (const k of this.o.shipKeys()) if (k && secretsMatch(token, k)) return "ok";
20150
+ for (const k of this.o.workspaceSecrets?.(workspaceId) ?? []) if (k && secretsMatch(token, k)) return "ok";
20139
20151
  const now = (this.o.now ?? Date.now)();
20140
20152
  const id = `${workspaceId}:${createHash9("sha256").update(token).digest("hex")}`;
20141
20153
  const hit = this.cache.get(id);
@@ -20465,6 +20477,9 @@ async function startControlServer(deps) {
20465
20477
  if (!deps.periscopeMcp) return send(res, err2(404, "not found"));
20466
20478
  return await deps.periscopeMcp(req, res, decodeURIComponent(mcp[1]));
20467
20479
  }
20480
+ if (isPeriscopePath(new URL(req.url ?? "/", "http://ship").pathname.replace(/\/+$/, "") || "/")) {
20481
+ return await send(res, await route(deps, req.method ?? "GET", new URL(req.url ?? "/", "http://ship"), req, abort.signal));
20482
+ }
20468
20483
  const m = /^Bearer (.+)$/.exec(req.headers.authorization ?? "");
20469
20484
  const verdict = await deps.auth.verify(m?.[1]?.trim() ?? "");
20470
20485
  if (verdict === "unauthorized") return send(res, err2(401, "invalid or revoked key"));
@@ -22979,6 +22994,10 @@ ${cu.tail.trimEnd()}
22979
22994
  return [];
22980
22995
  }
22981
22996
  }),
22997
+ workspaceSecrets: (ws) => {
22998
+ const sec = readPersistedTunnel(cfg.ship.stateDir, ws)?.mcpSecret;
22999
+ return sec ? [sec] : [];
23000
+ },
22982
23001
  userAgent: cfg.ship.userAgent
22983
23002
  });
22984
23003
  const controlServer = await startControlServer({
@@ -22992,6 +23011,17 @@ ${cu.tail.trimEnd()}
22992
23011
  maxPeriscopes: cfg.ship.maxPeriscopes,
22993
23012
  run: runCrew,
22994
23013
  verifyIn: (token, ws) => controlAuth.verifyIn(token, ws),
23014
+ previewRepos: (routeName) => {
23015
+ const r = controlRoutes.find((x) => x.route === routeName);
23016
+ if (!r) return [];
23017
+ return reposOf(r).filter((t) => {
23018
+ try {
23019
+ return !!loadRepoConfig(t.dir)?.hooks.preview;
23020
+ } catch {
23021
+ return false;
23022
+ }
23023
+ }).map((t) => t.name);
23024
+ },
22995
23025
  mcp: {
22996
23026
  onUp: async (r, done) => {
22997
23027
  const id = typeof done.ticketRecordId === "string" ? done.ticketRecordId : "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tablation/crew",
3
- "version": "0.100.3",
3
+ "version": "0.101.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",