@@ -124,20 +124,66 @@ async fn main() -> Result<()> {
124124}
125125
126126async fn add_account ( service : & str , account : & str , scopes : Option < & str > ) -> Result < ( ) > {
127- println ! ( "Adding account {}/{}" , service, account) ;
127+ use sigilforge_core:: { Account , AccountId , AccountStore , ServiceId } ;
128+
129+ let store = AccountStore :: load ( ) ?;
130+
131+ let service_id = ServiceId :: new ( service) ;
132+ let account_id = AccountId :: new ( account) ;
133+
134+ let scope_list = if let Some ( scopes) = scopes {
135+ scopes. split ( ',' ) . map ( |s| s. trim ( ) . to_string ( ) ) . collect ( )
136+ } else {
137+ Vec :: new ( )
138+ } ;
139+
140+ let new_account = Account :: new ( service_id. clone ( ) , account_id. clone ( ) , scope_list) ;
141+
142+ store. add_account ( new_account) ?;
143+
144+ println ! ( "Account {}/{} added successfully" , service, account) ;
128145 if let Some ( scopes) = scopes {
129146 println ! ( " Scopes: {}" , scopes) ;
130147 }
131- println ! ( " [stub] Would start OAuth flow here" ) ;
148+ println ! ( " Storage path: {:?}" , store. path( ) ) ;
149+ println ! ( " [stub] Would start OAuth flow to obtain tokens here" ) ;
150+
132151 Ok ( ( ) )
133152}
134153
135154async fn list_accounts ( service_filter : Option < & str > ) -> Result < ( ) > {
155+ use sigilforge_core:: { AccountStore , ServiceId } ;
156+
157+ let store = AccountStore :: load ( ) ?;
158+
159+ let filter = service_filter. map ( ServiceId :: new) ;
160+ let accounts = store. list_accounts ( filter. as_ref ( ) ) ?;
161+
162+ if accounts. is_empty ( ) {
163+ println ! ( "No accounts configured" ) ;
164+ if let Some ( service) = service_filter {
165+ println ! ( " (filtered by service: {})" , service) ;
166+ }
167+ return Ok ( ( ) ) ;
168+ }
169+
136170 println ! ( "Configured accounts:" ) ;
137171 if let Some ( service) = service_filter {
138172 println ! ( " (filtered by service: {})" , service) ;
139173 }
140- println ! ( " [stub] No accounts configured yet" ) ;
174+ println ! ( ) ;
175+
176+ for account in accounts {
177+ println ! ( " {}/{}" , account. service, account. id) ;
178+ if !account. scopes . is_empty ( ) {
179+ println ! ( " Scopes: {}" , account. scopes. join( ", " ) ) ;
180+ }
181+ println ! ( " Created: {}" , account. created_at) ;
182+ if let Some ( last_used) = account. last_used {
183+ println ! ( " Last used: {}" , last_used) ;
184+ }
185+ }
186+
141187 Ok ( ( ) )
142188}
143189
@@ -156,11 +202,67 @@ async fn get_token(service: &str, account: &str, format: &str) -> Result<()> {
156202}
157203
158204async fn remove_account ( service : & str , account : & str , force : bool ) -> Result < ( ) > {
205+ use sigilforge_core:: { AccountStore , CredentialType , MemoryStore , SecretStore , ServiceId , AccountId } ;
206+ use std:: io:: { self , Write } ;
207+
208+ let store = AccountStore :: load ( ) ?;
209+ let service_id = ServiceId :: new ( service) ;
210+ let account_id = AccountId :: new ( account) ;
211+
212+ // Verify account exists before prompting
213+ let account_entry = store. get_account ( & service_id, & account_id) ?;
214+ if account_entry. is_none ( ) {
215+ eprintln ! ( "Error: Account {}/{} not found" , service, account) ;
216+ std:: process:: exit ( 1 ) ;
217+ }
218+
219+ // Prompt for confirmation unless --force is used
159220 if !force {
160- println ! ( "Remove account {}/{}? [y/N]" , service, account) ;
161- println ! ( "[stub] Would prompt for confirmation" ) ;
221+ print ! ( "Remove account {}/{}? [y/N] " , service, account) ;
222+ io:: stdout ( ) . flush ( ) ?;
223+
224+ let mut response = String :: new ( ) ;
225+ io:: stdin ( ) . read_line ( & mut response) ?;
226+
227+ let confirmed = response. trim ( ) . eq_ignore_ascii_case ( "y" )
228+ || response. trim ( ) . eq_ignore_ascii_case ( "yes" ) ;
229+
230+ if !confirmed {
231+ println ! ( "Cancelled" ) ;
232+ return Ok ( ( ) ) ;
233+ }
234+ }
235+
236+ // Remove account from store
237+ store. remove_account ( & service_id, & account_id) ?;
238+
239+ // Delete associated secrets from secret store
240+ // For now, we use MemoryStore as a placeholder since we don't have
241+ // a global secret store instance yet. In a production implementation,
242+ // this would use the actual secret store backend.
243+ //
244+ // The secret keys follow the pattern: sigilforge/{service}/{account}/{type}
245+ let secret_store = MemoryStore :: new ( ) ;
246+
247+ // Common credential types to clean up
248+ let credential_types = [
249+ CredentialType :: AccessToken ,
250+ CredentialType :: RefreshToken ,
251+ CredentialType :: TokenExpiry ,
252+ CredentialType :: ApiKey ,
253+ CredentialType :: ClientId ,
254+ CredentialType :: ClientSecret ,
255+ ] ;
256+
257+ for cred_type in & credential_types {
258+ let key = format ! ( "sigilforge/{}/{}/{}" , service, account, cred_type) ;
259+ // Ignore errors - the key might not exist
260+ let _ = secret_store. delete ( & key) . await ;
162261 }
163- println ! ( "[stub] Account removed" ) ;
262+
263+ println ! ( "Account {}/{} removed successfully" , service, account) ;
264+ println ! ( " [Note: Associated secrets have been deleted from the secret store]" ) ;
265+
164266 Ok ( ( ) )
165267}
166268
0 commit comments