git-watchtower 1.6.1 → 1.7.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/bin/git-watchtower.js +49 -2
- package/package.json +6 -1
- package/sounds/README.md +34 -0
- package/src/casino/index.js +721 -0
- package/src/casino/sounds.js +245 -0
- package/src/cli/args.js +239 -0
- package/src/config/loader.js +329 -0
- package/src/config/schema.js +305 -0
- package/src/git/branch.js +428 -0
- package/src/git/commands.js +416 -0
- package/src/git/pr.js +111 -0
- package/src/git/remote.js +127 -0
- package/src/index.js +179 -0
- package/src/polling/engine.js +157 -0
- package/src/server/process.js +329 -0
- package/src/server/static.js +95 -0
- package/src/state/store.js +527 -0
- package/src/telemetry/analytics.js +142 -0
- package/src/telemetry/config.js +123 -0
- package/src/telemetry/index.js +93 -0
- package/src/ui/actions.js +425 -0
- package/src/ui/ansi.js +498 -0
- package/src/ui/keybindings.js +198 -0
- package/src/ui/renderer.js +1326 -0
- package/src/utils/async.js +219 -0
- package/src/utils/browser.js +40 -0
- package/src/utils/errors.js +490 -0
- package/src/utils/gitignore.js +174 -0
- package/src/utils/sound.js +33 -0
- package/src/utils/time.js +27 -0
package/bin/git-watchtower.js
CHANGED
|
@@ -66,6 +66,9 @@ const casinoSounds = require('../src/casino/sounds');
|
|
|
66
66
|
// Gitignore utilities for file watcher
|
|
67
67
|
const { loadGitignorePatterns, shouldIgnoreFile } = require('../src/utils/gitignore');
|
|
68
68
|
|
|
69
|
+
// Telemetry (opt-in PostHog analytics)
|
|
70
|
+
const telemetry = require('../src/telemetry');
|
|
71
|
+
|
|
69
72
|
// Extracted modules
|
|
70
73
|
const { formatTimeAgo } = require('../src/utils/time');
|
|
71
74
|
const { openInBrowser: openUrl } = require('../src/utils/browser');
|
|
@@ -195,6 +198,7 @@ async function runConfigurationWizard() {
|
|
|
195
198
|
|
|
196
199
|
// Save configuration
|
|
197
200
|
saveConfig(config);
|
|
201
|
+
telemetry.capture('config_wizard_completed', { server_mode: config.server.mode });
|
|
198
202
|
|
|
199
203
|
console.log('\n✓ Configuration saved to ' + CONFIG_FILE_NAME);
|
|
200
204
|
console.log(' You can edit this file manually or delete it to reconfigure.\n');
|
|
@@ -367,6 +371,10 @@ let AUTO_PULL = true;
|
|
|
367
371
|
const MAX_LOG_ENTRIES = 10;
|
|
368
372
|
const MAX_SERVER_LOG_LINES = 500;
|
|
369
373
|
|
|
374
|
+
// Telemetry session tracking
|
|
375
|
+
let branchSwitchCount = 0;
|
|
376
|
+
let sessionStartTime = null;
|
|
377
|
+
|
|
370
378
|
// Server process management (for command mode)
|
|
371
379
|
let serverProcess = null;
|
|
372
380
|
|
|
@@ -1419,6 +1427,7 @@ async function switchToBranch(branchName, recordHistory = true) {
|
|
|
1419
1427
|
addLog(`Cannot switch: uncommitted changes in working directory`, 'error');
|
|
1420
1428
|
pendingDirtyOperation = { type: 'switch', branch: branchName };
|
|
1421
1429
|
showStashConfirm(`switch to ${branchName}`);
|
|
1430
|
+
telemetry.capture('dirty_repo_encountered');
|
|
1422
1431
|
return { success: false, reason: 'dirty' };
|
|
1423
1432
|
}
|
|
1424
1433
|
|
|
@@ -1451,6 +1460,8 @@ async function switchToBranch(branchName, recordHistory = true) {
|
|
|
1451
1460
|
}
|
|
1452
1461
|
|
|
1453
1462
|
addLog(`Switched to ${safeBranchName}`, 'success');
|
|
1463
|
+
telemetry.capture('branch_switched');
|
|
1464
|
+
branchSwitchCount++;
|
|
1454
1465
|
pendingDirtyOperation = null;
|
|
1455
1466
|
|
|
1456
1467
|
// Restart server if configured (command mode)
|
|
@@ -1480,6 +1491,7 @@ async function switchToBranch(branchName, recordHistory = true) {
|
|
|
1480
1491
|
truncate(errMsg, 100),
|
|
1481
1492
|
'Check the activity log for details'
|
|
1482
1493
|
);
|
|
1494
|
+
telemetry.captureError(e);
|
|
1483
1495
|
}
|
|
1484
1496
|
return { success: false };
|
|
1485
1497
|
}
|
|
@@ -1588,6 +1600,7 @@ async function stashAndRetry() {
|
|
|
1588
1600
|
}
|
|
1589
1601
|
|
|
1590
1602
|
addLog('Changes stashed successfully', 'success');
|
|
1603
|
+
telemetry.capture('stash_performed');
|
|
1591
1604
|
|
|
1592
1605
|
if (operation.type === 'switch') {
|
|
1593
1606
|
const switchResult = await switchToBranch(operation.branch);
|
|
@@ -2248,6 +2261,7 @@ function setupKeyboardInput() {
|
|
|
2248
2261
|
openInBrowser(prUrl);
|
|
2249
2262
|
} else if (!prInfo && prLoaded && cliReady) {
|
|
2250
2263
|
// Create PR — only if we've confirmed no PR exists (prLoaded=true)
|
|
2264
|
+
telemetry.capture('pr_action', { action: 'create' });
|
|
2251
2265
|
addLog(`Creating ${prLabel} for ${aBranch.name}...`, 'update');
|
|
2252
2266
|
render();
|
|
2253
2267
|
try {
|
|
@@ -2289,6 +2303,7 @@ function setupKeyboardInput() {
|
|
|
2289
2303
|
return;
|
|
2290
2304
|
}
|
|
2291
2305
|
if (key === 'a' && prInfo && cliReady) { // Approve PR
|
|
2306
|
+
telemetry.capture('pr_action', { action: 'approve' });
|
|
2292
2307
|
addLog(`Approving ${prLabel} #${prInfo.number}...`, 'update');
|
|
2293
2308
|
render();
|
|
2294
2309
|
try {
|
|
@@ -2308,6 +2323,7 @@ function setupKeyboardInput() {
|
|
|
2308
2323
|
return;
|
|
2309
2324
|
}
|
|
2310
2325
|
if (key === 'm' && prInfo && cliReady) { // Merge PR
|
|
2326
|
+
telemetry.capture('pr_action', { action: 'merge' });
|
|
2311
2327
|
addLog(`Merging ${prLabel} #${prInfo.number}...`, 'update');
|
|
2312
2328
|
render();
|
|
2313
2329
|
try {
|
|
@@ -2406,6 +2422,7 @@ function setupKeyboardInput() {
|
|
|
2406
2422
|
addLog(`Failed to delete ${f.name}: ${f.error}`, 'error');
|
|
2407
2423
|
}
|
|
2408
2424
|
if (result.deleted.length > 0) {
|
|
2425
|
+
telemetry.capture('cleanup_branches_deleted', { count: result.deleted.length });
|
|
2409
2426
|
addLog(`Cleaned up ${result.deleted.length} branch${result.deleted.length === 1 ? '' : 'es'}`, 'success');
|
|
2410
2427
|
await pollGitChanges();
|
|
2411
2428
|
}
|
|
@@ -2528,12 +2545,14 @@ function setupKeyboardInput() {
|
|
|
2528
2545
|
render();
|
|
2529
2546
|
const pvData = await getPreviewData(branch.name);
|
|
2530
2547
|
store.setState({ previewData: pvData, previewMode: true });
|
|
2548
|
+
telemetry.capture('preview_opened');
|
|
2531
2549
|
render();
|
|
2532
2550
|
}
|
|
2533
2551
|
break;
|
|
2534
2552
|
|
|
2535
2553
|
case '/': // Search mode
|
|
2536
2554
|
applyUpdates(actions.enterSearchMode(actionState));
|
|
2555
|
+
telemetry.capture('search_used');
|
|
2537
2556
|
render();
|
|
2538
2557
|
break;
|
|
2539
2558
|
|
|
@@ -2548,11 +2567,13 @@ function setupKeyboardInput() {
|
|
|
2548
2567
|
break;
|
|
2549
2568
|
|
|
2550
2569
|
case 'u': // Undo last switch
|
|
2570
|
+
telemetry.capture('undo_branch_switch');
|
|
2551
2571
|
await undoLastSwitch();
|
|
2552
2572
|
await pollGitChanges();
|
|
2553
2573
|
break;
|
|
2554
2574
|
|
|
2555
2575
|
case 'p':
|
|
2576
|
+
telemetry.capture('pull_forced');
|
|
2556
2577
|
await pullCurrentBranch();
|
|
2557
2578
|
await pollGitChanges();
|
|
2558
2579
|
break;
|
|
@@ -2591,6 +2612,7 @@ function setupKeyboardInput() {
|
|
|
2591
2612
|
const branch = displayBranches.length > 0 && curSelIdx < displayBranches.length
|
|
2592
2613
|
? displayBranches[curSelIdx] : null;
|
|
2593
2614
|
if (branch) {
|
|
2615
|
+
telemetry.capture('branch_actions_opened');
|
|
2594
2616
|
// Phase 1: Open modal instantly with local/cached data
|
|
2595
2617
|
const localData = gatherLocalActionData(branch);
|
|
2596
2618
|
store.setState({ actionData: localData, actionMode: true, actionLoading: !localData.prLoaded });
|
|
@@ -2625,8 +2647,10 @@ function setupKeyboardInput() {
|
|
|
2625
2647
|
|
|
2626
2648
|
case 's': {
|
|
2627
2649
|
applyUpdates(actions.toggleSound(actionState));
|
|
2628
|
-
|
|
2629
|
-
|
|
2650
|
+
const soundNowEnabled = store.get('soundEnabled');
|
|
2651
|
+
addLog(`Sound notifications ${soundNowEnabled ? 'enabled' : 'disabled'}`, 'info');
|
|
2652
|
+
telemetry.capture('sound_toggled', { enabled: soundNowEnabled });
|
|
2653
|
+
if (soundNowEnabled) playSound();
|
|
2630
2654
|
render();
|
|
2631
2655
|
break;
|
|
2632
2656
|
}
|
|
@@ -2645,6 +2669,7 @@ function setupKeyboardInput() {
|
|
|
2645
2669
|
case 'c': { // Toggle casino mode
|
|
2646
2670
|
const newCasinoState = casino.toggle();
|
|
2647
2671
|
store.setState({ casinoModeEnabled: newCasinoState });
|
|
2672
|
+
telemetry.capture('casino_mode_toggled', { enabled: newCasinoState });
|
|
2648
2673
|
addLog(`Casino mode ${newCasinoState ? '🎰 ENABLED' : 'disabled'}`, newCasinoState ? 'success' : 'info');
|
|
2649
2674
|
if (newCasinoState) {
|
|
2650
2675
|
addLog(`Have you noticed this game has that 'variable rewards' thing going on? 🤔😉`, 'info');
|
|
@@ -2754,6 +2779,14 @@ async function shutdown() {
|
|
|
2754
2779
|
await Promise.race([serverClosePromise, timeoutPromise]);
|
|
2755
2780
|
}
|
|
2756
2781
|
|
|
2782
|
+
// Flush telemetry
|
|
2783
|
+
telemetry.capture('session_ended', {
|
|
2784
|
+
duration_seconds: sessionStartTime ? Math.round((Date.now() - sessionStartTime) / 1000) : 0,
|
|
2785
|
+
branch_switches: branchSwitchCount,
|
|
2786
|
+
branches_count: store.get('branches').length,
|
|
2787
|
+
});
|
|
2788
|
+
await telemetry.shutdown();
|
|
2789
|
+
|
|
2757
2790
|
console.log('\n✓ Git Watchtower stopped\n');
|
|
2758
2791
|
process.exit(0);
|
|
2759
2792
|
}
|
|
@@ -2761,6 +2794,7 @@ async function shutdown() {
|
|
|
2761
2794
|
process.on('SIGINT', shutdown);
|
|
2762
2795
|
process.on('SIGTERM', shutdown);
|
|
2763
2796
|
process.on('uncaughtException', (err) => {
|
|
2797
|
+
telemetry.captureError(err);
|
|
2764
2798
|
write(ansi.showCursor);
|
|
2765
2799
|
write(ansi.restoreScreen);
|
|
2766
2800
|
restoreTerminalTitle();
|
|
@@ -2787,6 +2821,19 @@ async function start() {
|
|
|
2787
2821
|
const config = await ensureConfig(cliArgs);
|
|
2788
2822
|
applyConfig(config);
|
|
2789
2823
|
|
|
2824
|
+
// Telemetry: opt-in prompt (first run only) and initialization
|
|
2825
|
+
await telemetry.promptIfNeeded(promptYesNo);
|
|
2826
|
+
telemetry.init({ version: PACKAGE_VERSION });
|
|
2827
|
+
sessionStartTime = Date.now();
|
|
2828
|
+
telemetry.capture('tool_launched', {
|
|
2829
|
+
version: PACKAGE_VERSION,
|
|
2830
|
+
node_version: process.version,
|
|
2831
|
+
os: process.platform,
|
|
2832
|
+
server_mode: SERVER_MODE,
|
|
2833
|
+
has_config: !!loadConfig(),
|
|
2834
|
+
casino_mode: config.casinoMode || false,
|
|
2835
|
+
});
|
|
2836
|
+
|
|
2790
2837
|
// Set up casino mode render callback for animations
|
|
2791
2838
|
casino.setRenderCallback(render);
|
|
2792
2839
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "git-watchtower",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Terminal-based Git branch monitor with activity sparklines and optional dev server with live reload",
|
|
5
5
|
"main": "bin/git-watchtower.js",
|
|
6
6
|
"bin": {
|
|
@@ -52,6 +52,8 @@
|
|
|
52
52
|
},
|
|
53
53
|
"files": [
|
|
54
54
|
"bin/git-watchtower.js",
|
|
55
|
+
"src/",
|
|
56
|
+
"sounds/",
|
|
55
57
|
"README.md",
|
|
56
58
|
"LICENSE"
|
|
57
59
|
],
|
|
@@ -85,5 +87,8 @@
|
|
|
85
87
|
}
|
|
86
88
|
]
|
|
87
89
|
]
|
|
90
|
+
},
|
|
91
|
+
"dependencies": {
|
|
92
|
+
"posthog-node": "^5.28.0"
|
|
88
93
|
}
|
|
89
94
|
}
|
package/sounds/README.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Casino Mode Sound Effects
|
|
2
|
+
|
|
3
|
+
Casino mode uses system sounds by default. For custom sounds, add `.wav` files to this directory:
|
|
4
|
+
|
|
5
|
+
## Sound Files
|
|
6
|
+
|
|
7
|
+
| File | Description |
|
|
8
|
+
|------|-------------|
|
|
9
|
+
| `win.wav` | Short victory sound (small/medium wins) |
|
|
10
|
+
| `jackpot.wav` | Exciting fanfare (big wins, jackpots) |
|
|
11
|
+
| `spin.wav` | Slot machine spinning sound |
|
|
12
|
+
| `loss.wav` | Sad trombone / failure sound |
|
|
13
|
+
|
|
14
|
+
## Free Sound Sources
|
|
15
|
+
|
|
16
|
+
- [Freesound.org](https://freesound.org/) - Search for "slot machine", "casino win", "coin"
|
|
17
|
+
- [Mixkit](https://mixkit.co/free-sound-effects/) - Game sounds category
|
|
18
|
+
- [Zapsplat](https://www.zapsplat.com/) - Royalty-free effects
|
|
19
|
+
|
|
20
|
+
## Recommended Search Terms
|
|
21
|
+
|
|
22
|
+
- "slot machine win"
|
|
23
|
+
- "casino jackpot"
|
|
24
|
+
- "coin drop"
|
|
25
|
+
- "cha-ching"
|
|
26
|
+
- "sad trombone"
|
|
27
|
+
- "wah wah"
|
|
28
|
+
- "game over"
|
|
29
|
+
|
|
30
|
+
## Format Notes
|
|
31
|
+
|
|
32
|
+
- WAV format recommended for best compatibility
|
|
33
|
+
- Keep files short (1-3 seconds)
|
|
34
|
+
- Mono or stereo, any sample rate works
|