app/cli/lib/watchedRepoResolution.ts
zzmq.dev@gmail.com/zz / app / cli / lib / watchedRepoResolution.ts
@ b9c01cc1
Download
Ladder Summary
- file The module implements the logic for determining which GitHub repository slugs the current **zz** session should consider “watched,” combining explicit `--repos` arguments, a logged‑in server‑side list, the user’s `~/.zz/config.json` fallback, and finally a local discovery of running zz processes and the current directory. It prints onboarding guidance when no local repos are found and never performs any cloning itself. **Exported members** - `resolveWatchedRepoSlugs` – async function that returns an array of repo slugs or `null` (the “nothing to show” case) based on the priority rules described above.
+// Shared "which repos am I watching" resolution -- originally `zz
+// top`'s own logic, extracted so `zz work --assist`'s ask picker (which
+// needs this same answer, but must never eagerly clone anything the way
+// `zz work`'s normal body does) can reuse it verbatim instead of
+// drifting out of sync with `zz top`'s own behavior.
+import { execFileSync } from "node:child_process";
+import { resolveRepo, isLocallyOnboarded, inferGitHubSlugFromCwd } from "./repo.js";
+import { configuredRepos } from "./config.js";
+import { apiFetch } from "./api.js";
+import { listRunningRepos } from "./processRegistry.js";
+import type { BackendMode } from "./backendMode.js";
+/** No explicit config/--repos, local mode: union whatever other local `zz work`/`zz review` processes are watching with the current directory, if it's an onboarded GitHub-backed repo. Prints onboarding guidance and returns null (meaning "nothing to show, caller should exit/show empty") rather than ever pretending there's something to watch. */
+async function discoverLocalRepos(): Promise<string[] | null> {
+ const running = listRunningRepos();
+ const cwdSlug = inferGitHubSlugFromCwd();
+ const cwdRepo = cwdSlug ? resolveRepo(cwdSlug) : null;
+ const cwdOnboarded = cwdRepo ? await isLocallyOnboarded(cwdRepo) : false;
+ const repos = cwdOnboarded && cwdSlug ? [...new Set([...running, cwdSlug])] : running;
+ if (repos.length > 0) return repos;
+
+ if (cwdSlug && !cwdOnboarded) {
+ console.log(`Nothing to show yet. This directory looks like it's "${cwdSlug}" on GitHub, but it isn't onboarded into zz locally. Run:\n\n zz ladder build ${cwdSlug}\n\nthen try again.`);
+ } else if (cwdRepo === null && cwdSlugLooksLikeGitRepo()) {
+ console.log(`Nothing to show yet. This directory is a git repo, but its origin isn't a GitHub remote -- \`zz ladder build\` (zz's only onboarding path right now) needs one. Run \`zz work --local <owner/repo>\`/\`zz review --local <owner/repo>\` directly on a repo you've already onboarded, or use Explore (local mode browses the filesystem) from a terminal where one is nearby.`);
+ } else {
+ console.log("Nothing to show yet -- no other local zz process is running, and this directory isn't a git repo. Run `zz work`/`zz review` on a repo, or `zz ladder build owner/repo` to onboard one from GitHub, then try again.");
+ }
+ return null;
+}
+
+// A plain git-repo check (no GitHub remote needed) just to pick the
+// right wording above -- inferGitHubSlugFromCwd() returning null is
+// ambiguous between "not a git repo" and "git repo, no GitHub
+// remote," and those two deserve different guidance.
+function cwdSlugLooksLikeGitRepo(): boolean {
+ try {
+ execFileSync("git", ["rev-parse", "--show-toplevel"], { stdio: "ignore" });
+ return true;
+ } catch {
+ return false;
+ }
+}
+
+/**
+ * Priority: --repos (explicit, always wins) > the server's own
+ * per-identity list, if logged in (never mixed with a local config file
+ * that isn't scoped per-identity -- confirmed live: logging in as a
+ * second account still showed the first account's repos, because
+ * ~/.zz/config.json was being checked unconditionally before login
+ * state at all) > ~/.zz/config.json (the fallback for the two cases
+ * with no server-side list to use instead: remote but not logged in, or
+ * local mode) > local discovery > empty.
+ *
+ * Never clones or otherwise touches disk beyond resolving paths --
+ * callers that need an actual working tree (zz work's normal body, an
+ * assist session once an ask is picked) do that themselves, deliberately
+ * separate from "what am I watching" so browsing/picking never triggers
+ * it as a side effect.
+ *
+ * Returns null only for the local-mode "truly nothing to watch" case
+ * (discoverLocalRepos already printed onboarding guidance) -- callers
+ * that would otherwise render a pointless empty view should treat that
+ * as "print nothing more, exit," same as `zz top` always has; an empty
+ * array (not logged in remotely, no config) is a normal state worth
+ * still rendering a view for.
+ */
+export async function resolveWatchedRepoSlugs(mode: BackendMode, options: { repos?: string[] }): Promise<string[] | null> {
+ if (options.repos && options.repos.length > 0) return options.repos;
+ if (mode.useRemote && mode.identity) {
+ return (await apiFetch<{ repos: { slug: string }[] }>("GET", "/api/me/repos")).repos.map((r) => r.slug);
+ }
+ const configured = configuredRepos();
+ if (configured) return configured;
+ if (mode.useRemote) return []; // not logged in, no config -- empty dashboard
+ return discoverLocalRepos();
+}
+