-
Notifications
You must be signed in to change notification settings - Fork 43
Expand file tree
/
Copy pathgit-command-explainer.html
More file actions
539 lines (501 loc) · 26.1 KB
/
Copy pathgit-command-explainer.html
File metadata and controls
539 lines (501 loc) · 26.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
<!doctype html>
<html lang="en" data-theme="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Git Command Explainer - One File Tools</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@600;700&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
<style>
:root {
--bg: #060b14;
--surface: #0c1720;
--surface-2: #12403c;
--border: #1c2b2a;
--text: #f5f5f5;
--muted: #9db3ae;
--accent: #efd7ab;
--accent-text: #12403c;
--flag: #f2c14e;
--flag-bg: rgba(242, 193, 78, 0.16);
--arg: #5fd4c0;
--arg-bg: rgba(95, 212, 192, 0.14);
--danger: #ff7a52;
--danger-bg: rgba(255, 122, 82, 0.16);
--unrec: #77848a;
--unrec-bg: rgba(119, 132, 138, 0.14);
--font-display: "Space Grotesk", sans-serif;
--font-sans: "Inter", system-ui, sans-serif;
--font-mono: "JetBrains Mono", monospace;
--radius: 10px;
}
[data-theme="light"] {
--bg: #f5f5f5;
--surface: #ffffff;
--surface-2: #efd7ab;
--border: #e3d6bd;
--text: #12403c;
--muted: #5c6f6c;
--accent: #c04e01;
--accent-text: #ffffff;
--flag: #8a6a10;
--flag-bg: rgba(138, 106, 16, 0.1);
--arg: #0e7d6c;
--arg-bg: rgba(14, 125, 108, 0.1);
--danger: #b3411a;
--danger-bg: rgba(179, 65, 26, 0.1);
--unrec: #6b7573;
--unrec-bg: rgba(107, 117, 115, 0.1);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background: var(--bg);
color: var(--text);
font-family: var(--font-sans);
min-height: 100vh;
transition:
background 0.2s,
color 0.2s;
}
header {
display: flex;
align-items: center;
gap: 16px;
padding: 22px 28px;
border-bottom: 1px solid var(--border);
flex-wrap: wrap;
}
.logo {
color: var(--flag);
flex-shrink: 0;
}
.brand {
display: flex;
flex-direction: column;
margin-right: auto;
}
.brand .eyebrow {
font-family: var(--font-mono);
font-size: 11px;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--muted);
}
.brand h1 {
font-family: var(--font-display);
font-size: 23px;
font-weight: 700;
letter-spacing: -0.01em;
}
.btn {
font-family: var(--font-sans);
font-size: 13px;
font-weight: 600;
color: var(--text);
background: var(--surface);
border: 1px solid var(--border);
border-radius: 100px;
padding: 7px 14px;
cursor: pointer;
transition: border-color 0.15s;
}
.btn:hover {
border-color: var(--muted);
}
.btn:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.input-section {
padding: 20px 28px;
border-bottom: 1px solid var(--border);
}
.input-label {
font-family: var(--font-mono);
font-size: 11px;
font-weight: 600;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--muted);
display: block;
margin-bottom: 8px;
}
input[type="text"] {
width: 100%;
background: var(--surface);
color: var(--text);
border: 1px solid var(--border);
border-radius: var(--radius);
outline: none;
font-family: var(--font-mono);
font-size: 14.5px;
padding: 13px 16px;
}
input[type="text"]:focus {
border-color: var(--accent);
}
input::placeholder {
color: var(--muted);
opacity: 0.7;
}
.examples {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-top: 12px;
}
.example-chip {
font-family: var(--font-mono);
font-size: 12px;
color: var(--muted);
background: var(--surface);
border: 1px solid var(--border);
border-radius: 100px;
padding: 5px 12px;
cursor: pointer;
}
.example-chip:hover {
border-color: var(--accent);
color: var(--text);
}
.output {
padding: 20px 28px 44px;
}
.empty {
display: flex;
align-items: center;
justify-content: center;
padding: 70px 20px;
color: var(--muted);
font-size: 14px;
text-align: center;
}
.result {
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--surface);
overflow: hidden;
}
.result-head {
padding: 18px 20px;
border-bottom: 1px solid var(--border);
background: color-mix(in srgb, var(--surface-2) 10%, var(--surface));
}
.cmd-name {
font-family: var(--font-mono);
font-size: 13px;
font-weight: 600;
color: var(--flag);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 6px;
}
.cmd-desc {
font-size: 14.5px;
line-height: 1.5;
}
.tier-note {
font-size: 12.5px;
color: var(--muted);
margin-top: 8px;
font-style: italic;
}
.danger-banner {
display: flex;
gap: 10px;
align-items: flex-start;
padding: 14px 20px;
background: var(--danger-bg);
border-bottom: 1px solid var(--border);
color: var(--danger);
font-size: 13.5px;
font-weight: 500;
line-height: 1.5;
}
.row {
display: flex;
gap: 14px;
align-items: flex-start;
padding: 13px 20px;
border-bottom: 1px solid var(--border);
}
.row:last-child {
border-bottom: none;
}
.chip {
font-family: var(--font-mono);
font-size: 12px;
font-weight: 600;
padding: 4px 10px;
border-radius: 100px;
flex-shrink: 0;
white-space: nowrap;
}
.chip.flag {
color: var(--flag);
background: var(--flag-bg);
}
.chip.danger {
color: var(--danger);
background: var(--danger-bg);
}
.chip.arg {
color: var(--arg);
background: var(--arg-bg);
}
.chip.unrec {
color: var(--unrec);
background: var(--unrec-bg);
}
.row-desc {
font-size: 13.5px;
line-height: 1.5;
flex: 1;
}
.row-value {
font-family: var(--font-mono);
font-size: 12px;
color: var(--arg);
margin-top: 4px;
}
.unknown {
padding: 20px;
font-size: 14px;
line-height: 1.6;
color: var(--danger);
}
@media (max-width: 720px) {
header,
.input-section,
.output {
padding-left: 16px;
padding-right: 16px;
}
.row {
flex-direction: column;
}
}
</style>
</head>
<body>
<header>
<svg class="logo" width="22" height="22" viewBox="0 0 22 22" fill="none" aria-hidden="true">
<circle cx="11" cy="4" r="2" stroke="currentColor" stroke-width="1.5" />
<circle cx="5" cy="18" r="2" stroke="currentColor" stroke-width="1.5" />
<circle cx="17" cy="18" r="2" stroke="currentColor" stroke-width="1.5" />
<path d="M11 6v4a4 4 0 0 1-4 4H7M11 10a4 4 0 0 0 4 4h2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
</svg>
<div class="brand">
<span class="eyebrow">Developer Utilities</span>
<h1>Git Command Explainer</h1>
</div>
<button class="btn" id="themeToggle">Light mode</button>
</header>
<div class="input-section">
<label class="input-label" for="cmdInput">Git command</label>
<input type="text" id="cmdInput" spellcheck="false" placeholder='e.g. git commit -am "fix bug" --amend' />
<div class="examples" id="examples"></div>
</div>
<div class="output" id="output">
<div class="empty">Type or paste a Git command to see it explained.</div>
</div>
<script>
const $ = (id) => document.getElementById(id);
const input = $("cmdInput"),
output = $("output"),
examplesEl = $("examples"),
themeToggle = $("themeToggle");
const escapeHtml = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
const F = (aliases, takesValue, desc, danger) => ({ aliases, takesValue: !!takesValue, desc, danger: !!danger });
const TIER1 = {
add: "Stage file changes for the next commit.",
am: "Apply a series of patches from a mailbox.",
archive: "Create an archive (zip/tar) of files from a named tree.",
bisect: "Use binary search to find the commit that introduced a bug.",
blame: "Show what revision and author last modified each line of a file.",
branch: "List, create, or delete branches.",
bundle: "Package objects and refs into a single file for transfer.",
checkout: "Switch branches or restore working tree files.",
"cherry-pick": "Apply the changes introduced by an existing commit onto the current branch.",
clean: "Remove untracked files from the working tree.",
clone: "Clone a repository into a new directory.",
commit: "Record staged changes as a new commit.",
config: "Get or set repository, global, or system Git configuration options.",
describe: "Give an object a human-readable name based on the nearest tag.",
diff: "Show changes between commits, the working tree, and the index.",
fetch: "Download objects and refs from a remote repository without merging them.",
"format-patch": "Prepare patches for e-mail submission.",
gc: "Cleanup unnecessary files and optimize the local repository.",
grep: "Search for a pattern in tracked files.",
init: "Create a new, empty Git repository.",
log: "Show the commit history.",
maintenance: "Run tasks to optimize repository data.",
merge: "Combine changes from one or more branches into the current branch.",
mv: "Move or rename a file, directory, or symlink, and stage the change.",
notes: "Add or inspect notes attached to commits.",
pull: "Fetch from a remote and integrate the changes into the current branch.",
push: "Upload local commits to a remote repository.",
"range-diff": "Compare two commit ranges, such as two versions of a branch.",
rebase: "Reapply commits on top of another base commit, rewriting history.",
reflog: "Show a log of where branch tips and HEAD have been, useful for recovering lost commits.",
remote: "Manage the set of tracked remote repositories.",
reset: "Move the current branch tip and optionally update the index and working tree.",
restore: "Restore working tree files from the index or another commit.",
revert: "Create a new commit that undoes the changes from an existing commit.",
rm: "Remove files from the working tree and stage the removal.",
shortlog: "Summarize commit log output, grouped by author.",
show: "Show details of a commit, tag, or other Git object.",
"sparse-checkout": "Check out only a subset of the repository's files.",
stash: "Temporarily shelve uncommitted changes to work on something else.",
status: "Show the state of the working tree and staging area.",
submodule: "Manage repositories embedded within another repository.",
switch: "Switch to a specified branch.",
tag: "Create, list, or delete tags.",
worktree: "Manage multiple working trees attached to the same repository.",
difftool: "Open diffs in an external visual diff tool.",
mergetool: "Open merge conflicts in an external visual merge tool.",
fsck: "Verify the integrity of objects in the repository.",
prune: "Remove unreachable objects that are no longer referenced."
};
const TIER2 = {
add: { flags: [F(["-A", "--all"], false, "Stage all changes, including deletions, across the whole working tree."), F(["-p", "--patch"], false, "Interactively choose which hunks of a file to stage."), F(["-u", "--update"], false, "Stage modifications and deletions of already-tracked files only, not new files."), F(["-n", "--dry-run"], false, "Show what would be staged without actually staging anything."), F(["-f", "--force"], false, "Allow staging files that are excluded by .gitignore.")] },
commit: { flags: [F(["-a", "--all"], false, "Automatically stage modified and deleted tracked files before committing."), F(["-m", "--message"], true, "Set the commit message directly instead of opening an editor."), F(["--amend"], false, "Replace the previous commit with a new one combining it with staged changes.", true), F(["--no-verify"], false, "Skip the pre-commit and commit-msg hooks."), F(["-v", "--verbose"], false, "Show the diff of what's being committed in the editor."), F(["--allow-empty"], false, "Allow creating a commit with no changes.")] },
push: { flags: [F(["-f", "--force"], false, "Overwrite the remote branch's history with your local branch's history.", true), F(["--force-with-lease"], false, "Force push only if the remote hasn't changed since your last fetch — safer than --force.", true), F(["-u", "--set-upstream"], false, "Set the remote branch as the upstream tracking branch."), F(["--tags"], false, "Push all local tags to the remote."), F(["-d", "--delete"], false, "Delete the specified branch on the remote.", true), F(["--dry-run"], false, "Show what would be pushed without actually pushing.")] },
pull: { flags: [F(["--rebase"], false, "Reapply local commits on top of fetched changes instead of a merge commit."), F(["--no-rebase"], false, "Force a merge commit even if rebase is configured as default."), F(["--ff-only"], false, "Only pull if it can fast-forward, otherwise abort.")] },
fetch: { flags: [F(["--all"], false, "Fetch from all configured remotes."), F(["-p", "--prune"], false, "Remove remote-tracking branches that no longer exist on the remote."), F(["--tags"], false, "Fetch all tags from the remote."), F(["--dry-run"], false, "Show what would be fetched without actually fetching.")] },
merge: { flags: [F(["--no-ff"], false, "Always create a merge commit, even if a fast-forward is possible."), F(["--ff-only"], false, "Only merge if it can fast-forward, otherwise abort."), F(["--squash"], false, "Combine all incoming commits into a single set of staged changes without committing."), F(["--abort"], false, "Abort the current merge and restore the pre-merge state."), F(["-X"], true, "Pass a merge strategy option, e.g. ours or theirs, to auto-resolve conflicts.")] },
rebase: { flags: [F(["-i", "--interactive"], false, "Open an editable list of commits to reorder, squash, edit, or drop."), F(["--onto"], true, "Rebase onto a different base commit than the upstream branch."), F(["--abort"], false, "Abort the rebase and restore the pre-rebase state."), F(["--continue"], false, "Continue the rebase after resolving a conflict."), F(["--autosquash"], false, "Automatically reorder fixup!/squash! commits during an interactive rebase.")] },
reset: { flags: [F(["--soft"], false, "Move the branch tip only, keeping the index and working tree unchanged."), F(["--mixed"], false, "Move the branch tip and reset the index, but keep working tree changes (default mode)."), F(["--hard"], false, "Move the branch tip and reset both the index and working tree, discarding all changes.", true)] },
checkout: { flags: [F(["-b"], true, "Create a new branch and switch to it."), F(["-f", "--force"], false, "Discard local changes when switching branches.", true), F(["--"], false, "Discard local changes to the given file, restoring it from the index.", true)] },
switch: { flags: [F(["-c", "--create"], true, "Create a new branch and switch to it."), F(["-f", "--force"], false, "Discard local changes when switching branches.", true), F(["--detach"], false, "Switch to a commit in a detached HEAD state, not on any branch.")] },
restore: { flags: [F(["--staged"], false, "Restore the index (unstage), not the working tree."), F(["--source"], true, "Restore from a specific commit instead of the index."), F(["-W", "--worktree"], false, "Restore the working tree file (default target).")] },
branch: { flags: [F(["-d", "--delete"], true, "Delete a branch, refusing if it has unmerged changes."), F(["-D"], true, "Force-delete a branch, even with unmerged changes.", true), F(["-m", "--move"], true, "Rename a branch."), F(["-a", "--all"], false, "List both local and remote-tracking branches."), F(["-r", "--remotes"], false, "List remote-tracking branches only.")] },
stash: { flags: [F(["push"], false, "Save uncommitted changes to a new stash entry (default subcommand)."), F(["pop"], false, "Reapply the most recent stash entry and remove it from the stash list."), F(["apply"], false, "Reapply a stash entry without removing it from the stash list."), F(["drop"], false, "Delete a stash entry without applying it.", true), F(["-u", "--include-untracked"], false, "Include untracked files in the stash.")] },
log: { flags: [F(["--oneline"], false, "Show each commit on a single, condensed line."), F(["--graph"], false, "Draw a text-based commit graph alongside the log."), F(["-p", "--patch"], false, "Show the full diff introduced by each commit."), F(["--author"], true, "Filter commits by author name or pattern."), F(["-n"], true, "Limit the number of commits shown.")] },
diff: { flags: [F(["--staged", "--cached"], false, "Show changes between the index and the last commit, instead of working tree vs index."), F(["--stat"], false, "Show a summary of changed files and line counts instead of the full diff."), F(["-w", "--ignore-all-space"], false, "Ignore whitespace-only differences.")] },
"cherry-pick": { flags: [F(["-n", "--no-commit"], false, "Apply the changes without automatically creating a commit."), F(["--abort"], false, "Cancel the cherry-pick and restore the previous state."), F(["--continue"], false, "Continue the cherry-pick after resolving a conflict.")] },
revert: { flags: [F(["-n", "--no-commit"], false, "Apply the revert without automatically creating a commit."), F(["-m"], true, "Specify the parent number when reverting a merge commit.")] },
tag: { flags: [F(["-a", "--annotate"], true, "Create an annotated tag with a message, author, and date."), F(["-d", "--delete"], true, "Delete a tag."), F(["-l", "--list"], false, "List existing tags.")] },
clone: { flags: [F(["--depth"], true, "Create a shallow clone with history truncated to the given number of commits."), F(["-b", "--branch"], true, "Clone and check out a specific branch instead of the default."), F(["--single-branch"], false, "Clone only the history of a single branch.")] },
remote: { flags: [F(["add"], false, "Add a new named remote repository."), F(["remove", "rm"], false, "Remove a named remote."), F(["-v", "--verbose"], false, "Show remote URLs alongside their names."), F(["set-url"], false, "Change the URL of an existing remote.")] },
clean: { flags: [F(["-n", "--dry-run"], false, "Show what would be removed without actually deleting anything."), F(["-f", "--force"], false, "Actually remove untracked files (required for clean to run at all in most configs).", true), F(["-d"], false, "Also remove untracked directories, not just files.", true), F(["-x"], false, "Also remove files ignored by .gitignore.", true)] },
blame: { flags: [F(["-L"], true, "Limit blame output to a specific line range."), F(["-w"], false, "Ignore whitespace-only changes when attributing lines.")] },
init: { flags: [F(["--bare"], false, "Create a bare repository with no working tree, typically used as a remote."), F(["-b", "--initial-branch"], true, "Set the name of the initial branch.")] },
show: { flags: [F(["--stat"], false, "Show a summary of changed files instead of the full diff."), F(["--name-only"], false, "Show only the names of changed files.")] },
worktree: { flags: [F(["add"], false, "Create a new working tree linked to the repository."), F(["remove"], false, "Remove a working tree."), F(["list"], false, "List all working trees.")] },
config: { flags: [F(["--global"], false, "Read from or write to the global (per-user) config file instead of the repo-local one."), F(["--list"], false, "List all configuration values currently in effect."), F(["--unset"], true, "Remove a configuration key.", true)] }
};
const EXAMPLES = ['git commit -am "fix bug" --amend', "git push --force", "git reset --hard HEAD~1", "git rebase -i HEAD~3", "git clean -fd"];
examplesEl.innerHTML = EXAMPLES.map((e) => `<span class="example-chip">${escapeHtml(e)}</span>`).join("");
examplesEl.querySelectorAll(".example-chip").forEach((chip, i) => {
chip.addEventListener("click", () => {
input.value = EXAMPLES[i];
render();
});
});
function tokenize(cmd) {
const tokens = [];
const regex = /"([^"]*)"|'([^']*)'|(\S+)/g;
let m;
while ((m = regex.exec(cmd))) tokens.push(m[1] ?? m[2] ?? m[3]);
return tokens;
}
function splitCombinedFlag(token, knownFlags) {
if (!/^-[a-zA-Z]{2,}$/.test(token)) return [token];
const chars = token.slice(1).split("");
const shortFlags = knownFlags.filter((f) => f.aliases.some((a) => /^-[a-zA-Z]$/.test(a)));
const matches = chars.every((c) => shortFlags.some((f) => f.aliases.includes("-" + c)));
if (!matches) return [token];
return chars.map((c) => "-" + c);
}
function findFlag(token, knownFlags) {
return knownFlags.find((f) => f.aliases.includes(token));
}
function render() {
const raw = input.value.trim();
if (!raw) {
output.innerHTML = '<div class="empty">Type or paste a Git command to see it explained.</div>';
return;
}
let tokens = tokenize(raw);
if (tokens[0] === "git") tokens = tokens.slice(1);
if (!tokens.length) {
output.innerHTML = '<div class="empty">Type or paste a Git command to see it explained.</div>';
return;
}
const cmdName = tokens[0];
const rest = tokens.slice(1);
if (!(cmdName in TIER1)) {
output.innerHTML = `<div class="result"><div class="unknown">"${escapeHtml(cmdName)}" isn't a recognized Git command. Check for a typo, or this may be a plumbing/scripting command outside this tool's scope.</div></div>`;
return;
}
const desc = TIER1[cmdName];
const tier2 = TIER2[cmdName];
let html = `<div class="result"><div class="result-head"><div class="cmd-name">git ${escapeHtml(cmdName)}</div><div class="cmd-desc">${escapeHtml(desc)}</div>`;
if (!tier2) html += `<div class="tier-note">Flag-by-flag breakdown isn't available for this command yet — showing command-level explanation only.</div>`;
html += `</div>`;
if (tier2 && rest.length) {
const knownFlags = tier2.flags;
const expanded = [];
rest.forEach((t) => {
if (t.startsWith("-") && !t.startsWith("--")) expanded.push(...splitCombinedFlag(t, knownFlags));
else expanded.push(t);
});
const dangerFlags = [];
const rows = [];
let i = 0;
while (i < expanded.length) {
const tok = expanded[i];
if (tok.startsWith("-")) {
const f = findFlag(tok, knownFlags);
if (f) {
let valueHtml = "";
if (f.takesValue && i + 1 < expanded.length && !expanded[i + 1].startsWith("-")) {
valueHtml = `<div class="row-value">${escapeHtml(expanded[i + 1])}</div>`;
i++;
}
if (f.danger) dangerFlags.push(tok);
rows.push(`<div class="row"><span class="chip ${f.danger ? "danger" : "flag"}">${escapeHtml(tok)}</span><div class="row-desc">${escapeHtml(f.desc)}${valueHtml}</div></div>`);
} else {
rows.push(`<div class="row"><span class="chip unrec">${escapeHtml(tok)}</span><div class="row-desc">Flag not covered by this tool's data yet.</div></div>`);
}
} else {
rows.push(`<div class="row"><span class="chip arg">arg</span><div class="row-desc">${escapeHtml(tok)}</div></div>`);
}
i++;
}
if (dangerFlags.length) {
html += `<div class="danger-banner">⚠ This command includes potentially destructive or irreversible flags (${dangerFlags.map(escapeHtml).join(", ")}). Double-check before running it.</div>`;
}
html += rows.join("");
}
html += `</div>`;
output.innerHTML = html;
}
let timer;
input.addEventListener("input", () => {
clearTimeout(timer);
timer = setTimeout(render, 200);
});
themeToggle.addEventListener("click", () => {
const isLight = document.documentElement.getAttribute("data-theme") === "light";
setTheme(isLight ? "dark" : "light");
});
function setTheme(theme) {
document.documentElement.setAttribute("data-theme", theme);
themeToggle.textContent = theme === "light" ? "Dark mode" : "Light mode";
localStorage.setItem("gce-theme", theme);
}
const saved = localStorage.getItem("gce-theme");
const prefersLight = window.matchMedia("(prefers-color-scheme: light)").matches;
setTheme(saved || (prefersLight ? "light" : "dark"));
input.value = 'git commit -am "fix bug" --amend';
render();
</script>
</body>
</html>