@snapcommit/cli 1.0.4 → 1.0.6
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/lib/auth.js +8 -0
- package/dist/repl.js +58 -1
- package/dist/utils/repo-manager.js +187 -0
- package/package.json +1 -1
package/dist/lib/auth.js
CHANGED
|
@@ -133,6 +133,14 @@ async function promptAuth() {
|
|
|
133
133
|
console.log(chalk_1.default.gray(' • ') + chalk_1.default.cyan('snap quick') + chalk_1.default.gray(' - AI commit in any repo'));
|
|
134
134
|
console.log(chalk_1.default.gray(' • ') + chalk_1.default.cyan('snap') + chalk_1.default.gray(' - Natural language Git commands'));
|
|
135
135
|
console.log(chalk_1.default.gray(' • Type ') + chalk_1.default.cyan('exit') + chalk_1.default.gray(' to leave SnapCommit\n'));
|
|
136
|
+
console.log(chalk_1.default.yellow('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
|
|
137
|
+
console.log(chalk_1.default.bold('💡 Want GitHub features too?\n'));
|
|
138
|
+
console.log(chalk_1.default.gray(' Create PRs, check CI, and more with natural language!'));
|
|
139
|
+
console.log(chalk_1.default.gray(' Run: ') + chalk_1.default.cyan('snap github connect'));
|
|
140
|
+
console.log(chalk_1.default.gray(' (You\'ll need a GitHub Personal Access Token - Classic)\n'));
|
|
141
|
+
console.log(chalk_1.default.gray(' Get one at: ') + chalk_1.default.cyan('https://github.com/settings/tokens'));
|
|
142
|
+
console.log(chalk_1.default.gray(' Scopes needed: ') + chalk_1.default.white('repo, workflow, read:user'));
|
|
143
|
+
console.log(chalk_1.default.gray(' 💡 Tip: Set no expiration or remember to renew!\n'));
|
|
136
144
|
return true;
|
|
137
145
|
}
|
|
138
146
|
else {
|
package/dist/repl.js
CHANGED
|
@@ -10,6 +10,8 @@ const auth_1 = require("./lib/auth");
|
|
|
10
10
|
const natural_1 = require("./commands/natural");
|
|
11
11
|
const quick_1 = require("./commands/quick");
|
|
12
12
|
const stats_1 = require("./commands/stats");
|
|
13
|
+
const github_connect_1 = require("./commands/github-connect");
|
|
14
|
+
const repo_manager_1 = require("./utils/repo-manager");
|
|
13
15
|
/**
|
|
14
16
|
* Start SnapCommit REPL (Read-Eval-Print-Loop)
|
|
15
17
|
* Interactive mode for natural language Git commands
|
|
@@ -27,12 +29,30 @@ async function startREPL() {
|
|
|
27
29
|
process.exit(1);
|
|
28
30
|
}
|
|
29
31
|
console.log(chalk_1.default.green(`✅ Logged in as ${chalk_1.default.bold(authConfig.email)}\n`));
|
|
32
|
+
// Check GitHub connection status
|
|
33
|
+
const githubConnected = (0, github_connect_1.isGitHubConnected)();
|
|
34
|
+
if (githubConnected) {
|
|
35
|
+
const githubConfig = (0, github_connect_1.getGitHubConfig)();
|
|
36
|
+
console.log(chalk_1.default.green(`✅ GitHub connected as ${chalk_1.default.bold('@' + githubConfig?.username)}\n`));
|
|
37
|
+
}
|
|
30
38
|
console.log(chalk_1.default.gray('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
|
|
31
39
|
console.log(chalk_1.default.bold('💡 What can SnapCommit do?\n'));
|
|
32
40
|
console.log(chalk_1.default.gray(' • ') + chalk_1.default.white('Natural language Git: ') + chalk_1.default.cyan('"undo my last commit"'));
|
|
33
41
|
console.log(chalk_1.default.gray(' • ') + chalk_1.default.white('Quick AI commits: ') + chalk_1.default.cyan('quick'));
|
|
34
|
-
console.log(chalk_1.default.gray(' • ') + chalk_1.default.white('
|
|
42
|
+
console.log(chalk_1.default.gray(' • ') + chalk_1.default.white('Switch repos: ') + chalk_1.default.cyan('cd /path/to/repo') + chalk_1.default.gray(' or ') + chalk_1.default.cyan('repos'));
|
|
43
|
+
if (githubConnected) {
|
|
44
|
+
console.log(chalk_1.default.gray(' • ') + chalk_1.default.white('GitHub operations: ') + chalk_1.default.cyan('"create a PR", "check CI"'));
|
|
45
|
+
}
|
|
35
46
|
console.log(chalk_1.default.gray(' • ') + chalk_1.default.white('Exit SnapCommit: ') + chalk_1.default.cyan('exit') + chalk_1.default.gray(' or ') + chalk_1.default.cyan('quit\n'));
|
|
47
|
+
// Show GitHub setup reminder if not connected
|
|
48
|
+
if (!githubConnected) {
|
|
49
|
+
console.log(chalk_1.default.yellow('💡 Want GitHub features? (Create PRs, check CI, etc.)\n'));
|
|
50
|
+
console.log(chalk_1.default.gray(' Run: ') + chalk_1.default.cyan('snap github connect'));
|
|
51
|
+
console.log(chalk_1.default.gray(' You\'ll need a GitHub Personal Access Token (Classic)'));
|
|
52
|
+
console.log(chalk_1.default.gray(' Get one at: ') + chalk_1.default.cyan('https://github.com/settings/tokens'));
|
|
53
|
+
console.log(chalk_1.default.gray(' Scopes: ') + chalk_1.default.white('repo, workflow, read:user'));
|
|
54
|
+
console.log(chalk_1.default.gray(' 💡 Set no expiration or remember to renew!\n'));
|
|
55
|
+
}
|
|
36
56
|
console.log(chalk_1.default.gray('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
|
|
37
57
|
console.log(chalk_1.default.bold.yellow('🎯 Try it now! Type what you want to do:\n'));
|
|
38
58
|
const rl = readline_1.default.createInterface({
|
|
@@ -64,8 +84,45 @@ async function startREPL() {
|
|
|
64
84
|
rl.prompt();
|
|
65
85
|
return;
|
|
66
86
|
}
|
|
87
|
+
// cd command - navigate to different repo
|
|
88
|
+
if (line.startsWith('cd ')) {
|
|
89
|
+
const targetPath = line.substring(3).trim();
|
|
90
|
+
const result = (0, repo_manager_1.changeToRepo)(targetPath);
|
|
91
|
+
if (result.success) {
|
|
92
|
+
console.log(chalk_1.default.green(`\n✅ Switched to: ${chalk_1.default.bold(result.repoName)}`));
|
|
93
|
+
console.log(chalk_1.default.gray(` Path: ${result.repoPath}\n`));
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
console.log(chalk_1.default.red(`\n❌ ${result.error}`));
|
|
97
|
+
console.log(chalk_1.default.gray(' Navigate to a valid git repository\n'));
|
|
98
|
+
}
|
|
99
|
+
rl.prompt();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
// repos command - show recent repos
|
|
103
|
+
if (line === 'repos' || line === 'repo') {
|
|
104
|
+
const repos = (0, repo_manager_1.getRecentRepos)();
|
|
105
|
+
if (repos.length === 0) {
|
|
106
|
+
console.log(chalk_1.default.yellow('\n📁 No recent repositories\n'));
|
|
107
|
+
console.log(chalk_1.default.gray(' Use ') + chalk_1.default.cyan('cd /path/to/repo') + chalk_1.default.gray(' to navigate to a repo\n'));
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
console.log(chalk_1.default.bold('\n📁 Recent Repositories:\n'));
|
|
111
|
+
repos.slice(0, 10).forEach((repo, index) => {
|
|
112
|
+
const isCurrent = repo.path === process.cwd();
|
|
113
|
+
const prefix = isCurrent ? chalk_1.default.green('→ ') : ' ';
|
|
114
|
+
console.log(prefix + chalk_1.default.cyan(`${index + 1}. ${repo.name}`) + chalk_1.default.gray(` (${repo.useCount} uses)`));
|
|
115
|
+
console.log(' ' + chalk_1.default.gray(repo.path));
|
|
116
|
+
});
|
|
117
|
+
console.log(chalk_1.default.gray('\n Use ') + chalk_1.default.cyan('cd <path>') + chalk_1.default.gray(' to switch repos\n'));
|
|
118
|
+
}
|
|
119
|
+
rl.prompt();
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
67
122
|
if (line === 'help' || line === 'h') {
|
|
68
123
|
console.log(chalk_1.default.bold('\n💡 SnapCommit Commands:\n'));
|
|
124
|
+
console.log(chalk_1.default.gray(' • ') + chalk_1.default.cyan('cd /path/to/repo') + chalk_1.default.gray(' - Switch to a different repository'));
|
|
125
|
+
console.log(chalk_1.default.gray(' • ') + chalk_1.default.cyan('repos') + chalk_1.default.gray(' - Show recent repositories'));
|
|
69
126
|
console.log(chalk_1.default.gray(' • ') + chalk_1.default.cyan('quick') + chalk_1.default.gray(' - Quick AI commit (stage all + commit)'));
|
|
70
127
|
console.log(chalk_1.default.gray(' • ') + chalk_1.default.cyan('stats') + chalk_1.default.gray(' - Show your coding stats'));
|
|
71
128
|
console.log(chalk_1.default.gray(' • ') + chalk_1.default.cyan('exit/quit') + chalk_1.default.gray(' - Exit SnapCommit'));
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Multi-Repo Manager
|
|
4
|
+
* Handles navigation and tracking of multiple git repositories
|
|
5
|
+
*/
|
|
6
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
|
+
};
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.isGitRepository = isGitRepository;
|
|
11
|
+
exports.changeToRepo = changeToRepo;
|
|
12
|
+
exports.getRecentRepos = getRecentRepos;
|
|
13
|
+
exports.getCurrentRepo = getCurrentRepo;
|
|
14
|
+
exports.findRepo = findRepo;
|
|
15
|
+
const fs_1 = __importDefault(require("fs"));
|
|
16
|
+
const path_1 = __importDefault(require("path"));
|
|
17
|
+
const os_1 = __importDefault(require("os"));
|
|
18
|
+
const child_process_1 = require("child_process");
|
|
19
|
+
const CONFIG_DIR = path_1.default.join(os_1.default.homedir(), '.snapcommit');
|
|
20
|
+
const REPOS_FILE = path_1.default.join(CONFIG_DIR, 'repos.json');
|
|
21
|
+
/**
|
|
22
|
+
* Ensure config directory exists
|
|
23
|
+
*/
|
|
24
|
+
function ensureConfigDir() {
|
|
25
|
+
if (!fs_1.default.existsSync(CONFIG_DIR)) {
|
|
26
|
+
fs_1.default.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Load repos configuration
|
|
31
|
+
*/
|
|
32
|
+
function loadReposConfig() {
|
|
33
|
+
ensureConfigDir();
|
|
34
|
+
if (!fs_1.default.existsSync(REPOS_FILE)) {
|
|
35
|
+
return { current: null, history: [] };
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const data = fs_1.default.readFileSync(REPOS_FILE, 'utf-8');
|
|
39
|
+
return JSON.parse(data);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return { current: null, history: [] };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Save repos configuration
|
|
47
|
+
*/
|
|
48
|
+
function saveReposConfig(config) {
|
|
49
|
+
ensureConfigDir();
|
|
50
|
+
fs_1.default.writeFileSync(REPOS_FILE, JSON.stringify(config, null, 2));
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Check if path is a git repository
|
|
54
|
+
*/
|
|
55
|
+
function isGitRepository(dirPath) {
|
|
56
|
+
try {
|
|
57
|
+
const gitDir = path_1.default.join(dirPath, '.git');
|
|
58
|
+
return fs_1.default.existsSync(gitDir) && fs_1.default.statSync(gitDir).isDirectory();
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Get repository name from path
|
|
66
|
+
*/
|
|
67
|
+
function getRepoName(repoPath) {
|
|
68
|
+
try {
|
|
69
|
+
// Try to get repo name from git config
|
|
70
|
+
const remoteName = (0, child_process_1.execSync)('git config --get remote.origin.url', {
|
|
71
|
+
cwd: repoPath,
|
|
72
|
+
encoding: 'utf-8',
|
|
73
|
+
stdio: ['pipe', 'pipe', 'ignore']
|
|
74
|
+
}).trim();
|
|
75
|
+
// Extract repo name from URL
|
|
76
|
+
const match = remoteName.match(/\/([^\/]+?)(\.git)?$/);
|
|
77
|
+
if (match) {
|
|
78
|
+
return match[1];
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// Fallback to directory name
|
|
83
|
+
}
|
|
84
|
+
return path_1.default.basename(repoPath);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Change to a repository directory
|
|
88
|
+
*/
|
|
89
|
+
function changeToRepo(targetPath) {
|
|
90
|
+
// Resolve path (handle ~ and relative paths)
|
|
91
|
+
let resolvedPath = targetPath;
|
|
92
|
+
if (targetPath.startsWith('~')) {
|
|
93
|
+
resolvedPath = targetPath.replace('~', os_1.default.homedir());
|
|
94
|
+
}
|
|
95
|
+
else if (!path_1.default.isAbsolute(targetPath)) {
|
|
96
|
+
resolvedPath = path_1.default.resolve(process.cwd(), targetPath);
|
|
97
|
+
}
|
|
98
|
+
// Check if path exists
|
|
99
|
+
if (!fs_1.default.existsSync(resolvedPath)) {
|
|
100
|
+
return { success: false, error: `Path does not exist: ${resolvedPath}` };
|
|
101
|
+
}
|
|
102
|
+
// Check if it's a directory
|
|
103
|
+
if (!fs_1.default.statSync(resolvedPath).isDirectory()) {
|
|
104
|
+
return { success: false, error: 'Path is not a directory' };
|
|
105
|
+
}
|
|
106
|
+
// Check if it's a git repository
|
|
107
|
+
if (!isGitRepository(resolvedPath)) {
|
|
108
|
+
return { success: false, error: 'Not a git repository' };
|
|
109
|
+
}
|
|
110
|
+
// Change process directory
|
|
111
|
+
try {
|
|
112
|
+
process.chdir(resolvedPath);
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
return { success: false, error: `Failed to change directory: ${error.message}` };
|
|
116
|
+
}
|
|
117
|
+
// Get repo name
|
|
118
|
+
const repoName = getRepoName(resolvedPath);
|
|
119
|
+
// Track in history
|
|
120
|
+
trackRepo(resolvedPath, repoName);
|
|
121
|
+
return { success: true, repoPath: resolvedPath, repoName };
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Track a repository in history
|
|
125
|
+
*/
|
|
126
|
+
function trackRepo(repoPath, repoName) {
|
|
127
|
+
const config = loadReposConfig();
|
|
128
|
+
// Update current
|
|
129
|
+
config.current = repoPath;
|
|
130
|
+
// Update or add to history
|
|
131
|
+
const existingIndex = config.history.findIndex(r => r.path === repoPath);
|
|
132
|
+
if (existingIndex >= 0) {
|
|
133
|
+
// Update existing entry
|
|
134
|
+
config.history[existingIndex].lastUsed = Date.now();
|
|
135
|
+
config.history[existingIndex].useCount++;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
// Add new entry
|
|
139
|
+
config.history.push({
|
|
140
|
+
path: repoPath,
|
|
141
|
+
name: repoName,
|
|
142
|
+
lastUsed: Date.now(),
|
|
143
|
+
useCount: 1
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
// Keep only last 20 repos
|
|
147
|
+
config.history = config.history
|
|
148
|
+
.sort((a, b) => b.lastUsed - a.lastUsed)
|
|
149
|
+
.slice(0, 20);
|
|
150
|
+
saveReposConfig(config);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Get recent repositories
|
|
154
|
+
*/
|
|
155
|
+
function getRecentRepos() {
|
|
156
|
+
const config = loadReposConfig();
|
|
157
|
+
return config.history.filter(repo => fs_1.default.existsSync(repo.path));
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Get current repository path
|
|
161
|
+
*/
|
|
162
|
+
function getCurrentRepo() {
|
|
163
|
+
const config = loadReposConfig();
|
|
164
|
+
return config.current;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Find a repo by name or partial path
|
|
168
|
+
*/
|
|
169
|
+
function findRepo(query) {
|
|
170
|
+
const config = loadReposConfig();
|
|
171
|
+
// Exact name match
|
|
172
|
+
const exactMatch = config.history.find(r => r.name.toLowerCase() === query.toLowerCase());
|
|
173
|
+
if (exactMatch && fs_1.default.existsSync(exactMatch.path)) {
|
|
174
|
+
return exactMatch;
|
|
175
|
+
}
|
|
176
|
+
// Partial name match
|
|
177
|
+
const partialMatch = config.history.find(r => r.name.toLowerCase().includes(query.toLowerCase()));
|
|
178
|
+
if (partialMatch && fs_1.default.existsSync(partialMatch.path)) {
|
|
179
|
+
return partialMatch;
|
|
180
|
+
}
|
|
181
|
+
// Path match
|
|
182
|
+
const pathMatch = config.history.find(r => r.path.includes(query));
|
|
183
|
+
if (pathMatch && fs_1.default.existsSync(pathMatch.path)) {
|
|
184
|
+
return pathMatch;
|
|
185
|
+
}
|
|
186
|
+
return null;
|
|
187
|
+
}
|