@snapcommit/cli 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 (40) hide show
  1. package/README.md +162 -0
  2. package/dist/ai/anthropic-client.js +92 -0
  3. package/dist/ai/commit-generator.js +200 -0
  4. package/dist/ai/gemini-client.js +201 -0
  5. package/dist/ai/git-interpreter.js +209 -0
  6. package/dist/ai/smart-solver.js +260 -0
  7. package/dist/auth/supabase-client.js +288 -0
  8. package/dist/commands/activate.js +108 -0
  9. package/dist/commands/commit.js +255 -0
  10. package/dist/commands/conflict.js +233 -0
  11. package/dist/commands/doctor.js +113 -0
  12. package/dist/commands/git-advanced.js +311 -0
  13. package/dist/commands/github-auth.js +193 -0
  14. package/dist/commands/login.js +11 -0
  15. package/dist/commands/natural.js +305 -0
  16. package/dist/commands/onboard.js +111 -0
  17. package/dist/commands/quick.js +173 -0
  18. package/dist/commands/setup.js +163 -0
  19. package/dist/commands/stats.js +128 -0
  20. package/dist/commands/uninstall.js +131 -0
  21. package/dist/db/database.js +99 -0
  22. package/dist/index.js +144 -0
  23. package/dist/lib/auth.js +171 -0
  24. package/dist/lib/github.js +280 -0
  25. package/dist/lib/multi-repo.js +276 -0
  26. package/dist/lib/supabase.js +153 -0
  27. package/dist/license/manager.js +203 -0
  28. package/dist/repl/index.js +185 -0
  29. package/dist/repl/interpreter.js +524 -0
  30. package/dist/utils/analytics.js +36 -0
  31. package/dist/utils/auth-storage.js +65 -0
  32. package/dist/utils/dopamine.js +211 -0
  33. package/dist/utils/errors.js +56 -0
  34. package/dist/utils/git.js +105 -0
  35. package/dist/utils/heatmap.js +265 -0
  36. package/dist/utils/rate-limit.js +68 -0
  37. package/dist/utils/retry.js +46 -0
  38. package/dist/utils/ui.js +189 -0
  39. package/dist/utils/version.js +81 -0
  40. package/package.json +69 -0
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.logActivity = logActivity;
7
+ exports.logCommit = logCommit;
8
+ exports.getStats = getStats;
9
+ exports.logAnalyticsEvent = logAnalyticsEvent;
10
+ const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
11
+ const path_1 = __importDefault(require("path"));
12
+ const fs_1 = __importDefault(require("fs"));
13
+ const os_1 = __importDefault(require("os"));
14
+ const DB_DIR = path_1.default.join(os_1.default.homedir(), '.snapcommit');
15
+ const DB_PATH = path_1.default.join(DB_DIR, 'snapcommit.db');
16
+ // Ensure directory exists
17
+ if (!fs_1.default.existsSync(DB_DIR)) {
18
+ fs_1.default.mkdirSync(DB_DIR, { recursive: true });
19
+ }
20
+ const db = new better_sqlite3_1.default(DB_PATH);
21
+ // Initialize database schema
22
+ db.exec(`
23
+ CREATE TABLE IF NOT EXISTS activities (
24
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
25
+ type TEXT NOT NULL,
26
+ command TEXT,
27
+ description TEXT,
28
+ timestamp INTEGER NOT NULL,
29
+ duration INTEGER,
30
+ metadata TEXT
31
+ );
32
+
33
+ CREATE INDEX IF NOT EXISTS idx_activities_timestamp ON activities(timestamp);
34
+ CREATE INDEX IF NOT EXISTS idx_activities_type ON activities(type);
35
+
36
+ CREATE TABLE IF NOT EXISTS commits (
37
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
38
+ message TEXT NOT NULL,
39
+ hash TEXT,
40
+ files_changed INTEGER,
41
+ insertions INTEGER,
42
+ deletions INTEGER,
43
+ timestamp INTEGER NOT NULL
44
+ );
45
+
46
+ CREATE INDEX IF NOT EXISTS idx_commits_timestamp ON commits(timestamp);
47
+ `);
48
+ function logActivity(activity) {
49
+ const stmt = db.prepare(`
50
+ INSERT INTO activities (type, command, description, timestamp, duration, metadata)
51
+ VALUES (?, ?, ?, ?, ?, ?)
52
+ `);
53
+ stmt.run(activity.type, activity.command || null, activity.description || null, activity.timestamp, activity.duration || null, activity.metadata ? JSON.stringify(activity.metadata) : null);
54
+ }
55
+ function logCommit(commit) {
56
+ const stmt = db.prepare(`
57
+ INSERT INTO commits (message, hash, files_changed, insertions, deletions, timestamp)
58
+ VALUES (?, ?, ?, ?, ?, ?)
59
+ `);
60
+ stmt.run(commit.message, commit.hash || null, commit.files_changed || null, commit.insertions || null, commit.deletions || null, commit.timestamp);
61
+ }
62
+ function getStats(daysBack = 7) {
63
+ const since = Date.now() - daysBack * 24 * 60 * 60 * 1000;
64
+ const totalCommits = db
65
+ .prepare('SELECT COUNT(*) as count FROM commits WHERE timestamp > ?')
66
+ .get(since);
67
+ const totalCommands = db
68
+ .prepare('SELECT COUNT(*) as count FROM activities WHERE type = "command" AND timestamp > ?')
69
+ .get(since);
70
+ const recentCommits = db
71
+ .prepare('SELECT * FROM commits WHERE timestamp > ? ORDER BY timestamp DESC LIMIT 10')
72
+ .all(since);
73
+ const totalInsertions = db
74
+ .prepare('SELECT SUM(insertions) as sum FROM commits WHERE timestamp > ?')
75
+ .get(since);
76
+ const totalDeletions = db
77
+ .prepare('SELECT SUM(deletions) as sum FROM commits WHERE timestamp > ?')
78
+ .get(since);
79
+ return {
80
+ totalCommits: totalCommits.count,
81
+ totalCommands: totalCommands.count,
82
+ totalInsertions: totalInsertions.sum || 0,
83
+ totalDeletions: totalDeletions.sum || 0,
84
+ recentCommits,
85
+ };
86
+ }
87
+ // Analytics
88
+ function logAnalyticsEvent(event, data = {}) {
89
+ try {
90
+ db.prepare(`
91
+ INSERT INTO activities (type, command, description, timestamp, metadata)
92
+ VALUES (?, ?, ?, ?, ?)
93
+ `).run('analytics', event, JSON.stringify(data), Date.now(), null);
94
+ }
95
+ catch (error) {
96
+ // Silent fail - don't break user experience
97
+ }
98
+ }
99
+ exports.default = db;
package/dist/index.js ADDED
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const commander_1 = require("commander");
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const dotenv_1 = require("dotenv");
10
+ const path_1 = __importDefault(require("path"));
11
+ const version_1 = require("./utils/version");
12
+ const commit_1 = require("./commands/commit");
13
+ const stats_1 = require("./commands/stats");
14
+ const setup_1 = require("./commands/setup");
15
+ const quick_1 = require("./commands/quick");
16
+ const doctor_1 = require("./commands/doctor");
17
+ const activate_1 = require("./commands/activate");
18
+ const onboard_1 = require("./commands/onboard");
19
+ const login_1 = require("./commands/login");
20
+ const natural_1 = require("./commands/natural");
21
+ const conflict_1 = require("./commands/conflict");
22
+ const uninstall_1 = require("./commands/uninstall");
23
+ const repl_1 = require("./repl");
24
+ // Load environment variables from root .env
25
+ (0, dotenv_1.config)({ path: path_1.default.join(__dirname, '../../.env') });
26
+ const program = new commander_1.Command();
27
+ // Check for updates (async, non-blocking)
28
+ (0, version_1.checkForUpdates)().then((result) => {
29
+ if (result && result.hasUpdate) {
30
+ console.log(chalk_1.default.yellow(`\n⚠️ Update available: ${result.currentVersion} → ${result.latestVersion}`));
31
+ console.log(chalk_1.default.gray(' Run: npm install -g builderos\n'));
32
+ }
33
+ }).catch(() => {
34
+ // Silent fail
35
+ });
36
+ program
37
+ .name('snapcommit')
38
+ .description('Snap. Commit. Track. - Instant AI commits with beautiful stats')
39
+ .version('1.0.0');
40
+ // Command: devflow commit
41
+ program
42
+ .command('commit')
43
+ .alias('c')
44
+ .description('Generate AI-powered commit message (interactive)')
45
+ .action(async () => {
46
+ await (0, commit_1.commitCommand)();
47
+ });
48
+ // Command: devflow quick
49
+ program
50
+ .command('quick')
51
+ .alias('q')
52
+ .description('Quick commit (stage all + AI commit, no prompts)')
53
+ .action(async () => {
54
+ await (0, quick_1.quickCommand)();
55
+ });
56
+ // Command: devflow stats
57
+ program
58
+ .command('stats')
59
+ .alias('s')
60
+ .description('Show your coding stats')
61
+ .action(() => {
62
+ (0, stats_1.statsCommand)();
63
+ });
64
+ // Command: devflow setup
65
+ program
66
+ .command('setup')
67
+ .description('Set up DevFlow shell integration')
68
+ .action(() => {
69
+ (0, setup_1.setupCommand)();
70
+ });
71
+ // Command: snapcommit doctor
72
+ program
73
+ .command('doctor')
74
+ .alias('check')
75
+ .description('Check if SnapCommit is set up correctly')
76
+ .action(() => {
77
+ (0, doctor_1.doctorCommand)();
78
+ });
79
+ // Command: snapcommit activate
80
+ program
81
+ .command('activate [license-key]')
82
+ .description('Activate your Pro license')
83
+ .action(async (licenseKey) => {
84
+ await (0, activate_1.activateCommand)(licenseKey);
85
+ });
86
+ // Command: snapcommit status
87
+ program
88
+ .command('status')
89
+ .description('Check your license status')
90
+ .action(() => {
91
+ (0, activate_1.statusCommand)();
92
+ });
93
+ // Command: snapcommit onboard
94
+ program
95
+ .command('onboard')
96
+ .alias('welcome')
97
+ .description('Interactive onboarding tour')
98
+ .action(async () => {
99
+ await (0, onboard_1.onboardCommand)();
100
+ });
101
+ // Command: snapcommit login
102
+ program
103
+ .command('login')
104
+ .description('Sign in or sign up to SnapCommit')
105
+ .action(async () => {
106
+ await (0, login_1.loginCommand)();
107
+ });
108
+ // Command: snapcommit logout
109
+ program
110
+ .command('logout')
111
+ .description('Sign out from SnapCommit')
112
+ .action(async () => {
113
+ await (0, login_1.logoutCommand)();
114
+ });
115
+ // Command: snapcommit conflict
116
+ program
117
+ .command('conflict')
118
+ .alias('resolve')
119
+ .description('AI-powered conflict resolution wizard')
120
+ .action(async () => {
121
+ await (0, conflict_1.conflictCommand)();
122
+ });
123
+ // Command: snapcommit uninstall
124
+ program
125
+ .command('uninstall')
126
+ .description('Remove SnapCommit shell integration')
127
+ .action(async () => {
128
+ await (0, uninstall_1.uninstallCommand)();
129
+ });
130
+ // Command: snapcommit <natural language>
131
+ // This catches any non-matching command and treats it as natural language
132
+ program
133
+ .command('* <words...>')
134
+ .description('Natural language Git commands (e.g., "undo that", "merge feature")')
135
+ .action(async (words) => {
136
+ const userInput = words.join(' ');
137
+ await (0, natural_1.naturalCommand)(userInput);
138
+ });
139
+ // Default action - Start REPL if no command is given
140
+ program.action(async () => {
141
+ // If user just types "snapcommit" or "snap", enter REPL mode
142
+ await (0, repl_1.startREPL)();
143
+ });
144
+ program.parse();
@@ -0,0 +1,171 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.isAuthenticated = isAuthenticated;
7
+ exports.getAuthConfig = getAuthConfig;
8
+ exports.clearAuth = clearAuth;
9
+ exports.promptAuth = promptAuth;
10
+ exports.ensureAuth = ensureAuth;
11
+ exports.getToken = getToken;
12
+ exports.logout = logout;
13
+ const fs_1 = __importDefault(require("fs"));
14
+ const path_1 = __importDefault(require("path"));
15
+ const os_1 = __importDefault(require("os"));
16
+ const chalk_1 = __importDefault(require("chalk"));
17
+ const readline_1 = __importDefault(require("readline"));
18
+ const CONFIG_DIR = path_1.default.join(os_1.default.homedir(), '.snapcommit');
19
+ const AUTH_FILE = path_1.default.join(CONFIG_DIR, 'auth.json');
20
+ // API URL - defaults to production, can be overridden for development
21
+ const API_BASE_URL = process.env.SNAPCOMMIT_API_URL || 'https://snapcommit.dev';
22
+ /**
23
+ * Ensure config directory exists
24
+ */
25
+ function ensureConfigDir() {
26
+ if (!fs_1.default.existsSync(CONFIG_DIR)) {
27
+ fs_1.default.mkdirSync(CONFIG_DIR, { recursive: true });
28
+ }
29
+ }
30
+ /**
31
+ * Check if user is authenticated
32
+ */
33
+ function isAuthenticated() {
34
+ return fs_1.default.existsSync(AUTH_FILE);
35
+ }
36
+ /**
37
+ * Get stored authentication config
38
+ */
39
+ function getAuthConfig() {
40
+ if (!fs_1.default.existsSync(AUTH_FILE)) {
41
+ return null;
42
+ }
43
+ try {
44
+ const data = fs_1.default.readFileSync(AUTH_FILE, 'utf-8');
45
+ return JSON.parse(data);
46
+ }
47
+ catch (error) {
48
+ return null;
49
+ }
50
+ }
51
+ /**
52
+ * Save authentication config
53
+ */
54
+ function saveAuthConfig(config) {
55
+ ensureConfigDir();
56
+ fs_1.default.writeFileSync(AUTH_FILE, JSON.stringify(config, null, 2));
57
+ }
58
+ /**
59
+ * Clear authentication
60
+ */
61
+ function clearAuth() {
62
+ if (fs_1.default.existsSync(AUTH_FILE)) {
63
+ fs_1.default.unlinkSync(AUTH_FILE);
64
+ }
65
+ }
66
+ /**
67
+ * Ask a question and return the answer
68
+ */
69
+ function askQuestion(query) {
70
+ const rl = readline_1.default.createInterface({
71
+ input: process.stdin,
72
+ output: process.stdout,
73
+ });
74
+ return new Promise((resolve) => rl.question(query, (ans) => {
75
+ rl.close();
76
+ resolve(ans);
77
+ }));
78
+ }
79
+ /**
80
+ * Verify token with backend
81
+ */
82
+ async function verifyToken(token) {
83
+ try {
84
+ const response = await fetch(`${API_BASE_URL}/api/auth/token?token=${encodeURIComponent(token)}`);
85
+ const data = await response.json();
86
+ if (response.ok && data.valid) {
87
+ return { valid: true, user: data.user };
88
+ }
89
+ return { valid: false };
90
+ }
91
+ catch (error) {
92
+ throw new Error('Failed to verify token. Please check your internet connection.');
93
+ }
94
+ }
95
+ /**
96
+ * Prompt user to authenticate
97
+ */
98
+ async function promptAuth() {
99
+ console.log(chalk_1.default.bold.cyan('\n🔐 Welcome to SnapCommit!\n'));
100
+ console.log(chalk_1.default.gray('To use SnapCommit, you need to authenticate.\n'));
101
+ console.log(chalk_1.default.bold('Step 1:') + chalk_1.default.gray(' Sign up or log in at:'));
102
+ console.log(chalk_1.default.cyan.underline(' 👉 https://snapcommit.dev/login\n'));
103
+ console.log(chalk_1.default.bold('Step 2:') + chalk_1.default.gray(' Generate a token from your dashboard'));
104
+ console.log(chalk_1.default.gray(' (You\'ll see a "Generate Token" button)\n'));
105
+ const token = await askQuestion(chalk_1.default.yellow('Step 3: Paste your token here: '));
106
+ if (!token || token.trim().length === 0) {
107
+ console.log(chalk_1.default.red('\n❌ No token provided. Authentication cancelled.\n'));
108
+ return false;
109
+ }
110
+ console.log(chalk_1.default.gray('\n🔄 Verifying token...\n'));
111
+ try {
112
+ const result = await verifyToken(token.trim());
113
+ if (result.valid && result.user) {
114
+ const config = {
115
+ token: token.trim(),
116
+ userId: result.user.id,
117
+ email: result.user.email,
118
+ name: result.user.name || 'Developer',
119
+ };
120
+ saveAuthConfig(config);
121
+ console.log(chalk_1.default.green(`✅ Authenticated successfully! Welcome, ${chalk_1.default.bold(config.name)}!\n`));
122
+ console.log(chalk_1.default.gray('💎 SnapCommit Pro - Unlimited AI commits'));
123
+ console.log(chalk_1.default.gray('🔥 Your coding journey starts now!\n'));
124
+ console.log(chalk_1.default.gray('Try: snap quick (in any git repo)\n'));
125
+ return true;
126
+ }
127
+ else {
128
+ console.log(chalk_1.default.red('\n❌ Invalid token. Please try again.\n'));
129
+ console.log(chalk_1.default.gray('Make sure you copied the entire token from your dashboard.\n'));
130
+ return false;
131
+ }
132
+ }
133
+ catch (error) {
134
+ console.log(chalk_1.default.red(`\n❌ ${error.message}\n`));
135
+ return false;
136
+ }
137
+ }
138
+ /**
139
+ * Ensure user is authenticated (prompt if not)
140
+ */
141
+ async function ensureAuth() {
142
+ if (isAuthenticated()) {
143
+ return getAuthConfig();
144
+ }
145
+ const success = await promptAuth();
146
+ if (success) {
147
+ return getAuthConfig();
148
+ }
149
+ return null;
150
+ }
151
+ /**
152
+ * Get authentication token for API calls
153
+ */
154
+ function getToken() {
155
+ const config = getAuthConfig();
156
+ return config?.token || null;
157
+ }
158
+ /**
159
+ * Logout command
160
+ */
161
+ async function logout() {
162
+ if (!isAuthenticated()) {
163
+ console.log(chalk_1.default.gray('\n Not currently logged in.\n'));
164
+ return;
165
+ }
166
+ const config = getAuthConfig();
167
+ console.log(chalk_1.default.yellow(`\n Logging out ${config?.email}...\n`));
168
+ clearAuth();
169
+ console.log(chalk_1.default.green(' ✅ Logged out successfully!\n'));
170
+ console.log(chalk_1.default.gray(' Run "snapcommit" again to log back in.\n'));
171
+ }
@@ -0,0 +1,280 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getCurrentRepo = getCurrentRepo;
7
+ exports.getCurrentBranch = getCurrentBranch;
8
+ exports.createPullRequest = createPullRequest;
9
+ exports.listPullRequests = listPullRequests;
10
+ exports.getPullRequest = getPullRequest;
11
+ exports.mergePullRequest = mergePullRequest;
12
+ exports.getCommitStatus = getCommitStatus;
13
+ exports.listWorkflowRuns = listWorkflowRuns;
14
+ exports.triggerWorkflow = triggerWorkflow;
15
+ exports.createIssue = createIssue;
16
+ exports.listIssues = listIssues;
17
+ exports.closeIssue = closeIssue;
18
+ exports.createRelease = createRelease;
19
+ exports.getRepoInfo = getRepoInfo;
20
+ const child_process_1 = require("child_process");
21
+ const chalk_1 = __importDefault(require("chalk"));
22
+ const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
23
+ const GITHUB_API = 'https://api.github.com';
24
+ if (!GITHUB_TOKEN) {
25
+ console.warn(chalk_1.default.yellow('⚠️ GitHub token not found. GitHub features will be disabled.'));
26
+ }
27
+ /**
28
+ * Get current repository info from git remote
29
+ */
30
+ function getCurrentRepo() {
31
+ try {
32
+ const remote = (0, child_process_1.execSync)('git remote get-url origin', { encoding: 'utf-8' }).trim();
33
+ // Parse GitHub URL (supports both HTTPS and SSH)
34
+ let match;
35
+ if (remote.startsWith('https://')) {
36
+ // https://github.com/owner/repo.git
37
+ match = remote.match(/github\.com[/:]([\w-]+)\/([\w-]+?)(\.git)?$/);
38
+ }
39
+ else {
40
+ // git@github.com:owner/repo.git
41
+ match = remote.match(/github\.com[/:]([\w-]+)\/([\w-]+?)(\.git)?$/);
42
+ }
43
+ if (match) {
44
+ return {
45
+ owner: match[1],
46
+ name: match[2],
47
+ };
48
+ }
49
+ return null;
50
+ }
51
+ catch (error) {
52
+ return null;
53
+ }
54
+ }
55
+ /**
56
+ * Get current branch name
57
+ */
58
+ function getCurrentBranch() {
59
+ try {
60
+ return (0, child_process_1.execSync)('git rev-parse --abbrev-ref HEAD', { encoding: 'utf-8' }).trim();
61
+ }
62
+ catch (error) {
63
+ return 'main';
64
+ }
65
+ }
66
+ /**
67
+ * GitHub API request helper
68
+ */
69
+ async function githubRequest(endpoint, options = {}) {
70
+ if (!GITHUB_TOKEN) {
71
+ throw new Error('GitHub token not configured. Please set GITHUB_TOKEN in your .env file.');
72
+ }
73
+ const url = `${GITHUB_API}${endpoint}`;
74
+ const headers = {
75
+ Authorization: `Bearer ${GITHUB_TOKEN}`,
76
+ Accept: 'application/vnd.github+json',
77
+ 'X-GitHub-Api-Version': '2022-11-28',
78
+ 'Content-Type': 'application/json',
79
+ ...options.headers,
80
+ };
81
+ const response = await fetch(url, {
82
+ ...options,
83
+ headers,
84
+ });
85
+ if (!response.ok) {
86
+ const error = await response.json().catch(() => ({ message: response.statusText }));
87
+ throw new Error(`GitHub API error: ${error.message || response.statusText}`);
88
+ }
89
+ return response.json();
90
+ }
91
+ /**
92
+ * Create a Pull Request
93
+ */
94
+ async function createPullRequest(options) {
95
+ const repo = getCurrentRepo();
96
+ if (!repo) {
97
+ throw new Error('Not a GitHub repository');
98
+ }
99
+ const currentBranch = options.head || getCurrentBranch();
100
+ const baseBranch = options.base || 'main';
101
+ // Get last commit message for default title
102
+ let defaultTitle = 'Update from ' + currentBranch;
103
+ try {
104
+ defaultTitle = (0, child_process_1.execSync)('git log -1 --pretty=%s', { encoding: 'utf-8' }).trim();
105
+ }
106
+ catch (error) {
107
+ // Use default
108
+ }
109
+ const title = options.title || defaultTitle;
110
+ const body = options.body || '';
111
+ const pr = await githubRequest(`/repos/${repo.owner}/${repo.name}/pulls`, {
112
+ method: 'POST',
113
+ body: JSON.stringify({
114
+ title,
115
+ body,
116
+ head: currentBranch,
117
+ base: baseBranch,
118
+ }),
119
+ });
120
+ return pr;
121
+ }
122
+ /**
123
+ * List Pull Requests
124
+ */
125
+ async function listPullRequests(options) {
126
+ const repo = getCurrentRepo();
127
+ if (!repo) {
128
+ throw new Error('Not a GitHub repository');
129
+ }
130
+ const state = options?.state || 'open';
131
+ const limit = options?.limit || 10;
132
+ const prs = await githubRequest(`/repos/${repo.owner}/${repo.name}/pulls?state=${state}&per_page=${limit}`);
133
+ return prs;
134
+ }
135
+ /**
136
+ * Get PR by number
137
+ */
138
+ async function getPullRequest(prNumber) {
139
+ const repo = getCurrentRepo();
140
+ if (!repo) {
141
+ throw new Error('Not a GitHub repository');
142
+ }
143
+ return await githubRequest(`/repos/${repo.owner}/${repo.name}/pulls/${prNumber}`);
144
+ }
145
+ /**
146
+ * Merge a Pull Request
147
+ */
148
+ async function mergePullRequest(prNumber, options) {
149
+ const repo = getCurrentRepo();
150
+ if (!repo) {
151
+ throw new Error('Not a GitHub repository');
152
+ }
153
+ const mergeMethod = options?.mergeMethod || 'merge';
154
+ return await githubRequest(`/repos/${repo.owner}/${repo.name}/pulls/${prNumber}/merge`, {
155
+ method: 'PUT',
156
+ body: JSON.stringify({
157
+ merge_method: mergeMethod,
158
+ commit_title: options?.commitTitle,
159
+ commit_message: options?.commitMessage,
160
+ }),
161
+ });
162
+ }
163
+ /**
164
+ * Check CI/CD status for a commit or PR
165
+ */
166
+ async function getCommitStatus(commitSha) {
167
+ const repo = getCurrentRepo();
168
+ if (!repo) {
169
+ throw new Error('Not a GitHub repository');
170
+ }
171
+ // Get current commit SHA if not provided
172
+ const sha = commitSha || (0, child_process_1.execSync)('git rev-parse HEAD', { encoding: 'utf-8' }).trim();
173
+ const status = await githubRequest(`/repos/${repo.owner}/${repo.name}/commits/${sha}/status`);
174
+ const checks = await githubRequest(`/repos/${repo.owner}/${repo.name}/commits/${sha}/check-runs`);
175
+ return {
176
+ status,
177
+ checks: checks.check_runs || [],
178
+ };
179
+ }
180
+ /**
181
+ * List workflow runs
182
+ */
183
+ async function listWorkflowRuns(options) {
184
+ const repo = getCurrentRepo();
185
+ if (!repo) {
186
+ throw new Error('Not a GitHub repository');
187
+ }
188
+ const limit = options?.limit || 10;
189
+ const endpoint = options?.workflowId
190
+ ? `/repos/${repo.owner}/${repo.name}/actions/workflows/${options.workflowId}/runs?per_page=${limit}`
191
+ : `/repos/${repo.owner}/${repo.name}/actions/runs?per_page=${limit}`;
192
+ return await githubRequest(endpoint);
193
+ }
194
+ /**
195
+ * Trigger a workflow
196
+ */
197
+ async function triggerWorkflow(workflowId, ref = 'main', inputs) {
198
+ const repo = getCurrentRepo();
199
+ if (!repo) {
200
+ throw new Error('Not a GitHub repository');
201
+ }
202
+ await githubRequest(`/repos/${repo.owner}/${repo.name}/actions/workflows/${workflowId}/dispatches`, {
203
+ method: 'POST',
204
+ body: JSON.stringify({
205
+ ref,
206
+ inputs,
207
+ }),
208
+ });
209
+ }
210
+ /**
211
+ * Create an issue
212
+ */
213
+ async function createIssue(options) {
214
+ const repo = getCurrentRepo();
215
+ if (!repo) {
216
+ throw new Error('Not a GitHub repository');
217
+ }
218
+ return await githubRequest(`/repos/${repo.owner}/${repo.name}/issues`, {
219
+ method: 'POST',
220
+ body: JSON.stringify(options),
221
+ });
222
+ }
223
+ /**
224
+ * List issues
225
+ */
226
+ async function listIssues(options) {
227
+ const repo = getCurrentRepo();
228
+ if (!repo) {
229
+ throw new Error('Not a GitHub repository');
230
+ }
231
+ const state = options?.state || 'open';
232
+ const limit = options?.limit || 10;
233
+ let endpoint = `/repos/${repo.owner}/${repo.name}/issues?state=${state}&per_page=${limit}`;
234
+ if (options?.labels && options.labels.length > 0) {
235
+ endpoint += `&labels=${options.labels.join(',')}`;
236
+ }
237
+ return await githubRequest(endpoint);
238
+ }
239
+ /**
240
+ * Close an issue
241
+ */
242
+ async function closeIssue(issueNumber) {
243
+ const repo = getCurrentRepo();
244
+ if (!repo) {
245
+ throw new Error('Not a GitHub repository');
246
+ }
247
+ return await githubRequest(`/repos/${repo.owner}/${repo.name}/issues/${issueNumber}`, {
248
+ method: 'PATCH',
249
+ body: JSON.stringify({ state: 'closed' }),
250
+ });
251
+ }
252
+ /**
253
+ * Create a release
254
+ */
255
+ async function createRelease(options) {
256
+ const repo = getCurrentRepo();
257
+ if (!repo) {
258
+ throw new Error('Not a GitHub repository');
259
+ }
260
+ return await githubRequest(`/repos/${repo.owner}/${repo.name}/releases`, {
261
+ method: 'POST',
262
+ body: JSON.stringify({
263
+ tag_name: options.tagName,
264
+ name: options.name || options.tagName,
265
+ body: options.body || '',
266
+ draft: options.draft || false,
267
+ prerelease: options.prerelease || false,
268
+ }),
269
+ });
270
+ }
271
+ /**
272
+ * Get repository info
273
+ */
274
+ async function getRepoInfo() {
275
+ const repo = getCurrentRepo();
276
+ if (!repo) {
277
+ throw new Error('Not a GitHub repository');
278
+ }
279
+ return await githubRequest(`/repos/${repo.owner}/${repo.name}`);
280
+ }