basic-password-manager 0.4.3__tar.gz → 0.4.5__tar.gz

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.
Files changed (20) hide show
  1. {basic_password_manager-0.4.3 → basic_password_manager-0.4.5}/PKG-INFO +5 -3
  2. {basic_password_manager-0.4.3 → basic_password_manager-0.4.5}/README.md +4 -2
  3. {basic_password_manager-0.4.3 → basic_password_manager-0.4.5}/basic_password_manager.egg-info/PKG-INFO +5 -3
  4. {basic_password_manager-0.4.3 → basic_password_manager-0.4.5}/pw_manager/logo.py +11 -2
  5. {basic_password_manager-0.4.3 → basic_password_manager-0.4.5}/pw_manager/mobile.py +4 -0
  6. {basic_password_manager-0.4.3 → basic_password_manager-0.4.5}/pw_manager/tui.py +49 -3
  7. {basic_password_manager-0.4.3 → basic_password_manager-0.4.5}/pyproject.toml +1 -1
  8. {basic_password_manager-0.4.3 → basic_password_manager-0.4.5}/LICENSE +0 -0
  9. {basic_password_manager-0.4.3 → basic_password_manager-0.4.5}/basic_password_manager.egg-info/SOURCES.txt +0 -0
  10. {basic_password_manager-0.4.3 → basic_password_manager-0.4.5}/basic_password_manager.egg-info/dependency_links.txt +0 -0
  11. {basic_password_manager-0.4.3 → basic_password_manager-0.4.5}/basic_password_manager.egg-info/entry_points.txt +0 -0
  12. {basic_password_manager-0.4.3 → basic_password_manager-0.4.5}/basic_password_manager.egg-info/requires.txt +0 -0
  13. {basic_password_manager-0.4.3 → basic_password_manager-0.4.5}/basic_password_manager.egg-info/top_level.txt +0 -0
  14. {basic_password_manager-0.4.3 → basic_password_manager-0.4.5}/pw_manager/__init__.py +0 -0
  15. {basic_password_manager-0.4.3 → basic_password_manager-0.4.5}/pw_manager/cli.py +0 -0
  16. {basic_password_manager-0.4.3 → basic_password_manager-0.4.5}/pw_manager/crypto.py +0 -0
  17. {basic_password_manager-0.4.3 → basic_password_manager-0.4.5}/pw_manager/vault.py +0 -0
  18. {basic_password_manager-0.4.3 → basic_password_manager-0.4.5}/pw_manager/web/argon2.umd.min.js +0 -0
  19. {basic_password_manager-0.4.3 → basic_password_manager-0.4.5}/pw_manager/web/page.html +0 -0
  20. {basic_password_manager-0.4.3 → basic_password_manager-0.4.5}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: basic-password-manager
3
- Version: 0.4.3
3
+ Version: 0.4.5
4
4
  Summary: The Paladin — a local encrypted password manager that lives in your terminal
5
5
  License-Expression: MIT
6
6
  Project-URL: Repository, https://github.com/Daisentaur/password-manager
@@ -74,8 +74,10 @@ changing the master password all live there, along with others.
74
74
 
75
75
  ![the command palette](assets/palette.gif)
76
76
 
77
- And themes. `t` opens the theme picker; the bundled ones — muted-slate (default),
78
- dawn, matrix — are lifted with love from
77
+ And themes. `t` opens the theme picker — your current one is marked and
78
+ preselected, and arrowing through them previews each live before you commit
79
+ (Enter keeps it, Esc puts back what you had). The bundled ones — muted-slate
80
+ (default), dawn, matrix — are lifted with love from
79
81
  [tuxedo](https://github.com/webstonehq/tuxedo)'s palettes, and all of
80
82
  Textual's built-ins are in there too. Your pick sticks across runs. Even the
81
83
  knight on the unlock screen dresses to match your theme, try it ;)
@@ -58,8 +58,10 @@ changing the master password all live there, along with others.
58
58
 
59
59
  ![the command palette](assets/palette.gif)
60
60
 
61
- And themes. `t` opens the theme picker; the bundled ones — muted-slate (default),
62
- dawn, matrix — are lifted with love from
61
+ And themes. `t` opens the theme picker — your current one is marked and
62
+ preselected, and arrowing through them previews each live before you commit
63
+ (Enter keeps it, Esc puts back what you had). The bundled ones — muted-slate
64
+ (default), dawn, matrix — are lifted with love from
63
65
  [tuxedo](https://github.com/webstonehq/tuxedo)'s palettes, and all of
64
66
  Textual's built-ins are in there too. Your pick sticks across runs. Even the
65
67
  knight on the unlock screen dresses to match your theme, try it ;)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: basic-password-manager
3
- Version: 0.4.3
3
+ Version: 0.4.5
4
4
  Summary: The Paladin — a local encrypted password manager that lives in your terminal
5
5
  License-Expression: MIT
6
6
  Project-URL: Repository, https://github.com/Daisentaur/password-manager
@@ -74,8 +74,10 @@ changing the master password all live there, along with others.
74
74
 
75
75
  ![the command palette](assets/palette.gif)
76
76
 
77
- And themes. `t` opens the theme picker; the bundled ones — muted-slate (default),
78
- dawn, matrix — are lifted with love from
77
+ And themes. `t` opens the theme picker — your current one is marked and
78
+ preselected, and arrowing through them previews each live before you commit
79
+ (Enter keeps it, Esc puts back what you had). The bundled ones — muted-slate
80
+ (default), dawn, matrix — are lifted with love from
79
81
  [tuxedo](https://github.com/webstonehq/tuxedo)'s palettes, and all of
80
82
  Textual's built-ins are in there too. Your pick sticks across runs. Even the
81
83
  knight on the unlock screen dresses to match your theme, try it ;)
@@ -75,9 +75,18 @@ def _tinted(pixel, tint, accent):
75
75
  return tuple(int(c * lum) for c in base)
76
76
 
77
77
 
78
- def _hex_rgb(color: str) -> tuple:
78
+ def _hex_rgb(color: str | None) -> tuple | None:
79
+ """Parse #rrggbb to (r,g,b). Returns None for anything that isn't hex —
80
+ ANSI themes use names like 'ansi_blue', and those just mean 'no tint'."""
81
+ if not color:
82
+ return None
79
83
  color = color.lstrip("#")
80
- return tuple(int(color[i : i + 2], 16) for i in (0, 2, 4))
84
+ if len(color) != 6:
85
+ return None
86
+ try:
87
+ return tuple(int(color[i : i + 2], 16) for i in (0, 2, 4))
88
+ except ValueError:
89
+ return None
81
90
 
82
91
 
83
92
  def rich_text(tint: str | None = None, accent: str | None = None) -> Text:
@@ -130,6 +130,10 @@ def _theme_colors() -> dict:
130
130
  registry = {t.name: t for t in TUXEDO_THEMES}
131
131
  registry.update(BUILTIN_THEMES)
132
132
  cs = registry[name].to_color_system().generate()
133
+ # ANSI themes resolve to names like 'ansi_blue', not hex — the browser
134
+ # can't use those and the knight can't tint from them, so fall back
135
+ if not str(cs.get("background", "")).startswith("#"):
136
+ return default
133
137
  return {**default, **cs}
134
138
  except (OSError, KeyError, ImportError):
135
139
  return default
@@ -17,7 +17,8 @@ from textual.command import DiscoveryHit, Hit, Hits, Provider
17
17
  from textual.containers import Vertical
18
18
  from textual.screen import ModalScreen, Screen
19
19
  from textual.theme import Theme
20
- from textual.widgets import DataTable, Footer, Input, Label, Static
20
+ from textual.widgets import DataTable, Footer, Input, Label, OptionList, Static
21
+ from textual.widgets.option_list import Option
21
22
 
22
23
  from . import logo, vault
23
24
  from .cli import VAULT_PATH, generate, import_csv, to_clipboard
@@ -102,6 +103,8 @@ class UnlockScreen(Screen):
102
103
  self.query_one("#master", Input).clear()
103
104
  return
104
105
  self.app.master = event.value
106
+ # stop repainting the (now-hidden) unlock knight on every theme change
107
+ self.app.theme_changed_signal.unsubscribe(self)
105
108
  self.app.push_screen(MainScreen())
106
109
 
107
110
 
@@ -166,7 +169,8 @@ def _qr_text(url: str, primary: str) -> tuple[Text, int]:
166
169
  must give the widget exactly that width: a wrapped QR is a dead QR."""
167
170
  import qrcode
168
171
 
169
- r, g, b = logo._hex_rgb(primary)
172
+ rgb = logo._hex_rgb(primary) or (0, 0, 0) # ansi themes → plain black QR
173
+ r, g, b = rgb
170
174
  while (r + g + b) / 765 > 0.45:
171
175
  r, g, b = int(r * 0.8), int(g * 0.8), int(b * 0.8)
172
176
  dark, light = f"rgb({r},{g},{b})", "rgb(255,255,255)"
@@ -277,6 +281,45 @@ class PasswdModal(ModalScreen):
277
281
  self.dismiss(new)
278
282
 
279
283
 
284
+ class ThemeModal(ModalScreen):
285
+ """Theme picker: cursor starts on the current theme, moving it previews
286
+ the theme live, Enter keeps it, Esc reverts to what you had."""
287
+
288
+ BINDINGS = [Binding("escape", "cancel", "cancel")]
289
+
290
+ def __init__(self) -> None:
291
+ super().__init__()
292
+ self.original = None
293
+
294
+ def compose(self) -> ComposeResult:
295
+ with Vertical(classes="modal-box", id="theme-box"):
296
+ yield Label("theme", classes="modal-title")
297
+ yield OptionList(id="theme-list")
298
+ yield Static("↑↓ preview · enter keeps · esc cancels", classes="modal-hint")
299
+
300
+ def on_mount(self) -> None:
301
+ self.original = self.app.theme
302
+ names = sorted(self.app.available_themes)
303
+ option_list = self.query_one(OptionList)
304
+ for name in names:
305
+ label = f"{name} ✓" if name == self.original else name
306
+ option_list.add_option(Option(label, id=name))
307
+ option_list.highlighted = names.index(self.original) # start on current
308
+
309
+ def on_option_list_option_highlighted(self, event: OptionList.OptionHighlighted) -> None:
310
+ if event.option.id:
311
+ self.app.theme = event.option.id # live preview
312
+
313
+ def on_option_list_option_selected(self, event: OptionList.OptionSelected) -> None:
314
+ if event.option.id:
315
+ self.app.theme = event.option.id # commit (persists via theme signal)
316
+ self.dismiss()
317
+
318
+ def action_cancel(self) -> None:
319
+ self.app.theme = self.original # revert the preview
320
+ self.dismiss()
321
+
322
+
280
323
  class ConfirmModal(ModalScreen):
281
324
  """y/n confirmation."""
282
325
 
@@ -308,7 +351,7 @@ class MainScreen(Screen):
308
351
  Binding("e", "edit", "edit"),
309
352
  Binding("d", "delete", "delete"),
310
353
  Binding("slash", "search", "search"),
311
- Binding("t", "app.change_theme", "theme"),
354
+ Binding("t", "theme", "theme"),
312
355
  Binding("escape", "clear_search", show=False),
313
356
  Binding("q", "quit", "quit"),
314
357
  ]
@@ -432,6 +475,9 @@ class MainScreen(Screen):
432
475
 
433
476
  self.app.push_screen(ConfirmModal(f"delete '{name}'?"), done)
434
477
 
478
+ def action_theme(self) -> None:
479
+ self.app.push_screen(ThemeModal())
480
+
435
481
  def action_search(self) -> None:
436
482
  self.query_one("#search", Input).focus()
437
483
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "basic-password-manager"
7
- version = "0.4.3"
7
+ version = "0.4.5"
8
8
  description = "The Paladin — a local encrypted password manager that lives in your terminal"
9
9
  readme = "README.md"
10
10
  license = "MIT"