@h-rig/github-provider-plugin 0.0.6-alpha.157 → 0.0.6-alpha.159

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,8 @@
1
+ /**
2
+ * profile-ops.ts — re-export shim. The execution/review profile read/write IO
3
+ * was relocated to the floor (`@rig/core/profile-ops`) so the CLI seed,
4
+ * cli-surface, and bundle lifecycle use it as DATA without a cross-plugin impl
5
+ * import. This shim preserves the `@rig/github-provider-plugin/profile-ops`
6
+ * subpath; implementation now lives in @rig/core.
7
+ */
8
+ export { setProfile, setReviewProfile, showProfile, showReviewProfile } from "@rig/core/profile-ops";
@@ -0,0 +1,9 @@
1
+ // @bun
2
+ // packages/github-provider-plugin/src/profile-ops.ts
3
+ import { setProfile, setReviewProfile, showProfile, showReviewProfile } from "@rig/core/profile-ops";
4
+ export {
5
+ showReviewProfile,
6
+ showProfile,
7
+ setReviewProfile,
8
+ setProfile
9
+ };
@@ -1,40 +1,6 @@
1
1
  // @bun
2
- // packages/github-provider-plugin/src/credentials.ts
3
- function selectedRepoTokenKey(input) {
4
- return `user:${input.userId}|repo:${input.owner}/${input.repo}|workspace:${input.workspaceId}`;
5
- }
6
- function cleanToken(value) {
7
- const trimmed = value?.trim() ?? "";
8
- return trimmed.length > 0 ? trimmed : null;
9
- }
10
- function createGitHubCredentialProvider(options = {}) {
11
- const sessionTokens = options.sessionTokens ?? {};
12
- const hostToken = cleanToken(options.hostToken ?? process.env.GH_TOKEN ?? null);
13
- return {
14
- async resolveGitHubToken(input) {
15
- const owner = input.owner.trim();
16
- const repo = input.repo.trim();
17
- const workspaceId = input.workspaceId.trim();
18
- const userId = input.userId?.trim() ?? "";
19
- if (input.purpose === "selected-repo") {
20
- if (!owner || !repo || !workspaceId || !userId) {
21
- throw new Error("No signed-in GitHub token is available for the selected repo; sign in to GitHub for this workspace.");
22
- }
23
- const token = cleanToken(sessionTokens[selectedRepoTokenKey({ owner, repo, workspaceId, userId })]);
24
- if (!token) {
25
- throw new Error("No signed-in GitHub token is available for the selected repo; sign in to GitHub for this workspace.");
26
- }
27
- return { token, source: "signed-in-user" };
28
- }
29
- if (hostToken) {
30
- return { token: hostToken, source: "host-admin-fallback" };
31
- }
32
- throw new Error("No host GitHub token is configured for the explicit admin fallback operation.");
33
- }
34
- };
35
- }
36
-
37
2
  // packages/github-provider-plugin/src/service.ts
3
+ import { createGitHubCredentialProvider } from "@rig/github-lib";
38
4
  var githubProviderService = {
39
5
  createCredentialProvider: (options) => createGitHubCredentialProvider(options)
40
6
  };
@@ -0,0 +1,3 @@
1
+ export declare function cleanToken(value: string | null | undefined): string | null;
2
+ export declare function authStateToken(env?: Record<string, string | undefined>): string | null;
3
+ export declare function resolveGitHubAuthToken(env?: Record<string, string | undefined>): string | null;
@@ -0,0 +1,26 @@
1
+ // @bun
2
+ // packages/github-provider-plugin/src/token-env.ts
3
+ import { existsSync, readFileSync } from "fs";
4
+ function cleanToken(value) {
5
+ const trimmed = value?.trim() ?? "";
6
+ return trimmed.length > 0 ? trimmed : null;
7
+ }
8
+ function authStateToken(env = process.env) {
9
+ const file = env.RIG_GITHUB_AUTH_STATE_FILE?.trim();
10
+ if (!file || !existsSync(file))
11
+ return null;
12
+ try {
13
+ const parsed = JSON.parse(readFileSync(file, "utf8"));
14
+ return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
15
+ } catch {
16
+ return null;
17
+ }
18
+ }
19
+ function resolveGitHubAuthToken(env = process.env) {
20
+ return cleanToken(env.RIG_GITHUB_TOKEN) ?? cleanToken(env.GH_TOKEN) ?? cleanToken(env.GITHUB_TOKEN) ?? authStateToken(env);
21
+ }
22
+ export {
23
+ resolveGitHubAuthToken,
24
+ cleanToken,
25
+ authStateToken
26
+ };
@@ -1,6 +1,6 @@
1
1
  // @bun
2
2
  // packages/github-provider-plugin/src/triage-run.ts
3
- import { buildPluginHostContext } from "@rig/runtime/control-plane/plugin-host-context";
3
+ import { buildPluginHostContext } from "@rig/core/plugin-host-context";
4
4
 
5
5
  // packages/github-provider-plugin/src/issue-analysis.ts
6
6
  import { createHash } from "crypto";
@@ -223,7 +223,7 @@ function createIssueAnalysisWriteBack(input) {
223
223
  throw new Error("Issue analysis writeback requires removeLabels for labelsToRemove.");
224
224
  await input.target.removeLabels(issue.id, uniqueLabels(result.labelsToRemove));
225
225
  }
226
- const comment = (input.buildStatusComment ?? defaultStatusComment)({ issue, result, reason });
226
+ const comment = (input.buildStatusComment ?? defaultStatusComment)({ issue, result, ...reason !== undefined ? { reason } : {} });
227
227
  if (comment?.trim()) {
228
228
  if (!input.target.updateTask)
229
229
  throw new Error("Issue analysis writeback requires updateTask for sticky status comments.");
@@ -248,7 +248,7 @@ function sourceWithWriteBackCapabilities(source) {
248
248
  if (typeof candidate.updateTask !== "function")
249
249
  return null;
250
250
  return {
251
- get: candidate.get?.bind(candidate),
251
+ ...typeof candidate.get === "function" ? { get: candidate.get.bind(candidate) } : {},
252
252
  updateTask: candidate.updateTask.bind(candidate),
253
253
  ...typeof candidate.addLabels === "function" ? { addLabels: candidate.addLabels.bind(candidate) } : {},
254
254
  ...typeof candidate.removeLabels === "function" ? { removeLabels: candidate.removeLabels.bind(candidate) } : {},
@@ -277,8 +277,8 @@ function createConfiguredIssueAnalysisRunner(input) {
277
277
  if (!target)
278
278
  return null;
279
279
  const analyzer = input.analyzer ?? createPiIssueAnalyzer({
280
- runCommand: input.runCommand,
281
- model: input.context.config.issueAnalysis?.model
280
+ ...input.runCommand ? { runCommand: input.runCommand } : {},
281
+ ...input.context.config.issueAnalysis?.model ? { model: input.context.config.issueAnalysis.model } : {}
282
282
  });
283
283
  const baseWriteBack = createIssueAnalysisWriteBack({ target });
284
284
  const service = createIssueAnalysisService({
@@ -291,7 +291,7 @@ function createConfiguredIssueAnalysisRunner(input) {
291
291
  return createContinuousIssueAnalysisRunner({
292
292
  loadIssues: async () => [...await source.list()],
293
293
  service,
294
- intervalMs: input.intervalMs,
294
+ ...input.intervalMs !== undefined ? { intervalMs: input.intervalMs } : {},
295
295
  reason: "continuous-issue-analysis",
296
296
  ...input.setIntervalFn ? { setIntervalFn: input.setIntervalFn } : {},
297
297
  ...input.clearIntervalFn ? { clearIntervalFn: input.clearIntervalFn } : {},
@@ -312,7 +312,7 @@ function createIssueAnalysisService(input) {
312
312
  const result = await input.analyzer({ issue, neighbors, prompt });
313
313
  analyzedHashes.set(issue.id, hash);
314
314
  if (result.metadataPatch || result.labelsToAdd?.length || result.labelsToRemove?.length || result.generatedIssues?.length) {
315
- await input.writeBack?.({ issue, result, reason: options.reason });
315
+ await input.writeBack?.({ issue, result, ...options.reason !== undefined ? { reason: options.reason } : {} });
316
316
  }
317
317
  results.push({ issue, result });
318
318
  }
@@ -390,7 +390,9 @@ async function loadContext(projectRoot) {
390
390
  if (!context)
391
391
  return null;
392
392
  return {
393
- config: context.config,
393
+ config: {
394
+ ...context.config.issueAnalysis ? { issueAnalysis: context.config.issueAnalysis } : {}
395
+ },
394
396
  taskSourceRegistry: context.taskSourceRegistry
395
397
  };
396
398
  }
@@ -448,8 +450,8 @@ async function runIssueAnalysisTriage(options) {
448
450
  const runner = createConfiguredIssueAnalysisRunner({
449
451
  projectRoot: options.projectRoot,
450
452
  context,
451
- analyzer: options.analyzer,
452
- runCommand: options.runCommand,
453
+ ...options.analyzer ? { analyzer: options.analyzer } : {},
454
+ ...options.runCommand ? { runCommand: options.runCommand } : {},
453
455
  onWriteBack: refreshSnapshotAfterWriteBack
454
456
  });
455
457
  if (!runner) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@h-rig/github-provider-plugin",
3
- "version": "0.0.6-alpha.157",
3
+ "version": "0.0.6-alpha.159",
4
4
  "type": "module",
5
5
  "description": "Swappable GitHub SCM-provider capability plugin for Rig (auth, credentials, Projects, issue analysis).",
6
6
  "license": "UNLICENSED",
@@ -17,9 +17,29 @@
17
17
  "types": "./dist/src/plugin.d.ts",
18
18
  "import": "./dist/src/plugin.js"
19
19
  },
20
+ "./lib": {
21
+ "types": "./dist/src/lib.d.ts",
22
+ "import": "./dist/src/lib.js"
23
+ },
20
24
  "./index": {
21
25
  "types": "./dist/src/index.d.ts",
22
26
  "import": "./dist/src/index.js"
27
+ },
28
+ "./identity": {
29
+ "types": "./dist/src/identity.d.ts",
30
+ "import": "./dist/src/identity.js"
31
+ },
32
+ "./identity-env": {
33
+ "types": "./dist/src/identity-env.d.ts",
34
+ "import": "./dist/src/identity-env.js"
35
+ },
36
+ "./token-env": {
37
+ "types": "./dist/src/token-env.d.ts",
38
+ "import": "./dist/src/token-env.js"
39
+ },
40
+ "./profile-ops": {
41
+ "types": "./dist/src/profile-ops.d.ts",
42
+ "import": "./dist/src/profile-ops.js"
23
43
  }
24
44
  },
25
45
  "engines": {
@@ -29,8 +49,8 @@
29
49
  "module": "./dist/src/index.js",
30
50
  "types": "./dist/src/index.d.ts",
31
51
  "dependencies": {
32
- "@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.157",
33
- "@rig/core": "npm:@h-rig/core@0.0.6-alpha.157",
34
- "@rig/runtime": "npm:@h-rig/runtime@0.0.6-alpha.157"
52
+ "@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.159",
53
+ "@rig/core": "npm:@h-rig/core@0.0.6-alpha.159",
54
+ "@rig/github-lib": "npm:@h-rig/github-lib@0.0.6-alpha.159"
35
55
  }
36
56
  }
@@ -1,42 +0,0 @@
1
- import type { GitHubAuthStatus } from "@rig/contracts";
2
- export type { GitHubAuthStatus };
3
- type PendingDeviceFlow = {
4
- readonly pollId: string;
5
- readonly deviceCode: string;
6
- readonly expiresAt: string;
7
- readonly intervalSeconds: number;
8
- };
9
- export type GitHubAuthStore = {
10
- readonly stateFile: string;
11
- readonly status: (options?: {
12
- oauthConfigured?: boolean;
13
- }) => GitHubAuthStatus;
14
- readonly readToken: () => string | null;
15
- readonly saveToken: (input: {
16
- readonly token: string;
17
- readonly tokenSource: "oauth-device" | "manual-token";
18
- readonly login?: string | null;
19
- readonly userId?: string | null;
20
- readonly scopes?: readonly string[];
21
- readonly selectedRepo?: string | null;
22
- }) => void;
23
- readonly createApiSession: () => {
24
- token: string;
25
- login: string | null;
26
- userId: string | null;
27
- };
28
- readonly readApiSession: (token: string) => {
29
- login: string | null;
30
- userId: string | null;
31
- } | null;
32
- readonly copyToProjectRoot: (projectRoot: string) => void;
33
- readonly copyToLocalProjectRoot: (projectRoot: string) => void;
34
- readonly savePendingDevice: (input: PendingDeviceFlow) => void;
35
- readonly saveSelectedRepo: (selectedRepo: string | null) => void;
36
- readonly readPendingDevice: (pollId: string) => PendingDeviceFlow | null;
37
- readonly clearPendingDevice: (pollId?: string) => void;
38
- };
39
- export declare function resolveGitHubAuthStateFile(projectRoot: string): string;
40
- export declare function copyGitHubAuthStateToLocalProjectRoot(stateFile: string, projectRoot: string): void;
41
- export declare function createGitHubAuthStoreFromStateFile(stateFile: string): GitHubAuthStore;
42
- export declare function createGitHubAuthStore(projectRoot: string): GitHubAuthStore;
@@ -1,226 +0,0 @@
1
- // @bun
2
- // packages/github-provider-plugin/src/auth-store.ts
3
- import { randomBytes } from "crypto";
4
- import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
5
- import { dirname, resolve } from "path";
6
- import { resolveRigStatePaths } from "@rig/runtime/control-plane/server-paths";
7
- function cleanString(value) {
8
- return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
9
- }
10
- function cleanScopes(value) {
11
- if (!Array.isArray(value))
12
- return [];
13
- return value.flatMap((entry) => {
14
- const clean = cleanString(entry);
15
- return clean ? [clean] : [];
16
- });
17
- }
18
- function parseApiSessions(value) {
19
- if (!Array.isArray(value))
20
- return [];
21
- return value.flatMap((entry) => {
22
- if (!entry || typeof entry !== "object" || Array.isArray(entry))
23
- return [];
24
- const record = entry;
25
- const token = cleanString(record.token);
26
- if (!token)
27
- return [];
28
- return [{
29
- token,
30
- login: cleanString(record.login),
31
- userId: cleanString(record.userId),
32
- createdAt: cleanString(record.createdAt) ?? undefined
33
- }];
34
- });
35
- }
36
- function parsePendingDevice(value) {
37
- if (!value || typeof value !== "object")
38
- return null;
39
- const record = value;
40
- const pollId = cleanString(record.pollId);
41
- const deviceCode = cleanString(record.deviceCode);
42
- const expiresAt = cleanString(record.expiresAt);
43
- const intervalSeconds = typeof record.intervalSeconds === "number" && Number.isFinite(record.intervalSeconds) ? Math.max(1, Math.floor(record.intervalSeconds)) : null;
44
- if (!pollId || !deviceCode || !expiresAt || !intervalSeconds)
45
- return null;
46
- return { pollId, deviceCode, expiresAt, intervalSeconds };
47
- }
48
- function parsePendingDevices(value) {
49
- if (!Array.isArray(value))
50
- return [];
51
- return value.flatMap((entry) => {
52
- const pending = parsePendingDevice(entry);
53
- return pending ? [pending] : [];
54
- });
55
- }
56
- function readStoredAuth(stateFile) {
57
- if (!existsSync(stateFile))
58
- return {};
59
- try {
60
- const parsed = JSON.parse(readFileSync(stateFile, "utf8"));
61
- return {
62
- ...cleanString(parsed.token) ? { token: cleanString(parsed.token) } : {},
63
- login: cleanString(parsed.login),
64
- userId: cleanString(parsed.userId),
65
- scopes: cleanScopes(parsed.scopes),
66
- selectedRepo: cleanString(parsed.selectedRepo),
67
- tokenSource: parsed.tokenSource === "oauth-device" || parsed.tokenSource === "manual-token" || parsed.tokenSource === "env" ? parsed.tokenSource : undefined,
68
- pendingDevice: parsePendingDevice(parsed.pendingDevice),
69
- pendingDevices: parsePendingDevices(parsed.pendingDevices),
70
- apiSessions: parseApiSessions(parsed.apiSessions),
71
- updatedAt: cleanString(parsed.updatedAt) ?? undefined
72
- };
73
- } catch {
74
- return {};
75
- }
76
- }
77
- function newApiSessionToken() {
78
- return `rig_${randomBytes(32).toString("base64url")}`;
79
- }
80
- function writeStoredAuth(stateFile, payload) {
81
- mkdirSync(dirname(stateFile), { recursive: true });
82
- writeFileSync(stateFile, `${JSON.stringify(payload, null, 2)}
83
- `, { encoding: "utf8", mode: 384 });
84
- try {
85
- chmodSync(stateFile, 384);
86
- } catch {}
87
- }
88
- function localProjectAuthStateFile(projectRoot) {
89
- return resolve(projectRoot, ".rig", "state", "github-auth.json");
90
- }
91
- function resolveGitHubAuthStateFile(projectRoot) {
92
- return resolve(resolveRigStatePaths(projectRoot).stateDir, "github-auth.json");
93
- }
94
- function copyGitHubAuthStateToLocalProjectRoot(stateFile, projectRoot) {
95
- const targetFile = localProjectAuthStateFile(projectRoot);
96
- mkdirSync(dirname(targetFile), { recursive: true });
97
- if (existsSync(stateFile)) {
98
- copyFileSync(stateFile, targetFile);
99
- try {
100
- chmodSync(targetFile, 384);
101
- } catch {}
102
- return;
103
- }
104
- writeStoredAuth(targetFile, {});
105
- }
106
- function createGitHubAuthStoreFromStateFile(stateFile) {
107
- return {
108
- stateFile,
109
- status(options) {
110
- const stored = readStoredAuth(stateFile);
111
- const token = cleanString(stored.token);
112
- return {
113
- signedIn: Boolean(token),
114
- login: cleanString(stored.login),
115
- userId: cleanString(stored.userId),
116
- scopes: cleanScopes(stored.scopes),
117
- selectedRepo: cleanString(stored.selectedRepo),
118
- oauthConfigured: options?.oauthConfigured === true,
119
- tokenSource: token ? stored.tokenSource ?? "manual-token" : null
120
- };
121
- },
122
- readToken() {
123
- return cleanString(readStoredAuth(stateFile).token);
124
- },
125
- saveToken(input) {
126
- const previous = readStoredAuth(stateFile);
127
- writeStoredAuth(stateFile, {
128
- ...previous,
129
- token: input.token,
130
- tokenSource: input.tokenSource,
131
- login: input.login ?? null,
132
- userId: input.userId ?? null,
133
- scopes: input.scopes ?? [],
134
- selectedRepo: input.selectedRepo ?? previous.selectedRepo ?? null,
135
- pendingDevice: null,
136
- pendingDevices: [],
137
- apiSessions: previous.apiSessions ?? [],
138
- updatedAt: new Date().toISOString()
139
- });
140
- },
141
- createApiSession() {
142
- const previous = readStoredAuth(stateFile);
143
- const token = newApiSessionToken();
144
- const session = {
145
- token,
146
- login: cleanString(previous.login),
147
- userId: cleanString(previous.userId),
148
- createdAt: new Date().toISOString()
149
- };
150
- writeStoredAuth(stateFile, {
151
- ...previous,
152
- apiSessions: [...(previous.apiSessions ?? []).slice(-9), session],
153
- updatedAt: new Date().toISOString()
154
- });
155
- return { token, login: session.login ?? null, userId: session.userId ?? null };
156
- },
157
- readApiSession(token) {
158
- const clean = cleanString(token);
159
- if (!clean)
160
- return null;
161
- const previous = readStoredAuth(stateFile);
162
- const session = (previous.apiSessions ?? []).find((candidate) => candidate.token === clean);
163
- return session ? { login: cleanString(session.login), userId: cleanString(session.userId) } : null;
164
- },
165
- copyToProjectRoot(projectRoot) {
166
- const targetFile = resolveGitHubAuthStateFile(projectRoot);
167
- writeStoredAuth(targetFile, readStoredAuth(stateFile));
168
- },
169
- copyToLocalProjectRoot(projectRoot) {
170
- copyGitHubAuthStateToLocalProjectRoot(stateFile, projectRoot);
171
- },
172
- savePendingDevice(input) {
173
- const previous = readStoredAuth(stateFile);
174
- const pendingDevices = [
175
- ...previous.pendingDevice ? [previous.pendingDevice] : [],
176
- ...previous.pendingDevices ?? [],
177
- input
178
- ].filter((entry, index, entries) => entries.findIndex((candidate) => candidate.pollId === entry.pollId) === index);
179
- writeStoredAuth(stateFile, {
180
- ...previous,
181
- pendingDevice: null,
182
- pendingDevices,
183
- updatedAt: new Date().toISOString()
184
- });
185
- },
186
- saveSelectedRepo(selectedRepo) {
187
- const previous = readStoredAuth(stateFile);
188
- writeStoredAuth(stateFile, {
189
- ...previous,
190
- selectedRepo: selectedRepo ?? null,
191
- updatedAt: new Date().toISOString()
192
- });
193
- },
194
- readPendingDevice(pollId) {
195
- const previous = readStoredAuth(stateFile);
196
- const pending = [
197
- ...previous.pendingDevice ? [previous.pendingDevice] : [],
198
- ...previous.pendingDevices ?? []
199
- ].find((entry) => entry.pollId === pollId) ?? null;
200
- if (!pending)
201
- return null;
202
- if (Date.parse(pending.expiresAt) <= Date.now())
203
- return null;
204
- return pending;
205
- },
206
- clearPendingDevice(pollId) {
207
- const previous = readStoredAuth(stateFile);
208
- const remaining = pollId ? (previous.pendingDevices ?? []).filter((entry) => entry.pollId !== pollId) : [];
209
- writeStoredAuth(stateFile, {
210
- ...previous,
211
- pendingDevice: null,
212
- pendingDevices: remaining,
213
- updatedAt: new Date().toISOString()
214
- });
215
- }
216
- };
217
- }
218
- function createGitHubAuthStore(projectRoot) {
219
- return createGitHubAuthStoreFromStateFile(resolveGitHubAuthStateFile(projectRoot));
220
- }
221
- export {
222
- resolveGitHubAuthStateFile,
223
- createGitHubAuthStoreFromStateFile,
224
- createGitHubAuthStore,
225
- copyGitHubAuthStateToLocalProjectRoot
226
- };
@@ -1,20 +0,0 @@
1
- import type { GitHubCredentialProvider, GitHubCredentialProviderOptions, GitHubStateCredentialProviderOptions } from "@rig/contracts";
2
- export type { GitHubCredentialProvider, GitHubCredentialProviderOptions, GitHubCredentialPurpose, GitHubCredentialSource, GitHubStateCredentialProviderOptions, ResolveGitHubTokenInput, ResolvedGitHubToken, } from "@rig/contracts";
3
- /**
4
- * Session-token-map credential provider. Selected-repo operations resolve from
5
- * the per-session token map; admin-fallback resolves from the host token.
6
- */
7
- export declare function createGitHubCredentialProvider(options?: GitHubCredentialProviderOptions): GitHubCredentialProvider;
8
- /**
9
- * Env-only credential provider. Selected-repo reads `RIG_GITHUB_SELECTED_TOKEN`
10
- * (browser-selected public repos read without a token); admin-fallback reads the
11
- * host token env vars. Consolidated here from task-sources so the credential
12
- * abstraction has a single home.
13
- */
14
- export declare function createEnvGitHubCredentialProvider(): GitHubCredentialProvider;
15
- /**
16
- * State-file-backed credential provider. Resolves the signed-in token from a
17
- * `github-auth.json` discovered across explicit/state/project-root candidates,
18
- * falling back to host token env vars. Consolidated here from task-sources.
19
- */
20
- export declare function createStateGitHubCredentialProvider(options?: GitHubStateCredentialProviderOptions): GitHubCredentialProvider;
@@ -1,118 +0,0 @@
1
- // @bun
2
- // packages/github-provider-plugin/src/credentials.ts
3
- import { existsSync, readFileSync } from "fs";
4
- import { resolve } from "path";
5
- function selectedRepoTokenKey(input) {
6
- return `user:${input.userId}|repo:${input.owner}/${input.repo}|workspace:${input.workspaceId}`;
7
- }
8
- function cleanToken(value) {
9
- const trimmed = value?.trim() ?? "";
10
- return trimmed.length > 0 ? trimmed : null;
11
- }
12
- function createGitHubCredentialProvider(options = {}) {
13
- const sessionTokens = options.sessionTokens ?? {};
14
- const hostToken = cleanToken(options.hostToken ?? process.env.GH_TOKEN ?? null);
15
- return {
16
- async resolveGitHubToken(input) {
17
- const owner = input.owner.trim();
18
- const repo = input.repo.trim();
19
- const workspaceId = input.workspaceId.trim();
20
- const userId = input.userId?.trim() ?? "";
21
- if (input.purpose === "selected-repo") {
22
- if (!owner || !repo || !workspaceId || !userId) {
23
- throw new Error("No signed-in GitHub token is available for the selected repo; sign in to GitHub for this workspace.");
24
- }
25
- const token = cleanToken(sessionTokens[selectedRepoTokenKey({ owner, repo, workspaceId, userId })]);
26
- if (!token) {
27
- throw new Error("No signed-in GitHub token is available for the selected repo; sign in to GitHub for this workspace.");
28
- }
29
- return { token, source: "signed-in-user" };
30
- }
31
- if (hostToken) {
32
- return { token: hostToken, source: "host-admin-fallback" };
33
- }
34
- throw new Error("No host GitHub token is configured for the explicit admin fallback operation.");
35
- }
36
- };
37
- }
38
- function createEnvGitHubCredentialProvider() {
39
- return {
40
- async resolveGitHubToken(input) {
41
- if (input.purpose === "selected-repo") {
42
- return { token: cleanToken(process.env.RIG_GITHUB_SELECTED_TOKEN ?? null) ?? "", source: "signed-in-user" };
43
- }
44
- const token = cleanToken(process.env.RIG_GITHUB_TOKEN ?? process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN ?? null);
45
- if (!token) {
46
- throw new Error("No host GitHub token is configured for admin fallback.");
47
- }
48
- return { token, source: "host-admin-fallback" };
49
- }
50
- };
51
- }
52
- function createStateGitHubCredentialProvider(options = {}) {
53
- const addCandidate = (candidates, path) => {
54
- const trimmed = path?.trim();
55
- if (!trimmed)
56
- return;
57
- const resolved = resolve(trimmed);
58
- if (!candidates.includes(resolved))
59
- candidates.push(resolved);
60
- };
61
- const addStateDir = (candidates, dir) => {
62
- const trimmed = dir?.trim();
63
- if (!trimmed)
64
- return;
65
- addCandidate(candidates, resolve(trimmed, "github-auth.json"));
66
- };
67
- const addProjectStateDir = (candidates, root) => {
68
- const trimmed = root?.trim();
69
- if (!trimmed)
70
- return;
71
- addStateDir(candidates, resolve(trimmed, ".rig", "state"));
72
- };
73
- const stateFileCandidates = () => {
74
- const candidates = [];
75
- addCandidate(candidates, options.stateFile ?? process.env.RIG_GITHUB_AUTH_STATE_FILE);
76
- addStateDir(candidates, options.stateDir);
77
- addStateDir(candidates, process.env.RIG_STATE_DIR);
78
- addProjectStateDir(candidates, process.env.PROJECT_RIG_ROOT);
79
- addProjectStateDir(candidates, process.env.RIG_PROJECT_ROOT);
80
- addProjectStateDir(candidates, process.env.RIG_HOST_PROJECT_ROOT);
81
- addProjectStateDir(candidates, process.cwd());
82
- return candidates;
83
- };
84
- const readToken = () => {
85
- for (const stateFile of stateFileCandidates()) {
86
- if (!existsSync(stateFile))
87
- continue;
88
- try {
89
- const parsed = JSON.parse(readFileSync(stateFile, "utf8"));
90
- const token = typeof parsed.token === "string" ? cleanToken(parsed.token) : null;
91
- if (token)
92
- return token;
93
- } catch {}
94
- }
95
- return null;
96
- };
97
- return {
98
- async resolveGitHubToken(input) {
99
- const token = readToken();
100
- if (input.purpose === "selected-repo") {
101
- return { token: token ?? cleanToken(process.env.RIG_GITHUB_SELECTED_TOKEN ?? null) ?? "", source: "signed-in-user" };
102
- }
103
- if (token) {
104
- return { token, source: "signed-in-user" };
105
- }
106
- const fallback = cleanToken(process.env.RIG_GITHUB_TOKEN ?? process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN ?? null);
107
- if (!fallback) {
108
- throw new Error("No signed-in GitHub token is stored for Rig and no host admin fallback token is configured.");
109
- }
110
- return { token: fallback, source: "host-admin-fallback" };
111
- }
112
- };
113
- }
114
- export {
115
- createStateGitHubCredentialProvider,
116
- createGitHubCredentialProvider,
117
- createEnvGitHubCredentialProvider
118
- };