@tokenfactory/acc-runner 0.43.0 → 0.44.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 (58) hide show
  1. package/README.md +52 -3
  2. package/dist/companion-run.d.ts.map +1 -1
  3. package/dist/companion-run.js +20 -0
  4. package/dist/companion-run.js.map +1 -1
  5. package/dist/config.d.ts +72 -0
  6. package/dist/config.d.ts.map +1 -1
  7. package/dist/config.js +117 -3
  8. package/dist/config.js.map +1 -1
  9. package/dist/doctor.d.ts.map +1 -1
  10. package/dist/doctor.js +13 -3
  11. package/dist/doctor.js.map +1 -1
  12. package/dist/engines/registry.d.ts +17 -6
  13. package/dist/engines/registry.d.ts.map +1 -1
  14. package/dist/engines/registry.js +38 -13
  15. package/dist/engines/registry.js.map +1 -1
  16. package/dist/git-auth/github-token-source.d.ts +54 -0
  17. package/dist/git-auth/github-token-source.d.ts.map +1 -0
  18. package/dist/git-auth/github-token-source.js +44 -0
  19. package/dist/git-auth/github-token-source.js.map +1 -0
  20. package/dist/git-auth/index.d.ts +4 -0
  21. package/dist/git-auth/index.d.ts.map +1 -1
  22. package/dist/git-auth/index.js +4 -0
  23. package/dist/git-auth/index.js.map +1 -1
  24. package/dist/git-auth/installation-token.d.ts +95 -0
  25. package/dist/git-auth/installation-token.d.ts.map +1 -0
  26. package/dist/git-auth/installation-token.js +192 -0
  27. package/dist/git-auth/installation-token.js.map +1 -0
  28. package/dist/local-repo/command.d.ts +28 -0
  29. package/dist/local-repo/command.d.ts.map +1 -0
  30. package/dist/local-repo/command.js +178 -0
  31. package/dist/local-repo/command.js.map +1 -0
  32. package/dist/local-repo/index.d.ts +119 -0
  33. package/dist/local-repo/index.d.ts.map +1 -0
  34. package/dist/local-repo/index.js +141 -0
  35. package/dist/local-repo/index.js.map +1 -0
  36. package/dist/mcp-spawn.d.ts +19 -0
  37. package/dist/mcp-spawn.d.ts.map +1 -1
  38. package/dist/mcp-spawn.js +52 -26
  39. package/dist/mcp-spawn.js.map +1 -1
  40. package/dist/program.d.ts.map +1 -1
  41. package/dist/program.js +21 -0
  42. package/dist/program.js.map +1 -1
  43. package/dist/provider-auth.d.ts +51 -1
  44. package/dist/provider-auth.d.ts.map +1 -1
  45. package/dist/provider-auth.js +83 -2
  46. package/dist/provider-auth.js.map +1 -1
  47. package/dist/runtime/reviewer.d.ts.map +1 -1
  48. package/dist/runtime/reviewer.js +38 -24
  49. package/dist/runtime/reviewer.js.map +1 -1
  50. package/dist/task-runner.d.ts +11 -2
  51. package/dist/task-runner.d.ts.map +1 -1
  52. package/dist/task-runner.js +73 -25
  53. package/dist/task-runner.js.map +1 -1
  54. package/dist/watch.d.ts +13 -0
  55. package/dist/watch.d.ts.map +1 -1
  56. package/dist/watch.js +81 -1
  57. package/dist/watch.js.map +1 -1
  58. package/package.json +1 -1
@@ -0,0 +1,192 @@
1
+ /**
2
+ * NS-RUN-3: per-container GitHub App installation tokens.
3
+ *
4
+ * Headless/container runners have historically shared one long-lived GH_TOKEN
5
+ * PAT across every node in the fleet (see docs/acc/RUNNER_AUTOSCALE.md §2,
6
+ * `ACC_AUTOSCALE_RUNNER_ENV_JSON`) — a single credential whose blast radius is
7
+ * the whole fleet's repo access, that never expires until an operator rotates
8
+ * it by hand, and that cannot be scoped tighter than whatever repos the PAT
9
+ * itself carries.
10
+ *
11
+ * This module is the RUNNER-SIDE client for replacing that PAT with
12
+ * short-lived (~1h) GitHub App installation tokens, scoped to only the
13
+ * repo(s) a given container is configured to serve. The privileged half of
14
+ * the mint — signing a JWT with the GitHub App's private key and exchanging
15
+ * it for an installation access token — already exists server-side in
16
+ * `api/_lib/github-app.ts` (`getInstallationTokenForRepo`); this module never
17
+ * holds that private key, only the short-lived bearer it fetches over HTTPS
18
+ * using the runner's OWN control-plane JWT — the same "runner authenticates,
19
+ * server does the privileged work" shape `secrets/inject.ts`'s
20
+ * `{{secret:NAME}}` broker already uses for tool-call secrets.
21
+ *
22
+ * Scope enforcement is defense-in-depth on BOTH sides: the request always
23
+ * names the exact repos this runner serves (never "all installs"), and this
24
+ * client additionally REFUSES to use a minted token whose granted repos
25
+ * exceed what it asked for (`InstallationTokenScopeError`) — a
26
+ * misconfigured or compromised server response cannot hand this container a
27
+ * broader credential than it was provisioned for.
28
+ *
29
+ * Wiring note (tracked as a follow-up, out of this module's file scope): the
30
+ * HTTP endpoint this fetcher targets (`/api/runner/github-installation-token`)
31
+ * does not exist yet, and neither `packages/acc-runner/docker/entrypoint.sh`
32
+ * nor `api/scheduled/runner-autoscale.ts` have been pointed at this provider
33
+ * in place of a shared `GH_TOKEN`. This module is the tested client primitive
34
+ * that follow-up work wires in.
35
+ */
36
+ import { maskGitCredentials } from "./probe.js";
37
+ /**
38
+ * Thrown when a mint hands back a token scoped to repos beyond what the
39
+ * runner requested. This is the client-side half of "token scope limited to
40
+ * the repos the runner serves" (AC3): even a server that returns an
41
+ * over-broad grant cannot make this runner USE the excess scope, because the
42
+ * provider refuses the token outright rather than caching/serving it.
43
+ */
44
+ export class InstallationTokenScopeError extends Error {
45
+ requested;
46
+ granted;
47
+ constructor(requested, granted) {
48
+ super(`installation token scope exceeds request: requested [${requested.join(", ")}], granted [${granted.join(", ")}]`);
49
+ this.name = "InstallationTokenScopeError";
50
+ this.requested = requested;
51
+ this.granted = granted;
52
+ }
53
+ }
54
+ function normaliseRepo(repo) {
55
+ return repo.trim().toLowerCase();
56
+ }
57
+ /**
58
+ * True when `granted` is a NON-EMPTY set and every entry is present in
59
+ * `requested` (case-insensitive).
60
+ *
61
+ * AC5: an empty grant is NOT treated as in-scope. A mint that hands back zero
62
+ * repos proves nothing about what it actually scoped the token to — vacuously
63
+ * satisfying a subset check would let a malformed or field-less response
64
+ * through as if it were a valid (if narrow) grant. Fail closed instead: no
65
+ * verifiable scope means the token is rejected, same as an over-broad one.
66
+ */
67
+ export function isWithinRequestedScope(requested, granted) {
68
+ if (granted.length === 0)
69
+ return false;
70
+ const allowed = new Set(requested.map(normaliseRepo));
71
+ return granted.every((r) => allowed.has(normaliseRepo(r)));
72
+ }
73
+ /** Default refresh margin: mint a new token once the cached one is this close to expiry. Mirrors api/_lib/github-app.ts's server-side cache. */
74
+ const DEFAULT_REFRESH_MARGIN_MS = 5 * 60 * 1000;
75
+ /**
76
+ * Runner-side cache/refresh for a single installation token, scoped to the
77
+ * repo(s) this container serves. In-memory only — like `EnvTokenProvider`
78
+ * (token-provider.ts), an ephemeral container must never write a credential
79
+ * to its writable layer.
80
+ */
81
+ export class InstallationTokenProvider {
82
+ repos;
83
+ fetcher;
84
+ runnerId;
85
+ refreshMarginMs;
86
+ cached = null;
87
+ inflight = null;
88
+ constructor(opts) {
89
+ const repos = (opts.repos ?? []).map((r) => r.trim()).filter(Boolean);
90
+ if (repos.length === 0) {
91
+ throw new Error("InstallationTokenProvider requires at least one repo — a container must be " +
92
+ "scoped to the repo(s) it serves, never minted a token for every install");
93
+ }
94
+ this.repos = repos;
95
+ this.fetcher = opts.fetcher;
96
+ this.runnerId = opts.runnerId;
97
+ this.refreshMarginMs = Math.max(0, opts.refreshMarginMs ?? DEFAULT_REFRESH_MARGIN_MS);
98
+ }
99
+ /** Repos this provider is configured to serve (defensive copy). */
100
+ reposServed() {
101
+ return [...this.repos];
102
+ }
103
+ /** True when the cached token is still fresh past the refresh margin. */
104
+ fresh() {
105
+ if (!this.cached)
106
+ return false;
107
+ return new Date(this.cached.expiresAt).getTime() - Date.now() > this.refreshMarginMs;
108
+ }
109
+ /**
110
+ * Return a valid installation token, minting or refreshing as needed.
111
+ * Concurrent callers share one in-flight mint (mirrors the server-side
112
+ * single-flight in `api/_lib/github-app.ts`) so a burst of git operations
113
+ * at container boot doesn't fire N redundant mints. A rejected mint clears
114
+ * the in-flight slot so the NEXT call retries rather than replaying the
115
+ * same rejection forever.
116
+ */
117
+ async getToken() {
118
+ if (this.fresh())
119
+ return this.cached.token;
120
+ if (this.inflight)
121
+ return (await this.inflight).token;
122
+ const mint = this.mint().finally(() => {
123
+ this.inflight = null;
124
+ });
125
+ this.inflight = mint;
126
+ return (await mint).token;
127
+ }
128
+ async mint() {
129
+ const result = await this.fetcher(this.repos, this.runnerId);
130
+ if (!isWithinRequestedScope(this.repos, result.repos)) {
131
+ throw new InstallationTokenScopeError(this.repos, result.repos);
132
+ }
133
+ this.cached = result;
134
+ return result;
135
+ }
136
+ /** Diagnostics-safe snapshot — NEVER includes the token itself. */
137
+ snapshot() {
138
+ return {
139
+ hasToken: this.cached !== null,
140
+ expiresAt: this.cached?.expiresAt ?? null,
141
+ repos: this.repos,
142
+ };
143
+ }
144
+ }
145
+ /**
146
+ * Build an HTTP-backed `InstallationTokenFetcher` against the (not-yet-shipped)
147
+ * `/api/runner/github-installation-token` endpoint. Mirrors
148
+ * `secrets/inject.ts`'s `makeHttpSecretFetcher`: the runner authenticates
149
+ * with its own bearer JWT, the server does the privileged GitHub App
150
+ * JWT-sign + installation-token exchange (`api/_lib/github-app.ts`), and only
151
+ * the resulting short-lived, repo-scoped token crosses the wire back.
152
+ */
153
+ export function makeHttpInstallationTokenFetcher(opts) {
154
+ const f = opts.fetchImpl ?? fetch;
155
+ const endpoint = `${opts.publicUrl.replace(/\/$/, "")}/api/runner/github-installation-token`;
156
+ return async (repos, runnerId) => {
157
+ const res = await f(endpoint, {
158
+ method: "POST",
159
+ headers: {
160
+ Authorization: `Bearer ${opts.jwt}`,
161
+ "Content-Type": "application/json",
162
+ },
163
+ body: JSON.stringify({ repos, runner_id: runnerId }),
164
+ });
165
+ if (!res.ok) {
166
+ // Masked defensively: the endpoint contract is that the body never
167
+ // carries token material, but a broken/aliased endpoint could echo
168
+ // request state back — never trust an error body unmasked.
169
+ const text = await res.text().catch(() => "");
170
+ throw new Error(`github-installation-token HTTP ${res.status}: ${maskGitCredentials(text || res.statusText)}`);
171
+ }
172
+ const data = (await res.json());
173
+ if (typeof data.token !== "string" || typeof data.expires_at !== "string") {
174
+ throw new Error("github-installation-token response missing token/expires_at");
175
+ }
176
+ // AC5: a field-less or empty `repos` is rejected here, not silently
177
+ // trusted back to whatever we asked for — an over-broad or malformed
178
+ // server response must never be papered over by re-stating the request
179
+ // as if it were the grant. isWithinRequestedScope() in the caller
180
+ // additionally refuses an empty grant, so this is defense-in-depth: the
181
+ // fetcher itself never fabricates a scope the server didn't state.
182
+ if (!Array.isArray(data.repos) || data.repos.length === 0) {
183
+ throw new Error("github-installation-token response missing/empty repos (no fallback to the requested repos)");
184
+ }
185
+ const grantedRepos = data.repos.filter((r) => typeof r === "string");
186
+ if (grantedRepos.length !== data.repos.length) {
187
+ throw new Error("github-installation-token response repos contains a non-string entry");
188
+ }
189
+ return { token: data.token, expiresAt: data.expires_at, repos: grantedRepos };
190
+ };
191
+ }
192
+ //# sourceMappingURL=installation-token.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installation-token.js","sourceRoot":"","sources":["../../src/git-auth/installation-token.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import { type LocalRepoProbe } from "./index.js";
2
+ export interface AddLocalRepoOptions {
3
+ /** Override the repo name (defaults to the directory name). */
4
+ name?: string;
5
+ /** Attach the repo to this project slug as part of the registration. */
6
+ project?: string;
7
+ /** Test seam. */
8
+ out?: (line: string) => void;
9
+ }
10
+ /**
11
+ * Probe a candidate directory. Every question is answered from the filesystem or
12
+ * a single read-only `git` call, and a failing `git` is an answer ("no origin"),
13
+ * never an exception — planLocalRepoRegistration decides what is fatal.
14
+ */
15
+ export declare function probeLocalRepo(rawPath: string, nameOverride?: string | null): LocalRepoProbe;
16
+ export declare function addLocalRepoCommand(repoPath: string, opts?: AddLocalRepoOptions): Promise<void>;
17
+ export interface ListLocalReposOptions {
18
+ json?: boolean;
19
+ out?: (line: string) => void;
20
+ }
21
+ /**
22
+ * What this machine believes it serves locally, and whether the server agrees.
23
+ * The two can disagree in both directions — a config entry whose grant was
24
+ * revoked, or a grant registered from another machine — and an operator
25
+ * debugging "why is my task not running" needs to see which side is missing.
26
+ */
27
+ export declare function listLocalReposCommand(opts?: ListLocalReposOptions): Promise<void>;
28
+ //# sourceMappingURL=command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/local-repo/command.ts"],"names":[],"mappings":"AAoCA,OAAO,EAIL,KAAK,cAAc,EAEpB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,mBAAmB;IAClC,+DAA+D;IAC/D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iBAAiB;IACjB,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAUD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,cAAc,CA0B5F;AAED,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,mBAAwB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAyDf;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAED;;;;;GAKG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,GAAE,qBAA0B,GAC/B,OAAO,CAAC,IAAI,CAAC,CA2Df"}
@@ -0,0 +1,178 @@
1
+ /**
2
+ * LOCAL-REPO-CLI — `acc repo add-local <path>` and `acc repo list-local`.
3
+ *
4
+ * The plumbing around ./index.ts's pure rules: probe the directory, call
5
+ * acc.register_local_repo under the operator's OWN session, and write the
6
+ * resulting slug→path pair into this machine's config.json.
7
+ *
8
+ * WHY THE PERSON LANE AND NOT THE RUNNER LANE
9
+ * ----------------------------------------------------------------------------
10
+ * The runner session is an ACC-signed JWT that impersonates a machine's owner.
11
+ * It would authenticate fine, and using it would be wrong: the row being written
12
+ * is "THIS human grants THEMSELVES access to a repo on their disk", and the
13
+ * server refuses a null auth.uid() on that path for exactly that reason. So this
14
+ * command runs on the generalized device flow's person credential (`acc login`),
15
+ * the same one `acc chat` and `acc task list` use, and the machine identity is
16
+ * computed locally rather than taken from a session — a person may register a
17
+ * repo before they have ever enrolled this host as a runner.
18
+ *
19
+ * ORDER MATTERS: the server write happens FIRST, the local config write second.
20
+ * A local map entry pointing at a repo the server does not know is a silently
21
+ * dead directory reference; a server registration whose local path failed to
22
+ * save is a legible error the operator can fix by re-running (both writes are
23
+ * idempotent upserts).
24
+ */
25
+ import { execFileSync } from "node:child_process";
26
+ import fs from "node:fs";
27
+ import os from "node:os";
28
+ import path from "node:path";
29
+ import chalk from "chalk";
30
+ import { loadLocalReposFromConfigFile, userConfigFilePath, writeLocalRepoToConfigFile, } from "../config.js";
31
+ import { machineIdentity } from "../login.js";
32
+ import { userClient } from "../user-cli/client.js";
33
+ import { isMigrationPending, localRepoDisplayName, planLocalRepoRegistration, } from "./index.js";
34
+ /** `~/code/app` → `/Users/me/code/app`. Absolute paths pass through. */
35
+ function expandHome(p) {
36
+ const trimmed = p.trim();
37
+ if (trimmed === "~")
38
+ return os.homedir();
39
+ if (trimmed.startsWith("~/"))
40
+ return path.resolve(os.homedir(), trimmed.slice(2));
41
+ return path.resolve(trimmed);
42
+ }
43
+ /**
44
+ * Probe a candidate directory. Every question is answered from the filesystem or
45
+ * a single read-only `git` call, and a failing `git` is an answer ("no origin"),
46
+ * never an exception — planLocalRepoRegistration decides what is fatal.
47
+ */
48
+ export function probeLocalRepo(rawPath, nameOverride) {
49
+ const abs = expandHome(rawPath);
50
+ let isDirectory = false;
51
+ try {
52
+ isDirectory = fs.statSync(abs).isDirectory();
53
+ }
54
+ catch {
55
+ isDirectory = false;
56
+ }
57
+ // A `.git` FILE (not directory) is a worktree or submodule — still a git repo,
58
+ // so existsSync rather than an isDirectory check.
59
+ const isGitRepo = isDirectory && fs.existsSync(path.join(abs, ".git"));
60
+ let originUrl = null;
61
+ if (isGitRepo) {
62
+ try {
63
+ const out = execFileSync("git", ["-C", abs, "remote", "get-url", "origin"], {
64
+ encoding: "utf8",
65
+ stdio: ["ignore", "pipe", "ignore"],
66
+ });
67
+ originUrl = out.trim() || null;
68
+ }
69
+ catch {
70
+ originUrl = null;
71
+ }
72
+ }
73
+ return { path: abs, isDirectory, isGitRepo, originUrl, nameOverride: nameOverride ?? null };
74
+ }
75
+ export async function addLocalRepoCommand(repoPath, opts = {}) {
76
+ const out = opts.out ?? ((line) => console.log(line));
77
+ const plan = planLocalRepoRegistration(probeLocalRepo(repoPath, opts.name));
78
+ const machine = machineIdentity();
79
+ const sb = await userClient();
80
+ const { data, error } = await sb.rpc("register_local_repo", {
81
+ p_name: plan.name,
82
+ p_machine: machine,
83
+ p_project_slug: opts.project?.trim() || null,
84
+ });
85
+ if (error) {
86
+ if (isMigrationPending(error)) {
87
+ throw new Error("local repos are unavailable — migration 0318 has not been applied to this " +
88
+ "environment yet. Until it is, attach repos through the GitHub App path.");
89
+ }
90
+ throw new Error(`could not register ${plan.name}: ${error.message}`);
91
+ }
92
+ const result = data;
93
+ if (!result?.repo) {
94
+ throw new Error("register_local_repo returned no repo slug — nothing was recorded locally");
95
+ }
96
+ const written = writeLocalRepoToConfigFile(result.repo, plan.path);
97
+ out(chalk.green(`✓ registered ${result.repo}`));
98
+ out(` path ${plan.path} ${chalk.dim("(this machine only)")}`);
99
+ out(` machine ${machine}`);
100
+ out(` access ${result.level} · source ${result.source} · you only`);
101
+ if (result.project_slug) {
102
+ out(` project ${result.project_slug}`);
103
+ }
104
+ out(chalk.dim(` recorded in ${written.path}`));
105
+ for (const warning of plan.warnings) {
106
+ out(chalk.yellow(` ! ${warning}`));
107
+ }
108
+ if (written.addedToServedRepos) {
109
+ out(chalk.yellow(" ! Added the slug to this machine's served-repo scope. Restart `acc watch` " +
110
+ "so it re-advertises its capabilities."));
111
+ }
112
+ out("");
113
+ out("Next: run `acc watch` on this machine to execute tasks for this repo.");
114
+ if (!result.project_slug) {
115
+ out(chalk.dim("It is now selectable in the project-create repo step, or attach it to an " +
116
+ "existing project with --project <slug>."));
117
+ }
118
+ }
119
+ /**
120
+ * What this machine believes it serves locally, and whether the server agrees.
121
+ * The two can disagree in both directions — a config entry whose grant was
122
+ * revoked, or a grant registered from another machine — and an operator
123
+ * debugging "why is my task not running" needs to see which side is missing.
124
+ */
125
+ export async function listLocalReposCommand(opts = {}) {
126
+ const out = opts.out ?? ((line) => console.log(line));
127
+ const local = loadLocalReposFromConfigFile();
128
+ let granted = [];
129
+ let grantError = null;
130
+ try {
131
+ const sb = await userClient();
132
+ // RLS scopes acc.user_repo_access to auth.uid(), so no user predicate is
133
+ // needed — and none should be added: the caller's own rows are the answer.
134
+ const { data, error } = await sb
135
+ .from("user_repo_access")
136
+ .select("repo, level, source")
137
+ .eq("source", "cli");
138
+ if (error)
139
+ throw new Error(error.message);
140
+ granted = (data ?? [])
141
+ .map((r) => r.repo)
142
+ .filter((r) => typeof r === "string");
143
+ }
144
+ catch (err) {
145
+ // Offline / signed out is not a reason to refuse to print the local half.
146
+ grantError = err.message;
147
+ }
148
+ if (opts.json) {
149
+ out(JSON.stringify({
150
+ config_path: userConfigFilePath(),
151
+ local_repos: local,
152
+ granted_repos: granted,
153
+ grant_error: grantError,
154
+ }, null, 2));
155
+ return;
156
+ }
157
+ const slugs = [...new Set([...Object.keys(local), ...granted.map((r) => r.toLowerCase())])].sort();
158
+ if (slugs.length === 0) {
159
+ out("No local repos registered on this machine.");
160
+ out(chalk.dim("Add one with `acc repo add-local <path>`."));
161
+ return;
162
+ }
163
+ for (const slug of slugs) {
164
+ const dir = local[slug];
165
+ const isGranted = granted.some((r) => r.toLowerCase() === slug);
166
+ const state = !dir
167
+ ? chalk.yellow("registered on another machine — no local path here")
168
+ : !isGranted && !grantError
169
+ ? chalk.yellow("no server grant — re-run `acc repo add-local`")
170
+ : chalk.dim(dir);
171
+ out(` ${localRepoDisplayName(slug).padEnd(24)} ${slug}`);
172
+ out(` ${" ".repeat(24)} ${state}`);
173
+ }
174
+ if (grantError) {
175
+ out(chalk.yellow(`! could not read your server grants: ${grantError}`));
176
+ }
177
+ }
178
+ //# sourceMappingURL=command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.js","sourceRoot":"","sources":["../../src/local-repo/command.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,119 @@
1
+ /**
2
+ * LOCAL-REPO-CLI — the decidable half of `acc repo add-local`.
3
+ *
4
+ * Everything here is pure: given a path on disk and a few probe results, decide
5
+ * what to register, what to refuse, and what to warn about. The plumbing that
6
+ * needs a keychain, a network and a filesystem lives in ./command.ts, the same
7
+ * split serve-scope uses — so every rule below is unit-testable without either.
8
+ *
9
+ * WHAT A "LOCAL REPO" IS, AND WHAT IT IS NOT
10
+ * ----------------------------------------------------------------------------
11
+ * It is a git working copy on THIS machine that ACC may run tasks in, attached
12
+ * to a project without the GitHub App being installed anywhere. That is the
13
+ * whole of it. In particular it is NOT:
14
+ *
15
+ * * a mirror of a GitHub repo ACC will keep in sync — nothing here clones,
16
+ * fetches or pushes on the server's behalf;
17
+ * * a repo other people on the team can work. The grant the server records is
18
+ * scoped to the registering user, and the path is known only to this
19
+ * machine, so a teammate's runner has neither the right nor the directory;
20
+ * * a way to skip a GitHub write grant on a repo that IS on GitHub. The
21
+ * server derives the slug from the caller's user id into a reserved
22
+ * `local.…` namespace precisely so this cannot be that.
23
+ *
24
+ * WHY THE PATH NEVER LEAVES THE MACHINE
25
+ * ----------------------------------------------------------------------------
26
+ * The server needs to know "machine M serves repo R" to route a task; it does
27
+ * not need to know that R is at /Users/priya/code/side-project. Sending the path
28
+ * would put a filesystem layout — usernames, project names, directory
29
+ * structure — into a shared database for no routing benefit, so the path stays
30
+ * in this machine's own config.json (`local-repos`) and the runner resolves it
31
+ * at task time. See docs/acc/LOCAL_REPO_CLI_SETUP.md.
32
+ */
33
+ /** The reserved namespace the server mints local slugs into (acc.local_repo_slug). */
34
+ export declare const LOCAL_REPO_PREFIX = "local.";
35
+ /**
36
+ * Is this slug in the reserved local/CLI namespace? Mirrors
37
+ * acc.is_local_repo_slug (migration 0318) and src/lib/local-repo.ts. A GitHub
38
+ * owner segment can never contain a '.', so the two namespaces cannot collide.
39
+ */
40
+ export declare function isLocalRepoSlug(repo: string | null | undefined): boolean;
41
+ /**
42
+ * The human-facing name of a local repo — the part after the '/', without the
43
+ * per-user hash fragment that makes the slug unique. `local.9f2c1a/my-app` reads
44
+ * as "my-app (local)" in a list; the full slug is still what gets matched.
45
+ */
46
+ export declare function localRepoDisplayName(repo: string): string;
47
+ /**
48
+ * Slugify a repo NAME the way acc.local_repo_slug does server-side, so the CLI
49
+ * can predict (and preview) the slug it is about to be handed. Kept in sync
50
+ * deliberately — the server is authoritative and the CLI stores whatever comes
51
+ * back, so a drift here is a cosmetic preview bug, never a wrong write.
52
+ */
53
+ export declare function sanitizeLocalRepoName(raw: string): string;
54
+ /** What ./command.ts probed about the directory the operator pointed at. */
55
+ export interface LocalRepoProbe {
56
+ /** The resolved absolute path. */
57
+ path: string;
58
+ /** Does the path exist and is it a directory? */
59
+ isDirectory: boolean;
60
+ /** Is it a git repository (a `.git` entry at the root)? */
61
+ isGitRepo: boolean;
62
+ /**
63
+ * `git remote get-url origin`, or null when there is no origin. Not a
64
+ * requirement — it only decides which warning the operator gets.
65
+ */
66
+ originUrl: string | null;
67
+ /** Explicit `--name`, when the operator overrode the directory name. */
68
+ nameOverride?: string | null;
69
+ }
70
+ export interface LocalRepoPlan {
71
+ /** The name to send; the server derives the slug from it. */
72
+ name: string;
73
+ /** The absolute path to record in config.json. */
74
+ path: string;
75
+ /** The slug the server is expected to mint, for the pre-flight preview. */
76
+ previewSlug: string;
77
+ /**
78
+ * Advisory notes to print before the write. A plan always registers — these
79
+ * are the things an operator would rather learn now than at task time.
80
+ */
81
+ warnings: string[];
82
+ }
83
+ /**
84
+ * Decide what to register for a probed directory, or throw with the reason it
85
+ * cannot be registered.
86
+ *
87
+ * Refusals are limited to the two facts that make registration meaningless: the
88
+ * path is not a directory, or it is not a git repository (the runner's whole
89
+ * task loop is `git` — branch, commit, diff — so a plain folder would fail at
90
+ * the first step rather than at a legible place).
91
+ *
92
+ * A missing `origin` is NOT a refusal. Running tasks in a local working copy is
93
+ * useful on its own; it is opening a PR at the end that needs a remote, and the
94
+ * warning says exactly that instead of pre-emptively blocking the register.
95
+ */
96
+ export declare function planLocalRepoRegistration(probe: LocalRepoProbe): LocalRepoPlan;
97
+ /** The shape acc.register_local_repo returns (migration 0318 §7). */
98
+ export interface RegisterLocalRepoResult {
99
+ repo: string;
100
+ machine: string;
101
+ org_id: string;
102
+ assignment_id: string | null;
103
+ project_id: string | null;
104
+ project_slug: string | null;
105
+ level: string;
106
+ source: string;
107
+ }
108
+ /**
109
+ * Is this error the "migration 0318 has not been applied here yet" shape?
110
+ * HALT-before-apply means every environment has a window where this command
111
+ * ships and the RPC does not exist; "run psql" and "something is broken" have
112
+ * different remedies, so the command says which. Mirrors serve-scope's
113
+ * isMigrationPending.
114
+ */
115
+ export declare function isMigrationPending(err: {
116
+ message?: string;
117
+ code?: string;
118
+ } | null): boolean;
119
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/local-repo/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,sFAAsF;AACtF,eAAO,MAAM,iBAAiB,WAAW,CAAC;AAE1C;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAKxE;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGzD;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAOzD;AAED,4EAA4E;AAC5E,MAAM,WAAW,cAAc;IAC7B,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,WAAW,EAAE,OAAO,CAAC;IACrB,2DAA2D;IAC3D,SAAS,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,wEAAwE;IACxE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,MAAM,WAAW,aAAa;IAC5B,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,2EAA2E;IAC3E,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,cAAc,GAAG,aAAa,CAuC9E;AAYD,qEAAqE;AACrE,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,GAAG,OAAO,CAY3F"}