git-slot-machine 2.3.1 → 2.4.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/dist/animation/slotMachine.js +47 -44
- package/dist/index.js +4 -1
- package/dist/templates/post-commit.d.ts +1 -1
- package/dist/templates/post-commit.js +3 -11
- package/package.json +6 -1
- package/.claude/release.md +0 -74
- package/CHANGELOG.md +0 -241
- package/dist/animation/slotMachine.d.ts.map +0 -1
- package/dist/animation/slotMachine.js.map +0 -1
- package/dist/api.d.ts.map +0 -1
- package/dist/api.js.map +0 -1
- package/dist/balance.d.ts.map +0 -1
- package/dist/balance.js.map +0 -1
- package/dist/commands/auth.d.ts.map +0 -1
- package/dist/commands/auth.js.map +0 -1
- package/dist/commands/balance.d.ts.map +0 -1
- package/dist/commands/balance.js.map +0 -1
- package/dist/commands/config.d.ts.map +0 -1
- package/dist/commands/config.js.map +0 -1
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/play.d.ts.map +0 -1
- package/dist/commands/play.js.map +0 -1
- package/dist/commands/spin.d.ts.map +0 -1
- package/dist/commands/spin.js.map +0 -1
- package/dist/commands/sync.d.ts.map +0 -1
- package/dist/commands/sync.js.map +0 -1
- package/dist/commands/test.d.ts.map +0 -1
- package/dist/commands/test.js.map +0 -1
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/patterns.d.ts.map +0 -1
- package/dist/patterns.js.map +0 -1
- package/dist/secrets.d.ts.map +0 -1
- package/dist/secrets.js.map +0 -1
- package/dist/templates/post-commit.d.ts.map +0 -1
- package/dist/templates/post-commit.js.map +0 -1
- package/dist/utils/amendDetector.d.ts.map +0 -1
- package/dist/utils/amendDetector.js.map +0 -1
- package/dist/utils/fetch-polyfill.d.ts.map +0 -1
- package/dist/utils/fetch-polyfill.js.map +0 -1
- package/dist/utils/git.d.ts.map +0 -1
- package/dist/utils/git.js.map +0 -1
- package/jest.config.js +0 -12
- package/src/animation/slotMachine.ts +0 -159
- package/src/api.ts +0 -207
- package/src/balance.ts +0 -118
- package/src/commands/auth.ts +0 -92
- package/src/commands/balance.ts +0 -28
- package/src/commands/config.ts +0 -59
- package/src/commands/init.ts +0 -259
- package/src/commands/play.ts +0 -196
- package/src/commands/spin.ts +0 -17
- package/src/commands/sync.ts +0 -49
- package/src/commands/test.ts +0 -19
- package/src/config.ts +0 -189
- package/src/index.ts +0 -132
- package/src/patterns.test.ts +0 -44
- package/src/patterns.ts +0 -313
- package/src/secrets.ts +0 -44
- package/src/templates/post-commit.ts +0 -23
- package/src/utils/amendDetector.ts +0 -74
- package/src/utils/fetch-polyfill.ts +0 -13
- package/src/utils/git.ts +0 -88
- package/test.txt +0 -2
- package/tsconfig.json +0 -21
package/src/commands/auth.ts
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import { createToken, logout as apiLogout, verifyToken } from '../api.js';
|
|
3
|
-
import { setApiToken, clearApiToken, getApiToken, getApiUrl, setGitHubUsername } from '../config.js';
|
|
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
|
-
}
|
package/src/commands/balance.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import { getRepoStats } from '../balance.js';
|
|
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
|
-
}
|
package/src/commands/config.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import {
|
|
3
|
-
getApiUrl,
|
|
4
|
-
setApiUrl,
|
|
5
|
-
isSyncEnabled,
|
|
6
|
-
setSyncEnabled,
|
|
7
|
-
getConfig
|
|
8
|
-
} from '../config.js';
|
|
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
|
-
}
|
package/src/commands/init.ts
DELETED
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
import * as readline from 'readline';
|
|
4
|
-
import chalk from 'chalk';
|
|
5
|
-
import { isGitRepo, detectGitHubUsername } from '../utils/git.js';
|
|
6
|
-
import { POST_COMMIT_HOOK } from '../templates/post-commit.js';
|
|
7
|
-
import { getRepoInfo, setGitHubUsername, getGitHubUsername, setPrivateRepo, setPlayAsUsername } from '../config.js';
|
|
8
|
-
import { authLoginCommand } from './auth.js';
|
|
9
|
-
|
|
10
|
-
async function isRepoPublic(owner: string, repo: string): Promise<boolean | null> {
|
|
11
|
-
try {
|
|
12
|
-
const response = await fetch(`https://api.github.com/repos/${owner}/${repo}`, {
|
|
13
|
-
headers: {
|
|
14
|
-
'Accept': 'application/vnd.github+json',
|
|
15
|
-
'X-GitHub-Api-Version': '2022-11-28',
|
|
16
|
-
},
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
if (response.ok) {
|
|
20
|
-
const data = await response.json() as { private: boolean };
|
|
21
|
-
return data.private === false;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// 404 could mean private or doesn't exist
|
|
25
|
-
return null;
|
|
26
|
-
} catch (error) {
|
|
27
|
-
// Network error or API unavailable
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function askQuestion(question: string): Promise<string> {
|
|
33
|
-
const rl = readline.createInterface({
|
|
34
|
-
input: process.stdin,
|
|
35
|
-
output: process.stdout
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
return new Promise((resolve) => {
|
|
39
|
-
rl.question(question, (answer) => {
|
|
40
|
-
rl.close();
|
|
41
|
-
resolve(answer.trim().toLowerCase());
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export async function initCommand(): Promise<void> {
|
|
47
|
-
// Check if git repo
|
|
48
|
-
if (!isGitRepo()) {
|
|
49
|
-
console.error(chalk.red('Error: Not a git repository'));
|
|
50
|
-
console.log(chalk.dim('Run this command from the root of a git repository'));
|
|
51
|
-
process.exit(1);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Check for GitHub remote
|
|
55
|
-
const repoInfo = getRepoInfo();
|
|
56
|
-
|
|
57
|
-
if (!repoInfo) {
|
|
58
|
-
console.log();
|
|
59
|
-
console.error(chalk.red('Error: No GitHub remote detected'));
|
|
60
|
-
console.log();
|
|
61
|
-
console.log(chalk.yellow('Git Slot Machine requires a GitHub repository.'));
|
|
62
|
-
console.log();
|
|
63
|
-
console.log(chalk.dim('Add a GitHub remote to this repo:'));
|
|
64
|
-
console.log(chalk.cyan(' git remote add origin https://github.com/username/repo.git'));
|
|
65
|
-
console.log();
|
|
66
|
-
process.exit(1);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Detect GitHub username (not repo owner)
|
|
70
|
-
let githubUsername = getGitHubUsername();
|
|
71
|
-
|
|
72
|
-
if (!githubUsername) {
|
|
73
|
-
// Try to detect from git config
|
|
74
|
-
githubUsername = detectGitHubUsername();
|
|
75
|
-
|
|
76
|
-
if (githubUsername) {
|
|
77
|
-
console.log(chalk.dim(`Detected GitHub username: ${githubUsername}`));
|
|
78
|
-
} else {
|
|
79
|
-
// Couldn't detect, prompt user
|
|
80
|
-
console.log();
|
|
81
|
-
console.log(chalk.yellow('GitHub username not detected'));
|
|
82
|
-
console.log(chalk.dim('We need your GitHub username (not the repo owner) for the leaderboard'));
|
|
83
|
-
githubUsername = await askQuestion(chalk.cyan('Enter your GitHub username: '));
|
|
84
|
-
|
|
85
|
-
if (!githubUsername) {
|
|
86
|
-
console.log(chalk.red('GitHub username is required'));
|
|
87
|
-
process.exit(1);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
setGitHubUsername(githubUsername);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Check if repository is public
|
|
95
|
-
console.log(chalk.dim('Checking repository visibility...'));
|
|
96
|
-
const isPublic = await isRepoPublic(repoInfo.owner, repoInfo.name);
|
|
97
|
-
|
|
98
|
-
let usePrivacyMode = false;
|
|
99
|
-
|
|
100
|
-
if (isPublic === false) {
|
|
101
|
-
console.log(chalk.yellow('⚠️ Private repository detected'));
|
|
102
|
-
console.log();
|
|
103
|
-
console.log(chalk.cyan('Privacy Mode Available'));
|
|
104
|
-
console.log(chalk.dim('You can still use Git Slot Machine with privacy mode enabled.'));
|
|
105
|
-
console.log();
|
|
106
|
-
console.log(chalk.yellow('What happens in privacy mode:'));
|
|
107
|
-
console.log(chalk.dim(' • Repository name/org are NOT stored on the server'));
|
|
108
|
-
console.log(chalk.dim(' • Sent as "private/private" to the API'));
|
|
109
|
-
console.log(chalk.dim(' • Displayed as "*******/*******" on leaderboard'));
|
|
110
|
-
console.log(chalk.dim(' • Your GitHub username is still public'));
|
|
111
|
-
console.log(chalk.dim(' • All private repos share one balance'));
|
|
112
|
-
console.log();
|
|
113
|
-
|
|
114
|
-
const answer = await askQuestion(chalk.green('Enable privacy mode? (y/n): '));
|
|
115
|
-
|
|
116
|
-
if (answer === 'y' || answer === 'yes') {
|
|
117
|
-
usePrivacyMode = true;
|
|
118
|
-
setPrivateRepo(true);
|
|
119
|
-
console.log(chalk.green('✓ Privacy mode enabled'));
|
|
120
|
-
console.log(chalk.dim('Repository details will never be sent to the server.'));
|
|
121
|
-
} else {
|
|
122
|
-
console.log();
|
|
123
|
-
console.log(chalk.red('Cannot proceed without privacy mode for private repos.'));
|
|
124
|
-
console.log(chalk.dim('Please make the repository public or enable privacy mode.'));
|
|
125
|
-
process.exit(1);
|
|
126
|
-
}
|
|
127
|
-
} else if (isPublic === null) {
|
|
128
|
-
console.log(chalk.yellow('⚠️ Could not verify repository visibility'));
|
|
129
|
-
console.log();
|
|
130
|
-
|
|
131
|
-
const answer = await askQuestion(chalk.green('Is this a private repository? (y/n): '));
|
|
132
|
-
|
|
133
|
-
if (answer === 'y' || answer === 'yes') {
|
|
134
|
-
usePrivacyMode = true;
|
|
135
|
-
setPrivateRepo(true);
|
|
136
|
-
console.log(chalk.green('✓ Privacy mode enabled'));
|
|
137
|
-
console.log(chalk.dim('Repository details will never be sent to the server.'));
|
|
138
|
-
} else {
|
|
139
|
-
console.log(chalk.dim('Proceeding with public repository mode...'));
|
|
140
|
-
}
|
|
141
|
-
} else {
|
|
142
|
-
console.log(chalk.green('✓ Public repository confirmed'));
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const hookPath = path.join(process.cwd(), '.git', 'hooks', 'post-commit');
|
|
146
|
-
|
|
147
|
-
// Check if hook already exists
|
|
148
|
-
if (fs.existsSync(hookPath)) {
|
|
149
|
-
console.log(chalk.yellow('⚠️ Post-commit hook already exists'));
|
|
150
|
-
console.log(chalk.dim(`Location: ${hookPath}`));
|
|
151
|
-
console.log();
|
|
152
|
-
console.log(chalk.yellow('Skipping hook installation to avoid overwriting.'));
|
|
153
|
-
console.log(chalk.dim('To use Git Slot Machine, manually add this to your existing hook:'));
|
|
154
|
-
console.log(chalk.cyan(' git-slot-machine play'));
|
|
155
|
-
console.log();
|
|
156
|
-
} else {
|
|
157
|
-
// Write the hook
|
|
158
|
-
fs.writeFileSync(hookPath, POST_COMMIT_HOOK, { mode: 0o755 });
|
|
159
|
-
console.log(chalk.green('✓ Post-commit hook installed'));
|
|
160
|
-
console.log();
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Ask if they want to join the leaderboard
|
|
164
|
-
const joinLeaderboard = await askQuestion(chalk.cyan('Join the global leaderboard? (Y/n): '));
|
|
165
|
-
console.log();
|
|
166
|
-
|
|
167
|
-
if (joinLeaderboard === 'n' || joinLeaderboard === 'no') {
|
|
168
|
-
console.log(chalk.green('✓ Git Slot Machine is ready (local mode only)'));
|
|
169
|
-
console.log(chalk.dim('Every commit will spin the slot machine locally.'));
|
|
170
|
-
console.log();
|
|
171
|
-
console.log(chalk.dim('To join the leaderboard later:'));
|
|
172
|
-
console.log(chalk.cyan(' git-slot-machine login your-username'));
|
|
173
|
-
console.log();
|
|
174
|
-
} else {
|
|
175
|
-
// Confirm detected username
|
|
176
|
-
console.log(chalk.dim(`Detected GitHub username: ${githubUsername}`));
|
|
177
|
-
const isCorrect = await askQuestion(chalk.cyan('Is this correct? (Y/n): '));
|
|
178
|
-
console.log();
|
|
179
|
-
|
|
180
|
-
if (isCorrect === 'n' || isCorrect === 'no') {
|
|
181
|
-
githubUsername = await askQuestion(chalk.cyan('Enter your GitHub username: '));
|
|
182
|
-
if (!githubUsername) {
|
|
183
|
-
console.log(chalk.yellow('Skipping authentication - you can join later'));
|
|
184
|
-
console.log(chalk.cyan(' git-slot-machine login your-username'));
|
|
185
|
-
console.log();
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
setGitHubUsername(githubUsername);
|
|
189
|
-
console.log();
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Ask if they want to play as org or personal username
|
|
193
|
-
const repoOwner = repoInfo.owner;
|
|
194
|
-
|
|
195
|
-
// Only ask if repo owner is different from personal username and not in privacy mode
|
|
196
|
-
if (!usePrivacyMode && repoOwner.toLowerCase() !== githubUsername.toLowerCase()) {
|
|
197
|
-
console.log(chalk.cyan('Who should get credit for commits in this repo?'));
|
|
198
|
-
console.log();
|
|
199
|
-
console.log(chalk.dim(` 1) ${githubUsername} (your personal account)`));
|
|
200
|
-
console.log(chalk.dim(` 2) ${repoOwner} (this repo's organization)`));
|
|
201
|
-
console.log();
|
|
202
|
-
|
|
203
|
-
const choice = await askQuestion(chalk.cyan('Choose (1 or 2): '));
|
|
204
|
-
console.log();
|
|
205
|
-
|
|
206
|
-
if (choice === '2') {
|
|
207
|
-
// Play as org
|
|
208
|
-
setPlayAsUsername(repoOwner);
|
|
209
|
-
console.log(chalk.green(`✓ Commits in this repo will be credited to ${repoOwner}`));
|
|
210
|
-
console.log();
|
|
211
|
-
|
|
212
|
-
// Update githubUsername for authentication
|
|
213
|
-
githubUsername = repoOwner;
|
|
214
|
-
} else {
|
|
215
|
-
// Play as personal username (default)
|
|
216
|
-
console.log(chalk.green(`✓ Commits in this repo will be credited to ${githubUsername}`));
|
|
217
|
-
console.log();
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// Authenticate
|
|
222
|
-
console.log(chalk.dim('Authenticating...'));
|
|
223
|
-
try {
|
|
224
|
-
await authLoginCommand(githubUsername);
|
|
225
|
-
console.log(chalk.green('✓ You\'re on the leaderboard!'));
|
|
226
|
-
console.log(chalk.dim('View it at: https://gitslotmachine.com'));
|
|
227
|
-
console.log();
|
|
228
|
-
} catch (error) {
|
|
229
|
-
console.log(chalk.yellow('⚠️ Authentication failed'));
|
|
230
|
-
console.log(chalk.dim('Your commits will work locally, but won\'t appear on the leaderboard'));
|
|
231
|
-
console.log(chalk.dim(`Try again: git-slot-machine login ${githubUsername}`));
|
|
232
|
-
console.log();
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
console.log(chalk.cyan('Try it out:'));
|
|
237
|
-
console.log(chalk.dim(' git commit --allow-empty -m "test"'));
|
|
238
|
-
console.log();
|
|
239
|
-
console.log(chalk.yellow('What gets sent to the server:'));
|
|
240
|
-
|
|
241
|
-
if (usePrivacyMode) {
|
|
242
|
-
// Privacy mode - show what is NOT sent
|
|
243
|
-
console.log(chalk.green(' ✓ Commit hash (7 and 40 character versions)'));
|
|
244
|
-
console.log(chalk.red(' ✗ Repository URL, owner, and name'));
|
|
245
|
-
console.log(chalk.dim(' (Sent as "private/private" instead)'));
|
|
246
|
-
console.log(chalk.green(' ✓ GitHub username'));
|
|
247
|
-
console.log(chalk.green(' ✓ Pattern type, payout, and balance'));
|
|
248
|
-
} else {
|
|
249
|
-
// Public mode - everything is sent
|
|
250
|
-
console.log(chalk.green(' ✓ Commit hash (7 and 40 character versions)'));
|
|
251
|
-
console.log(chalk.green(' ✓ Repository URL, owner, and name'));
|
|
252
|
-
console.log(chalk.green(' ✓ GitHub username'));
|
|
253
|
-
console.log(chalk.green(' ✓ Pattern type, payout, and balance'));
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
console.log();
|
|
257
|
-
console.log(chalk.dim('You can disable API sync anytime:'));
|
|
258
|
-
console.log(chalk.dim(' git-slot-machine sync:disable'));
|
|
259
|
-
}
|
package/src/commands/play.ts
DELETED
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
import { detectPattern, PatternType } from '../patterns.js';
|
|
2
|
-
import { animateSlotMachine, animateSmallMode } from '../animation/slotMachine.js';
|
|
3
|
-
import { getBalance, updateBalance, setBalance } from '../balance.js';
|
|
4
|
-
import { sendPlayToAPI } from '../api.js';
|
|
5
|
-
import { getRepoInfo, getGitHubUsername } from '../config.js';
|
|
6
|
-
import { detectAmendGrinding, getAmendWarningMessage } from '../utils/amendDetector.js';
|
|
7
|
-
import { checkSecret } from '../secrets.js';
|
|
8
|
-
import chalk from 'chalk';
|
|
9
|
-
|
|
10
|
-
function floodTerminal(emoji: string): void {
|
|
11
|
-
const cols = process.stdout.columns || 80;
|
|
12
|
-
const rows = 5;
|
|
13
|
-
const line = emoji.repeat(Math.floor(cols / 2));
|
|
14
|
-
for (let i = 0; i < rows; i++) {
|
|
15
|
-
console.log(line);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
interface PlayOptions {
|
|
20
|
-
small?: boolean;
|
|
21
|
-
fullHash?: string; // Optional full hash for CLI integration
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export async function playCommand(hash: string, options: PlayOptions): Promise<void> {
|
|
25
|
-
try {
|
|
26
|
-
// Get balance before playing
|
|
27
|
-
const balanceBefore = getBalance();
|
|
28
|
-
|
|
29
|
-
// Check for secret combos first
|
|
30
|
-
const secret = checkSecret(hash);
|
|
31
|
-
|
|
32
|
-
// If secret with emoji flood, do it before anything else
|
|
33
|
-
if (secret?.flood && secret.emoji) {
|
|
34
|
-
if (options.small) {
|
|
35
|
-
// Small mode - inline emoji burst
|
|
36
|
-
process.stdout.write(secret.emoji.repeat(10) + ' ');
|
|
37
|
-
} else {
|
|
38
|
-
console.clear();
|
|
39
|
-
floodTerminal(secret.emoji);
|
|
40
|
-
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Detect pattern (or use secret)
|
|
45
|
-
const result = secret
|
|
46
|
-
? {
|
|
47
|
-
type: PatternType.SECRET,
|
|
48
|
-
name: secret.name,
|
|
49
|
-
payout: secret.payout,
|
|
50
|
-
description: '???',
|
|
51
|
-
highlightIndices: [0, 1, 2, 3, 4, 5, 6],
|
|
52
|
-
}
|
|
53
|
-
: detectPattern(hash);
|
|
54
|
-
|
|
55
|
-
// Animate based on mode
|
|
56
|
-
const config = {
|
|
57
|
-
finalHash: hash.toLowerCase(),
|
|
58
|
-
small: options.small || false,
|
|
59
|
-
patternResult: result
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
if (options.small) {
|
|
63
|
-
await animateSmallMode(config);
|
|
64
|
-
} else {
|
|
65
|
-
await animateSlotMachine(config);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Show result
|
|
69
|
-
if (!options.small) {
|
|
70
|
-
console.log();
|
|
71
|
-
|
|
72
|
-
// Center the text below the box (box width is 41 chars)
|
|
73
|
-
const boxWidth = 41;
|
|
74
|
-
|
|
75
|
-
if (result.payout > 0) {
|
|
76
|
-
const resultText = `${result.name}!`;
|
|
77
|
-
const resultPadding = Math.floor((boxWidth - resultText.length) / 2);
|
|
78
|
-
console.log(' '.repeat(resultPadding) + chalk.cyan.bold(resultText));
|
|
79
|
-
|
|
80
|
-
const payoutText = `+${result.payout} points`;
|
|
81
|
-
const payoutPadding = Math.floor((boxWidth - payoutText.length) / 2);
|
|
82
|
-
console.log(' '.repeat(payoutPadding) + chalk.white.bold(payoutText));
|
|
83
|
-
} else {
|
|
84
|
-
const noWinText = 'No win';
|
|
85
|
-
const noWinPadding = Math.floor((boxWidth - noWinText.length) / 2);
|
|
86
|
-
console.log(' '.repeat(noWinPadding) + chalk.red.bold(noWinText));
|
|
87
|
-
|
|
88
|
-
const lossText = '-10 points';
|
|
89
|
-
const lossPadding = Math.floor((boxWidth - lossText.length) / 2);
|
|
90
|
-
console.log(' '.repeat(lossPadding) + chalk.white.bold(lossText));
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
console.log();
|
|
94
|
-
const descText = result.description;
|
|
95
|
-
const descPadding = Math.floor((boxWidth - descText.length) / 2);
|
|
96
|
-
console.log(' '.repeat(descPadding) + chalk.dim(descText));
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Validate GitHub remote for API sync
|
|
100
|
-
const repoInfo = getRepoInfo();
|
|
101
|
-
const githubUsername = getGitHubUsername();
|
|
102
|
-
|
|
103
|
-
if (!repoInfo && githubUsername) {
|
|
104
|
-
console.log();
|
|
105
|
-
console.log(chalk.yellow.bold('⚠ Warning: No GitHub remote detected'));
|
|
106
|
-
console.log(chalk.dim('This repo will not sync to the leaderboard.'));
|
|
107
|
-
console.log(chalk.dim('To sync, add a GitHub remote:'));
|
|
108
|
-
console.log(chalk.cyan(' git remote add origin https://github.com/username/repo.git'));
|
|
109
|
-
console.log();
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Update balance locally
|
|
113
|
-
let newBalance = updateBalance(hash.toLowerCase(), result.payout);
|
|
114
|
-
|
|
115
|
-
// Detect potential hash grinding
|
|
116
|
-
const amendDetection = detectAmendGrinding();
|
|
117
|
-
const warningMessage = getAmendWarningMessage(amendDetection);
|
|
118
|
-
|
|
119
|
-
// Send to API and sync balance with server
|
|
120
|
-
let shareUrl: string | undefined;
|
|
121
|
-
|
|
122
|
-
if (repoInfo && githubUsername) {
|
|
123
|
-
const playData: any = {
|
|
124
|
-
commit_hash: hash.toLowerCase(),
|
|
125
|
-
pattern_type: result.type,
|
|
126
|
-
pattern_name: result.name,
|
|
127
|
-
payout: result.payout,
|
|
128
|
-
wager: 10,
|
|
129
|
-
balance_before: balanceBefore,
|
|
130
|
-
balance_after: newBalance,
|
|
131
|
-
repo_url: repoInfo.url,
|
|
132
|
-
github_username: githubUsername,
|
|
133
|
-
repo_owner: repoInfo.owner,
|
|
134
|
-
repo_name: repoInfo.name,
|
|
135
|
-
suspicious: amendDetection.suspiciousActivity,
|
|
136
|
-
amend_count: amendDetection.recentAmendCount,
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
// Only send full hash if we have one (not in test mode)
|
|
140
|
-
if (options.fullHash && options.fullHash.length === 40) {
|
|
141
|
-
playData.commit_full_hash = options.fullHash;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
try {
|
|
145
|
-
const apiResponse = await sendPlayToAPI(playData);
|
|
146
|
-
// Sync local balance to match server's balance
|
|
147
|
-
if (apiResponse && apiResponse.balance !== undefined) {
|
|
148
|
-
setBalance(apiResponse.balance);
|
|
149
|
-
newBalance = apiResponse.balance;
|
|
150
|
-
shareUrl = apiResponse.share_url;
|
|
151
|
-
}
|
|
152
|
-
} catch (error) {
|
|
153
|
-
// Silently fail - local play already succeeded
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Show result and balance
|
|
158
|
-
if (options.small) {
|
|
159
|
-
// Small mode - everything on one line (animateSmallMode already wrote the hash without newline)
|
|
160
|
-
if (result.payout > 0) {
|
|
161
|
-
console.log(chalk.dim(' • ') + chalk.cyan.bold(`${result.name} +${result.payout}`) + chalk.dim(' • ') + chalk.white(`Balance: ${chalk.green.bold(newBalance)}`));
|
|
162
|
-
} else {
|
|
163
|
-
console.log(chalk.dim(' • ') + chalk.red('No win -10') + chalk.dim(' • ') + chalk.white(`Balance: ${newBalance >= 0 ? chalk.green.bold(newBalance) : chalk.red.bold(newBalance)}`));
|
|
164
|
-
}
|
|
165
|
-
} else {
|
|
166
|
-
console.log();
|
|
167
|
-
const boxWidth = 41;
|
|
168
|
-
// Note: we can't measure the exact length with color codes, so estimate based on text content
|
|
169
|
-
const balanceText = `Balance: ${newBalance} points`;
|
|
170
|
-
const balancePadding = Math.floor((boxWidth - balanceText.length) / 2);
|
|
171
|
-
console.log(' '.repeat(balancePadding) + chalk.white.bold(`Balance: ${newBalance >= 0 ? chalk.green.bold(newBalance) : chalk.red.bold(newBalance)} points`));
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Show share URL for wins
|
|
175
|
-
if (shareUrl && result.payout > 0 && !options.small) {
|
|
176
|
-
console.log();
|
|
177
|
-
const boxWidth = 41;
|
|
178
|
-
const shareText = 'Share your win:';
|
|
179
|
-
const sharePadding = Math.floor((boxWidth - shareText.length) / 2);
|
|
180
|
-
console.log(' '.repeat(sharePadding) + chalk.dim(shareText));
|
|
181
|
-
|
|
182
|
-
const urlPadding = Math.floor((boxWidth - shareUrl.length) / 2);
|
|
183
|
-
console.log(' '.repeat(urlPadding) + chalk.green.underline(shareUrl));
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Show warning if suspicious activity detected
|
|
187
|
-
if (warningMessage && !options.small) {
|
|
188
|
-
console.log();
|
|
189
|
-
console.log(chalk.yellow(warningMessage));
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
} catch (error) {
|
|
193
|
-
console.error(chalk.red(`Error: ${(error as Error).message}`));
|
|
194
|
-
process.exit(1);
|
|
195
|
-
}
|
|
196
|
-
}
|
package/src/commands/spin.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { getCurrentCommitHash, getCurrentCommitFullHash } from '../utils/git.js';
|
|
2
|
-
import { playCommand } from './play.js';
|
|
3
|
-
|
|
4
|
-
interface SpinOptions {
|
|
5
|
-
small?: boolean;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export async function spinCommand(options: SpinOptions): Promise<void> {
|
|
9
|
-
try {
|
|
10
|
-
const hash = getCurrentCommitHash();
|
|
11
|
-
const fullHash = getCurrentCommitFullHash();
|
|
12
|
-
await playCommand(hash, { ...options, fullHash });
|
|
13
|
-
} catch (error) {
|
|
14
|
-
console.error(`Error: ${(error as Error).message}`);
|
|
15
|
-
process.exit(1);
|
|
16
|
-
}
|
|
17
|
-
}
|