@@ -12,7 +12,6 @@ import { logger } from '../utils/logger.js';
1212async function readSecretInput ( rl : readline . Interface , prompt : string ) : Promise < string > {
1313 const rlAny = rl as any ;
1414 const originalWrite = rlAny . _writeToOutput ;
15- // Suppress character echoing: only allow the initial prompt to be written
1615 let promptWritten = false ;
1716 rlAny . _writeToOutput = function _writeToOutput ( str : string ) {
1817 if ( ! promptWritten ) {
@@ -38,6 +37,7 @@ export async function initCommand() {
3837 console . log ( '🚀 Welcome to AI-Git-Terminal Setup\n' ) ;
3938
4039 try {
40+ // --- Step 1: Read API Key ---
4141 let apiKey = '' ;
4242 while ( ! apiKey ) {
4343 const apiKeyInput = await readSecretInput ( rl , '🔑 Enter your Gemini API Key: ' ) ;
@@ -47,56 +47,63 @@ export async function initCommand() {
4747 }
4848 }
4949
50+ // --- Step 2: Read model name ---
5051 const modelInput = await rl . question ( '🤖 Enter model name (default: gemini-1.5-flash): ' ) ;
5152 const model = modelInput . trim ( ) || 'gemini-1.5-flash' ;
5253
54+ // --- Step 3: Build config object ---
5355 const newConfig : Config = {
54- ai : {
55- provider : 'gemini' ,
56- apiKey,
57- model : model ,
58- } ,
59- git : {
60- autoStage : false ,
61- } ,
62- ui : {
63- theme : 'dark' ,
64- showIcons : true ,
65- } ,
56+ ai : { provider : 'gemini' , apiKey, model } ,
57+ git : { autoStage : false } ,
58+ ui : { theme : 'dark' , showIcons : true } ,
6659 } ;
6760
68- // Validate with Zod and persist with restricted permissions (mode 0o600)
61+ // Validate with Zod
6962 ConfigSchema . parse ( newConfig ) ;
7063
7164 const configPath = path . join ( os . homedir ( ) , '.aigitrc' ) ;
7265
73- if ( fs . existsSync ( configPath ) ) {
74- const overwriteChoice = ( await rl . question (
75- '⚠️ Existing config found. Choose [o]verwrite, [b]ackup then replace, or [c]ancel: '
76- ) ) . trim ( ) . toLowerCase ( ) ;
66+ // --- Step 4: Attempt atomic creation ---
67+ try {
68+ const fd = fs . openSync ( configPath , fs . O_CREAT | fs . O_EXCL | fs . O_RDWR , 0o600 ) ;
69+ fs . writeFileSync ( fd , JSON . stringify ( newConfig , null , 2 ) ) ;
70+ fs . closeSync ( fd ) ;
71+ console . log ( `\n✅ Configuration saved to ${ configPath } ` ) ;
72+ console . log ( 'Try running: ai-git commit' ) ;
73+ return ;
74+ } catch ( err : any ) {
75+ if ( err . code !== 'EEXIST' ) throw err ;
76+ // File already exists, proceed to backup/overwrite prompt
77+ }
7778
78- if ( overwriteChoice === 'b' || overwriteChoice === 'backup' ) {
79- const timestamp = new Date ( ) . toISOString ( ) . replace ( / [: .] / g, '-' ) ;
80- const backupPath = `${ configPath } .bak-${ timestamp } ` ;
81- fs . renameSync ( configPath , backupPath ) ;
82- console . log ( `📦 Existing config backed up to ${ backupPath } ` ) ;
83- } else if ( overwriteChoice === 'o' || overwriteChoice === 'overwrite' ) {
84- console . log ( '📝 Overwriting existing config file.' ) ;
85- } else {
86- console . log ( '🚫 Initialization canceled. Existing config left unchanged.' ) ;
87- return ;
88- }
79+ // --- Step 5: Handle existing file ---
80+ const overwriteChoice = ( await rl . question (
81+ '⚠️ Existing config found. Choose [o]verwrite, [b]ackup then replace, or [c]ancel: '
82+ ) ) . trim ( ) . toLowerCase ( ) ;
83+
84+ if ( overwriteChoice === 'b' || overwriteChoice === 'backup' ) {
85+ const timestamp = new Date ( ) . toISOString ( ) . replace ( / [: .] / g, '-' ) ;
86+ const backupPath = `${ configPath } .bak-${ timestamp } ` ;
87+ fs . renameSync ( configPath , backupPath ) ;
88+ console . log ( `📦 Existing config backed up to ${ backupPath } ` ) ;
89+ // Now write new config
90+ fs . writeFileSync ( configPath , JSON . stringify ( newConfig , null , 2 ) , { mode : 0o600 } ) ;
91+ } else if ( overwriteChoice === 'o' || overwriteChoice === 'overwrite' ) {
92+ fs . writeFileSync ( configPath , JSON . stringify ( newConfig , null , 2 ) , { mode : 0o600 } ) ;
93+ console . log ( '📝 Overwriting existing config file.' ) ;
94+ } else {
95+ console . log ( '🚫 Initialization canceled. Existing config left unchanged.' ) ;
96+ return ;
8997 }
9098
91- fs . writeFileSync ( configPath , JSON . stringify ( newConfig , null , 2 ) , { mode : 0o600 } ) ;
9299 fs . chmodSync ( configPath , 0o600 ) ;
93-
94100 console . log ( `\n✅ Configuration saved to ${ configPath } ` ) ;
95101 console . log ( 'Try running: ai-git commit' ) ;
102+
96103 } catch ( error ) {
97104 logger . error ( 'Failed to save configuration: ' + ( error instanceof Error ? error . message : String ( error ) ) ) ;
98105 console . error ( '\n❌ Invalid input or failed to write config file.' ) ;
99106 } finally {
100107 rl . close ( ) ;
101108 }
102- }
109+ }
0 commit comments