git-slot-machine 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.
Files changed (87) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +313 -0
  3. package/dist/animation/slotMachine.d.ts +9 -0
  4. package/dist/animation/slotMachine.d.ts.map +1 -0
  5. package/dist/animation/slotMachine.js +139 -0
  6. package/dist/animation/slotMachine.js.map +1 -0
  7. package/dist/api.d.ts +40 -0
  8. package/dist/api.d.ts.map +1 -0
  9. package/dist/api.js +154 -0
  10. package/dist/api.js.map +1 -0
  11. package/dist/balance.d.ts +11 -0
  12. package/dist/balance.d.ts.map +1 -0
  13. package/dist/balance.js +131 -0
  14. package/dist/balance.js.map +1 -0
  15. package/dist/commands/auth.d.ts +4 -0
  16. package/dist/commands/auth.d.ts.map +1 -0
  17. package/dist/commands/auth.js +91 -0
  18. package/dist/commands/auth.js.map +1 -0
  19. package/dist/commands/balance.d.ts +2 -0
  20. package/dist/commands/balance.d.ts.map +1 -0
  21. package/dist/commands/balance.js +32 -0
  22. package/dist/commands/balance.js.map +1 -0
  23. package/dist/commands/config.d.ts +3 -0
  24. package/dist/commands/config.d.ts.map +1 -0
  25. package/dist/commands/config.js +60 -0
  26. package/dist/commands/config.js.map +1 -0
  27. package/dist/commands/init.d.ts +2 -0
  28. package/dist/commands/init.d.ts.map +1 -0
  29. package/dist/commands/init.js +148 -0
  30. package/dist/commands/init.js.map +1 -0
  31. package/dist/commands/play.d.ts +7 -0
  32. package/dist/commands/play.d.ts.map +1 -0
  33. package/dist/commands/play.js +137 -0
  34. package/dist/commands/play.js.map +1 -0
  35. package/dist/commands/spin.d.ts +6 -0
  36. package/dist/commands/spin.d.ts.map +1 -0
  37. package/dist/commands/spin.js +17 -0
  38. package/dist/commands/spin.js.map +1 -0
  39. package/dist/commands/sync.d.ts +2 -0
  40. package/dist/commands/sync.d.ts.map +1 -0
  41. package/dist/commands/sync.js +52 -0
  42. package/dist/commands/sync.js.map +1 -0
  43. package/dist/commands/test.d.ts +6 -0
  44. package/dist/commands/test.d.ts.map +1 -0
  45. package/dist/commands/test.js +17 -0
  46. package/dist/commands/test.js.map +1 -0
  47. package/dist/config.d.ts +27 -0
  48. package/dist/config.d.ts.map +1 -0
  49. package/dist/config.js +144 -0
  50. package/dist/config.js.map +1 -0
  51. package/dist/index.d.ts +3 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +100 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/patterns.d.ts +27 -0
  56. package/dist/patterns.d.ts.map +1 -0
  57. package/dist/patterns.js +266 -0
  58. package/dist/patterns.js.map +1 -0
  59. package/dist/templates/post-commit.d.ts +2 -0
  60. package/dist/templates/post-commit.d.ts.map +1 -0
  61. package/dist/templates/post-commit.js +19 -0
  62. package/dist/templates/post-commit.js.map +1 -0
  63. package/dist/utils/git.d.ts +4 -0
  64. package/dist/utils/git.d.ts.map +1 -0
  65. package/dist/utils/git.js +42 -0
  66. package/dist/utils/git.js.map +1 -0
  67. package/jest.config.js +12 -0
  68. package/package.json +50 -0
  69. package/src/animation/slotMachine.ts +159 -0
  70. package/src/api.ts +203 -0
  71. package/src/balance.ts +118 -0
  72. package/src/commands/auth.ts +92 -0
  73. package/src/commands/balance.ts +28 -0
  74. package/src/commands/config.ts +59 -0
  75. package/src/commands/init.ts +121 -0
  76. package/src/commands/play.ts +150 -0
  77. package/src/commands/spin.ts +17 -0
  78. package/src/commands/sync.ts +49 -0
  79. package/src/commands/test.ts +19 -0
  80. package/src/config.ts +154 -0
  81. package/src/index.ts +114 -0
  82. package/src/patterns.test.ts +44 -0
  83. package/src/patterns.ts +292 -0
  84. package/src/templates/post-commit.ts +15 -0
  85. package/src/utils/git.ts +38 -0
  86. package/test.txt +2 -0
  87. package/tsconfig.json +20 -0
@@ -0,0 +1,159 @@
1
+ import chalk from 'chalk';
2
+ import { PatternResult } from '../patterns';
3
+
4
+ const HEX_CHARS = '0123456789abcdef'.split('');
5
+ const ANIMATION_SPEED = 50; // ms per frame
6
+ const SPIN_DURATION = 2000; // total animation time
7
+ const FRAMES = SPIN_DURATION / ANIMATION_SPEED;
8
+
9
+ export interface SlotConfig {
10
+ finalHash: string;
11
+ small: boolean;
12
+ patternResult?: PatternResult;
13
+ }
14
+
15
+ function getRandomHexChar(): string {
16
+ return HEX_CHARS[Math.floor(Math.random() * HEX_CHARS.length)];
17
+ }
18
+
19
+ function clearLine(): void {
20
+ process.stdout.write('\r\x1b[K');
21
+ }
22
+
23
+ function drawSlotMachine(chars: string[], spinning: boolean, highlightIndices: number[] = [], flash: boolean = false): void {
24
+ // Casino color palette: red borders, white title
25
+ const borderColor = chalk.rgb(220, 20, 60); // Crimson red
26
+ const titleColor = chalk.white; // White
27
+
28
+ const borderWidth = 39;
29
+ const border = '═'.repeat(borderWidth);
30
+ const topBorder = borderColor('╔' + border + '╗');
31
+ const middleBorder = borderColor('╠' + border + '╣');
32
+ const bottomBorder = borderColor('╚' + border + '╝');
33
+
34
+ // Title line - emojis count as 2 visual chars each
35
+ const titleText = 'GIT SLOT MACHINE';
36
+ const titleVisualWidth = 2 + 2 + titleText.length + 2 + 2; // emoji + spaces + text + spaces + emoji
37
+ const titlePadding = Math.floor((borderWidth - titleVisualWidth) / 2);
38
+ const titleRightPad = borderWidth - titleVisualWidth - titlePadding;
39
+ const titleLine = borderColor('║') + ' '.repeat(titlePadding) + chalk.rgb(255, 255, 255)('🎰 ' + titleText + ' 🎰') + ' '.repeat(titleRightPad) + borderColor('║');
40
+
41
+ console.log(topBorder);
42
+ console.log(titleLine);
43
+ console.log(middleBorder);
44
+
45
+ // Display the 7 characters as slot reels
46
+ const display = chars.map((char, i) => {
47
+ if (spinning) {
48
+ // Spinning - white blur
49
+ return chalk.rgb(255, 255, 255).bold(char);
50
+ } else {
51
+ // Check if this character should be highlighted
52
+ const isHighlighted = highlightIndices.includes(i);
53
+
54
+ if (isHighlighted && flash) {
55
+ // Flash state - yellow inverse (yellow background, black text)
56
+ return chalk.bgRgb(255, 255, 0).rgb(0, 0, 0).bold(char);
57
+ } else if (isHighlighted) {
58
+ // Normal highlighted state - yellow inverse (yellow background, black text)
59
+ return chalk.bgRgb(255, 255, 0).rgb(0, 0, 0).bold(char);
60
+ } else {
61
+ // Not highlighted - white text
62
+ return chalk.rgb(255, 255, 255)(char);
63
+ }
64
+ }
65
+ }).join(' ');
66
+
67
+ // Calculate padding for character display (7 chars + 6 double-space separators = 19 visual chars)
68
+ const displayVisualWidth = 7 + 12; // 7 chars + 6*2 spaces
69
+ const displayPadding = Math.floor((borderWidth - displayVisualWidth) / 2);
70
+ const displayRightPad = borderWidth - displayVisualWidth - displayPadding;
71
+ const displayLine = borderColor('║') + ' '.repeat(displayPadding) + display + ' '.repeat(displayRightPad) + borderColor('║');
72
+
73
+ console.log(displayLine);
74
+ console.log(bottomBorder);
75
+ }
76
+
77
+ export async function animateSlotMachine(config: SlotConfig): Promise<void> {
78
+ const { finalHash, patternResult } = config;
79
+ const highlightIndices = patternResult?.highlightIndices || [];
80
+
81
+ // Clear screen
82
+ console.clear();
83
+
84
+ // Initialize with random characters
85
+ let currentChars = Array(7).fill(0).map(() => getRandomHexChar());
86
+
87
+ // Animate spinning
88
+ for (let frame = 0; frame < FRAMES; frame++) {
89
+ // Gradually slow down and settle on final hash
90
+ const progress = frame / FRAMES;
91
+
92
+ if (progress < 0.9) {
93
+ // Still spinning - show random
94
+ currentChars = currentChars.map(() => getRandomHexChar());
95
+ } else {
96
+ // Start settling - reveal characters one by one
97
+ const revealIndex = Math.floor((progress - 0.9) / 0.1 * 7);
98
+ for (let i = 0; i < revealIndex; i++) {
99
+ currentChars[i] = finalHash[i];
100
+ }
101
+ }
102
+
103
+ // Redraw
104
+ console.clear();
105
+ drawSlotMachine(currentChars, progress < 0.9);
106
+
107
+ // Wait for next frame
108
+ await new Promise(resolve => setTimeout(resolve, ANIMATION_SPEED));
109
+ }
110
+
111
+ // Final reveal with flashing
112
+ const finalChars = finalHash.split('');
113
+
114
+ // Flash 3 times if there are highlighted characters
115
+ if (highlightIndices.length > 0) {
116
+ for (let flashCount = 0; flashCount < 3; flashCount++) {
117
+ // Flash on
118
+ console.clear();
119
+ drawSlotMachine(finalChars, false, highlightIndices, true);
120
+ await new Promise(resolve => setTimeout(resolve, 200));
121
+
122
+ // Flash off
123
+ console.clear();
124
+ drawSlotMachine(finalChars, false, highlightIndices, false);
125
+ await new Promise(resolve => setTimeout(resolve, 200));
126
+ }
127
+ }
128
+
129
+ // Steady state
130
+ console.clear();
131
+ drawSlotMachine(finalChars, false, highlightIndices, false);
132
+ }
133
+
134
+ export async function animateSmallMode(config: SlotConfig): Promise<void> {
135
+ const { finalHash, patternResult } = config;
136
+ const highlightIndices = patternResult?.highlightIndices || [];
137
+
138
+ // Single line, rapid character flicker
139
+ process.stdout.write(chalk.cyan('🎰 '));
140
+
141
+ for (let frame = 0; frame < 20; frame++) {
142
+ const chars = Array(7).fill(0).map(() => getRandomHexChar()).join('');
143
+ clearLine();
144
+ process.stdout.write(chalk.cyan('🎰 ') + chalk.rgb(255, 255, 255)(chars));
145
+ await new Promise(resolve => setTimeout(resolve, 50));
146
+ }
147
+
148
+ // Final with highlighting - don't add newline, let the caller add result info
149
+ clearLine();
150
+ const display = finalHash.split('').map((char, i) => {
151
+ if (highlightIndices.includes(i)) {
152
+ return chalk.bgRgb(255, 255, 0).rgb(0, 0, 0).bold(char);
153
+ } else {
154
+ return chalk.rgb(255, 255, 255)(char);
155
+ }
156
+ }).join('');
157
+ process.stdout.write(chalk.cyan('🎰 ') + display);
158
+ // Don't write newline - let caller continue on same line
159
+ }
package/src/api.ts ADDED
@@ -0,0 +1,203 @@
1
+ import { getApiUrl, getApiToken, isSyncEnabled } from './config';
2
+
3
+ // Fallback domains to try in order (if primary fails)
4
+ const FALLBACK_DOMAINS = [
5
+ 'https://gitslotmachinecom-main-vilmm1.laravel.cloud',
6
+ ];
7
+
8
+ export interface PlayData {
9
+ commit_hash: string;
10
+ commit_full_hash: string;
11
+ pattern_type: string;
12
+ pattern_name: string;
13
+ payout: number;
14
+ wager: number;
15
+ balance_before: number;
16
+ balance_after: number;
17
+ repo_url: string;
18
+ github_username: string;
19
+ repo_owner: string;
20
+ repo_name: string;
21
+ }
22
+
23
+ export interface ApiResponse<T = any> {
24
+ success: boolean;
25
+ data?: T;
26
+ message?: string;
27
+ }
28
+
29
+ export interface BalanceResponse {
30
+ balance: number;
31
+ total_commits: number;
32
+ total_winnings: number;
33
+ biggest_win: number;
34
+ biggest_win_pattern?: string;
35
+ biggest_win_hash?: string;
36
+ }
37
+
38
+ // Helper to build headers with authentication
39
+ function getHeaders(): Record<string, string> {
40
+ const headers: Record<string, string> = {
41
+ 'Content-Type': 'application/json',
42
+ 'Accept': 'application/json',
43
+ };
44
+
45
+ const token = getApiToken();
46
+ if (token) {
47
+ headers['Authorization'] = `Bearer ${token}`;
48
+ }
49
+
50
+ return headers;
51
+ }
52
+
53
+ // Check if API sync is enabled and available
54
+ export function isApiAvailable(): boolean {
55
+ return isSyncEnabled() && getApiToken() !== null;
56
+ }
57
+
58
+ export interface PlayResponse {
59
+ balance: number;
60
+ payout: number;
61
+ pattern_name: string;
62
+ share_url?: string;
63
+ }
64
+
65
+ // Helper to try API call with fallback domains
66
+ async function fetchWithFallback(endpoint: string, options: RequestInit): Promise<Response | null> {
67
+ const configuredUrl = getApiUrl();
68
+
69
+ // Build list of URLs to try: configured URL first, then fallbacks (excluding duplicates)
70
+ const urlsToTry = [
71
+ configuredUrl,
72
+ ...FALLBACK_DOMAINS.filter(domain => !configuredUrl.startsWith(domain))
73
+ ];
74
+
75
+ for (const baseUrl of urlsToTry) {
76
+ try {
77
+ const url = `${baseUrl.replace(/\/api$/, '')}/api${endpoint}`;
78
+ const response = await fetch(url, options);
79
+
80
+ if (response.ok) {
81
+ return response;
82
+ }
83
+
84
+ // If not ok, try next domain
85
+ continue;
86
+ } catch (error) {
87
+ // Network error, try next domain
88
+ continue;
89
+ }
90
+ }
91
+
92
+ return null;
93
+ }
94
+
95
+ // Send play data to API
96
+ export async function sendPlayToAPI(data: PlayData): Promise<PlayResponse | null> {
97
+ if (!isApiAvailable()) {
98
+ return null;
99
+ }
100
+
101
+ try {
102
+ const response = await fetchWithFallback('/play', {
103
+ method: 'POST',
104
+ headers: getHeaders(),
105
+ body: JSON.stringify(data),
106
+ });
107
+
108
+ if (!response) {
109
+ return null;
110
+ }
111
+
112
+ const result = await response.json() as ApiResponse<PlayResponse>;
113
+ return result.data || null;
114
+ } catch (error) {
115
+ // Silently fail if offline or API unavailable
116
+ // The user still gets local feedback
117
+ return null;
118
+ }
119
+ }
120
+
121
+ // Get balance from API
122
+ export async function getBalance(): Promise<BalanceResponse | null> {
123
+ if (!isApiAvailable()) {
124
+ return null;
125
+ }
126
+
127
+ try {
128
+ const response = await fetchWithFallback('/balance', {
129
+ method: 'GET',
130
+ headers: getHeaders(),
131
+ });
132
+
133
+ if (!response) {
134
+ return null;
135
+ }
136
+
137
+ const result = await response.json() as ApiResponse<BalanceResponse>;
138
+ return result.data || null;
139
+ } catch (error) {
140
+ return null;
141
+ }
142
+ }
143
+
144
+ // Create API token (simplified - just pass github username)
145
+ export async function createToken(githubUsername: string): Promise<string | null> {
146
+ try {
147
+ const response = await fetchWithFallback('/auth/token', {
148
+ method: 'POST',
149
+ headers: {
150
+ 'Content-Type': 'application/json',
151
+ 'Accept': 'application/json',
152
+ },
153
+ body: JSON.stringify({
154
+ github_username: githubUsername,
155
+ }),
156
+ });
157
+
158
+ if (!response) {
159
+ return null;
160
+ }
161
+
162
+ const result = await response.json() as ApiResponse<{ token: string }>;
163
+ return result.data?.token || null;
164
+ } catch (error) {
165
+ return null;
166
+ }
167
+ }
168
+
169
+ // Verify token is valid
170
+ export async function verifyToken(token: string): Promise<boolean> {
171
+ try {
172
+ const response = await fetchWithFallback('/auth/user', {
173
+ method: 'GET',
174
+ headers: {
175
+ 'Content-Type': 'application/json',
176
+ 'Accept': 'application/json',
177
+ 'Authorization': `Bearer ${token}`,
178
+ },
179
+ });
180
+
181
+ return response !== null && response.ok;
182
+ } catch (error) {
183
+ return false;
184
+ }
185
+ }
186
+
187
+ // Logout (revoke token)
188
+ export async function logout(): Promise<boolean> {
189
+ if (!getApiToken()) {
190
+ return true;
191
+ }
192
+
193
+ try {
194
+ const response = await fetchWithFallback('/auth/token', {
195
+ method: 'DELETE',
196
+ headers: getHeaders(),
197
+ });
198
+
199
+ return response !== null && response.ok;
200
+ } catch (error) {
201
+ return false;
202
+ }
203
+ }
package/src/balance.ts ADDED
@@ -0,0 +1,118 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import * as os from 'os';
4
+
5
+ interface BalanceData {
6
+ repos: Record<string, {
7
+ balance: number;
8
+ totalCommits: number;
9
+ totalWinnings: number;
10
+ biggestWin: number;
11
+ lastCommit: string;
12
+ }>;
13
+ }
14
+
15
+ const BALANCE_FILE = path.join(os.homedir(), '.git-slot-machine-balance.json');
16
+
17
+ function getRepoPath(): string | null {
18
+ try {
19
+ const { execSync } = require('child_process');
20
+ return execSync('git rev-parse --show-toplevel', {
21
+ encoding: 'utf-8',
22
+ stdio: ['pipe', 'pipe', 'pipe']
23
+ }).trim();
24
+ } catch {
25
+ // Not in a git repo - use global balance instead
26
+ return null;
27
+ }
28
+ }
29
+
30
+ function loadBalance(): BalanceData {
31
+ if (!fs.existsSync(BALANCE_FILE)) {
32
+ return { repos: {} };
33
+ }
34
+
35
+ const data = fs.readFileSync(BALANCE_FILE, 'utf-8');
36
+ return JSON.parse(data);
37
+ }
38
+
39
+ function saveBalance(data: BalanceData): void {
40
+ fs.writeFileSync(BALANCE_FILE, JSON.stringify(data, null, 2));
41
+ }
42
+
43
+ export function getBalance(): number {
44
+ const repoPath = getRepoPath() || 'global';
45
+ const data = loadBalance();
46
+
47
+ if (!data.repos[repoPath]) {
48
+ // Initialize new repo
49
+ data.repos[repoPath] = {
50
+ balance: 100,
51
+ totalCommits: 0,
52
+ totalWinnings: 0,
53
+ biggestWin: 0,
54
+ lastCommit: ''
55
+ };
56
+ saveBalance(data);
57
+ }
58
+
59
+ return data.repos[repoPath].balance;
60
+ }
61
+
62
+ export function updateBalance(hash: string, payout: number): number {
63
+ const repoPath = getRepoPath() || 'global';
64
+ const data = loadBalance();
65
+
66
+ if (!data.repos[repoPath]) {
67
+ data.repos[repoPath] = {
68
+ balance: 100,
69
+ totalCommits: 0,
70
+ totalWinnings: 0,
71
+ biggestWin: 0,
72
+ lastCommit: ''
73
+ };
74
+ }
75
+
76
+ const repo = data.repos[repoPath];
77
+
78
+ // Deduct cost
79
+ repo.balance -= 10;
80
+ repo.totalCommits += 1;
81
+
82
+ // Add winnings
83
+ if (payout > 0) {
84
+ repo.balance += payout;
85
+ repo.totalWinnings += payout;
86
+ repo.biggestWin = Math.max(repo.biggestWin, payout);
87
+ }
88
+
89
+ repo.lastCommit = hash;
90
+
91
+ saveBalance(data);
92
+ return repo.balance;
93
+ }
94
+
95
+ export function setBalance(newBalance: number): void {
96
+ const repoPath = getRepoPath() || 'global';
97
+ const data = loadBalance();
98
+
99
+ if (!data.repos[repoPath]) {
100
+ data.repos[repoPath] = {
101
+ balance: newBalance,
102
+ totalCommits: 0,
103
+ totalWinnings: 0,
104
+ biggestWin: 0,
105
+ lastCommit: ''
106
+ };
107
+ } else {
108
+ data.repos[repoPath].balance = newBalance;
109
+ }
110
+
111
+ saveBalance(data);
112
+ }
113
+
114
+ export function getRepoStats() {
115
+ const repoPath = getRepoPath() || 'global';
116
+ const data = loadBalance();
117
+ return data.repos[repoPath] || null;
118
+ }
@@ -0,0 +1,92 @@
1
+ import chalk from 'chalk';
2
+ import { createToken, logout as apiLogout, verifyToken } from '../api';
3
+ import { setApiToken, clearApiToken, getApiToken, getApiUrl, setGitHubUsername } from '../config';
4
+
5
+ export async function authLoginCommand(githubUsername: string): Promise<void> {
6
+ try {
7
+ console.log(chalk.dim(`Generating token for ${githubUsername}...`));
8
+
9
+ // Generate token from GitHub username
10
+ const token = await createToken(githubUsername);
11
+
12
+ if (!token) {
13
+ console.error(chalk.red('Failed to generate token. Please check your GitHub username.'));
14
+ process.exit(1);
15
+ }
16
+
17
+ // Save token and username
18
+ setApiToken(token);
19
+ setGitHubUsername(githubUsername);
20
+
21
+ console.log(chalk.green('Successfully authenticated!'));
22
+ console.log(chalk.dim(`Token saved. API URL: ${getApiUrl()}`));
23
+ console.log(chalk.dim(`GitHub Username: ${githubUsername}`));
24
+ console.log();
25
+ console.log(chalk.yellow('Data sent to server on each commit:'));
26
+ console.log(chalk.dim(' • Commit hash (7 and 40 character versions)'));
27
+ console.log(chalk.dim(' • Repository URL, owner, and name'));
28
+ console.log(chalk.dim(' • GitHub username'));
29
+ console.log(chalk.dim(' • Pattern type, payout, and balance'));
30
+ console.log();
31
+ console.log(chalk.dim('To disable sync: git-slot-machine config set sync-enabled false'));
32
+ } catch (error) {
33
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
34
+ process.exit(1);
35
+ }
36
+ }
37
+
38
+ export async function authLogoutCommand(): Promise<void> {
39
+ try {
40
+ const token = getApiToken();
41
+
42
+ if (!token) {
43
+ console.log(chalk.yellow('Not currently authenticated.'));
44
+ return;
45
+ }
46
+
47
+ // Try to revoke token on server
48
+ await apiLogout();
49
+
50
+ // Clear local token
51
+ clearApiToken();
52
+
53
+ console.log(chalk.green('Successfully logged out.'));
54
+ } catch (error) {
55
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
56
+ process.exit(1);
57
+ }
58
+ }
59
+
60
+ export async function authStatusCommand(): Promise<void> {
61
+ try {
62
+ const token = getApiToken();
63
+ const apiUrl = getApiUrl();
64
+
65
+ if (!token) {
66
+ console.log(chalk.yellow('Not authenticated.'));
67
+ console.log(chalk.dim(`API URL: ${apiUrl}`));
68
+ console.log();
69
+ console.log('To authenticate, run:');
70
+ console.log(chalk.cyan(' git-slot-machine auth login <your-github-username>'));
71
+ return;
72
+ }
73
+
74
+ // Verify token is still valid
75
+ const isValid = await verifyToken(token);
76
+
77
+ if (isValid) {
78
+ console.log(chalk.green('Authenticated'));
79
+ console.log(chalk.dim(`API URL: ${apiUrl}`));
80
+ console.log(chalk.dim(`Token: ${token.substring(0, 10)}...`));
81
+ } else {
82
+ console.log(chalk.red('Authentication expired or invalid.'));
83
+ console.log(chalk.dim(`API URL: ${apiUrl}`));
84
+ console.log();
85
+ console.log('Please login again:');
86
+ console.log(chalk.cyan(' git-slot-machine auth login <your-github-username>'));
87
+ }
88
+ } catch (error) {
89
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
90
+ process.exit(1);
91
+ }
92
+ }
@@ -0,0 +1,28 @@
1
+ import chalk from 'chalk';
2
+ import { getRepoStats } from '../balance';
3
+
4
+ export function balanceCommand(): void {
5
+ try {
6
+ const stats = getRepoStats();
7
+
8
+ if (!stats) {
9
+ console.log(chalk.yellow('No balance data for this repository'));
10
+ console.log(chalk.dim('Run a commit or use "git-slot-machine spin" to start playing'));
11
+ return;
12
+ }
13
+
14
+ console.log();
15
+ console.log(chalk.cyan.bold('Repository Stats'));
16
+ console.log(chalk.dim('━'.repeat(40)));
17
+ console.log(chalk.white(`Balance: ${stats.balance >= 0 ? chalk.green(stats.balance) : chalk.red(stats.balance)} points`));
18
+ console.log(chalk.white(`Total Commits: ${stats.totalCommits}`));
19
+ console.log(chalk.white(`Total Winnings: ${chalk.yellow(stats.totalWinnings)} points`));
20
+ console.log(chalk.white(`Biggest Win: ${chalk.yellow(stats.biggestWin)} points`));
21
+ console.log(chalk.white(`Last Commit: ${chalk.dim(stats.lastCommit)}`));
22
+ console.log();
23
+
24
+ } catch (error) {
25
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
26
+ process.exit(1);
27
+ }
28
+ }
@@ -0,0 +1,59 @@
1
+ import chalk from 'chalk';
2
+ import {
3
+ getApiUrl,
4
+ setApiUrl,
5
+ isSyncEnabled,
6
+ setSyncEnabled,
7
+ getConfig
8
+ } from '../config';
9
+
10
+ export async function configGetCommand(key: string): Promise<void> {
11
+ try {
12
+ const config = getConfig();
13
+
14
+ switch (key) {
15
+ case 'api-url':
16
+ console.log(getApiUrl());
17
+ break;
18
+ case 'sync-enabled':
19
+ console.log(isSyncEnabled());
20
+ break;
21
+ case 'all':
22
+ console.log(chalk.bold('Configuration:'));
23
+ console.log(` API URL: ${chalk.cyan(getApiUrl())}`);
24
+ console.log(` Sync Enabled: ${chalk.cyan(isSyncEnabled())}`);
25
+ console.log(` Has Token: ${chalk.cyan(config.apiToken ? 'yes' : 'no')}`);
26
+ break;
27
+ default:
28
+ console.error(chalk.red(`Unknown config key: ${key}`));
29
+ console.log('Available keys: api-url, sync-enabled, all');
30
+ process.exit(1);
31
+ }
32
+ } catch (error) {
33
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
34
+ process.exit(1);
35
+ }
36
+ }
37
+
38
+ export async function configSetCommand(key: string, value: string): Promise<void> {
39
+ try {
40
+ switch (key) {
41
+ case 'api-url':
42
+ setApiUrl(value);
43
+ console.log(chalk.green(`API URL set to: ${value}`));
44
+ break;
45
+ case 'sync-enabled':
46
+ const enabled = value.toLowerCase() === 'true' || value === '1';
47
+ setSyncEnabled(enabled);
48
+ console.log(chalk.green(`Sync ${enabled ? 'enabled' : 'disabled'}`));
49
+ break;
50
+ default:
51
+ console.error(chalk.red(`Unknown config key: ${key}`));
52
+ console.log('Available keys: api-url, sync-enabled');
53
+ process.exit(1);
54
+ }
55
+ } catch (error) {
56
+ console.error(chalk.red(`Error: ${(error as Error).message}`));
57
+ process.exit(1);
58
+ }
59
+ }