@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.
package/dist/src/index.js CHANGED
@@ -15,715 +15,6 @@ var __export = (target, all) => {
15
15
  };
16
16
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
17
 
18
- // packages/github-provider-plugin/src/credentials.ts
19
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
20
- import { resolve as resolve2 } from "path";
21
- function selectedRepoTokenKey(input) {
22
- return `user:${input.userId}|repo:${input.owner}/${input.repo}|workspace:${input.workspaceId}`;
23
- }
24
- function cleanToken(value) {
25
- const trimmed = value?.trim() ?? "";
26
- return trimmed.length > 0 ? trimmed : null;
27
- }
28
- function createGitHubCredentialProvider(options = {}) {
29
- const sessionTokens = options.sessionTokens ?? {};
30
- const hostToken = cleanToken(options.hostToken ?? process.env.GH_TOKEN ?? null);
31
- return {
32
- async resolveGitHubToken(input) {
33
- const owner = input.owner.trim();
34
- const repo = input.repo.trim();
35
- const workspaceId = input.workspaceId.trim();
36
- const userId = input.userId?.trim() ?? "";
37
- if (input.purpose === "selected-repo") {
38
- if (!owner || !repo || !workspaceId || !userId) {
39
- throw new Error("No signed-in GitHub token is available for the selected repo; sign in to GitHub for this workspace.");
40
- }
41
- const token = cleanToken(sessionTokens[selectedRepoTokenKey({ owner, repo, workspaceId, userId })]);
42
- if (!token) {
43
- throw new Error("No signed-in GitHub token is available for the selected repo; sign in to GitHub for this workspace.");
44
- }
45
- return { token, source: "signed-in-user" };
46
- }
47
- if (hostToken) {
48
- return { token: hostToken, source: "host-admin-fallback" };
49
- }
50
- throw new Error("No host GitHub token is configured for the explicit admin fallback operation.");
51
- }
52
- };
53
- }
54
- function createEnvGitHubCredentialProvider() {
55
- return {
56
- async resolveGitHubToken(input) {
57
- if (input.purpose === "selected-repo") {
58
- return { token: cleanToken(process.env.RIG_GITHUB_SELECTED_TOKEN ?? null) ?? "", source: "signed-in-user" };
59
- }
60
- const token = cleanToken(process.env.RIG_GITHUB_TOKEN ?? process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN ?? null);
61
- if (!token) {
62
- throw new Error("No host GitHub token is configured for admin fallback.");
63
- }
64
- return { token, source: "host-admin-fallback" };
65
- }
66
- };
67
- }
68
- function createStateGitHubCredentialProvider(options = {}) {
69
- const addCandidate = (candidates, path) => {
70
- const trimmed = path?.trim();
71
- if (!trimmed)
72
- return;
73
- const resolved = resolve2(trimmed);
74
- if (!candidates.includes(resolved))
75
- candidates.push(resolved);
76
- };
77
- const addStateDir = (candidates, dir) => {
78
- const trimmed = dir?.trim();
79
- if (!trimmed)
80
- return;
81
- addCandidate(candidates, resolve2(trimmed, "github-auth.json"));
82
- };
83
- const addProjectStateDir = (candidates, root) => {
84
- const trimmed = root?.trim();
85
- if (!trimmed)
86
- return;
87
- addStateDir(candidates, resolve2(trimmed, ".rig", "state"));
88
- };
89
- const stateFileCandidates = () => {
90
- const candidates = [];
91
- addCandidate(candidates, options.stateFile ?? process.env.RIG_GITHUB_AUTH_STATE_FILE);
92
- addStateDir(candidates, options.stateDir);
93
- addStateDir(candidates, process.env.RIG_STATE_DIR);
94
- addProjectStateDir(candidates, process.env.PROJECT_RIG_ROOT);
95
- addProjectStateDir(candidates, process.env.RIG_PROJECT_ROOT);
96
- addProjectStateDir(candidates, process.env.RIG_HOST_PROJECT_ROOT);
97
- addProjectStateDir(candidates, process.cwd());
98
- return candidates;
99
- };
100
- const readToken = () => {
101
- for (const stateFile of stateFileCandidates()) {
102
- if (!existsSync2(stateFile))
103
- continue;
104
- try {
105
- const parsed = JSON.parse(readFileSync2(stateFile, "utf8"));
106
- const token = typeof parsed.token === "string" ? cleanToken(parsed.token) : null;
107
- if (token)
108
- return token;
109
- } catch {}
110
- }
111
- return null;
112
- };
113
- return {
114
- async resolveGitHubToken(input) {
115
- const token = readToken();
116
- if (input.purpose === "selected-repo") {
117
- return { token: token ?? cleanToken(process.env.RIG_GITHUB_SELECTED_TOKEN ?? null) ?? "", source: "signed-in-user" };
118
- }
119
- if (token) {
120
- return { token, source: "signed-in-user" };
121
- }
122
- const fallback = cleanToken(process.env.RIG_GITHUB_TOKEN ?? process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN ?? null);
123
- if (!fallback) {
124
- throw new Error("No signed-in GitHub token is stored for Rig and no host admin fallback token is configured.");
125
- }
126
- return { token: fallback, source: "host-admin-fallback" };
127
- }
128
- };
129
- }
130
- var init_credentials = () => {};
131
-
132
- // packages/github-provider-plugin/src/service.ts
133
- var exports_service = {};
134
- __export(exports_service, {
135
- githubProviderService: () => githubProviderService
136
- });
137
- var githubProviderService;
138
- var init_service = __esm(() => {
139
- init_credentials();
140
- githubProviderService = {
141
- createCredentialProvider: (options) => createGitHubCredentialProvider(options)
142
- };
143
- });
144
-
145
- // packages/github-provider-plugin/src/auth-store.ts
146
- import { randomBytes } from "crypto";
147
- import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
148
- import { dirname, resolve } from "path";
149
- import { resolveRigStatePaths } from "@rig/runtime/control-plane/server-paths";
150
- function cleanString(value) {
151
- return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
152
- }
153
- function cleanScopes(value) {
154
- if (!Array.isArray(value))
155
- return [];
156
- return value.flatMap((entry) => {
157
- const clean = cleanString(entry);
158
- return clean ? [clean] : [];
159
- });
160
- }
161
- function parseApiSessions(value) {
162
- if (!Array.isArray(value))
163
- return [];
164
- return value.flatMap((entry) => {
165
- if (!entry || typeof entry !== "object" || Array.isArray(entry))
166
- return [];
167
- const record = entry;
168
- const token = cleanString(record.token);
169
- if (!token)
170
- return [];
171
- return [{
172
- token,
173
- login: cleanString(record.login),
174
- userId: cleanString(record.userId),
175
- createdAt: cleanString(record.createdAt) ?? undefined
176
- }];
177
- });
178
- }
179
- function parsePendingDevice(value) {
180
- if (!value || typeof value !== "object")
181
- return null;
182
- const record = value;
183
- const pollId = cleanString(record.pollId);
184
- const deviceCode = cleanString(record.deviceCode);
185
- const expiresAt = cleanString(record.expiresAt);
186
- const intervalSeconds = typeof record.intervalSeconds === "number" && Number.isFinite(record.intervalSeconds) ? Math.max(1, Math.floor(record.intervalSeconds)) : null;
187
- if (!pollId || !deviceCode || !expiresAt || !intervalSeconds)
188
- return null;
189
- return { pollId, deviceCode, expiresAt, intervalSeconds };
190
- }
191
- function parsePendingDevices(value) {
192
- if (!Array.isArray(value))
193
- return [];
194
- return value.flatMap((entry) => {
195
- const pending = parsePendingDevice(entry);
196
- return pending ? [pending] : [];
197
- });
198
- }
199
- function readStoredAuth(stateFile) {
200
- if (!existsSync(stateFile))
201
- return {};
202
- try {
203
- const parsed = JSON.parse(readFileSync(stateFile, "utf8"));
204
- return {
205
- ...cleanString(parsed.token) ? { token: cleanString(parsed.token) } : {},
206
- login: cleanString(parsed.login),
207
- userId: cleanString(parsed.userId),
208
- scopes: cleanScopes(parsed.scopes),
209
- selectedRepo: cleanString(parsed.selectedRepo),
210
- tokenSource: parsed.tokenSource === "oauth-device" || parsed.tokenSource === "manual-token" || parsed.tokenSource === "env" ? parsed.tokenSource : undefined,
211
- pendingDevice: parsePendingDevice(parsed.pendingDevice),
212
- pendingDevices: parsePendingDevices(parsed.pendingDevices),
213
- apiSessions: parseApiSessions(parsed.apiSessions),
214
- updatedAt: cleanString(parsed.updatedAt) ?? undefined
215
- };
216
- } catch {
217
- return {};
218
- }
219
- }
220
- function newApiSessionToken() {
221
- return `rig_${randomBytes(32).toString("base64url")}`;
222
- }
223
- function writeStoredAuth(stateFile, payload) {
224
- mkdirSync(dirname(stateFile), { recursive: true });
225
- writeFileSync(stateFile, `${JSON.stringify(payload, null, 2)}
226
- `, { encoding: "utf8", mode: 384 });
227
- try {
228
- chmodSync(stateFile, 384);
229
- } catch {}
230
- }
231
- function localProjectAuthStateFile(projectRoot) {
232
- return resolve(projectRoot, ".rig", "state", "github-auth.json");
233
- }
234
- function resolveGitHubAuthStateFile(projectRoot) {
235
- return resolve(resolveRigStatePaths(projectRoot).stateDir, "github-auth.json");
236
- }
237
- function copyGitHubAuthStateToLocalProjectRoot(stateFile, projectRoot) {
238
- const targetFile = localProjectAuthStateFile(projectRoot);
239
- mkdirSync(dirname(targetFile), { recursive: true });
240
- if (existsSync(stateFile)) {
241
- copyFileSync(stateFile, targetFile);
242
- try {
243
- chmodSync(targetFile, 384);
244
- } catch {}
245
- return;
246
- }
247
- writeStoredAuth(targetFile, {});
248
- }
249
- function createGitHubAuthStoreFromStateFile(stateFile) {
250
- return {
251
- stateFile,
252
- status(options) {
253
- const stored = readStoredAuth(stateFile);
254
- const token = cleanString(stored.token);
255
- return {
256
- signedIn: Boolean(token),
257
- login: cleanString(stored.login),
258
- userId: cleanString(stored.userId),
259
- scopes: cleanScopes(stored.scopes),
260
- selectedRepo: cleanString(stored.selectedRepo),
261
- oauthConfigured: options?.oauthConfigured === true,
262
- tokenSource: token ? stored.tokenSource ?? "manual-token" : null
263
- };
264
- },
265
- readToken() {
266
- return cleanString(readStoredAuth(stateFile).token);
267
- },
268
- saveToken(input) {
269
- const previous = readStoredAuth(stateFile);
270
- writeStoredAuth(stateFile, {
271
- ...previous,
272
- token: input.token,
273
- tokenSource: input.tokenSource,
274
- login: input.login ?? null,
275
- userId: input.userId ?? null,
276
- scopes: input.scopes ?? [],
277
- selectedRepo: input.selectedRepo ?? previous.selectedRepo ?? null,
278
- pendingDevice: null,
279
- pendingDevices: [],
280
- apiSessions: previous.apiSessions ?? [],
281
- updatedAt: new Date().toISOString()
282
- });
283
- },
284
- createApiSession() {
285
- const previous = readStoredAuth(stateFile);
286
- const token = newApiSessionToken();
287
- const session = {
288
- token,
289
- login: cleanString(previous.login),
290
- userId: cleanString(previous.userId),
291
- createdAt: new Date().toISOString()
292
- };
293
- writeStoredAuth(stateFile, {
294
- ...previous,
295
- apiSessions: [...(previous.apiSessions ?? []).slice(-9), session],
296
- updatedAt: new Date().toISOString()
297
- });
298
- return { token, login: session.login ?? null, userId: session.userId ?? null };
299
- },
300
- readApiSession(token) {
301
- const clean = cleanString(token);
302
- if (!clean)
303
- return null;
304
- const previous = readStoredAuth(stateFile);
305
- const session = (previous.apiSessions ?? []).find((candidate) => candidate.token === clean);
306
- return session ? { login: cleanString(session.login), userId: cleanString(session.userId) } : null;
307
- },
308
- copyToProjectRoot(projectRoot) {
309
- const targetFile = resolveGitHubAuthStateFile(projectRoot);
310
- writeStoredAuth(targetFile, readStoredAuth(stateFile));
311
- },
312
- copyToLocalProjectRoot(projectRoot) {
313
- copyGitHubAuthStateToLocalProjectRoot(stateFile, projectRoot);
314
- },
315
- savePendingDevice(input) {
316
- const previous = readStoredAuth(stateFile);
317
- const pendingDevices = [
318
- ...previous.pendingDevice ? [previous.pendingDevice] : [],
319
- ...previous.pendingDevices ?? [],
320
- input
321
- ].filter((entry, index, entries) => entries.findIndex((candidate) => candidate.pollId === entry.pollId) === index);
322
- writeStoredAuth(stateFile, {
323
- ...previous,
324
- pendingDevice: null,
325
- pendingDevices,
326
- updatedAt: new Date().toISOString()
327
- });
328
- },
329
- saveSelectedRepo(selectedRepo) {
330
- const previous = readStoredAuth(stateFile);
331
- writeStoredAuth(stateFile, {
332
- ...previous,
333
- selectedRepo: selectedRepo ?? null,
334
- updatedAt: new Date().toISOString()
335
- });
336
- },
337
- readPendingDevice(pollId) {
338
- const previous = readStoredAuth(stateFile);
339
- const pending = [
340
- ...previous.pendingDevice ? [previous.pendingDevice] : [],
341
- ...previous.pendingDevices ?? []
342
- ].find((entry) => entry.pollId === pollId) ?? null;
343
- if (!pending)
344
- return null;
345
- if (Date.parse(pending.expiresAt) <= Date.now())
346
- return null;
347
- return pending;
348
- },
349
- clearPendingDevice(pollId) {
350
- const previous = readStoredAuth(stateFile);
351
- const remaining = pollId ? (previous.pendingDevices ?? []).filter((entry) => entry.pollId !== pollId) : [];
352
- writeStoredAuth(stateFile, {
353
- ...previous,
354
- pendingDevice: null,
355
- pendingDevices: remaining,
356
- updatedAt: new Date().toISOString()
357
- });
358
- }
359
- };
360
- }
361
- function createGitHubAuthStore(projectRoot) {
362
- return createGitHubAuthStoreFromStateFile(resolveGitHubAuthStateFile(projectRoot));
363
- }
364
-
365
- // packages/github-provider-plugin/src/index.ts
366
- init_credentials();
367
-
368
- // packages/github-provider-plugin/src/projects.ts
369
- function asRecord(value) {
370
- return value && typeof value === "object" && !Array.isArray(value) ? value : null;
371
- }
372
- function asString(value) {
373
- return typeof value === "string" && value.trim().length > 0 ? value : undefined;
374
- }
375
- function asNumber(value) {
376
- return typeof value === "number" && Number.isFinite(value) ? value : undefined;
377
- }
378
- async function defaultGraphQLFetch(query, variables, token) {
379
- const response = await fetch("https://api.github.com/graphql", {
380
- method: "POST",
381
- headers: {
382
- "content-type": "application/json",
383
- authorization: `Bearer ${token}`,
384
- accept: "application/vnd.github+json"
385
- },
386
- body: JSON.stringify({ query, variables })
387
- });
388
- const json = await response.json().catch(() => ({}));
389
- if (!response.ok || json.errors) {
390
- throw new Error(`GitHub Projects GraphQL request failed: ${JSON.stringify(json.errors ?? { status: response.status })}`);
391
- }
392
- return json.data;
393
- }
394
- function projectNodesFrom(data) {
395
- const root = asRecord(data);
396
- const owner = asRecord(root?.organization) ?? asRecord(root?.user);
397
- const projects = asRecord(owner?.projectsV2);
398
- const nodes = projects?.nodes;
399
- return Array.isArray(nodes) ? nodes : [];
400
- }
401
- async function listGitHubProjects(input) {
402
- const query = `
403
- query RigListProjects($owner: String!, $first: Int!) {
404
- organization(login: $owner) { projectsV2(first: $first, orderBy: { field: UPDATED_AT, direction: DESC }) { nodes { id number title url } } }
405
- user(login: $owner) { projectsV2(first: $first, orderBy: { field: UPDATED_AT, direction: DESC }) { nodes { id number title url } } }
406
- }
407
- `;
408
- const fetchGraphQL = input.fetchGraphQL ?? defaultGraphQLFetch;
409
- const data = await fetchGraphQL(query, { owner: input.owner, first: input.first ?? 20 }, input.token);
410
- return projectNodesFrom(data).flatMap((node) => {
411
- const record = asRecord(node);
412
- const id = asString(record?.id);
413
- const number = asNumber(record?.number);
414
- const title = asString(record?.title);
415
- if (!id || number === undefined || !title)
416
- return [];
417
- return [{ id, number, title, ...asString(record?.url) ? { url: asString(record?.url) } : {} }];
418
- });
419
- }
420
- async function resolveProjectStatusField(input) {
421
- const query = `
422
- query RigProjectStatusField($projectId: ID!) {
423
- node(id: $projectId) {
424
- ... on ProjectV2 {
425
- fields(first: 50) {
426
- nodes {
427
- ... on ProjectV2FieldCommon { id name }
428
- ... on ProjectV2SingleSelectField { id name options { id name } }
429
- }
430
- }
431
- }
432
- }
433
- }
434
- `;
435
- const fetchGraphQL = input.fetchGraphQL ?? defaultGraphQLFetch;
436
- const data = await fetchGraphQL(query, { projectId: input.projectId }, input.token);
437
- const fields = asRecord(asRecord(asRecord(data)?.node)?.fields)?.nodes;
438
- for (const node of Array.isArray(fields) ? fields : []) {
439
- const record = asRecord(node);
440
- if (asString(record?.name)?.toLowerCase() !== "status")
441
- continue;
442
- const id = asString(record?.id);
443
- if (!id)
444
- continue;
445
- const options = Array.isArray(record?.options) ? record.options.flatMap((option) => {
446
- const optionRecord = asRecord(option);
447
- const optionId = asString(optionRecord?.id);
448
- const name = asString(optionRecord?.name);
449
- return optionId && name ? [{ id: optionId, name }] : [];
450
- }) : [];
451
- return { id, name: "Status", options };
452
- }
453
- throw new Error(`GitHub Project ${input.projectId} does not expose a Status single-select field.`);
454
- }
455
- async function ensureIssueProjectItem(input) {
456
- const query = `
457
- query RigFindProjectIssueItem($projectId: ID!, $issueNodeId: ID!) {
458
- node(id: $projectId) {
459
- ... on ProjectV2 {
460
- items(first: 100) { nodes { id content { ... on Issue { id } } } }
461
- }
462
- }
463
- }
464
- `;
465
- const fetchGraphQL = input.fetchGraphQL ?? defaultGraphQLFetch;
466
- const data = await fetchGraphQL(query, { projectId: input.projectId, issueNodeId: input.issueNodeId }, input.token);
467
- const nodes = asRecord(asRecord(asRecord(data)?.node)?.items)?.nodes;
468
- for (const node of Array.isArray(nodes) ? nodes : []) {
469
- const record = asRecord(node);
470
- const content = asRecord(record?.content);
471
- if (asString(content?.id) === input.issueNodeId) {
472
- const id2 = asString(record?.id);
473
- if (id2)
474
- return { id: id2, created: false };
475
- }
476
- }
477
- const mutation = `
478
- mutation RigAddIssueToProject($projectId: ID!, $contentId: ID!) {
479
- addProjectV2ItemById(input: { projectId: $projectId, contentId: $contentId }) { item { id } }
480
- }
481
- `;
482
- const created = await fetchGraphQL(mutation, { projectId: input.projectId, contentId: input.issueNodeId }, input.token);
483
- const addResult = asRecord(asRecord(created)?.addProjectV2ItemById);
484
- const id = asString(asRecord(addResult?.item)?.id);
485
- if (!id)
486
- throw new Error("GitHub Project item creation did not return an item id.");
487
- return { id, created: true };
488
- }
489
- async function updateIssueProjectStatus(input) {
490
- const mutation = `
491
- mutation RigUpdateProjectStatus($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) {
492
- updateProjectV2ItemFieldValue(input: {
493
- projectId: $projectId,
494
- itemId: $itemId,
495
- fieldId: $fieldId,
496
- value: { singleSelectOptionId: $optionId }
497
- }) { projectV2Item { id } }
498
- }
499
- `;
500
- const fetchGraphQL = input.fetchGraphQL ?? defaultGraphQLFetch;
501
- await fetchGraphQL(mutation, {
502
- projectId: input.projectId,
503
- itemId: input.itemId,
504
- fieldId: input.fieldId,
505
- optionId: input.optionId
506
- }, input.token);
507
- }
508
- // packages/github-provider-plugin/src/github-api.ts
509
- import { randomUUID } from "crypto";
510
- function normalizeString(value) {
511
- return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
512
- }
513
- function normalizeScopes(value) {
514
- if (Array.isArray(value)) {
515
- return value.flatMap((entry) => {
516
- const normalized = normalizeString(entry);
517
- return normalized ? [normalized] : [];
518
- });
519
- }
520
- if (typeof value === "string") {
521
- return value.split(/[ ,]+/).map((entry) => entry.trim()).filter(Boolean);
522
- }
523
- return [];
524
- }
525
- async function defaultPostGitHubForm(endpoint, body) {
526
- const response = await fetch(endpoint, {
527
- method: "POST",
528
- headers: {
529
- accept: "application/json",
530
- "content-type": "application/x-www-form-urlencoded",
531
- "user-agent": "rig"
532
- },
533
- body: new URLSearchParams(body)
534
- });
535
- const payload = await response.json().catch(() => ({}));
536
- return { status: response.status, payload };
537
- }
538
- async function fetchGitHubUserInfo(token) {
539
- const response = await fetch("https://api.github.com/user", {
540
- headers: {
541
- accept: "application/vnd.github+json",
542
- authorization: `Bearer ${token}`,
543
- "user-agent": "rig"
544
- }
545
- });
546
- const payload = await response.json().catch(() => ({}));
547
- if (!response.ok) {
548
- throw new Error(typeof payload.message === "string" ? payload.message : `GitHub user lookup failed (${response.status}).`);
549
- }
550
- const login = normalizeString(payload.login);
551
- const id = typeof payload.id === "number" ? String(payload.id) : normalizeString(payload.id);
552
- if (!login || !id) {
553
- throw new Error("GitHub user lookup did not return login/id.");
554
- }
555
- return { login, id, scopes: normalizeScopes(response.headers.get("x-oauth-scopes")) };
556
- }
557
- function resolveGitHubAuthStatus(input) {
558
- return createGitHubAuthStore(input.projectRoot).status({ oauthConfigured: input.oauthConfigured });
559
- }
560
- async function saveGitHubTokenForProject(input) {
561
- const token = normalizeString(input.token);
562
- if (!token) {
563
- throw new Error("token is required");
564
- }
565
- const user = await (input.fetchUser ?? fetchGitHubUserInfo)(token);
566
- const store = createGitHubAuthStore(input.projectRoot);
567
- store.saveToken({
568
- token,
569
- tokenSource: input.tokenSource ?? "manual-token",
570
- login: user.login,
571
- userId: user.id,
572
- scopes: user.scopes ?? [],
573
- selectedRepo: input.selectedRepo ?? undefined
574
- });
575
- return { ok: true, ...store.status({ oauthConfigured: true }) };
576
- }
577
- async function beginGitHubDeviceFlow(input) {
578
- const clientId = normalizeString(input.clientId);
579
- if (!clientId) {
580
- throw new Error("clientId is required");
581
- }
582
- const postForm = input.postForm ?? defaultPostGitHubForm;
583
- const result = await postForm("https://github.com/login/device/code", {
584
- client_id: clientId,
585
- scope: normalizeString(input.scope) ?? "repo read:project user:email"
586
- });
587
- const deviceCode = normalizeString(result.payload.device_code);
588
- if (result.status < 200 || result.status >= 300 || !deviceCode) {
589
- throw new Error(normalizeString(result.payload.error_description) ?? "GitHub device flow start failed.");
590
- }
591
- const expiresIn = typeof result.payload.expires_in === "number" ? result.payload.expires_in : 900;
592
- const intervalSeconds = typeof result.payload.interval === "number" ? result.payload.interval : 5;
593
- const pollId = randomUUID();
594
- createGitHubAuthStore(input.projectRoot).savePendingDevice({
595
- pollId,
596
- deviceCode,
597
- expiresAt: new Date(Date.now() + expiresIn * 1000).toISOString(),
598
- intervalSeconds
599
- });
600
- if (input.selectedRepo) {
601
- createGitHubAuthStore(input.projectRoot).saveSelectedRepo(input.selectedRepo);
602
- }
603
- return {
604
- ok: true,
605
- pollId,
606
- userCode: normalizeString(result.payload.user_code),
607
- verificationUri: normalizeString(result.payload.verification_uri),
608
- expiresIn,
609
- intervalSeconds
610
- };
611
- }
612
- async function pollGitHubDeviceFlow(input) {
613
- const clientId = normalizeString(input.clientId);
614
- if (!clientId) {
615
- throw new Error("clientId is required");
616
- }
617
- const store = createGitHubAuthStore(input.projectRoot);
618
- const pending = store.readPendingDevice(input.pollId);
619
- if (!pending) {
620
- return { ok: false, status: "expired", error: "GitHub device flow expired or unknown." };
621
- }
622
- const result = await (input.postForm ?? defaultPostGitHubForm)("https://github.com/login/oauth/access_token", {
623
- client_id: clientId,
624
- device_code: pending.deviceCode,
625
- grant_type: "urn:ietf:params:oauth:grant-type:device_code"
626
- });
627
- const error = normalizeString(result.payload.error);
628
- if (error === "authorization_pending" || error === "slow_down") {
629
- return {
630
- ok: false,
631
- status: error === "slow_down" ? "slow-down" : "pending",
632
- intervalSeconds: error === "slow_down" ? pending.intervalSeconds + 5 : pending.intervalSeconds
633
- };
634
- }
635
- if (error || typeof result.payload.access_token !== "string") {
636
- return {
637
- ok: false,
638
- status: "error",
639
- error: normalizeString(result.payload.error_description) ?? "GitHub device authorization failed."
640
- };
641
- }
642
- const token = result.payload.access_token;
643
- const user = await (input.fetchUser ?? fetchGitHubUserInfo)(token);
644
- store.saveToken({
645
- token,
646
- tokenSource: "oauth-device",
647
- login: user.login,
648
- userId: user.id,
649
- scopes: user.scopes ?? normalizeScopes(result.payload.scope),
650
- selectedRepo: input.selectedRepo ?? undefined
651
- });
652
- store.clearPendingDevice(input.pollId);
653
- return { ok: true, status: "signed-in", ...store.status({ oauthConfigured: true }) };
654
- }
655
- function checkGitHubRepoPermissions(input) {
656
- const store = createGitHubAuthStore(input.projectRoot);
657
- const auth = store.status({ oauthConfigured: input.oauthConfigured });
658
- if (!auth.signedIn) {
659
- return { ok: false, signedIn: false, canOpenPullRequest: false, reason: "not-authenticated" };
660
- }
661
- const normalizedScopes = auth.scopes.map((scope) => scope.toLowerCase());
662
- const broadEnough = normalizedScopes.includes("repo") || normalizedScopes.includes("public_repo");
663
- return {
664
- ok: true,
665
- signedIn: true,
666
- login: auth.login,
667
- scopes: auth.scopes,
668
- canOpenPullRequest: broadEnough,
669
- pullRequests: broadEnough,
670
- push: broadEnough,
671
- reason: broadEnough ? "stored-token" : "token-scope-unverified"
672
- };
673
- }
674
- async function probeGitHubRepository(input) {
675
- const headers = {
676
- accept: "application/vnd.github+json",
677
- "user-agent": "rig"
678
- };
679
- if (input.token) {
680
- headers.authorization = `Bearer ${input.token}`;
681
- }
682
- try {
683
- const response = await (input.fetchRepository ?? fetch)(`https://api.github.com/repos/${encodeURIComponent(input.owner)}/${encodeURIComponent(input.repo)}`, { headers });
684
- const payload = await response.json().catch(() => ({}));
685
- if (response.ok) {
686
- return {
687
- ok: true,
688
- owner: input.owner,
689
- repo: input.repo,
690
- status: response.status,
691
- authenticated: Boolean(input.token),
692
- authenticationRequired: payload.private === true && !input.token,
693
- fullName: typeof payload.full_name === "string" ? payload.full_name : `${input.owner}/${input.repo}`,
694
- private: typeof payload.private === "boolean" ? payload.private : null,
695
- message: input.token ? "Repository access verified with signed-in GitHub credentials." : "Public repository access verified without credentials.",
696
- scopes: input.scopes
697
- };
698
- }
699
- const authenticationRequired = !input.token && (response.status === 401 || response.status === 403 || response.status === 404);
700
- return {
701
- ok: false,
702
- owner: input.owner,
703
- repo: input.repo,
704
- status: response.status,
705
- authenticated: Boolean(input.token),
706
- authenticationRequired,
707
- fullName: null,
708
- private: null,
709
- message: authenticationRequired ? "Repository is private, missing, or inaccessible without GitHub sign-in. Sign in before saving this config." : typeof payload.message === "string" ? payload.message : `GitHub repository probe failed (${response.status}).`,
710
- scopes: input.scopes
711
- };
712
- } catch (error) {
713
- return {
714
- ok: false,
715
- owner: input.owner,
716
- repo: input.repo,
717
- status: 0,
718
- authenticated: Boolean(input.token),
719
- authenticationRequired: !input.token,
720
- fullName: null,
721
- private: null,
722
- message: error instanceof Error ? error.message : "GitHub repository probe failed.",
723
- scopes: input.scopes
724
- };
725
- }
726
- }
727
18
  // packages/github-provider-plugin/src/issue-analysis.ts
728
19
  import { createHash } from "crypto";
729
20
  function stableIssueHash(issue) {
@@ -945,7 +236,7 @@ function createIssueAnalysisWriteBack(input) {
945
236
  throw new Error("Issue analysis writeback requires removeLabels for labelsToRemove.");
946
237
  await input.target.removeLabels(issue.id, uniqueLabels(result.labelsToRemove));
947
238
  }
948
- const comment = (input.buildStatusComment ?? defaultStatusComment)({ issue, result, reason });
239
+ const comment = (input.buildStatusComment ?? defaultStatusComment)({ issue, result, ...reason !== undefined ? { reason } : {} });
949
240
  if (comment?.trim()) {
950
241
  if (!input.target.updateTask)
951
242
  throw new Error("Issue analysis writeback requires updateTask for sticky status comments.");
@@ -970,7 +261,7 @@ function sourceWithWriteBackCapabilities(source) {
970
261
  if (typeof candidate.updateTask !== "function")
971
262
  return null;
972
263
  return {
973
- get: candidate.get?.bind(candidate),
264
+ ...typeof candidate.get === "function" ? { get: candidate.get.bind(candidate) } : {},
974
265
  updateTask: candidate.updateTask.bind(candidate),
975
266
  ...typeof candidate.addLabels === "function" ? { addLabels: candidate.addLabels.bind(candidate) } : {},
976
267
  ...typeof candidate.removeLabels === "function" ? { removeLabels: candidate.removeLabels.bind(candidate) } : {},
@@ -999,8 +290,8 @@ function createConfiguredIssueAnalysisRunner(input) {
999
290
  if (!target)
1000
291
  return null;
1001
292
  const analyzer = input.analyzer ?? createPiIssueAnalyzer({
1002
- runCommand: input.runCommand,
1003
- model: input.context.config.issueAnalysis?.model
293
+ ...input.runCommand ? { runCommand: input.runCommand } : {},
294
+ ...input.context.config.issueAnalysis?.model ? { model: input.context.config.issueAnalysis.model } : {}
1004
295
  });
1005
296
  const baseWriteBack = createIssueAnalysisWriteBack({ target });
1006
297
  const service = createIssueAnalysisService({
@@ -1013,7 +304,7 @@ function createConfiguredIssueAnalysisRunner(input) {
1013
304
  return createContinuousIssueAnalysisRunner({
1014
305
  loadIssues: async () => [...await source.list()],
1015
306
  service,
1016
- intervalMs: input.intervalMs,
307
+ ...input.intervalMs !== undefined ? { intervalMs: input.intervalMs } : {},
1017
308
  reason: "continuous-issue-analysis",
1018
309
  ...input.setIntervalFn ? { setIntervalFn: input.setIntervalFn } : {},
1019
310
  ...input.clearIntervalFn ? { clearIntervalFn: input.clearIntervalFn } : {},
@@ -1034,7 +325,7 @@ function createIssueAnalysisService(input) {
1034
325
  const result = await input.analyzer({ issue, neighbors, prompt });
1035
326
  analyzedHashes.set(issue.id, hash);
1036
327
  if (result.metadataPatch || result.labelsToAdd?.length || result.labelsToRemove?.length || result.generatedIssues?.length) {
1037
- await input.writeBack?.({ issue, result, reason: options.reason });
328
+ await input.writeBack?.({ issue, result, ...options.reason !== undefined ? { reason: options.reason } : {} });
1038
329
  }
1039
330
  results.push({ issue, result });
1040
331
  }
@@ -1094,8 +385,14 @@ function createContinuousIssueAnalysisRunner(input) {
1094
385
  }
1095
386
  };
1096
387
  }
388
+ var init_issue_analysis = () => {};
389
+
1097
390
  // packages/github-provider-plugin/src/triage-run.ts
1098
- import { buildPluginHostContext } from "@rig/runtime/control-plane/plugin-host-context";
391
+ var exports_triage_run = {};
392
+ __export(exports_triage_run, {
393
+ runIssueAnalysisTriage: () => runIssueAnalysisTriage
394
+ });
395
+ import { buildPluginHostContext } from "@rig/core/plugin-host-context";
1099
396
  function summarizeResults(results) {
1100
397
  return results.reduce((summary, entry) => {
1101
398
  if (entry.result.metadataPatch && Object.keys(entry.result.metadataPatch).length > 0) {
@@ -1112,7 +409,9 @@ async function loadContext(projectRoot) {
1112
409
  if (!context)
1113
410
  return null;
1114
411
  return {
1115
- config: context.config,
412
+ config: {
413
+ ...context.config.issueAnalysis ? { issueAnalysis: context.config.issueAnalysis } : {}
414
+ },
1116
415
  taskSourceRegistry: context.taskSourceRegistry
1117
416
  };
1118
417
  }
@@ -1170,8 +469,8 @@ async function runIssueAnalysisTriage(options) {
1170
469
  const runner = createConfiguredIssueAnalysisRunner({
1171
470
  projectRoot: options.projectRoot,
1172
471
  context,
1173
- analyzer: options.analyzer,
1174
- runCommand: options.runCommand,
472
+ ...options.analyzer ? { analyzer: options.analyzer } : {},
473
+ ...options.runCommand ? { runCommand: options.runCommand } : {},
1175
474
  onWriteBack: refreshSnapshotAfterWriteBack
1176
475
  });
1177
476
  if (!runner) {
@@ -1191,10 +490,343 @@ async function runIssueAnalysisTriage(options) {
1191
490
  refreshedIssueCount
1192
491
  };
1193
492
  }
493
+ var init_triage_run = __esm(() => {
494
+ init_issue_analysis();
495
+ });
496
+
497
+ // packages/github-provider-plugin/src/identity.ts
498
+ import {
499
+ RIG_WORKFLOW_STARTED
500
+ } from "@rig/contracts";
501
+ function stringField(value) {
502
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
503
+ }
504
+ function numericStringField(value) {
505
+ return typeof value === "number" && Number.isInteger(value) ? String(value) : stringField(value);
506
+ }
507
+ function asRecord(value) {
508
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
509
+ }
510
+ function customEntries(entries) {
511
+ const customs = [];
512
+ for (const entry of entries) {
513
+ if (entry.type === "custom")
514
+ customs.push(entry);
515
+ }
516
+ return customs;
517
+ }
518
+ function candidateFromStartedData(data) {
519
+ const record = asRecord(data);
520
+ if (!record)
521
+ return null;
522
+ const owner = asRecord(record.owner);
523
+ const selectedRepo = stringField(record.selectedRepo);
524
+ const githubUserId = stringField(owner?.githubUserId) ?? numericStringField(record.githubUserId) ?? numericStringField(record.userId);
525
+ const login = stringField(owner?.login) ?? stringField(record.login);
526
+ const namespaceKey = stringField(owner?.namespaceKey) ?? stringField(record.namespaceKey) ?? stringField(record.userNamespaceKey);
527
+ if (!selectedRepo && !githubUserId && !login && !namespaceKey)
528
+ return null;
529
+ return { selectedRepo, githubUserId, login, namespaceKey, source: "workflow-metadata" };
530
+ }
531
+ function workflowIdentityCandidate(entries) {
532
+ const customs = customEntries(entries);
533
+ for (let index = customs.length - 1;index >= 0; index -= 1) {
534
+ const entry = customs[index];
535
+ if (entry?.customType !== RIG_WORKFLOW_STARTED)
536
+ continue;
537
+ const candidate = candidateFromStartedData(entry.data);
538
+ if (candidate)
539
+ return candidate;
540
+ }
541
+ return null;
542
+ }
543
+ function ompGitHubIdentityCandidate(ctx) {
544
+ const account = asRecord(ctx.modelRegistry?.authStorage?.getOAuthAccountIdentity?.("github-copilot", ctx.sessionManager.getSessionId()));
545
+ if (!account)
546
+ return null;
547
+ const githubUserId = stringField(account.accountId);
548
+ const login = stringField(account.login) ?? stringField(account.email);
549
+ if (!githubUserId && !login)
550
+ return null;
551
+ return {
552
+ githubUserId,
553
+ login,
554
+ namespaceKey: githubUserId ? `gh:${githubUserId}` : undefined,
555
+ source: "omp-github-copilot-auth"
556
+ };
557
+ }
558
+ function envField(name) {
559
+ return stringField(process.env[name]);
560
+ }
561
+ function envIdentityCandidate() {
562
+ const selectedRepo = envField("RIG_SELECTED_REPO");
563
+ const githubUserId = envField("RIG_GITHUB_USER_ID");
564
+ const login = envField("RIG_GITHUB_LOGIN");
565
+ const namespaceKey = envField("RIG_GITHUB_NAMESPACE_KEY") ?? (githubUserId ? `gh:${githubUserId}` : undefined);
566
+ if (!selectedRepo && !githubUserId && !login && !namespaceKey)
567
+ return null;
568
+ return {
569
+ selectedRepo,
570
+ githubUserId,
571
+ login,
572
+ namespaceKey,
573
+ source: "rig-public-identity-env"
574
+ };
575
+ }
576
+ function mergeSource(sources, source) {
577
+ if (source && !sources.includes(source))
578
+ sources.push(source);
579
+ }
580
+ function completeIdentity(candidate, sources) {
581
+ const selectedRepo = stringField(candidate.selectedRepo);
582
+ const githubUserId = stringField(candidate.githubUserId);
583
+ const login = stringField(candidate.login);
584
+ const namespaceKey = stringField(candidate.namespaceKey) ?? (githubUserId ? `gh:${githubUserId}` : undefined);
585
+ if (!selectedRepo || !githubUserId || !login || !namespaceKey)
586
+ return null;
587
+ const uniqueSources = [];
588
+ for (const value of sources) {
589
+ if (!uniqueSources.includes(value))
590
+ uniqueSources.push(value);
591
+ }
592
+ return {
593
+ selectedRepo,
594
+ owner: { githubUserId, login, namespaceKey },
595
+ source: uniqueSources.join("+")
596
+ };
597
+ }
598
+ function resolveRigIdentity(ctx) {
599
+ const workflow = workflowIdentityCandidate(ctx.sessionManager.getBranch());
600
+ const omp = ompGitHubIdentityCandidate(ctx);
601
+ const env = envIdentityCandidate();
602
+ const sources = [];
603
+ const selectedRepo = workflow?.selectedRepo ?? env?.selectedRepo;
604
+ const githubUserId = workflow?.githubUserId ?? omp?.githubUserId ?? env?.githubUserId;
605
+ const login = workflow?.login ?? omp?.login ?? env?.login;
606
+ const namespaceKey = workflow?.namespaceKey ?? omp?.namespaceKey ?? env?.namespaceKey;
607
+ if (workflow && (workflow.selectedRepo || workflow.githubUserId || workflow.login || workflow.namespaceKey))
608
+ mergeSource(sources, workflow.source);
609
+ if (omp && (!workflow?.githubUserId || !workflow?.login || !workflow?.namespaceKey))
610
+ mergeSource(sources, omp.source);
611
+ if (env && (!workflow?.selectedRepo && env.selectedRepo || !workflow?.githubUserId && !omp?.githubUserId && env.githubUserId || !workflow?.login && !omp?.login && env.login || !workflow?.namespaceKey && !omp?.namespaceKey && env.namespaceKey))
612
+ mergeSource(sources, env.source);
613
+ return completeIdentity({ selectedRepo, githubUserId, login, namespaceKey, source: sources.join("+") }, sources);
614
+ }
615
+ function resolveRigIdentityFilter(ctx) {
616
+ const identity = resolveRigIdentity(ctx);
617
+ if (!identity)
618
+ return null;
619
+ return {
620
+ cwd: ctx.sessionManager.getCwd(),
621
+ selectedRepo: identity.selectedRepo,
622
+ githubUserId: identity.owner.githubUserId,
623
+ namespaceKey: identity.owner.namespaceKey
624
+ };
625
+ }
626
+ var init_identity = () => {};
627
+
628
+ // packages/github-provider-plugin/src/identity-env.ts
629
+ var exports_identity_env = {};
630
+ __export(exports_identity_env, {
631
+ stripRunChildCredentialEnv: () => stripRunChildCredentialEnv,
632
+ resolveRigIdentityFilter: () => resolveRigIdentityFilter,
633
+ resolveRigIdentity: () => resolveRigIdentity,
634
+ resolveIdentityEnv: () => resolveIdentityEnv,
635
+ identityFilterFromEnv: () => identityFilterFromEnv,
636
+ applyIdentityEnv: () => applyIdentityEnv,
637
+ RUN_CHILD_STRIPPED_CREDENTIAL_ENV_KEYS: () => RUN_CHILD_STRIPPED_CREDENTIAL_ENV_KEYS
638
+ });
639
+ import { existsSync, readFileSync } from "fs";
640
+ import { resolve } from "path";
641
+ function stringField2(value) {
642
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
643
+ }
644
+ function githubUserId(value) {
645
+ if (typeof value === "number" && Number.isInteger(value))
646
+ return String(value);
647
+ return stringField2(value);
648
+ }
649
+ function readJsonRecord(path) {
650
+ if (!existsSync(path))
651
+ return null;
652
+ try {
653
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
654
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
655
+ } catch {
656
+ return null;
657
+ }
658
+ }
659
+ function resolveIdentityEnv(workspaceRoot) {
660
+ const stateDir = resolve(workspaceRoot, ".rig", "state");
661
+ const auth = readJsonRecord(resolve(stateDir, "github-auth.json"));
662
+ const connection = readJsonRecord(resolve(stateDir, "connection.json"));
663
+ const projectLink = readJsonRecord(resolve(stateDir, "project-link.json"));
664
+ const values = {};
665
+ const selectedRepo = stringField2(auth?.selectedRepo) ?? stringField2(connection?.project) ?? stringField2(projectLink?.repoSlug);
666
+ const userId = githubUserId(auth?.userId);
667
+ const login = stringField2(auth?.login);
668
+ const namespaceKey = stringField2(auth?.userNamespaceKey);
669
+ if (selectedRepo)
670
+ values.RIG_SELECTED_REPO = selectedRepo;
671
+ if (userId)
672
+ values.RIG_GITHUB_USER_ID = userId;
673
+ if (login)
674
+ values.RIG_GITHUB_LOGIN = login;
675
+ if (namespaceKey)
676
+ values.RIG_GITHUB_NAMESPACE_KEY = namespaceKey;
677
+ return values;
678
+ }
679
+ function applyIdentityEnv(workspaceRoot) {
680
+ const previous = new Map;
681
+ for (const key of [...PUBLIC_IDENTITY_ENV_KEYS, ...RIG_STATE_ENV_KEYS])
682
+ previous.set(key, process.env[key]);
683
+ const stateDir = resolve(workspaceRoot, ".rig", "state");
684
+ const auth = readJsonRecord(resolve(stateDir, "github-auth.json"));
685
+ const connection = readJsonRecord(resolve(stateDir, "connection.json"));
686
+ const projectLink = readJsonRecord(resolve(stateDir, "project-link.json"));
687
+ const values = { ...resolveIdentityEnv(workspaceRoot) };
688
+ values.RIG_GITHUB_AUTH_STATE_FILE = resolve(stateDir, "github-auth.json");
689
+ const hasLocalIdentityState = Boolean(auth || connection || projectLink);
690
+ for (const key of [...PUBLIC_IDENTITY_ENV_KEYS, ...RIG_STATE_ENV_KEYS]) {
691
+ const value = values[key];
692
+ if (value)
693
+ process.env[key] = value;
694
+ else if (hasLocalIdentityState)
695
+ delete process.env[key];
696
+ }
697
+ return () => {
698
+ for (const key of [...PUBLIC_IDENTITY_ENV_KEYS, ...RIG_STATE_ENV_KEYS]) {
699
+ const value = previous.get(key);
700
+ if (value === undefined)
701
+ delete process.env[key];
702
+ else
703
+ process.env[key] = value;
704
+ }
705
+ };
706
+ }
707
+ function identityFilterFromEnv() {
708
+ return {
709
+ ...process.env.RIG_SELECTED_REPO ? { selectedRepo: process.env.RIG_SELECTED_REPO } : {},
710
+ ...process.env.RIG_GITHUB_USER_ID ? { githubUserId: process.env.RIG_GITHUB_USER_ID } : {},
711
+ ...process.env.RIG_GITHUB_NAMESPACE_KEY ? { namespaceKey: process.env.RIG_GITHUB_NAMESPACE_KEY } : {}
712
+ };
713
+ }
714
+ function stripRunChildCredentialEnv(env = process.env) {
715
+ const next = { ...env };
716
+ for (const key of RUN_CHILD_STRIPPED_CREDENTIAL_ENV_KEYS)
717
+ delete next[key];
718
+ return next;
719
+ }
720
+ var PUBLIC_IDENTITY_ENV_KEYS, RIG_STATE_ENV_KEYS, RUN_CHILD_STRIPPED_CREDENTIAL_ENV_KEYS;
721
+ var init_identity_env = __esm(() => {
722
+ init_identity();
723
+ PUBLIC_IDENTITY_ENV_KEYS = [
724
+ "RIG_SELECTED_REPO",
725
+ "RIG_GITHUB_USER_ID",
726
+ "RIG_GITHUB_LOGIN",
727
+ "RIG_GITHUB_NAMESPACE_KEY"
728
+ ];
729
+ RIG_STATE_ENV_KEYS = [
730
+ "RIG_GITHUB_AUTH_STATE_FILE"
731
+ ];
732
+ RUN_CHILD_STRIPPED_CREDENTIAL_ENV_KEYS = [
733
+ "RIG_GITHUB_TOKEN",
734
+ "RIG_GITHUB_SELECTED_TOKEN",
735
+ "GH_TOKEN",
736
+ "GITHUB_TOKEN"
737
+ ];
738
+ });
739
+
740
+ // packages/github-provider-plugin/src/service.ts
741
+ var exports_service = {};
742
+ __export(exports_service, {
743
+ githubProviderService: () => githubProviderService
744
+ });
745
+ import { createGitHubCredentialProvider } from "@rig/github-lib";
746
+ var githubProviderService;
747
+ var init_service = __esm(() => {
748
+ githubProviderService = {
749
+ createCredentialProvider: (options) => createGitHubCredentialProvider(options)
750
+ };
751
+ });
752
+
753
+ // packages/github-provider-plugin/src/index.ts
754
+ init_issue_analysis();
755
+ init_triage_run();
756
+
757
+ export * from "@rig/github-lib";
758
+
1194
759
  // packages/github-provider-plugin/src/plugin.ts
760
+ import { spawnSync } from "child_process";
1195
761
  import { definePlugin } from "@rig/core/config";
1196
- import { GITHUB_PROVIDER_CAPABILITY_ID } from "@rig/contracts";
762
+ import { defineCapability } from "@rig/core/capability";
763
+ import {
764
+ GITHUB_PROVIDER_CAPABILITY_ID,
765
+ ISSUE_TRIAGE,
766
+ RUN_IDENTITY_ENV
767
+ } from "@rig/contracts";
768
+ var IssueTriageCap = defineCapability(ISSUE_TRIAGE);
769
+ var issueTriageService = async () => {
770
+ const { runIssueAnalysisTriage: runIssueAnalysisTriage2 } = await Promise.resolve().then(() => (init_triage_run(), exports_triage_run));
771
+ return {
772
+ runTriage: (input) => runIssueAnalysisTriage2({ projectRoot: input.projectRoot, ...input.reason !== undefined ? { reason: input.reason } : {} })
773
+ };
774
+ };
775
+ var RunIdentityEnvCap = defineCapability(RUN_IDENTITY_ENV);
776
+ var runIdentityEnvService = async () => {
777
+ const {
778
+ resolveIdentityEnv: resolveIdentityEnv2,
779
+ applyIdentityEnv: applyIdentityEnv2,
780
+ identityFilterFromEnv: identityFilterFromEnv2,
781
+ stripRunChildCredentialEnv: stripRunChildCredentialEnv2,
782
+ resolveRigIdentity: resolveRigIdentity2,
783
+ resolveRigIdentityFilter: resolveRigIdentityFilter2
784
+ } = await Promise.resolve().then(() => (init_identity_env(), exports_identity_env));
785
+ return {
786
+ resolveIdentityEnv: resolveIdentityEnv2,
787
+ applyIdentityEnv: applyIdentityEnv2,
788
+ identityFilterFromEnv: identityFilterFromEnv2,
789
+ stripRunChildCredentialEnv: stripRunChildCredentialEnv2,
790
+ resolveRigIdentity: resolveRigIdentity2,
791
+ resolveRigIdentityFilter: resolveRigIdentityFilter2
792
+ };
793
+ };
1197
794
  var GITHUB_PROVIDER_PLUGIN_NAME = "@rig/github-provider-plugin";
795
+ function parseGitHubSlugFromRemote(remoteUrl) {
796
+ const match = remoteUrl.trim().match(/github\.com[:/]([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i);
797
+ return match ? `${match[1]}/${match[2]}` : null;
798
+ }
799
+ function detectGitHubRepoSlug(projectRoot) {
800
+ try {
801
+ const result = spawnSync("git", ["-C", projectRoot, "remote", "get-url", "origin"], {
802
+ encoding: "utf8",
803
+ timeout: 5000
804
+ });
805
+ if (result.status !== 0 || result.error)
806
+ return null;
807
+ return parseGitHubSlugFromRemote(result.stdout.trim());
808
+ } catch {
809
+ return null;
810
+ }
811
+ }
812
+ function githubConfigDefaults(context) {
813
+ const slug = context.repoSlug ?? detectGitHubRepoSlug(context.projectRoot);
814
+ if (!slug)
815
+ return null;
816
+ const [owner, repo] = slug.split("/", 2);
817
+ if (!owner || !repo)
818
+ return null;
819
+ return {
820
+ project: { name: slug, repo: slug },
821
+ taskSource: { kind: "github-issues", owner, repo, state: "open" },
822
+ github: { issueUpdates: "lifecycle", projects: { enabled: false } },
823
+ standard: {
824
+ githubWorkspaceId: slug,
825
+ githubUserId: process.env.RIG_GITHUB_USER_ID ?? "server-selected-user"
826
+ },
827
+ issueAnalysis: { enabled: true, harness: "pi", mode: "continuous" }
828
+ };
829
+ }
1198
830
  var githubProviderPlugin = definePlugin({
1199
831
  name: GITHUB_PROVIDER_PLUGIN_NAME,
1200
832
  version: "0.0.0-alpha.1",
@@ -1205,43 +837,31 @@ var githubProviderPlugin = definePlugin({
1205
837
  title: "GitHub SCM provider",
1206
838
  description: "Resolve GitHub credentials for the configured SCM provider.",
1207
839
  run: async () => (await Promise.resolve().then(() => (init_service(), exports_service))).githubProviderService
1208
- }
1209
- ]
840
+ },
841
+ IssueTriageCap.provide(issueTriageService, { title: "GitHub issue-analysis triage" }),
842
+ RunIdentityEnvCap.provide(runIdentityEnvService, {
843
+ title: "GitHub/Rig run identity env",
844
+ description: "Resolve GitHub/Rig identity, hydrate public identity env, and strip run-scoped credential tokens."
845
+ })
846
+ ],
847
+ config: { defaults: githubConfigDefaults }
1210
848
  }
1211
849
  });
1212
850
  function createGitHubProviderPlugin() {
1213
851
  return githubProviderPlugin;
1214
852
  }
1215
853
  export {
1216
- updateIssueProjectStatus,
1217
- saveGitHubTokenForProject,
1218
854
  runIssueAnalysisTriage,
1219
- resolveProjectStatusField,
1220
- resolveGitHubAuthStatus,
1221
- resolveGitHubAuthStateFile,
1222
855
  renderIssueAnalysisPrompt,
1223
- probeGitHubRepository,
1224
- pollGitHubDeviceFlow,
1225
856
  parseIssueAnalysisResult,
1226
- listGitHubProjects,
1227
857
  issueAnalysisEnabled,
1228
858
  githubProviderPlugin,
1229
- fetchGitHubUserInfo,
1230
- ensureIssueProjectItem,
1231
- createStateGitHubCredentialProvider,
1232
859
  createPiIssueAnalyzer,
1233
860
  createIssueAnalysisWriteBack,
1234
861
  createIssueAnalysisService,
1235
862
  createGitHubProviderPlugin,
1236
- createGitHubCredentialProvider,
1237
- createGitHubAuthStoreFromStateFile,
1238
- createGitHubAuthStore,
1239
- createEnvGitHubCredentialProvider,
1240
863
  createDefaultPiIssueAnalysisCommandRunner,
1241
864
  createContinuousIssueAnalysisRunner,
1242
865
  createConfiguredIssueAnalysisRunner,
1243
- copyGitHubAuthStateToLocalProjectRoot,
1244
- checkGitHubRepoPermissions,
1245
- beginGitHubDeviceFlow,
1246
866
  GITHUB_PROVIDER_PLUGIN_NAME
1247
867
  };