git-cvnty 1.0.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/cli.js ADDED
@@ -0,0 +1,1296 @@
1
+ #!/usr/bin/env node
2
+
3
+ // cli.js
4
+ import React13 from "react";
5
+ import { render } from "ink";
6
+ import meow from "meow";
7
+
8
+ // src/app.js
9
+ import React12, { useState as useState7, useEffect as useEffect5, useCallback as useCallback2 } from "react";
10
+ import { Box as Box12, Text as Text12 } from "ink";
11
+ import SelectInput4 from "ink-select-input";
12
+
13
+ // src/theme.js
14
+ var theme = {
15
+ /** Primary accent color (borders, headings) */
16
+ accent: "cyan",
17
+ /** Secondary accent for highlights */
18
+ highlight: "magenta",
19
+ /** Default text color */
20
+ text: "white",
21
+ /** Dim text for hints/labels */
22
+ dim: "gray",
23
+ /** Color for staged file indicators */
24
+ staged: "green",
25
+ /** Color for unstaged / modified file indicators */
26
+ unstaged: "yellow",
27
+ /** Color for untracked / new file indicators */
28
+ untracked: "red",
29
+ /** Color for the git branch tree */
30
+ tree: "cyan",
31
+ /** Color for success messages */
32
+ success: "green",
33
+ /** Color for error messages */
34
+ error: "red",
35
+ /** Border style for boxes: 'single' | 'double' | 'round' | 'bold' | 'singleDouble' | 'doubleSingle' | 'classic' */
36
+ border: "round",
37
+ /** Gradient colors for the header logo */
38
+ gradient: ["cyan", "magenta"],
39
+ /** Show the git-cvnty ASCII logo at the top */
40
+ showLogo: true,
41
+ /** Whether the app should resize dynamically */
42
+ appResize: true,
43
+ /** Map of git status flags to display symbols */
44
+ flagSymbols: {
45
+ M: "\u25CF",
46
+ // modified
47
+ A: "\u271A",
48
+ // added/staged
49
+ D: "\u2716",
50
+ // deleted
51
+ R: "\xBB",
52
+ // renamed
53
+ C: "\xBB",
54
+ // copied
55
+ U: "\u2550",
56
+ // unmerged
57
+ "?": "?"
58
+ // untracked
59
+ }
60
+ };
61
+ var theme_default = theme;
62
+
63
+ // src/git.js
64
+ import { execSync, spawnSync } from "child_process";
65
+ function git(args, opts = {}) {
66
+ try {
67
+ return execSync(`git ${args}`, { encoding: "utf8", ...opts }).trim();
68
+ } catch {
69
+ return "";
70
+ }
71
+ }
72
+ function gitRaw(args, opts = {}) {
73
+ const argArray = Array.isArray(args) ? args : args.split(" ");
74
+ const result = spawnSync("git", argArray, {
75
+ encoding: "utf8",
76
+ ...opts
77
+ });
78
+ return {
79
+ stdout: (result.stdout || "").trim(),
80
+ stderr: (result.stderr || "").trim(),
81
+ code: result.status ?? 1
82
+ };
83
+ }
84
+ function getCurrentBranch() {
85
+ return git("branch --show-current");
86
+ }
87
+ function getStatus() {
88
+ const raw = git("status --porcelain=v1");
89
+ const staged = [];
90
+ const unstaged = [];
91
+ const untracked = [];
92
+ for (const line of raw.split("\n")) {
93
+ if (!line) continue;
94
+ const xy = line.slice(0, 2);
95
+ const file = line.slice(3).trim();
96
+ const x = xy[0];
97
+ const y = xy[1];
98
+ if (x !== " " && x !== "?") {
99
+ staged.push({ flag: x, file });
100
+ }
101
+ if (y !== " " && y !== "?") {
102
+ unstaged.push({ flag: y, file });
103
+ }
104
+ if (x === "?" && y === "?") {
105
+ untracked.push({ flag: "?", file });
106
+ }
107
+ }
108
+ return { staged, unstaged: [...unstaged, ...untracked] };
109
+ }
110
+ function getBranchGraph(maxLines = 30) {
111
+ return git(
112
+ `log --all --graph --decorate --oneline --color=never -${maxLines}`
113
+ );
114
+ }
115
+ function getBranches() {
116
+ const raw = git("branch");
117
+ return raw.split("\n").map((b) => b.replace(/^\*?\s*/, "").trim()).filter(Boolean);
118
+ }
119
+ function stageFile(file) {
120
+ const { code, stderr } = gitRaw(["add", "--", file]);
121
+ return { ok: code === 0, error: stderr };
122
+ }
123
+ function unstageFile(file) {
124
+ const { code, stderr } = gitRaw(["restore", "--staged", "--", file]);
125
+ return { ok: code === 0, error: stderr };
126
+ }
127
+ function stageAll() {
128
+ const { code, stderr } = gitRaw(["add", "."]);
129
+ return { ok: code === 0, error: stderr };
130
+ }
131
+ function unstageAll() {
132
+ const { code, stderr } = gitRaw(["restore", "--staged", "."]);
133
+ return { ok: code === 0, error: stderr };
134
+ }
135
+ function commit(message) {
136
+ const result = spawnSync("git", ["commit", "-m", message], {
137
+ encoding: "utf8"
138
+ });
139
+ return {
140
+ ok: result.status === 0,
141
+ error: (result.stderr || "").trim(),
142
+ output: (result.stdout || "").trim()
143
+ };
144
+ }
145
+ function push() {
146
+ const branch = getCurrentBranch();
147
+ const result = spawnSync("git", ["push", "origin", branch], {
148
+ encoding: "utf8"
149
+ });
150
+ return {
151
+ ok: result.status === 0,
152
+ error: (result.stderr || "").trim(),
153
+ output: (result.stdout || "").trim()
154
+ };
155
+ }
156
+ function pull() {
157
+ const result = spawnSync("git", ["pull"], { encoding: "utf8" });
158
+ return {
159
+ ok: result.status === 0,
160
+ error: (result.stderr || "").trim(),
161
+ output: (result.stdout || "").trim()
162
+ };
163
+ }
164
+ function checkoutBranch(name) {
165
+ const branches = getBranches();
166
+ const args = branches.includes(name) ? ["checkout", name] : ["checkout", "-b", name];
167
+ const result = spawnSync("git", args, { encoding: "utf8" });
168
+ return {
169
+ ok: result.status === 0,
170
+ error: (result.stderr || "").trim(),
171
+ created: !branches.includes(name)
172
+ };
173
+ }
174
+ function deleteBranch(name) {
175
+ const result = spawnSync("git", ["branch", "-D", name], {
176
+ encoding: "utf8"
177
+ });
178
+ return {
179
+ ok: result.status === 0,
180
+ error: (result.stderr || "").trim()
181
+ };
182
+ }
183
+ function getStashList() {
184
+ const raw = git("stash list");
185
+ return raw ? raw.split("\n").filter(Boolean) : [];
186
+ }
187
+ function stashPush(message) {
188
+ const args = message ? ["stash", "push", "-m", message] : ["stash", "push"];
189
+ const result = spawnSync("git", args, { encoding: "utf8" });
190
+ return {
191
+ ok: result.status === 0,
192
+ error: (result.stderr || "").trim()
193
+ };
194
+ }
195
+ function stashPop() {
196
+ const result = spawnSync("git", ["stash", "pop"], { encoding: "utf8" });
197
+ return {
198
+ ok: result.status === 0,
199
+ error: (result.stderr || "").trim()
200
+ };
201
+ }
202
+ function stashDrop(index = 0) {
203
+ const result = spawnSync("git", ["stash", "drop", `stash@{${index}}`], {
204
+ encoding: "utf8"
205
+ });
206
+ return {
207
+ ok: result.status === 0,
208
+ error: (result.stderr || "").trim()
209
+ };
210
+ }
211
+ function isGitRepo() {
212
+ const result = spawnSync("git", ["rev-parse", "--is-inside-work-tree"], {
213
+ encoding: "utf8"
214
+ });
215
+ return result.status === 0;
216
+ }
217
+
218
+ // src/components/Header.js
219
+ import React from "react";
220
+ import { Box, Text } from "ink";
221
+ import BigText from "ink-big-text";
222
+ import Gradient from "ink-gradient";
223
+ import { jsx, jsxs } from "react/jsx-runtime";
224
+ var Header = ({ branch, showLogo, accentColor }) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
225
+ showLogo && /* @__PURE__ */ jsx(Gradient, { name: "cristal", children: /* @__PURE__ */ jsx(BigText, { text: "git-cvnty", font: "tiny" }) }),
226
+ /* @__PURE__ */ jsxs(Box, { children: [
227
+ /* @__PURE__ */ jsx(Text, { color: accentColor, bold: true, children: " \u2387 Branch: " }),
228
+ /* @__PURE__ */ jsx(Text, { color: "white", bold: true, children: branch || "(unknown)" })
229
+ ] })
230
+ ] });
231
+ var Header_default = Header;
232
+
233
+ // src/components/StatusPanel.js
234
+ import React2 from "react";
235
+ import { Box as Box2, Text as Text2 } from "ink";
236
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
237
+ var FLAG_COLORS = {
238
+ M: "yellow",
239
+ A: "green",
240
+ D: "red",
241
+ R: "cyan",
242
+ C: "cyan",
243
+ U: "magenta",
244
+ "?": "red"
245
+ };
246
+ var FLAG_SYMBOLS = {
247
+ M: "\u25CF",
248
+ A: "\u271A",
249
+ D: "\u2716",
250
+ R: "\xBB",
251
+ C: "\xBB",
252
+ U: "\u2550",
253
+ "?": "?"
254
+ };
255
+ var FileEntry = ({ flag, file }) => {
256
+ const color = FLAG_COLORS[flag] ?? "white";
257
+ const sym = FLAG_SYMBOLS[flag] ?? "\xB7";
258
+ return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate-end", children: [
259
+ /* @__PURE__ */ jsxs2(Text2, { color, children: [
260
+ sym,
261
+ " "
262
+ ] }),
263
+ /* @__PURE__ */ jsx2(Text2, { color: "white", children: file })
264
+ ] });
265
+ };
266
+ var Section = ({ title, files, accentColor, emptyMsg }) => /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", flexGrow: 1, children: [
267
+ /* @__PURE__ */ jsx2(Text2, { color: accentColor, bold: true, underline: true, children: title }),
268
+ files.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: "gray", dimColor: true, children: emptyMsg }) : files.map((f, i) => /* @__PURE__ */ jsx2(FileEntry, { flag: f.flag, file: f.file }, `${f.flag}${f.file}${i}`))
269
+ ] });
270
+ var StatusPanel = ({ staged, unstaged, accentColor, borderStyle }) => /* @__PURE__ */ jsx2(
271
+ Box2,
272
+ {
273
+ borderStyle,
274
+ borderColor: accentColor,
275
+ flexDirection: "column",
276
+ paddingX: 1,
277
+ flexGrow: 1,
278
+ marginBottom: 1,
279
+ children: /* @__PURE__ */ jsxs2(Box2, { flexDirection: "row", gap: 4, children: [
280
+ /* @__PURE__ */ jsx2(
281
+ Section,
282
+ {
283
+ title: "Unstaged Changes",
284
+ files: unstaged,
285
+ accentColor,
286
+ emptyMsg: "nothing to show"
287
+ }
288
+ ),
289
+ /* @__PURE__ */ jsx2(
290
+ Section,
291
+ {
292
+ title: "Staged Changes",
293
+ files: staged,
294
+ accentColor,
295
+ emptyMsg: "nothing staged"
296
+ }
297
+ )
298
+ ] })
299
+ }
300
+ );
301
+ var StatusPanel_default = StatusPanel;
302
+
303
+ // src/components/BranchPanel.js
304
+ import React3 from "react";
305
+ import { Box as Box3, Text as Text3 } from "ink";
306
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
307
+ var BranchPanel = ({ graph, accentColor, treeColor, borderStyle }) => /* @__PURE__ */ jsxs3(
308
+ Box3,
309
+ {
310
+ borderStyle,
311
+ borderColor: accentColor,
312
+ flexDirection: "column",
313
+ paddingX: 1,
314
+ marginBottom: 1,
315
+ children: [
316
+ /* @__PURE__ */ jsx3(Text3, { color: accentColor, bold: true, underline: true, children: "Commit Graph (newest \u2192 oldest)" }),
317
+ graph ? graph.split("\n").map((line, i) => /* @__PURE__ */ jsx3(Text3, { color: treeColor, wrap: "truncate-end", children: line }, i)) : /* @__PURE__ */ jsx3(Text3, { color: "gray", dimColor: true, children: "no commits yet" })
318
+ ]
319
+ }
320
+ );
321
+ var BranchPanel_default = BranchPanel;
322
+
323
+ // src/components/Footer.js
324
+ import React4 from "react";
325
+ import { Box as Box4, Text as Text4 } from "ink";
326
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
327
+ var Hint = ({ keys, desc }) => /* @__PURE__ */ jsxs4(Box4, { marginRight: 3, children: [
328
+ /* @__PURE__ */ jsx4(Text4, { color: "cyan", bold: true, children: keys }),
329
+ /* @__PURE__ */ jsxs4(Text4, { color: "gray", children: [
330
+ " ",
331
+ desc
332
+ ] })
333
+ ] });
334
+ var Footer = () => /* @__PURE__ */ jsxs4(Box4, { borderStyle: "single", borderColor: "gray", paddingX: 1, marginTop: 1, children: [
335
+ /* @__PURE__ */ jsx4(Hint, { keys: "Tab / \u2190\u2192", desc: "navigate" }),
336
+ /* @__PURE__ */ jsx4(Hint, { keys: "\u2191\u2193", desc: "scroll" }),
337
+ /* @__PURE__ */ jsx4(Hint, { keys: "Enter", desc: "select" }),
338
+ /* @__PURE__ */ jsx4(Hint, { keys: "Esc", desc: "back" }),
339
+ /* @__PURE__ */ jsx4(Hint, { keys: "Ctrl+C", desc: "quit" })
340
+ ] });
341
+ var Footer_default = Footer;
342
+
343
+ // src/components/CommitDialog.js
344
+ import React5, { useState } from "react";
345
+ import { Box as Box5, Text as Text5 } from "ink";
346
+ import TextInput from "ink-text-input";
347
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
348
+ var CommitDialog = ({ onBack, accentColor }) => {
349
+ const [message, setMessage] = useState("");
350
+ const [result, setResult] = useState(null);
351
+ const handleSubmit = () => {
352
+ if (!message.trim()) return;
353
+ const r = commit(message.trim());
354
+ setResult(r);
355
+ if (r.ok) {
356
+ setTimeout(() => onBack(), 800);
357
+ }
358
+ };
359
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
360
+ /* @__PURE__ */ jsx5(Text5, { color: accentColor, bold: true, underline: true, children: "Commit Changes" }),
361
+ /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
362
+ /* @__PURE__ */ jsx5(Text5, { color: accentColor, children: " Commit message: " }),
363
+ /* @__PURE__ */ jsx5(TextInput, { value: message, onChange: setMessage, onSubmit: handleSubmit })
364
+ ] }),
365
+ result && !result.ok && /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsxs5(Text5, { color: "red", children: [
366
+ " \u2716 ",
367
+ result.error || "Commit failed"
368
+ ] }) }),
369
+ result && result.ok && /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { color: "green", children: " \u2714 Committed successfully!" }) }),
370
+ /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { color: "gray", dimColor: true, children: " Press Enter to commit \xB7 Esc to go back" }) })
371
+ ] });
372
+ };
373
+ var CommitDialog_default = CommitDialog;
374
+
375
+ // src/components/CheckoutDialog.js
376
+ import React6, { useState as useState2 } from "react";
377
+ import { Box as Box6, Text as Text6 } from "ink";
378
+ import TextInput2 from "ink-text-input";
379
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
380
+ var CheckoutDialog = ({ onBack, accentColor }) => {
381
+ const [query, setQuery] = useState2("");
382
+ const [result, setResult] = useState2(null);
383
+ const branches = getBranches();
384
+ const handleSubmit = () => {
385
+ if (!query.trim()) return;
386
+ const r = checkoutBranch(query.trim());
387
+ setResult(r);
388
+ if (r.ok) {
389
+ setTimeout(() => onBack(), 800);
390
+ }
391
+ };
392
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
393
+ /* @__PURE__ */ jsx6(Text6, { color: accentColor, bold: true, underline: true, children: "Checkout / Create Branch" }),
394
+ /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, children: [
395
+ /* @__PURE__ */ jsx6(Text6, { color: accentColor, children: " Branches: " }),
396
+ /* @__PURE__ */ jsx6(Text6, { color: "white", children: branches.join(" ") })
397
+ ] }),
398
+ /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, children: [
399
+ /* @__PURE__ */ jsx6(Text6, { color: accentColor, children: " Branch name: " }),
400
+ /* @__PURE__ */ jsx6(TextInput2, { value: query, onChange: setQuery, onSubmit: handleSubmit })
401
+ ] }),
402
+ result && !result.ok && /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsxs6(Text6, { color: "red", children: [
403
+ " \u2716 ",
404
+ result.error || "Checkout failed"
405
+ ] }) }),
406
+ result && result.ok && /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsxs6(Text6, { color: "green", children: [
407
+ "\u2714 ",
408
+ result.created ? `Created and checked out '${query}'` : `Checked out '${query}'`
409
+ ] }) }),
410
+ /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { color: "gray", dimColor: true, children: " Enter existing name to switch \xB7 new name to create \xB7 Esc to go back" }) })
411
+ ] });
412
+ };
413
+ var CheckoutDialog_default = CheckoutDialog;
414
+
415
+ // src/components/DeleteDialog.js
416
+ import React7, { useState as useState3 } from "react";
417
+ import { Box as Box7, Text as Text7 } from "ink";
418
+ import TextInput3 from "ink-text-input";
419
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
420
+ var DeleteDialog = ({ onBack, accentColor }) => {
421
+ const [name, setName] = useState3("");
422
+ const [result, setResult] = useState3(null);
423
+ const branches = getBranches();
424
+ const handleSubmit = () => {
425
+ if (!name.trim()) return;
426
+ const r = deleteBranch(name.trim());
427
+ setResult(r);
428
+ if (r.ok) {
429
+ setTimeout(() => onBack(), 800);
430
+ }
431
+ };
432
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
433
+ /* @__PURE__ */ jsx7(Text7, { color: accentColor, bold: true, underline: true, children: "Delete Branch" }),
434
+ /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, children: [
435
+ /* @__PURE__ */ jsx7(Text7, { color: accentColor, children: " Local branches: " }),
436
+ /* @__PURE__ */ jsx7(Text7, { color: "white", children: branches.join(" ") })
437
+ ] }),
438
+ /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, children: [
439
+ /* @__PURE__ */ jsx7(Text7, { color: "red", bold: true, children: " \u26A0 " }),
440
+ /* @__PURE__ */ jsx7(Text7, { color: accentColor, children: "Branch to delete: " }),
441
+ /* @__PURE__ */ jsx7(TextInput3, { value: name, onChange: setName, onSubmit: handleSubmit })
442
+ ] }),
443
+ result && !result.ok && /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsxs7(Text7, { color: "red", children: [
444
+ " \u2716 ",
445
+ result.error || "Delete failed"
446
+ ] }) }),
447
+ result && result.ok && /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsxs7(Text7, { color: "green", children: [
448
+ " \u2714 Branch '",
449
+ name,
450
+ "' deleted."
451
+ ] }) }),
452
+ /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text7, { color: "gray", dimColor: true, children: " Press Enter to delete \xB7 Esc to go back" }) })
453
+ ] });
454
+ };
455
+ var DeleteDialog_default = DeleteDialog;
456
+
457
+ // src/components/StageDialog.js
458
+ import React8, { useState as useState4, useEffect } from "react";
459
+ import { Box as Box8, Text as Text8, useInput } from "ink";
460
+ import SelectInput from "ink-select-input";
461
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
462
+ var Item = ({ isSelected, label }) => /* @__PURE__ */ jsxs8(Text8, { color: isSelected ? "cyan" : "white", bold: isSelected, children: [
463
+ isSelected ? "\u276F " : " ",
464
+ label
465
+ ] });
466
+ var StageDialog = ({ onBack, accentColor }) => {
467
+ const [status, setStatus] = useState4({ staged: [], unstaged: [] });
468
+ const [message, setMessage] = useState4(null);
469
+ const refresh = () => setStatus(getStatus());
470
+ useEffect(() => {
471
+ refresh();
472
+ }, []);
473
+ useInput((_, key) => {
474
+ if (key.escape) onBack();
475
+ });
476
+ const buildItems = () => {
477
+ const all = {
478
+ label: "\u2500\u2500 STAGE/UNSTAGE ALL \u2500\u2500",
479
+ value: "__all__",
480
+ kind: "all"
481
+ };
482
+ const unstagedItems = status.unstaged.map((f) => ({
483
+ label: `[unstaged] ${f.flag !== "?" ? f.flag : "?"} ${f.file}`,
484
+ value: f.file,
485
+ kind: "unstaged",
486
+ file: f.file
487
+ }));
488
+ const stagedItems = status.staged.map((f) => ({
489
+ label: `[staged] ${f.flag} ${f.file}`,
490
+ value: f.file,
491
+ kind: "staged",
492
+ file: f.file
493
+ }));
494
+ return [all, ...unstagedItems, ...stagedItems];
495
+ };
496
+ const handleSelect = async (item) => {
497
+ setMessage(null);
498
+ if (item.kind === "all") {
499
+ const hasUnstaged = status.unstaged.length > 0;
500
+ const r = hasUnstaged ? stageAll() : unstageAll();
501
+ setMessage(r.ok ? "\u2714 Done" : `\u2716 ${r.error}`);
502
+ } else if (item.kind === "unstaged" && item.file) {
503
+ const file = item.file;
504
+ const r = stageFile(file);
505
+ setMessage(r.ok ? `\u2714 Staged: ${file}` : `\u2716 ${r.error}`);
506
+ } else if (item.kind === "staged" && item.file) {
507
+ const file = item.file;
508
+ const r = unstageFile(file);
509
+ setMessage(r.ok ? `\u2714 Unstaged: ${file}` : `\u2716 ${r.error}`);
510
+ }
511
+ refresh();
512
+ };
513
+ const items = buildItems();
514
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
515
+ /* @__PURE__ */ jsx8(Text8, { color: accentColor, bold: true, underline: true, children: "Stage Changes" }),
516
+ /* @__PURE__ */ jsx8(Text8, { color: "gray", dimColor: true, children: "Select a file to toggle staging \xB7 Esc to go back" }),
517
+ items.length === 1 ? /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { color: "gray", dimColor: true, children: " No changes detected." }) }) : /* @__PURE__ */ jsx8(Box8, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx8(
518
+ SelectInput,
519
+ {
520
+ items,
521
+ itemComponent: Item,
522
+ onSelect: handleSelect
523
+ }
524
+ ) }),
525
+ message && /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { color: message.startsWith("\u2714") ? "green" : "red", children: message }) })
526
+ ] });
527
+ };
528
+ var StageDialog_default = StageDialog;
529
+
530
+ // src/components/StashDialog.js
531
+ import React9, { useState as useState5, useEffect as useEffect2 } from "react";
532
+ import { Box as Box9, Text as Text9, useInput as useInput2 } from "ink";
533
+ import SelectInput2 from "ink-select-input";
534
+ import TextInput4 from "ink-text-input";
535
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
536
+ var ACTIONS = [
537
+ { label: "Push stash (save current changes)", value: "push" },
538
+ { label: "Pop stash (apply & remove latest)", value: "pop" },
539
+ { label: "Drop stash (discard latest)", value: "drop" }
540
+ ];
541
+ var Item2 = ({ isSelected, label }) => /* @__PURE__ */ jsxs9(Text9, { color: isSelected ? "cyan" : "white", bold: isSelected, children: [
542
+ isSelected ? "\u276F " : " ",
543
+ label
544
+ ] });
545
+ var StashDialog = ({ onBack, accentColor }) => {
546
+ const [stashes, setStashes] = useState5([]);
547
+ const [mode, setMode] = useState5("menu");
548
+ const [stashMsg, setStashMsg] = useState5("");
549
+ const [result, setResult] = useState5(null);
550
+ const refresh = () => setStashes(getStashList());
551
+ useEffect2(() => {
552
+ refresh();
553
+ }, []);
554
+ useInput2((_, key) => {
555
+ if (key.escape) {
556
+ if (mode === "push") setMode("menu");
557
+ else onBack();
558
+ }
559
+ });
560
+ const handleAction = (item) => {
561
+ setResult(null);
562
+ if (item.value === "push") {
563
+ setMode("push");
564
+ } else if (item.value === "pop") {
565
+ const r = stashPop();
566
+ setResult(r);
567
+ refresh();
568
+ } else if (item.value === "drop") {
569
+ const r = stashDrop(0);
570
+ setResult(r);
571
+ refresh();
572
+ }
573
+ };
574
+ const handlePush = () => {
575
+ const r = stashPush(stashMsg.trim() || void 0);
576
+ setResult(r);
577
+ setStashMsg("");
578
+ setMode("menu");
579
+ refresh();
580
+ };
581
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
582
+ /* @__PURE__ */ jsx9(Text9, { color: accentColor, bold: true, underline: true, children: "Stash Manager" }),
583
+ /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
584
+ /* @__PURE__ */ jsx9(Text9, { color: accentColor, children: " Stash list:" }),
585
+ stashes.length === 0 ? /* @__PURE__ */ jsx9(Text9, { color: "gray", dimColor: true, children: " (empty)" }) : stashes.map((s, i) => /* @__PURE__ */ jsxs9(Text9, { color: "white", children: [
586
+ " ",
587
+ s
588
+ ] }, i))
589
+ ] }),
590
+ mode === "menu" ? /* @__PURE__ */ jsx9(Box9, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx9(SelectInput2, { items: ACTIONS, itemComponent: Item2, onSelect: handleAction }) }) : /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, children: [
591
+ /* @__PURE__ */ jsx9(Text9, { color: accentColor, children: " Stash message (optional): " }),
592
+ /* @__PURE__ */ jsx9(TextInput4, { value: stashMsg, onChange: setStashMsg, onSubmit: handlePush })
593
+ ] }),
594
+ result && /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text9, { color: result.ok ? "green" : "red", children: result.ok ? "\u2714 Done" : `\u2716 ${result.error}` }) }),
595
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text9, { color: "gray", dimColor: true, children: " Esc to go back" }) })
596
+ ] });
597
+ };
598
+ var StashDialog_default = StashDialog;
599
+
600
+ // src/components/PushPullStatus.js
601
+ import React10, { useEffect as useEffect3 } from "react";
602
+ import { Box as Box10, Text as Text10 } from "ink";
603
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
604
+ var PushPullStatus = ({ result, onBack }) => {
605
+ useEffect3(() => {
606
+ if (result) {
607
+ const timer = setTimeout(() => onBack(), 1500);
608
+ return () => clearTimeout(timer);
609
+ }
610
+ }, [result, onBack]);
611
+ if (!result) return null;
612
+ return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", paddingX: 2, paddingY: 1, children: result.ok ? /* @__PURE__ */ jsxs10(Text10, { color: "green", children: [
613
+ "\u2714 ",
614
+ result.output || "Operation completed successfully."
615
+ ] }) : /* @__PURE__ */ jsxs10(Text10, { color: "red", children: [
616
+ "\u2716 ",
617
+ result.error || "Operation failed."
618
+ ] }) });
619
+ };
620
+ var PushPullStatus_default = PushPullStatus;
621
+
622
+ // src/components/ChangelogDialog.js
623
+ import React11, { useState as useState6, useEffect as useEffect4, useCallback } from "react";
624
+ import { Box as Box11, Text as Text11, useInput as useInput3, useStdout } from "ink";
625
+ import Spinner from "ink-spinner";
626
+ import SelectInput3 from "ink-select-input";
627
+ import TextInput5 from "ink-text-input";
628
+
629
+ // src/changelog.js
630
+ import fs from "fs";
631
+ import path from "path";
632
+ import { execSync as execSync2 } from "child_process";
633
+ import semver from "semver";
634
+ import findVersions from "find-versions";
635
+ function semverize(title) {
636
+ title = title.replace(/(\d+\.\d+)\.x/g, "$1.99999");
637
+ title = title.replace(/(\d+)\.x/g, "$1.99999.99999");
638
+ const versions = findVersions(title, { loose: true });
639
+ if (!versions || versions.length === 0) return null;
640
+ const version = versions.sort(semver.compare).pop();
641
+ return semver.valid(semver.coerce(version)) ? semver.coerce(version).version : null;
642
+ }
643
+ function parseChangelogMarkdown(markdown) {
644
+ const lines = markdown.split("\n");
645
+ const sections = [];
646
+ let current = null;
647
+ for (const line of lines) {
648
+ const headingMatch = line.match(/^#{1,3}\s+(.+)/);
649
+ if (headingMatch) {
650
+ const version = semverize(headingMatch[1]);
651
+ if (version) {
652
+ if (current) sections.push(current);
653
+ current = { version, lines: [line] };
654
+ continue;
655
+ }
656
+ }
657
+ if (current) current.lines.push(line);
658
+ }
659
+ if (current) sections.push(current);
660
+ return sections.map((s) => ({ version: s.version, content: s.lines.join("\n").trim() })).filter((s) => semver.valid(s.version)).sort((a, b) => semver.compare(b.version, a.version));
661
+ }
662
+ var CHANGELOG_FILENAMES = [
663
+ "CHANGELOG.md",
664
+ "CHANGELOG",
665
+ "HISTORY.md",
666
+ "HISTORY",
667
+ "ChangeLog.md",
668
+ "ChangeLog",
669
+ "CHANGES.md",
670
+ "CHANGES",
671
+ "RELEASES.md"
672
+ ];
673
+ function readLocalChangelog(dir = process.cwd()) {
674
+ for (const name of CHANGELOG_FILENAMES) {
675
+ const full = path.join(dir, name);
676
+ if (fs.existsSync(full)) {
677
+ return { file: name, content: fs.readFileSync(full, "utf8") };
678
+ }
679
+ }
680
+ return null;
681
+ }
682
+ function getRepoRoot() {
683
+ try {
684
+ return execSync2("git rev-parse --show-toplevel", { encoding: "utf8" }).trim();
685
+ } catch {
686
+ return process.cwd();
687
+ }
688
+ }
689
+ var GITHUB_RAW = "https://raw.githubusercontent.com";
690
+ var GITHUB_API = "https://api.github.com";
691
+ var UA = "git-cvnty/1.0";
692
+ async function fetchText(url) {
693
+ const { default: fetch } = await import("node-fetch");
694
+ const res = await fetch(url, { headers: { "User-Agent": UA } });
695
+ if (!res.ok) throw Object.assign(new Error(`HTTP ${res.status}`), { status: res.status });
696
+ return res.text();
697
+ }
698
+ async function fetchJson(url) {
699
+ const { default: fetch } = await import("node-fetch");
700
+ const res = await fetch(url, {
701
+ headers: { "User-Agent": UA, Accept: "application/vnd.github+json" }
702
+ });
703
+ if (!res.ok) throw Object.assign(new Error(`HTTP ${res.status}`), { status: res.status });
704
+ return res.json();
705
+ }
706
+ async function fetchGitHubChangelog(ownerRepo, branch = "HEAD") {
707
+ for (const filename of ["CHANGELOG.md", "HISTORY.md", "ChangeLog.md", "CHANGES.md"]) {
708
+ try {
709
+ const raw = await fetchText(
710
+ `${GITHUB_RAW}/${ownerRepo}/${branch}/${filename}`
711
+ );
712
+ const versions = parseChangelogMarkdown(raw);
713
+ if (versions.length > 0) return versions;
714
+ } catch {
715
+ }
716
+ }
717
+ try {
718
+ const releases = await fetchJson(`${GITHUB_API}/repos/${ownerRepo}/releases`);
719
+ return releases.filter((r) => semver.valid(semver.clean(r.tag_name))).map((r) => ({
720
+ version: semver.clean(r.tag_name),
721
+ content: `## ${semver.clean(r.tag_name)}
722
+
723
+ ${r.body || "*(no release notes)*"}`
724
+ })).sort((a, b) => semver.compare(b.version, a.version));
725
+ } catch {
726
+ throw new Error(`No changelog found for "${ownerRepo}" on GitHub.`);
727
+ }
728
+ }
729
+ var GITHUB_REPO_REGEX = /([^/:]+\/[^/:]+?)(?:\.git)?$/;
730
+ function repoFromUrl(url) {
731
+ if (!url) return null;
732
+ const m = url.match(GITHUB_REPO_REGEX);
733
+ return m ? m[1] : null;
734
+ }
735
+ function resolveNpmPackage(packageName) {
736
+ try {
737
+ const pkgPath = path.join(process.cwd(), "node_modules", packageName, "package.json");
738
+ const raw = fs.readFileSync(pkgPath, "utf8");
739
+ const info = JSON.parse(raw);
740
+ const repoUrl = typeof info.repository === "string" ? info.repository : info.repository?.url;
741
+ const ownerRepo = repoFromUrl(repoUrl);
742
+ if (!ownerRepo) return null;
743
+ return { ownerRepo, version: info.version };
744
+ } catch {
745
+ return null;
746
+ }
747
+ }
748
+ function parsePackageString(str) {
749
+ if (str.startsWith("@")) {
750
+ const scopedMatch = str.match(/^(@[^@]+)@(\d+\.\d+\.\d+.*)$/);
751
+ if (scopedMatch) {
752
+ const resolved3 = resolveNpmPackage(scopedMatch[1]);
753
+ return {
754
+ packageName: scopedMatch[1],
755
+ ownerRepo: resolved3?.ownerRepo ?? null,
756
+ version: scopedMatch[2]
757
+ };
758
+ }
759
+ const resolved2 = resolveNpmPackage(str);
760
+ if (resolved2) {
761
+ return {
762
+ packageName: str,
763
+ ownerRepo: resolved2.ownerRepo,
764
+ version: resolved2.version
765
+ };
766
+ }
767
+ return null;
768
+ }
769
+ const ghMatch = str.match(/^([^/]+\/[^@]+)@?(\d[\d.]*)?$/);
770
+ if (ghMatch && ghMatch[1].includes("/")) {
771
+ return {
772
+ packageName: ghMatch[1],
773
+ ownerRepo: ghMatch[1],
774
+ version: ghMatch[2] || null
775
+ };
776
+ }
777
+ const pkgMatch = str.match(/^(.+?)@(\d+\.\d+\.\d+.*)$/);
778
+ if (pkgMatch) {
779
+ const resolved2 = resolveNpmPackage(pkgMatch[1]);
780
+ return {
781
+ packageName: pkgMatch[1],
782
+ ownerRepo: resolved2?.ownerRepo ?? null,
783
+ version: pkgMatch[2]
784
+ };
785
+ }
786
+ const resolved = resolveNpmPackage(str);
787
+ if (resolved) {
788
+ return { packageName: str, ownerRepo: resolved.ownerRepo, version: resolved.version };
789
+ }
790
+ return null;
791
+ }
792
+ async function loadChangelog(source, packageQuery) {
793
+ if (source === "local") {
794
+ const root = getRepoRoot();
795
+ const found = readLocalChangelog(root);
796
+ if (!found) {
797
+ return { title: "Local Changelog", versions: [], error: "No CHANGELOG.md found in this repository." };
798
+ }
799
+ const versions = parseChangelogMarkdown(found.content);
800
+ return {
801
+ title: found.file,
802
+ versions,
803
+ error: versions.length === 0 ? "Could not parse any version sections from the changelog." : void 0
804
+ };
805
+ }
806
+ if (source === "package") {
807
+ const info = parsePackageString(packageQuery);
808
+ if (!info) {
809
+ return { title: packageQuery, versions: [], error: `Cannot resolve package "${packageQuery}".` };
810
+ }
811
+ if (!info.ownerRepo) {
812
+ return {
813
+ title: info.packageName,
814
+ versions: [],
815
+ error: `Could not find GitHub repository for "${info.packageName}". Is it installed in node_modules?`
816
+ };
817
+ }
818
+ try {
819
+ const versions = await fetchGitHubChangelog(info.ownerRepo);
820
+ const filtered = info.version ? versions.filter((v) => semver.gt(v.version, info.version)) : versions;
821
+ return {
822
+ title: `${info.packageName} (changes since ${info.version ?? "all"})`,
823
+ versions: filtered.length ? filtered : versions,
824
+ error: filtered.length === 0 && versions.length > 0 ? `All ${versions.length} changelog entries are at or below v${info.version}.` : void 0
825
+ };
826
+ } catch (err) {
827
+ return { title: info.packageName, versions: [], error: err.message };
828
+ }
829
+ }
830
+ return { title: "", versions: [], error: "Unknown source." };
831
+ }
832
+
833
+ // src/components/ChangelogDialog.js
834
+ import { Fragment, jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
835
+ function renderMarkdownLine(line, index) {
836
+ if (!line.trim()) return /* @__PURE__ */ jsx11(Text11, { children: "" }, index);
837
+ if (/^# /.test(line)) {
838
+ return /* @__PURE__ */ jsx11(Text11, { color: "cyan", bold: true, underline: true, children: line.replace(/^# /, "") }, index);
839
+ }
840
+ if (/^## /.test(line)) {
841
+ return /* @__PURE__ */ jsx11(Text11, { color: "cyan", bold: true, children: line.replace(/^## /, "") }, index);
842
+ }
843
+ if (/^### /.test(line)) {
844
+ return /* @__PURE__ */ jsx11(Text11, { color: "magenta", bold: true, children: line.replace(/^### /, "") }, index);
845
+ }
846
+ if (/^#{4,} /.test(line)) {
847
+ return /* @__PURE__ */ jsx11(Text11, { color: "magenta", children: line.replace(/^#{4,} /, "") }, index);
848
+ }
849
+ if (/^[\-\*] /.test(line)) {
850
+ const content = line.slice(2);
851
+ return /* @__PURE__ */ jsxs11(Text11, { children: [
852
+ /* @__PURE__ */ jsx11(Text11, { color: "cyan", children: " \u25CF " }),
853
+ /* @__PURE__ */ jsx11(Text11, { children: inlineFormat(content) })
854
+ ] }, index);
855
+ }
856
+ if (/^\d+\. /.test(line)) {
857
+ const content = line.replace(/^\d+\. /, "");
858
+ return /* @__PURE__ */ jsxs11(Text11, { children: [
859
+ /* @__PURE__ */ jsxs11(Text11, { color: "cyan", children: [
860
+ " ",
861
+ line.match(/^\d+/)[0],
862
+ ". "
863
+ ] }),
864
+ /* @__PURE__ */ jsx11(Text11, { children: inlineFormat(content) })
865
+ ] }, index);
866
+ }
867
+ if (/^( |\t)/.test(line)) {
868
+ return /* @__PURE__ */ jsx11(Text11, { color: "gray", children: line }, index);
869
+ }
870
+ if (/^[-*]{3,}$/.test(line.trim())) {
871
+ return /* @__PURE__ */ jsx11(Text11, { color: "gray", children: "\u2500".repeat(40) }, index);
872
+ }
873
+ return /* @__PURE__ */ jsx11(Text11, { children: inlineFormat(line) }, index);
874
+ }
875
+ function inlineFormat(text) {
876
+ text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
877
+ text = text.replace(/\*\*(.+?)\*\*/g, "$1");
878
+ text = text.replace(/__(.+?)__/g, "$1");
879
+ text = text.replace(/\*(.+?)\*/g, "$1");
880
+ text = text.replace(/_(.+?)_/g, "$1");
881
+ text = text.replace(/`([^`]+)`/g, "`$1`");
882
+ return text;
883
+ }
884
+ var VersionItem = ({ isSelected, label }) => /* @__PURE__ */ jsx11(Box11, { paddingX: 1, children: isSelected ? /* @__PURE__ */ jsxs11(Text11, { color: "cyan", bold: true, children: [
885
+ "\u25B6 ",
886
+ label
887
+ ] }) : /* @__PURE__ */ jsxs11(Text11, { color: "white", dimColor: true, children: [
888
+ " ",
889
+ label
890
+ ] }) });
891
+ var Separator = ({ color = "gray" }) => {
892
+ const { stdout } = useStdout();
893
+ const cols = stdout?.columns ?? 80;
894
+ return /* @__PURE__ */ jsx11(Text11, { color, children: "\u2500".repeat(cols) });
895
+ };
896
+ var ContentPanel = ({ content, scrollOffset, maxLines }) => {
897
+ const lines = (content || "").split("\n");
898
+ const visible = lines.slice(scrollOffset, scrollOffset + maxLines);
899
+ return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", flexGrow: 1, paddingX: 2, overflow: "hidden", children: visible.map((line, i) => renderMarkdownLine(line, i)) });
900
+ };
901
+ var SOURCE_ITEMS = [
902
+ { label: "\u{1F4C4} Local CHANGELOG.md in this repo", value: "local" },
903
+ { label: "\u{1F4E6} npm package / GitHub repo (e.g. owner/repo or package@ver)", value: "package" }
904
+ ];
905
+ var SourceItem = ({ isSelected, label }) => /* @__PURE__ */ jsxs11(Text11, { color: isSelected ? "cyan" : "white", bold: isSelected, children: [
906
+ isSelected ? "\u276F " : " ",
907
+ label
908
+ ] });
909
+ var ChangelogDialog = ({ onBack, accentColor = "cyan" }) => {
910
+ const [mode, setMode] = useState6("select-source");
911
+ const [source, setSource] = useState6(null);
912
+ const [packageQuery, setPackageQuery] = useState6("");
913
+ const [changelogTitle, setChangelogTitle] = useState6("");
914
+ const [versions, setVersions] = useState6([]);
915
+ const [selectedIdx, setSelectedIdx] = useState6(0);
916
+ const [scrollOffset, setScrollOffset] = useState6(0);
917
+ const [errorMsg, setErrorMsg] = useState6("");
918
+ const { stdout } = useStdout();
919
+ const termHeight = stdout?.rows ?? 40;
920
+ const HEADER_ROWS = 7;
921
+ const FOOTER_ROWS = 3;
922
+ const maxContentLines = Math.max(5, termHeight - HEADER_ROWS - FOOTER_ROWS);
923
+ const currentVersion = versions[selectedIdx];
924
+ const contentLines = currentVersion ? currentVersion.content.split("\n") : [];
925
+ const maxScroll = Math.max(0, contentLines.length - maxContentLines);
926
+ useEffect4(() => {
927
+ setScrollOffset(0);
928
+ }, [selectedIdx]);
929
+ const fetchData = useCallback(async (src, query) => {
930
+ setMode("loading");
931
+ setErrorMsg("");
932
+ try {
933
+ const result = await loadChangelog(src, query);
934
+ setChangelogTitle(result.title);
935
+ setVersions(result.versions);
936
+ setSelectedIdx(0);
937
+ if (result.error && result.versions.length === 0) {
938
+ setErrorMsg(result.error);
939
+ setMode("error");
940
+ } else {
941
+ if (result.error) setErrorMsg(result.error);
942
+ setMode("view");
943
+ }
944
+ } catch (err) {
945
+ setErrorMsg(err.message ?? "Unknown error");
946
+ setMode("error");
947
+ }
948
+ }, []);
949
+ useInput3((input, key) => {
950
+ if (key.escape) {
951
+ if (mode === "view" || mode === "error") {
952
+ setMode("select-source");
953
+ setVersions([]);
954
+ setChangelogTitle("");
955
+ setErrorMsg("");
956
+ setPackageQuery("");
957
+ } else if (mode === "input-package") {
958
+ setMode("select-source");
959
+ } else {
960
+ onBack();
961
+ }
962
+ return;
963
+ }
964
+ if (mode === "view") {
965
+ if (versions.length === 0) {
966
+ return;
967
+ }
968
+ if (key.upArrow) {
969
+ if (key.shift) {
970
+ setSelectedIdx((i) => Math.max(0, i - 1));
971
+ } else {
972
+ setScrollOffset((o) => Math.max(0, o - 1));
973
+ }
974
+ }
975
+ if (key.downArrow) {
976
+ if (key.shift) {
977
+ setSelectedIdx((i) => Math.min(versions.length - 1, i + 1));
978
+ } else {
979
+ setScrollOffset((o) => Math.min(maxScroll, o + 1));
980
+ }
981
+ }
982
+ if (key.tab) {
983
+ setSelectedIdx((i) => (i + 1) % versions.length);
984
+ }
985
+ if (key.pageUp) setScrollOffset((o) => Math.max(0, o - Math.floor(maxContentLines / 2)));
986
+ if (key.pageDown) setScrollOffset((o) => Math.min(maxScroll, o + Math.floor(maxContentLines / 2)));
987
+ }
988
+ });
989
+ const handleSourceSelect = (item) => {
990
+ setSource(item.value);
991
+ if (item.value === "local") {
992
+ fetchData("local", null);
993
+ } else {
994
+ setMode("input-package");
995
+ }
996
+ };
997
+ const handlePackageSubmit = () => {
998
+ if (!packageQuery.trim()) return;
999
+ fetchData("package", packageQuery.trim());
1000
+ };
1001
+ const versionItems = versions.map((v) => ({ label: v.version, value: v.version }));
1002
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
1003
+ /* @__PURE__ */ jsxs11(Box11, { paddingX: 2, paddingY: 1, children: [
1004
+ /* @__PURE__ */ jsx11(Text11, { color: accentColor, bold: true, children: "\u{1F4CB} CHANGELOG VIEWER" }),
1005
+ changelogTitle ? /* @__PURE__ */ jsxs11(Text11, { color: "white", dimColor: true, children: [
1006
+ " \xB7 ",
1007
+ changelogTitle
1008
+ ] }) : null
1009
+ ] }),
1010
+ /* @__PURE__ */ jsx11(Separator, { color: accentColor }),
1011
+ mode === "select-source" && /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
1012
+ /* @__PURE__ */ jsx11(Text11, { color: "white", bold: true, children: "Select a changelog source:" }),
1013
+ /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx11(
1014
+ SelectInput3,
1015
+ {
1016
+ items: SOURCE_ITEMS,
1017
+ itemComponent: SourceItem,
1018
+ onSelect: handleSourceSelect
1019
+ }
1020
+ ) }),
1021
+ /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text11, { color: "gray", dimColor: true, children: "Esc to go back" }) })
1022
+ ] }),
1023
+ mode === "input-package" && /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
1024
+ /* @__PURE__ */ jsx11(Text11, { color: accentColor, bold: true, children: "Package / Repository:" }),
1025
+ /* @__PURE__ */ jsx11(Text11, { color: "gray", dimColor: true, children: ' Examples: "react", "facebook/react", "react@18.0.0", "owner/repo@1.2.3"' }),
1026
+ /* @__PURE__ */ jsxs11(Box11, { marginTop: 1, children: [
1027
+ /* @__PURE__ */ jsx11(Text11, { color: accentColor, children: " \u276F " }),
1028
+ /* @__PURE__ */ jsx11(
1029
+ TextInput5,
1030
+ {
1031
+ value: packageQuery,
1032
+ onChange: setPackageQuery,
1033
+ onSubmit: handlePackageSubmit,
1034
+ placeholder: "package@version or owner/repo\u2026"
1035
+ }
1036
+ )
1037
+ ] }),
1038
+ /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text11, { color: "gray", dimColor: true, children: "Enter to fetch \xB7 Esc to go back" }) })
1039
+ ] }),
1040
+ mode === "loading" && /* @__PURE__ */ jsxs11(Box11, { paddingX: 2, paddingY: 1, children: [
1041
+ /* @__PURE__ */ jsx11(Text11, { color: "cyan", children: /* @__PURE__ */ jsx11(Spinner, { type: "dots" }) }),
1042
+ /* @__PURE__ */ jsx11(Text11, { color: "white", children: " Loading changelog\u2026" })
1043
+ ] }),
1044
+ mode === "error" && /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
1045
+ /* @__PURE__ */ jsxs11(Text11, { color: "red", bold: true, children: [
1046
+ "\u2716 ",
1047
+ errorMsg
1048
+ ] }),
1049
+ /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text11, { color: "gray", dimColor: true, children: "Esc to go back" }) })
1050
+ ] }),
1051
+ mode === "view" && /* @__PURE__ */ jsxs11(Fragment, { children: [
1052
+ errorMsg ? /* @__PURE__ */ jsx11(Box11, { paddingX: 2, children: /* @__PURE__ */ jsxs11(Text11, { color: "yellow", children: [
1053
+ "\u26A0 ",
1054
+ errorMsg
1055
+ ] }) }) : null,
1056
+ versions.length === 0 ? /* @__PURE__ */ jsx11(Box11, { paddingX: 2, paddingY: 1, children: /* @__PURE__ */ jsx11(Text11, { color: "gray", dimColor: true, children: "No version entries found." }) }) : /* @__PURE__ */ jsxs11(Box11, { flexDirection: "row", children: [
1057
+ /* @__PURE__ */ jsxs11(
1058
+ Box11,
1059
+ {
1060
+ flexDirection: "column",
1061
+ borderStyle: "round",
1062
+ borderColor: accentColor,
1063
+ width: 14,
1064
+ paddingY: 1,
1065
+ children: [
1066
+ /* @__PURE__ */ jsx11(Box11, { paddingX: 1, marginBottom: 1, children: /* @__PURE__ */ jsx11(Text11, { color: accentColor, bold: true, underline: true, children: "Versions" }) }),
1067
+ versionItems.map((v, i) => /* @__PURE__ */ jsx11(
1068
+ VersionItem,
1069
+ {
1070
+ label: v.label,
1071
+ isSelected: i === selectedIdx
1072
+ },
1073
+ v.value
1074
+ ))
1075
+ ]
1076
+ }
1077
+ ),
1078
+ /* @__PURE__ */ jsxs11(
1079
+ Box11,
1080
+ {
1081
+ flexDirection: "column",
1082
+ flexGrow: 1,
1083
+ borderStyle: "round",
1084
+ borderColor: "gray",
1085
+ paddingY: 1,
1086
+ marginLeft: 1,
1087
+ overflow: "hidden",
1088
+ children: [
1089
+ /* @__PURE__ */ jsxs11(Box11, { paddingX: 2, marginBottom: 1, children: [
1090
+ /* @__PURE__ */ jsxs11(Text11, { color: accentColor, bold: true, children: [
1091
+ "v",
1092
+ currentVersion?.version
1093
+ ] }),
1094
+ /* @__PURE__ */ jsx11(Text11, { color: "gray", dimColor: true, children: ` ${scrollOffset + 1}\u2013${Math.min(scrollOffset + maxContentLines, contentLines.length)} / ${contentLines.length} lines` })
1095
+ ] }),
1096
+ /* @__PURE__ */ jsx11(Separator, { color: "gray" }),
1097
+ /* @__PURE__ */ jsx11(
1098
+ ContentPanel,
1099
+ {
1100
+ content: currentVersion?.content ?? "",
1101
+ scrollOffset,
1102
+ maxLines: maxContentLines
1103
+ }
1104
+ )
1105
+ ]
1106
+ }
1107
+ )
1108
+ ] })
1109
+ ] }),
1110
+ /* @__PURE__ */ jsx11(Separator, { color: accentColor }),
1111
+ /* @__PURE__ */ jsx11(Box11, { paddingX: 2, paddingY: 0, children: mode === "view" ? /* @__PURE__ */ jsx11(Text11, { color: "gray", dimColor: true, children: "Tab / Shift+\u2191\u2193 switch version \xB7 \u2191\u2193 scroll \xB7 PgUp/PgDn fast scroll \xB7 Esc back" }) : /* @__PURE__ */ jsx11(Text11, { color: "gray", dimColor: true, children: "\u2191\u2193 navigate \xB7 Enter select \xB7 Esc back" }) })
1112
+ ] });
1113
+ };
1114
+ var ChangelogDialog_default = ChangelogDialog;
1115
+
1116
+ // src/app.js
1117
+ import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
1118
+ var MENU_ITEMS = [
1119
+ { label: " Stage Changes", value: "stage" },
1120
+ { label: " Commit Changes", value: "commit" },
1121
+ { label: " Push to Remote", value: "push" },
1122
+ { label: " Pull from Remote", value: "pull" },
1123
+ { label: " Checkout / New Branch", value: "checkout" },
1124
+ { label: " Delete Branch", value: "delete" },
1125
+ { label: " Stash Manager", value: "stash" },
1126
+ { label: " View Changelog", value: "changelog" }
1127
+ ];
1128
+ var MenuItem = ({ isSelected, label }) => /* @__PURE__ */ jsx12(Box12, { marginRight: 1, paddingX: 1, children: /* @__PURE__ */ jsx12(
1129
+ Text12,
1130
+ {
1131
+ color: isSelected ? "black" : "cyan",
1132
+ backgroundColor: isSelected ? "cyan" : void 0,
1133
+ bold: isSelected,
1134
+ children: label
1135
+ }
1136
+ ) });
1137
+ var App = ({ logo: showLogo = theme_default.showLogo }) => {
1138
+ const [gitOk] = useState7(() => isGitRepo());
1139
+ const [branch, setBranch] = useState7(() => getCurrentBranch());
1140
+ const [status, setStatus] = useState7(() => getStatus());
1141
+ const [graph, setGraph] = useState7(() => getBranchGraph(20));
1142
+ const [activeView, setActiveView] = useState7("menu");
1143
+ const [opResult, setOpResult] = useState7(null);
1144
+ const refresh = useCallback2(() => {
1145
+ setBranch(getCurrentBranch());
1146
+ setStatus(getStatus());
1147
+ setGraph(getBranchGraph(20));
1148
+ }, []);
1149
+ useEffect5(() => {
1150
+ refresh();
1151
+ const id = setInterval(() => {
1152
+ if (activeView === "menu") refresh();
1153
+ }, 2e3);
1154
+ return () => clearInterval(id);
1155
+ }, [refresh, activeView]);
1156
+ const goBack = useCallback2(() => {
1157
+ setActiveView("menu");
1158
+ setOpResult(null);
1159
+ refresh();
1160
+ }, [refresh]);
1161
+ const handleMenuSelect = (item) => {
1162
+ const { value } = item;
1163
+ if (value === "push") {
1164
+ const result = push();
1165
+ setOpResult({ ...result, action: "Push" });
1166
+ setActiveView("op-result");
1167
+ return;
1168
+ }
1169
+ if (value === "pull") {
1170
+ const result = pull();
1171
+ setOpResult({ ...result, action: "Pull" });
1172
+ setActiveView("op-result");
1173
+ return;
1174
+ }
1175
+ setActiveView(value);
1176
+ };
1177
+ if (!gitOk) {
1178
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
1179
+ /* @__PURE__ */ jsx12(Text12, { color: "red", bold: true, children: "\u2716 Not a git repository." }),
1180
+ /* @__PURE__ */ jsx12(Text12, { color: "gray", children: "Navigate to a directory that contains a .git folder and try again." })
1181
+ ] });
1182
+ }
1183
+ if (activeView === "commit") {
1184
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
1185
+ /* @__PURE__ */ jsx12(Header_default, { branch, showLogo, accentColor: theme_default.accent }),
1186
+ /* @__PURE__ */ jsx12(CommitDialog_default, { onBack: goBack, accentColor: theme_default.accent }),
1187
+ /* @__PURE__ */ jsx12(Footer_default, {})
1188
+ ] });
1189
+ }
1190
+ if (activeView === "checkout") {
1191
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
1192
+ /* @__PURE__ */ jsx12(Header_default, { branch, showLogo, accentColor: theme_default.accent }),
1193
+ /* @__PURE__ */ jsx12(CheckoutDialog_default, { onBack: goBack, accentColor: theme_default.accent }),
1194
+ /* @__PURE__ */ jsx12(Footer_default, {})
1195
+ ] });
1196
+ }
1197
+ if (activeView === "delete") {
1198
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
1199
+ /* @__PURE__ */ jsx12(Header_default, { branch, showLogo, accentColor: theme_default.accent }),
1200
+ /* @__PURE__ */ jsx12(DeleteDialog_default, { onBack: goBack, accentColor: theme_default.accent }),
1201
+ /* @__PURE__ */ jsx12(Footer_default, {})
1202
+ ] });
1203
+ }
1204
+ if (activeView === "stage") {
1205
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
1206
+ /* @__PURE__ */ jsx12(Header_default, { branch, showLogo, accentColor: theme_default.accent }),
1207
+ /* @__PURE__ */ jsx12(StageDialog_default, { onBack: goBack, accentColor: theme_default.accent }),
1208
+ /* @__PURE__ */ jsx12(Footer_default, {})
1209
+ ] });
1210
+ }
1211
+ if (activeView === "stash") {
1212
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
1213
+ /* @__PURE__ */ jsx12(Header_default, { branch, showLogo, accentColor: theme_default.accent }),
1214
+ /* @__PURE__ */ jsx12(StashDialog_default, { onBack: goBack, accentColor: theme_default.accent }),
1215
+ /* @__PURE__ */ jsx12(Footer_default, {})
1216
+ ] });
1217
+ }
1218
+ if (activeView === "changelog") {
1219
+ return /* @__PURE__ */ jsx12(ChangelogDialog_default, { onBack: goBack, accentColor: theme_default.accent });
1220
+ }
1221
+ if (activeView === "op-result") {
1222
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
1223
+ /* @__PURE__ */ jsx12(Header_default, { branch, showLogo, accentColor: theme_default.accent }),
1224
+ /* @__PURE__ */ jsx12(PushPullStatus_default, { result: opResult, onBack: goBack }),
1225
+ /* @__PURE__ */ jsx12(Footer_default, {})
1226
+ ] });
1227
+ }
1228
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
1229
+ /* @__PURE__ */ jsx12(Header_default, { branch, showLogo, accentColor: theme_default.accent }),
1230
+ /* @__PURE__ */ jsxs12(Box12, { flexDirection: "row", gap: 1, marginBottom: 1, children: [
1231
+ /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx12(
1232
+ StatusPanel_default,
1233
+ {
1234
+ staged: status.staged,
1235
+ unstaged: status.unstaged,
1236
+ accentColor: theme_default.accent,
1237
+ borderStyle: theme_default.border
1238
+ }
1239
+ ) }),
1240
+ /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", flexBasis: "55%", children: /* @__PURE__ */ jsx12(
1241
+ BranchPanel_default,
1242
+ {
1243
+ graph,
1244
+ accentColor: theme_default.accent,
1245
+ treeColor: theme_default.tree,
1246
+ borderStyle: theme_default.border
1247
+ }
1248
+ ) })
1249
+ ] }),
1250
+ /* @__PURE__ */ jsxs12(
1251
+ Box12,
1252
+ {
1253
+ borderStyle: theme_default.border,
1254
+ borderColor: theme_default.accent,
1255
+ flexDirection: "column",
1256
+ paddingX: 1,
1257
+ paddingY: 0,
1258
+ children: [
1259
+ /* @__PURE__ */ jsx12(Text12, { color: theme_default.accent, bold: true, children: "Actions" }),
1260
+ /* @__PURE__ */ jsx12(Box12, { flexDirection: "row", flexWrap: "wrap", children: /* @__PURE__ */ jsx12(
1261
+ SelectInput4,
1262
+ {
1263
+ items: MENU_ITEMS,
1264
+ itemComponent: MenuItem,
1265
+ onSelect: handleMenuSelect
1266
+ }
1267
+ ) })
1268
+ ]
1269
+ }
1270
+ ),
1271
+ /* @__PURE__ */ jsx12(Footer_default, {})
1272
+ ] });
1273
+ };
1274
+ var app_default = App;
1275
+
1276
+ // cli.js
1277
+ var cli = meow(`
1278
+ Usage
1279
+ $ cvnty
1280
+
1281
+ Options
1282
+ --no-logo Hide the git-cvnty logo
1283
+
1284
+ Examples
1285
+ $ cvnty
1286
+ $ cvnty --no-logo
1287
+ `, {
1288
+ importMeta: import.meta,
1289
+ flags: {
1290
+ logo: {
1291
+ type: "boolean",
1292
+ default: true
1293
+ }
1294
+ }
1295
+ });
1296
+ render(React13.createElement(app_default, cli.flags));