@@ -75,14 +75,19 @@ def _praison_home() -> Path:
7575 return Path (override ) if override else Path .home () / ".praisonai"
7676
7777
78- def _save_env_vars (env_vars : Dict [str , str ]) -> Optional [Path ]:
78+ def _save_env_vars (env_vars : Dict [str , Optional [ str ] ]) -> Optional [Path ]:
7979 """Atomically merge ``env_vars`` into ``~/.praisonai/.env`` (chmod 600).
8080
81- Empty/None values are skipped. Existing keys not in ``env_vars`` are
82- preserved. Written with a temp-then-rename to avoid partial writes.
81+ - Non-empty string values are written (updating existing keys).
82+ - ``None`` values **remove** the key from the file (supports "clear" updates
83+ so the user can drop an allowlist or home-channel).
84+ - Empty-string values are skipped.
85+ Existing keys not referenced in ``env_vars`` are preserved. Written with a
86+ temp-then-rename to avoid partial writes.
8387 """
84- filtered = {k : v for k , v in env_vars .items () if v }
85- if not filtered :
88+ updates = {k : v for k , v in env_vars .items () if v }
89+ deletes = {k for k , v in env_vars .items () if v is None }
90+ if not updates and not deletes :
8691 return None
8792
8893 env_file = _praison_home () / ".env"
@@ -99,7 +104,9 @@ def _save_env_vars(env_vars: Dict[str, str]) -> Optional[Path]:
99104 if s and not s .startswith ("#" ) and "=" in s :
100105 k , v = s .split ("=" , 1 )
101106 existing [k .strip ()] = v .strip ()
102- existing .update (filtered )
107+ existing .update (updates )
108+ for k in deletes :
109+ existing .pop (k , None )
103110
104111 body = ["# PraisonAI configuration" ,
105112 "# Managed by praisonai onboard / setup" , "" ]
@@ -204,16 +211,45 @@ def run(self) -> None:
204211 return
205212
206213 # Step 2: Token configuration (password-hidden; persisted to ~/.praisonai/.env)
214+ # Every setting can be updated even if already present — existing values are
215+ # shown as masked defaults; press Enter to keep, or type a new value to
216+ # overwrite (mirrors hermes-agent setup behaviour).
207217 console .print ("\n [bold]Step 2: Configure tokens[/bold]\n " )
208- env_to_save : Dict [str , str ] = {}
218+ console .print (
219+ " [dim]Press Enter to keep the existing value, or type a new one to update.[/dim]\n "
220+ )
221+ env_to_save : Dict [str , Optional [str ]] = {}
222+
223+ def _mask (value : str ) -> str :
224+ if not value :
225+ return ""
226+ if len (value ) <= 6 :
227+ return "*" * len (value )
228+ return f"{ value [:2 ]} { '*' * (len (value ) - 4 )} { value [- 2 :]} "
229+
209230 for plat in self .selected_platforms :
210231 info = PLATFORMS [plat ]
211232 env_var = info ["token_env" ]
212233 existing = os .environ .get (env_var , "" )
213234
214235 if existing :
215- console .print (f" [green]✓[/green] { info ['name' ]} : { env_var } already set" )
216- self .tokens [plat ] = existing
236+ console .print (
237+ f" [green]✓[/green] { info ['name' ]} : { env_var } = [cyan]{ _mask (existing )} [/cyan]"
238+ )
239+ console .print (f" [dim]{ info ['token_help' ]} [/dim]" )
240+ new_token = Prompt .ask (
241+ f" Update { info ['name' ]} token? (Enter = keep current)" ,
242+ password = True ,
243+ default = "" ,
244+ show_default = False ,
245+ )
246+ if new_token :
247+ self .tokens [plat ] = new_token
248+ os .environ [env_var ] = new_token
249+ env_to_save [env_var ] = new_token
250+ console .print (" [green]✓[/green] Token updated" )
251+ else :
252+ self .tokens [plat ] = existing
217253 else :
218254 console .print (f" [dim]{ info ['token_help' ]} [/dim]" )
219255 token = Prompt .ask (
@@ -228,11 +264,28 @@ def run(self) -> None:
228264 env_to_save [env_var ] = token
229265 console .print (" [green]✓[/green] Token captured" )
230266 else :
231- console .print (f" [yellow]⚠[/yellow] No token — you'll need to set { env_var } before starting" )
267+ console .print (
268+ f" [yellow]⚠[/yellow] No token — you'll need to set { env_var } before starting"
269+ )
232270
233271 # Extra env vars (e.g., Slack app token) — also hidden by default
234272 for extra_env , extra_desc in info .get ("extra_env" , {}).items ():
235- if not os .environ .get (extra_env ):
273+ existing_extra = os .environ .get (extra_env , "" )
274+ if existing_extra :
275+ console .print (
276+ f" [green]✓[/green] { extra_env } = [cyan]{ _mask (existing_extra )} [/cyan]"
277+ )
278+ new_extra = Prompt .ask (
279+ f" Update { extra_desc } ? (Enter = keep current)" ,
280+ password = True ,
281+ default = "" ,
282+ show_default = False ,
283+ )
284+ if new_extra :
285+ os .environ [extra_env ] = new_extra
286+ env_to_save [extra_env ] = new_extra
287+ console .print (f" [green]✓[/green] { extra_env } updated" )
288+ else :
236289 extra_val = Prompt .ask (
237290 f" { extra_desc } ({ extra_env } )" ,
238291 password = True ,
@@ -249,38 +302,77 @@ def run(self) -> None:
249302 if allowed_env :
250303 existing_allow = os .environ .get (allowed_env , "" ).strip ()
251304 if existing_allow :
252- console .print (f" [green]✓[/green] { allowed_env } already set" )
305+ console .print (
306+ f" [green]✓[/green] { allowed_env } = [cyan]{ existing_allow } [/cyan]"
307+ )
308+ console .print (
309+ f" [dim]{ info .get ('user_id_help' , 'Comma-separated user IDs' )} [/dim]"
310+ )
311+ new_allow = Prompt .ask (
312+ f" Update allowed users for { info ['name' ]} ? (Enter = keep, 'clear' = remove)" ,
313+ default = "" ,
314+ show_default = False ,
315+ ).strip ()
316+ if new_allow .lower () == "clear" :
317+ os .environ .pop (allowed_env , None )
318+ env_to_save [allowed_env ] = None
319+ console .print (
320+ " [yellow]✓ Allowlist cleared — open access restored[/yellow]"
321+ )
322+ elif new_allow :
323+ new_allow = new_allow .replace (" " , "" )
324+ os .environ [allowed_env ] = new_allow
325+ env_to_save [allowed_env ] = new_allow
326+ console .print (" [green]✓[/green] Allowlist updated" )
253327 else :
254- console .print (f" [bold]🔒 Security — restrict who can use your { info ['name' ]} bot[/bold]" )
255- console .print (f" [dim]{ info .get ('user_id_help' , 'Enter comma-separated user IDs' )} [/dim]" )
328+ console .print (
329+ f" [bold]🔒 Security — restrict who can use your { info ['name' ]} bot[/bold]"
330+ )
331+ console .print (
332+ f" [dim]{ info .get ('user_id_help' , 'Enter comma-separated user IDs' )} [/dim]"
333+ )
256334 allow_val = Prompt .ask (
257- f " Allowed user IDs (comma-separated, empty = open access)" ,
335+ " Allowed user IDs (comma-separated, empty = open access)" ,
258336 default = "" ,
259337 show_default = False ,
260338 )
261339 allow_val = allow_val .replace (" " , "" )
262340 if allow_val :
263341 os .environ [allowed_env ] = allow_val
264342 env_to_save [allowed_env ] = allow_val
265- console .print (f" [green]✓[/green] Allowlist saved — only listed users can talk to the bot" )
343+ console .print (
344+ " [green]✓[/green] Allowlist saved — only listed users can talk to the bot"
345+ )
266346 else :
267347 console .print (
268- f " [yellow]⚠ Warning:[/yellow] no allowlist set — anyone who finds your bot can use it."
348+ " [yellow]⚠ Warning:[/yellow] no allowlist set — anyone who finds your bot can use it."
269349 )
270350
271- # Home channel (default = first allowed user)
272- if home_env and not os .environ .get (home_env ):
351+ # Home channel (default = first allowed user) — also updatable
352+ if home_env :
353+ existing_home = os .environ .get (home_env , "" ).strip ()
273354 first_allowed = ""
274355 saved_allow = env_to_save .get (allowed_env , existing_allow )
275- if saved_allow :
356+ if isinstance ( saved_allow , str ) and saved_allow :
276357 first_allowed = saved_allow .split ("," )[0 ].strip ()
358+ default_home = existing_home or first_allowed
359+ if existing_home :
360+ console .print (
361+ f" [green]✓[/green] { home_env } = [cyan]{ existing_home } [/cyan]"
362+ )
363+ prompt_label = (
364+ f" Update home channel for { info ['name' ]} ? (Enter = keep current)"
365+ )
366+ else :
367+ prompt_label = (
368+ f" Home channel / user ID for proactive messages ({ home_env } )"
369+ )
277370 home_val = Prompt .ask (
278- f" Home channel / user ID for proactive messages ({ home_env } )" ,
279- default = first_allowed ,
280- show_default = bool (first_allowed ),
281- )
282- home_val = home_val .strip ()
283- if home_val :
371+ prompt_label ,
372+ default = default_home ,
373+ show_default = bool (default_home ),
374+ ).strip ()
375+ if home_val and home_val != existing_home :
284376 os .environ [home_env ] = home_val
285377 env_to_save [home_env ] = home_val
286378 console .print (f" [green]✓[/green] Home channel set to { home_val } " )
0 commit comments