@tokenfactory/acc-runner 0.43.0 → 0.44.1

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 (110) hide show
  1. package/README.md +75 -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/cost-pricing.d.ts +45 -0
  10. package/dist/cost-pricing.d.ts.map +1 -1
  11. package/dist/cost-pricing.js +57 -0
  12. package/dist/cost-pricing.js.map +1 -1
  13. package/dist/doctor.d.ts +4 -0
  14. package/dist/doctor.d.ts.map +1 -1
  15. package/dist/doctor.js +32 -3
  16. package/dist/doctor.js.map +1 -1
  17. package/dist/engines/registry.d.ts +17 -6
  18. package/dist/engines/registry.d.ts.map +1 -1
  19. package/dist/engines/registry.js +38 -13
  20. package/dist/engines/registry.js.map +1 -1
  21. package/dist/git-auth/github-token-source.d.ts +54 -0
  22. package/dist/git-auth/github-token-source.d.ts.map +1 -0
  23. package/dist/git-auth/github-token-source.js +44 -0
  24. package/dist/git-auth/github-token-source.js.map +1 -0
  25. package/dist/git-auth/index.d.ts +4 -0
  26. package/dist/git-auth/index.d.ts.map +1 -1
  27. package/dist/git-auth/index.js +4 -0
  28. package/dist/git-auth/index.js.map +1 -1
  29. package/dist/git-auth/installation-token.d.ts +95 -0
  30. package/dist/git-auth/installation-token.d.ts.map +1 -0
  31. package/dist/git-auth/installation-token.js +192 -0
  32. package/dist/git-auth/installation-token.js.map +1 -0
  33. package/dist/instance.d.ts +30 -0
  34. package/dist/instance.d.ts.map +1 -0
  35. package/dist/instance.js +51 -0
  36. package/dist/instance.js.map +1 -0
  37. package/dist/keychain.d.ts +4 -8
  38. package/dist/keychain.d.ts.map +1 -1
  39. package/dist/keychain.js +10 -4
  40. package/dist/keychain.js.map +1 -1
  41. package/dist/local-repo/command.d.ts +28 -0
  42. package/dist/local-repo/command.d.ts.map +1 -0
  43. package/dist/local-repo/command.js +178 -0
  44. package/dist/local-repo/command.js.map +1 -0
  45. package/dist/local-repo/index.d.ts +119 -0
  46. package/dist/local-repo/index.d.ts.map +1 -0
  47. package/dist/local-repo/index.js +141 -0
  48. package/dist/local-repo/index.js.map +1 -0
  49. package/dist/login.d.ts +4 -0
  50. package/dist/login.d.ts.map +1 -1
  51. package/dist/login.js +6 -1
  52. package/dist/login.js.map +1 -1
  53. package/dist/mcp-spawn.d.ts +19 -0
  54. package/dist/mcp-spawn.d.ts.map +1 -1
  55. package/dist/mcp-spawn.js +52 -26
  56. package/dist/mcp-spawn.js.map +1 -1
  57. package/dist/memory-context.d.ts +106 -0
  58. package/dist/memory-context.d.ts.map +1 -0
  59. package/dist/memory-context.js +220 -0
  60. package/dist/memory-context.js.map +1 -0
  61. package/dist/memory-curation.d.ts +172 -0
  62. package/dist/memory-curation.d.ts.map +1 -0
  63. package/dist/memory-curation.js +277 -0
  64. package/dist/memory-curation.js.map +1 -0
  65. package/dist/program.d.ts.map +1 -1
  66. package/dist/program.js +34 -0
  67. package/dist/program.js.map +1 -1
  68. package/dist/provider-auth.d.ts +70 -1
  69. package/dist/provider-auth.d.ts.map +1 -1
  70. package/dist/provider-auth.js +106 -2
  71. package/dist/provider-auth.js.map +1 -1
  72. package/dist/runtime/cache-dir.d.ts.map +1 -1
  73. package/dist/runtime/cache-dir.js +8 -3
  74. package/dist/runtime/cache-dir.js.map +1 -1
  75. package/dist/runtime/decision-provenance.d.ts +63 -0
  76. package/dist/runtime/decision-provenance.d.ts.map +1 -0
  77. package/dist/runtime/decision-provenance.js +107 -0
  78. package/dist/runtime/decision-provenance.js.map +1 -0
  79. package/dist/runtime/prompt-substitute.d.ts +100 -0
  80. package/dist/runtime/prompt-substitute.d.ts.map +1 -0
  81. package/dist/runtime/prompt-substitute.js +124 -0
  82. package/dist/runtime/prompt-substitute.js.map +1 -0
  83. package/dist/runtime/review-cost-cap.d.ts +167 -0
  84. package/dist/runtime/review-cost-cap.d.ts.map +1 -0
  85. package/dist/runtime/review-cost-cap.js +182 -0
  86. package/dist/runtime/review-cost-cap.js.map +1 -0
  87. package/dist/runtime/reviewer-abstain.d.ts +101 -0
  88. package/dist/runtime/reviewer-abstain.d.ts.map +1 -0
  89. package/dist/runtime/reviewer-abstain.js +138 -0
  90. package/dist/runtime/reviewer-abstain.js.map +1 -0
  91. package/dist/runtime/reviewer.d.ts +75 -0
  92. package/dist/runtime/reviewer.d.ts.map +1 -1
  93. package/dist/runtime/reviewer.js +235 -81
  94. package/dist/runtime/reviewer.js.map +1 -1
  95. package/dist/runtime/singleton.d.ts.map +1 -1
  96. package/dist/runtime/singleton.js +9 -1
  97. package/dist/runtime/singleton.js.map +1 -1
  98. package/dist/serving-auth/index.d.ts +79 -0
  99. package/dist/serving-auth/index.d.ts.map +1 -0
  100. package/dist/serving-auth/index.js +169 -0
  101. package/dist/serving-auth/index.js.map +1 -0
  102. package/dist/task-runner.d.ts +19 -2
  103. package/dist/task-runner.d.ts.map +1 -1
  104. package/dist/task-runner.js +110 -28
  105. package/dist/task-runner.js.map +1 -1
  106. package/dist/watch.d.ts +13 -0
  107. package/dist/watch.d.ts.map +1 -1
  108. package/dist/watch.js +128 -20
  109. package/dist/watch.js.map +1 -1
  110. package/package.json +3 -2
@@ -0,0 +1,44 @@
1
+ export const GITHUB_TOKEN_SOURCE_ENV = "ACC_RUNNER_GITHUB_TOKEN_SOURCE";
2
+ /**
3
+ * Resolve which source GH_TOKEN should come from. Defaults to "env" (today's
4
+ * behaviour, unchanged) so existing desktop runners and containers with
5
+ * GH_TOKEN baked into their launch env see no change.
6
+ */
7
+ export function resolveGithubTokenSource(env = process.env) {
8
+ const v = (env[GITHUB_TOKEN_SOURCE_ENV] ?? "").trim().toLowerCase();
9
+ return v === "installation-token" || v === "installation_token" ? "installation-token" : "env";
10
+ }
11
+ /**
12
+ * The repo(s) an installation-token mint should be scoped to for this
13
+ * container: the runner's declared served-repo scope (RunnerConfig.repos)
14
+ * when non-empty, else the single configured target repo — the same
15
+ * fallback the pre-multi-repo single-repo default already uses everywhere
16
+ * else in the runner (an undeclared scope means "this one repo", not
17
+ * "every repo the App can see").
18
+ */
19
+ export function effectiveInstallationTokenRepos(cfg) {
20
+ return cfg.repos && cfg.repos.length > 0 ? cfg.repos : [cfg.targetRepo];
21
+ }
22
+ /**
23
+ * Startup/refresh-time env hydration: when ACC_RUNNER_GITHUB_TOKEN_SOURCE is
24
+ * "installation-token", mints (or serves the cached) short-lived, repo-scoped
25
+ * token from `provider` and returns `base` with GH_TOKEN set to it;
26
+ * otherwise returns `base` unchanged. Unlike hydrateAnthropicApiKey, an
27
+ * existing GH_TOKEN in `base` is deliberately OVERWRITTEN when opted in — the
28
+ * whole point is to replace the shared PAT a container's launch env may
29
+ * already carry, not defer to it. Never mutates `base`.
30
+ *
31
+ * Callers invoke this once at startup and again on a refresh timer (the
32
+ * provider's own cache means a call before the refresh margin is a cheap
33
+ * no-op mint).
34
+ */
35
+ export async function hydrateGithubToken(base = process.env, provider) {
36
+ if (resolveGithubTokenSource(base) !== "installation-token")
37
+ return base;
38
+ if (!provider) {
39
+ throw new Error(`${GITHUB_TOKEN_SOURCE_ENV}=installation-token requires an InstallationTokenProvider at startup`);
40
+ }
41
+ const token = await provider.getToken();
42
+ return { ...base, GH_TOKEN: token };
43
+ }
44
+ //# sourceMappingURL=github-token-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-token-source.js","sourceRoot":"","sources":["../../src/git-auth/github-token-source.ts"],"names":[],"mappings":"AAsBA,MAAM,CAAC,MAAM,uBAAuB,GAAG,gCAAyC,CAAC;AAEjF;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CACtC,MAAyB,OAAO,CAAC,GAAG;IAEpC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,uBAAuB,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACpE,OAAO,CAAC,KAAK,oBAAoB,IAAI,CAAC,KAAK,oBAAoB,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,KAAK,CAAC;AACjG,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,+BAA+B,CAAC,GAG/C;IACC,OAAO,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;AAC1E,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,OAA0B,OAAO,CAAC,GAAG,EACrC,QAA0C;IAE1C,IAAI,wBAAwB,CAAC,IAAI,CAAC,KAAK,oBAAoB;QAAE,OAAO,IAAI,CAAC;IACzE,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CACb,GAAG,uBAAuB,sEAAsE,CACjG,CAAC;IACJ,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,CAAC;IACxC,OAAO,EAAE,GAAG,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;AACtC,CAAC"}
@@ -7,6 +7,10 @@
7
7
  export { classifyGitFetchError, isGitAuthFailure, GIT_AUTH_FAILURE_PATTERNS, type GitFetchFailureClass, } from "./classify.js";
8
8
  export { makeGitAuthProbe, maskGitCredentials, type GitAuthProbe, type GitAuthProbeResult, type DefaultProbeOptions, } from "./probe.js";
9
9
  export { GitAuthController, DEFAULT_GIT_AUTH_FAILURE_THRESHOLD, type GitAuthControllerDeps, type GitAuthDegradedInfo, type GitAuthRecoveredInfo, type GitAuthSnapshot, } from "./controller.js";
10
+ /** NS-RUN-3: per-container GitHub App installation tokens (replaces a shared GH_TOKEN PAT). */
11
+ export { InstallationTokenProvider, InstallationTokenScopeError, isWithinRequestedScope, makeHttpInstallationTokenFetcher, type InstallationTokenFetcher, type InstallationTokenProviderOptions, type InstallationTokenResult, } from "./installation-token.js";
12
+ /** NS-RUN-3: where a running container's GH_TOKEN comes from (env vs. minted). */
13
+ export { GITHUB_TOKEN_SOURCE_ENV, resolveGithubTokenSource, effectiveInstallationTokenRepos, hydrateGithubToken, type GithubTokenSource, } from "./github-token-source.js";
10
14
  /** Runner-emitted audit verbs. The alerts tick (api/_lib/alerts-git-auth.ts) */
11
15
  /** scans these to derive currently-degraded machines and page the operator. */
12
16
  export declare const RUNNER_GIT_AUTH_FAILED_VERB = "runner.git_auth_failed";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/git-auth/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,qBAAqB,EACrB,gBAAgB,EAChB,yBAAyB,EACzB,KAAK,oBAAoB,GAC1B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,KAAK,YAAY,EACjB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,GACzB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,iBAAiB,EACjB,kCAAkC,EAClC,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,eAAe,GACrB,MAAM,iBAAiB,CAAC;AAEzB,gFAAgF;AAChF,+EAA+E;AAC/E,eAAO,MAAM,2BAA2B,2BAA2B,CAAC;AACpE,eAAO,MAAM,8BAA8B,8BAA8B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/git-auth/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,qBAAqB,EACrB,gBAAgB,EAChB,yBAAyB,EACzB,KAAK,oBAAoB,GAC1B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,KAAK,YAAY,EACjB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,GACzB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,iBAAiB,EACjB,kCAAkC,EAClC,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,eAAe,GACrB,MAAM,iBAAiB,CAAC;AACzB,+FAA+F;AAC/F,OAAO,EACL,yBAAyB,EACzB,2BAA2B,EAC3B,sBAAsB,EACtB,gCAAgC,EAChC,KAAK,wBAAwB,EAC7B,KAAK,gCAAgC,EACrC,KAAK,uBAAuB,GAC7B,MAAM,yBAAyB,CAAC;AACjC,kFAAkF;AAClF,OAAO,EACL,uBAAuB,EACvB,wBAAwB,EACxB,+BAA+B,EAC/B,kBAAkB,EAClB,KAAK,iBAAiB,GACvB,MAAM,0BAA0B,CAAC;AAElC,gFAAgF;AAChF,+EAA+E;AAC/E,eAAO,MAAM,2BAA2B,2BAA2B,CAAC;AACpE,eAAO,MAAM,8BAA8B,8BAA8B,CAAC"}
@@ -7,6 +7,10 @@
7
7
  export { classifyGitFetchError, isGitAuthFailure, GIT_AUTH_FAILURE_PATTERNS, } from "./classify.js";
8
8
  export { makeGitAuthProbe, maskGitCredentials, } from "./probe.js";
9
9
  export { GitAuthController, DEFAULT_GIT_AUTH_FAILURE_THRESHOLD, } from "./controller.js";
10
+ /** NS-RUN-3: per-container GitHub App installation tokens (replaces a shared GH_TOKEN PAT). */
11
+ export { InstallationTokenProvider, InstallationTokenScopeError, isWithinRequestedScope, makeHttpInstallationTokenFetcher, } from "./installation-token.js";
12
+ /** NS-RUN-3: where a running container's GH_TOKEN comes from (env vs. minted). */
13
+ export { GITHUB_TOKEN_SOURCE_ENV, resolveGithubTokenSource, effectiveInstallationTokenRepos, hydrateGithubToken, } from "./github-token-source.js";
10
14
  /** Runner-emitted audit verbs. The alerts tick (api/_lib/alerts-git-auth.ts) */
11
15
  /** scans these to derive currently-degraded machines and page the operator. */
12
16
  export const RUNNER_GIT_AUTH_FAILED_VERB = "runner.git_auth_failed";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/git-auth/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,qBAAqB,EACrB,gBAAgB,EAChB,yBAAyB,GAE1B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,gBAAgB,EAChB,kBAAkB,GAInB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,iBAAiB,EACjB,kCAAkC,GAKnC,MAAM,iBAAiB,CAAC;AAEzB,gFAAgF;AAChF,+EAA+E;AAC/E,MAAM,CAAC,MAAM,2BAA2B,GAAG,wBAAwB,CAAC;AACpE,MAAM,CAAC,MAAM,8BAA8B,GAAG,2BAA2B,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/git-auth/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,qBAAqB,EACrB,gBAAgB,EAChB,yBAAyB,GAE1B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,gBAAgB,EAChB,kBAAkB,GAInB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,iBAAiB,EACjB,kCAAkC,GAKnC,MAAM,iBAAiB,CAAC;AACzB,+FAA+F;AAC/F,OAAO,EACL,yBAAyB,EACzB,2BAA2B,EAC3B,sBAAsB,EACtB,gCAAgC,GAIjC,MAAM,yBAAyB,CAAC;AACjC,kFAAkF;AAClF,OAAO,EACL,uBAAuB,EACvB,wBAAwB,EACxB,+BAA+B,EAC/B,kBAAkB,GAEnB,MAAM,0BAA0B,CAAC;AAElC,gFAAgF;AAChF,+EAA+E;AAC/E,MAAM,CAAC,MAAM,2BAA2B,GAAG,wBAAwB,CAAC;AACpE,MAAM,CAAC,MAAM,8BAA8B,GAAG,2BAA2B,CAAC"}
@@ -0,0 +1,95 @@
1
+ /** One minted installation token plus the metadata needed to cache/refresh it. */
2
+ export interface InstallationTokenResult {
3
+ token: string;
4
+ /** ISO-8601 expiry timestamp (GitHub installation tokens live ~1h). */
5
+ expiresAt: string;
6
+ /** The repos this token is ACTUALLY scoped to, as returned by the mint. */
7
+ repos: string[];
8
+ }
9
+ /** Injectable seam. Production wires this via `makeHttpInstallationTokenFetcher`; tests pass a stub. */
10
+ export type InstallationTokenFetcher = (repos: string[], runnerId: string) => Promise<InstallationTokenResult>;
11
+ /**
12
+ * Thrown when a mint hands back a token scoped to repos beyond what the
13
+ * runner requested. This is the client-side half of "token scope limited to
14
+ * the repos the runner serves" (AC3): even a server that returns an
15
+ * over-broad grant cannot make this runner USE the excess scope, because the
16
+ * provider refuses the token outright rather than caching/serving it.
17
+ */
18
+ export declare class InstallationTokenScopeError extends Error {
19
+ readonly requested: string[];
20
+ readonly granted: string[];
21
+ constructor(requested: string[], granted: string[]);
22
+ }
23
+ /**
24
+ * True when `granted` is a NON-EMPTY set and every entry is present in
25
+ * `requested` (case-insensitive).
26
+ *
27
+ * AC5: an empty grant is NOT treated as in-scope. A mint that hands back zero
28
+ * repos proves nothing about what it actually scoped the token to — vacuously
29
+ * satisfying a subset check would let a malformed or field-less response
30
+ * through as if it were a valid (if narrow) grant. Fail closed instead: no
31
+ * verifiable scope means the token is rejected, same as an over-broad one.
32
+ */
33
+ export declare function isWithinRequestedScope(requested: string[], granted: string[]): boolean;
34
+ export interface InstallationTokenProviderOptions {
35
+ /**
36
+ * The repo(s) ("owner/name") this container is configured to serve.
37
+ * Required and non-empty — a provider with no configured repos can never
38
+ * mint a token, closing off the "one credential, every repo" shape a
39
+ * shared PAT had.
40
+ */
41
+ repos: string[];
42
+ fetcher: InstallationTokenFetcher;
43
+ runnerId: string;
44
+ /** Refresh this many ms before the cached token's stated expiry. Default 5 min. */
45
+ refreshMarginMs?: number;
46
+ }
47
+ /**
48
+ * Runner-side cache/refresh for a single installation token, scoped to the
49
+ * repo(s) this container serves. In-memory only — like `EnvTokenProvider`
50
+ * (token-provider.ts), an ephemeral container must never write a credential
51
+ * to its writable layer.
52
+ */
53
+ export declare class InstallationTokenProvider {
54
+ private readonly repos;
55
+ private readonly fetcher;
56
+ private readonly runnerId;
57
+ private readonly refreshMarginMs;
58
+ private cached;
59
+ private inflight;
60
+ constructor(opts: InstallationTokenProviderOptions);
61
+ /** Repos this provider is configured to serve (defensive copy). */
62
+ reposServed(): string[];
63
+ /** True when the cached token is still fresh past the refresh margin. */
64
+ private fresh;
65
+ /**
66
+ * Return a valid installation token, minting or refreshing as needed.
67
+ * Concurrent callers share one in-flight mint (mirrors the server-side
68
+ * single-flight in `api/_lib/github-app.ts`) so a burst of git operations
69
+ * at container boot doesn't fire N redundant mints. A rejected mint clears
70
+ * the in-flight slot so the NEXT call retries rather than replaying the
71
+ * same rejection forever.
72
+ */
73
+ getToken(): Promise<string>;
74
+ private mint;
75
+ /** Diagnostics-safe snapshot — NEVER includes the token itself. */
76
+ snapshot(): {
77
+ hasToken: boolean;
78
+ expiresAt: string | null;
79
+ repos: string[];
80
+ };
81
+ }
82
+ /**
83
+ * Build an HTTP-backed `InstallationTokenFetcher` against the (not-yet-shipped)
84
+ * `/api/runner/github-installation-token` endpoint. Mirrors
85
+ * `secrets/inject.ts`'s `makeHttpSecretFetcher`: the runner authenticates
86
+ * with its own bearer JWT, the server does the privileged GitHub App
87
+ * JWT-sign + installation-token exchange (`api/_lib/github-app.ts`), and only
88
+ * the resulting short-lived, repo-scoped token crosses the wire back.
89
+ */
90
+ export declare function makeHttpInstallationTokenFetcher(opts: {
91
+ publicUrl: string;
92
+ jwt: string;
93
+ fetchImpl?: typeof fetch;
94
+ }): InstallationTokenFetcher;
95
+ //# sourceMappingURL=installation-token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installation-token.d.ts","sourceRoot":"","sources":["../../src/git-auth/installation-token.ts"],"names":[],"mappings":"AAqCA,kFAAkF;AAClF,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,SAAS,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,wGAAwG;AACxG,MAAM,MAAM,wBAAwB,GAAG,CACrC,KAAK,EAAE,MAAM,EAAE,EACf,QAAQ,EAAE,MAAM,KACb,OAAO,CAAC,uBAAuB,CAAC,CAAC;AAEtC;;;;;;GAMG;AACH,qBAAa,2BAA4B,SAAQ,KAAK;IACpD,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC;gBAEf,SAAS,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE;CAUnD;AAMD;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAItF;AAKD,MAAM,WAAW,gCAAgC;IAC/C;;;;;OAKG;IACH,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,EAAE,wBAAwB,CAAC;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,mFAAmF;IACnF,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;GAKG;AACH,qBAAa,yBAAyB;IACpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAW;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA2B;IACnD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,MAAM,CAAwC;IACtD,OAAO,CAAC,QAAQ,CAAiD;gBAErD,IAAI,EAAE,gCAAgC;IAclD,mEAAmE;IACnE,WAAW,IAAI,MAAM,EAAE;IAIvB,yEAAyE;IACzE,OAAO,CAAC,KAAK;IAKb;;;;;;;OAOG;IACG,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;YAWnB,IAAI;IASlB,mEAAmE;IACnE,QAAQ,IAAI;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE;CAO7E;AAED;;;;;;;GAOG;AACH,wBAAgB,gCAAgC,CAAC,IAAI,EAAE;IACrD,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B,GAAG,wBAAwB,CA4C3B"}
@@ -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,30 @@
1
+ /**
2
+ * RUNNER-NAMED-INSTANCES-1: named runner instances. Keychain mode stores ONE
3
+ * runner session per OS user, and `acc.register_runner` dedups identity on
4
+ * (bound_user_id, machine) — so a second runner on one Mac silently became a
5
+ * duplicate of the first (incident 2026-08-27). A name fixes that client-side:
6
+ * folded into the keychain account, machine string, cache root and singleton
7
+ * key, the server dedup mints each name its own stable runner id. Unnamed,
8
+ * every resolver below returns its historic value byte-for-byte (frozen).
9
+ */
10
+ /** Env var carrying the active name (set by `--instance <name>`). */
11
+ export declare const INSTANCE_ENV = "ACC_RUNNER_INSTANCE";
12
+ /** Keychain account slot for the unnamed instance. FROZEN. */
13
+ export declare const DEFAULT_INSTANCE_ACCOUNT = "default";
14
+ /** Narrow on purpose: the name lands in a path, a keychain account and a
15
+ * server-side machine string — nothing escapable/quotable is allowed. */
16
+ export declare const INSTANCE_NAME_RE: RegExp;
17
+ export declare function isValidInstanceName(name: string): boolean;
18
+ /** Throws on an invalid name; returns the trimmed name otherwise. */
19
+ export declare function assertInstanceName(name: string): string;
20
+ /** Active name or null. Throws on an invalid env value — a typo must not
21
+ * fall back to the default slot and re-create the duplicate incident. */
22
+ export declare function resolveInstance(env?: NodeJS.ProcessEnv): string | null;
23
+ /** Publish `--instance <name>` into the env so every resolver (including in
24
+ * spawned children) agrees. No-op without the flag. */
25
+ export declare function applyInstanceOption(name: string | undefined): string | null;
26
+ /** Keychain account: "default" unnamed, else "instance:<name>". */
27
+ export declare function instanceKeychainAccount(env?: NodeJS.ProcessEnv): string;
28
+ /** Machine-string suffix: "" unnamed, else "#<name>". */
29
+ export declare function instanceMachineSuffix(env?: NodeJS.ProcessEnv): string;
30
+ //# sourceMappingURL=instance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../src/instance.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,qEAAqE;AACrE,eAAO,MAAM,YAAY,wBAAwB,CAAC;AAElD,8DAA8D;AAC9D,eAAO,MAAM,wBAAwB,YAAY,CAAC;AAElD;yEACyE;AACzE,eAAO,MAAM,gBAAgB,QAA8B,CAAC;AAE5D,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED,qEAAqE;AACrE,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOvD;AAED;yEACyE;AACzE,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,GAAG,IAAI,CAGnF;AAED;uDACuD;AACvD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAG3E;AAED,mEAAmE;AACnE,wBAAgB,uBAAuB,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,CAGvE;AAED,yDAAyD;AACzD,wBAAgB,qBAAqB,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,CAGrE"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * RUNNER-NAMED-INSTANCES-1: named runner instances. Keychain mode stores ONE
3
+ * runner session per OS user, and `acc.register_runner` dedups identity on
4
+ * (bound_user_id, machine) — so a second runner on one Mac silently became a
5
+ * duplicate of the first (incident 2026-08-27). A name fixes that client-side:
6
+ * folded into the keychain account, machine string, cache root and singleton
7
+ * key, the server dedup mints each name its own stable runner id. Unnamed,
8
+ * every resolver below returns its historic value byte-for-byte (frozen).
9
+ */
10
+ /** Env var carrying the active name (set by `--instance <name>`). */
11
+ export const INSTANCE_ENV = "ACC_RUNNER_INSTANCE";
12
+ /** Keychain account slot for the unnamed instance. FROZEN. */
13
+ export const DEFAULT_INSTANCE_ACCOUNT = "default";
14
+ /** Narrow on purpose: the name lands in a path, a keychain account and a
15
+ * server-side machine string — nothing escapable/quotable is allowed. */
16
+ export const INSTANCE_NAME_RE = /^[a-z0-9][a-z0-9-]{0,31}$/;
17
+ export function isValidInstanceName(name) {
18
+ return INSTANCE_NAME_RE.test(name);
19
+ }
20
+ /** Throws on an invalid name; returns the trimmed name otherwise. */
21
+ export function assertInstanceName(name) {
22
+ const n = name.trim();
23
+ if (isValidInstanceName(n))
24
+ return n;
25
+ throw new Error(`Invalid --instance name "${name}": must match ${INSTANCE_NAME_RE.source} ` +
26
+ "(lowercase letters, digits and dashes, starting alphanumeric, max 32 chars).");
27
+ }
28
+ /** Active name or null. Throws on an invalid env value — a typo must not
29
+ * fall back to the default slot and re-create the duplicate incident. */
30
+ export function resolveInstance(env = process.env) {
31
+ const raw = env[INSTANCE_ENV]?.trim();
32
+ return raw ? assertInstanceName(raw) : null;
33
+ }
34
+ /** Publish `--instance <name>` into the env so every resolver (including in
35
+ * spawned children) agrees. No-op without the flag. */
36
+ export function applyInstanceOption(name) {
37
+ if (name !== undefined)
38
+ process.env[INSTANCE_ENV] = assertInstanceName(name);
39
+ return resolveInstance();
40
+ }
41
+ /** Keychain account: "default" unnamed, else "instance:<name>". */
42
+ export function instanceKeychainAccount(env) {
43
+ const name = resolveInstance(env);
44
+ return name ? `instance:${name}` : DEFAULT_INSTANCE_ACCOUNT;
45
+ }
46
+ /** Machine-string suffix: "" unnamed, else "#<name>". */
47
+ export function instanceMachineSuffix(env) {
48
+ const name = resolveInstance(env);
49
+ return name ? `#${name}` : "";
50
+ }
51
+ //# sourceMappingURL=instance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instance.js","sourceRoot":"","sources":["../src/instance.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,qEAAqE;AACrE,MAAM,CAAC,MAAM,YAAY,GAAG,qBAAqB,CAAC;AAElD,8DAA8D;AAC9D,MAAM,CAAC,MAAM,wBAAwB,GAAG,SAAS,CAAC;AAElD;yEACyE;AACzE,MAAM,CAAC,MAAM,gBAAgB,GAAG,2BAA2B,CAAC;AAE5D,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,OAAO,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACrC,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IACtB,IAAI,mBAAmB,CAAC,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC;IACrC,MAAM,IAAI,KAAK,CACb,4BAA4B,IAAI,iBAAiB,gBAAgB,CAAC,MAAM,GAAG;QACzE,8EAA8E,CACjF,CAAC;AACJ,CAAC;AAED;yEACyE;AACzE,MAAM,UAAU,eAAe,CAAC,MAAyB,OAAO,CAAC,GAAG;IAClE,MAAM,GAAG,GAAG,GAAG,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,CAAC;IACtC,OAAO,GAAG,CAAC,CAAC,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC9C,CAAC;AAED;uDACuD;AACvD,MAAM,UAAU,mBAAmB,CAAC,IAAwB;IAC1D,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;IAC7E,OAAO,eAAe,EAAE,CAAC;AAC3B,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,uBAAuB,CAAC,GAAuB;IAC7D,MAAM,IAAI,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;IAClC,OAAO,IAAI,CAAC,CAAC,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,CAAC,wBAAwB,CAAC;AAC9D,CAAC;AAED,yDAAyD;AACzD,MAAM,UAAU,qBAAqB,CAAC,GAAuB;IAC3D,MAAM,IAAI,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;IAClC,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AAChC,CAAC"}
@@ -1,12 +1,8 @@
1
- /**
2
- * OS-keychain wrapper around `keytar`. The CLI never persists a JWT to
3
- * disk in plaintext — keychain is the only writable surface.
4
- *
5
- * keytar is a native module; we lazy-import so commands that don't need
6
- * credentials (`--version`, `--help`) still work on systems where the
7
- * binding has not built (`linux` headless CI without libsecret, etc).
8
- */
9
1
  import { type StoredSession } from "./types.js";
2
+ /** RUNNER-NAMED-INSTANCES-1: the session slot — frozen "default" unless a
3
+ * named instance is active. Resolved per call (not at module load) so login /
4
+ * watch / logout / doctor can never disagree about it. */
5
+ export declare function sessionAccount(): string;
10
6
  type KeytarLike = {
11
7
  setPassword(service: string, account: string, password: string): Promise<void>;
12
8
  getPassword(service: string, account: string): Promise<string | null>;
@@ -1 +1 @@
1
- {"version":3,"file":"keychain.d.ts","sourceRoot":"","sources":["../src/keychain.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAA2B,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAIzE,KAAK,UAAU,GAAG;IAChB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/E,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACtE,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACpE,CAAC;AAYF,wBAAsB,WAAW,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAGvE;AAED,wBAAsB,WAAW,IAAI,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAUjE;AAED,wBAAsB,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC,CAGrD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAG7E;AAED,wBAAsB,QAAQ,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAWpE;AAED,wBAAsB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAGjE;AAED,4EAA4E;AAC5E,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,GAAG,IAAI,CAEzD"}
1
+ {"version":3,"file":"keychain.d.ts","sourceRoot":"","sources":["../src/keychain.ts"],"names":[],"mappings":"AASA,OAAO,EAA2B,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAEzE;;0DAE0D;AAC1D,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED,KAAK,UAAU,GAAG;IAChB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/E,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACtE,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACpE,CAAC;AAYF,wBAAsB,WAAW,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAGvE;AAED,wBAAsB,WAAW,IAAI,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAUjE;AAED,wBAAsB,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC,CAGrD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAG7E;AAED,wBAAsB,QAAQ,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAWpE;AAED,wBAAsB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAGjE;AAED,4EAA4E;AAC5E,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,GAAG,IAAI,CAEzD"}
package/dist/keychain.js CHANGED
@@ -6,8 +6,14 @@
6
6
  * credentials (`--version`, `--help`) still work on systems where the
7
7
  * binding has not built (`linux` headless CI without libsecret, etc).
8
8
  */
9
+ import { instanceKeychainAccount } from "./instance.js";
9
10
  import { RUNNER_KEYCHAIN_SERVICE } from "./types.js";
10
- const ACCOUNT = "default";
11
+ /** RUNNER-NAMED-INSTANCES-1: the session slot — frozen "default" unless a
12
+ * named instance is active. Resolved per call (not at module load) so login /
13
+ * watch / logout / doctor can never disagree about it. */
14
+ export function sessionAccount() {
15
+ return instanceKeychainAccount();
16
+ }
11
17
  let cached = null;
12
18
  async function getKeytar() {
13
19
  if (cached)
@@ -19,11 +25,11 @@ async function getKeytar() {
19
25
  }
20
26
  export async function saveSession(session) {
21
27
  const k = await getKeytar();
22
- await k.setPassword(RUNNER_KEYCHAIN_SERVICE, ACCOUNT, JSON.stringify(session));
28
+ await k.setPassword(RUNNER_KEYCHAIN_SERVICE, sessionAccount(), JSON.stringify(session));
23
29
  }
24
30
  export async function loadSession() {
25
31
  const k = await getKeytar();
26
- const raw = await k.getPassword(RUNNER_KEYCHAIN_SERVICE, ACCOUNT);
32
+ const raw = await k.getPassword(RUNNER_KEYCHAIN_SERVICE, sessionAccount());
27
33
  if (!raw)
28
34
  return null;
29
35
  try {
@@ -36,7 +42,7 @@ export async function loadSession() {
36
42
  }
37
43
  export async function clearSession() {
38
44
  const k = await getKeytar();
39
- return k.deletePassword(RUNNER_KEYCHAIN_SERVICE, ACCOUNT);
45
+ return k.deletePassword(RUNNER_KEYCHAIN_SERVICE, sessionAccount());
40
46
  }
41
47
  /**
42
48
  * AX-G1: a SECOND entry in the same keychain service, under a different account.
@@ -1 +1 @@
1
- {"version":3,"file":"keychain.js","sourceRoot":"","sources":["../src/keychain.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,uBAAuB,EAAsB,MAAM,YAAY,CAAC;AAEzE,MAAM,OAAO,GAAG,SAAkB,CAAC;AAQnC,IAAI,MAAM,GAAsB,IAAI,CAAC;AAErC,KAAK,UAAU,SAAS;IACtB,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAC1B,qEAAqE;IACrE,MAAM,GAAG,GAAG,CAAC,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAqD,CAAC;IACzF,MAAM,GAAG,CAAC,GAAG,CAAC,OAAO,IAAI,GAAG,CAAe,CAAC;IAC5C,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAsB;IACtD,MAAM,CAAC,GAAG,MAAM,SAAS,EAAE,CAAC;IAC5B,MAAM,CAAC,CAAC,WAAW,CAAC,uBAAuB,EAAE,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;AACjF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,MAAM,CAAC,GAAG,MAAM,SAAS,EAAE,CAAC;IAC5B,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,WAAW,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAC;IAClE,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAkB,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,mEAAmE;QACnE,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,MAAM,CAAC,GAAG,MAAM,SAAS,EAAE,CAAC;IAC5B,OAAO,CAAC,CAAC,cAAc,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAC;AAC5D,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,OAAe,EAAE,KAAc;IAC5D,MAAM,CAAC,GAAG,MAAM,SAAS,EAAE,CAAC;IAC5B,MAAM,CAAC,CAAC,WAAW,CAAC,uBAAuB,EAAE,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;AAC/E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAI,OAAe;IAC/C,MAAM,CAAC,GAAG,MAAM,SAAS,EAAE,CAAC;IAC5B,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,WAAW,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAC;IAClE,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAM,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,0EAA0E;QAC1E,qBAAqB;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,OAAe;IAC7C,MAAM,CAAC,GAAG,MAAM,SAAS,EAAE,CAAC;IAC5B,OAAO,CAAC,CAAC,cAAc,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAC;AAC5D,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,WAAW,CAAC,IAAuB;IACjD,MAAM,GAAG,IAAI,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"keychain.js","sourceRoot":"","sources":["../src/keychain.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,uBAAuB,EAAsB,MAAM,YAAY,CAAC;AAEzE;;0DAE0D;AAC1D,MAAM,UAAU,cAAc;IAC5B,OAAO,uBAAuB,EAAE,CAAC;AACnC,CAAC;AAQD,IAAI,MAAM,GAAsB,IAAI,CAAC;AAErC,KAAK,UAAU,SAAS;IACtB,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAC1B,qEAAqE;IACrE,MAAM,GAAG,GAAG,CAAC,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAqD,CAAC;IACzF,MAAM,GAAG,CAAC,GAAG,CAAC,OAAO,IAAI,GAAG,CAAe,CAAC;IAC5C,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAsB;IACtD,MAAM,CAAC,GAAG,MAAM,SAAS,EAAE,CAAC;IAC5B,MAAM,CAAC,CAAC,WAAW,CAAC,uBAAuB,EAAE,cAAc,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;AAC1F,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,MAAM,CAAC,GAAG,MAAM,SAAS,EAAE,CAAC;IAC5B,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,WAAW,CAAC,uBAAuB,EAAE,cAAc,EAAE,CAAC,CAAC;IAC3E,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAkB,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,mEAAmE;QACnE,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,MAAM,CAAC,GAAG,MAAM,SAAS,EAAE,CAAC;IAC5B,OAAO,CAAC,CAAC,cAAc,CAAC,uBAAuB,EAAE,cAAc,EAAE,CAAC,CAAC;AACrE,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,OAAe,EAAE,KAAc;IAC5D,MAAM,CAAC,GAAG,MAAM,SAAS,EAAE,CAAC;IAC5B,MAAM,CAAC,CAAC,WAAW,CAAC,uBAAuB,EAAE,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;AAC/E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAI,OAAe;IAC/C,MAAM,CAAC,GAAG,MAAM,SAAS,EAAE,CAAC;IAC5B,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,WAAW,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAC;IAClE,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAM,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,0EAA0E;QAC1E,qBAAqB;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,OAAe;IAC7C,MAAM,CAAC,GAAG,MAAM,SAAS,EAAE,CAAC;IAC5B,OAAO,CAAC,CAAC,cAAc,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAC;AAC5D,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,WAAW,CAAC,IAAuB;IACjD,MAAM,GAAG,IAAI,CAAC;AAChB,CAAC"}
@@ -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"}