@@ -4,64 +4,193 @@ import chalk from 'chalk';
44import { homedir } from 'os' ;
55import { join } from 'path' ;
66import { existsSync , mkdirSync , writeFileSync , readFileSync } from 'fs' ;
7+ import open from 'open' ;
78
89interface ConfigShape {
910 version ?: string ;
1011 setupCompleted ?: string ;
1112 features ?: any ;
1213 paths ?: any ;
1314 database ?: { mode ?: 'local' | 'hosted' ; url ?: string } ;
15+ auth ?: {
16+ apiKey ?: string ;
17+ apiUrl ?: string ;
18+ email ?: string ;
19+ } ;
20+ }
21+
22+ interface AuthResponse {
23+ success : boolean ;
24+ apiKey ?: string ;
25+ databaseUrl ?: string ;
26+ email ?: string ;
27+ error ?: string ;
1428}
1529
1630export function registerLoginCommand ( program : Command ) : void {
1731 program
1832 . command ( 'login' )
19- . description ( 'Login to hosted StackMemory (configure managed Postgres)' )
20- . option ( '--open' , 'Open hosted signup/login page before prompting' )
33+ . description ( 'Login to hosted StackMemory service' )
34+ . option ( '--api-url <url>' , 'Custom API URL' , 'https://api.stackmemory.ai' )
35+ . option ( '--email <email>' , 'Email address for login' )
36+ . option ( '--password <password>' , 'Password (not recommended in CLI)' )
2137 . action ( async ( options ) => {
2238 const cfgDir = join ( homedir ( ) , '.stackmemory' ) ;
2339 if ( ! existsSync ( cfgDir ) ) mkdirSync ( cfgDir , { recursive : true } ) ;
2440
25- if ( options . open ) {
26- try {
27- const signupUrl = 'https://stackmemory.ai/hosted' ;
28- const mod = await import ( 'open' ) ;
29- await mod . default ( signupUrl ) ;
30- } catch ( e ) {
31- console . log ( chalk . yellow ( 'Could not open browser automatically.' ) ) ;
32- }
33- }
41+ console . log ( chalk . cyan ( '🔐 StackMemory Hosted Service Login\n' ) ) ;
3442
35- const { databaseUrl } = await inquirer . prompt ( [
43+ // Prompt for credentials
44+ const credentials = await inquirer . prompt ( [
45+ {
46+ type : 'input' ,
47+ name : 'email' ,
48+ message : 'Email:' ,
49+ default : options . email ,
50+ validate : ( input : string ) => {
51+ const emailRegex = / ^ [ ^ \s @ ] + @ [ ^ \s @ ] + \. [ ^ \s @ ] + $ / ;
52+ return emailRegex . test ( input ) ? true : 'Please enter a valid email' ;
53+ } ,
54+ } ,
3655 {
3756 type : 'password' ,
38- name : 'databaseUrl' ,
39- message : 'Paste your hosted DATABASE_URL (postgres://...)' ,
40- validate : ( input : string ) =>
41- input . startsWith ( 'postgres://' ) || input . startsWith ( 'postgresql://' )
42- ? true
43- : 'Must start with postgres:// or postgresql://' ,
57+ name : 'password' ,
58+ message : 'Password:' ,
59+ default : options . password ,
60+ mask : '*' ,
61+ validate : ( input : string ) => input . length >= 6 ? true : 'Password must be at least 6 characters' ,
4462 } ,
4563 ] ) ;
4664
47- // Merge into config.json
48- const cfgPath = join ( cfgDir , 'config.json' ) ;
49- let cfg : ConfigShape = { } ;
50- try {
51- if ( existsSync ( cfgPath ) ) cfg = JSON . parse ( readFileSync ( cfgPath , 'utf-8' ) ) ;
52- } catch { }
53- cfg . database = { ...( cfg . database || { } ) , mode : 'hosted' , url : databaseUrl } ;
54- writeFileSync ( cfgPath , JSON . stringify ( cfg , null , 2 ) ) ;
55- console . log ( chalk . green ( '✓ Hosted database configured in ~/.stackmemory/config.json' ) ) ;
65+ console . log ( chalk . gray ( '\nAuthenticating with StackMemory API...' ) ) ;
5666
57- // Save env helper
5867 try {
59- const envFile = join ( cfgDir , 'railway.env' ) ;
60- writeFileSync ( envFile , `# StackMemory hosted DB\nDATABASE_URL=${ databaseUrl } \n` ) ;
61- console . log ( chalk . green ( '✓ Saved DATABASE_URL to ~/.stackmemory/railway.env' ) ) ;
62- } catch { }
68+ // Authenticate with the hosted API
69+ const apiUrl = options . apiUrl || process . env . STACKMEMORY_API_URL || 'https://api.stackmemory.ai' ;
70+ const response = await fetch ( `${ apiUrl } /auth/login` , {
71+ method : 'POST' ,
72+ headers : {
73+ 'Content-Type' : 'application/json' ,
74+ 'User-Agent' : 'StackMemory-CLI/0.3.19' ,
75+ } ,
76+ body : JSON . stringify ( {
77+ email : credentials . email ,
78+ password : credentials . password ,
79+ } ) ,
80+ } ) ;
6381
64- console . log ( chalk . gray ( 'Tip: export DATABASE_URL before starting the server.' ) ) ;
65- } ) ;
66- }
82+ const data : AuthResponse = await response . json ( ) ;
83+
84+ if ( ! response . ok || ! data . success ) {
85+ if ( response . status === 404 ) {
86+ // Fallback to Railway server if hosted API not available
87+ console . log ( chalk . yellow ( '\n⚠️ Hosted API not available. Would you like to:' ) ) ;
88+ const { choice } = await inquirer . prompt ( [
89+ {
90+ type : 'list' ,
91+ name : 'choice' ,
92+ message : 'Select an option:' ,
93+ choices : [
94+ { name : 'Open signup page in browser' , value : 'signup' } ,
95+ { name : 'Configure database URL manually' , value : 'manual' } ,
96+ { name : 'Use local database' , value : 'local' } ,
97+ { name : 'Cancel' , value : 'cancel' } ,
98+ ] ,
99+ } ,
100+ ] ) ;
101+
102+ if ( choice === 'signup' ) {
103+ await open ( 'https://stackmemory.ai/signup' ) ;
104+ console . log ( chalk . cyan ( 'Opening signup page in browser...' ) ) ;
105+ return ;
106+ } else if ( choice === 'manual' ) {
107+ const { databaseUrl } = await inquirer . prompt ( [
108+ {
109+ type : 'password' ,
110+ name : 'databaseUrl' ,
111+ message : 'Enter your DATABASE_URL (postgres://...):' ,
112+ validate : ( input : string ) =>
113+ input . startsWith ( 'postgres://' ) || input . startsWith ( 'postgresql://' )
114+ ? true
115+ : 'Must start with postgres:// or postgresql://' ,
116+ } ,
117+ ] ) ;
67118
119+ // Save manual configuration
120+ const cfgPath = join ( cfgDir , 'config.json' ) ;
121+ let cfg : ConfigShape = { } ;
122+ try {
123+ if ( existsSync ( cfgPath ) ) cfg = JSON . parse ( readFileSync ( cfgPath , 'utf-8' ) ) ;
124+ } catch { }
125+
126+ cfg . database = { mode : 'hosted' , url : databaseUrl } ;
127+ cfg . auth = { email : credentials . email } ;
128+
129+ writeFileSync ( cfgPath , JSON . stringify ( cfg , null , 2 ) ) ;
130+ console . log ( chalk . green ( '✓ Database configured successfully' ) ) ;
131+ return ;
132+ } else if ( choice === 'local' ) {
133+ const cfgPath = join ( cfgDir , 'config.json' ) ;
134+ let cfg : ConfigShape = { } ;
135+ try {
136+ if ( existsSync ( cfgPath ) ) cfg = JSON . parse ( readFileSync ( cfgPath , 'utf-8' ) ) ;
137+ } catch { }
138+
139+ cfg . database = { mode : 'local' } ;
140+ writeFileSync ( cfgPath , JSON . stringify ( cfg , null , 2 ) ) ;
141+ console . log ( chalk . green ( '✓ Switched to local database mode' ) ) ;
142+ return ;
143+ } else {
144+ console . log ( chalk . gray ( 'Login cancelled' ) ) ;
145+ return ;
146+ }
147+ }
148+
149+ throw new Error ( data . error || 'Authentication failed' ) ;
150+ }
151+
152+ // Save configuration
153+ const cfgPath = join ( cfgDir , 'config.json' ) ;
154+ let cfg : ConfigShape = { } ;
155+ try {
156+ if ( existsSync ( cfgPath ) ) cfg = JSON . parse ( readFileSync ( cfgPath , 'utf-8' ) ) ;
157+ } catch { }
158+
159+ cfg . auth = {
160+ apiKey : data . apiKey ,
161+ apiUrl : apiUrl ,
162+ email : credentials . email ,
163+ } ;
164+
165+ if ( data . databaseUrl ) {
166+ cfg . database = {
167+ mode : 'hosted' ,
168+ url : data . databaseUrl ,
169+ } ;
170+ }
171+
172+ writeFileSync ( cfgPath , JSON . stringify ( cfg , null , 2 ) ) ;
173+
174+ // Save environment variables
175+ const envFile = join ( cfgDir , 'stackmemory.env' ) ;
176+ const envContent = `# StackMemory Authentication
177+ STACKMEMORY_API_KEY=${ data . apiKey }
178+ STACKMEMORY_API_URL=${ apiUrl }
179+ ${ data . databaseUrl ? `DATABASE_URL=${ data . databaseUrl } ` : '' }
180+ ` ;
181+ writeFileSync ( envFile , envContent ) ;
182+
183+ console . log ( chalk . green ( '\n✅ Successfully logged in to StackMemory' ) ) ;
184+ console . log ( chalk . green ( `✓ Configuration saved to ~/.stackmemory/config.json` ) ) ;
185+ console . log ( chalk . gray ( '\nYou can now use:' ) ) ;
186+ console . log ( chalk . cyan ( ' stackmemory sync ' ) + chalk . gray ( '- Sync your context to the cloud' ) ) ;
187+ console . log ( chalk . cyan ( ' stackmemory db status' ) + chalk . gray ( '- Check database connection' ) ) ;
188+ console . log ( chalk . cyan ( ' stackmemory context ' ) + chalk . gray ( '- Manage your contexts' ) ) ;
189+
190+ } catch ( error : any ) {
191+ console . error ( chalk . red ( '\n❌ Login failed:' ) , error . message ) ;
192+ console . log ( chalk . yellow ( '\nTip: Visit https://stackmemory.ai/signup to create an account' ) ) ;
193+ process . exit ( 1 ) ;
194+ }
195+ } ) ;
196+ }
0 commit comments