sncommit 1.0.1 → 1.0.3

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/eslint.config.mjs DELETED
@@ -1,34 +0,0 @@
1
- import js from "@eslint/js";
2
- import globals from "globals";
3
- import reactHooks from "eslint-plugin-react-hooks";
4
- import react from "eslint-plugin-react";
5
- import tseslint from "typescript-eslint";
6
- import { defineConfig } from "eslint/config";
7
-
8
- export default defineConfig(
9
- { ignores: ["dist", "eslint.config.mjs"] },
10
- {
11
- extends: [js.configs.recommended, ...tseslint.configs.recommended],
12
- files: ["**/*.{ts,tsx,js,jsx}"],
13
- languageOptions: {
14
- ecmaVersion: 2020,
15
- globals: globals.node,
16
- },
17
- plugins: {
18
- "react-hooks": reactHooks,
19
- react,
20
- },
21
- rules: {
22
- ...reactHooks.configs.recommended.rules,
23
- "react/react-in-jsx-scope": "off",
24
- // Allow explicit any for now as we might have some generic types or old code
25
- "@typescript-eslint/no-explicit-any": "warn",
26
- "@typescript-eslint/no-unused-vars": [
27
- "warn",
28
- { argsIgnorePattern: "^_" },
29
- ],
30
- // Suppress punycode warning in index.tsx requires process manipulation which is hacky but fine
31
- "@typescript-eslint/ban-ts-comment": "off",
32
- },
33
- },
34
- );
@@ -1,325 +0,0 @@
1
- import React, { useState, useEffect } from "react";
2
- import { Box, Text, useApp, useInput, useStdout } from "ink";
3
- import { GitService } from "../services/git";
4
- import { GroqService } from "../services/groq";
5
- import { configManager } from "../utils/config";
6
- import { StagedFiles } from "./StagedFiles";
7
- import { CommitSuggestions } from "./CommitSuggestions";
8
- import { CustomInputPrompt } from "./CustomInputPrompt";
9
- import { AppState, GitFile } from "../types";
10
-
11
- interface AppProps {
12
- addAll?: boolean;
13
- onExit: (message?: string) => void;
14
- }
15
-
16
- export const BetterCommitApp: React.FC<AppProps> = ({
17
- addAll: _addAll = false,
18
- onExit,
19
- }) => {
20
- const { exit } = useApp();
21
- const { write } = useStdout();
22
- const [state, setState] = useState<AppState>({
23
- stagedFiles: [],
24
- suggestions: [],
25
- selectedIndex: 0,
26
- isLoading: false,
27
- error: undefined,
28
- });
29
-
30
- const [isUsingFallback, setIsUsingFallback] = useState(false);
31
-
32
- const [successMessage, setSuccessMessage] = useState<string | undefined>(
33
- undefined,
34
- );
35
- const [customInput, setCustomInput] = useState("");
36
- const [isCustomInputMode, setIsCustomInputMode] = useState(false);
37
-
38
- // Auto-exit when showing success message
39
- useEffect(() => {
40
- if (successMessage) {
41
- write("\x1B[2J\x1B[0f");
42
- const timer = setTimeout(() => {
43
- exit();
44
- }, 1500);
45
- return () => clearTimeout(timer);
46
- }
47
- }, [successMessage, exit, write]);
48
-
49
- // Auto-exit when error occurs
50
- useEffect(() => {
51
- if (state.error) {
52
- exit();
53
- }
54
- }, [state.error, exit]);
55
-
56
- // Handle global exit keys (disabled when custom input is active)
57
- useInput(
58
- (input, key) => {
59
- if (key.escape || (key.ctrl && input === "c")) {
60
- onExit("Operation cancelled");
61
- exit();
62
- }
63
- },
64
- { isActive: !isCustomInputMode },
65
- );
66
-
67
- const config = configManager.getConfig();
68
- const gitService = new GitService();
69
-
70
- useEffect(() => {
71
- const initializeApp = async () => {
72
- try {
73
- const stagedFiles = await gitService.getStagedFiles();
74
- // No need to check for empty staged files here as index.tsx handles it
75
-
76
- await gitService.getDiff();
77
-
78
- await fetchSuggestions(stagedFiles);
79
-
80
- setState((prev) => ({
81
- ...prev,
82
- stagedFiles,
83
- }));
84
- } catch (error) {
85
- setState((prev) => ({
86
- ...prev,
87
- error: `Error: ${error instanceof Error ? error.message : String(error)}`,
88
- }));
89
- }
90
- };
91
-
92
- initializeApp();
93
- // eslint-disable-next-line react-hooks/exhaustive-deps
94
- }, []);
95
-
96
- const fetchSuggestions = async (
97
- stagedFiles: GitFile[],
98
- customPrompt?: string,
99
- ) => {
100
- if (!config.groqApiKey || config.groqApiKey.trim() === "") {
101
- setState((prev) => ({
102
- ...prev,
103
- error:
104
- 'Groq API key not configured. Run "better-commit config" to set it up.',
105
- isLoading: false,
106
- }));
107
- return;
108
- }
109
-
110
- try {
111
- const groqService = new GroqService(config.groqApiKey, config);
112
- const diff = await gitService.getDiff();
113
- const diffStats = await gitService.getDiffStats();
114
- const recentCommits = await gitService.getRecentCommits(
115
- config.maxHistoryCommits || 50,
116
- );
117
-
118
- let suggestions;
119
- if (customPrompt) {
120
- suggestions =
121
- await groqService.generateCommitSuggestionsFromCustomInput(
122
- stagedFiles,
123
- diff,
124
- customPrompt,
125
- recentCommits,
126
- diffStats,
127
- );
128
- } else {
129
- suggestions = await groqService.generateCommitSuggestions(
130
- stagedFiles,
131
- diff,
132
- recentCommits,
133
- diffStats,
134
- );
135
- }
136
-
137
- const hasFallback = suggestions.some((s) => s.isFallback);
138
- setState((prev) => ({
139
- ...prev,
140
- suggestions,
141
- isLoading: false,
142
- error: undefined,
143
- }));
144
- setIsUsingFallback(hasFallback);
145
- } catch (error) {
146
- setState((prev) => ({
147
- ...prev,
148
- error: `Failed to generate suggestions: ${error instanceof Error ? error.message : String(error)}`,
149
- isLoading: false,
150
- }));
151
- }
152
- };
153
-
154
- const handleSelect = (index: number) => {
155
- setState((prev) => ({ ...prev, selectedIndex: index }));
156
- };
157
-
158
- const handleCommit = async (index: number) => {
159
- const selectedSuggestion = state.suggestions[index];
160
- if (selectedSuggestion) {
161
- try {
162
- await gitService.commit(selectedSuggestion.message);
163
- setSuccessMessage(selectedSuggestion.message);
164
- } catch (error) {
165
- setState((prev) => ({
166
- ...prev,
167
- error: `Failed to commit: ${error instanceof Error ? error.message : String(error)}`,
168
- }));
169
- }
170
- }
171
- };
172
-
173
- const handleTryAgain = () => {
174
- setState((prev) => ({ ...prev, isLoading: true, suggestions: [] }));
175
- fetchSuggestions(state.stagedFiles);
176
- };
177
-
178
- const handleCustomInput = () => {
179
- setIsCustomInputMode(true);
180
- setCustomInput("");
181
- };
182
-
183
- const handleCustomInputCancel = () => {
184
- setIsCustomInputMode(false);
185
- onExit("Custom input cancelled");
186
- exit();
187
- };
188
-
189
- const handleCustomInputSubmit = async () => {
190
- if (!customInput.trim()) {
191
- setIsCustomInputMode(false);
192
- return;
193
- }
194
-
195
- setIsCustomInputMode(false);
196
- setState((prev) => ({ ...prev, isLoading: true, suggestions: [] }));
197
- await fetchSuggestions(state.stagedFiles, customInput.trim());
198
- };
199
-
200
- // Error State
201
- if (state.error) {
202
- return (
203
- <Box
204
- borderStyle="single"
205
- borderColor="#ef4444"
206
- padding={2}
207
- marginBottom={1}
208
- >
209
- <Text color="#ef4444" bold>
210
- Error
211
- </Text>
212
- <Box marginTop={1}>
213
- <Text color="#e5e7eb">{state.error}</Text>
214
- </Box>
215
- </Box>
216
- );
217
- }
218
-
219
- // Success State
220
- if (successMessage) {
221
- return (
222
- <Box
223
- flexDirection="column"
224
- padding={2}
225
- justifyContent="center"
226
- alignItems="center"
227
- flexGrow={1}
228
- width="100%"
229
- height="100%"
230
- >
231
- <Box
232
- flexDirection="column"
233
- borderStyle="single"
234
- borderColor="#10b981"
235
- padding={2}
236
- width={60}
237
- >
238
- <Box marginBottom={1} justifyContent="center">
239
- <Text color="#10b981" bold>
240
- Commit Successful
241
- </Text>
242
- </Box>
243
- <Box marginBottom={1} justifyContent="center">
244
- <Text color="#e5e7eb">"{successMessage}"</Text>
245
- </Box>
246
- <Box justifyContent="center">
247
- <Text color="#10b981">
248
- Changes have been committed successfully!
249
- </Text>
250
- </Box>
251
- </Box>
252
- </Box>
253
- );
254
- }
255
-
256
- // Loading State
257
- if (state.stagedFiles.length === 0 && !state.error) {
258
- return (
259
- <Box flexDirection="column" padding={2} justifyContent="center">
260
- <Text bold color="#8b5cf6">
261
- Better-Commit
262
- </Text>
263
- <Box marginTop={1}>
264
- <Text color="#6b7280">Loading staged files...</Text>
265
- </Box>
266
- </Box>
267
- );
268
- }
269
-
270
- return (
271
- <Box flexDirection="column" padding={1}>
272
- {/* Header */}
273
- <Box marginBottom={1}>
274
- <Text bold color="#8b5cf6">
275
- Better-Commit
276
- </Text>
277
- <Text color="#6b7280"> • AI-Powered Commit Suggestions</Text>
278
- </Box>
279
-
280
- {/* Staged Files */}
281
- <StagedFiles files={state.stagedFiles} />
282
-
283
- {/* Commit Suggestions or Custom Input */}
284
- {isCustomInputMode ? (
285
- <CustomInputPrompt
286
- value={customInput}
287
- onChange={setCustomInput}
288
- onSubmit={handleCustomInputSubmit}
289
- onCancel={handleCustomInputCancel}
290
- />
291
- ) : (
292
- <CommitSuggestions
293
- suggestions={state.suggestions}
294
- selectedIndex={state.selectedIndex}
295
- onSelect={handleSelect}
296
- onCommit={handleCommit}
297
- onTryAgain={handleTryAgain}
298
- onCustomInput={handleCustomInput}
299
- isLoading={state.isLoading}
300
- isUsingFallback={isUsingFallback}
301
- />
302
- )}
303
-
304
- {/* Footer - hide when custom input is open */}
305
- {!isCustomInputMode && (
306
- <Box
307
- borderTop={true}
308
- borderStyle="single"
309
- borderColor="#374151"
310
- paddingTop={1}
311
- paddingLeft={1}
312
- paddingRight={1}
313
- >
314
- <Text color="#6b7280">
315
- <Text color="#60a5fa">↑↓</Text> navigate{" "}
316
- <Text color="#10b981">Enter</Text> select{" "}
317
- <Text color="#ef4444">Esc</Text> exit
318
- </Text>
319
- </Box>
320
- )}
321
- </Box>
322
- );
323
- };
324
-
325
- export const App = BetterCommitApp;
@@ -1,157 +0,0 @@
1
- import React, { useState, useEffect } from "react";
2
- import { Box, Text, useInput } from "ink";
3
- import { CommitSuggestion } from "../types";
4
-
5
- interface CommitSuggestionsProps {
6
- suggestions: CommitSuggestion[];
7
- selectedIndex: number;
8
- onSelect: (index: number) => void;
9
- onCommit: (index: number) => void;
10
- onTryAgain: () => void;
11
- onCustomInput: () => void;
12
- isLoading: boolean;
13
- isUsingFallback?: boolean;
14
- }
15
-
16
- export const CommitSuggestions: React.FC<CommitSuggestionsProps> = ({
17
- suggestions,
18
- selectedIndex,
19
- onSelect,
20
- onCommit,
21
- onTryAgain,
22
- onCustomInput,
23
- isLoading,
24
- isUsingFallback = false,
25
- }) => {
26
- const totalOptions = isUsingFallback
27
- ? suggestions.length
28
- : suggestions.length + 2;
29
- const [frame, setFrame] = useState(0);
30
-
31
- const spinnerFrames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
32
- // Alternative spinners:
33
- // const spinnerFrames = ['◐', '◓', '◑', '◒'];
34
-
35
- useEffect(() => {
36
- if (isLoading) {
37
- const timer = setInterval(() => {
38
- setFrame((prev) => (prev + 1) % spinnerFrames.length);
39
- }, 80);
40
- return () => clearInterval(timer);
41
- }
42
- }, [isLoading, spinnerFrames.length]);
43
-
44
- useInput((input, key) => {
45
- if (key.upArrow) {
46
- const newIndex = selectedIndex > 0 ? selectedIndex - 1 : totalOptions - 1;
47
- onSelect(newIndex);
48
- } else if (key.downArrow) {
49
- const newIndex = selectedIndex < totalOptions - 1 ? selectedIndex + 1 : 0;
50
- onSelect(newIndex);
51
- } else if (key.return) {
52
- if (selectedIndex < suggestions.length) {
53
- onCommit(selectedIndex);
54
- } else if (!isUsingFallback) {
55
- if (selectedIndex === suggestions.length) {
56
- onTryAgain();
57
- } else if (selectedIndex === suggestions.length + 1) {
58
- onCustomInput();
59
- }
60
- }
61
- }
62
- });
63
-
64
- if (isLoading) {
65
- return (
66
- <Box borderStyle="round" borderColor="#374151" paddingX={1}>
67
- <Text color="#6b7280">
68
- {spinnerFrames[frame]} Generating commit suggestions...
69
- </Text>
70
- </Box>
71
- );
72
- }
73
-
74
- if (suggestions.length === 0) {
75
- return (
76
- <Box borderStyle="round" borderColor="#ef4444" paddingX={1}>
77
- <Text color="#ef4444">No suggestions available</Text>
78
- </Box>
79
- );
80
- }
81
-
82
- return (
83
- <Box
84
- flexDirection="column"
85
- borderStyle="round"
86
- borderColor="#374151"
87
- paddingX={1}
88
- >
89
- <Box>
90
- <Text bold color="#8b5cf6">
91
- Commit Messages
92
- </Text>
93
- </Box>
94
-
95
- {suggestions.map((suggestion, index) => {
96
- const isSelected = index === selectedIndex;
97
- const displayMsg =
98
- suggestion.message.length > 65
99
- ? suggestion.message.substring(0, 62) + "..."
100
- : suggestion.message;
101
-
102
- return (
103
- <Box key={index} marginLeft={1}>
104
- <Text color={isSelected ? "#10b981" : "#6b7280"}>
105
- {isSelected ? "❯" : " "}
106
- </Text>
107
- <Text color={isSelected ? "#ffffff" : "#9ca3af"} bold={isSelected}>
108
- {" "}
109
- {displayMsg}
110
- </Text>
111
- </Box>
112
- );
113
- })}
114
-
115
- {!isUsingFallback && (
116
- <Box marginTop={1} marginLeft={1} flexDirection="row">
117
- <Box marginRight={3}>
118
- <Text
119
- color={
120
- selectedIndex === suggestions.length ? "#10b981" : "#6b7280"
121
- }
122
- >
123
- {selectedIndex === suggestions.length ? "❯" : " "}
124
- </Text>
125
- <Text
126
- color={
127
- selectedIndex === suggestions.length ? "#60a5fa" : "#6b7280"
128
- }
129
- bold={selectedIndex === suggestions.length}
130
- >
131
- {" "}
132
- ↻ Try again
133
- </Text>
134
- </Box>
135
- <Box>
136
- <Text
137
- color={
138
- selectedIndex === suggestions.length + 1 ? "#10b981" : "#6b7280"
139
- }
140
- >
141
- {selectedIndex === suggestions.length + 1 ? "❯" : " "}
142
- </Text>
143
- <Text
144
- color={
145
- selectedIndex === suggestions.length + 1 ? "#f59e0b" : "#6b7280"
146
- }
147
- bold={selectedIndex === suggestions.length + 1}
148
- >
149
- {" "}
150
- ✎ Custom input
151
- </Text>
152
- </Box>
153
- </Box>
154
- )}
155
- </Box>
156
- );
157
- };