@mehmetsagir/git-ai 0.0.9 → 0.0.18

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 (66) hide show
  1. package/README.md +91 -319
  2. package/dist/commit.d.ts +1 -4
  3. package/dist/commit.d.ts.map +1 -1
  4. package/dist/commit.js +214 -208
  5. package/dist/commit.js.map +1 -1
  6. package/dist/config.d.ts +0 -39
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/config.js +3 -95
  9. package/dist/config.js.map +1 -1
  10. package/dist/git.d.ts +19 -51
  11. package/dist/git.d.ts.map +1 -1
  12. package/dist/git.js +137 -269
  13. package/dist/git.js.map +1 -1
  14. package/dist/index.js +54 -109
  15. package/dist/index.js.map +1 -1
  16. package/dist/openai.d.ts +2 -21
  17. package/dist/openai.d.ts.map +1 -1
  18. package/dist/openai.js +17 -96
  19. package/dist/openai.js.map +1 -1
  20. package/dist/prompts.d.ts +2 -16
  21. package/dist/prompts.d.ts.map +1 -1
  22. package/dist/prompts.js +32 -146
  23. package/dist/prompts.js.map +1 -1
  24. package/dist/reset.d.ts +0 -3
  25. package/dist/reset.d.ts.map +1 -1
  26. package/dist/reset.js +10 -20
  27. package/dist/reset.js.map +1 -1
  28. package/dist/setup.d.ts +0 -3
  29. package/dist/setup.d.ts.map +1 -1
  30. package/dist/setup.js +28 -156
  31. package/dist/setup.js.map +1 -1
  32. package/dist/stash.d.ts +2 -0
  33. package/dist/stash.d.ts.map +1 -0
  34. package/dist/stash.js +818 -0
  35. package/dist/stash.js.map +1 -0
  36. package/dist/types.d.ts +9 -23
  37. package/dist/types.d.ts.map +1 -1
  38. package/dist/utils/hunk-parser.d.ts +22 -0
  39. package/dist/utils/hunk-parser.d.ts.map +1 -0
  40. package/dist/utils/hunk-parser.js +155 -0
  41. package/dist/utils/hunk-parser.js.map +1 -0
  42. package/package.json +8 -9
  43. package/dist/add.d.ts +0 -5
  44. package/dist/add.d.ts.map +0 -1
  45. package/dist/add.js +0 -98
  46. package/dist/add.js.map +0 -1
  47. package/dist/commit-processor.d.ts +0 -6
  48. package/dist/commit-processor.d.ts.map +0 -1
  49. package/dist/commit-processor.js +0 -226
  50. package/dist/commit-processor.js.map +0 -1
  51. package/dist/update.d.ts +0 -9
  52. package/dist/update.d.ts.map +0 -1
  53. package/dist/update.js +0 -69
  54. package/dist/update.js.map +0 -1
  55. package/dist/user-management.d.ts +0 -10
  56. package/dist/user-management.d.ts.map +0 -1
  57. package/dist/user-management.js +0 -175
  58. package/dist/user-management.js.map +0 -1
  59. package/dist/users.d.ts +0 -9
  60. package/dist/users.d.ts.map +0 -1
  61. package/dist/users.js +0 -129
  62. package/dist/users.js.map +0 -1
  63. package/dist/utils/validation.d.ts +0 -24
  64. package/dist/utils/validation.d.ts.map +0 -1
  65. package/dist/utils/validation.js +0 -81
  66. package/dist/utils/validation.js.map +0 -1
package/dist/stash.js ADDED
@@ -0,0 +1,818 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.runStashViewer = runStashViewer;
40
+ const http = __importStar(require("http"));
41
+ const child_process_1 = require("child_process");
42
+ const chalk_1 = __importDefault(require("chalk"));
43
+ const git = __importStar(require("./git"));
44
+ const PORT = 3847;
45
+ function getHtml(data) {
46
+ const stashesJson = JSON.stringify(data.stashes);
47
+ const diffsJson = {};
48
+ const filesJson = {};
49
+ data.diffs.forEach((v, k) => { diffsJson[k] = v; });
50
+ data.files.forEach((v, k) => { filesJson[k] = v; });
51
+ return `<!DOCTYPE html>
52
+ <html>
53
+ <head>
54
+ <meta charset="UTF-8">
55
+ <title>Git Stash Viewer</title>
56
+ <style>
57
+ * { box-sizing: border-box; margin: 0; padding: 0; }
58
+ body {
59
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
60
+ background: #1e1e1e;
61
+ color: #cccccc;
62
+ height: 100vh;
63
+ overflow: hidden;
64
+ }
65
+ .container { display: flex; height: 100vh; }
66
+
67
+ /* Sidebar */
68
+ .sidebar {
69
+ width: 300px;
70
+ background: #252526;
71
+ border-right: 1px solid #3c3c3c;
72
+ display: flex;
73
+ flex-direction: column;
74
+ flex-shrink: 0;
75
+ }
76
+ .sidebar-header {
77
+ padding: 12px 16px;
78
+ background: #2d2d2d;
79
+ border-bottom: 1px solid #3c3c3c;
80
+ font-size: 11px;
81
+ text-transform: uppercase;
82
+ letter-spacing: 0.5px;
83
+ color: #bbbbbb;
84
+ display: flex;
85
+ align-items: center;
86
+ gap: 8px;
87
+ }
88
+ .back-btn {
89
+ display: flex;
90
+ align-items: center;
91
+ gap: 6px;
92
+ background: none;
93
+ border: none;
94
+ color: #58a6ff;
95
+ cursor: pointer;
96
+ font-size: 12px;
97
+ padding: 4px 8px;
98
+ border-radius: 4px;
99
+ transition: background 0.1s;
100
+ }
101
+ .back-btn:hover { background: #3c3c3c; }
102
+ .back-btn svg {
103
+ width: 14px;
104
+ height: 14px;
105
+ }
106
+ .stash-title {
107
+ color: #4ec9b0;
108
+ font-size: 12px;
109
+ font-weight: 500;
110
+ text-transform: none;
111
+ letter-spacing: normal;
112
+ }
113
+ .sidebar-footer {
114
+ padding: 14px 16px;
115
+ border-top: 1px solid #3c3c3c;
116
+ background: #1e1e1e;
117
+ text-align: center;
118
+ }
119
+ .sidebar-footer-brand {
120
+ font-size: 13px;
121
+ font-weight: 600;
122
+ color: #e1e1e1;
123
+ margin-bottom: 6px;
124
+ font-family: 'SF Mono', Monaco, monospace;
125
+ }
126
+ .sidebar-footer a {
127
+ display: inline-flex;
128
+ align-items: center;
129
+ gap: 6px;
130
+ color: #6e7681;
131
+ text-decoration: none;
132
+ font-size: 11px;
133
+ transition: color 0.15s;
134
+ }
135
+ .sidebar-footer a:hover {
136
+ color: #58a6ff;
137
+ }
138
+ .sidebar-footer a svg {
139
+ width: 14px;
140
+ height: 14px;
141
+ }
142
+ .stash-actions {
143
+ display: flex;
144
+ gap: 8px;
145
+ padding: 12px 16px;
146
+ background: #2d2d2d;
147
+ border-bottom: 1px solid #3c3c3c;
148
+ }
149
+ .stash-actions button {
150
+ flex: 1;
151
+ padding: 8px 12px;
152
+ border: none;
153
+ border-radius: 4px;
154
+ font-size: 12px;
155
+ font-weight: 500;
156
+ cursor: pointer;
157
+ display: flex;
158
+ align-items: center;
159
+ justify-content: center;
160
+ gap: 6px;
161
+ transition: background 0.15s, opacity 0.15s;
162
+ }
163
+ .stash-actions button:disabled {
164
+ opacity: 0.5;
165
+ cursor: not-allowed;
166
+ }
167
+ .stash-actions button svg {
168
+ width: 14px;
169
+ height: 14px;
170
+ }
171
+ .btn-apply {
172
+ background: #238636;
173
+ color: #fff;
174
+ }
175
+ .btn-apply:hover:not(:disabled) {
176
+ background: #2ea043;
177
+ }
178
+ .btn-delete {
179
+ background: #da3633;
180
+ color: #fff;
181
+ }
182
+ .btn-delete:hover:not(:disabled) {
183
+ background: #f85149;
184
+ }
185
+ .toast {
186
+ position: fixed;
187
+ bottom: 20px;
188
+ right: 20px;
189
+ padding: 12px 20px;
190
+ border-radius: 6px;
191
+ font-size: 13px;
192
+ color: #fff;
193
+ opacity: 0;
194
+ transform: translateY(10px);
195
+ transition: opacity 0.2s, transform 0.2s;
196
+ z-index: 1000;
197
+ }
198
+ .toast.show {
199
+ opacity: 1;
200
+ transform: translateY(0);
201
+ }
202
+ .toast.success { background: #238636; }
203
+ .toast.error { background: #da3633; }
204
+ .sidebar-content {
205
+ flex: 1;
206
+ overflow-y: auto;
207
+ }
208
+
209
+ /* Stash List View */
210
+ .stash-item {
211
+ padding: 12px 16px;
212
+ cursor: pointer;
213
+ border-bottom: 1px solid #2d2d2d;
214
+ transition: background 0.1s;
215
+ }
216
+ .stash-item:hover { background: #2a2d2e; }
217
+ .stash-index {
218
+ color: #4ec9b0;
219
+ font-size: 12px;
220
+ font-weight: 600;
221
+ }
222
+ .stash-message {
223
+ font-size: 13px;
224
+ margin-top: 4px;
225
+ white-space: nowrap;
226
+ overflow: hidden;
227
+ text-overflow: ellipsis;
228
+ }
229
+ .stash-meta {
230
+ font-size: 11px;
231
+ color: #808080;
232
+ margin-top: 4px;
233
+ display: flex;
234
+ align-items: center;
235
+ gap: 8px;
236
+ }
237
+ .stash-files-count {
238
+ background: #3c3c3c;
239
+ padding: 2px 6px;
240
+ border-radius: 10px;
241
+ font-size: 10px;
242
+ }
243
+
244
+ /* File List View */
245
+ .file-item {
246
+ padding: 8px 16px;
247
+ cursor: pointer;
248
+ font-size: 13px;
249
+ font-family: 'SF Mono', Monaco, monospace;
250
+ border-bottom: 1px solid #2d2d2d;
251
+ display: flex;
252
+ align-items: center;
253
+ gap: 8px;
254
+ transition: background 0.1s;
255
+ }
256
+ .file-item:hover { background: #2a2d2e; }
257
+ .file-item.active { background: #094771; }
258
+ .file-status {
259
+ width: 8px;
260
+ height: 8px;
261
+ border-radius: 50%;
262
+ flex-shrink: 0;
263
+ background: #e2c08d;
264
+ }
265
+ .file-status.new { background: #73c991; }
266
+ .file-status.deleted { background: #f14c4c; }
267
+ .file-name {
268
+ flex: 1;
269
+ white-space: nowrap;
270
+ overflow: hidden;
271
+ text-overflow: ellipsis;
272
+ }
273
+ .file-path {
274
+ color: #6e7681;
275
+ font-size: 11px;
276
+ }
277
+ .all-changes-item {
278
+ padding: 10px 16px;
279
+ cursor: pointer;
280
+ font-size: 13px;
281
+ border-bottom: 1px solid #3c3c3c;
282
+ background: #2d2d2d;
283
+ display: flex;
284
+ align-items: center;
285
+ gap: 8px;
286
+ transition: background 0.1s;
287
+ }
288
+ .all-changes-item:hover { background: #3c3c3c; }
289
+ .all-changes-item.active { background: #094771; }
290
+ .all-changes-item svg {
291
+ width: 16px;
292
+ height: 16px;
293
+ color: #58a6ff;
294
+ }
295
+
296
+ /* Main Content */
297
+ .main {
298
+ flex: 1;
299
+ display: flex;
300
+ flex-direction: column;
301
+ overflow: hidden;
302
+ }
303
+ .toolbar {
304
+ padding: 8px 16px;
305
+ background: #2d2d2d;
306
+ border-bottom: 1px solid #3c3c3c;
307
+ font-size: 13px;
308
+ display: flex;
309
+ align-items: center;
310
+ gap: 12px;
311
+ }
312
+ .toolbar-file {
313
+ font-family: 'SF Mono', Monaco, monospace;
314
+ color: #dcdcaa;
315
+ }
316
+ .diff-container {
317
+ flex: 1;
318
+ display: flex;
319
+ overflow: hidden;
320
+ }
321
+ .diff-pane {
322
+ flex: 1;
323
+ overflow: auto;
324
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
325
+ font-size: 13px;
326
+ line-height: 20px;
327
+ }
328
+ .diff-pane.old { background: #1e1e1e; border-right: 1px solid #3c3c3c; }
329
+ .diff-pane.new { background: #1e1e1e; }
330
+ .diff-pane-header {
331
+ padding: 8px 16px;
332
+ background: #2d2d2d;
333
+ border-bottom: 1px solid #3c3c3c;
334
+ font-size: 12px;
335
+ color: #808080;
336
+ position: sticky;
337
+ top: 0;
338
+ }
339
+ .diff-line {
340
+ padding: 0 16px;
341
+ white-space: pre;
342
+ min-height: 20px;
343
+ }
344
+ .diff-line.add { background: #2ea04326; color: #3fb950; }
345
+ .diff-line.del { background: #f8514926; color: #f85149; }
346
+ .diff-line.hunk { background: #388bfd26; color: #58a6ff; }
347
+ .line-num {
348
+ display: inline-block;
349
+ width: 40px;
350
+ color: #6e7681;
351
+ text-align: right;
352
+ margin-right: 16px;
353
+ user-select: none;
354
+ }
355
+
356
+ /* Empty State */
357
+ .empty-state {
358
+ flex: 1;
359
+ display: flex;
360
+ flex-direction: column;
361
+ align-items: center;
362
+ justify-content: center;
363
+ color: #6e7681;
364
+ }
365
+ .empty-state svg {
366
+ width: 64px;
367
+ height: 64px;
368
+ margin-bottom: 16px;
369
+ opacity: 0.5;
370
+ }
371
+
372
+ /* Scrollbar */
373
+ ::-webkit-scrollbar { width: 10px; height: 10px; }
374
+ ::-webkit-scrollbar-track { background: #1e1e1e; }
375
+ ::-webkit-scrollbar-thumb { background: #424242; border-radius: 5px; }
376
+ ::-webkit-scrollbar-thumb:hover { background: #4f4f4f; }
377
+
378
+ .hidden { display: none !important; }
379
+ </style>
380
+ </head>
381
+ <body>
382
+ <div class="container">
383
+ <div class="sidebar">
384
+ <div class="sidebar-header" id="sidebarHeader">
385
+ <span id="headerTitle">Stashes</span>
386
+ </div>
387
+ <div class="sidebar-content" id="sidebarContent"></div>
388
+ <div class="sidebar-footer">
389
+ <div class="sidebar-footer-brand">git-ai</div>
390
+ <a href="https://github.com/mehmetsagir/git-ai" target="_blank">
391
+ <svg viewBox="0 0 24 24" fill="currentColor">
392
+ <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
393
+ </svg>
394
+ Open source on GitHub
395
+ </a>
396
+ </div>
397
+ </div>
398
+ <div class="main">
399
+ <div class="toolbar" id="toolbar" style="display:none;">
400
+ <span class="toolbar-file" id="toolbarFile"></span>
401
+ </div>
402
+ <div class="diff-container" id="diffContainer">
403
+ <div class="empty-state" id="emptyState">
404
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
405
+ <path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
406
+ </svg>
407
+ <div>Select a stash to view changes</div>
408
+ </div>
409
+ </div>
410
+ </div>
411
+ </div>
412
+ <div class="toast" id="toast"></div>
413
+
414
+ <script>
415
+ let stashes = ${stashesJson};
416
+ let diffs = ${JSON.stringify(diffsJson)};
417
+ let files = ${JSON.stringify(filesJson)};
418
+ let isLoading = false;
419
+
420
+ let currentView = 'list'; // 'list' or 'detail'
421
+ let selectedStash = null;
422
+ let selectedFile = null;
423
+
424
+ function renderStashList() {
425
+ currentView = 'list';
426
+ selectedStash = null;
427
+ selectedFile = null;
428
+
429
+ // Update header
430
+ document.getElementById('sidebarHeader').innerHTML = '<span id="headerTitle">Stashes</span>';
431
+
432
+ // Hide toolbar and show empty state
433
+ document.getElementById('toolbar').style.display = 'none';
434
+ document.getElementById('diffContainer').innerHTML = \`
435
+ <div class="empty-state">
436
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
437
+ <path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
438
+ </svg>
439
+ <div>Select a stash to view changes</div>
440
+ </div>
441
+ \`;
442
+
443
+ // Render stash list
444
+ const content = document.getElementById('sidebarContent');
445
+ if (stashes.length === 0) {
446
+ content.innerHTML = '<div style="padding:20px;color:#6e7681;text-align:center;">No stashes found</div>';
447
+ return;
448
+ }
449
+
450
+ content.innerHTML = stashes.map(s => {
451
+ const date = new Date(s.date).toLocaleDateString();
452
+ const fileCount = (files[s.index] || []).length;
453
+ return \`
454
+ <div class="stash-item" onclick="openStash(\${s.index})">
455
+ <div class="stash-index">stash@{\${s.index}}</div>
456
+ <div class="stash-message">\${escapeHtml(s.message)}</div>
457
+ <div class="stash-meta">
458
+ <span>\${s.branch}</span>
459
+ <span>\${date}</span>
460
+ <span class="stash-files-count">\${fileCount} file\${fileCount !== 1 ? 's' : ''}</span>
461
+ </div>
462
+ </div>
463
+ \`;
464
+ }).join('');
465
+ }
466
+
467
+ function openStash(index) {
468
+ currentView = 'detail';
469
+ selectedStash = index;
470
+ selectedFile = null;
471
+
472
+ const stash = stashes.find(s => s.index === index);
473
+ const stashFiles = files[index] || [];
474
+
475
+ // Update header with back button
476
+ document.getElementById('sidebarHeader').innerHTML = \`
477
+ <button class="back-btn" onclick="renderStashList()">
478
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
479
+ <path d="M15 18l-6-6 6-6"/>
480
+ </svg>
481
+ Back
482
+ </button>
483
+ <span class="stash-title">stash@{\${index}}</span>
484
+ \`;
485
+
486
+ // Render file list with action buttons
487
+ const content = document.getElementById('sidebarContent');
488
+ let html = \`
489
+ <div class="stash-actions">
490
+ <button class="btn-apply" onclick="applyStash(\${index})" \${isLoading ? 'disabled' : ''}>
491
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
492
+ <path d="M5 13l4 4L19 7"/>
493
+ </svg>
494
+ Apply
495
+ </button>
496
+ <button class="btn-delete" onclick="deleteStash(\${index})" \${isLoading ? 'disabled' : ''}>
497
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
498
+ <path d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
499
+ </svg>
500
+ Delete
501
+ </button>
502
+ </div>
503
+ <div class="all-changes-item active" onclick="showAllChanges()">
504
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
505
+ <path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
506
+ </svg>
507
+ All Changes
508
+ </div>
509
+ \`;
510
+
511
+ html += stashFiles.map(f => {
512
+ const isNew = diffs[index]?.includes('new file mode') && diffs[index]?.includes(f);
513
+ const isDel = diffs[index]?.includes('deleted file mode') && diffs[index]?.includes(f);
514
+ const statusCls = isDel ? 'deleted' : (isNew ? 'new' : '');
515
+ const fileName = f.split('/').pop();
516
+ const dirPath = f.includes('/') ? f.substring(0, f.lastIndexOf('/')) : '';
517
+ return \`
518
+ <div class="file-item" data-file="\${escapeHtml(f)}" onclick="selectFile('\${escapeHtml(f)}')">
519
+ <div class="file-status \${statusCls}"></div>
520
+ <div class="file-name">
521
+ \${escapeHtml(fileName)}
522
+ \${dirPath ? \`<span class="file-path">\${escapeHtml(dirPath)}</span>\` : ''}
523
+ </div>
524
+ </div>
525
+ \`;
526
+ }).join('');
527
+
528
+ content.innerHTML = html;
529
+
530
+ // Show full diff by default
531
+ showAllChanges();
532
+ }
533
+
534
+ function showAllChanges() {
535
+ selectedFile = null;
536
+ updateFileSelection();
537
+
538
+ const diff = diffs[selectedStash] || '';
539
+ document.getElementById('toolbar').style.display = 'flex';
540
+ document.getElementById('toolbarFile').textContent = 'All Changes';
541
+ renderSplitDiff(diff);
542
+ }
543
+
544
+ function selectFile(filename) {
545
+ selectedFile = filename;
546
+ updateFileSelection();
547
+
548
+ const fullDiff = diffs[selectedStash] || '';
549
+ document.getElementById('toolbar').style.display = 'flex';
550
+ document.getElementById('toolbarFile').textContent = filename;
551
+
552
+ const fileDiff = extractFileDiff(fullDiff, filename);
553
+ renderSplitDiff(fileDiff);
554
+ }
555
+
556
+ function updateFileSelection() {
557
+ // Update all changes item
558
+ document.querySelectorAll('.all-changes-item').forEach(el => {
559
+ el.classList.toggle('active', selectedFile === null);
560
+ });
561
+
562
+ // Update file items
563
+ document.querySelectorAll('.file-item').forEach(el => {
564
+ el.classList.toggle('active', el.dataset.file === selectedFile);
565
+ });
566
+ }
567
+
568
+ function extractFileDiff(fullDiff, filename) {
569
+ const parts = fullDiff.split(/(?=diff --git )/);
570
+ for (const part of parts) {
571
+ if (part.includes(filename)) {
572
+ return part;
573
+ }
574
+ }
575
+ return '';
576
+ }
577
+
578
+ function renderSplitDiff(diff) {
579
+ const container = document.getElementById('diffContainer');
580
+
581
+ if (!diff) {
582
+ container.innerHTML = '<div class="empty-state"><div>No changes to display</div></div>';
583
+ return;
584
+ }
585
+
586
+ const lines = diff.split('\\n');
587
+ let oldLines = [];
588
+ let newLines = [];
589
+ let oldNum = 0, newNum = 0;
590
+
591
+ for (const line of lines) {
592
+ if (line.startsWith('@@')) {
593
+ const match = line.match(/@@ -(\\d+)/);
594
+ if (match) oldNum = parseInt(match[1]) - 1;
595
+ const match2 = line.match(/\\+(\\d+)/);
596
+ if (match2) newNum = parseInt(match2[1]) - 1;
597
+ oldLines.push({ type: 'hunk', text: line, num: '' });
598
+ newLines.push({ type: 'hunk', text: line, num: '' });
599
+ } else if (line.startsWith('diff --git') || line.startsWith('index ') ||
600
+ line.startsWith('---') || line.startsWith('+++') ||
601
+ line.startsWith('new file') || line.startsWith('deleted file')) {
602
+ // Skip header lines
603
+ } else if (line.startsWith('-')) {
604
+ oldNum++;
605
+ oldLines.push({ type: 'del', text: line.substring(1), num: oldNum });
606
+ newLines.push({ type: 'empty', text: '', num: '' });
607
+ } else if (line.startsWith('+')) {
608
+ newNum++;
609
+ oldLines.push({ type: 'empty', text: '', num: '' });
610
+ newLines.push({ type: 'add', text: line.substring(1), num: newNum });
611
+ } else if (line.startsWith(' ') || line === '') {
612
+ oldNum++;
613
+ newNum++;
614
+ oldLines.push({ type: 'context', text: line.substring(1) || '', num: oldNum });
615
+ newLines.push({ type: 'context', text: line.substring(1) || '', num: newNum });
616
+ }
617
+ }
618
+
619
+ container.innerHTML = \`
620
+ <div class="diff-pane old">
621
+ <div class="diff-pane-header">Original</div>
622
+ \${oldLines.map(l => \`<div class="diff-line \${l.type}"><span class="line-num">\${l.num}</span>\${escapeHtml(l.text)}</div>\`).join('')}
623
+ </div>
624
+ <div class="diff-pane new">
625
+ <div class="diff-pane-header">Modified</div>
626
+ \${newLines.map(l => \`<div class="diff-line \${l.type}"><span class="line-num">\${l.num}</span>\${escapeHtml(l.text)}</div>\`).join('')}
627
+ </div>
628
+ \`;
629
+
630
+ // Sync scroll
631
+ const oldPane = container.querySelector('.diff-pane.old');
632
+ const newPane = container.querySelector('.diff-pane.new');
633
+ oldPane.addEventListener('scroll', () => { newPane.scrollTop = oldPane.scrollTop; });
634
+ newPane.addEventListener('scroll', () => { oldPane.scrollTop = newPane.scrollTop; });
635
+ }
636
+
637
+ function escapeHtml(text) {
638
+ return text?.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;') || '';
639
+ }
640
+
641
+ function showToast(message, type = 'success') {
642
+ const toast = document.getElementById('toast');
643
+ toast.textContent = message;
644
+ toast.className = 'toast ' + type + ' show';
645
+ setTimeout(() => { toast.classList.remove('show'); }, 3000);
646
+ }
647
+
648
+ async function refreshData() {
649
+ const res = await fetch('/api/data');
650
+ const data = await res.json();
651
+ stashes = data.stashes;
652
+ diffs = data.diffs;
653
+ files = data.files;
654
+ }
655
+
656
+ async function applyStash(index) {
657
+ if (isLoading) return;
658
+ isLoading = true;
659
+
660
+ try {
661
+ const res = await fetch('/api/apply/' + index, { method: 'POST' });
662
+ const data = await res.json();
663
+
664
+ if (data.success) {
665
+ showToast('Stash applied successfully!', 'success');
666
+ await refreshData();
667
+ renderStashList();
668
+ } else {
669
+ showToast('Failed to apply stash: ' + (data.error || 'Unknown error'), 'error');
670
+ }
671
+ } catch (err) {
672
+ showToast('Failed to apply stash', 'error');
673
+ }
674
+
675
+ isLoading = false;
676
+ }
677
+
678
+ async function deleteStash(index) {
679
+ if (isLoading) return;
680
+ if (!confirm('Are you sure you want to delete this stash? This cannot be undone.')) return;
681
+
682
+ isLoading = true;
683
+
684
+ try {
685
+ const res = await fetch('/api/drop/' + index, { method: 'POST' });
686
+ const data = await res.json();
687
+
688
+ if (data.success) {
689
+ showToast('Stash deleted successfully!', 'success');
690
+ await refreshData();
691
+ renderStashList();
692
+ } else {
693
+ showToast('Failed to delete stash: ' + (data.error || 'Unknown error'), 'error');
694
+ }
695
+ } catch (err) {
696
+ showToast('Failed to delete stash', 'error');
697
+ }
698
+
699
+ isLoading = false;
700
+ }
701
+
702
+ // Initial render
703
+ renderStashList();
704
+ </script>
705
+ </body>
706
+ </html>`;
707
+ }
708
+ function openBrowser(url) {
709
+ const cmd = process.platform === "darwin"
710
+ ? `open "${url}"`
711
+ : process.platform === "win32"
712
+ ? `start "${url}"`
713
+ : `xdg-open "${url}"`;
714
+ (0, child_process_1.exec)(cmd, (err) => {
715
+ if (err) {
716
+ console.log(chalk_1.default.yellow(`Open manually: ${url}`));
717
+ }
718
+ });
719
+ }
720
+ async function runStashViewer() {
721
+ console.log(chalk_1.default.blue.bold("\nšŸ“¦ Git Stash Viewer\n"));
722
+ if (!(await git.isGitRepository())) {
723
+ console.log(chalk_1.default.red("āŒ Not a git repository\n"));
724
+ return;
725
+ }
726
+ console.log(chalk_1.default.gray("Loading stashes..."));
727
+ const stashes = await git.getStashList();
728
+ const diffs = new Map();
729
+ const files = new Map();
730
+ for (const stash of stashes) {
731
+ const diff = await git.getStashDiff(stash.index);
732
+ const fileList = await git.getStashFiles(stash.index);
733
+ diffs.set(stash.index, diff);
734
+ files.set(stash.index, fileList);
735
+ }
736
+ console.log(chalk_1.default.green(`āœ“ Found ${stashes.length} stash(es)`));
737
+ let data = { stashes, diffs, files };
738
+ async function reloadStashes() {
739
+ const newStashes = await git.getStashList();
740
+ const newDiffs = new Map();
741
+ const newFiles = new Map();
742
+ for (const stash of newStashes) {
743
+ const diff = await git.getStashDiff(stash.index);
744
+ const fileList = await git.getStashFiles(stash.index);
745
+ newDiffs.set(stash.index, diff);
746
+ newFiles.set(stash.index, fileList);
747
+ }
748
+ data = { stashes: newStashes, diffs: newDiffs, files: newFiles };
749
+ }
750
+ const server = http.createServer(async (req, res) => {
751
+ const url = req.url || "/";
752
+ // API: Apply stash
753
+ if (url.startsWith("/api/apply/") && req.method === "POST") {
754
+ const index = parseInt(url.split("/").pop() || "");
755
+ try {
756
+ await git.applyStash(index);
757
+ await reloadStashes();
758
+ res.writeHead(200, { "Content-Type": "application/json" });
759
+ res.end(JSON.stringify({ success: true }));
760
+ }
761
+ catch (err) {
762
+ res.writeHead(500, { "Content-Type": "application/json" });
763
+ res.end(JSON.stringify({ success: false, error: String(err) }));
764
+ }
765
+ return;
766
+ }
767
+ // API: Drop stash
768
+ if (url.startsWith("/api/drop/") && req.method === "POST") {
769
+ const index = parseInt(url.split("/").pop() || "");
770
+ try {
771
+ await git.dropStash(index);
772
+ await reloadStashes();
773
+ res.writeHead(200, { "Content-Type": "application/json" });
774
+ res.end(JSON.stringify({ success: true }));
775
+ }
776
+ catch (err) {
777
+ res.writeHead(500, { "Content-Type": "application/json" });
778
+ res.end(JSON.stringify({ success: false, error: String(err) }));
779
+ }
780
+ return;
781
+ }
782
+ // API: Get fresh data
783
+ if (url === "/api/data" && req.method === "GET") {
784
+ const diffsJson = {};
785
+ const filesJson = {};
786
+ data.diffs.forEach((v, k) => { diffsJson[k] = v; });
787
+ data.files.forEach((v, k) => { filesJson[k] = v; });
788
+ res.writeHead(200, { "Content-Type": "application/json" });
789
+ res.end(JSON.stringify({ stashes: data.stashes, diffs: diffsJson, files: filesJson }));
790
+ return;
791
+ }
792
+ // Default: serve HTML
793
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
794
+ res.end(getHtml(data));
795
+ });
796
+ server.on("error", (err) => {
797
+ if (err.code === "EADDRINUSE") {
798
+ console.log(chalk_1.default.red(`\nāŒ Port ${PORT} is already in use.`));
799
+ console.log(chalk_1.default.gray(`Run: lsof -ti:${PORT} | xargs kill -9\n`));
800
+ }
801
+ else {
802
+ console.log(chalk_1.default.red(`\nāŒ Server error: ${err.message}\n`));
803
+ }
804
+ process.exit(1);
805
+ });
806
+ server.listen(PORT, () => {
807
+ const url = `http://localhost:${PORT}`;
808
+ console.log(chalk_1.default.blue(`\n🌐 Server running at ${url}`));
809
+ console.log(chalk_1.default.gray("Press Ctrl+C to stop\n"));
810
+ openBrowser(url);
811
+ });
812
+ process.on("SIGINT", () => {
813
+ console.log(chalk_1.default.yellow("\n\nšŸ‘‹ Server stopped\n"));
814
+ server.close();
815
+ process.exit(0);
816
+ });
817
+ }
818
+ //# sourceMappingURL=stash.js.map