@jannael/glinter 1.1.0 → 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.
Files changed (3) hide show
  1. package/README.md +93 -8
  2. package/dist/index.js +749 -111
  3. package/package.json +8 -7
package/README.md CHANGED
@@ -1,23 +1,106 @@
1
- # Glinter
2
-
3
- Glinter is a high-performance, transparent Git wrapper built with **Bun**. It enhances the standard `git add` workflow with a beautiful, interactive CLI interface while acting as a seamless pass-through for all other Git commands.
1
+ <p align="center">
2
+ <br>
3
+ <br>
4
+ <a href="https://glinter.jannael.com" target="_blank" rel="noopener noreferrer">
5
+ <picture>
6
+ <img alt="Glinter" src="https://github.com/Jannael/glinter/raw/main/apps/web/public/og.png">
7
+ </picture>
8
+ </a>
9
+ <br>
10
+ <br>
11
+ <br>
12
+ </p>
13
+
14
+ Glinter is a high-performance, transparent Git wrapper built with **Bun**.
4
15
 
5
16
  ## Preview
6
17
 
7
- <video src="https://github.com/user-attachments/assets/f488ac07-f36a-4a56-a533-9598c46596ed" controls="false" autoplay="true" loop="true" muted="true" style="max-width: 100%;">
18
+ <video src="https://github.com/user-attachments/assets/63b401a0-e1e1-453c-9e38-c36cd14e200f" controls="false" autoplay="true" loop="true" muted="true" style="max-width: 100%;">
8
19
  Your browser does not support the video tag.
9
20
  </video>
10
21
 
11
-
12
22
  ## Features
13
23
 
14
- - **Interactive `add`**: When you run `g add`, it presents a color-coded list of your modified, new, and deleted files. You can multi-select exactly what you want to stage using a GUI-like interface in your terminal.
24
+ - **Abbreviation**: You can use `g` instead of `git`.
25
+
26
+ - **Safe by Default**: Automatically filters and prevents accidental staging of sensitive files: `.env` and `node_modules`.
15
27
 
16
28
  - **Transparent Wrapper**: For every other command (like `commit`, `push`, `log`, or `status`), Glinter acts as a direct tunnel to Git. It preserves all original colors, formatting, and interactive features of the native Git CLI.
17
29
 
18
- - **Safe by Default**: Automatically filters and prevents accidental staging of sensitive files: `.env` and `node_modules`.
19
30
 
20
- - **Abbreviation**: You can use `g` instead of `git`.
31
+ ### Commands
32
+
33
+ | Command | Description |
34
+ |---|---|
35
+ | `g add` | Opens an interactive file selector to stage changes, filtering out sensitive files like `.env` and `node_modules` |
36
+ | `g commit` | Opens an interactive prompt to select a commit type and write a commit message |
37
+ | `g switch` | Opens an interactive prompt to switch branches |
38
+ | `g setup` | sets up alias for git and glinter |
39
+ | `g alias` | shows all the aliases |
40
+
41
+ ### Aliases
42
+
43
+ | Alias | Command | Description |
44
+ |---|---|---|
45
+ | gs | git status -sb | Status with short format and branch info |
46
+ | gl | git log --oneline --decorate --graph --all -n 20 | Recent commits graph |
47
+ | gll | git log --stat | Log with file change statistics |
48
+ | gd | git diff --word-diff=color | Word-level diff with colors |
49
+ | gds | git diff --staged --word-diff=color | Staged diff with colors |
50
+ | ga | glinter add | Interactive staging |
51
+ | gaa | git add -A | Add all changes |
52
+ | gc | glinter commit | Interactive commit |
53
+ | gcm | git commit -m | Direct commit message |
54
+ | gca | git commit --amend | Amend last commit |
55
+ | gcan | git commit --amend --no-edit | Amend without editing |
56
+ | gb | git branch | List branches |
57
+ | gba | git branch -a | List all branches |
58
+ | gco | glinter switch | Interactive branch switch |
59
+ | gcb | git checkout -b | Create and switch branch |
60
+ | gpl | git pull | Pull from remote |
61
+ | gplr | git pull --rebase | Pull with rebase |
62
+ | gp | git push | Push to remote |
63
+ | ggpush | git push origin HEAD | Push current branch |
64
+ | gpf | git push --force-with-lease | Force push with lease |
65
+ | gst | git stash | Stash changes |
66
+ | gstp | git stash pop | Pop stash |
67
+ | gstl | git stash list | List stashes |
68
+ | gcl | git clean -fd | Clean untracked files |
69
+ | grh | git reset --hard | Hard reset |
70
+
71
+ ## Quick Start
72
+
73
+ 1. **Install Glinter**:
74
+
75
+ ```bash
76
+ npm install -g @jannael/glinter
77
+ ```
78
+
79
+ 2. **Set up aliases** (optional, recommended):
80
+
81
+ ```bash
82
+ g setup
83
+ ```
84
+
85
+ 3. **Start using Glinter**:
86
+
87
+ ```bash
88
+ g add # Interactive file staging
89
+ g commit # Interactive commit
90
+ g status # Standard git status
91
+ g push # Standard git push
92
+ ```
93
+
94
+
95
+
96
+
97
+ ### Screenshots
98
+
99
+ | `g add` | `g commit` |
100
+ |---|---|
101
+ | ![glinter add](./screenshots/ga.png) | ![glinter commit](./screenshots/gc.png) |
102
+
103
+
21
104
 
22
105
  ## How it works
23
106
 
@@ -52,7 +135,9 @@ npm install -g @jannael/glinter
52
135
  now you can simply run:
53
136
  ```bash
54
137
  g add # Opens the interactive selector
138
+ g commit # Opens commit type + message prompt
55
139
  g add <file> # Runs standard git add <file>
140
+ g commit -m "" # Runs standard git commit -m ""
56
141
  g status # Runs standard git status
57
142
  g push # Runs standard git push
58
143
  ```
package/dist/index.js CHANGED
@@ -90,22 +90,64 @@ var require_src = __commonJS((exports, module) => {
90
90
  module.exports = { cursor, scroll, erase, beep };
91
91
  });
92
92
 
93
- // src/utils/colors.ts
94
- var GREEN = "\x1B[32m";
95
- var YELLOW = "\x1B[33m";
96
- var RED = "\x1B[31m";
97
- var MAGENTA = "\x1B[35m";
98
- var BLUE = "\x1B[34m";
99
- var BLACK = "\x1B[30m";
100
- var BG_YELLOW = "\x1B[43m";
93
+ // apps/cli/error/error-constructor.ts
94
+ function CreateError(name) {
95
+ const capitalize = (text) => text.charAt(0).toUpperCase() + text.slice(1);
96
+ return class extends Error {
97
+ description;
98
+ constructor(message, description) {
99
+ super(capitalize(message));
100
+ this.name = name;
101
+ this.description = description !== undefined ? capitalize(description) : undefined;
102
+ }
103
+ };
104
+ }
105
+
106
+ // apps/cli/error/error-instance.ts
107
+ var NotFound = CreateError("NotFound");
108
+ var Forbidden = CreateError("Forbidden");
109
+ var Conflict = CreateError("Conflict");
110
+ var ServerError = CreateError("ServerError");
111
+ var BadRequest = CreateError("BadRequest");
112
+
113
+ // apps/cli/utils/colors.ts
101
114
  var RESET = "\x1B[0m";
115
+ var GREEN = ({ text }) => `\x1B[32m${text}${RESET}`;
116
+ var YELLOW = ({ text }) => `\x1B[33m${text}${RESET}`;
117
+ var RED = ({ text }) => `\x1B[31m${text}${RESET}`;
118
+ var MAGENTA = ({ text }) => `\x1B[35m${text}${RESET}`;
119
+ var BLUE = ({ text }) => `\x1B[34m${text}${RESET}`;
120
+ var BLACK = ({ text }) => `\x1B[30m${text}${RESET}`;
121
+ var BG_YELLOW = ({ text }) => `\x1B[43m${text}${RESET}`;
122
+
123
+ // apps/cli/utils/icons-terminal.ts
124
+ var X = ({ text }) => RED({ text: `\u2716 ${text}` });
125
+ var CHECK = ({ text }) => `${GREEN({ text: "\u2714" })} ${text}`;
126
+ var WARNING = ({ text }) => BG_YELLOW({ text: BLACK({ text: ` \u26A0 ${text}` }) });
102
127
 
103
- // src/utils/check.ts
104
- var CHECK = `${GREEN}\u2714${RESET}`;
128
+ // apps/cli/error/error-handler.ts
129
+ function errorHandler(error) {
130
+ if (error instanceof ServerError) {
131
+ console.error(X({ text: error.message }));
132
+ if (error.description) {
133
+ console.error(` ${error.description}`);
134
+ }
135
+ return;
136
+ }
137
+ if (error instanceof NotFound) {
138
+ console.error(WARNING({ text: error.message }));
139
+ return;
140
+ }
141
+ console.error(X({ text: "An unexpected error occurred" }));
142
+ if (error instanceof Error) {
143
+ console.error(` ${error.message}`);
144
+ }
145
+ }
105
146
 
106
147
  // node_modules/@clack/core/dist/index.mjs
107
148
  import { styleText as y } from "util";
108
149
  import { stdout as S, stdin as $ } from "process";
150
+ import * as _ from "readline";
109
151
  import P from "readline";
110
152
 
111
153
  // node_modules/fast-string-truncated-width/dist/utils.js
@@ -494,6 +536,7 @@ function wrapAnsi(string, columns, options) {
494
536
 
495
537
  // node_modules/@clack/core/dist/index.mjs
496
538
  var import_sisteransi = __toESM(require_src(), 1);
539
+ import { ReadStream as D } from "tty";
497
540
  function d(r, t, e) {
498
541
  if (!e.some((o) => !o.disabled))
499
542
  return r;
@@ -530,6 +573,28 @@ function w(r, t) {
530
573
  const e = r;
531
574
  e.isTTY && e.setRawMode(t);
532
575
  }
576
+ function z({ input: r = $, output: t = S, overwrite: e = true, hideCursor: s = true } = {}) {
577
+ const i = _.createInterface({ input: r, output: t, prompt: "", tabSize: 1 });
578
+ _.emitKeypressEvents(r, i), r instanceof D && r.isTTY && r.setRawMode(true);
579
+ const n = (o, { name: a, sequence: h }) => {
580
+ const l = String(o);
581
+ if (V([l, a, h], "cancel")) {
582
+ s && t.write(import_sisteransi.cursor.show), process.exit(0);
583
+ return;
584
+ }
585
+ if (!e)
586
+ return;
587
+ const f = a === "return" ? 0 : -1, v = a === "return" ? -1 : 0;
588
+ _.moveCursor(t, f, v, () => {
589
+ _.clearLine(t, 1, () => {
590
+ r.once("keypress", n);
591
+ });
592
+ });
593
+ };
594
+ return s && t.write(import_sisteransi.cursor.hide), r.once("keypress", n), () => {
595
+ r.off("keypress", n), s && t.write(import_sisteransi.cursor.show), r instanceof D && r.isTTY && !Y && r.setRawMode(false), i.terminal = false, i.close();
596
+ };
597
+ }
533
598
  var O = (r) => ("columns" in r) && typeof r.columns == "number" ? r.columns : 80;
534
599
  var A = (r) => ("rows" in r) && typeof r.rows == "number" ? r.rows : 20;
535
600
  function R(r, t, e, s = e) {
@@ -744,9 +809,27 @@ var H = class extends p {
744
809
  }
745
810
  }
746
811
  };
747
- var X = { Y: { type: "year", len: 4 }, M: { type: "month", len: 2 }, D: { type: "day", len: 2 } };
812
+
813
+ class Q extends p {
814
+ get cursor() {
815
+ return this.value ? 0 : 1;
816
+ }
817
+ get _value() {
818
+ return this.cursor === 0;
819
+ }
820
+ constructor(t) {
821
+ super(t, false), this.value = !!t.initialValue, this.on("userInput", () => {
822
+ this.value = this._value;
823
+ }), this.on("confirm", (e) => {
824
+ this.output.write(import_sisteransi.cursor.move(0, -1)), this.value = e, this.state = "submit", this.close();
825
+ }), this.on("cursor", () => {
826
+ this.value = !this.value;
827
+ });
828
+ }
829
+ }
830
+ var X2 = { Y: { type: "year", len: 4 }, M: { type: "month", len: 2 }, D: { type: "day", len: 2 } };
748
831
  function L(r) {
749
- return [...r].map((t) => X[t]);
832
+ return [...r].map((t) => X2[t]);
750
833
  }
751
834
  function Z(r) {
752
835
  const t = new Intl.DateTimeFormat(r, { year: "numeric", month: "2-digit", day: "2-digit" }).formatToParts(new Date(2000, 0, 15)), e = [];
@@ -1048,6 +1131,27 @@ class nt extends p {
1048
1131
  });
1049
1132
  }
1050
1133
  }
1134
+ class at extends p {
1135
+ get userInputWithCursor() {
1136
+ if (this.state === "submit")
1137
+ return this.userInput;
1138
+ const t = this.userInput;
1139
+ if (this.cursor >= t.length)
1140
+ return `${this.userInput}\u2588`;
1141
+ const e = t.slice(0, this.cursor), [s, ...i] = t.slice(this.cursor);
1142
+ return `${e}${y("inverse", s)}${i.join("")}`;
1143
+ }
1144
+ get cursor() {
1145
+ return this._cursor;
1146
+ }
1147
+ constructor(t) {
1148
+ super({ ...t, initialUserInput: t.initialUserInput ?? t.initialValue }), this.on("userInput", (e) => {
1149
+ this._setValue(e);
1150
+ }), this.on("finalize", () => {
1151
+ this.value || (this.value = t.defaultValue), this.value === undefined && (this.value = "");
1152
+ });
1153
+ }
1154
+ }
1051
1155
 
1052
1156
  // node_modules/@clack/prompts/dist/index.mjs
1053
1157
  import { styleText as t, stripVTControlCharacters as ne } from "util";
@@ -1057,6 +1161,7 @@ function Ze() {
1057
1161
  return P2.platform !== "win32" ? P2.env.TERM !== "linux" : !!P2.env.CI || !!P2.env.WT_SESSION || !!P2.env.TERMINUS_SUBLIME || P2.env.ConEmuTask === "{cmd::Cmder}" || P2.env.TERM_PROGRAM === "Terminus-Sublime" || P2.env.TERM_PROGRAM === "vscode" || P2.env.TERM === "xterm-256color" || P2.env.TERM === "alacritty" || P2.env.TERMINAL_EMULATOR === "JetBrains-JediTerm";
1058
1162
  }
1059
1163
  var ee = Ze();
1164
+ var ae = () => process.env.CI === "true";
1060
1165
  var w2 = (e, i) => ee ? e : i;
1061
1166
  var _e = w2("\u25C6", "*");
1062
1167
  var oe = w2("\u25A0", "x");
@@ -1134,7 +1239,7 @@ var Y2 = ({ cursor: e, options: i, style: s, output: r = process.stdout, maxItem
1134
1239
  }
1135
1240
  if (f > $2) {
1136
1241
  let b = 0, x = 0, G2 = f;
1137
- const M2 = e - v, R2 = (j2, D) => et2(h, G2, j2, D, $2);
1242
+ const M2 = e - v, R2 = (j2, D2) => et2(h, G2, j2, D2, $2);
1138
1243
  m ? ({ lineCount: G2, removals: b } = R2(0, M2), G2 > $2 && ({ lineCount: G2, removals: x } = R2(M2 + 1, h.length))) : ({ lineCount: G2, removals: x } = R2(M2 + 1, h.length), G2 > $2 && ({ lineCount: G2, removals: b } = R2(0, M2))), b > 0 && (m = true, h.splice(0, b)), x > 0 && (g = true, h.splice(h.length - x, x));
1139
1244
  }
1140
1245
  const C2 = [];
@@ -1144,10 +1249,49 @@ var Y2 = ({ cursor: e, options: i, style: s, output: r = process.stdout, maxItem
1144
1249
  C2.push(x);
1145
1250
  return g && C2.push(l), C2;
1146
1251
  };
1252
+ var ot2 = (e) => {
1253
+ const i = e.active ?? "Yes", s = e.inactive ?? "No";
1254
+ return new Q({ active: i, inactive: s, signal: e.signal, input: e.input, output: e.output, initialValue: e.initialValue ?? true, render() {
1255
+ const r = e.withGuide ?? u.withGuide, u2 = `${V2(this.state)} `, n = r ? `${t("gray", d2)} ` : "", o = R(e.output, e.message, n, u2), c2 = `${r ? `${t("gray", d2)}
1256
+ ` : ""}${o}
1257
+ `, a = this.value ? i : s;
1258
+ switch (this.state) {
1259
+ case "submit": {
1260
+ const l = r ? `${t("gray", d2)} ` : "";
1261
+ return `${c2}${l}${t("dim", a)}`;
1262
+ }
1263
+ case "cancel": {
1264
+ const l = r ? `${t("gray", d2)} ` : "";
1265
+ return `${c2}${l}${t(["strikethrough", "dim"], a)}${r ? `
1266
+ ${t("gray", d2)}` : ""}`;
1267
+ }
1268
+ default: {
1269
+ const l = r ? `${t("cyan", d2)} ` : "", $2 = r ? t("cyan", E2) : "";
1270
+ return `${c2}${l}${this.value ? `${t("green", z2)} ${i}` : `${t("dim", H2)} ${t("dim", i)}`}${e.vertical ? r ? `
1271
+ ${t("cyan", d2)} ` : `
1272
+ ` : ` ${t("dim", "/")} `}${this.value ? `${t("dim", H2)} ${t("dim", s)}` : `${t("green", z2)} ${s}`}
1273
+ ${$2}
1274
+ `;
1275
+ }
1276
+ }
1277
+ } }).prompt();
1278
+ };
1147
1279
  var pt = (e = "", i) => {
1148
1280
  const s = i?.output ?? process.stdout, r = i?.withGuide ?? u.withGuide ? `${t("gray", E2)} ` : "";
1149
1281
  s.write(`${r}${t("red", e)}
1150
1282
 
1283
+ `);
1284
+ };
1285
+ var mt = (e = "", i) => {
1286
+ const s = i?.output ?? process.stdout, r = i?.withGuide ?? u.withGuide ? `${t("gray", le)} ` : "";
1287
+ s.write(`${r}${e}
1288
+ `);
1289
+ };
1290
+ var gt = (e = "", i) => {
1291
+ const s = i?.output ?? process.stdout, r = i?.withGuide ?? u.withGuide ? `${t("gray", d2)}
1292
+ ${t("gray", E2)} ` : "";
1293
+ s.write(`${r}${e}
1294
+
1151
1295
  `);
1152
1296
  };
1153
1297
  var Q2 = (e, i) => e.split(`
@@ -1206,6 +1350,64 @@ ${r ? t("cyan", E2) : ""}
1206
1350
  }
1207
1351
  } }).prompt();
1208
1352
  };
1353
+ var Ct = (e) => t("magenta", e);
1354
+ var fe = ({ indicator: e = "dots", onCancel: i, output: s = process.stdout, cancelMessage: r, errorMessage: u2, frames: n = ee ? ["\u25D2", "\u25D0", "\u25D3", "\u25D1"] : ["\u2022", "o", "O", "0"], delay: o = ee ? 80 : 120, signal: c2, ...a } = {}) => {
1355
+ const l = ae();
1356
+ let $2, y2, p2 = false, m = false, g = "", S2, h = performance.now();
1357
+ const f = O(s), v = a?.styleFrame ?? Ct, T2 = (_2) => {
1358
+ const A2 = _2 > 1 ? u2 ?? u.messages.error : r ?? u.messages.cancel;
1359
+ m = _2 === 1, p2 && (W2(A2, _2), m && typeof i == "function" && i());
1360
+ }, C2 = () => T2(2), b = () => T2(1), x = () => {
1361
+ process.on("uncaughtExceptionMonitor", C2), process.on("unhandledRejection", C2), process.on("SIGINT", b), process.on("SIGTERM", b), process.on("exit", T2), c2 && c2.addEventListener("abort", b);
1362
+ }, G2 = () => {
1363
+ process.removeListener("uncaughtExceptionMonitor", C2), process.removeListener("unhandledRejection", C2), process.removeListener("SIGINT", b), process.removeListener("SIGTERM", b), process.removeListener("exit", T2), c2 && c2.removeEventListener("abort", b);
1364
+ }, M2 = () => {
1365
+ if (S2 === undefined)
1366
+ return;
1367
+ l && s.write(`
1368
+ `);
1369
+ const _2 = wrapAnsi(S2, f, { hard: true, trim: false }).split(`
1370
+ `);
1371
+ _2.length > 1 && s.write(import_sisteransi2.cursor.up(_2.length - 1)), s.write(import_sisteransi2.cursor.to(0)), s.write(import_sisteransi2.erase.down());
1372
+ }, R2 = (_2) => _2.replace(/\.+$/, ""), j2 = (_2) => {
1373
+ const A2 = (performance.now() - _2) / 1000, k2 = Math.floor(A2 / 60), L2 = Math.floor(A2 % 60);
1374
+ return k2 > 0 ? `[${k2}m ${L2}s]` : `[${L2}s]`;
1375
+ }, D2 = a.withGuide ?? u.withGuide, ie = (_2 = "") => {
1376
+ p2 = true, $2 = z({ output: s }), g = R2(_2), h = performance.now(), D2 && s.write(`${t("gray", d2)}
1377
+ `);
1378
+ let A2 = 0, k2 = 0;
1379
+ x(), y2 = setInterval(() => {
1380
+ if (l && g === S2)
1381
+ return;
1382
+ M2(), S2 = g;
1383
+ const L2 = v(n[A2]);
1384
+ let Z2;
1385
+ if (l)
1386
+ Z2 = `${L2} ${g}...`;
1387
+ else if (e === "timer")
1388
+ Z2 = `${L2} ${g} ${j2(h)}`;
1389
+ else {
1390
+ const Be = ".".repeat(Math.floor(k2)).slice(0, 3);
1391
+ Z2 = `${L2} ${g}${Be}`;
1392
+ }
1393
+ const Ne = wrapAnsi(Z2, f, { hard: true, trim: false });
1394
+ s.write(Ne), A2 = A2 + 1 < n.length ? A2 + 1 : 0, k2 = k2 < 4 ? k2 + 0.125 : 0;
1395
+ }, o);
1396
+ }, W2 = (_2 = "", A2 = 0, k2 = false) => {
1397
+ if (!p2)
1398
+ return;
1399
+ p2 = false, clearInterval(y2), M2();
1400
+ const L2 = A2 === 0 ? t("green", F2) : A2 === 1 ? t("red", oe) : t("red", ue);
1401
+ g = _2 ?? g, k2 || (e === "timer" ? s.write(`${L2} ${g} ${j2(h)}
1402
+ `) : s.write(`${L2} ${g}
1403
+ `)), G2(), $2();
1404
+ };
1405
+ return { start: ie, stop: (_2 = "") => W2(_2, 0), message: (_2 = "") => {
1406
+ g = R2(_2 ?? g);
1407
+ }, cancel: (_2 = "") => W2(_2, 1), error: (_2 = "") => W2(_2, 2), clear: () => W2("", 0, true), get isCancelled() {
1408
+ return m;
1409
+ } };
1410
+ };
1209
1411
  var Ve = { light: w2("\u2500", "-"), heavy: w2("\u2501", "="), block: w2("\u2588", "#") };
1210
1412
  var re = (e, i) => e.includes(`
1211
1413
  `) ? e.split(`
@@ -1253,8 +1455,37 @@ ${a}
1253
1455
  } }).prompt();
1254
1456
  };
1255
1457
  var je = `${t("gray", d2)} `;
1458
+ var Ot = (e) => new at({ validate: e.validate, placeholder: e.placeholder, defaultValue: e.defaultValue, initialValue: e.initialValue, output: e.output, signal: e.signal, input: e.input, render() {
1459
+ const i = e?.withGuide ?? u.withGuide, s = `${`${i ? `${t("gray", d2)}
1460
+ ` : ""}${V2(this.state)} `}${e.message}
1461
+ `, r = e.placeholder ? t("inverse", e.placeholder[0]) + t("dim", e.placeholder.slice(1)) : t(["inverse", "hidden"], "_"), u2 = this.userInput ? this.userInputWithCursor : r, n = this.value ?? "";
1462
+ switch (this.state) {
1463
+ case "error": {
1464
+ const o = this.error ? ` ${t("yellow", this.error)}` : "", c2 = i ? `${t("yellow", d2)} ` : "", a = i ? t("yellow", E2) : "";
1465
+ return `${s.trim()}
1466
+ ${c2}${u2}
1467
+ ${a}${o}
1468
+ `;
1469
+ }
1470
+ case "submit": {
1471
+ const o = n ? ` ${t("dim", n)}` : "", c2 = i ? t("gray", d2) : "";
1472
+ return `${s}${c2}${o}`;
1473
+ }
1474
+ case "cancel": {
1475
+ const o = n ? ` ${t(["strikethrough", "dim"], n)}` : "", c2 = i ? t("gray", d2) : "";
1476
+ return `${s}${c2}${o}${n.trim() ? `
1477
+ ${c2}` : ""}`;
1478
+ }
1479
+ default: {
1480
+ const o = i ? `${t("cyan", d2)} ` : "", c2 = i ? t("cyan", E2) : "";
1481
+ return `${s}${o}${u2}
1482
+ ${c2}
1483
+ `;
1484
+ }
1485
+ }
1486
+ } }).prompt();
1256
1487
 
1257
- // src/utils/multiselect.ts
1488
+ // apps/cli/utils/multiselect.ts
1258
1489
  async function MultiSelect({
1259
1490
  message,
1260
1491
  options
@@ -1271,7 +1502,7 @@ async function MultiSelect({
1271
1502
  return selected;
1272
1503
  }
1273
1504
 
1274
- // src/modules/add/app/add-command.ts
1505
+ // apps/cli/modules/add/app/add-command.ts
1275
1506
  class AddCommand {
1276
1507
  getChangesUseCase;
1277
1508
  stageChangesUseCase;
@@ -1280,37 +1511,46 @@ class AddCommand {
1280
1511
  this.stageChangesUseCase = stageChangesUseCase;
1281
1512
  }
1282
1513
  async execute() {
1283
- const { changes, warnings } = await this.getChangesUseCase.execute();
1284
- if (changes.length === 0) {
1285
- console.log(`${CHECK} All changes are either staged or sensitive (like .env).`);
1286
- return;
1287
- }
1288
- const options = [
1289
- ...changes.map((c2) => ({ value: c2.value, label: c2.label }))
1290
- ];
1291
- const selectedChanges = await MultiSelect({
1292
- message: `Select the changes you want to commit. ${BLUE}[space] to select and${RESET} ${GREEN}[enter] to confirm${RESET} ${MAGENTA}[a] to select all${RESET} ${RED}[esc] to cancel${RESET}`,
1293
- options
1294
- });
1295
- let selected = selectedChanges.map((file) => file.trim());
1296
- if (selected.includes("all")) {
1297
- selected = changes.map((c2) => c2.value);
1298
- }
1299
- if (selected.length > 0) {
1300
- await this.stageChangesUseCase.execute(selected);
1301
- if (warnings.size > 0) {
1302
- console.log(`
1303
- ${BG_YELLOW}${BLACK} WARNING ${RESET}`);
1304
- for (const warning of warnings) {
1305
- console.log(warning);
1514
+ try {
1515
+ const { changes, warnings } = await this.getChangesUseCase.execute();
1516
+ if (changes.length === 0) {
1517
+ console.log(`${CHECK({ text: "All changes are either staged or sensitive (like .env)." })}`);
1518
+ return;
1519
+ }
1520
+ const options = [
1521
+ ...changes.map((c2) => ({ value: c2.value, label: c2.label }))
1522
+ ];
1523
+ const selectedChanges = await MultiSelect({
1524
+ message: `Select the changes you want to commit.
1525
+ ` + BLUE({ text: "[space] to select" }) + `
1526
+ ` + GREEN({ text: "[enter] to confirm" }) + `
1527
+ ` + MAGENTA({ text: "[a] to select all" }) + `
1528
+ ` + RED({ text: "[esc] to cancel" }) + `
1529
+ `,
1530
+ options
1531
+ });
1532
+ let selected = selectedChanges.map((file) => file.trim());
1533
+ if (selected.includes("all")) {
1534
+ selected = changes.map((c2) => c2.value);
1535
+ }
1536
+ if (selected.length > 0) {
1537
+ await this.stageChangesUseCase.execute(selected);
1538
+ if (warnings.size > 0) {
1539
+ console.log(`
1540
+ ${WARNING({ text: " WARNING " })}`);
1541
+ for (const warning of warnings) {
1542
+ console.log(warning);
1543
+ }
1544
+ console.log("");
1306
1545
  }
1307
- console.log("");
1308
1546
  }
1547
+ } catch (error) {
1548
+ errorHandler(error);
1309
1549
  }
1310
1550
  }
1311
1551
  }
1312
1552
 
1313
- // src/modules/add/domain/change.ts
1553
+ // apps/cli/modules/add/domain/change.ts
1314
1554
  class Change {
1315
1555
  props;
1316
1556
  constructor(props) {
@@ -1329,16 +1569,16 @@ class Change {
1329
1569
  const { status, displayPath } = this.props;
1330
1570
  let label = `${status}: ${displayPath}`;
1331
1571
  if (status.includes("M")) {
1332
- label = `${YELLOW}modified:${RESET} ${displayPath}`;
1572
+ label = YELLOW({ text: `modified: ${displayPath}` });
1333
1573
  }
1334
1574
  if (status.includes("A") || status.includes("?")) {
1335
- label = `${GREEN}new file:${RESET} ${displayPath}`;
1575
+ label = GREEN({ text: `new file: ${displayPath}` });
1336
1576
  }
1337
1577
  if (status.includes("D")) {
1338
- label = `${RED}deleted:${RESET} ${displayPath}`;
1578
+ label = RED({ text: `deleted: ${displayPath}` });
1339
1579
  }
1340
1580
  if (status.includes("R")) {
1341
- label = `${MAGENTA}renamed:${RESET} ${displayPath}`;
1581
+ label = MAGENTA({ text: `renamed: ${displayPath}` });
1342
1582
  }
1343
1583
  return label;
1344
1584
  }
@@ -1347,10 +1587,10 @@ class Change {
1347
1587
  }
1348
1588
  getWarning() {
1349
1589
  if (this.value.includes(".env")) {
1350
- return `${YELLOW} .env file hidden${RESET} (Add to .gitignore to avoid leaks)`;
1590
+ return YELLOW({ text: " .env file hidden" }) + " (Add to .gitignore to avoid leaks)";
1351
1591
  }
1352
1592
  if (this.value.includes("node_modules")) {
1353
- return `${YELLOW} node_modules hidden${RESET} (Add to .gitignore to save space)`;
1593
+ return YELLOW({ text: " node_modules hidden" }) + " (Add to .gitignore to save space)";
1354
1594
  }
1355
1595
  return null;
1356
1596
  }
@@ -1377,7 +1617,7 @@ class Change {
1377
1617
  }
1378
1618
  }
1379
1619
 
1380
- // src/modules/add/app/get-changes.use-case.ts
1620
+ // apps/cli/modules/add/app/get-changes.use-case.ts
1381
1621
  class GetChangesUseCase {
1382
1622
  gitRepository;
1383
1623
  constructor(gitRepository) {
@@ -1404,7 +1644,7 @@ class GetChangesUseCase {
1404
1644
  }
1405
1645
  }
1406
1646
 
1407
- // src/modules/add/app/stage-changes.use-case.ts
1647
+ // apps/cli/modules/add/app/stage-changes.use-case.ts
1408
1648
  class StageChangesUseCase {
1409
1649
  gitRepository;
1410
1650
  constructor(gitRepository) {
@@ -1417,16 +1657,19 @@ class StageChangesUseCase {
1417
1657
  }
1418
1658
  }
1419
1659
 
1420
- // src/modules/add/infra/bun-git.repository.ts
1660
+ // apps/cli/modules/add/infra/bun-git.repository.ts
1421
1661
  var {$: $2 } = globalThis.Bun;
1422
-
1423
1662
  class BunGitRepository {
1424
1663
  async getEntries() {
1425
- const output = await $2`git status --porcelain -z`.quiet().text();
1426
- if (!output.trim()) {
1427
- return [];
1664
+ try {
1665
+ const output = await $2`git status --porcelain -z`.quiet().text();
1666
+ if (!output.trim()) {
1667
+ return [];
1668
+ }
1669
+ return output.split("\x00").filter(Boolean);
1670
+ } catch {
1671
+ throw new ServerError("Git status failed", "Could not retrieve repository status");
1428
1672
  }
1429
- return output.split("\x00").filter(Boolean);
1430
1673
  }
1431
1674
  async stageFiles(files) {
1432
1675
  if (files.length === 0)
@@ -1436,12 +1679,12 @@ class BunGitRepository {
1436
1679
  });
1437
1680
  const exitCode = await proc.exited;
1438
1681
  if (exitCode !== 0) {
1439
- throw new Error(`Git add failed with exit code ${exitCode}`);
1682
+ throw new ServerError("Git add failed", `Failed to stage ${files.length} file(s)`);
1440
1683
  }
1441
1684
  }
1442
1685
  }
1443
1686
 
1444
- // src/modules/add/main.ts
1687
+ // apps/cli/modules/add/main.ts
1445
1688
  async function addCommand() {
1446
1689
  const gitRepository = new BunGitRepository;
1447
1690
  const getChangesUseCase = new GetChangesUseCase(gitRepository);
@@ -1450,7 +1693,405 @@ async function addCommand() {
1450
1693
  await addCommand2.execute();
1451
1694
  }
1452
1695
 
1453
- // src/modules/switch/domain/branch.ts
1696
+ // apps/cli/alias.ts
1697
+ var ALIASES = [
1698
+ { name: "gs", command: "status -sb", kind: "git" },
1699
+ {
1700
+ name: "gl",
1701
+ command: "log --oneline --decorate --graph --all -n 20",
1702
+ kind: "git"
1703
+ },
1704
+ { name: "gll", command: "log --stat", kind: "git" },
1705
+ { name: "gd", command: "diff --word-diff=color", kind: "git" },
1706
+ { name: "gds", command: "diff --staged --word-diff=color", kind: "git" },
1707
+ { name: "ga", command: "add", kind: "glinter" },
1708
+ { name: "gaa", command: "add -A", kind: "git" },
1709
+ { name: "gc", command: "commit", kind: "glinter" },
1710
+ { name: "gcm", command: "commit -m", kind: "git" },
1711
+ { name: "gca", command: "commit --amend", kind: "git" },
1712
+ { name: "gcan", command: "commit --amend --no-edit", kind: "git" },
1713
+ { name: "gb", command: "branch", kind: "git" },
1714
+ { name: "gba", command: "branch -a", kind: "git" },
1715
+ { name: "gco", command: "switch", kind: "glinter" },
1716
+ { name: "gcb", command: "checkout -b", kind: "git" },
1717
+ { name: "gpl", command: "pull", kind: "git" },
1718
+ { name: "gplr", command: "pull --rebase", kind: "git" },
1719
+ { name: "gp", command: "push", kind: "git" },
1720
+ { name: "ggpush", command: "push origin HEAD", kind: "git" },
1721
+ { name: "gpf", command: "push --force-with-lease", kind: "git" },
1722
+ { name: "gst", command: "stash", kind: "git" },
1723
+ { name: "gstp", command: "stash pop", kind: "git" },
1724
+ { name: "gstl", command: "stash list", kind: "git" },
1725
+ { name: "gcl", command: "clean -fd", kind: "git" },
1726
+ { name: "grh", command: "reset --hard", kind: "git" }
1727
+ ];
1728
+
1729
+ // apps/cli/modules/alias/main.ts
1730
+ function resolveAlias(name, command) {
1731
+ return { name, value: command };
1732
+ }
1733
+ function printAliases({
1734
+ title = "The following aliases are configured by setup:"
1735
+ } = {}) {
1736
+ console.log(YELLOW({ text: `
1737
+ ${title}
1738
+ ` }));
1739
+ for (const alias of ALIASES) {
1740
+ const tag = alias.kind === "glinter" ? MAGENTA({ text: "[glinter]" }) : BLUE({ text: "[git] " });
1741
+ const { name, value } = resolveAlias(alias.name, alias.command);
1742
+ console.log(` ${tag} ${GREEN({ text: name.padEnd(8) })} \u2192 ${value}`);
1743
+ }
1744
+ console.log("");
1745
+ }
1746
+ async function aliasCommand() {
1747
+ printAliases();
1748
+ }
1749
+
1750
+ // apps/cli/modules/commit/app/commit.use-case.ts
1751
+ class CommitUseCase {
1752
+ commitRepository;
1753
+ constructor(commitRepository) {
1754
+ this.commitRepository = commitRepository;
1755
+ }
1756
+ async execute({ message }) {
1757
+ await this.commitRepository.commit(message);
1758
+ }
1759
+ }
1760
+
1761
+ // apps/cli/commit-options.ts
1762
+ var commitTypeOptions = [
1763
+ {
1764
+ value: "feat",
1765
+ label: `${GREEN({ text: "feat" })}: A new feature`
1766
+ },
1767
+ {
1768
+ value: "fix",
1769
+ label: `${GREEN({ text: "fix" })}: A bug fix`
1770
+ },
1771
+ {
1772
+ value: "chore",
1773
+ label: `${GREEN({ text: "chore" })}: Routine maintenance`
1774
+ },
1775
+ {
1776
+ value: "docs",
1777
+ label: `${GREEN({ text: "docs" })}: Documentation updates`
1778
+ },
1779
+ {
1780
+ value: "refactor",
1781
+ label: `${GREEN({ text: "refactor" })}: Code changes without behavior change`
1782
+ },
1783
+ {
1784
+ value: "test",
1785
+ label: `${GREEN({ text: "test" })}: Add or update tests`
1786
+ },
1787
+ {
1788
+ value: "perf",
1789
+ label: `${GREEN({ text: "perf" })}: Performance improvements`
1790
+ },
1791
+ {
1792
+ value: "style",
1793
+ label: `${GREEN({ text: "style" })}: Formatting or style-only changes`
1794
+ }
1795
+ ];
1796
+
1797
+ // apps/cli/utils/input.ts
1798
+ async function Input({
1799
+ message,
1800
+ placeholder
1801
+ }) {
1802
+ const value = await Ot({
1803
+ message,
1804
+ placeholder,
1805
+ validate(input) {
1806
+ if (!input?.trim()) {
1807
+ return "Commit message is required.";
1808
+ }
1809
+ }
1810
+ });
1811
+ if (q(value)) {
1812
+ pt("Operation cancelled.");
1813
+ process.exit(0);
1814
+ }
1815
+ return value.trim();
1816
+ }
1817
+
1818
+ // apps/cli/utils/select.ts
1819
+ async function Select({
1820
+ message,
1821
+ options
1822
+ }) {
1823
+ const selected = await _t({
1824
+ message,
1825
+ options
1826
+ });
1827
+ if (q(selected)) {
1828
+ pt("Operation cancelled.");
1829
+ process.exit(0);
1830
+ }
1831
+ return selected;
1832
+ }
1833
+
1834
+ // apps/cli/modules/commit/app/commit-command.ts
1835
+ class CommitCommand {
1836
+ commitUseCase;
1837
+ constructor(commitUseCase) {
1838
+ this.commitUseCase = commitUseCase;
1839
+ }
1840
+ async execute() {
1841
+ try {
1842
+ const commitType = await Select({
1843
+ message: "Select the commit type.",
1844
+ options: commitTypeOptions
1845
+ });
1846
+ const commitDescription = await Input({
1847
+ message: "Write your commit message.",
1848
+ placeholder: "e.g. add interactive commit flow"
1849
+ });
1850
+ await this.commitUseCase.execute({
1851
+ message: `${commitType}: ${commitDescription}`
1852
+ });
1853
+ } catch (error) {
1854
+ errorHandler(error);
1855
+ }
1856
+ }
1857
+ }
1858
+
1859
+ // apps/cli/modules/commit/infra/bun-commit.repository.ts
1860
+ class BunCommitRepository {
1861
+ async commit(message) {
1862
+ const proc = Bun.spawn(["git", "commit", "-m", message], {
1863
+ stdio: ["inherit", "inherit", "inherit"]
1864
+ });
1865
+ const exitCode = await proc.exited;
1866
+ if (exitCode !== 0) {
1867
+ throw new ServerError("Git commit failed", "Could not create commit from interactive prompt");
1868
+ }
1869
+ }
1870
+ }
1871
+
1872
+ // apps/cli/modules/commit/main.ts
1873
+ async function commitCommand() {
1874
+ const commitRepository = new BunCommitRepository;
1875
+ const commitUseCase = new CommitUseCase(commitRepository);
1876
+ const commitCommand2 = new CommitCommand(commitUseCase);
1877
+ await commitCommand2.execute();
1878
+ }
1879
+
1880
+ // apps/cli/modules/setup/app/setup-aliases.use-case.ts
1881
+ class SetupAliasesUseCase {
1882
+ aliasRepository;
1883
+ constructor(aliasRepository) {
1884
+ this.aliasRepository = aliasRepository;
1885
+ }
1886
+ async execute() {
1887
+ for (const alias of ALIASES) {
1888
+ const { name, value } = this.resolveAlias(alias.name, alias.command, alias.kind);
1889
+ await this.aliasRepository.setAlias(name, value);
1890
+ }
1891
+ return { total: ALIASES.length };
1892
+ }
1893
+ resolveAlias(name, command, kind) {
1894
+ const aliasCommand2 = kind === "git" ? `git ${command}` : `g ${command}`;
1895
+ return { name, value: aliasCommand2 };
1896
+ }
1897
+ }
1898
+
1899
+ // apps/cli/utils/confirm.ts
1900
+ async function Confirm({
1901
+ message,
1902
+ cancelMessage,
1903
+ exitOnCancel = true
1904
+ }) {
1905
+ const confirmed = await ot2({
1906
+ message
1907
+ });
1908
+ if (q(confirmed)) {
1909
+ if (exitOnCancel) {
1910
+ pt(cancelMessage ?? "Operation cancelled.");
1911
+ process.exit(0);
1912
+ }
1913
+ return null;
1914
+ }
1915
+ return confirmed;
1916
+ }
1917
+
1918
+ // apps/cli/utils/intro.ts
1919
+ function Intro(message) {
1920
+ mt(message);
1921
+ }
1922
+
1923
+ // apps/cli/utils/outro.ts
1924
+ function Outro(message) {
1925
+ gt(message);
1926
+ }
1927
+
1928
+ // apps/cli/utils/spinner.ts
1929
+ function Spinner() {
1930
+ return fe();
1931
+ }
1932
+
1933
+ // apps/cli/modules/setup/app/setup-command.ts
1934
+ class SetupCommand {
1935
+ setupAliasesUseCase;
1936
+ constructor(setupAliasesUseCase) {
1937
+ this.setupAliasesUseCase = setupAliasesUseCase;
1938
+ }
1939
+ async execute() {
1940
+ try {
1941
+ Intro(MAGENTA({ text: " Glinter Setup " }));
1942
+ printAliases({ title: "The following aliases will be set globally:" });
1943
+ const confirmed = await Confirm({
1944
+ message: "Apply these aliases to your global git config?",
1945
+ exitOnCancel: false
1946
+ });
1947
+ if (!confirmed) {
1948
+ Outro("Setup cancelled.");
1949
+ return;
1950
+ }
1951
+ const spinner = Spinner();
1952
+ spinner.start("Writing aliases...");
1953
+ const { total } = await this.setupAliasesUseCase.execute();
1954
+ console.log("");
1955
+ spinner.stop(`${CHECK({ text: `${total} aliases configured successfully.` })}`);
1956
+ Outro(`${GREEN({ text: "GLINTER" })}, if you like the project, give a star on github: https://github.com/jannael/glinter`);
1957
+ console.log("");
1958
+ console.log(MAGENTA({ text: "To use the aliases, please restart your terminal." }));
1959
+ } catch (error) {
1960
+ errorHandler(error);
1961
+ }
1962
+ }
1963
+ }
1964
+
1965
+ // apps/cli/modules/setup/infra/bun-alias-unix.ts
1966
+ import fs from "fs";
1967
+ import os from "os";
1968
+ import path from "path";
1969
+
1970
+ class BunAliasUnix {
1971
+ async setAlias(name, value) {
1972
+ const profilePath = this.getUnixProfilePath();
1973
+ fs.mkdirSync(path.dirname(profilePath), { recursive: true });
1974
+ const current = fs.existsSync(profilePath) ? fs.readFileSync(profilePath, "utf8") : "";
1975
+ const updated = this.upsertUnixProfileAlias(current, name, value);
1976
+ fs.writeFileSync(profilePath, updated);
1977
+ }
1978
+ upsertUnixProfileAlias(content, name, value) {
1979
+ const escapedName = this.escapeRegex(name);
1980
+ const escapedValue = this.escapeSingleQuotes(value);
1981
+ const aliasLine = `alias ${name}='${escapedValue}'`;
1982
+ const aliasRegex = new RegExp(`^alias ${escapedName}=.*\\r?\\n?`, "gm");
1983
+ const cleaned = content.replace(aliasRegex, "").trimEnd();
1984
+ const prefix = cleaned.length > 0 ? `${cleaned}
1985
+ ` : "";
1986
+ return `${prefix}${aliasLine}
1987
+ `;
1988
+ }
1989
+ getUnixProfilePath() {
1990
+ const home = os.homedir();
1991
+ const shellName = path.basename(process.env.SHELL ?? "");
1992
+ const preferredProfile = shellName === "zsh" ? ".zshrc" : shellName === "bash" ? ".bashrc" : ".profile";
1993
+ const candidates = [preferredProfile, ".bashrc", ".zshrc", ".profile"];
1994
+ const uniqueCandidates = [...new Set(candidates)];
1995
+ for (const candidate of uniqueCandidates) {
1996
+ const candidatePath = path.join(home, candidate);
1997
+ if (fs.existsSync(candidatePath)) {
1998
+ return candidatePath;
1999
+ }
2000
+ }
2001
+ return path.join(home, preferredProfile);
2002
+ }
2003
+ escapeRegex(value) {
2004
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2005
+ }
2006
+ escapeSingleQuotes(value) {
2007
+ return value.replace(/'/g, `'"'"'`);
2008
+ }
2009
+ }
2010
+
2011
+ // apps/cli/modules/setup/infra/bun-alias-windows.ts
2012
+ import fs2 from "fs";
2013
+ import os2 from "os";
2014
+ import path2 from "path";
2015
+
2016
+ class BunAliasWindows {
2017
+ async setAlias(name, value) {
2018
+ const psProfilePath = this.getWindowsProfilePath();
2019
+ fs2.mkdirSync(path2.dirname(psProfilePath), { recursive: true });
2020
+ const current = fs2.existsSync(psProfilePath) ? fs2.readFileSync(psProfilePath, "utf8") : "";
2021
+ const updated = this.upsertWindowsProfileFunction(current, name, value);
2022
+ fs2.writeFileSync(psProfilePath, updated);
2023
+ }
2024
+ upsertWindowsProfileFunction(content, name, value) {
2025
+ const escapedName = this.escapeRegex(name);
2026
+ const removeAliasLine = `if (Get-Alias -Name ${name} -ErrorAction SilentlyContinue) { Remove-Item Alias:${name} -Force }`;
2027
+ const functionLine = `function ${name} { ${value} @args }`;
2028
+ const removeAliasRegex = new RegExp(`^if \\(Get-Alias -Name ${escapedName} -ErrorAction SilentlyContinue\\) \\{ Remove-Item Alias:${escapedName} -Force \\}\\r?\\n?`, "gm");
2029
+ const functionRegex = new RegExp(`^function ${escapedName} \\{[^\\r\\n]*\\}\\r?\\n?`, "gm");
2030
+ const cleaned = content.replace(removeAliasRegex, "").replace(functionRegex, "").trimEnd();
2031
+ const prefix = cleaned.length > 0 ? `${cleaned}
2032
+ ` : "";
2033
+ return `${prefix}${removeAliasLine}
2034
+ ${functionLine}
2035
+ `;
2036
+ }
2037
+ escapeRegex(value) {
2038
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2039
+ }
2040
+ getWindowsProfilePath() {
2041
+ return path2.join(this.getWindowsDocumentsPath(), "PowerShell", "Microsoft.PowerShell_profile.ps1");
2042
+ }
2043
+ getWindowsDocumentsPath() {
2044
+ const proc = Bun.spawnSync([
2045
+ "powershell",
2046
+ "-NoProfile",
2047
+ "-Command",
2048
+ "[Environment]::GetFolderPath('MyDocuments')"
2049
+ ], {
2050
+ stdout: "pipe",
2051
+ stderr: "pipe"
2052
+ });
2053
+ if (proc.exitCode === 0) {
2054
+ const docsPath = new TextDecoder().decode(proc.stdout).trim();
2055
+ if (docsPath.length > 0) {
2056
+ return docsPath;
2057
+ }
2058
+ }
2059
+ return path2.join(os2.homedir(), "Documents");
2060
+ }
2061
+ }
2062
+
2063
+ // apps/cli/modules/setup/infra/bun-alias.repository.ts
2064
+ class BunAliasRepository {
2065
+ getOS() {
2066
+ return process.platform === "win32" ? "windows" : "unix";
2067
+ }
2068
+ async setAlias(name, value) {
2069
+ try {
2070
+ const system = this.getOS();
2071
+ if (system === "windows") {
2072
+ const bunAliasWindows = new BunAliasWindows;
2073
+ await bunAliasWindows.setAlias(name, value);
2074
+ }
2075
+ if (system === "unix") {
2076
+ const bunAliasUnix = new BunAliasUnix;
2077
+ await bunAliasUnix.setAlias(name, value);
2078
+ }
2079
+ console.log(` ${MAGENTA({ text: name.padEnd(8) })} set ${GREEN({ text: "successfully".padStart(10) })}`);
2080
+ } catch {
2081
+ throw new ServerError("Unexpected execution error", `Failed to set alias: ${name}`);
2082
+ }
2083
+ }
2084
+ }
2085
+
2086
+ // apps/cli/modules/setup/main.ts
2087
+ async function setupCommand() {
2088
+ const aliasRepository = new BunAliasRepository;
2089
+ const setupAliasesUseCase = new SetupAliasesUseCase(aliasRepository);
2090
+ const setupCommand2 = new SetupCommand(setupAliasesUseCase);
2091
+ await setupCommand2.execute();
2092
+ }
2093
+
2094
+ // apps/cli/modules/switch/domain/branch.ts
1454
2095
  class Branch {
1455
2096
  props;
1456
2097
  constructor(props) {
@@ -1486,7 +2127,7 @@ class Branch {
1486
2127
  }
1487
2128
  }
1488
2129
 
1489
- // src/modules/switch/app/get-branches.use-case.ts
2130
+ // apps/cli/modules/switch/app/get-branches.use-case.ts
1490
2131
  class GetBranchesUseCase {
1491
2132
  branchRepository;
1492
2133
  constructor(branchRepository) {
@@ -1499,7 +2140,7 @@ class GetBranchesUseCase {
1499
2140
  }
1500
2141
  }
1501
2142
 
1502
- // src/modules/switch/app/switch-branch.use-case.ts
2143
+ // apps/cli/modules/switch/app/switch-branch.use-case.ts
1503
2144
  class SwitchBranch {
1504
2145
  branchRepository;
1505
2146
  constructor(branchRepository) {
@@ -1510,23 +2151,7 @@ class SwitchBranch {
1510
2151
  }
1511
2152
  }
1512
2153
 
1513
- // src/utils/select.ts
1514
- async function Select({
1515
- message,
1516
- options
1517
- }) {
1518
- const selected = await _t({
1519
- message,
1520
- options
1521
- });
1522
- if (q(selected)) {
1523
- pt("Operation cancelled.");
1524
- process.exit(0);
1525
- }
1526
- return selected;
1527
- }
1528
-
1529
- // src/modules/switch/app/switch-command.ts
2154
+ // apps/cli/modules/switch/app/switch-command.ts
1530
2155
  class SwitchCommand {
1531
2156
  getBranchesUseCase;
1532
2157
  switchBranch;
@@ -1535,49 +2160,56 @@ class SwitchCommand {
1535
2160
  this.switchBranch = switchBranch;
1536
2161
  }
1537
2162
  async execute() {
1538
- const branches = await this.getBranchesUseCase.execute();
1539
- if (branches.length === 0) {
1540
- console.log(`${CHECK} No branches found.`);
1541
- return;
1542
- }
1543
- const options = [];
1544
- for (const branch of branches) {
1545
- let label = branch.name;
1546
- let value = branch.name;
1547
- if (branch.isHeadBranch)
1548
- continue;
1549
- if (branch.isCurrentBranch) {
1550
- console.log(`${CHECK} Current branch: ${branch.name}`);
1551
- continue;
2163
+ try {
2164
+ const branches = await this.getBranchesUseCase.execute();
2165
+ if (branches.length === 0) {
2166
+ console.log(`${CHECK({ text: "No branches found." })}`);
2167
+ return;
1552
2168
  }
1553
- if (branch.isRemoteBranch) {
1554
- label = branch.name.replace("remotes/origin/", `${GREEN}remote branch:${RESET} `);
1555
- value = branch.name.replace("remotes/origin/", "");
2169
+ const options = [];
2170
+ for (const branch of branches) {
2171
+ let label = branch.name;
2172
+ let value = branch.name;
2173
+ if (branch.isHeadBranch)
2174
+ continue;
2175
+ if (branch.isCurrentBranch) {
2176
+ console.log(`${CHECK({ text: `Current branch: ${branch.name}` })}`);
2177
+ continue;
2178
+ }
2179
+ if (branch.isRemoteBranch) {
2180
+ label = branch.name.replace("remotes/origin/", GREEN({ text: "remote branch: " }));
2181
+ value = branch.name.replace("remotes/origin/", "");
2182
+ }
2183
+ options.push({
2184
+ value,
2185
+ label
2186
+ });
1556
2187
  }
1557
- options.push({
1558
- value,
1559
- label
2188
+ const selectedBranch = await Select({
2189
+ message: "Select the branch you want to switch to.",
2190
+ options
1560
2191
  });
2192
+ await this.switchBranch.execute({ branch: selectedBranch });
2193
+ } catch (error) {
2194
+ errorHandler(error);
1561
2195
  }
1562
- const selectedBranch = await Select({
1563
- message: "Select the branch you want to switch to.",
1564
- options
1565
- });
1566
- await this.switchBranch.execute({ branch: selectedBranch });
1567
2196
  }
1568
2197
  }
1569
2198
 
1570
- // src/modules/switch/infra/bun-switch-repository.ts
2199
+ // apps/cli/modules/switch/infra/bun-switch-repository.ts
1571
2200
  var {$: $3 } = globalThis.Bun;
1572
-
1573
2201
  class BunSwitchRepository {
1574
2202
  async getBranches() {
1575
- const output = await $3`git branch -a`.quiet().text();
1576
- if (!output.trim()) {
1577
- return [];
1578
- }
1579
- return output.split(`
2203
+ try {
2204
+ const output = await $3`git branch -a`.quiet().text();
2205
+ if (!output.trim()) {
2206
+ return [];
2207
+ }
2208
+ return output.split(`
1580
2209
  `).map((branch) => branch.trim());
2210
+ } catch {
2211
+ throw new ServerError("Git branch failed", "Could not retrieve branches");
2212
+ }
1581
2213
  }
1582
2214
  async switchBranch({ branch }) {
1583
2215
  const proc = Bun.spawn(["git", "checkout", branch], {
@@ -1585,12 +2217,12 @@ class BunSwitchRepository {
1585
2217
  });
1586
2218
  const exitCode = await proc.exited;
1587
2219
  if (exitCode !== 0) {
1588
- throw new Error(`Git checkout failed with exit code ${exitCode}`);
2220
+ throw new ServerError("Git checkout failed", `Failed to switch to branch ${branch}`);
1589
2221
  }
1590
2222
  }
1591
2223
  }
1592
2224
 
1593
- // src/modules/switch/main.ts
2225
+ // apps/cli/modules/switch/main.ts
1594
2226
  async function switchCommand() {
1595
2227
  const switchRepo = new BunSwitchRepository;
1596
2228
  const switchBranchUseCase = new SwitchBranch(switchRepo);
@@ -1599,12 +2231,18 @@ async function switchCommand() {
1599
2231
  await switchCommand2.execute();
1600
2232
  }
1601
2233
 
1602
- // src/index.ts
2234
+ // apps/cli/index.ts
1603
2235
  var args = Bun.argv.slice(2);
1604
2236
  if (args[0] === "add" && !args[1])
1605
2237
  await addCommand();
2238
+ else if (args[0] === "commit" && !args[1])
2239
+ await commitCommand();
1606
2240
  else if (args[0] === "switch" && !args[1])
1607
2241
  await switchCommand();
2242
+ else if (args[0] === "alias" && !args[1])
2243
+ await aliasCommand();
2244
+ else if (args[0] === "setup")
2245
+ await setupCommand();
1608
2246
  else {
1609
2247
  const proc = Bun.spawn(["git", ...args], {
1610
2248
  stdio: ["inherit", "inherit", "inherit"]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jannael/glinter",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "A high-performance, transparent Git wrapper with interactive staging",
5
5
  "type": "module",
6
6
  "private": false,
@@ -13,7 +13,7 @@
13
13
  "name": "Jannael",
14
14
  "url": "https://github.com/jannael"
15
15
  },
16
- "homepage": "https://github.com/jannael/glinter#readme",
16
+ "homepage": "https://glinter.jannael.com",
17
17
  "bugs": {
18
18
  "url": "https://github.com/jannael/glinter/issues"
19
19
  },
@@ -34,12 +34,13 @@
34
34
  ".": "./dist/index.js"
35
35
  },
36
36
  "scripts": {
37
- "dev": "bun run src/index.ts",
38
- "build": "bun build ./src/index.ts --outfile=./dist/index.js --target=bun",
39
- "prepublishOnly": "bun run build",
37
+ "build:cli": "bun build ./apps/cli/index.ts --outfile=./dist/index.js --target=bun",
38
+ "prepublishOnly": "bun run build:cli",
39
+
40
40
  "test": "vitest",
41
41
  "lint": "biome check",
42
- "lint:fix": "biome format --write"
42
+ "lint:fix": "biome format --write",
43
+ "type-check": "bunx tsc --noEmit"
43
44
  },
44
45
  "bin": {
45
46
  "g": "./dist/index.js"
@@ -59,4 +60,4 @@
59
60
  "dependencies": {
60
61
  "@clack/prompts": "1.2.0"
61
62
  }
62
- }
63
+ }