codeam-cli 2.61.31 → 2.61.33

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 (3) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/index.js +183 -35
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,18 @@ All notable changes to `codeam-cli` are documented here.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [2.61.32] — 2026-07-24
8
+
9
+ ### Added
10
+
11
+ - **shared:** Add GitLab as a version_control integration
12
+
13
+ ## [2.61.31] — 2026-07-24
14
+
15
+ ### Added
16
+
17
+ - **shared:** Make GitHub a first-class version_control integration
18
+
7
19
  ## [2.61.30] — 2026-07-24
8
20
 
9
21
  ### Added
package/dist/index.js CHANGED
@@ -641,6 +641,36 @@ var INTEGRATION_REGISTRY = {
641
641
  // No MCP: a deployed box already has an authenticated `gh` on PATH.
642
642
  delivery: {}
643
643
  },
644
+ gitlab: {
645
+ id: "gitlab",
646
+ name: "GitLab",
647
+ icon: "gitlab",
648
+ category: "version_control",
649
+ // LIVE — a user-owned gitlab.com application (Confidential) with BOTH env
650
+ // callbacks registered, so dev/prod share the client and differ only in
651
+ // GITLAB_OAUTH_REDIRECT_URI. The backend GitLabOAuthProvider is
652
+ // config-gated (503 if env unset), so this is safe mid-rollout.
653
+ //
654
+ // ⚠️ Unlike `github`, this is a NORMAL `oauth_redirect` integration: its
655
+ // credential is vaulted here rather than living in a ProviderToken, because
656
+ // nothing else in the product owns a GitLab connection (GitHub's lives on
657
+ // the codespaces rail, which is why it's `kind: 'connection'`).
658
+ enabled: true,
659
+ auth: {
660
+ kind: "oauth_redirect",
661
+ // GitLab has NO per-resource scopes — `api` is the only one that grants
662
+ // merge-request WRITE (comment / approve / merge / close), so a
663
+ // read-only alternative would make the whole MR surface useless.
664
+ // `write_repository` is the git-over-HTTPS rail for the agent's push;
665
+ // `api` already covers it for user tokens, but it costs nothing on a
666
+ // consent screen that already says "complete read/write access" and
667
+ // changing scopes later forces EVERY user to re-authorize.
668
+ scopes: ["api", "write_repository"]
669
+ },
670
+ // No MCP: the box's `git` is authenticated for push, and the MR surface is
671
+ // served backend-side by the VCS provider — same shape as `github`.
672
+ delivery: {}
673
+ },
644
674
  github_issues: {
645
675
  id: "github_issues",
646
676
  name: "GitHub Issues",
@@ -6325,7 +6355,7 @@ function readAnonId() {
6325
6355
  }
6326
6356
  function superProperties() {
6327
6357
  return {
6328
- cliVersion: true ? "2.61.31" : "0.0.0-dev",
6358
+ cliVersion: true ? "2.61.33" : "0.0.0-dev",
6329
6359
  nodeVersion: process.version,
6330
6360
  platform: process.platform,
6331
6361
  arch: process.arch,
@@ -6506,7 +6536,7 @@ var os4 = __toESM(require("os"));
6506
6536
  // package.json
6507
6537
  var package_default = {
6508
6538
  name: "codeam-cli",
6509
- version: "2.61.31",
6539
+ version: "2.61.33",
6510
6540
  description: "Workflow-continuity bridge for AI coding agents. Wrap Claude Code or Codex in a PTY and supervise, approve, and redirect the session from any device \u2014 async. The terminal companion for CodeAgent Mobile.",
6511
6541
  type: "commonjs",
6512
6542
  main: "dist/index.js",
@@ -7677,7 +7707,7 @@ var CommandRelayService = class _CommandRelayService {
7677
7707
  // fresh + clear the "CLI update available" banner after a self-update
7678
7708
  // (a codespace that reinstalls @latest reconnects via heartbeat, not
7679
7709
  // pair/reconnect). Older backends ignore the extra field.
7680
- ..."2.61.31" ? { ideVersion: "2.61.31" } : {}
7710
+ ..."2.61.33" ? { ideVersion: "2.61.33" } : {}
7681
7711
  }).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
7682
7712
  }
7683
7713
  /**
@@ -17294,8 +17324,24 @@ function githubOwnerRepo(repoRef) {
17294
17324
  if (httpsMatch) return { owner: httpsMatch[1], repo: httpsMatch[2] };
17295
17325
  return null;
17296
17326
  }
17297
- function repoCloneUrl(repoRef, cloneToken) {
17327
+ function gitlabProjectPath(repoRef) {
17298
17328
  const trimmed = repoRef.trim();
17329
+ const https9 = /^https?:\/\/gitlab\.com\/(.+?)(?:\.git)?\/?$/.exec(trimmed);
17330
+ if (https9) return https9[1];
17331
+ if (!/^https?:\/\//.test(trimmed) && !trimmed.startsWith("git@") && trimmed.includes("/")) {
17332
+ return trimmed.replace(/\.git$/, "");
17333
+ }
17334
+ return null;
17335
+ }
17336
+ function repoCloneUrl(repoRef, cloneToken, provider = "github") {
17337
+ const trimmed = repoRef.trim();
17338
+ if (provider === "gitlab") {
17339
+ const glPath = gitlabProjectPath(trimmed);
17340
+ if (glPath) {
17341
+ return cloneToken ? `https://oauth2:${cloneToken}@gitlab.com/${glPath}.git` : `https://gitlab.com/${glPath}.git`;
17342
+ }
17343
+ if (/^https?:\/\//.test(trimmed) || trimmed.startsWith("git@")) return trimmed;
17344
+ }
17299
17345
  if (cloneToken) {
17300
17346
  const gh = githubOwnerRepo(trimmed);
17301
17347
  if (gh) {
@@ -17324,12 +17370,15 @@ async function fetchGithubIdentity(token) {
17324
17370
  return null;
17325
17371
  }
17326
17372
  }
17327
- async function configureGitCredentials(dest, repoRef, cloneToken) {
17328
- const gh = githubOwnerRepo(repoRef.trim());
17329
- if (!gh || !cloneToken) return;
17373
+ async function configureGitCredentials(dest, repoRef, cloneToken, provider = "github") {
17374
+ const isGitlab = provider === "gitlab";
17375
+ if (isGitlab ? !gitlabProjectPath(repoRef.trim()) : !githubOwnerRepo(repoRef.trim())) return;
17376
+ if (!cloneToken) return;
17330
17377
  const credFile = path40.join(dest, ".git", "codeam-credentials");
17331
- fs36.writeFileSync(credFile, `https://x-access-token:${cloneToken}@github.com
17332
- `, { mode: 384 });
17378
+ const credLine = isGitlab ? `https://oauth2:${cloneToken}@gitlab.com
17379
+ ` : `https://x-access-token:${cloneToken}@github.com
17380
+ `;
17381
+ fs36.writeFileSync(credFile, credLine, { mode: 384 });
17333
17382
  restrictToOwner(credFile);
17334
17383
  const env = nonInteractiveGitEnv();
17335
17384
  const git2 = (args2) => execFileP4("git", ["-C", dest, ...args2], { timeout: 3e4, env });
@@ -17344,20 +17393,17 @@ async function configureGitCredentials(dest, repoRef, cloneToken) {
17344
17393
  `store --file=${credFilePosix}`
17345
17394
  ]).catch(() => {
17346
17395
  });
17347
- await git2(["remote", "set-url", "origin", repoCloneUrl(repoRef)]).catch(() => {
17396
+ await git2(["remote", "set-url", "origin", repoCloneUrl(repoRef, void 0, provider)]).catch(() => {
17348
17397
  });
17349
17398
  const hasIdentity = await git2(["config", "user.email"]).then(() => true).catch(() => false);
17350
17399
  if (!hasIdentity) {
17351
- const who = await fetchGithubIdentity(cloneToken);
17352
- if (who?.login) {
17353
- await git2(["config", "--local", "user.name", who.login]).catch(() => {
17354
- });
17355
- }
17356
- const email = who?.email ?? (who?.login ? `${who.login}@users.noreply.github.com` : void 0);
17357
- if (email) {
17358
- await git2(["config", "--local", "user.email", email]).catch(() => {
17359
- });
17360
- }
17400
+ const who = isGitlab ? null : await fetchGithubIdentity(cloneToken);
17401
+ const login = who?.login ?? "CodeAgent";
17402
+ await git2(["config", "--local", "user.name", login]).catch(() => {
17403
+ });
17404
+ const email = who?.email ?? (isGitlab ? "agent@codeagent-mobile.com" : `${login}@users.noreply.github.com`);
17405
+ await git2(["config", "--local", "user.email", email]).catch(() => {
17406
+ });
17361
17407
  }
17362
17408
  }
17363
17409
  function maskToken(text, cloneToken) {
@@ -17367,7 +17413,7 @@ function maskToken(text, cloneToken) {
17367
17413
  }
17368
17414
  return masked;
17369
17415
  }
17370
- async function prepareWorkspace(repoOrPath, deployId, cloneToken) {
17416
+ async function prepareWorkspace(repoOrPath, deployId, cloneToken, provider = "github") {
17371
17417
  if (isAbsolutePathTarget(repoOrPath)) {
17372
17418
  if (!fs36.existsSync(repoOrPath)) {
17373
17419
  throw new Error(`deploy target path does not exist: ${repoOrPath}`);
@@ -17376,11 +17422,11 @@ async function prepareWorkspace(repoOrPath, deployId, cloneToken) {
17376
17422
  }
17377
17423
  const dest = path40.join(selfHostedWorkspaceRoot(), deployId);
17378
17424
  if (fs36.existsSync(path40.join(dest, ".git"))) {
17379
- if (cloneToken) await configureGitCredentials(dest, repoOrPath, cloneToken);
17425
+ if (cloneToken) await configureGitCredentials(dest, repoOrPath, cloneToken, provider);
17380
17426
  return dest;
17381
17427
  }
17382
17428
  fs36.mkdirSync(selfHostedWorkspaceRoot(), { recursive: true, mode: 448 });
17383
- const cloneUrl = repoCloneUrl(repoOrPath, cloneToken);
17429
+ const cloneUrl = repoCloneUrl(repoOrPath, cloneToken, provider);
17384
17430
  try {
17385
17431
  await execFileP4("git", ["clone", "--depth", "1", cloneUrl, dest], {
17386
17432
  timeout: 12e4,
@@ -17391,7 +17437,7 @@ async function prepareWorkspace(repoOrPath, deployId, cloneToken) {
17391
17437
  const reason = err instanceof Error ? err.message : String(err);
17392
17438
  throw new Error(`git clone failed for ${maskCloneUrl(cloneUrl)}: ${maskToken(reason, cloneToken)}`);
17393
17439
  }
17394
- if (cloneToken) await configureGitCredentials(dest, repoOrPath, cloneToken);
17440
+ if (cloneToken) await configureGitCredentials(dest, repoOrPath, cloneToken, provider);
17395
17441
  return dest;
17396
17442
  }
17397
17443
 
@@ -17692,6 +17738,92 @@ async function ensureGhAuth(runner, ghCmd, token) {
17692
17738
  log.warn("host-agent", `gh auth errored: ${e instanceof Error ? e.message : String(e)}`);
17693
17739
  }
17694
17740
  }
17741
+ var FALLBACK_GLAB_VERSION = "1.54.0";
17742
+ var GLAB_RELEASE_API = "https://gitlab.com/api/v4/projects/gitlab-org%2Fcli/releases/permalink/latest";
17743
+ async function resolveLatestGlabVersion() {
17744
+ try {
17745
+ const res = await fetch(GLAB_RELEASE_API, { headers: { Accept: "application/json" } });
17746
+ if (res.ok) {
17747
+ const body = await res.json();
17748
+ const tag = (body.tag_name ?? "").replace(/^v/, "");
17749
+ if (/^\d+\.\d+\.\d+$/.test(tag)) return tag;
17750
+ }
17751
+ } catch {
17752
+ }
17753
+ return FALLBACK_GLAB_VERSION;
17754
+ }
17755
+ async function ensureGlabCli(runner, deps = {}) {
17756
+ if (runner.which("glab")) return "glab";
17757
+ const arch2 = ghArch();
17758
+ const platform3 = process.platform;
17759
+ if (arch2 === null || platform3 !== "linux" && platform3 !== "darwin" && platform3 !== "win32") {
17760
+ log.warn("host-agent", `glab auto-install unsupported on ${platform3}/${process.arch} \u2014 skipping`);
17761
+ return null;
17762
+ }
17763
+ const osToken = platform3 === "darwin" ? "darwin" : platform3 === "win32" ? "windows" : "linux";
17764
+ const ext = platform3 === "win32" ? "zip" : "tar.gz";
17765
+ const binaryName = platform3 === "win32" ? "glab.exe" : "glab";
17766
+ const downloadFn = deps.downloadFn ?? download;
17767
+ try {
17768
+ const version3 = await resolveLatestGlabVersion();
17769
+ const asset = `glab_${version3}_${osToken}_${arch2}`;
17770
+ const url2 = `https://gitlab.com/gitlab-org/cli/-/releases/v${version3}/downloads/${asset}.${ext}`;
17771
+ const tmpRoot = fs38.mkdtempSync(path42.join(os34.tmpdir(), "codeam-glab-"));
17772
+ const archive = path42.join(tmpRoot, `${asset}.${ext}`);
17773
+ if (!await downloadFn(url2, archive)) {
17774
+ log.warn("host-agent", "glab download failed \u2014 skipping (git push/pull still work)");
17775
+ return null;
17776
+ }
17777
+ const extract = await runner.run("tar", ["-xf", archive, "-C", tmpRoot], { timeoutMs: 6e4 });
17778
+ if (extract.code !== 0) {
17779
+ log.warn("host-agent", `glab archive extraction failed (code=${String(extract.code)}) \u2014 skipping`);
17780
+ return null;
17781
+ }
17782
+ const candidates = [
17783
+ path42.join(tmpRoot, "bin", binaryName),
17784
+ path42.join(tmpRoot, asset, "bin", binaryName),
17785
+ path42.join(tmpRoot, binaryName)
17786
+ ];
17787
+ const extractedBin = candidates.find((c2) => fs38.existsSync(c2));
17788
+ if (!extractedBin) {
17789
+ log.warn("host-agent", "glab binary not found in the extracted archive \u2014 skipping");
17790
+ return null;
17791
+ }
17792
+ const binDir = codeamBinDir();
17793
+ fs38.mkdirSync(binDir, { recursive: true });
17794
+ const target = path42.join(binDir, binaryName);
17795
+ fs38.copyFileSync(extractedBin, target);
17796
+ fs38.chmodSync(target, 493);
17797
+ log.info("host-agent", `glab installed to ${target} (v${version3})`);
17798
+ return target;
17799
+ } catch (e) {
17800
+ log.warn("host-agent", `glab auto-install errored: ${e instanceof Error ? e.message : String(e)} \u2014 skipping`);
17801
+ return null;
17802
+ }
17803
+ }
17804
+ async function ensureGlabAuth(runner, glabCmd, token) {
17805
+ if (!token) return;
17806
+ try {
17807
+ const status2 = await runner.run(glabCmd, ["auth", "status"], { timeoutMs: 15e3 });
17808
+ if (status2.code === 0) {
17809
+ log.info("host-agent", "glab already authenticated \u2014 leaving the existing login untouched");
17810
+ return;
17811
+ }
17812
+ const login = await runner.run(
17813
+ glabCmd,
17814
+ ["auth", "login", "--hostname", "gitlab.com", "--stdin"],
17815
+ { timeoutMs: 2e4, input: `${token}
17816
+ ` }
17817
+ );
17818
+ if (login.code === 0) {
17819
+ log.info("host-agent", "glab authenticated with the linked GitLab token");
17820
+ } else {
17821
+ log.warn("host-agent", `glab auth login failed (code=${String(login.code)}) \u2014 glab unauthenticated`);
17822
+ }
17823
+ } catch (e) {
17824
+ log.warn("host-agent", `glab auth errored: ${e instanceof Error ? e.message : String(e)}`);
17825
+ }
17826
+ }
17695
17827
  var defaultGitToolingRunner = {
17696
17828
  which(cmd) {
17697
17829
  try {
@@ -18539,7 +18671,7 @@ async function autoUpgradeBeforeCriticalCommand() {
18539
18671
  if (process.env.NODE_ENV === "test") return;
18540
18672
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
18541
18673
  if (process.env.CI) return;
18542
- const current = true ? "2.61.31" : null;
18674
+ const current = true ? "2.61.33" : null;
18543
18675
  if (!current) return;
18544
18676
  const cache = readCache();
18545
18677
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -18556,7 +18688,7 @@ function checkForUpdates() {
18556
18688
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
18557
18689
  if (process.env.CI) return;
18558
18690
  if (!process.stdout.isTTY) return;
18559
- const current = true ? "2.61.31" : null;
18691
+ const current = true ? "2.61.33" : null;
18560
18692
  if (!current) return;
18561
18693
  const cache = readCache();
18562
18694
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -18576,7 +18708,7 @@ var SELF_UPDATE_INTERVAL_MS = 60 * 60 * 1e3;
18576
18708
  var SELF_UPDATE_VIEW_TIMEOUT_MS = 3e4;
18577
18709
  var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
18578
18710
  function currentCliVersion() {
18579
- return true ? "2.61.31" : null;
18711
+ return true ? "2.61.33" : null;
18580
18712
  }
18581
18713
  function runCmd(cmd, args2, timeoutMs) {
18582
18714
  return new Promise((resolve8) => {
@@ -18785,6 +18917,9 @@ function isDeployPayload(p2) {
18785
18917
  if (p2.cloneToken !== void 0 && typeof p2.cloneToken !== "string") {
18786
18918
  return false;
18787
18919
  }
18920
+ if (p2.repoProvider !== void 0 && p2.repoProvider !== "github" && p2.repoProvider !== "gitlab") {
18921
+ return false;
18922
+ }
18788
18923
  if (p2.headroomEnabled !== void 0 && typeof p2.headroomEnabled !== "boolean") {
18789
18924
  return false;
18790
18925
  }
@@ -19433,7 +19568,12 @@ var HostAgentSupervisor = class {
19433
19568
  if (!isAbsolutePathTarget(payload.repoOrPath)) {
19434
19569
  report("cloning", "cloning repository");
19435
19570
  }
19436
- const cwd = await prepareWorkspace(payload.repoOrPath, payload.deployId, payload.cloneToken);
19571
+ const cwd = await prepareWorkspace(
19572
+ payload.repoOrPath,
19573
+ payload.deployId,
19574
+ payload.cloneToken,
19575
+ payload.repoProvider ?? "github"
19576
+ );
19437
19577
  let childEnv;
19438
19578
  let extraArgs = [];
19439
19579
  if (payload.houseProxy) {
@@ -19474,15 +19614,23 @@ var HostAgentSupervisor = class {
19474
19614
  if (payload.cloneToken) {
19475
19615
  try {
19476
19616
  report("preparing", "configuring git tooling");
19477
- const ghCmd = await ensureGhCli(defaultGitToolingRunner, payload.cloneToken);
19478
- if (ghCmd) {
19479
- childEnv.PATH = `${codeamBinDir()}${path46.delimiter}${childEnv.PATH}`;
19480
- await ensureGhAuth(defaultGitToolingRunner, ghCmd, payload.cloneToken);
19617
+ if ((payload.repoProvider ?? "github") === "gitlab") {
19618
+ const glabCmd = await ensureGlabCli(defaultGitToolingRunner);
19619
+ if (glabCmd) {
19620
+ childEnv.PATH = `${codeamBinDir()}${path46.delimiter}${childEnv.PATH}`;
19621
+ await ensureGlabAuth(defaultGitToolingRunner, glabCmd, payload.cloneToken);
19622
+ }
19623
+ } else {
19624
+ const ghCmd = await ensureGhCli(defaultGitToolingRunner, payload.cloneToken);
19625
+ if (ghCmd) {
19626
+ childEnv.PATH = `${codeamBinDir()}${path46.delimiter}${childEnv.PATH}`;
19627
+ await ensureGhAuth(defaultGitToolingRunner, ghCmd, payload.cloneToken);
19628
+ }
19481
19629
  }
19482
19630
  } catch (e) {
19483
19631
  log.warn(
19484
19632
  "host-agent",
19485
- `gh tooling setup skipped: ${e instanceof Error ? e.message : String(e)}`
19633
+ `git tooling setup skipped: ${e instanceof Error ? e.message : String(e)}`
19486
19634
  );
19487
19635
  }
19488
19636
  }
@@ -38569,7 +38717,7 @@ function checkChokidar() {
38569
38717
  }
38570
38718
  async function doctor(args2 = []) {
38571
38719
  const json = args2.includes("--json");
38572
- const cliVersion = true ? "2.61.31" : "0.0.0-dev";
38720
+ const cliVersion = true ? "2.61.33" : "0.0.0-dev";
38573
38721
  const apiBase2 = resolveApiBaseUrl();
38574
38722
  const diagnosticId = (0, import_node_crypto13.randomUUID)();
38575
38723
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -39080,7 +39228,7 @@ async function mcpRun(args2) {
39080
39228
  // src/commands/version.ts
39081
39229
  var import_picocolors15 = __toESM(require("picocolors"));
39082
39230
  function version2() {
39083
- const v = true ? "2.61.31" : "unknown";
39231
+ const v = true ? "2.61.33" : "unknown";
39084
39232
  console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
39085
39233
  }
39086
39234
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.61.31",
3
+ "version": "2.61.33",
4
4
  "description": "Workflow-continuity bridge for AI coding agents. Wrap Claude Code or Codex in a PTY and supervise, approve, and redirect the session from any device — async. The terminal companion for CodeAgent Mobile.",
5
5
  "type": "commonjs",
6
6
  "main": "dist/index.js",