lestash 1.63.0__tar.gz → 1.63.2__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 (66) hide show
  1. {lestash-1.63.0 → lestash-1.63.2}/PKG-INFO +1 -1
  2. {lestash-1.63.0 → lestash-1.63.2}/pyproject.toml +1 -1
  3. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/google_auth.py +29 -7
  4. {lestash-1.63.0 → lestash-1.63.2}/.gitignore +0 -0
  5. {lestash-1.63.0 → lestash-1.63.2}/README.md +0 -0
  6. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/__init__.py +0 -0
  7. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/__main__.py +0 -0
  8. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/cli/__init__.py +0 -0
  9. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/cli/config.py +0 -0
  10. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/cli/db.py +0 -0
  11. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/cli/embeddings.py +0 -0
  12. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/cli/enrich.py +0 -0
  13. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/cli/google.py +0 -0
  14. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/cli/items.py +0 -0
  15. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/cli/main.py +0 -0
  16. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/cli/media.py +0 -0
  17. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/cli/profiles.py +0 -0
  18. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/cli/sources.py +0 -0
  19. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/__init__.py +0 -0
  20. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/config.py +0 -0
  21. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/database.py +0 -0
  22. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/db_admin.py +0 -0
  23. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/embeddings.py +0 -0
  24. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/enrichment.py +0 -0
  25. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/exceptions.py +0 -0
  26. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/google_drive.py +0 -0
  27. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/logging.py +0 -0
  28. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/pdf_enrich/TUNING.md +0 -0
  29. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/pdf_enrich/__init__.py +0 -0
  30. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/pdf_enrich/annotations.py +0 -0
  31. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/pdf_enrich/cleanup.py +0 -0
  32. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/pdf_enrich/extractor.py +0 -0
  33. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/pdf_enrich/images.py +0 -0
  34. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/pdf_enrich/links.py +0 -0
  35. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/pdf_enrich/ocr.py +0 -0
  36. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/pdf_enrich/persistence.py +0 -0
  37. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/pdf_enrich/runner.py +0 -0
  38. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/pdf_enrich/types.py +0 -0
  39. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/pdf_enrich/version.py +0 -0
  40. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/text_extract.py +0 -0
  41. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/models/__init__.py +0 -0
  42. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/models/item.py +0 -0
  43. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/models/source.py +0 -0
  44. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/models/tag.py +0 -0
  45. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/plugins/__init__.py +0 -0
  46. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/plugins/base.py +0 -0
  47. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/plugins/loader.py +0 -0
  48. {lestash-1.63.0 → lestash-1.63.2}/src/lestash/plugins/publisher.py +0 -0
  49. {lestash-1.63.0 → lestash-1.63.2}/tests/__init__.py +0 -0
  50. {lestash-1.63.0 → lestash-1.63.2}/tests/conftest.py +0 -0
  51. {lestash-1.63.0 → lestash-1.63.2}/tests/pdf_enrich/__init__.py +0 -0
  52. {lestash-1.63.0 → lestash-1.63.2}/tests/pdf_enrich/conftest.py +0 -0
  53. {lestash-1.63.0 → lestash-1.63.2}/tests/pdf_enrich/test_annotations.py +0 -0
  54. {lestash-1.63.0 → lestash-1.63.2}/tests/pdf_enrich/test_cleanup.py +0 -0
  55. {lestash-1.63.0 → lestash-1.63.2}/tests/pdf_enrich/test_extractor.py +0 -0
  56. {lestash-1.63.0 → lestash-1.63.2}/tests/pdf_enrich/test_images.py +0 -0
  57. {lestash-1.63.0 → lestash-1.63.2}/tests/pdf_enrich/test_links.py +0 -0
  58. {lestash-1.63.0 → lestash-1.63.2}/tests/pdf_enrich/test_ocr.py +0 -0
  59. {lestash-1.63.0 → lestash-1.63.2}/tests/pdf_enrich/test_persistence.py +0 -0
  60. {lestash-1.63.0 → lestash-1.63.2}/tests/pdf_enrich/test_runner.py +0 -0
  61. {lestash-1.63.0 → lestash-1.63.2}/tests/test_db_admin.py +0 -0
  62. {lestash-1.63.0 → lestash-1.63.2}/tests/test_history.py +0 -0
  63. {lestash-1.63.0 → lestash-1.63.2}/tests/test_items_display.py +0 -0
  64. {lestash-1.63.0 → lestash-1.63.2}/tests/test_post_cache.py +0 -0
  65. {lestash-1.63.0 → lestash-1.63.2}/tests/test_publisher.py +0 -0
  66. {lestash-1.63.0 → lestash-1.63.2}/tests/test_text_extract.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lestash
3
- Version: 1.63.0
3
+ Version: 1.63.2
4
4
  Summary: Personal knowledge base CLI - aggregate content from multiple sources
5
5
  License: AGPL-3.0-only
6
6
  Requires-Python: >=3.12
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "lestash"
3
- version = "1.63.0"
3
+ version = "1.63.2"
4
4
  description = "Personal knowledge base CLI - aggregate content from multiple sources"
5
5
  readme = "README.md"
6
6
  license = {text = "AGPL-3.0-only"}
@@ -80,6 +80,7 @@ def save_credentials(credentials: Credentials, scopes: list[str] | None = None)
80
80
  "client_id": credentials.client_id,
81
81
  "client_secret": credentials.client_secret,
82
82
  "scopes": list(credentials.scopes) if credentials.scopes else (scopes or DEFAULT_SCOPES),
83
+ "expiry": credentials.expiry.isoformat() if credentials.expiry else None,
83
84
  }
84
85
  path = get_credentials_path()
85
86
  path.write_text(json.dumps(creds_data, indent=2))
@@ -88,12 +89,19 @@ def save_credentials(credentials: Credentials, scopes: list[str] | None = None)
88
89
 
89
90
  def load_credentials() -> Credentials | None:
90
91
  """Load OAuth credentials from config file."""
92
+ from datetime import datetime
93
+
91
94
  path = get_credentials_path()
92
95
  if not path.exists():
93
96
  return None
94
97
 
95
98
  try:
96
99
  creds_data = json.loads(path.read_text())
100
+ expiry_str = creds_data.get("expiry")
101
+ expiry = datetime.fromisoformat(expiry_str) if expiry_str else None
102
+ # google-auth uses naive UTC datetimes internally; strip tzinfo if present
103
+ if expiry and expiry.tzinfo is not None:
104
+ expiry = expiry.replace(tzinfo=None)
97
105
  return Credentials(
98
106
  token=creds_data.get("token"),
99
107
  refresh_token=creds_data.get("refresh_token"),
@@ -101,6 +109,7 @@ def load_credentials() -> Credentials | None:
101
109
  client_id=creds_data.get("client_id"),
102
110
  client_secret=creds_data.get("client_secret"),
103
111
  scopes=creds_data.get("scopes", DEFAULT_SCOPES),
112
+ expiry=expiry,
104
113
  )
105
114
  except (json.JSONDecodeError, OSError, KeyError):
106
115
  return None
@@ -132,7 +141,12 @@ def run_auth_flow(
132
141
  raise FileNotFoundError(msg)
133
142
 
134
143
  effective_scopes = scopes or DEFAULT_SCOPES
135
- flow = InstalledAppFlow.from_client_secrets_file(str(secrets_path), effective_scopes)
144
+ # Explicitly load the `installed` client config — from_client_secrets_file checks
145
+ # for `web` before `installed`, so a file with both sections always picks web.
146
+ # OOB redirect (used by the headless flow) is not allowed for web client types.
147
+ raw = json.loads(secrets_path.read_text())
148
+ installed_config = raw.get("installed") or raw
149
+ flow = InstalledAppFlow.from_client_config({"installed": installed_config}, effective_scopes)
136
150
 
137
151
  use_headless = headless if headless is not None else is_headless()
138
152
  credentials = _run_manual_flow(flow) if use_headless else flow.run_local_server(port=0)
@@ -167,16 +181,20 @@ def get_credentials(scopes: list[str] | None = None) -> Credentials:
167
181
  """
168
182
  credentials = load_credentials()
169
183
 
170
- if credentials and credentials.valid:
184
+ if credentials and credentials.valid and credentials.expiry is not None:
171
185
  return credentials
172
186
 
173
- if credentials and credentials.expired and credentials.refresh_token:
187
+ if credentials and credentials.refresh_token:
174
188
  try:
175
189
  credentials.refresh(Request())
176
190
  save_credentials(credentials, scopes)
177
191
  return credentials
178
- except Exception:
179
- pass
192
+ except Exception as e:
193
+ if "invalid_grant" in str(e).lower():
194
+ get_credentials_path().unlink(missing_ok=True)
195
+ raise ValueError(
196
+ f"Google credentials are no longer valid ({e}). Re-authenticate via the UI."
197
+ ) from e
180
198
 
181
199
  raise ValueError("No valid Google credentials. Run 'lestash google auth' to authenticate.")
182
200
 
@@ -193,15 +211,19 @@ def check_auth_status() -> dict:
193
211
  credentials = load_credentials()
194
212
  if credentials:
195
213
  result["scopes"] = list(credentials.scopes) if credentials.scopes else []
196
- if credentials.valid:
214
+ # Treat missing expiry as expired — legacy credentials saved without expiry
215
+ # would otherwise appear valid indefinitely even after revocation.
216
+ if credentials.valid and credentials.expiry is not None:
197
217
  result["authenticated"] = True
198
- elif credentials.expired and credentials.refresh_token:
218
+ elif credentials.refresh_token:
199
219
  try:
200
220
  credentials.refresh(Request())
201
221
  save_credentials(credentials)
202
222
  result["authenticated"] = True
203
223
  except Exception as e:
204
224
  result["refresh_error"] = str(e)
225
+ if "invalid_grant" in str(e).lower():
226
+ get_credentials_path().unlink(missing_ok=True)
205
227
 
206
228
  return result
207
229
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes