gh-manager-cli 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1595 @@
1
+ #!/usr/bin/env node
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __commonJS = (cb, mod) => function __require() {
4
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
5
+ };
6
+
7
+ // package.json
8
+ var require_package = __commonJS({
9
+ "package.json"(exports, module) {
10
+ module.exports = {
11
+ name: "gh-manager-cli",
12
+ version: "1.2.0",
13
+ private: false,
14
+ description: "Interactive CLI to manage your GitHub repos (personal) with Ink",
15
+ license: "MIT",
16
+ main: "dist/index.js",
17
+ bin: {
18
+ "gh-manager": "dist/index.js"
19
+ },
20
+ files: [
21
+ "dist/",
22
+ "README.md",
23
+ "LICENSE",
24
+ "CHANGELOG.md"
25
+ ],
26
+ keywords: [
27
+ "github",
28
+ "cli",
29
+ "repository",
30
+ "management",
31
+ "terminal",
32
+ "tui",
33
+ "react-ink"
34
+ ],
35
+ type: "module",
36
+ scripts: {
37
+ build: "tsup",
38
+ "build:binaries": "npm run build && pkg dist/index.js --targets node18-linux-x64,node18-macos-x64,node18-windows-x64 --out-path ./binaries",
39
+ dev: "tsup --watch",
40
+ start: "node dist/index.js",
41
+ prepublishOnly: "pnpm run build"
42
+ },
43
+ engines: {
44
+ node: ">=18"
45
+ },
46
+ dependencies: {
47
+ "@octokit/graphql": "^9.0.1",
48
+ chalk: "^5.6.0",
49
+ dotenv: "^17.2.1",
50
+ "env-paths": "^3.0.0",
51
+ ink: "^6.2.3",
52
+ "ink-spinner": "^5.0.0",
53
+ "ink-text-input": "^6.0.0",
54
+ react: "^19.1.1"
55
+ },
56
+ devDependencies: {
57
+ "@semantic-release/changelog": "^6.0.3",
58
+ "@semantic-release/git": "^10.0.1",
59
+ "@types/node": "^24.3.0",
60
+ "@types/react": "^19.1.12",
61
+ pkg: "^5.8.1",
62
+ "semantic-release": "^24.2.7",
63
+ tsup: "^8.5.0",
64
+ typescript: "^5.9.2"
65
+ },
66
+ repository: {
67
+ type: "git",
68
+ url: "https://github.com/wiiiimm/gh-manager-cli.git"
69
+ },
70
+ bugs: {
71
+ url: "https://github.com/wiiiimm/gh-manager-cli/issues"
72
+ },
73
+ homepage: "https://github.com/wiiiimm/gh-manager-cli#readme",
74
+ release: {
75
+ branches: [
76
+ "main"
77
+ ],
78
+ plugins: [
79
+ "@semantic-release/commit-analyzer",
80
+ "@semantic-release/release-notes-generator",
81
+ "@semantic-release/changelog",
82
+ "@semantic-release/npm",
83
+ [
84
+ "@semantic-release/git",
85
+ {
86
+ assets: [
87
+ "CHANGELOG.md",
88
+ "package.json"
89
+ ],
90
+ message: "chore(release): ${nextRelease.version}\n\n${nextRelease.notes}"
91
+ }
92
+ ],
93
+ [
94
+ "@semantic-release/github",
95
+ {
96
+ assets: [
97
+ {
98
+ path: "binaries/gh-manager-linux-x64/gh-manager-linux-x64",
99
+ name: "gh-manager-linux-x64",
100
+ label: "Linux x64 Binary"
101
+ },
102
+ {
103
+ path: "binaries/gh-manager-macos-x64/gh-manager-macos-x64",
104
+ name: "gh-manager-macos-x64",
105
+ label: "macOS x64 Binary"
106
+ },
107
+ {
108
+ path: "binaries/gh-manager-windows-x64/gh-manager-windows-x64.exe",
109
+ name: "gh-manager-windows-x64.exe",
110
+ label: "Windows x64 Binary"
111
+ }
112
+ ]
113
+ }
114
+ ]
115
+ ]
116
+ }
117
+ };
118
+ }
119
+ });
120
+
121
+ // src/index.tsx
122
+ import { render, Box as Box3, Text as Text3 } from "ink";
123
+ import "dotenv/config";
124
+
125
+ // src/ui/App.tsx
126
+ import { useEffect as useEffect2, useMemo as useMemo2, useState as useState2 } from "react";
127
+ import { Box as Box2, Text as Text2, useApp as useApp2, useStdout as useStdout2, useInput as useInput2 } from "ink";
128
+ import TextInput2 from "ink-text-input";
129
+
130
+ // src/config.ts
131
+ import fs from "fs";
132
+ import path from "path";
133
+ import envPaths from "env-paths";
134
+ var paths = envPaths("gh-manager-cli");
135
+ var configDir = paths.config;
136
+ var configFile = path.join(configDir, "config.json");
137
+ function readConfig() {
138
+ try {
139
+ const data = fs.readFileSync(configFile, "utf8");
140
+ const json = JSON.parse(data);
141
+ return json;
142
+ } catch {
143
+ return {};
144
+ }
145
+ }
146
+ function writeConfig(cfg) {
147
+ fs.mkdirSync(configDir, { recursive: true });
148
+ const body = JSON.stringify(cfg, null, 2);
149
+ fs.writeFileSync(configFile, body, "utf8");
150
+ if (process.platform !== "win32") {
151
+ try {
152
+ fs.chmodSync(configFile, 384);
153
+ } catch {
154
+ }
155
+ }
156
+ }
157
+ function getTokenFromEnv() {
158
+ return process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
159
+ }
160
+ function getStoredToken() {
161
+ const cfg = readConfig();
162
+ return cfg.token;
163
+ }
164
+ function storeToken(token) {
165
+ const existing = readConfig();
166
+ writeConfig({ ...existing, token, tokenVersion: 1 });
167
+ }
168
+ function clearStoredToken() {
169
+ const existing = readConfig();
170
+ const { token, tokenVersion, ...rest } = existing;
171
+ writeConfig({ ...rest });
172
+ }
173
+ function getUIPrefs() {
174
+ const cfg = readConfig();
175
+ return cfg.ui || {};
176
+ }
177
+ function storeUIPrefs(patch) {
178
+ const existing = readConfig();
179
+ const mergedUI = { ...existing.ui || {}, ...patch };
180
+ writeConfig({ ...existing, ui: mergedUI });
181
+ }
182
+
183
+ // src/github.ts
184
+ import { graphql as makeGraphQL } from "@octokit/graphql";
185
+ function makeClient(token) {
186
+ return makeGraphQL.defaults({
187
+ headers: { authorization: `token ${token}` }
188
+ });
189
+ }
190
+ async function getViewerLogin(client) {
191
+ const query = (
192
+ /* GraphQL */
193
+ `
194
+ query ViewerLogin {
195
+ viewer {
196
+ login
197
+ }
198
+ }
199
+ `
200
+ );
201
+ const res = await client(query);
202
+ return res.viewer.login;
203
+ }
204
+ async function fetchViewerReposPage(client, first, after, orderBy, includeForkTracking = true) {
205
+ const sortField = orderBy?.field || "UPDATED_AT";
206
+ const sortDirection = orderBy?.direction || "DESC";
207
+ const query = (
208
+ /* GraphQL */
209
+ `
210
+ query ViewerRepos(
211
+ $first: Int!
212
+ $after: String
213
+ $sortField: RepositoryOrderField!
214
+ $sortDirection: OrderDirection!
215
+ ) {
216
+ rateLimit {
217
+ limit
218
+ remaining
219
+ resetAt
220
+ }
221
+ viewer {
222
+ repositories(
223
+ ownerAffiliations: OWNER
224
+ first: $first
225
+ after: $after
226
+ orderBy: { field: $sortField, direction: $sortDirection }
227
+ ) {
228
+ totalCount
229
+ pageInfo {
230
+ endCursor
231
+ hasNextPage
232
+ }
233
+ nodes {
234
+ id
235
+ name
236
+ nameWithOwner
237
+ description
238
+ visibility
239
+ isPrivate
240
+ isFork
241
+ isArchived
242
+ stargazerCount
243
+ forkCount
244
+ primaryLanguage {
245
+ name
246
+ color
247
+ }
248
+ updatedAt
249
+ pushedAt
250
+ diskUsage
251
+ ${includeForkTracking ? `
252
+ parent {
253
+ nameWithOwner
254
+ defaultBranchRef {
255
+ target {
256
+ ... on Commit {
257
+ history(first: 0) {
258
+ totalCount
259
+ }
260
+ }
261
+ }
262
+ }
263
+ }
264
+ defaultBranchRef {
265
+ target {
266
+ ... on Commit {
267
+ history(first: 0) {
268
+ totalCount
269
+ }
270
+ }
271
+ }
272
+ }` : `
273
+ parent {
274
+ nameWithOwner
275
+ }`}
276
+ }
277
+ }
278
+ }
279
+ }
280
+ `
281
+ );
282
+ const res = await client(query, {
283
+ first,
284
+ after: after ?? null,
285
+ sortField,
286
+ sortDirection
287
+ });
288
+ const data = res.viewer.repositories;
289
+ return {
290
+ nodes: data.nodes,
291
+ endCursor: data.pageInfo.endCursor,
292
+ hasNextPage: data.pageInfo.hasNextPage,
293
+ totalCount: data.totalCount,
294
+ rateLimit: res.rateLimit
295
+ };
296
+ }
297
+ async function deleteRepositoryRest(token, owner, repo) {
298
+ const url = `https://api.github.com/repos/${owner}/${repo}`;
299
+ const res = await fetch(url, {
300
+ method: "DELETE",
301
+ headers: {
302
+ "Authorization": `token ${token}`,
303
+ "Accept": "application/vnd.github+json",
304
+ "User-Agent": "gh-manager-cli"
305
+ }
306
+ });
307
+ if (res.status === 204) return;
308
+ let msg = `GitHub REST delete failed (status ${res.status})`;
309
+ try {
310
+ const body = await res.json();
311
+ if (body && body.message) msg += `: ${body.message}`;
312
+ } catch {
313
+ }
314
+ throw new Error(msg);
315
+ }
316
+ async function archiveRepositoryById(client, repositoryId) {
317
+ const mutation = (
318
+ /* GraphQL */
319
+ `
320
+ mutation ArchiveRepo($repositoryId: ID!) {
321
+ archiveRepository(input: { repositoryId: $repositoryId }) {
322
+ clientMutationId
323
+ }
324
+ }
325
+ `
326
+ );
327
+ await client(mutation, { repositoryId });
328
+ }
329
+ async function unarchiveRepositoryById(client, repositoryId) {
330
+ const mutation = (
331
+ /* GraphQL */
332
+ `
333
+ mutation UnarchiveRepo($repositoryId: ID!) {
334
+ unarchiveRepository(input: { repositoryId: $repositoryId }) {
335
+ clientMutationId
336
+ }
337
+ }
338
+ `
339
+ );
340
+ await client(mutation, { repositoryId });
341
+ }
342
+ async function syncForkWithUpstream(token, owner, repo, branch = "main") {
343
+ const url = `https://api.github.com/repos/${owner}/${repo}/merge-upstream`;
344
+ const res = await fetch(url, {
345
+ method: "POST",
346
+ headers: {
347
+ "Authorization": `token ${token}`,
348
+ "Accept": "application/vnd.github+json",
349
+ "User-Agent": "gh-manager-cli"
350
+ },
351
+ body: JSON.stringify({ branch })
352
+ });
353
+ if (res.status === 204) {
354
+ return { message: "Already up-to-date", merge_type: "none", base_branch: branch };
355
+ }
356
+ if (res.status === 200) {
357
+ const body = await res.json();
358
+ return body;
359
+ }
360
+ let msg = `Fork sync failed (status ${res.status})`;
361
+ try {
362
+ const body = await res.json();
363
+ if (body && body.message) {
364
+ msg += `: ${body.message}`;
365
+ if (res.status === 409) {
366
+ msg += " (conflicts detected - manual merge required)";
367
+ }
368
+ }
369
+ } catch {
370
+ }
371
+ throw new Error(msg);
372
+ }
373
+
374
+ // src/ui/RepoList.tsx
375
+ import { useEffect, useMemo, useState } from "react";
376
+ import { Box, Text, useApp, useInput, useStdout } from "ink";
377
+ import TextInput from "ink-text-input";
378
+ import chalk from "chalk";
379
+ import { exec } from "child_process";
380
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
381
+ var PAGE_SIZE = process.env.GH_MANAGER_DEV === "1" || process.env.NODE_ENV === "development" ? 5 : 15;
382
+ function SlowSpinner() {
383
+ const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
384
+ const [frame, setFrame] = useState(0);
385
+ useEffect(() => {
386
+ const timer = setInterval(() => {
387
+ setFrame((f) => (f + 1) % frames.length);
388
+ }, 500);
389
+ return () => clearInterval(timer);
390
+ }, [frames.length]);
391
+ return /* @__PURE__ */ jsx(Text, { children: frames[frame] });
392
+ }
393
+ function truncate(str, max = 80) {
394
+ if (str.length <= max) return str;
395
+ return str.slice(0, Math.max(0, max - 1)) + "\u2026";
396
+ }
397
+ function formatDate(dateStr) {
398
+ const date = new Date(dateStr);
399
+ const now = /* @__PURE__ */ new Date();
400
+ const diffMs = now.getTime() - date.getTime();
401
+ const diffDays = Math.floor(diffMs / (1e3 * 60 * 60 * 24));
402
+ if (diffDays === 0) return "today";
403
+ if (diffDays === 1) return "yesterday";
404
+ if (diffDays < 7) return `${diffDays} days ago`;
405
+ if (diffDays < 30) return `${Math.floor(diffDays / 7)} weeks ago`;
406
+ if (diffDays < 365) return `${Math.floor(diffDays / 30)} months ago`;
407
+ return `${Math.floor(diffDays / 365)} years ago`;
408
+ }
409
+ function RepoRow({ repo, selected, index, maxWidth, spacingLines, dim, forkTracking }) {
410
+ const langName = repo.primaryLanguage?.name || "";
411
+ const langColor = repo.primaryLanguage?.color || "#666666";
412
+ const hasCommitData = repo.isFork && repo.parent && repo.defaultBranchRef && repo.parent.defaultBranchRef && repo.parent.defaultBranchRef.target?.history && repo.defaultBranchRef.target?.history;
413
+ const commitsBehind = hasCommitData ? repo.parent.defaultBranchRef.target.history.totalCount - repo.defaultBranchRef.target.history.totalCount : 0;
414
+ const showCommitsBehind = forkTracking && hasCommitData;
415
+ let line1 = "";
416
+ const numColor = selected ? chalk.cyan : chalk.gray;
417
+ const nameColor = selected ? chalk.cyan.bold : chalk.white;
418
+ line1 += numColor(`${String(index).padStart(3, " ")}.`);
419
+ line1 += nameColor(` ${repo.nameWithOwner}`);
420
+ if (repo.isPrivate) line1 += chalk.yellow(" Private");
421
+ if (repo.isArchived) line1 += " " + chalk.bgGray.whiteBright(" Archived ") + " ";
422
+ if (repo.isFork && repo.parent) {
423
+ line1 += chalk.blue(` Fork of ${repo.parent.nameWithOwner}`);
424
+ if (showCommitsBehind) {
425
+ if (commitsBehind > 0) {
426
+ line1 += chalk.yellow(` (${commitsBehind} behind)`);
427
+ } else {
428
+ line1 += chalk.green(` (0 behind)`);
429
+ }
430
+ }
431
+ }
432
+ let line2 = " ";
433
+ const metaColor = selected ? chalk.white : chalk.gray;
434
+ if (langName) line2 += chalk.hex(langColor)("\u25CF ") + metaColor(`${langName} `);
435
+ line2 += metaColor(`\u2605 ${repo.stargazerCount} \u2442 ${repo.forkCount} Updated ${formatDate(repo.updatedAt)}`);
436
+ const line3 = repo.description ? ` ${truncate(repo.description, Math.max(30, maxWidth - 10))}` : null;
437
+ let fullText = line1 + "\n" + line2;
438
+ if (line3) fullText += "\n" + metaColor(line3);
439
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", backgroundColor: selected ? "gray" : void 0, children: [
440
+ /* @__PURE__ */ jsx(Text, { children: dim ? chalk.dim(fullText) : fullText }),
441
+ spacingLines > 0 && /* @__PURE__ */ jsx(Box, { height: spacingLines, children: /* @__PURE__ */ jsx(Text, { children: " " }) })
442
+ ] });
443
+ }
444
+ function RepoList({ token, maxVisibleRows }) {
445
+ const { exit } = useApp();
446
+ const { stdout } = useStdout();
447
+ const client = useMemo(() => makeClient(token), [token]);
448
+ const terminalWidth = stdout?.columns ?? 80;
449
+ const availableHeight = maxVisibleRows ?? 20;
450
+ const [items, setItems] = useState([]);
451
+ const [cursor, setCursor] = useState(0);
452
+ const [endCursor, setEndCursor] = useState(null);
453
+ const [hasNextPage, setHasNextPage] = useState(false);
454
+ const [totalCount, setTotalCount] = useState(0);
455
+ const [loading, setLoading] = useState(true);
456
+ const [sortingLoading, setSortingLoading] = useState(false);
457
+ const [refreshing, setRefreshing] = useState(false);
458
+ const [loadingMore, setLoadingMore] = useState(false);
459
+ const [error, setError] = useState(null);
460
+ const [rateLimit, setRateLimit] = useState(void 0);
461
+ const [prevRateLimit, setPrevRateLimit] = useState(void 0);
462
+ const [density, setDensity] = useState(2);
463
+ const [prefsLoaded, setPrefsLoaded] = useState(false);
464
+ const [deleteMode, setDeleteMode] = useState(false);
465
+ const [deleteTarget, setDeleteTarget] = useState(null);
466
+ const [deleteCode, setDeleteCode] = useState("");
467
+ const [typedCode, setTypedCode] = useState("");
468
+ const [deleting, setDeleting] = useState(false);
469
+ const [deleteError, setDeleteError] = useState(null);
470
+ const [deleteConfirmStage, setDeleteConfirmStage] = useState(false);
471
+ const [confirmFocus, setConfirmFocus] = useState("delete");
472
+ const [archiveMode, setArchiveMode] = useState(false);
473
+ const [archiveTarget, setArchiveTarget] = useState(null);
474
+ const [archiving, setArchiving] = useState(false);
475
+ const [archiveError, setArchiveError] = useState(null);
476
+ const [archiveFocus, setArchiveFocus] = useState("confirm");
477
+ const [syncMode, setSyncMode] = useState(false);
478
+ const [syncTarget, setSyncTarget] = useState(null);
479
+ const [syncing, setSyncing] = useState(false);
480
+ const [syncError, setSyncError] = useState(null);
481
+ const [syncFocus, setSyncFocus] = useState("confirm");
482
+ function closeArchiveModal() {
483
+ setArchiveMode(false);
484
+ setArchiveTarget(null);
485
+ setArchiving(false);
486
+ setArchiveError(null);
487
+ setArchiveFocus("confirm");
488
+ }
489
+ function closeSyncModal() {
490
+ setSyncMode(false);
491
+ setSyncTarget(null);
492
+ setSyncing(false);
493
+ setSyncError(null);
494
+ setSyncFocus("confirm");
495
+ }
496
+ function cancelDeleteModal() {
497
+ setDeleteMode(false);
498
+ setDeleteTarget(null);
499
+ setTypedCode("");
500
+ setDeleteError(null);
501
+ setDeleteConfirmStage(false);
502
+ setDeleting(false);
503
+ setConfirmFocus("delete");
504
+ }
505
+ async function confirmDeleteNow() {
506
+ if (!deleteTarget) return;
507
+ try {
508
+ setDeleting(true);
509
+ const [owner, repo] = (deleteTarget.nameWithOwner || "").split("/");
510
+ await deleteRepositoryRest(token, owner, repo);
511
+ setItems((prev) => prev.filter((r) => r.id !== deleteTarget.id));
512
+ setTotalCount((c) => Math.max(0, c - 1));
513
+ setDeleteMode(false);
514
+ setDeleteTarget(null);
515
+ setTypedCode("");
516
+ setDeleteError(null);
517
+ setDeleting(false);
518
+ setDeleteConfirmStage(false);
519
+ setCursor((c) => Math.max(0, Math.min(c, filteredAndSorted.length - 2)));
520
+ } catch (e) {
521
+ setDeleting(false);
522
+ setDeleteError("Failed to delete repository. Ensure delete_repo scope and admin permissions.");
523
+ }
524
+ }
525
+ const [filter, setFilter] = useState("");
526
+ const [filterMode, setFilterMode] = useState(false);
527
+ const [sortKey, setSortKey] = useState("updated");
528
+ const [sortDir, setSortDir] = useState("desc");
529
+ const [forkTracking, setForkTracking] = useState(true);
530
+ const sortFieldMap = {
531
+ "updated": "UPDATED_AT",
532
+ "pushed": "PUSHED_AT",
533
+ "name": "NAME",
534
+ "stars": "STARGAZERS"
535
+ };
536
+ const fetchPage = async (after, reset = false, isSortChange = false, overrideForkTracking) => {
537
+ if (isSortChange) {
538
+ setSortingLoading(true);
539
+ } else if (after && !reset) {
540
+ setLoadingMore(true);
541
+ } else {
542
+ setLoading(true);
543
+ }
544
+ try {
545
+ const orderBy = {
546
+ field: sortFieldMap[sortKey],
547
+ direction: sortDir.toUpperCase()
548
+ };
549
+ const page = await fetchViewerReposPage(client, PAGE_SIZE, after ?? null, orderBy, overrideForkTracking ?? forkTracking);
550
+ setItems((prev) => reset || !after ? page.nodes : [...prev, ...page.nodes]);
551
+ setEndCursor(page.endCursor);
552
+ setHasNextPage(page.hasNextPage);
553
+ setTotalCount(page.totalCount);
554
+ if (page.rateLimit && rateLimit) {
555
+ setPrevRateLimit(rateLimit.remaining);
556
+ }
557
+ setRateLimit(page.rateLimit);
558
+ setError(null);
559
+ } catch (e) {
560
+ setError("Failed to load repositories. Check network or token.");
561
+ } finally {
562
+ setLoading(false);
563
+ setSortingLoading(false);
564
+ setRefreshing(false);
565
+ setLoadingMore(false);
566
+ }
567
+ };
568
+ useEffect(() => {
569
+ const ui = getUIPrefs();
570
+ if (ui.density !== void 0) setDensity(ui.density);
571
+ if (ui.sortKey && ["updated", "pushed", "name", "stars"].includes(ui.sortKey)) {
572
+ setSortKey(ui.sortKey);
573
+ }
574
+ if (ui.sortDir && (ui.sortDir === "asc" || ui.sortDir === "desc")) {
575
+ setSortDir(ui.sortDir);
576
+ }
577
+ if (ui.forkTracking !== void 0) setForkTracking(ui.forkTracking);
578
+ else setForkTracking(true);
579
+ setPrefsLoaded(true);
580
+ }, []);
581
+ useEffect(() => {
582
+ if (!prefsLoaded) return;
583
+ fetchPage();
584
+ }, [client, prefsLoaded]);
585
+ useEffect(() => {
586
+ if (items.length > 0) {
587
+ fetchPage(null, true, true);
588
+ }
589
+ }, [sortKey, sortDir]);
590
+ useInput((input, key) => {
591
+ if (deleteMode) {
592
+ if (key.escape || input && input.toUpperCase() === "C") {
593
+ cancelDeleteModal();
594
+ return;
595
+ }
596
+ if (deleteConfirmStage) {
597
+ if (key.leftArrow) {
598
+ setConfirmFocus("delete");
599
+ return;
600
+ }
601
+ if (key.rightArrow) {
602
+ setConfirmFocus("cancel");
603
+ return;
604
+ }
605
+ if (key.return) {
606
+ if (confirmFocus === "delete") confirmDeleteNow();
607
+ else cancelDeleteModal();
608
+ return;
609
+ }
610
+ if (input && input.toUpperCase() === "Y") {
611
+ confirmDeleteNow();
612
+ return;
613
+ }
614
+ }
615
+ return;
616
+ }
617
+ if (archiveMode) {
618
+ if (key.escape || input && input.toUpperCase() === "C") {
619
+ closeArchiveModal();
620
+ return;
621
+ }
622
+ if (key.leftArrow) {
623
+ setArchiveFocus("confirm");
624
+ return;
625
+ }
626
+ if (key.rightArrow) {
627
+ setArchiveFocus("cancel");
628
+ return;
629
+ }
630
+ if (key.return || input && input.toUpperCase() === "Y") {
631
+ if (archiveFocus === "cancel") {
632
+ closeArchiveModal();
633
+ return;
634
+ }
635
+ if (!archiveTarget) return;
636
+ (async () => {
637
+ try {
638
+ setArchiving(true);
639
+ const isArchived = archiveTarget.isArchived;
640
+ const id = archiveTarget.id;
641
+ if (isArchived) await unarchiveRepositoryById(client, id);
642
+ else await archiveRepositoryById(client, id);
643
+ setItems((prev) => prev.map((r) => r.id === archiveTarget.id ? { ...r, isArchived: !isArchived } : r));
644
+ closeArchiveModal();
645
+ } catch (e) {
646
+ setArchiving(false);
647
+ setArchiveError("Failed to update archive state. Check permissions.");
648
+ }
649
+ })();
650
+ return;
651
+ }
652
+ return;
653
+ }
654
+ if (syncMode) {
655
+ if (key.escape || input && input.toUpperCase() === "C") {
656
+ closeSyncModal();
657
+ return;
658
+ }
659
+ if (key.leftArrow) {
660
+ setSyncFocus("confirm");
661
+ return;
662
+ }
663
+ if (key.rightArrow) {
664
+ setSyncFocus("cancel");
665
+ return;
666
+ }
667
+ if (key.return || input && input.toUpperCase() === "Y") {
668
+ if (syncFocus === "cancel") {
669
+ closeSyncModal();
670
+ return;
671
+ }
672
+ if (!syncTarget) return;
673
+ (async () => {
674
+ try {
675
+ setSyncing(true);
676
+ const [owner, repo] = syncTarget.nameWithOwner.split("/");
677
+ const result = await syncForkWithUpstream(token, owner, repo);
678
+ setItems((prev) => prev.map((r) => {
679
+ if (r.id === syncTarget.id && r.parent && r.defaultBranchRef?.target?.history && r.parent.defaultBranchRef?.target?.history) {
680
+ return {
681
+ ...r,
682
+ defaultBranchRef: {
683
+ ...r.defaultBranchRef,
684
+ target: {
685
+ ...r.defaultBranchRef.target,
686
+ history: {
687
+ totalCount: r.parent.defaultBranchRef.target.history.totalCount
688
+ }
689
+ }
690
+ }
691
+ };
692
+ }
693
+ return r;
694
+ }));
695
+ closeSyncModal();
696
+ } catch (e) {
697
+ setSyncing(false);
698
+ setSyncError(e.message || "Failed to sync fork. Check permissions and network.");
699
+ }
700
+ })();
701
+ return;
702
+ }
703
+ return;
704
+ }
705
+ if (filterMode) {
706
+ if (key.escape) {
707
+ setFilterMode(false);
708
+ return;
709
+ }
710
+ return;
711
+ }
712
+ if (input && input.toUpperCase() === "Q") {
713
+ try {
714
+ const seq = "\x1B[2J\x1B[3J\x1B[H";
715
+ if (stdout && typeof stdout.write === "function") stdout.write(seq);
716
+ else if (typeof process.stdout.write === "function") process.stdout.write(seq);
717
+ } catch {
718
+ }
719
+ exit();
720
+ return;
721
+ }
722
+ if (key.downArrow) setCursor((c) => Math.min(c + 1, items.length - 1));
723
+ if (key.upArrow) setCursor((c) => Math.max(c - 1, 0));
724
+ if (key.pageDown) setCursor((c) => Math.min(c + 10, items.length - 1));
725
+ if (key.pageUp) setCursor((c) => Math.max(c - 10, 0));
726
+ if (key.return) {
727
+ const repo = filteredAndSorted[cursor];
728
+ if (repo) openInBrowser(`https://github.com/${repo.nameWithOwner}`);
729
+ }
730
+ if (key.delete && !key.backspace || key.backspace && key.ctrl) {
731
+ const repo = filteredAndSorted[cursor];
732
+ if (repo) {
733
+ setDeleteTarget(repo);
734
+ setDeleteMode(true);
735
+ setTypedCode("");
736
+ setDeleteError(null);
737
+ const letters = "ABDEFGHIJKLMNOPQRSTUVWXYZ";
738
+ const code = Array.from({ length: 4 }, () => letters[Math.floor(Math.random() * letters.length)]).join("");
739
+ setDeleteCode(code);
740
+ setDeleteConfirmStage(false);
741
+ setConfirmFocus("delete");
742
+ }
743
+ return;
744
+ }
745
+ if (key.ctrl && (input === "g" || input === "G")) {
746
+ setCursor(0);
747
+ return;
748
+ }
749
+ if (!key.ctrl && input && input.toUpperCase() === "G") {
750
+ setCursor(items.length - 1);
751
+ return;
752
+ }
753
+ if (input && input.toUpperCase() === "R") {
754
+ setCursor(0);
755
+ setRefreshing(true);
756
+ setSortingLoading(true);
757
+ fetchPage(null, true, true);
758
+ }
759
+ if (key.ctrl && (input === "a" || input === "A")) {
760
+ const repo = filteredAndSorted[cursor];
761
+ if (repo) {
762
+ setArchiveTarget(repo);
763
+ setArchiveMode(true);
764
+ setArchiveError(null);
765
+ setArchiving(false);
766
+ setArchiveFocus("confirm");
767
+ }
768
+ return;
769
+ }
770
+ if (key.ctrl && (input === "u" || input === "U")) {
771
+ const repo = filteredAndSorted[cursor];
772
+ if (repo && repo.isFork && repo.parent) {
773
+ const hasCommitData = repo.defaultBranchRef && repo.parent.defaultBranchRef && repo.parent.defaultBranchRef.target?.history && repo.defaultBranchRef.target?.history;
774
+ const commitsBehind = hasCommitData ? repo.parent.defaultBranchRef.target.history.totalCount - repo.defaultBranchRef.target.history.totalCount : 0;
775
+ setSyncTarget(repo);
776
+ setSyncMode(true);
777
+ setSyncError(null);
778
+ setSyncing(false);
779
+ setSyncFocus("confirm");
780
+ }
781
+ return;
782
+ }
783
+ if (key.ctrl && (input === "l" || input === "L")) {
784
+ setLogoutMode(true);
785
+ setLogoutError(null);
786
+ setLogoutFocus("confirm");
787
+ return;
788
+ }
789
+ if (input === "/") {
790
+ setFilterMode(true);
791
+ return;
792
+ }
793
+ if (input && input.toUpperCase() === "S") {
794
+ const order = ["updated", "pushed", "name", "stars"];
795
+ const idx = order.indexOf(sortKey);
796
+ const newSortKey = order[(idx + 1) % order.length];
797
+ setSortKey(newSortKey);
798
+ setCursor(0);
799
+ storeUIPrefs({ sortKey: newSortKey });
800
+ return;
801
+ }
802
+ if (input && input.toUpperCase() === "D") {
803
+ setSortDir((prev) => {
804
+ const next = prev === "asc" ? "desc" : "asc";
805
+ storeUIPrefs({ sortDir: next });
806
+ return next;
807
+ });
808
+ setCursor(0);
809
+ return;
810
+ }
811
+ if (input && input.toUpperCase() === "O") {
812
+ const repo = filteredAndSorted[cursor];
813
+ if (repo) openInBrowser(`https://github.com/${repo.nameWithOwner}`);
814
+ return;
815
+ }
816
+ if (input && input.toUpperCase() === "T") {
817
+ setDensity((d) => {
818
+ const next = (d + 1) % 3;
819
+ storeUIPrefs({ density: next });
820
+ return next;
821
+ });
822
+ return;
823
+ }
824
+ if (input && input.toUpperCase() === "F") {
825
+ setForkTracking((prev) => {
826
+ const next = !prev;
827
+ storeUIPrefs({ forkTracking: next });
828
+ const needsRefresh = next && items.some(
829
+ (repo) => repo.isFork && repo.parent && (!repo.defaultBranchRef?.target?.history || !repo.parent.defaultBranchRef?.target?.history)
830
+ );
831
+ if (needsRefresh) {
832
+ setSortingLoading(true);
833
+ fetchPage(null, true, true, next);
834
+ }
835
+ return next;
836
+ });
837
+ return;
838
+ }
839
+ });
840
+ useEffect(() => {
841
+ if (!loading && !loadingMore && hasNextPage && cursor >= filteredAndSorted.length - 5) {
842
+ fetchPage(endCursor);
843
+ }
844
+ }, [cursor, hasNextPage, endCursor, loading, loadingMore]);
845
+ const filtered = useMemo(() => {
846
+ const q = filter.trim().toLowerCase();
847
+ if (!q) return items;
848
+ return items.filter(
849
+ (r) => r.nameWithOwner.toLowerCase().includes(q) || (r.description ? r.description.toLowerCase().includes(q) : false)
850
+ );
851
+ }, [items, filter]);
852
+ const filteredAndSorted = useMemo(() => {
853
+ const arr = [...filtered];
854
+ const dir = sortDir === "asc" ? 1 : -1;
855
+ arr.sort((a, b) => {
856
+ switch (sortKey) {
857
+ case "name":
858
+ return a.nameWithOwner.localeCompare(b.nameWithOwner) * dir;
859
+ case "stars":
860
+ return (a.stargazerCount - b.stargazerCount) * dir;
861
+ case "forks":
862
+ return (a.forkCount - b.forkCount) * dir;
863
+ case "pushed":
864
+ return (new Date(a.pushedAt).getTime() - new Date(b.pushedAt).getTime()) * dir;
865
+ case "updated":
866
+ default:
867
+ return (new Date(a.updatedAt).getTime() - new Date(b.updatedAt).getTime()) * dir;
868
+ }
869
+ });
870
+ return arr;
871
+ }, [filtered, sortKey, sortDir]);
872
+ useEffect(() => {
873
+ setCursor((c) => Math.min(c, Math.max(0, filteredAndSorted.length - 1)));
874
+ }, [filteredAndSorted.length]);
875
+ const headerHeight = 2;
876
+ const footerHeight = 4;
877
+ const containerPadding = 2;
878
+ const contentHeight = Math.max(1, availableHeight - headerHeight - footerHeight - containerPadding);
879
+ const listHeight = Math.max(1, contentHeight - (filterMode ? 2 : 0) - 2);
880
+ const spacingLines = density;
881
+ const windowed = useMemo(() => {
882
+ const total = filteredAndSorted.length;
883
+ const LINES_PER_REPO = 3 + spacingLines;
884
+ const visibleRepos = Math.max(1, Math.floor(listHeight / LINES_PER_REPO));
885
+ if (visibleRepos >= total) return { start: 0, end: total };
886
+ const buffer = 2;
887
+ const half = Math.floor(visibleRepos / 2);
888
+ let start = Math.max(0, cursor - half - buffer);
889
+ start = Math.min(start, Math.max(0, total - visibleRepos));
890
+ const end = Math.min(total, start + visibleRepos + buffer);
891
+ return { start, end };
892
+ }, [filteredAndSorted.length, cursor, listHeight, spacingLines]);
893
+ function openInBrowser(url) {
894
+ const platform = process.platform;
895
+ const cmd = platform === "darwin" ? `open "${url}"` : platform === "win32" ? `start "" "${url}"` : `xdg-open "${url}"`;
896
+ exec(cmd);
897
+ }
898
+ const lowRate = rateLimit && rateLimit.remaining <= Math.ceil(rateLimit.limit * 0.1);
899
+ const modalOpen = deleteMode || archiveMode || syncMode || logoutMode;
900
+ const headerBar = useMemo(() => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", justifyContent: "space-between", height: 1, marginBottom: 1, children: [
901
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
902
+ /* @__PURE__ */ jsx(Text, { bold: true, color: modalOpen ? "gray" : void 0, dimColor: modalOpen ? true : void 0, children: " Repositories" }),
903
+ /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
904
+ "(",
905
+ filteredAndSorted.length,
906
+ "/",
907
+ totalCount,
908
+ ")"
909
+ ] }),
910
+ loading && /* @__PURE__ */ jsx(Box, { width: 2, flexShrink: 0, flexGrow: 0, marginLeft: 1, children: /* @__PURE__ */ jsx(Text, { color: "yellow", children: /* @__PURE__ */ jsx(SlowSpinner, {}) }) })
911
+ ] }),
912
+ rateLimit && /* @__PURE__ */ jsxs(Text, { color: lowRate ? "yellow" : "gray", children: [
913
+ "API: ",
914
+ rateLimit.remaining,
915
+ "/",
916
+ rateLimit.limit,
917
+ prevRateLimit !== void 0 && prevRateLimit !== rateLimit.remaining && /* @__PURE__ */ jsx(Text, { color: rateLimit.remaining < prevRateLimit ? "red" : "green", children: ` (${rateLimit.remaining - prevRateLimit > 0 ? "+" : ""}${rateLimit.remaining - prevRateLimit})` })
918
+ ] })
919
+ ] }), [filteredAndSorted.length, totalCount, loading, rateLimit, lowRate, modalOpen, prevRateLimit]);
920
+ if (error) {
921
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", alignItems: "center", justifyContent: "center", flexGrow: 1, children: [
922
+ /* @__PURE__ */ jsx(Text, { color: "red", children: error }),
923
+ /* @__PURE__ */ jsx(Text, { color: "gray", dimColor: true, children: "Press 'r' to retry or 'q' to quit" })
924
+ ] });
925
+ }
926
+ if (loading && items.length === 0 || sortingLoading) {
927
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", height: availableHeight, children: [
928
+ /* @__PURE__ */ jsx(Box, { flexDirection: "row", justifyContent: "space-between", height: 1, marginBottom: 1, children: /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
929
+ /* @__PURE__ */ jsx(Text, { bold: true, children: " Repositories" }),
930
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "(Loading...)" })
931
+ ] }) }),
932
+ /* @__PURE__ */ jsx(Box, { borderStyle: "single", borderColor: "yellow", paddingX: 1, paddingY: 1, marginX: 1, height: contentHeight + containerPadding + 2, flexDirection: "column", children: /* @__PURE__ */ jsx(Box, { height: contentHeight, justifyContent: "center", alignItems: "center", children: /* @__PURE__ */ jsx(Box, { flexDirection: "column", alignItems: "center", children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", alignItems: "center", children: [
933
+ /* @__PURE__ */ jsxs(Box, { height: 1, flexDirection: "row", children: [
934
+ /* @__PURE__ */ jsx(Box, { width: 2, flexShrink: 0, flexGrow: 0, children: /* @__PURE__ */ jsx(Text, { color: "cyan", children: /* @__PURE__ */ jsx(SlowSpinner, {}) }) }),
935
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: refreshing ? "Refreshing..." : sortingLoading ? "Applying sort..." : "Loading repositories..." })
936
+ ] }),
937
+ /* @__PURE__ */ jsx(Box, { height: 1, marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "gray", children: refreshing ? "Fetching latest repository data" : sortingLoading ? `Sorting by ${sortKey} (${sortDir === "asc" ? "ascending" : "descending"})` : "Fetching your GitHub repositories" }) })
938
+ ] }) }) }) }),
939
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, paddingX: 1, children: /* @__PURE__ */ jsx(Text, { color: "gray", children: "Please wait..." }) })
940
+ ] });
941
+ }
942
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", height: availableHeight, children: [
943
+ headerBar,
944
+ /* @__PURE__ */ jsx(Box, { borderStyle: "single", borderColor: modalOpen ? "gray" : "yellow", paddingX: 1, paddingY: 1, marginX: 1, height: contentHeight + containerPadding + 2, flexDirection: "column", children: deleteMode && deleteTarget ? (
945
+ // Centered modal; hide list content while modal is open
946
+ /* @__PURE__ */ jsx(Box, { height: contentHeight, alignItems: "center", justifyContent: "center", children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "red", paddingX: 3, paddingY: 2, width: Math.min(terminalWidth - 8, 80), children: [
947
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Delete Confirmation" }),
948
+ /* @__PURE__ */ jsx(Text, { color: "red", children: "\u26A0\uFE0F Delete repository?" }),
949
+ /* @__PURE__ */ jsx(Box, { height: 2, children: /* @__PURE__ */ jsx(Text, { children: " " }) }),
950
+ (() => {
951
+ const langName = deleteTarget.primaryLanguage?.name || "";
952
+ const langColor = deleteTarget.primaryLanguage?.color || "#666666";
953
+ let line1 = "";
954
+ line1 += chalk.white(deleteTarget.nameWithOwner);
955
+ if (deleteTarget.isPrivate) line1 += chalk.yellow(" Private");
956
+ if (deleteTarget.isArchived) line1 += chalk.gray.dim(" Archived");
957
+ if (deleteTarget.isFork && deleteTarget.parent) line1 += chalk.blue(` Fork of ${deleteTarget.parent.nameWithOwner}`);
958
+ let line2 = "";
959
+ if (langName) line2 += chalk.hex(langColor)("\u25CF ") + chalk.gray(`${langName} `);
960
+ line2 += chalk.gray(`\u2605 ${deleteTarget.stargazerCount} \u2442 ${deleteTarget.forkCount} Updated ${formatDate(deleteTarget.updatedAt)}`);
961
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
962
+ /* @__PURE__ */ jsx(Text, { children: line1 }),
963
+ /* @__PURE__ */ jsx(Text, { children: line2 })
964
+ ] });
965
+ })(),
966
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsxs(Text, { children: [
967
+ "Type ",
968
+ /* @__PURE__ */ jsx(Text, { color: "yellow", bold: true, children: deleteCode }),
969
+ " to confirm."
970
+ ] }) }),
971
+ !deleteConfirmStage && /* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
972
+ /* @__PURE__ */ jsx(Text, { children: "Confirm code: " }),
973
+ /* @__PURE__ */ jsx(
974
+ TextInput,
975
+ {
976
+ value: typedCode,
977
+ onChange: (v) => {
978
+ const up = (v || "").toUpperCase();
979
+ const cut = up.slice(0, 4);
980
+ setTypedCode(cut);
981
+ if (cut.length < 4) {
982
+ setDeleteError(null);
983
+ }
984
+ if (cut.length === 4) {
985
+ if (cut === deleteCode && deleteTarget) {
986
+ setDeleteError(null);
987
+ setDeleteConfirmStage(true);
988
+ setConfirmFocus("delete");
989
+ } else {
990
+ setDeleteError("Code does not match");
991
+ }
992
+ }
993
+ },
994
+ onSubmit: () => {
995
+ },
996
+ placeholder: deleteCode
997
+ }
998
+ )
999
+ ] }),
1000
+ deleteConfirmStage && /* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "column", children: [
1001
+ /* @__PURE__ */ jsx(Text, { color: "red", children: "This action will permanently delete the repository. This cannot be undone." }),
1002
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "row", justifyContent: "center", gap: 6, children: [
1003
+ /* @__PURE__ */ jsx(
1004
+ Box,
1005
+ {
1006
+ borderStyle: "round",
1007
+ borderColor: "red",
1008
+ height: 3,
1009
+ width: 20,
1010
+ alignItems: "center",
1011
+ justifyContent: "center",
1012
+ flexDirection: "column",
1013
+ children: /* @__PURE__ */ jsx(Text, { children: confirmFocus === "delete" ? chalk.bgRed.white.bold(" Delete ") : chalk.red.bold("Delete") })
1014
+ }
1015
+ ),
1016
+ /* @__PURE__ */ jsx(
1017
+ Box,
1018
+ {
1019
+ borderStyle: "round",
1020
+ borderColor: confirmFocus === "cancel" ? "white" : "gray",
1021
+ height: 3,
1022
+ width: 20,
1023
+ alignItems: "center",
1024
+ justifyContent: "center",
1025
+ flexDirection: "column",
1026
+ children: /* @__PURE__ */ jsx(Text, { children: confirmFocus === "cancel" ? chalk.bgGray.white.bold(" Cancel ") : chalk.gray.bold("Cancel") })
1027
+ }
1028
+ )
1029
+ ] }),
1030
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, flexDirection: "row", justifyContent: "center", children: /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
1031
+ "Press Enter to ",
1032
+ confirmFocus === "delete" ? "Delete" : "Cancel",
1033
+ " \u2022 Y to confirm \u2022 C to cancel"
1034
+ ] }) }),
1035
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(
1036
+ TextInput,
1037
+ {
1038
+ value: "",
1039
+ onChange: () => {
1040
+ },
1041
+ onSubmit: () => {
1042
+ if (confirmFocus === "delete") confirmDeleteNow();
1043
+ else cancelDeleteModal();
1044
+ },
1045
+ placeholder: ""
1046
+ }
1047
+ ) })
1048
+ ] }),
1049
+ deleteError && /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "magenta", children: deleteError }) }),
1050
+ deleting && /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "yellow", children: "Deleting..." }) })
1051
+ ] }) })
1052
+ ) : archiveMode && archiveTarget ? /* @__PURE__ */ jsx(Box, { height: contentHeight, alignItems: "center", justifyContent: "center", children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: archiveTarget.isArchived ? "green" : "yellow", paddingX: 3, paddingY: 2, width: Math.min(terminalWidth - 8, 80), children: [
1053
+ /* @__PURE__ */ jsx(Text, { bold: true, children: archiveTarget.isArchived ? "Unarchive Confirmation" : "Archive Confirmation" }),
1054
+ /* @__PURE__ */ jsx(Text, { color: archiveTarget.isArchived ? "green" : "yellow", children: archiveTarget.isArchived ? "\u21BA Unarchive repository?" : "\u26A0\uFE0F Archive repository?" }),
1055
+ /* @__PURE__ */ jsx(Box, { height: 1, children: /* @__PURE__ */ jsx(Text, { children: " " }) }),
1056
+ /* @__PURE__ */ jsx(Text, { children: archiveTarget.nameWithOwner }),
1057
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { children: archiveTarget.isArchived ? "This will make the repository active again." : "This will make the repository read-only." }) }),
1058
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "row", justifyContent: "center", gap: 6, children: [
1059
+ /* @__PURE__ */ jsx(
1060
+ Box,
1061
+ {
1062
+ borderStyle: "round",
1063
+ borderColor: archiveTarget.isArchived ? "green" : "yellow",
1064
+ height: 3,
1065
+ width: 20,
1066
+ alignItems: "center",
1067
+ justifyContent: "center",
1068
+ flexDirection: "column",
1069
+ children: /* @__PURE__ */ jsx(Text, { children: archiveFocus === "confirm" ? chalk.bgGreen.white.bold(` ${archiveTarget.isArchived ? "Unarchive" : "Archive"} `) : chalk.bold[archiveTarget.isArchived ? "green" : "yellow"](archiveTarget.isArchived ? "Unarchive" : "Archive") })
1070
+ }
1071
+ ),
1072
+ /* @__PURE__ */ jsx(
1073
+ Box,
1074
+ {
1075
+ borderStyle: "round",
1076
+ borderColor: archiveFocus === "cancel" ? "white" : "gray",
1077
+ height: 3,
1078
+ width: 20,
1079
+ alignItems: "center",
1080
+ justifyContent: "center",
1081
+ flexDirection: "column",
1082
+ children: /* @__PURE__ */ jsx(Text, { children: archiveFocus === "cancel" ? chalk.bgGray.white.bold(" Cancel ") : chalk.gray.bold("Cancel") })
1083
+ }
1084
+ )
1085
+ ] }),
1086
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, flexDirection: "row", justifyContent: "center", children: /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
1087
+ "Press Enter to ",
1088
+ archiveFocus === "confirm" ? archiveTarget.isArchived ? "Unarchive" : "Archive" : "Cancel",
1089
+ " \u2022 Y to confirm \u2022 C to cancel"
1090
+ ] }) }),
1091
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(
1092
+ TextInput,
1093
+ {
1094
+ value: "",
1095
+ onChange: () => {
1096
+ },
1097
+ onSubmit: () => {
1098
+ if (archiveFocus === "confirm") {
1099
+ (async () => {
1100
+ try {
1101
+ setArchiving(true);
1102
+ const isArchived = archiveTarget.isArchived;
1103
+ const id = archiveTarget.id;
1104
+ if (isArchived) await unarchiveRepositoryById(client, id);
1105
+ else await archiveRepositoryById(client, id);
1106
+ setItems((prev) => prev.map((r) => r.id === archiveTarget.id ? { ...r, isArchived: !isArchived } : r));
1107
+ closeArchiveModal();
1108
+ } catch (e) {
1109
+ setArchiving(false);
1110
+ setArchiveError("Failed to update archive state. Check permissions.");
1111
+ }
1112
+ })();
1113
+ } else {
1114
+ closeArchiveModal();
1115
+ }
1116
+ }
1117
+ }
1118
+ ) }),
1119
+ archiveError && /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "magenta", children: archiveError }) }),
1120
+ archiving && /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "yellow", children: archiveTarget.isArchived ? "Unarchiving..." : "Archiving..." }) })
1121
+ ] }) }) : syncMode && syncTarget ? /* @__PURE__ */ jsx(Box, { height: contentHeight, alignItems: "center", justifyContent: "center", children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "blue", paddingX: 3, paddingY: 2, width: Math.min(terminalWidth - 8, 80), children: [
1122
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Sync Fork Confirmation" }),
1123
+ /* @__PURE__ */ jsx(Text, { color: "blue", children: "\u27F2 Sync fork with upstream?" }),
1124
+ /* @__PURE__ */ jsx(Box, { height: 1, children: /* @__PURE__ */ jsx(Text, { children: " " }) }),
1125
+ /* @__PURE__ */ jsx(Text, { children: syncTarget.nameWithOwner }),
1126
+ syncTarget.parent && /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
1127
+ "Upstream: ",
1128
+ syncTarget.parent.nameWithOwner
1129
+ ] }),
1130
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { children: "This will merge upstream changes into your fork." }) }),
1131
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "row", justifyContent: "center", gap: 6, children: [
1132
+ /* @__PURE__ */ jsx(
1133
+ Box,
1134
+ {
1135
+ borderStyle: "round",
1136
+ borderColor: "blue",
1137
+ height: 3,
1138
+ width: 20,
1139
+ alignItems: "center",
1140
+ justifyContent: "center",
1141
+ flexDirection: "column",
1142
+ children: /* @__PURE__ */ jsx(Text, { children: syncFocus === "confirm" ? chalk.bgBlue.white.bold(" Sync ") : chalk.blue.bold("Sync") })
1143
+ }
1144
+ ),
1145
+ /* @__PURE__ */ jsx(
1146
+ Box,
1147
+ {
1148
+ borderStyle: "round",
1149
+ borderColor: syncFocus === "cancel" ? "white" : "gray",
1150
+ height: 3,
1151
+ width: 20,
1152
+ alignItems: "center",
1153
+ justifyContent: "center",
1154
+ flexDirection: "column",
1155
+ children: /* @__PURE__ */ jsx(Text, { children: syncFocus === "cancel" ? chalk.bgGray.white.bold(" Cancel ") : chalk.gray.bold("Cancel") })
1156
+ }
1157
+ )
1158
+ ] }),
1159
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, flexDirection: "row", justifyContent: "center", children: /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
1160
+ "Press Enter to ",
1161
+ syncFocus === "confirm" ? "Sync" : "Cancel",
1162
+ " \u2022 y to confirm \u2022 c to cancel"
1163
+ ] }) }),
1164
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(
1165
+ TextInput,
1166
+ {
1167
+ value: "",
1168
+ onChange: () => {
1169
+ },
1170
+ onSubmit: () => {
1171
+ if (syncFocus === "confirm") {
1172
+ (async () => {
1173
+ try {
1174
+ setSyncing(true);
1175
+ const [owner, repo] = syncTarget.nameWithOwner.split("/");
1176
+ const result = await syncForkWithUpstream(token, owner, repo);
1177
+ setItems((prev) => prev.map((r) => {
1178
+ if (r.id === syncTarget.id && r.parent && r.defaultBranchRef?.target?.history && r.parent.defaultBranchRef?.target?.history) {
1179
+ return {
1180
+ ...r,
1181
+ defaultBranchRef: {
1182
+ ...r.defaultBranchRef,
1183
+ target: {
1184
+ ...r.defaultBranchRef.target,
1185
+ history: {
1186
+ totalCount: r.parent.defaultBranchRef.target.history.totalCount
1187
+ }
1188
+ }
1189
+ }
1190
+ };
1191
+ }
1192
+ return r;
1193
+ }));
1194
+ closeSyncModal();
1195
+ } catch (e) {
1196
+ setSyncing(false);
1197
+ setSyncError(e.message || "Failed to sync fork. Check permissions and network.");
1198
+ }
1199
+ })();
1200
+ } else {
1201
+ closeSyncModal();
1202
+ }
1203
+ }
1204
+ }
1205
+ ) }),
1206
+ syncError && /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "magenta", children: syncError }) }),
1207
+ syncing && /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "yellow", children: "Syncing..." }) })
1208
+ ] }) }) : logoutMode ? /* @__PURE__ */ jsx(Box, { height: contentHeight, alignItems: "center", justifyContent: "center", children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 3, paddingY: 2, width: Math.min(terminalWidth - 8, 80), children: [
1209
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Logout Confirmation" }),
1210
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: "Are you sure you want to log out?" }),
1211
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "row", justifyContent: "center", gap: 6, children: [
1212
+ /* @__PURE__ */ jsx(
1213
+ Box,
1214
+ {
1215
+ borderStyle: "round",
1216
+ borderColor: "cyan",
1217
+ height: 3,
1218
+ width: 20,
1219
+ alignItems: "center",
1220
+ justifyContent: "center",
1221
+ flexDirection: "column",
1222
+ children: /* @__PURE__ */ jsx(Text, { children: logoutFocus === "confirm" ? chalk.bgCyan.white.bold(" Logout ") : chalk.cyan.bold("Logout") })
1223
+ }
1224
+ ),
1225
+ /* @__PURE__ */ jsx(
1226
+ Box,
1227
+ {
1228
+ borderStyle: "round",
1229
+ borderColor: logoutFocus === "cancel" ? "white" : "gray",
1230
+ height: 3,
1231
+ width: 20,
1232
+ alignItems: "center",
1233
+ justifyContent: "center",
1234
+ flexDirection: "column",
1235
+ children: /* @__PURE__ */ jsx(Text, { children: logoutFocus === "cancel" ? chalk.bgGray.white.bold(" Cancel ") : chalk.gray.bold("Cancel") })
1236
+ }
1237
+ )
1238
+ ] }),
1239
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, flexDirection: "row", justifyContent: "center", children: /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
1240
+ "Press Enter to ",
1241
+ logoutFocus === "confirm" ? "Logout" : "Cancel",
1242
+ " \u2022 Y to confirm \u2022 C to cancel"
1243
+ ] }) })
1244
+ ] }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1245
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 2, marginBottom: 1, children: [
1246
+ /* @__PURE__ */ jsxs(Text, { color: "gray", dimColor: true, children: [
1247
+ "Sort: ",
1248
+ sortKey,
1249
+ " ",
1250
+ sortDir === "asc" ? "\u2191" : "\u2193"
1251
+ ] }),
1252
+ /* @__PURE__ */ jsxs(Text, { color: "gray", dimColor: true, children: [
1253
+ "Forks - Commits Behind: ",
1254
+ forkTracking ? "ON" : "OFF"
1255
+ ] }),
1256
+ filter && /* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
1257
+ 'Filter: "',
1258
+ filter,
1259
+ '"'
1260
+ ] })
1261
+ ] }),
1262
+ filterMode && /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
1263
+ /* @__PURE__ */ jsx(Text, { children: "Filter: " }),
1264
+ /* @__PURE__ */ jsx(
1265
+ TextInput,
1266
+ {
1267
+ value: filter,
1268
+ onChange: setFilter,
1269
+ onSubmit: () => setFilterMode(false),
1270
+ placeholder: "Type to filter..."
1271
+ }
1272
+ )
1273
+ ] }),
1274
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", height: listHeight, children: [
1275
+ filteredAndSorted.slice(windowed.start, windowed.end).map((repo, i) => {
1276
+ const idx = windowed.start + i;
1277
+ return /* @__PURE__ */ jsx(
1278
+ RepoRow,
1279
+ {
1280
+ repo,
1281
+ selected: idx === cursor,
1282
+ index: idx + 1,
1283
+ maxWidth: terminalWidth - 6,
1284
+ spacingLines,
1285
+ forkTracking
1286
+ },
1287
+ repo.nameWithOwner
1288
+ );
1289
+ }),
1290
+ loadingMore && hasNextPage && /* @__PURE__ */ jsx(Box, { justifyContent: "center", alignItems: "center", marginTop: 1, children: /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
1291
+ /* @__PURE__ */ jsx(Box, { width: 2, flexShrink: 0, flexGrow: 0, marginRight: 1, children: /* @__PURE__ */ jsx(Text, { color: "cyan", children: /* @__PURE__ */ jsx(SlowSpinner, {}) }) }),
1292
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: "Loading more repositories..." })
1293
+ ] }) }),
1294
+ !loading && filteredAndSorted.length === 0 && /* @__PURE__ */ jsx(Box, { justifyContent: "center", alignItems: "center", flexGrow: 1, children: /* @__PURE__ */ jsx(Text, { color: "gray", dimColor: true, children: filter ? "No repositories match your filter" : "No repositories found" }) })
1295
+ ] })
1296
+ ] }) }),
1297
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, paddingX: 1, flexDirection: "column", children: [
1298
+ /* @__PURE__ */ jsx(Box, { width: terminalWidth, justifyContent: "center", children: /* @__PURE__ */ jsx(Text, { color: "gray", dimColor: modalOpen ? true : void 0, children: "\u2191\u2193 Navigate \u2022 Ctrl+G Top \u2022 G Bottom \u2022 / Filter \u2022 S Sort \u2022 D Direction \u2022 T Density \u2022 F Forks - Commits Behind \u2022 \u23CE/O Open" }) }),
1299
+ /* @__PURE__ */ jsx(Box, { width: terminalWidth, justifyContent: "center", children: /* @__PURE__ */ jsx(Text, { color: "gray", dimColor: modalOpen ? true : void 0, children: "Del/Ctrl+Backspace Delete \u2022 Ctrl+A Un/Archive \u2022 Ctrl+U Sync Fork \u2022 Ctrl+L Logout \u2022 R Refresh \u2022 Q Quit" }) })
1300
+ ] })
1301
+ ] });
1302
+ }
1303
+
1304
+ // src/ui/App.tsx
1305
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1306
+ var packageJson = require_package();
1307
+ function App() {
1308
+ const { exit } = useApp2();
1309
+ const { stdout } = useStdout2();
1310
+ const [mode, setMode] = useState2("checking");
1311
+ const [token, setToken] = useState2(null);
1312
+ const [input, setInput] = useState2("");
1313
+ const [error, setError] = useState2(null);
1314
+ const [viewer, setViewer] = useState2(null);
1315
+ const [rateLimitReset, setRateLimitReset] = useState2(null);
1316
+ const [dims, setDims] = useState2(() => {
1317
+ const cols = stdout?.columns ?? 100;
1318
+ const rows = stdout?.rows ?? 30;
1319
+ return { cols, rows };
1320
+ });
1321
+ useEffect2(() => {
1322
+ if (!stdout) return;
1323
+ const onResize = () => {
1324
+ const cols = stdout.columns ?? 100;
1325
+ const rows = stdout.rows ?? 30;
1326
+ setDims({ cols, rows });
1327
+ };
1328
+ stdout.on("resize", onResize);
1329
+ return () => {
1330
+ stdout.off?.("resize", onResize);
1331
+ };
1332
+ }, [stdout]);
1333
+ useEffect2(() => {
1334
+ const env = getTokenFromEnv();
1335
+ const stored = getStoredToken();
1336
+ if (env) {
1337
+ setToken(env);
1338
+ setMode("validating");
1339
+ } else if (stored) {
1340
+ setToken(stored);
1341
+ setMode("validating");
1342
+ } else {
1343
+ setMode("prompt");
1344
+ }
1345
+ }, []);
1346
+ useEffect2(() => {
1347
+ (async () => {
1348
+ if (mode !== "validating" || !token) return;
1349
+ const timeoutId = setTimeout(() => {
1350
+ setError("Token validation timed out. Please check your network connection.");
1351
+ setMode("prompt");
1352
+ setToken(null);
1353
+ }, 15e3);
1354
+ try {
1355
+ const client = makeClient(token);
1356
+ const login = await getViewerLogin(client);
1357
+ clearTimeout(timeoutId);
1358
+ setViewer(login);
1359
+ if (!getStoredToken()) {
1360
+ storeToken(token);
1361
+ }
1362
+ setMode("ready");
1363
+ } catch (e) {
1364
+ clearTimeout(timeoutId);
1365
+ let errorMessage = "Invalid or unauthorized token. Please enter a valid Personal Access Token.";
1366
+ let isRateLimit = false;
1367
+ let resetTime = null;
1368
+ if (e.message) {
1369
+ const msg = e.message.toLowerCase();
1370
+ if (msg.includes("rate limit") || msg.includes("rate-limit") || msg.includes("abuse")) {
1371
+ isRateLimit = true;
1372
+ const resetMatch = e.message.match(/resets? at (\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z)/i);
1373
+ if (resetMatch) {
1374
+ resetTime = resetMatch[1];
1375
+ }
1376
+ } else if (msg.includes("bad credentials") || msg.includes("unauthorized") || msg.includes("401")) {
1377
+ errorMessage = "Invalid token. Please check your Personal Access Token and try again.";
1378
+ } else if (msg.includes("forbidden") || msg.includes("403")) {
1379
+ errorMessage = 'Token lacks required permissions. Please ensure your token has "repo" scope.';
1380
+ } else if (msg.includes("not found") || msg.includes("404")) {
1381
+ errorMessage = "GitHub API endpoint not found. Please check your network connection.";
1382
+ } else if (msg.includes("network") || msg.includes("timeout") || msg.includes("econnrefused")) {
1383
+ errorMessage = "Network error. Please check your internet connection and try again.";
1384
+ }
1385
+ }
1386
+ if (e.errors && Array.isArray(e.errors)) {
1387
+ const firstError = e.errors[0];
1388
+ if (firstError?.type === "RATE_LIMITED") {
1389
+ isRateLimit = true;
1390
+ } else if (firstError?.type === "FORBIDDEN") {
1391
+ errorMessage = 'Token lacks required permissions. Please ensure your token has "repo" scope.';
1392
+ }
1393
+ }
1394
+ if (e.response?.headers) {
1395
+ const rateLimitRemaining = e.response.headers["x-ratelimit-remaining"];
1396
+ const rateLimitReset2 = e.response.headers["x-ratelimit-reset"];
1397
+ if (rateLimitRemaining === "0" || rateLimitRemaining === 0) {
1398
+ isRateLimit = true;
1399
+ if (rateLimitReset2) {
1400
+ const resetDate = new Date(parseInt(rateLimitReset2) * 1e3);
1401
+ resetTime = resetDate.toISOString();
1402
+ }
1403
+ }
1404
+ }
1405
+ if (isRateLimit) {
1406
+ setRateLimitReset(resetTime);
1407
+ setMode("rate_limited");
1408
+ } else {
1409
+ setError(errorMessage);
1410
+ setMode("prompt");
1411
+ }
1412
+ setToken(null);
1413
+ }
1414
+ })();
1415
+ }, [mode, token]);
1416
+ const onSubmitToken = async () => {
1417
+ if (!input.trim()) return;
1418
+ setToken(input.trim());
1419
+ setError(null);
1420
+ setMode("validating");
1421
+ };
1422
+ const handleLogout = () => {
1423
+ try {
1424
+ clearStoredToken();
1425
+ } catch {
1426
+ }
1427
+ setToken(null);
1428
+ setViewer(null);
1429
+ setMode("prompt");
1430
+ };
1431
+ useInput2((input2, key) => {
1432
+ if (mode === "prompt" && key.escape) {
1433
+ exit();
1434
+ }
1435
+ if (mode === "rate_limited") {
1436
+ if (key.escape || input2 === "q") {
1437
+ exit();
1438
+ } else if (input2 === "r") {
1439
+ setMode("validating");
1440
+ } else if (input2 === "l") {
1441
+ setToken(null);
1442
+ setInput("");
1443
+ setRateLimitReset(null);
1444
+ setMode("prompt");
1445
+ }
1446
+ }
1447
+ if (mode === "validating" && key.escape) {
1448
+ if (rateLimitReset) {
1449
+ setMode("rate_limited");
1450
+ } else {
1451
+ setMode("prompt");
1452
+ setToken(null);
1453
+ }
1454
+ }
1455
+ });
1456
+ const verticalPadding = Math.floor(dims.rows * 0.15);
1457
+ const header = useMemo2(() => /* @__PURE__ */ jsxs2(Box2, { flexDirection: "row", justifyContent: "space-between", marginBottom: 1, children: [
1458
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "row", gap: 1, children: [
1459
+ /* @__PURE__ */ jsxs2(Text2, { bold: true, color: "cyan", children: [
1460
+ " ",
1461
+ "GitHub Repository Manager"
1462
+ ] }),
1463
+ /* @__PURE__ */ jsxs2(Text2, { color: "gray", dimColor: true, children: [
1464
+ "v",
1465
+ packageJson.version
1466
+ ] })
1467
+ ] }),
1468
+ viewer && /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
1469
+ "@",
1470
+ viewer,
1471
+ " "
1472
+ ] })
1473
+ ] }), [viewer]);
1474
+ if (mode === "rate_limited") {
1475
+ const formatResetTime = (resetTime) => {
1476
+ if (!resetTime) return "Unknown";
1477
+ try {
1478
+ const resetDate = new Date(resetTime);
1479
+ const now = /* @__PURE__ */ new Date();
1480
+ const diffMs = resetDate.getTime() - now.getTime();
1481
+ const diffMinutes = Math.ceil(diffMs / (1e3 * 60));
1482
+ if (diffMinutes <= 0) {
1483
+ return "Now (should be reset)";
1484
+ } else if (diffMinutes < 60) {
1485
+ return `${diffMinutes} minute${diffMinutes !== 1 ? "s" : ""}`;
1486
+ } else {
1487
+ const hours = Math.floor(diffMinutes / 60);
1488
+ const mins = diffMinutes % 60;
1489
+ return `${hours} hour${hours !== 1 ? "s" : ""} ${mins > 0 ? `${mins} min` : ""}`;
1490
+ }
1491
+ } catch {
1492
+ return "Unknown";
1493
+ }
1494
+ };
1495
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: dims.rows, paddingX: 2, paddingTop: verticalPadding, paddingBottom: verticalPadding, children: [
1496
+ header,
1497
+ /* @__PURE__ */ jsx2(Box2, { flexGrow: 1, justifyContent: "center", alignItems: "center", children: /* @__PURE__ */ jsxs2(Box2, { borderStyle: "single", borderColor: "yellow", paddingX: 3, paddingY: 2, flexDirection: "column", width: Math.min(dims.cols - 8, 80), children: [
1498
+ /* @__PURE__ */ jsx2(Text2, { bold: true, color: "yellow", marginBottom: 1, children: "\u26A0\uFE0F Rate Limit Exceeded" }),
1499
+ /* @__PURE__ */ jsx2(Text2, { color: "gray", marginBottom: 1, children: "You've hit GitHub's API rate limit for your token." }),
1500
+ /* @__PURE__ */ jsx2(Text2, { color: "gray", marginBottom: 1, children: "This happens when you make too many requests in a short time." }),
1501
+ rateLimitReset && /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, marginBottom: 1, children: [
1502
+ /* @__PURE__ */ jsxs2(Text2, { children: [
1503
+ /* @__PURE__ */ jsx2(Text2, { color: "cyan", children: "Reset in:" }),
1504
+ " ",
1505
+ /* @__PURE__ */ jsx2(Text2, { bold: true, children: formatResetTime(rateLimitReset) })
1506
+ ] }),
1507
+ /* @__PURE__ */ jsxs2(Text2, { color: "gray", dimColor: true, children: [
1508
+ "(",
1509
+ new Date(rateLimitReset).toLocaleTimeString(),
1510
+ ")"
1511
+ ] })
1512
+ ] }),
1513
+ /* @__PURE__ */ jsxs2(Box2, { marginTop: 2, flexDirection: "column", gap: 1, children: [
1514
+ /* @__PURE__ */ jsx2(Text2, { bold: true, children: "What would you like to do?" }),
1515
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingLeft: 2, children: [
1516
+ /* @__PURE__ */ jsxs2(Text2, { children: [
1517
+ /* @__PURE__ */ jsx2(Text2, { color: "cyan", bold: true, children: "r" }),
1518
+ " - Retry now ",
1519
+ rateLimitReset && formatResetTime(rateLimitReset) !== "Now (should be reset)" ? "(likely to fail until reset)" : "(should work now)"
1520
+ ] }),
1521
+ /* @__PURE__ */ jsxs2(Text2, { children: [
1522
+ /* @__PURE__ */ jsx2(Text2, { color: "cyan", bold: true, children: "l" }),
1523
+ " - Logout and use a different token"
1524
+ ] }),
1525
+ /* @__PURE__ */ jsxs2(Text2, { children: [
1526
+ /* @__PURE__ */ jsx2(Text2, { color: "gray", bold: true, children: "q/Esc" }),
1527
+ " - Quit application"
1528
+ ] })
1529
+ ] })
1530
+ ] }),
1531
+ /* @__PURE__ */ jsx2(Text2, { color: "gray", dimColor: true, marginTop: 2, children: "Tip: Using multiple tokens or waiting between requests can help avoid rate limits." })
1532
+ ] }) })
1533
+ ] });
1534
+ }
1535
+ if (mode === "prompt") {
1536
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: dims.rows, paddingX: 2, paddingTop: verticalPadding, paddingBottom: verticalPadding, children: [
1537
+ header,
1538
+ /* @__PURE__ */ jsx2(Box2, { flexGrow: 1, justifyContent: "center", alignItems: "center", children: /* @__PURE__ */ jsxs2(Box2, { borderStyle: "single", borderColor: "cyan", paddingX: 2, paddingY: 1, flexDirection: "column", children: [
1539
+ /* @__PURE__ */ jsx2(Text2, { bold: true, marginBottom: 1, children: "Authentication Required" }),
1540
+ /* @__PURE__ */ jsx2(Text2, { color: "gray", marginBottom: 1, children: "Enter your GitHub Personal Access Token" }),
1541
+ /* @__PURE__ */ jsxs2(Box2, { children: [
1542
+ /* @__PURE__ */ jsx2(Text2, { children: "Token: " }),
1543
+ /* @__PURE__ */ jsx2(
1544
+ TextInput2,
1545
+ {
1546
+ value: input,
1547
+ onChange: setInput,
1548
+ onSubmit: onSubmitToken,
1549
+ mask: "*"
1550
+ }
1551
+ )
1552
+ ] }),
1553
+ error && /* @__PURE__ */ jsx2(Text2, { color: "red", marginTop: 1, children: error }),
1554
+ /* @__PURE__ */ jsx2(Text2, { color: "gray", dimColor: true, marginTop: 1, children: "The token will be stored securely in your local config" }),
1555
+ /* @__PURE__ */ jsx2(Text2, { color: "gray", dimColor: true, marginTop: 1, children: "Press Esc to quit" })
1556
+ ] }) })
1557
+ ] });
1558
+ }
1559
+ if (mode === "validating" || mode === "checking") {
1560
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: dims.rows, paddingX: 2, paddingTop: verticalPadding, paddingBottom: verticalPadding, children: [
1561
+ header,
1562
+ /* @__PURE__ */ jsx2(Box2, { flexGrow: 1, justifyContent: "center", alignItems: "center", children: /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", alignItems: "center", children: [
1563
+ /* @__PURE__ */ jsx2(Text2, { color: "yellow", children: "Validating token..." }),
1564
+ mode === "validating" && /* @__PURE__ */ jsx2(Text2, { color: "gray", dimColor: true, marginTop: 1, children: "Press Esc to cancel" })
1565
+ ] }) })
1566
+ ] });
1567
+ }
1568
+ if (mode === "error") {
1569
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: dims.rows, paddingX: 2, paddingTop: verticalPadding, paddingBottom: verticalPadding, children: [
1570
+ header,
1571
+ /* @__PURE__ */ jsx2(Box2, { flexGrow: 1, justifyContent: "center", alignItems: "center", children: /* @__PURE__ */ jsx2(Text2, { color: "red", children: error ?? "Unexpected error" }) })
1572
+ ] });
1573
+ }
1574
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: dims.rows, paddingX: 2, paddingTop: verticalPadding, paddingBottom: verticalPadding, children: [
1575
+ header,
1576
+ /* @__PURE__ */ jsx2(RepoList, { token, maxVisibleRows: dims.rows - verticalPadding * 2 - 4, onLogout: handleLogout })
1577
+ ] });
1578
+ }
1579
+
1580
+ // src/index.tsx
1581
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1582
+ process.on("uncaughtException", (err) => {
1583
+ console.error("Unhandled error:", err.message || err);
1584
+ process.exit(1);
1585
+ });
1586
+ process.on("unhandledRejection", (reason) => {
1587
+ console.error("Unhandled rejection:", reason?.message || reason);
1588
+ process.exit(1);
1589
+ });
1590
+ render(
1591
+ /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
1592
+ /* @__PURE__ */ jsx3(App, {}),
1593
+ /* @__PURE__ */ jsx3(Text3, { color: "gray" })
1594
+ ] })
1595
+ );