@manthank/mgit 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/LICENSE +21 -0
- package/README.md +96 -0
- package/dist/app.js +468 -0
- package/dist/catalog.js +585 -0
- package/dist/cli.js +48 -0
- package/dist/components/Footer.js +38 -0
- package/dist/components/Header.js +46 -0
- package/dist/components/Menu.js +56 -0
- package/dist/config.js +40 -0
- package/dist/git.js +263 -0
- package/dist/hooks.js +23 -0
- package/dist/recipes.js +96 -0
- package/dist/themes.js +89 -0
- package/dist/views/ActionRunner.js +175 -0
- package/dist/views/BranchView.js +84 -0
- package/dist/views/ConfigView.js +60 -0
- package/dist/views/ContribView.js +80 -0
- package/dist/views/GitignoreView.js +85 -0
- package/dist/views/LogGraphView.js +116 -0
- package/dist/views/RecipeView.js +153 -0
- package/dist/views/SettingsView.js +66 -0
- package/dist/views/SetupWizard.js +120 -0
- package/dist/views/StatusView.js +121 -0
- package/package.json +52 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Animated header: gradient "mgit" wordmark with a shimmering tagline.
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
import Gradient from 'ink-gradient';
|
|
5
|
+
import BigText from 'ink-big-text';
|
|
6
|
+
import { useFrame } from '../hooks.js';
|
|
7
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
8
|
+
const taglines = ['git & GitHub, minus the fear', 'your friendly terminal guide', 'learn by doing, safely', 'every command, explained'];
|
|
9
|
+
export default function Header({
|
|
10
|
+
theme,
|
|
11
|
+
animations,
|
|
12
|
+
subtitle
|
|
13
|
+
}) {
|
|
14
|
+
const frame = useFrame(animations, 3);
|
|
15
|
+
// Rotate the gradient colours to create a flowing shimmer on the wordmark.
|
|
16
|
+
const g = theme.gradient;
|
|
17
|
+
const rotated = animations ? g.map((_, i) => g[(i + frame) % g.length]) : g;
|
|
18
|
+
const tagline = taglines[Math.floor(frame / 4) % taglines.length];
|
|
19
|
+
|
|
20
|
+
// A little animated spark that travels along the underline.
|
|
21
|
+
const width = 34;
|
|
22
|
+
const pos = animations ? frame % width : 0;
|
|
23
|
+
const underline = Array.from({
|
|
24
|
+
length: width
|
|
25
|
+
}, (_, i) => i === pos ? '◆' : i === (pos + width - 2) % width ? '◇' : '─').join('');
|
|
26
|
+
return /*#__PURE__*/_jsxs(Box, {
|
|
27
|
+
flexDirection: "column",
|
|
28
|
+
alignItems: "center",
|
|
29
|
+
marginBottom: 1,
|
|
30
|
+
children: [/*#__PURE__*/_jsx(Gradient, {
|
|
31
|
+
colors: rotated,
|
|
32
|
+
children: /*#__PURE__*/_jsx(BigText, {
|
|
33
|
+
text: "mgit",
|
|
34
|
+
font: "tiny"
|
|
35
|
+
})
|
|
36
|
+
}), /*#__PURE__*/_jsx(Gradient, {
|
|
37
|
+
colors: theme.gradient,
|
|
38
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
39
|
+
children: underline
|
|
40
|
+
})
|
|
41
|
+
}), /*#__PURE__*/_jsxs(Text, {
|
|
42
|
+
color: theme.secondary,
|
|
43
|
+
children: [theme.emoji, " ", subtitle || tagline]
|
|
44
|
+
})]
|
|
45
|
+
});
|
|
46
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// A themed wrapper around ink-select-input with an animated pointer and
|
|
2
|
+
// two-line items (label + dim hint).
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { Box, Text } from 'ink';
|
|
5
|
+
import SelectInput from 'ink-select-input';
|
|
6
|
+
import { useFrame } from '../hooks.js';
|
|
7
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
8
|
+
function makeIndicator(theme, animations) {
|
|
9
|
+
const spinners = ['›', '»', '›', '·'];
|
|
10
|
+
return function Indicator({
|
|
11
|
+
isSelected
|
|
12
|
+
}) {
|
|
13
|
+
const frame = useFrame(animations && isSelected, 6);
|
|
14
|
+
if (!isSelected) return /*#__PURE__*/_jsx(Text, {
|
|
15
|
+
children: " "
|
|
16
|
+
});
|
|
17
|
+
const ch = animations ? spinners[frame % spinners.length] : theme.pointer;
|
|
18
|
+
return /*#__PURE__*/_jsxs(Text, {
|
|
19
|
+
color: theme.accent,
|
|
20
|
+
bold: true,
|
|
21
|
+
children: [ch, ' ']
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function makeItem(theme) {
|
|
26
|
+
return function Item({
|
|
27
|
+
isSelected,
|
|
28
|
+
label
|
|
29
|
+
}) {
|
|
30
|
+
return /*#__PURE__*/_jsx(Text, {
|
|
31
|
+
color: isSelected ? theme.accent : 'white',
|
|
32
|
+
bold: isSelected,
|
|
33
|
+
children: label
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export default function Menu({
|
|
38
|
+
items,
|
|
39
|
+
onSelect,
|
|
40
|
+
onHighlight,
|
|
41
|
+
theme,
|
|
42
|
+
animations,
|
|
43
|
+
limit
|
|
44
|
+
}) {
|
|
45
|
+
return /*#__PURE__*/_jsx(Box, {
|
|
46
|
+
flexDirection: "column",
|
|
47
|
+
children: /*#__PURE__*/_jsx(SelectInput, {
|
|
48
|
+
items: items,
|
|
49
|
+
onSelect: onSelect,
|
|
50
|
+
onHighlight: onHighlight,
|
|
51
|
+
limit: limit,
|
|
52
|
+
indicatorComponent: makeIndicator(theme, animations),
|
|
53
|
+
itemComponent: makeItem(theme)
|
|
54
|
+
})
|
|
55
|
+
});
|
|
56
|
+
}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Tiny JSON config store persisted at ~/.mgit/config.json.
|
|
2
|
+
// Keeps the user's chosen theme and small UI preferences between runs.
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { mkdirSync, readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
6
|
+
const dir = join(homedir(), '.mgit');
|
|
7
|
+
const file = join(dir, 'config.json');
|
|
8
|
+
const defaults = {
|
|
9
|
+
theme: 'neon',
|
|
10
|
+
animations: true,
|
|
11
|
+
confirmDanger: true,
|
|
12
|
+
showHints: true
|
|
13
|
+
};
|
|
14
|
+
export function loadConfig() {
|
|
15
|
+
try {
|
|
16
|
+
if (existsSync(file)) {
|
|
17
|
+
const raw = JSON.parse(readFileSync(file, 'utf8'));
|
|
18
|
+
return {
|
|
19
|
+
...defaults,
|
|
20
|
+
...raw
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
} catch {
|
|
24
|
+
// corrupt/unreadable config → fall back to defaults
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
...defaults
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function saveConfig(config) {
|
|
31
|
+
try {
|
|
32
|
+
if (!existsSync(dir)) mkdirSync(dir, {
|
|
33
|
+
recursive: true
|
|
34
|
+
});
|
|
35
|
+
writeFileSync(file, JSON.stringify(config, null, 2), 'utf8');
|
|
36
|
+
} catch {
|
|
37
|
+
// non-fatal: settings just won't persist this session
|
|
38
|
+
}
|
|
39
|
+
return config;
|
|
40
|
+
}
|
package/dist/git.js
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
// Git engine: thin async wrappers around the `git` binary using execa.
|
|
2
|
+
// Parsers turn raw output into structured data the visual components consume.
|
|
3
|
+
import { execa } from 'execa';
|
|
4
|
+
|
|
5
|
+
// Run an arbitrary git command. Returns {ok, stdout, stderr, code, command}.
|
|
6
|
+
// Never throws — callers inspect `ok` so the UI can render failures gracefully.
|
|
7
|
+
export async function git(args, options = {}) {
|
|
8
|
+
const command = 'git ' + args.map(quoteIfNeeded).join(' ');
|
|
9
|
+
try {
|
|
10
|
+
const res = await execa('git', args, {
|
|
11
|
+
cwd: options.cwd || process.cwd(),
|
|
12
|
+
reject: false,
|
|
13
|
+
all: false,
|
|
14
|
+
...options
|
|
15
|
+
});
|
|
16
|
+
return {
|
|
17
|
+
ok: res.exitCode === 0,
|
|
18
|
+
stdout: (res.stdout || '').trimEnd(),
|
|
19
|
+
stderr: (res.stderr || '').trimEnd(),
|
|
20
|
+
code: res.exitCode,
|
|
21
|
+
command
|
|
22
|
+
};
|
|
23
|
+
} catch (error) {
|
|
24
|
+
return {
|
|
25
|
+
ok: false,
|
|
26
|
+
stdout: '',
|
|
27
|
+
stderr: error.shortMessage || error.message || String(error),
|
|
28
|
+
code: error.exitCode ?? 1,
|
|
29
|
+
command
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function quoteIfNeeded(arg) {
|
|
34
|
+
return /\s/.test(arg) ? `"${arg}"` : arg;
|
|
35
|
+
}
|
|
36
|
+
export async function isGitRepo(cwd = process.cwd()) {
|
|
37
|
+
const res = await git(['rev-parse', '--is-inside-work-tree'], {
|
|
38
|
+
cwd
|
|
39
|
+
});
|
|
40
|
+
return res.ok && res.stdout.trim() === 'true';
|
|
41
|
+
}
|
|
42
|
+
export async function getCurrentBranch() {
|
|
43
|
+
const res = await git(['branch', '--show-current']);
|
|
44
|
+
return res.ok ? res.stdout.trim() : '';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Parse `git status --porcelain=v1 -b` into a friendly summary.
|
|
48
|
+
export async function getStatus() {
|
|
49
|
+
const res = await git(['status', '--porcelain=v1', '-b']);
|
|
50
|
+
if (!res.ok) return {
|
|
51
|
+
ok: false,
|
|
52
|
+
error: res.stderr,
|
|
53
|
+
files: []
|
|
54
|
+
};
|
|
55
|
+
const lines = res.stdout.split('\n').filter(Boolean);
|
|
56
|
+
let branchLine = '';
|
|
57
|
+
const files = [];
|
|
58
|
+
for (const line of lines) {
|
|
59
|
+
if (line.startsWith('##')) {
|
|
60
|
+
branchLine = line.slice(2).trim();
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const x = line[0]; // staged column
|
|
64
|
+
const y = line[1]; // worktree column
|
|
65
|
+
const name = line.slice(3);
|
|
66
|
+
files.push({
|
|
67
|
+
x,
|
|
68
|
+
y,
|
|
69
|
+
name,
|
|
70
|
+
...classify(x, y)
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Parse ahead/behind from a line like: main...origin/main [ahead 1, behind 2]
|
|
75
|
+
let ahead = 0;
|
|
76
|
+
let behind = 0;
|
|
77
|
+
const am = branchLine.match(/ahead (\d+)/);
|
|
78
|
+
const bm = branchLine.match(/behind (\d+)/);
|
|
79
|
+
if (am) ahead = Number(am[1]);
|
|
80
|
+
if (bm) behind = Number(bm[1]);
|
|
81
|
+
return {
|
|
82
|
+
ok: true,
|
|
83
|
+
branchLine,
|
|
84
|
+
ahead,
|
|
85
|
+
behind,
|
|
86
|
+
files,
|
|
87
|
+
staged: files.filter(f => f.staged),
|
|
88
|
+
unstaged: files.filter(f => f.unstaged),
|
|
89
|
+
untracked: files.filter(f => f.untracked)
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function classify(x, y) {
|
|
93
|
+
const untracked = x === '?' && y === '?';
|
|
94
|
+
const staged = !untracked && x !== ' ' && x !== '?';
|
|
95
|
+
const unstaged = !untracked && y !== ' ';
|
|
96
|
+
let label = 'Modified';
|
|
97
|
+
const code = untracked ? '?' : x !== ' ' ? x : y;
|
|
98
|
+
switch (code) {
|
|
99
|
+
case 'A':
|
|
100
|
+
label = 'Added';
|
|
101
|
+
break;
|
|
102
|
+
case 'M':
|
|
103
|
+
label = 'Modified';
|
|
104
|
+
break;
|
|
105
|
+
case 'D':
|
|
106
|
+
label = 'Deleted';
|
|
107
|
+
break;
|
|
108
|
+
case 'R':
|
|
109
|
+
label = 'Renamed';
|
|
110
|
+
break;
|
|
111
|
+
case 'C':
|
|
112
|
+
label = 'Copied';
|
|
113
|
+
break;
|
|
114
|
+
case 'U':
|
|
115
|
+
label = 'Conflict';
|
|
116
|
+
break;
|
|
117
|
+
case '?':
|
|
118
|
+
label = 'Untracked';
|
|
119
|
+
break;
|
|
120
|
+
default:
|
|
121
|
+
label = 'Changed';
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
untracked,
|
|
125
|
+
staged,
|
|
126
|
+
unstaged,
|
|
127
|
+
label
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// List local branches with current marker and last-commit subject.
|
|
132
|
+
export async function getBranches() {
|
|
133
|
+
const res = await git(['branch', '--format=%(HEAD)\t%(refname:short)\t%(objectname:short)\t%(contents:subject)']);
|
|
134
|
+
if (!res.ok) return {
|
|
135
|
+
ok: false,
|
|
136
|
+
error: res.stderr,
|
|
137
|
+
branches: []
|
|
138
|
+
};
|
|
139
|
+
const branches = res.stdout.split('\n').filter(Boolean).map(line => {
|
|
140
|
+
const [head, name, sha, ...rest] = line.split('\t');
|
|
141
|
+
return {
|
|
142
|
+
current: head.trim() === '*',
|
|
143
|
+
name,
|
|
144
|
+
sha,
|
|
145
|
+
subject: rest.join('\t') || ''
|
|
146
|
+
};
|
|
147
|
+
});
|
|
148
|
+
return {
|
|
149
|
+
ok: true,
|
|
150
|
+
branches
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Structured commit log for the graph view.
|
|
155
|
+
export async function getLog(limit = 25, all = true) {
|
|
156
|
+
const args = ['log', `--max-count=${limit}`, '--date=short',
|
|
157
|
+
// Field separator \x1f between fields, record separator \x1e between commits.
|
|
158
|
+
'--pretty=format:%h\x1f%an\x1f%ad\x1f%d\x1f%s\x1e'];
|
|
159
|
+
if (all) args.push('--all');
|
|
160
|
+
const res = await git(args);
|
|
161
|
+
if (!res.ok) return {
|
|
162
|
+
ok: false,
|
|
163
|
+
error: res.stderr,
|
|
164
|
+
commits: []
|
|
165
|
+
};
|
|
166
|
+
const commits = res.stdout.split('\x1e').map(s => s.replace(/^\n/, '')).filter(Boolean).map(rec => {
|
|
167
|
+
const [hash, author, date, refs, subject] = rec.split('\x1f');
|
|
168
|
+
return {
|
|
169
|
+
hash,
|
|
170
|
+
author,
|
|
171
|
+
date,
|
|
172
|
+
refs: (refs || '').replace(/[()]/g, '').trim(),
|
|
173
|
+
subject: subject || ''
|
|
174
|
+
};
|
|
175
|
+
});
|
|
176
|
+
return {
|
|
177
|
+
ok: true,
|
|
178
|
+
commits
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
export async function getRemotes() {
|
|
182
|
+
const res = await git(['remote', '-v']);
|
|
183
|
+
if (!res.ok) return {
|
|
184
|
+
ok: false,
|
|
185
|
+
error: res.stderr,
|
|
186
|
+
remotes: []
|
|
187
|
+
};
|
|
188
|
+
const map = new Map();
|
|
189
|
+
for (const line of res.stdout.split('\n').filter(Boolean)) {
|
|
190
|
+
const [name, rest] = line.split('\t');
|
|
191
|
+
if (!rest) continue;
|
|
192
|
+
const url = rest.split(' ')[0];
|
|
193
|
+
if (!map.has(name)) map.set(name, url);
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
ok: true,
|
|
197
|
+
remotes: [...map.entries()].map(([name, url]) => ({
|
|
198
|
+
name,
|
|
199
|
+
url
|
|
200
|
+
}))
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
export async function getStashes() {
|
|
204
|
+
const res = await git(['stash', 'list']);
|
|
205
|
+
if (!res.ok) return {
|
|
206
|
+
ok: false,
|
|
207
|
+
error: res.stderr,
|
|
208
|
+
stashes: []
|
|
209
|
+
};
|
|
210
|
+
const stashes = res.stdout.split('\n').filter(Boolean);
|
|
211
|
+
return {
|
|
212
|
+
ok: true,
|
|
213
|
+
stashes
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
export async function getConfigList() {
|
|
217
|
+
const res = await git(['config', '--list']);
|
|
218
|
+
if (!res.ok) return {
|
|
219
|
+
ok: false,
|
|
220
|
+
error: res.stderr,
|
|
221
|
+
entries: []
|
|
222
|
+
};
|
|
223
|
+
const entries = res.stdout.split('\n').filter(Boolean).map(l => {
|
|
224
|
+
const idx = l.indexOf('=');
|
|
225
|
+
return {
|
|
226
|
+
key: l.slice(0, idx),
|
|
227
|
+
value: l.slice(idx + 1)
|
|
228
|
+
};
|
|
229
|
+
});
|
|
230
|
+
return {
|
|
231
|
+
ok: true,
|
|
232
|
+
entries
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
export async function getGlobalIdentity() {
|
|
236
|
+
const name = await git(['config', '--global', 'user.name']);
|
|
237
|
+
const email = await git(['config', '--global', 'user.email']);
|
|
238
|
+
return {
|
|
239
|
+
name: name.ok ? name.stdout.trim() : '',
|
|
240
|
+
email: email.ok ? email.stdout.trim() : ''
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Contribution-style counts per author for the "chart" feature.
|
|
245
|
+
export async function getShortstat() {
|
|
246
|
+
const res = await git(['shortlog', '-sn', '--all', '--no-merges']);
|
|
247
|
+
if (!res.ok) return {
|
|
248
|
+
ok: false,
|
|
249
|
+
error: res.stderr,
|
|
250
|
+
authors: []
|
|
251
|
+
};
|
|
252
|
+
const authors = res.stdout.split('\n').filter(Boolean).map(line => {
|
|
253
|
+
const m = line.trim().match(/^(\d+)\s+(.*)$/);
|
|
254
|
+
return m ? {
|
|
255
|
+
count: Number(m[1]),
|
|
256
|
+
name: m[2]
|
|
257
|
+
} : null;
|
|
258
|
+
}).filter(Boolean);
|
|
259
|
+
return {
|
|
260
|
+
ok: true,
|
|
261
|
+
authors
|
|
262
|
+
};
|
|
263
|
+
}
|
package/dist/hooks.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Small reusable hooks for animation and timers.
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
// Fire a callback on a fixed interval. Pass delay=null to pause.
|
|
5
|
+
export function useInterval(callback, delay) {
|
|
6
|
+
const saved = useRef(callback);
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
saved.current = callback;
|
|
9
|
+
}, [callback]);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
if (delay === null || delay === undefined) return undefined;
|
|
12
|
+
const id = setInterval(() => saved.current(), delay);
|
|
13
|
+
return () => clearInterval(id);
|
|
14
|
+
}, [delay]);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// A monotonically increasing frame counter, handy for cycling colours/positions.
|
|
18
|
+
// Disabled (stays at 0) when `enabled` is false so animations can be turned off.
|
|
19
|
+
export function useFrame(enabled = true, fps = 12) {
|
|
20
|
+
const [frame, setFrame] = useState(0);
|
|
21
|
+
useInterval(() => setFrame(f => f + 1), enabled ? Math.round(1000 / fps) : null);
|
|
22
|
+
return enabled ? frame : 0;
|
|
23
|
+
}
|
package/dist/recipes.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// Guided multi-step workflows. Each step has a title, explanation, the git args
|
|
2
|
+
// to run, and optional inputs collected up front. The RecipeView walks the user
|
|
3
|
+
// through them one at a time, running each on confirmation.
|
|
4
|
+
|
|
5
|
+
export const recipes = {
|
|
6
|
+
'github-first': {
|
|
7
|
+
title: 'Publish to GitHub (first time)',
|
|
8
|
+
emoji: '🚀',
|
|
9
|
+
intro: 'Take a plain folder and push it to a brand-new GitHub repository.',
|
|
10
|
+
inputs: [{
|
|
11
|
+
name: 'msg',
|
|
12
|
+
label: 'First commit message',
|
|
13
|
+
placeholder: 'Initial commit',
|
|
14
|
+
default: 'Initial commit'
|
|
15
|
+
}, {
|
|
16
|
+
name: 'url',
|
|
17
|
+
label: 'GitHub repo URL',
|
|
18
|
+
placeholder: 'https://github.com/you/repo.git'
|
|
19
|
+
}],
|
|
20
|
+
steps: v => [{
|
|
21
|
+
title: 'Initialize repo',
|
|
22
|
+
why: 'Start tracking this folder with git.',
|
|
23
|
+
args: ['init']
|
|
24
|
+
}, {
|
|
25
|
+
title: 'Stage everything',
|
|
26
|
+
why: 'Mark all files to be included in the first commit.',
|
|
27
|
+
args: ['add', '.']
|
|
28
|
+
}, {
|
|
29
|
+
title: 'First commit',
|
|
30
|
+
why: 'Save the initial snapshot.',
|
|
31
|
+
args: ['commit', '-m', v.msg || 'Initial commit']
|
|
32
|
+
}, {
|
|
33
|
+
title: 'Name branch main',
|
|
34
|
+
why: 'GitHub expects a "main" branch by default.',
|
|
35
|
+
args: ['branch', '-M', 'main']
|
|
36
|
+
}, {
|
|
37
|
+
title: 'Link to GitHub',
|
|
38
|
+
why: 'Connect your local repo to the remote URL.',
|
|
39
|
+
args: ['remote', 'add', 'origin', v.url]
|
|
40
|
+
}, {
|
|
41
|
+
title: 'Push',
|
|
42
|
+
why: 'Upload your commits and remember the link.',
|
|
43
|
+
args: ['push', '-u', 'origin', 'main']
|
|
44
|
+
}]
|
|
45
|
+
},
|
|
46
|
+
feature: {
|
|
47
|
+
title: 'Feature branch workflow',
|
|
48
|
+
emoji: '🌿',
|
|
49
|
+
intro: 'The everyday team flow: get latest, branch off, then push your work.',
|
|
50
|
+
inputs: [{
|
|
51
|
+
name: 'branch',
|
|
52
|
+
label: 'New feature branch name',
|
|
53
|
+
placeholder: 'feature/login'
|
|
54
|
+
}, {
|
|
55
|
+
name: 'msg',
|
|
56
|
+
label: 'Commit message',
|
|
57
|
+
placeholder: 'Add login screen'
|
|
58
|
+
}],
|
|
59
|
+
steps: v => [{
|
|
60
|
+
title: 'Pull latest',
|
|
61
|
+
why: 'Start from the most recent code.',
|
|
62
|
+
args: ['pull']
|
|
63
|
+
}, {
|
|
64
|
+
title: 'Create feature branch',
|
|
65
|
+
why: 'Isolate your work from main.',
|
|
66
|
+
args: ['switch', '-c', v.branch]
|
|
67
|
+
}, {
|
|
68
|
+
title: 'Stage your changes',
|
|
69
|
+
why: 'Mark your edits for commit. (Make your edits before this step!)',
|
|
70
|
+
args: ['add', '.']
|
|
71
|
+
}, {
|
|
72
|
+
title: 'Commit',
|
|
73
|
+
why: 'Save a snapshot of your work.',
|
|
74
|
+
args: ['commit', '-m', v.msg || 'Update']
|
|
75
|
+
}, {
|
|
76
|
+
title: 'Push branch',
|
|
77
|
+
why: 'Publish your branch so you can open a Pull Request.',
|
|
78
|
+
args: ['push', '-u', 'origin', v.branch]
|
|
79
|
+
}]
|
|
80
|
+
},
|
|
81
|
+
'sync-main': {
|
|
82
|
+
title: 'Sync main branch',
|
|
83
|
+
emoji: '🔄',
|
|
84
|
+
intro: 'Switch to main and pull the newest changes.',
|
|
85
|
+
inputs: [],
|
|
86
|
+
steps: () => [{
|
|
87
|
+
title: 'Switch to main',
|
|
88
|
+
why: 'Move onto the main branch.',
|
|
89
|
+
args: ['switch', 'main']
|
|
90
|
+
}, {
|
|
91
|
+
title: 'Pull origin main',
|
|
92
|
+
why: 'Download and merge the latest main.',
|
|
93
|
+
args: ['pull', 'origin', 'main']
|
|
94
|
+
}]
|
|
95
|
+
}
|
|
96
|
+
};
|
package/dist/themes.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Theme catalog for mgit.
|
|
2
|
+
// Each theme controls the gradient of the logo, accent colours used across the UI,
|
|
3
|
+
// and small decorative choices (icons/pointer). Colours are standard chalk/ink
|
|
4
|
+
// colour names or hex strings so they render nicely in most terminals.
|
|
5
|
+
|
|
6
|
+
export const themes = {
|
|
7
|
+
neon: {
|
|
8
|
+
label: 'Neon Night',
|
|
9
|
+
emoji: '🌆',
|
|
10
|
+
gradient: ['#ff00cc', '#3333ff', '#00ffcc'],
|
|
11
|
+
accent: '#ff36c9',
|
|
12
|
+
secondary: '#00e5ff',
|
|
13
|
+
success: '#4aff26',
|
|
14
|
+
danger: '#ff4d6d',
|
|
15
|
+
warn: '#ffd166',
|
|
16
|
+
muted: 'gray',
|
|
17
|
+
pointer: '›',
|
|
18
|
+
branchColors: ['#ff36c9', '#00e5ff', '#ffd166', '#7cff6b', '#b892ff', '#ff8c42']
|
|
19
|
+
},
|
|
20
|
+
ocean: {
|
|
21
|
+
label: 'Deep Ocean',
|
|
22
|
+
emoji: '🌊',
|
|
23
|
+
gradient: ['#2E3192', '#1BFFFF'],
|
|
24
|
+
accent: '#1BC7FF',
|
|
25
|
+
secondary: '#5efce8',
|
|
26
|
+
success: '#38ef7d',
|
|
27
|
+
danger: '#ff6b6b',
|
|
28
|
+
warn: '#ffe66d',
|
|
29
|
+
muted: 'gray',
|
|
30
|
+
pointer: '»',
|
|
31
|
+
branchColors: ['#1BC7FF', '#5efce8', '#38ef7d', '#8ec5ff', '#b3f0ff', '#00b4d8']
|
|
32
|
+
},
|
|
33
|
+
sunset: {
|
|
34
|
+
label: 'Sunset',
|
|
35
|
+
emoji: '🌅',
|
|
36
|
+
gradient: ['#f12711', '#f5af19'],
|
|
37
|
+
accent: '#ff7e5f',
|
|
38
|
+
secondary: '#feb47b',
|
|
39
|
+
success: '#8ee000',
|
|
40
|
+
danger: '#e63946',
|
|
41
|
+
warn: '#ffb703',
|
|
42
|
+
muted: 'gray',
|
|
43
|
+
pointer: '➜',
|
|
44
|
+
branchColors: ['#ff7e5f', '#feb47b', '#ffb703', '#f9c74f', '#f8961e', '#f3722c']
|
|
45
|
+
},
|
|
46
|
+
forest: {
|
|
47
|
+
label: 'Forest',
|
|
48
|
+
emoji: '🌲',
|
|
49
|
+
gradient: ['#134E5E', '#71B280'],
|
|
50
|
+
accent: '#71B280',
|
|
51
|
+
secondary: '#a8e063',
|
|
52
|
+
success: '#a8e063',
|
|
53
|
+
danger: '#e07a5f',
|
|
54
|
+
warn: '#f2cc8f',
|
|
55
|
+
muted: 'gray',
|
|
56
|
+
pointer: '▸',
|
|
57
|
+
branchColors: ['#71B280', '#a8e063', '#56ab2f', '#c1ffc1', '#3d8361', '#1c6758']
|
|
58
|
+
},
|
|
59
|
+
mono: {
|
|
60
|
+
label: 'Monochrome',
|
|
61
|
+
emoji: '⬛',
|
|
62
|
+
gradient: ['#ffffff', '#888888'],
|
|
63
|
+
accent: '#ffffff',
|
|
64
|
+
secondary: '#bbbbbb',
|
|
65
|
+
success: '#ffffff',
|
|
66
|
+
danger: '#ff5555',
|
|
67
|
+
warn: '#dddddd',
|
|
68
|
+
muted: 'gray',
|
|
69
|
+
pointer: '▌',
|
|
70
|
+
branchColors: ['#ffffff', '#cccccc', '#999999', '#bbbbbb', '#dddddd', '#777777']
|
|
71
|
+
},
|
|
72
|
+
candy: {
|
|
73
|
+
label: 'Candy Pop',
|
|
74
|
+
emoji: '🍬',
|
|
75
|
+
gradient: ['#ff9a9e', '#fad0c4', '#a18cd1'],
|
|
76
|
+
accent: '#ff6ec7',
|
|
77
|
+
secondary: '#a18cd1',
|
|
78
|
+
success: '#7bed9f',
|
|
79
|
+
danger: '#ff4757',
|
|
80
|
+
warn: '#ffdb4d',
|
|
81
|
+
muted: 'gray',
|
|
82
|
+
pointer: '★',
|
|
83
|
+
branchColors: ['#ff6ec7', '#a18cd1', '#7bed9f', '#ff9ff3', '#feca57', '#48dbfb']
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
export const themeOrder = ['neon', 'ocean', 'sunset', 'forest', 'mono', 'candy'];
|
|
87
|
+
export function getTheme(name) {
|
|
88
|
+
return themes[name] || themes.neon;
|
|
89
|
+
}
|