@tablation/crew 0.98.0 → 0.99.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 -16
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1602,6 +1602,28 @@ import { dirname as dirname3, join as join9 } from "path";
1602
1602
  var ForgeError = class extends Error {
1603
1603
  };
1604
1604
  var DEFAULT_FORGE_USERNAME = "x-access-token";
1605
+ var DEFAULT_FORGE_HOST = "github.com";
1606
+ var FORGE_DESCRIPTORS = {
1607
+ github: {
1608
+ kind: "github",
1609
+ httpsUrl: (host, remote) => `https://${host}/${remote}.git`,
1610
+ sshUrl: (host, remote) => `git@${host}:${remote}.git`,
1611
+ credentialUsername: DEFAULT_FORGE_USERNAME
1612
+ },
1613
+ gitlab: {
1614
+ kind: "gitlab",
1615
+ httpsUrl: (host, remote) => `https://${host}/${remote}.git`,
1616
+ sshUrl: (host, remote) => `git@${host}:${remote}.git`,
1617
+ credentialUsername: "oauth2"
1618
+ }
1619
+ };
1620
+ function normalizeForgeHost(host) {
1621
+ const h = host?.trim().toLowerCase().replace(/^[a-z][a-z0-9+.-]*:\/\//, "").replace(/\/+$/, "");
1622
+ return h || DEFAULT_FORGE_HOST;
1623
+ }
1624
+ function forgeDescriptorFor(host) {
1625
+ return normalizeForgeHost(host).includes("gitlab") ? FORGE_DESCRIPTORS.gitlab : FORGE_DESCRIPTORS.github;
1626
+ }
1605
1627
  var DEFAULT_KEYCHAIN_SERVICE = "crew-forge";
1606
1628
  function defaultTokenFilePath(stateDir, host) {
1607
1629
  return join9(stateDir, "forges", `${host.replace(/[^a-z0-9.-]/gi, "_")}.token`);
@@ -1814,7 +1836,7 @@ function parseForges(raw) {
1814
1836
  throw new ForgeError(`${where}.token.source must be one of: ${Object.keys(TOKEN_SOURCES).join(", ")}`);
1815
1837
  }
1816
1838
  out[host.toLowerCase()] = {
1817
- username: typeof e.username === "string" && e.username ? e.username : DEFAULT_FORGE_USERNAME,
1839
+ username: typeof e.username === "string" && e.username ? e.username : forgeDescriptorFor(host).credentialUsername,
1818
1840
  token
1819
1841
  };
1820
1842
  }
@@ -2932,6 +2954,7 @@ function parseResolved(raw, m, where) {
2932
2954
  reposModelId: raw.reposModelId,
2933
2955
  repoNames: raw.repoNames,
2934
2956
  repoRemotes: raw.repoRemotes,
2957
+ repoForgeHosts: raw.repoForgeHosts,
2935
2958
  models: {
2936
2959
  issues: m.req(raw.models?.issues, `${where}.resolved.models.issues`),
2937
2960
  comments: m.req(raw.models?.comments, `${where}.resolved.models.comments`),
@@ -3437,23 +3460,29 @@ function reposOf(r) {
3437
3460
  if (r.dirIsCloneBase) {
3438
3461
  const [workspace2] = splitRoute(r.route);
3439
3462
  const remotes2 = r.resolved?.repoRemotes ?? {};
3463
+ const hosts = r.resolved?.repoForgeHosts ?? {};
3440
3464
  return Object.entries(r.resolved?.repoNames ?? {}).map(([id, name]) => ({
3441
3465
  name,
3442
3466
  dir: defaultRepoDir(r.reposBasePath, workspace2, name),
3443
- remote: remotes2[id]
3467
+ remote: remotes2[id],
3468
+ ...hosts[id] ? { forgeHost: hosts[id] } : {}
3444
3469
  }));
3445
3470
  }
3446
3471
  if (trackerNames.length <= 1) return [{ name: basename3(r.dir), dir: r.dir }];
3447
3472
  const remotes = r.resolved?.repoRemotes ?? {};
3448
3473
  const remoteByName = {};
3474
+ const hostByName = {};
3449
3475
  for (const [id, name] of Object.entries(r.resolved.repoNames)) {
3450
3476
  if (remotes[id]) remoteByName[name] = remotes[id];
3477
+ const h = r.resolved?.repoForgeHosts?.[id];
3478
+ if (h) hostByName[name] = h;
3451
3479
  }
3452
3480
  const [workspace] = splitRoute(r.route);
3453
3481
  return trackerNames.map((name) => ({
3454
3482
  name,
3455
3483
  dir: defaultRepoDir(r.reposBasePath, workspace, name),
3456
- remote: remoteByName[name]
3484
+ remote: remoteByName[name],
3485
+ ...hostByName[name] ? { forgeHost: hostByName[name] } : {}
3457
3486
  }));
3458
3487
  }
3459
3488
  function passengerRepoTargets(r) {
@@ -3469,10 +3498,12 @@ function passengerCheckoutTargets(r) {
3469
3498
  const remotes = r.resolved?.repoRemotes ?? {};
3470
3499
  return passengerRepoTargets(r).map((t) => {
3471
3500
  const id = repoIdForName(r, t.name);
3501
+ const forgeHost = t.forgeHost ?? (id ? r.resolved?.repoForgeHosts?.[id] : void 0);
3472
3502
  return {
3473
3503
  name: t.name,
3474
3504
  dir: passengerCheckoutDir(r.reposBasePath, workspace, t.name),
3475
- remote: t.remote ?? (id ? remotes[id] : void 0)
3505
+ remote: t.remote ?? (id ? remotes[id] : void 0),
3506
+ ...forgeHost ? { forgeHost } : {}
3476
3507
  };
3477
3508
  });
3478
3509
  }
@@ -4780,8 +4811,10 @@ function setForgeTokenHosts(hosts) {
4780
4811
  }
4781
4812
  function cloneUrlFor(remote, opts = {}) {
4782
4813
  const origin = opts.originUrl?.trim() ?? "";
4783
- const scheme = tokenHosts.has("github.com") ? "https" : /^https?:\/\//i.test(origin) ? "https" : /^(git@|ssh:\/\/)/i.test(origin) ? "ssh" : opts.scheme ?? "ssh";
4784
- return scheme === "https" ? `https://github.com/${remote}.git` : `git@github.com:${remote}.git`;
4814
+ const host = normalizeForgeHost(opts.host);
4815
+ const scheme = tokenHosts.has(host) ? "https" : /^https?:\/\//i.test(origin) ? "https" : /^(git@|ssh:\/\/)/i.test(origin) ? "ssh" : opts.scheme ?? "ssh";
4816
+ const forge = forgeDescriptorFor(host);
4817
+ return scheme === "https" ? forge.httpsUrl(host, remote) : forge.sshUrl(host, remote);
4785
4818
  }
4786
4819
  function originUrlOf(dir, remoteName) {
4787
4820
  try {
@@ -5244,7 +5277,7 @@ function probePushAuth(o, run2 = realGit, baseEnv = process.env) {
5244
5277
  const raw = (err3.stderr ? String(err3.stderr) : err3.message).trim().split("\n").filter(Boolean).slice(-2).join(" / ");
5245
5278
  const detail = raw || "push dry-run failed";
5246
5279
  const f = o.forge;
5247
- if (!f) return { state: "failed", detail, hint: pushAuthHint(o.remoteUrl) };
5280
+ if (!f) return { state: "failed", detail: hostOfRemote(o.remoteUrl) ? `${hostOfRemote(o.remoteUrl)}: ${detail}` : detail, hint: pushAuthHint(o.remoteUrl) };
5248
5281
  if (!f.hasToken) {
5249
5282
  return {
5250
5283
  state: "failed",
@@ -13156,7 +13189,8 @@ function planRepoClone(i) {
13156
13189
  const basePath = String(m.get("reposBasePath") ?? (isMap(ship) ? ship.get("reposBasePath") : void 0) ?? "~/Crew");
13157
13190
  const target = i.dir ? expandDir(i.dir, i.base) : join29(expandDir(basePath, i.base), i.route.split("/")[0], name);
13158
13191
  const add = planRepoAdd({ text: i.text, base: i.base, route: i.route, path: target, origin: remote, resolved: i.resolved, name, isCheckout: i.isCheckout });
13159
- return { ...add, remote, target };
13192
+ const forgeHost = i.resolved?.repoForgeHosts?.[id];
13193
+ return { ...add, remote, target, ...forgeHost ? { forgeHost } : {} };
13160
13194
  }
13161
13195
 
13162
13196
  // src/doctor-fix.ts
@@ -15282,7 +15316,7 @@ function runForge(a, io) {
15282
15316
  const doc = parseDocument4(io.readFile(io.configFile));
15283
15317
  doc.setIn(["ship", "forges", host, "token"], tok);
15284
15318
  io.writeFile(io.configFile, doc.toString());
15285
- io.forges = { ...io.forges ?? {}, [host]: { username: "x-access-token", token: tok } };
15319
+ io.forges = { ...io.forges ?? {}, [host]: { username: forgeDescriptorFor(host).credentialUsername, token: tok } };
15286
15320
  existing = forgeFor(io.forges, host);
15287
15321
  }
15288
15322
  if (existing.entry.token.source === "env") {
@@ -16279,6 +16313,10 @@ async function discover(o) {
16279
16313
  repos.filter((r) => r.remote).map((r) => [r.id, r.remote])
16280
16314
  );
16281
16315
  if (Object.keys(remotes).length > 0) out.repoRemotes = remotes;
16316
+ const forgeHosts = Object.fromEntries(
16317
+ repos.filter((r) => r.forge_host?.trim()).map((r) => [r.id, r.forge_host.trim().toLowerCase()])
16318
+ );
16319
+ if (Object.keys(forgeHosts).length > 0) out.repoForgeHosts = forgeHosts;
16282
16320
  }
16283
16321
  if (o.area) {
16284
16322
  if (!out.areaModelId) problems.push(`--area given but this project has no "Projects" table`);
@@ -18432,7 +18470,8 @@ function passengerCheckoutSyncSpecs(cfg, workspaceId) {
18432
18470
  gitRemoteName: effective.branch.remote,
18433
18471
  base: effective.branch.base,
18434
18472
  originUrl: ordinary ? originUrlOf(ordinary.dir, effective.branch.remote) : null,
18435
- cloneScheme: cfg.ship.cloneScheme
18473
+ cloneScheme: cfg.ship.cloneScheme,
18474
+ ...target.forgeHost ? { forgeHost: target.forgeHost } : {}
18436
18475
  });
18437
18476
  }
18438
18477
  }
@@ -18442,7 +18481,7 @@ function syncAllPassengerCheckouts(cfg) {
18442
18481
  return passengerCheckoutSyncSpecs(cfg).map((spec) => ({
18443
18482
  name: spec.name,
18444
18483
  workspaceId: spec.workspaceId,
18445
- outcome: syncPassengerCheckout(spec.dir, spec.remote, spec.gitRemoteName, spec.base, { originUrl: spec.originUrl, scheme: spec.cloneScheme })
18484
+ outcome: syncPassengerCheckout(spec.dir, spec.remote, spec.gitRemoteName, spec.base, { originUrl: spec.originUrl, scheme: spec.cloneScheme, host: spec.forgeHost })
18446
18485
  }));
18447
18486
  }
18448
18487
 
@@ -18815,7 +18854,7 @@ function createSyncDaemonServer(targets, secret) {
18815
18854
  try {
18816
18855
  const synced = targets.map((t) => ({
18817
18856
  name: t.name,
18818
- outcome: syncPassengerCheckout(t.dir, t.remote, t.gitRemoteName, t.base, { originUrl: t.originUrl, scheme: t.cloneScheme })
18857
+ outcome: syncPassengerCheckout(t.dir, t.remote, t.gitRemoteName, t.base, { originUrl: t.originUrl, scheme: t.cloneScheme, host: t.forgeHost })
18819
18858
  }));
18820
18859
  res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify({ synced }));
18821
18860
  } catch (error) {
@@ -19511,7 +19550,7 @@ async function runRoleAgentLocked(role, decision, ctx, hooks, held) {
19511
19550
  const target = repoTargetFor(route2, result.ticket.repo_id);
19512
19551
  ticketRepo = target?.name;
19513
19552
  try {
19514
- if (target && ensureRepoCheckout(target.dir, target.remote, { scheme: ship.cloneScheme })) {
19553
+ if (target && ensureRepoCheckout(target.dir, target.remote, { scheme: ship.cloneScheme, host: target.forgeHost })) {
19515
19554
  emit.emit(`cloned ${target.name} into ${target.dir}`, { step: "select", role });
19516
19555
  }
19517
19556
  } catch (e) {
@@ -21828,7 +21867,7 @@ ${cu.tail.trimEnd()}
21828
21867
  const target = repoTargetFor(w.route, result.ticket.repo_id);
21829
21868
  fleetTicketRepo = target?.name;
21830
21869
  try {
21831
- if (target && ensureRepoCheckout(target.dir, target.remote, { scheme: cfg.ship.cloneScheme })) {
21870
+ if (target && ensureRepoCheckout(target.dir, target.remote, { scheme: cfg.ship.cloneScheme, host: target.forgeHost })) {
21832
21871
  wemit.emit(`cloned ${target.name} into ${target.dir}`, { step: "select", role: w.role });
21833
21872
  }
21834
21873
  } catch (e) {
@@ -22045,7 +22084,7 @@ ${cu.tail.trimEnd()}
22045
22084
  const target = repoTargetFor(route2, result.ticket.repo_id);
22046
22085
  ticketRepo = target?.name;
22047
22086
  try {
22048
- if (target && ensureRepoCheckout(target.dir, target.remote, { scheme: cfg.ship.cloneScheme })) {
22087
+ if (target && ensureRepoCheckout(target.dir, target.remote, { scheme: cfg.ship.cloneScheme, host: target.forgeHost })) {
22049
22088
  emit.emit(`cloned ${target.name} into ${target.dir}`, { step: "select", role: current });
22050
22089
  }
22051
22090
  } catch (e) {
@@ -22785,7 +22824,7 @@ ${cu.tail.trimEnd()}
22785
22824
  }
22786
22825
  say("cloning", { remote: plan2.remote, target: plan2.target }, `cloning ${plan2.remote} into ${plan2.target}`);
22787
22826
  try {
22788
- ensureRepoCheckout(plan2.target, plan2.remote, { scheme: cfg.ship.cloneScheme ?? "https" });
22827
+ ensureRepoCheckout(plan2.target, plan2.remote, { scheme: cfg.ship.cloneScheme ?? "https", host: plan2.forgeHost });
22789
22828
  } catch (e) {
22790
22829
  if (!(e instanceof GitError)) throw e;
22791
22830
  const authHint = /authentication failed|could not read (username|password)|permission denied|terminal prompts disabled|repository not found/i.test(e.message) ? "\n hint: git could not authenticate \u2014 run `gh auth login` (or add an SSH key and set ship.cloneScheme: ssh), then re-run" : "";
@@ -23375,6 +23414,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
23375
23414
  reposModelId: found.reposModelId,
23376
23415
  repoNames: found.repoNames,
23377
23416
  repoRemotes: found.repoRemotes,
23417
+ repoForgeHosts: found.repoForgeHosts,
23378
23418
  models: found.models,
23379
23419
  seats: found.seats,
23380
23420
  holds: found.holds.map((h) => ({ id: h.id, role: h.name })),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tablation/crew",
3
- "version": "0.98.0",
3
+ "version": "0.99.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",