git-thing 1.0.2 → 1.0.6

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/app.d.ts CHANGED
@@ -1,6 +1,2 @@
1
1
  import React from 'react';
2
- type Props = {
3
- name: string | undefined;
4
- };
5
- export default function App({ name }: Props): React.JSX.Element;
6
- export {};
2
+ export default function App(): React.JSX.Element;
package/dist/app.js CHANGED
@@ -1,15 +1,48 @@
1
- import React from 'react';
2
- import { Text, useApp, useInput } from 'ink';
3
- export default function App({ name = 'Stranger' }) {
1
+ import React, { useState, useEffect } from 'react';
2
+ import { Box, Text, useApp, useInput } from 'ink';
3
+ import { BranchList } from './components/BranchList.js';
4
+ import { Settings } from './components/Settings.js';
5
+ import { useSettings } from './hooks/useSettings.js';
6
+ import { theme } from './constants/theme.js';
7
+ export default function App() {
4
8
  const { exit } = useApp();
9
+ const { settings, updateSetting } = useSettings();
10
+ const [activeTab, setActiveTab] = useState('branches');
11
+ const [statusMessage, setStatusMessage] = useState(null);
12
+ // Auto-clear status message after 3s
13
+ useEffect(() => {
14
+ if (!statusMessage) {
15
+ return;
16
+ }
17
+ const timer = setTimeout(() => {
18
+ setStatusMessage(null);
19
+ }, 3000);
20
+ return () => clearTimeout(timer);
21
+ }, [statusMessage]);
5
22
  useInput((input, key) => {
6
- if (key.escape || (key.ctrl && input === 'c')) {
23
+ if (input === 'q' || (key.ctrl && input === 'c')) {
7
24
  exit();
8
25
  }
26
+ // Tab key switches tabs (but not Shift+Tab which is used for mode switching)
27
+ if (key.tab && !key.shift) {
28
+ setActiveTab((t) => (t === 'branches' ? 'settings' : 'branches'));
29
+ }
9
30
  });
10
- return (React.createElement(Text, null,
11
- "Hello, ",
12
- React.createElement(Text, { color: "green" }, name),
13
- '\n',
14
- React.createElement(Text, { dimColor: true }, "Press ESC or Ctrl+C to exit")));
31
+ const handleStatusChange = (message, type) => {
32
+ if (message) {
33
+ setStatusMessage({ text: message, type });
34
+ }
35
+ else {
36
+ setStatusMessage(null);
37
+ }
38
+ };
39
+ return (React.createElement(Box, { flexDirection: "column" },
40
+ React.createElement(Box, { marginBottom: 1 },
41
+ React.createElement(Text, { bold: activeTab === 'branches', color: activeTab === 'branches' ? theme.tabs.activeColor : theme.tabs.inactiveColor }, "[Branches]"),
42
+ React.createElement(Text, null, " "),
43
+ React.createElement(Text, { bold: activeTab === 'settings', color: activeTab === 'settings' ? theme.tabs.activeColor : theme.tabs.inactiveColor }, "[Settings]")),
44
+ React.createElement(Box, { flexDirection: "column", flexGrow: 1 }, activeTab === 'branches' ? (React.createElement(BranchList, { baseBranch: settings.baseBranch, autoStash: settings.autoStash, isActive: activeTab === 'branches', onStatusChange: handleStatusChange })) : (React.createElement(Settings, { baseBranch: settings.baseBranch, autoStash: settings.autoStash, onBaseBranchChange: (value) => updateSetting('baseBranch', value), onAutoStashChange: (value) => updateSetting('autoStash', value), isActive: activeTab === 'settings' }))),
45
+ React.createElement(Box, { marginTop: 1, height: 1 }, statusMessage ? (React.createElement(Text, { color: statusMessage.type === 'success'
46
+ ? theme.status.successColor
47
+ : theme.status.errorColor }, statusMessage.text)) : (React.createElement(Text, { dimColor: true }, "Press q to exit")))));
15
48
  }
package/dist/cli.js CHANGED
@@ -1,24 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  import React from 'react';
3
3
  import { render } from 'ink';
4
- import meow from 'meow';
5
4
  import App from './app.js';
6
- const cli = meow(`
7
- Usage
8
- $ Git-thing
9
-
10
- Options
11
- --name Your name
12
-
13
- Examples
14
- $ Git-thing --name=Jane
15
- Hello, Jane
16
- `, {
17
- importMeta: import.meta,
18
- flags: {
19
- name: {
20
- type: 'string',
21
- },
22
- },
23
- });
24
- render(React.createElement(App, { name: cli.flags.name }));
5
+ render(React.createElement(App, null));
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ type BranchListProps = {
3
+ baseBranch: string;
4
+ autoStash: boolean;
5
+ isActive: boolean;
6
+ onStatusChange: (message: string | null, type: 'success' | 'error') => void;
7
+ };
8
+ export declare function BranchList({ baseBranch, autoStash, isActive, onStatusChange, }: BranchListProps): React.JSX.Element;
9
+ export {};
@@ -0,0 +1,93 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { Box, Text, useInput } from 'ink';
3
+ import { useGitBranches } from '../hooks/useGitBranches.js';
4
+ import { theme } from '../constants/theme.js';
5
+ export function BranchList({ baseBranch, autoStash, isActive, onStatusChange, }) {
6
+ const { branches, loading, error, checkout, rebaseMaster } = useGitBranches();
7
+ const [selectedIndex, setSelectedIndex] = useState(0);
8
+ const [mode, setMode] = useState('selection');
9
+ const [rebasedBranch, setRebasedBranch] = useState(null);
10
+ // Clear rebased indicator after 3 seconds
11
+ useEffect(() => {
12
+ if (!rebasedBranch) {
13
+ return;
14
+ }
15
+ const timer = setTimeout(() => {
16
+ setRebasedBranch(null);
17
+ }, 3000);
18
+ return () => clearTimeout(timer);
19
+ }, [rebasedBranch]);
20
+ const cycleMode = () => {
21
+ setMode((m) => (m === 'selection' ? 'rebase' : 'selection'));
22
+ };
23
+ useInput((_input, key) => {
24
+ // SHIFT+TAB to switch modes within branch list
25
+ if (key.shift && key.tab) {
26
+ cycleMode();
27
+ return;
28
+ }
29
+ if (key.upArrow) {
30
+ setSelectedIndex((i) => (i > 0 ? i - 1 : branches.length - 1));
31
+ }
32
+ else if (key.downArrow) {
33
+ setSelectedIndex((i) => (i < branches.length - 1 ? i + 1 : 0));
34
+ }
35
+ else if (key.return) {
36
+ const branch = branches[selectedIndex];
37
+ if (branch && !branch.isCurrent) {
38
+ if (mode === 'selection') {
39
+ checkout(branch.name).catch((err) => {
40
+ onStatusChange(err.message, 'error');
41
+ });
42
+ }
43
+ else if (mode === 'rebase') {
44
+ const branchName = branch.name;
45
+ rebaseMaster({ baseBranch, autoStash })
46
+ .then(() => {
47
+ setRebasedBranch(branchName);
48
+ })
49
+ .catch((err) => {
50
+ onStatusChange(err.message, 'error');
51
+ });
52
+ }
53
+ }
54
+ }
55
+ }, { isActive });
56
+ if (loading) {
57
+ return React.createElement(Text, { dimColor: true }, "Loading branches...");
58
+ }
59
+ if (error) {
60
+ return React.createElement(Text, { color: theme.error.color },
61
+ "Error: ",
62
+ error);
63
+ }
64
+ if (branches.length === 0) {
65
+ return React.createElement(Text, { dimColor: true }, "No branches found");
66
+ }
67
+ const modeConfig = theme.modes[mode];
68
+ return (React.createElement(Box, { flexDirection: "column" },
69
+ React.createElement(Box, { alignSelf: "flex-start", borderStyle: theme.heading.borderStyle, borderColor: theme.heading.borderColor, paddingX: theme.heading.paddingX, paddingY: theme.heading.paddingY, marginBottom: theme.heading.marginBottom },
70
+ React.createElement(Text, { bold: theme.heading.bold, color: theme.heading.color }, theme.heading.text)),
71
+ React.createElement(Box, { marginBottom: 1 },
72
+ React.createElement(Text, null, "MODE: "),
73
+ React.createElement(Text, { bold: true, color: modeConfig.color }, modeConfig.label),
74
+ mode === 'rebase' && React.createElement(Text, { dimColor: true },
75
+ " (base: ",
76
+ baseBranch,
77
+ ")")),
78
+ branches.map((branch, index) => {
79
+ const isSelected = index === selectedIndex;
80
+ const wasRebased = branch.name === rebasedBranch;
81
+ return (React.createElement(Box, { key: branch.name },
82
+ React.createElement(Text, { color: branch.isCurrent ? theme.branch.currentColor : undefined }, branch.isCurrent ? '* ' : ' '),
83
+ React.createElement(Text, { color: isSelected ? modeConfig.color : undefined, bold: isSelected && theme.branch.selectedBold }, branch.name),
84
+ wasRebased && (React.createElement(Text, { bold: true, color: theme.modes.rebase.color },
85
+ ' ',
86
+ "REBASED"))));
87
+ }),
88
+ React.createElement(Box, { marginTop: 1 },
89
+ React.createElement(Text, { dimColor: theme.help.dimmed },
90
+ "\u2191\u2193 navigate Enter ",
91
+ mode === 'selection' ? 'checkout' : 'rebase',
92
+ " Shift+Tab switch mode Tab switch tab q quit"))));
93
+ }
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ type SettingsProps = {
3
+ baseBranch: string;
4
+ autoStash: boolean;
5
+ onBaseBranchChange: (value: string) => void;
6
+ onAutoStashChange: (value: boolean) => void;
7
+ isActive: boolean;
8
+ };
9
+ export declare function Settings({ baseBranch, autoStash, onBaseBranchChange, onAutoStashChange, isActive, }: SettingsProps): React.JSX.Element;
10
+ export {};
@@ -0,0 +1,67 @@
1
+ import React, { useState } from 'react';
2
+ import { Box, Text, useInput } from 'ink';
3
+ import { theme } from '../constants/theme.js';
4
+ export function Settings({ baseBranch, autoStash, onBaseBranchChange, onAutoStashChange, isActive, }) {
5
+ const [selectedItem, setSelectedItem] = useState('baseBranch');
6
+ const [editValue, setEditValue] = useState(baseBranch);
7
+ const [isEditing, setIsEditing] = useState(false);
8
+ const settingItems = ['baseBranch', 'autoStash'];
9
+ useInput((input, key) => {
10
+ if (isEditing) {
11
+ if (key.return) {
12
+ if (editValue.trim()) {
13
+ onBaseBranchChange(editValue.trim());
14
+ }
15
+ setIsEditing(false);
16
+ }
17
+ else if (key.escape) {
18
+ setEditValue(baseBranch);
19
+ setIsEditing(false);
20
+ }
21
+ else if (key.backspace || key.delete) {
22
+ setEditValue((v) => v.slice(0, -1));
23
+ }
24
+ else if (input && !key.ctrl && !key.meta && !key.tab) {
25
+ setEditValue((v) => v + input);
26
+ }
27
+ return;
28
+ }
29
+ if (key.upArrow) {
30
+ const currentIndex = settingItems.indexOf(selectedItem);
31
+ const newIndex = currentIndex > 0 ? currentIndex - 1 : settingItems.length - 1;
32
+ setSelectedItem(settingItems[newIndex]);
33
+ }
34
+ else if (key.downArrow) {
35
+ const currentIndex = settingItems.indexOf(selectedItem);
36
+ const newIndex = currentIndex < settingItems.length - 1 ? currentIndex + 1 : 0;
37
+ setSelectedItem(settingItems[newIndex]);
38
+ }
39
+ else if (key.return || input === ' ') {
40
+ if (selectedItem === 'baseBranch') {
41
+ setIsEditing(true);
42
+ setEditValue(baseBranch);
43
+ }
44
+ else if (selectedItem === 'autoStash') {
45
+ onAutoStashChange(!autoStash);
46
+ }
47
+ }
48
+ }, { isActive });
49
+ const modeConfig = theme.modes.settings;
50
+ return (React.createElement(Box, { flexDirection: "column" },
51
+ React.createElement(Box, { alignSelf: "flex-start", borderStyle: theme.heading.borderStyle, borderColor: modeConfig.color, paddingX: theme.heading.paddingX, paddingY: theme.heading.paddingY, marginBottom: 1 },
52
+ React.createElement(Text, { bold: true, color: modeConfig.color }, "SETTINGS")),
53
+ React.createElement(Box, null,
54
+ React.createElement(Text, { color: selectedItem === 'baseBranch' ? modeConfig.color : undefined }, selectedItem === 'baseBranch' ? '▸ ' : ' '),
55
+ React.createElement(Text, null, "Base Branch: "),
56
+ isEditing ? (React.createElement(Text, { color: "yellow" },
57
+ editValue,
58
+ "\u2588")) : (React.createElement(Text, { bold: selectedItem === 'baseBranch', color: modeConfig.color }, baseBranch))),
59
+ React.createElement(Box, null,
60
+ React.createElement(Text, { color: selectedItem === 'autoStash' ? modeConfig.color : undefined }, selectedItem === 'autoStash' ? '▸ ' : ' '),
61
+ React.createElement(Text, null, "Auto Stash: "),
62
+ React.createElement(Text, { bold: selectedItem === 'autoStash', color: autoStash ? 'green' : 'red' }, autoStash ? 'ON' : 'OFF')),
63
+ React.createElement(Box, { marginTop: 1 },
64
+ React.createElement(Text, { dimColor: theme.help.dimmed }, isEditing
65
+ ? 'Enter save Esc cancel'
66
+ : '↑↓ navigate Enter/Space toggle Tab switch tab q quit'))));
67
+ }
@@ -0,0 +1,45 @@
1
+ export declare const theme: {
2
+ heading: {
3
+ text: string;
4
+ color: string;
5
+ bold: boolean;
6
+ borderStyle: "double";
7
+ borderColor: string;
8
+ paddingX: number;
9
+ paddingY: number;
10
+ marginBottom: number;
11
+ };
12
+ branch: {
13
+ currentColor: string;
14
+ selectedColor: string;
15
+ selectedBold: boolean;
16
+ };
17
+ help: {
18
+ dimmed: boolean;
19
+ };
20
+ error: {
21
+ color: string;
22
+ };
23
+ modes: {
24
+ selection: {
25
+ label: string;
26
+ color: string;
27
+ };
28
+ rebase: {
29
+ label: string;
30
+ color: string;
31
+ };
32
+ settings: {
33
+ label: string;
34
+ color: string;
35
+ };
36
+ };
37
+ status: {
38
+ successColor: string;
39
+ errorColor: string;
40
+ };
41
+ tabs: {
42
+ activeColor: string;
43
+ inactiveColor: string;
44
+ };
45
+ };
@@ -0,0 +1,55 @@
1
+ // Terminal colors: black, red, green, yellow, blue, magenta, cyan, white, gray
2
+ // Or hex: #ff0000, rgb: rgb(255,0,0)
3
+ export const theme = {
4
+ // Heading
5
+ heading: {
6
+ text: 'BRANCHES',
7
+ color: 'blue',
8
+ bold: true,
9
+ // Border: 'single', 'double', 'round', 'bold', 'classic', 'singleDouble', 'doubleSingle'
10
+ borderStyle: 'double',
11
+ borderColor: 'blue',
12
+ paddingX: 2, // horizontal padding inside box
13
+ paddingY: 0, // vertical padding inside box
14
+ marginBottom: 0, // space below heading
15
+ },
16
+ // Branch list
17
+ branch: {
18
+ currentColor: 'green',
19
+ selectedColor: 'blue',
20
+ selectedBold: true,
21
+ },
22
+ // Help text
23
+ help: {
24
+ dimmed: true,
25
+ },
26
+ // Errors
27
+ error: {
28
+ color: 'red',
29
+ },
30
+ // Modes
31
+ modes: {
32
+ selection: {
33
+ label: 'Selection',
34
+ color: 'blue',
35
+ },
36
+ rebase: {
37
+ label: 'Rebase Master',
38
+ color: 'magenta',
39
+ },
40
+ settings: {
41
+ label: 'Settings',
42
+ color: 'cyan',
43
+ },
44
+ },
45
+ // Status messages
46
+ status: {
47
+ successColor: 'green',
48
+ errorColor: 'red',
49
+ },
50
+ // Tabs
51
+ tabs: {
52
+ activeColor: 'cyan',
53
+ inactiveColor: 'gray',
54
+ },
55
+ };
@@ -0,0 +1,17 @@
1
+ export type Branch = {
2
+ name: string;
3
+ isCurrent: boolean;
4
+ };
5
+ export type RebaseOptions = {
6
+ baseBranch: string;
7
+ autoStash: boolean;
8
+ };
9
+ export type UseGitBranchesResult = {
10
+ branches: Branch[];
11
+ loading: boolean;
12
+ error: string | null;
13
+ refresh: () => void;
14
+ checkout: (branchName: string) => Promise<void>;
15
+ rebaseMaster: (options: RebaseOptions) => Promise<string>;
16
+ };
17
+ export declare function useGitBranches(): UseGitBranchesResult;
@@ -0,0 +1,84 @@
1
+ import { useState, useEffect, useCallback } from 'react';
2
+ import { execFile } from 'child_process';
3
+ export function useGitBranches() {
4
+ const [branches, setBranches] = useState([]);
5
+ const [loading, setLoading] = useState(true);
6
+ const [error, setError] = useState(null);
7
+ const loadBranches = useCallback(() => {
8
+ execFile('git', ['branch'], (err, stdout, stderr) => {
9
+ setLoading(false);
10
+ if (err) {
11
+ if (stderr.includes('not a git repository')) {
12
+ setError('Not a git repository');
13
+ }
14
+ else {
15
+ setError(stderr || err.message);
16
+ }
17
+ return;
18
+ }
19
+ const parsedBranches = stdout
20
+ .split('\n')
21
+ .filter((line) => line.trim())
22
+ .map((line) => {
23
+ const isCurrent = line.startsWith('*');
24
+ const name = line.replace(/^\*?\s+/, '');
25
+ return { name, isCurrent };
26
+ });
27
+ setBranches(parsedBranches);
28
+ });
29
+ }, []);
30
+ const checkout = useCallback((branchName) => {
31
+ return new Promise((resolve, reject) => {
32
+ execFile('git', ['checkout', branchName], (err, _stdout, stderr) => {
33
+ if (err) {
34
+ reject(new Error(stderr || err.message));
35
+ return;
36
+ }
37
+ loadBranches();
38
+ resolve();
39
+ });
40
+ });
41
+ }, [loadBranches]);
42
+ const rebaseMaster = useCallback(({ baseBranch, autoStash }) => {
43
+ return new Promise((resolve, reject) => {
44
+ const currentBranch = branches.find((b) => b.isCurrent)?.name;
45
+ if (currentBranch === 'master') {
46
+ // On master branch: just pull --rebase
47
+ execFile('git', ['pull', '--rebase', 'origin', 'master'], (err, stdout, stderr) => {
48
+ if (err) {
49
+ reject(new Error(stderr || err.message));
50
+ return;
51
+ }
52
+ loadBranches();
53
+ resolve(stdout || 'Successfully pulled and rebased master');
54
+ });
55
+ }
56
+ else {
57
+ // On other branch: fetch origin master:master, then rebase
58
+ execFile('git', ['fetch', 'origin', 'master:master'], (fetchErr, _fetchStdout, fetchStderr) => {
59
+ if (fetchErr) {
60
+ reject(new Error(fetchStderr || fetchErr.message));
61
+ return;
62
+ }
63
+ const rebaseArgs = ['rebase'];
64
+ if (autoStash) {
65
+ rebaseArgs.push('--autostash');
66
+ }
67
+ rebaseArgs.push(baseBranch);
68
+ execFile('git', rebaseArgs, (err, stdout, stderr) => {
69
+ if (err) {
70
+ reject(new Error(stderr || err.message));
71
+ return;
72
+ }
73
+ loadBranches();
74
+ resolve(stdout || `Successfully rebased onto ${baseBranch}`);
75
+ });
76
+ });
77
+ }
78
+ });
79
+ }, [loadBranches, branches]);
80
+ useEffect(() => {
81
+ loadBranches();
82
+ }, [loadBranches]);
83
+ return { branches, loading, error, refresh: loadBranches, checkout, rebaseMaster };
84
+ }
@@ -0,0 +1,9 @@
1
+ type Settings = {
2
+ baseBranch: string;
3
+ autoStash: boolean;
4
+ };
5
+ export declare function useSettings(): {
6
+ settings: Settings;
7
+ updateSetting: <K extends keyof Settings>(key: K, value: Settings[K]) => void;
8
+ };
9
+ export {};
@@ -0,0 +1,31 @@
1
+ import { useState, useEffect, useCallback } from 'react';
2
+ import { readFileSync, writeFileSync, existsSync } from 'fs';
3
+ import { homedir } from 'os';
4
+ import { join } from 'path';
5
+ const SETTINGS_PATH = join(homedir(), '.git-thing-settings.json');
6
+ const DEFAULT_SETTINGS = {
7
+ baseBranch: 'main',
8
+ autoStash: true,
9
+ };
10
+ export function useSettings() {
11
+ const [settings, setSettings] = useState(DEFAULT_SETTINGS);
12
+ useEffect(() => {
13
+ if (existsSync(SETTINGS_PATH)) {
14
+ try {
15
+ const data = JSON.parse(readFileSync(SETTINGS_PATH, 'utf-8'));
16
+ setSettings({ ...DEFAULT_SETTINGS, ...data });
17
+ }
18
+ catch {
19
+ // If file is corrupted, use defaults
20
+ }
21
+ }
22
+ }, []);
23
+ const updateSetting = useCallback((key, value) => {
24
+ setSettings((prev) => {
25
+ const next = { ...prev, [key]: value };
26
+ writeFileSync(SETTINGS_PATH, JSON.stringify(next, null, 2));
27
+ return next;
28
+ });
29
+ }, []);
30
+ return { settings, updateSetting };
31
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-thing",
3
- "version": "1.0.2",
3
+ "version": "1.0.6",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -13,16 +13,13 @@
13
13
  },
14
14
  "scripts": {
15
15
  "build": "tsc",
16
- "dev": "tsc --watch",
17
- "dev:watch": "tsx watch source/cli.tsx",
18
- "test": "prettier --check . && xo && ava"
16
+ "dev": "tsc && node dist/cli.js"
19
17
  },
20
18
  "files": [
21
19
  "dist"
22
20
  ],
23
21
  "dependencies": {
24
22
  "ink": "^4.1.0",
25
- "meow": "^11.0.0",
26
23
  "react": "^18.2.0"
27
24
  },
28
25
  "devDependencies": {
package/readme.md CHANGED
@@ -1,25 +1,75 @@
1
- # Git-thing
1
+ # git-thing
2
2
 
3
- > This readme is automatically generated by [create-ink-app](https://github.com/vadimdemedes/create-ink-app)
3
+ A streamlined CLI tool to handle the heavy lifting of feature branch workflows.
4
+ Stop typing long Git commands and let `git-thing` manage your branching, switching, and rebasing.
4
5
 
5
- ## Install
6
+ ## Description
6
7
 
7
- ```bash
8
- $ npm install --global Git-thing
9
- ```
8
+ `git-thing` is a simple utility designed to automate the repetitive parts of a Git-based workflow. It allows you to:
10
9
 
11
- ## CLI
10
+ - **Create** new feature branches instantly.
11
+ - **Switch** between active tasks without friction.
12
+ - **Rebase** your work against the main branch to keep your history clean.
12
13
 
13
- ```
14
- $ Git-thing --help
14
+ ---
15
15
 
16
- Usage
17
- $ Git-thing
16
+ ## Installation
18
17
 
19
- Options
20
- --name Your name
18
+ To use `git-thing` as a permanent command on your machine without using `npx` every time, install it globally:
21
19
 
22
- Examples
23
- $ Git-thing --name=Jane
24
- Hello, Jane
25
- ```
20
+ npm install -g git-thing
21
+
22
+ ## How to Update
23
+
24
+ When a new version is published to NPM, sync your local copy by running:
25
+
26
+ npm update -g git-thing
27
+
28
+ ## How to Uninstall
29
+
30
+ If you no longer need the tool, remove it with:
31
+
32
+ npm uninstall -g git-thing
33
+
34
+ ---
35
+
36
+ ## Setting up an Alias (e.g., gt)
37
+
38
+ If `git-thing` is too long to type, you can set up a shorter alias like `gt`.
39
+
40
+ ### macOS and Linux (Zsh or Bash)
41
+
42
+ 1. Open your configuration file (usually `~/.zshrc` or `~/.bashrc`):
43
+
44
+ nano ~/.zshrc
45
+
46
+ 2. Add this line at the end:
47
+
48
+ alias gt='git-thing'
49
+
50
+ 3. Save, exit, and reload your shell:
51
+
52
+ source ~/.zshrc
53
+
54
+ ### Windows (PowerShell)
55
+
56
+ 1. Open your PowerShell profile:
57
+
58
+ notepad $PROFILE
59
+
60
+ 2. Add this line:
61
+
62
+ Set-Alias -Name gt -Value git-thing
63
+
64
+ 3. Save and restart your PowerShell terminal.
65
+
66
+ ---
67
+
68
+ ## Usage
69
+
70
+ Once installed (and optionally aliased), you can run the tool from any directory:
71
+
72
+ git-thing
73
+
74
+ # OR, if aliased:
75
+ gt