@bivy/bivy 0.18.5-staging.6 → 0.18.5-staging.7

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.
@@ -0,0 +1,57 @@
1
+ /** User credentials (configured token or gh login) can reach repos outside the Apps. */
2
+ export async function listGithubUserRepos(token, fetchImpl = fetch) {
3
+ const repos = [];
4
+ for (let page = 1;; page++) {
5
+ const response = await fetchImpl(`https://api.github.com/user/repos?sort=updated&per_page=100&affiliation=owner,collaborator,organization_member&page=${page}`, {
6
+ headers: { authorization: `Bearer ${token}`, accept: "application/vnd.github+json", "user-agent": "bivy" },
7
+ });
8
+ if (!response.ok)
9
+ throw new Error(`GitHub responded ${response.status} while listing user repositories`);
10
+ const batch = await response.json();
11
+ if (!Array.isArray(batch))
12
+ throw new Error("GitHub returned an invalid repository listing");
13
+ for (const repo of batch) {
14
+ if (typeof repo?.full_name !== "string" || !repo.full_name)
15
+ continue;
16
+ repos.push({
17
+ slug: repo.full_name,
18
+ description: typeof repo.description === "string" ? repo.description : "",
19
+ private: Boolean(repo.private),
20
+ pushedAt: typeof repo.pushed_at === "string" ? repo.pushed_at : undefined,
21
+ defaultBranch: typeof repo.default_branch === "string" ? repo.default_branch : undefined,
22
+ });
23
+ }
24
+ if (batch.length < 100)
25
+ return repos;
26
+ }
27
+ }
28
+ /** Query both sources independently; a failed credential must not hide usable repos. */
29
+ export async function discoverGithubRepos(sources) {
30
+ const results = await Promise.allSettled([sources.appRepos(), sources.userRepos()]);
31
+ const repos = new Map();
32
+ const errors = [];
33
+ let authed = false;
34
+ for (const [index, result] of results.entries()) {
35
+ if (result.status === "rejected") {
36
+ errors.push(`${index === 0 ? "GitHub App" : "GitHub user"}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`);
37
+ continue;
38
+ }
39
+ if (result.value === undefined)
40
+ continue;
41
+ authed = true;
42
+ for (const repo of result.value) {
43
+ const key = repo.slug.toLowerCase();
44
+ if (!repos.has(key))
45
+ repos.set(key, repo);
46
+ }
47
+ }
48
+ const listing = {
49
+ authed,
50
+ repos: [...repos.values()].sort((a, b) => (b.pushedAt ?? "").localeCompare(a.pushedAt ?? "") || a.slug.localeCompare(b.slug)),
51
+ };
52
+ if (errors.length)
53
+ listing.error = errors.join("; ");
54
+ else if (!authed)
55
+ listing.reason = await sources.ghInstalled() ? "gh-unauthed" : "no-token";
56
+ return listing;
57
+ }
package/dist/server.js CHANGED
@@ -144,6 +144,7 @@ import { SecretVault, resolveSecret } from "./secrets.js";
144
144
  import { deviceFlowClientId, requestDeviceCode, pollAccessTokenOnce, REPO_CONNECT_SCOPE } from "./github-device-auth.js";
145
145
  import { InstallationTokenCache, createAppJwt, resolveInstallationId } from "./github-app-auth.js";
146
146
  import { listGithubAppRepos } from "./github-app-repos.js";
147
+ import { discoverGithubRepos, listGithubUserRepos } from "./github-repos.js";
147
148
  import { loadGitHubAppConfigs, orderAppsForOwner, listGitHubApps, removeGitHubApp, upsertGitHubApp, privateKeyIdFor, } from "./github-apps.js";
148
149
  import { buildAppManifest, convertManifest, renderManifestForm } from "./github-app-manifest.js";
149
150
  import { encryptGithubAppEnvelope, decryptGithubAppEnvelope, readLocalGithubAppVaultKey, writeLocalGithubAppVaultKey, forgetLocalGithubAppVaultKey, mintLocalGithubAppVaultKey, } from "./github-app-vault.js";
@@ -10492,46 +10493,25 @@ async function createControlPlaneHook(kind = "github") {
10492
10493
  throw new Error(data?.error || `Could not create Bivy webhook (${res.status})`);
10493
10494
  return data;
10494
10495
  }
10495
- // List the GitHub repos the node can reach with its own token (for picking a
10496
- // git workspace). Uses local App installations when available, otherwise the
10497
- // token stored on this machine (env or `gh`). Returns `authed: false` with
10498
- // an empty list when there's no token so the UI falls back to manual owner/repo.
10496
+ // Combine local App access with the configured user token or gh login, matching
10497
+ // the credentials available to clone/fetch. Cache only complete, successful lists.
10499
10498
  async function listAccessibleRepos() {
10500
10499
  if (reposCache && Date.now() - reposCache.at < REPO_LIST_TTL_MS)
10501
10500
  return reposCache.val;
10502
- try {
10503
- const apps = await ensureGitHubApps();
10504
- if (apps.length) {
10505
- const val = { authed: true, repos: await listGithubAppRepos(apps) };
10506
- reposCache = { at: Date.now(), val };
10507
- return val;
10508
- }
10509
- const token = await resolveGitHubToken();
10510
- if (!token)
10511
- return { authed: false, repos: [], reason: (await ghCliInstalled()) ? "gh-unauthed" : "no-token" };
10512
- const ghRes = await fetch("https://api.github.com/user/repos?sort=updated&per_page=100&affiliation=owner,collaborator,organization_member", {
10513
- headers: { authorization: `Bearer ${token}`, accept: "application/vnd.github+json", "user-agent": "bivy" },
10514
- });
10515
- if (!ghRes.ok)
10516
- return { authed: true, repos: [], error: `GitHub responded ${ghRes.status}` };
10517
- const raw = (await ghRes.json().catch(() => []));
10518
- const repos = (Array.isArray(raw) ? raw : []).map((r) => ({
10519
- slug: String(r.full_name ?? ""),
10520
- description: typeof r.description === "string" ? r.description : "",
10521
- private: Boolean(r.private),
10522
- pushedAt: typeof r.pushed_at === "string" ? r.pushed_at : undefined,
10523
- // GitHub already hands us each repo's default branch here, so the branch
10524
- // picker can label "Default branch (main)" instantly from the cached repo
10525
- // list — no separate /repos/{owner}/{repo} round trip when it opens.
10526
- defaultBranch: typeof r.default_branch === "string" ? r.default_branch : undefined,
10527
- })).filter((r) => r.slug);
10528
- const val = { authed: true, repos };
10501
+ const val = await discoverGithubRepos({
10502
+ appRepos: async () => {
10503
+ const apps = await ensureGitHubApps();
10504
+ return apps.length ? listGithubAppRepos(apps) : undefined;
10505
+ },
10506
+ userRepos: async () => {
10507
+ const token = await resolveGitHubToken();
10508
+ return token ? listGithubUserRepos(token) : undefined;
10509
+ },
10510
+ ghInstalled: ghCliInstalled,
10511
+ });
10512
+ if (val.authed && !val.error)
10529
10513
  reposCache = { at: Date.now(), val };
10530
- return val;
10531
- }
10532
- catch (error) {
10533
- return { authed: false, repos: [], error: error instanceof Error ? error.message : String(error) };
10534
- }
10514
+ return val;
10535
10515
  }
10536
10516
  let pendingGithubConnect = null;
10537
10517
  async function startGithubConnect() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bivy/bivy",
3
- "version": "0.18.5-staging.6",
3
+ "version": "0.18.5-staging.7",
4
4
  "type": "module",
5
5
  "license": "AGPL-3.0-only",
6
6
  "description": "Run coding agents on machines you own. Open-source, self-hostable agent workspace.",