@snapcommit/cli 1.0.5 → 1.0.7
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/index.js +5 -2
- package/dist/repl.js +52 -0
- package/dist/utils/repo-manager.js +187 -0
- package/dist/utils/version.js +4 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -28,8 +28,11 @@ const program = new commander_1.Command();
|
|
|
28
28
|
// Check for updates (async, non-blocking)
|
|
29
29
|
(0, version_1.checkForUpdates)().then((result) => {
|
|
30
30
|
if (result && result.hasUpdate) {
|
|
31
|
-
console.log(chalk_1.default.yellow(
|
|
32
|
-
console.log(chalk_1.default.
|
|
31
|
+
console.log(chalk_1.default.yellow.bold('\n╔════════════════════════════════════════╗'));
|
|
32
|
+
console.log(chalk_1.default.yellow.bold('║ 🚀 UPDATE AVAILABLE! ║'));
|
|
33
|
+
console.log(chalk_1.default.yellow.bold('╚════════════════════════════════════════╝'));
|
|
34
|
+
console.log(chalk_1.default.white(` Current: ${chalk_1.default.red(result.currentVersion)} → Latest: ${chalk_1.default.green(result.latestVersion)}`));
|
|
35
|
+
console.log(chalk_1.default.cyan(' Update now: ') + chalk_1.default.white('npm install -g @snapcommit/cli@latest\n'));
|
|
33
36
|
}
|
|
34
37
|
}).catch(() => {
|
|
35
38
|
// Silent fail
|
package/dist/repl.js
CHANGED
|
@@ -11,6 +11,8 @@ const natural_1 = require("./commands/natural");
|
|
|
11
11
|
const quick_1 = require("./commands/quick");
|
|
12
12
|
const stats_1 = require("./commands/stats");
|
|
13
13
|
const github_connect_1 = require("./commands/github-connect");
|
|
14
|
+
const repo_manager_1 = require("./utils/repo-manager");
|
|
15
|
+
const version_1 = require("./utils/version");
|
|
14
16
|
/**
|
|
15
17
|
* Start SnapCommit REPL (Read-Eval-Print-Loop)
|
|
16
18
|
* Interactive mode for natural language Git commands
|
|
@@ -28,6 +30,18 @@ async function startREPL() {
|
|
|
28
30
|
process.exit(1);
|
|
29
31
|
}
|
|
30
32
|
console.log(chalk_1.default.green(`✅ Logged in as ${chalk_1.default.bold(authConfig.email)}\n`));
|
|
33
|
+
// Check for updates (non-blocking)
|
|
34
|
+
(0, version_1.checkForUpdates)().then(updateInfo => {
|
|
35
|
+
if (updateInfo && updateInfo.hasUpdate) {
|
|
36
|
+
console.log(chalk_1.default.yellow.bold('╔════════════════════════════════════════╗'));
|
|
37
|
+
console.log(chalk_1.default.yellow.bold('║ 🚀 UPDATE AVAILABLE! ║'));
|
|
38
|
+
console.log(chalk_1.default.yellow.bold('╚════════════════════════════════════════╝'));
|
|
39
|
+
console.log(chalk_1.default.white(` Current: ${chalk_1.default.red(updateInfo.currentVersion)} → Latest: ${chalk_1.default.green(updateInfo.latestVersion)}\n`));
|
|
40
|
+
console.log(chalk_1.default.cyan(' Update now: ') + chalk_1.default.white('npm install -g @snapcommit/cli@latest\n'));
|
|
41
|
+
}
|
|
42
|
+
}).catch(() => {
|
|
43
|
+
// Silent fail - don't interrupt user experience
|
|
44
|
+
});
|
|
31
45
|
// Check GitHub connection status
|
|
32
46
|
const githubConnected = (0, github_connect_1.isGitHubConnected)();
|
|
33
47
|
if (githubConnected) {
|
|
@@ -38,6 +52,7 @@ async function startREPL() {
|
|
|
38
52
|
console.log(chalk_1.default.bold('💡 What can SnapCommit do?\n'));
|
|
39
53
|
console.log(chalk_1.default.gray(' • ') + chalk_1.default.white('Natural language Git: ') + chalk_1.default.cyan('"undo my last commit"'));
|
|
40
54
|
console.log(chalk_1.default.gray(' • ') + chalk_1.default.white('Quick AI commits: ') + chalk_1.default.cyan('quick'));
|
|
55
|
+
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'));
|
|
41
56
|
if (githubConnected) {
|
|
42
57
|
console.log(chalk_1.default.gray(' • ') + chalk_1.default.white('GitHub operations: ') + chalk_1.default.cyan('"create a PR", "check CI"'));
|
|
43
58
|
}
|
|
@@ -82,8 +97,45 @@ async function startREPL() {
|
|
|
82
97
|
rl.prompt();
|
|
83
98
|
return;
|
|
84
99
|
}
|
|
100
|
+
// cd command - navigate to different repo
|
|
101
|
+
if (line.startsWith('cd ')) {
|
|
102
|
+
const targetPath = line.substring(3).trim();
|
|
103
|
+
const result = (0, repo_manager_1.changeToRepo)(targetPath);
|
|
104
|
+
if (result.success) {
|
|
105
|
+
console.log(chalk_1.default.green(`\n✅ Switched to: ${chalk_1.default.bold(result.repoName)}`));
|
|
106
|
+
console.log(chalk_1.default.gray(` Path: ${result.repoPath}\n`));
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
console.log(chalk_1.default.red(`\n❌ ${result.error}`));
|
|
110
|
+
console.log(chalk_1.default.gray(' Navigate to a valid git repository\n'));
|
|
111
|
+
}
|
|
112
|
+
rl.prompt();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
// repos command - show recent repos
|
|
116
|
+
if (line === 'repos' || line === 'repo') {
|
|
117
|
+
const repos = (0, repo_manager_1.getRecentRepos)();
|
|
118
|
+
if (repos.length === 0) {
|
|
119
|
+
console.log(chalk_1.default.yellow('\n📁 No recent repositories\n'));
|
|
120
|
+
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'));
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
console.log(chalk_1.default.bold('\n📁 Recent Repositories:\n'));
|
|
124
|
+
repos.slice(0, 10).forEach((repo, index) => {
|
|
125
|
+
const isCurrent = repo.path === process.cwd();
|
|
126
|
+
const prefix = isCurrent ? chalk_1.default.green('→ ') : ' ';
|
|
127
|
+
console.log(prefix + chalk_1.default.cyan(`${index + 1}. ${repo.name}`) + chalk_1.default.gray(` (${repo.useCount} uses)`));
|
|
128
|
+
console.log(' ' + chalk_1.default.gray(repo.path));
|
|
129
|
+
});
|
|
130
|
+
console.log(chalk_1.default.gray('\n Use ') + chalk_1.default.cyan('cd <path>') + chalk_1.default.gray(' to switch repos\n'));
|
|
131
|
+
}
|
|
132
|
+
rl.prompt();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
85
135
|
if (line === 'help' || line === 'h') {
|
|
86
136
|
console.log(chalk_1.default.bold('\n💡 SnapCommit Commands:\n'));
|
|
137
|
+
console.log(chalk_1.default.gray(' • ') + chalk_1.default.cyan('cd /path/to/repo') + chalk_1.default.gray(' - Switch to a different repository'));
|
|
138
|
+
console.log(chalk_1.default.gray(' • ') + chalk_1.default.cyan('repos') + chalk_1.default.gray(' - Show recent repositories'));
|
|
87
139
|
console.log(chalk_1.default.gray(' • ') + chalk_1.default.cyan('quick') + chalk_1.default.gray(' - Quick AI commit (stage all + commit)'));
|
|
88
140
|
console.log(chalk_1.default.gray(' • ') + chalk_1.default.cyan('stats') + chalk_1.default.gray(' - Show your coding stats'));
|
|
89
141
|
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
|
+
}
|
package/dist/utils/version.js
CHANGED
|
@@ -33,7 +33,7 @@ async function checkForUpdates() {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
// Fetch latest version from npm
|
|
36
|
-
const latestVersion = await getLatestVersionFromNpm('snapcommit');
|
|
36
|
+
const latestVersion = await getLatestVersionFromNpm('@snapcommit/cli');
|
|
37
37
|
// Update cache
|
|
38
38
|
const newCache = {
|
|
39
39
|
lastCheck: Date.now(),
|
|
@@ -53,7 +53,9 @@ async function checkForUpdates() {
|
|
|
53
53
|
}
|
|
54
54
|
function getLatestVersionFromNpm(packageName) {
|
|
55
55
|
return new Promise((resolve, reject) => {
|
|
56
|
-
|
|
56
|
+
// Encode package name for URL (handles scoped packages like @snapcommit/cli)
|
|
57
|
+
const encodedName = packageName.replace('/', '%2F');
|
|
58
|
+
https_1.default.get(`https://registry.npmjs.org/${encodedName}/latest`, (res) => {
|
|
57
59
|
let data = '';
|
|
58
60
|
res.on('data', (chunk) => data += chunk);
|
|
59
61
|
res.on('end', () => {
|