delqhi/sin-github-issues
0
1import { runCommand, runJsonCommand } from './command.js';2import { recordAuditEvent } from './audit.js';3 4export async function getProfile() {5 const profile = await runJsonCommand<{6 login: string;7 id: number;8 name: string | null;9 company: string | null;10 blog: string | null;11 location: string | null;12 bio: string | null;13 twitter_username: string | null;14 hireable: boolean | null;15 public_repos: number;16 followers: number;17 following: number;18 html_url: string;19 }>('gh', ['api', 'user']);20 return { ok: true, profile };21}22 23export async function updateProfile(input: {24 name?: string;25 company?: string;26 blog?: string;27 location?: string;28 bio?: string;29 twitter_username?: string;30 hireable?: boolean;31}) {32 const args = ['api', 'user', '--method', 'PATCH'];33 if (input.name !== undefined) args.push('-f', `name=${input.name}`);34 if (input.company !== undefined) args.push('-f', `company=${input.company}`);35 if (input.blog !== undefined) args.push('-f', `blog=${input.blog}`);36 if (input.location !== undefined) args.push('-f', `location=${input.location}`);37 if (input.bio !== undefined) args.push('-f', `bio=${input.bio}`);38 if (input.twitter_username !== undefined) args.push('-f', `twitter_username=${input.twitter_username}`);39 if (input.hireable !== undefined) args.push('-F', `hireable=${input.hireable}`);40 const result = await runJsonCommand<object>('gh', args);41 await recordAuditEvent({ kind: 'profile.update', result });42 return { ok: true, profile: result };43}44 45export async function listPinnedRepos() {46 const query = `47 query {48 user(login: "Delqhi") {49 pinnedItems(first: 6, types: REPOSITORY) {50 nodes {51 ... on Repository {52 id53 name54 description55 url56 isFork57 stargazerCount58 primaryLanguage { name }59 }60 }61 }62 }63 }64 `.replace(/\s+/g, ' ');65 const response = await runJsonCommand<{ data: { user: { pinnedItems: { nodes: Array<{ id: string; name: string; description: string | null; url: string; isFork: boolean; stargazerCount: number; primaryLanguage: { name: string } | null }> } } } }>('gh', ['api', 'graphql', '-f', `query=${query}`]);66 const nodes = response.data?.user?.pinnedItems?.nodes ?? [];67 return { ok: true, pinned: nodes };68}69 70export async function setPinnedRepos(repoIds?: string[]) {71 const current = await listPinnedRepos();72 const existingIds = new Set(current.pinned.map((r) => r.id));73 for (const existingId of existingIds) {74 try {75 await runCommand('gh', ['api', 'graphql', '-f', `query=mutation { unpinRepository(input: {repositoryId: "${existingId}"}) { repository { name } } }`], undefined);76 } catch {}77 }78 const desiredIds = repoIds ?? [];79 for (const repoId of desiredIds) {80 await runCommand('gh', ['api', 'graphql', '-f', `query=mutation { pinRepository(input: {repositoryId: "${repoId}"}) { repository { name } } }`], undefined);81 }82 await recordAuditEvent({ kind: 'profile.pinned_repos.set', result: { repoIds: desiredIds } });83 return { ok: true, pinned: desiredIds };84}85 86export async function updateRepoMetadata(input: { repo: string; description?: string; homepage?: string; topics?: string[]; visibility?: 'public' | 'private' }) {87 const args = ['api', `repos/${input.repo}`, '--method', 'PATCH'];88 if (input.description !== undefined) args.push('-f', `description=${input.description}`);89 if (input.homepage !== undefined) args.push('-f', `homepage=${input.homepage}`);90 if (input.visibility !== undefined) args.push('-f', `visibility=${input.visibility}`);91 await runCommand('gh', args, undefined);92 if (input.topics && input.topics.length > 0) {93 const topicArgs = ['api', `repos/${input.repo}/topics`, '--method', 'PUT', '-H', 'Accept: application/vnd.github+json'];94 for (const topic of input.topics) {95 topicArgs.push('-F', `names[]=${topic}`);96 }97 await runCommand('gh', topicArgs, undefined);98 }99 await recordAuditEvent({ kind: 'repo.metadata.update', result: input });100 return { ok: true, repo: input.repo };101}102 103export async function triggerQuickdraw(repo?: string) {104 const target = repo ?? 'Delqhi/Delqhi';105 const issue = await runJsonCommand<{ number: number; html_url: string }>('gh', ['issue', 'create', '--repo', target, '--title', 'Quickdraw profile check', '--body', 'Triggering Quickdraw achievement via issue lifecycle.']);106 await runCommand('gh', ['issue', 'close', String(issue.number), '--repo', target, '--comment', 'Closed for Quickdraw verification.'], undefined);107 await recordAuditEvent({ kind: 'achievement.quickdraw.trigger', result: { repo: target, issueNumber: issue.number } });108 return { ok: true, repo: target, issueNumber: issue.number, issueUrl: issue.html_url };109}110 111export async function quickdrawStatus() {112 const issues = await runJsonCommand<Array<{ number: number; state: string; closed_at: string | null }>>('gh', ['issue', 'list', '--repo', 'Delqhi/Delqhi', '--state', 'all', '--limit', '10', '--json', 'number,state,closed_at']);113 const closed = issues.filter((i) => i.state === 'closed' && i.closed_at).length;114 return { ok: true, totalClosed: closed, likelyQuickdraw: closed >= 1 };115}116 