lestash 1.48.3__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 (40) hide show
  1. lestash-1.48.3/.gitignore +20 -0
  2. lestash-1.48.3/PKG-INFO +194 -0
  3. lestash-1.48.3/README.md +176 -0
  4. lestash-1.48.3/pyproject.toml +29 -0
  5. lestash-1.48.3/src/lestash/__init__.py +3 -0
  6. lestash-1.48.3/src/lestash/__main__.py +6 -0
  7. lestash-1.48.3/src/lestash/cli/__init__.py +1 -0
  8. lestash-1.48.3/src/lestash/cli/config.py +85 -0
  9. lestash-1.48.3/src/lestash/cli/db.py +525 -0
  10. lestash-1.48.3/src/lestash/cli/embeddings.py +51 -0
  11. lestash-1.48.3/src/lestash/cli/google.py +199 -0
  12. lestash-1.48.3/src/lestash/cli/items.py +385 -0
  13. lestash-1.48.3/src/lestash/cli/main.py +61 -0
  14. lestash-1.48.3/src/lestash/cli/media.py +220 -0
  15. lestash-1.48.3/src/lestash/cli/profiles.py +96 -0
  16. lestash-1.48.3/src/lestash/cli/sources.py +273 -0
  17. lestash-1.48.3/src/lestash/core/__init__.py +10 -0
  18. lestash-1.48.3/src/lestash/core/config.py +108 -0
  19. lestash-1.48.3/src/lestash/core/database.py +785 -0
  20. lestash-1.48.3/src/lestash/core/db_admin.py +568 -0
  21. lestash-1.48.3/src/lestash/core/embeddings.py +182 -0
  22. lestash-1.48.3/src/lestash/core/enrichment.py +157 -0
  23. lestash-1.48.3/src/lestash/core/exceptions.py +21 -0
  24. lestash-1.48.3/src/lestash/core/google_auth.py +236 -0
  25. lestash-1.48.3/src/lestash/core/google_drive.py +299 -0
  26. lestash-1.48.3/src/lestash/core/logging.py +254 -0
  27. lestash-1.48.3/src/lestash/core/text_extract.py +57 -0
  28. lestash-1.48.3/src/lestash/models/__init__.py +7 -0
  29. lestash-1.48.3/src/lestash/models/item.py +81 -0
  30. lestash-1.48.3/src/lestash/models/source.py +26 -0
  31. lestash-1.48.3/src/lestash/models/tag.py +10 -0
  32. lestash-1.48.3/src/lestash/plugins/__init__.py +6 -0
  33. lestash-1.48.3/src/lestash/plugins/base.py +55 -0
  34. lestash-1.48.3/src/lestash/plugins/loader.py +32 -0
  35. lestash-1.48.3/tests/__init__.py +1 -0
  36. lestash-1.48.3/tests/conftest.py +18 -0
  37. lestash-1.48.3/tests/test_db_admin.py +357 -0
  38. lestash-1.48.3/tests/test_history.py +265 -0
  39. lestash-1.48.3/tests/test_items_display.py +246 -0
  40. lestash-1.48.3/tests/test_post_cache.py +336 -0
@@ -0,0 +1,20 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ # Tool caches
13
+ .mypy_cache/
14
+ .pytest_cache/
15
+ .ruff_cache/
16
+
17
+ # Tauri app
18
+ app/node_modules/
19
+ app/src-tauri/target/
20
+ app/src-tauri/gen/schemas/
@@ -0,0 +1,194 @@
1
+ Metadata-Version: 2.4
2
+ Name: lestash
3
+ Version: 1.48.3
4
+ Summary: Personal knowledge base CLI - aggregate content from multiple sources
5
+ Requires-Python: >=3.12
6
+ Requires-Dist: docling>=2.84.0
7
+ Requires-Dist: google-api-python-client>=2.0.0
8
+ Requires-Dist: google-auth-httplib2>=0.2.0
9
+ Requires-Dist: google-auth-oauthlib>=1.0.0
10
+ Requires-Dist: pydantic-settings>=2.1.0
11
+ Requires-Dist: pydantic>=2.5.0
12
+ Requires-Dist: rich>=13.7.0
13
+ Requires-Dist: sentence-transformers>=3.0.0
14
+ Requires-Dist: sqlite-vec>=0.1.6
15
+ Requires-Dist: toml>=0.10.2
16
+ Requires-Dist: typer>=0.12.0
17
+ Description-Content-Type: text/markdown
18
+
19
+ # lestash
20
+
21
+ [![PyPI version](https://badge.fury.io/py/lestash.svg)](https://pypi.org/project/lestash/)
22
+ [![GitHub](https://img.shields.io/github/license/thompsonson/lestash)](https://github.com/thompsonson/lestash)
23
+
24
+ Core CLI and plugin system for Le Stash - a personal knowledge base that aggregates content from multiple sources into a unified, searchable database.
25
+
26
+ > **Note**: For project overview, features, and quick start guide, see the [GitHub repository](https://github.com/thompsonson/lestash).
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ uv add lestash
32
+ ```
33
+
34
+ Or install the full workspace from the repository root:
35
+
36
+ ```bash
37
+ uv sync --all-packages
38
+ ```
39
+
40
+ ## CLI Commands
41
+
42
+ ### Items
43
+
44
+ ```bash
45
+ # List all items (with date/time and resolved author names)
46
+ lestash items list
47
+
48
+ # Filter by source
49
+ lestash items list --source linkedin
50
+
51
+ # Search items
52
+ lestash items search "query"
53
+
54
+ # Show item details (includes reaction/comment target URLs)
55
+ lestash items show <id>
56
+
57
+ # Export items to JSON
58
+ lestash items export --output backup.json
59
+
60
+ # Create a Micro.blog draft from an item
61
+ lestash items draft <id> --output ~/blog/content/drafts/
62
+ ```
63
+
64
+ ### Profiles
65
+
66
+ Map person URNs (e.g., LinkedIn) to human-readable names and profile URLs:
67
+
68
+ ```bash
69
+ # Add a profile mapping
70
+ lestash profiles add "urn:li:person:abc123" --name "John Doe" --url "https://linkedin.com/in/johndoe"
71
+
72
+ # List all profiles
73
+ lestash profiles list
74
+
75
+ # Show a specific profile
76
+ lestash profiles show "urn:li:person:abc123"
77
+
78
+ # Remove a profile
79
+ lestash profiles remove "urn:li:person:abc123"
80
+ ```
81
+
82
+ When profiles are configured, `items list` and `items search` display names instead of URNs, and `items show` includes the profile URL.
83
+
84
+ ### Sources
85
+
86
+ ```bash
87
+ # List configured sources
88
+ lestash sources list
89
+
90
+ # Sync all sources
91
+ lestash sources sync
92
+
93
+ # View sync history
94
+ lestash sources history
95
+ ```
96
+
97
+ ### Configuration
98
+
99
+ ```bash
100
+ # Show current config
101
+ lestash config show
102
+
103
+ # Initialize config file
104
+ lestash config init
105
+
106
+ # Set a config value
107
+ lestash config set logging.level DEBUG
108
+ ```
109
+
110
+ ## Plugin Architecture
111
+
112
+ Le Stash uses entry points for plugin discovery. Plugins implement the `SourcePlugin` base class:
113
+
114
+ ```python
115
+ from lestash.plugins.base import SourcePlugin
116
+ from lestash.models.item import ItemCreate
117
+
118
+ class MySource(SourcePlugin):
119
+ name = "my-source"
120
+ description = "My custom data source"
121
+
122
+ def get_commands(self) -> typer.Typer:
123
+ """Return Typer app with source-specific commands."""
124
+ app = typer.Typer()
125
+ # Add commands...
126
+ return app
127
+
128
+ def sync(self, config: dict) -> Iterator[ItemCreate]:
129
+ """Fetch items from the source."""
130
+ yield ItemCreate(
131
+ source_type="my-source",
132
+ source_id="unique-id",
133
+ content="Item content",
134
+ )
135
+ ```
136
+
137
+ Register the plugin in `pyproject.toml`:
138
+
139
+ ```toml
140
+ [project.entry-points."lestash.sources"]
141
+ my-source = "my_package:MySource"
142
+ ```
143
+
144
+ ## Data Model
145
+
146
+ ### Item
147
+
148
+ The core unit of content:
149
+
150
+ | Field | Type | Description |
151
+ |-------|------|-------------|
152
+ | `id` | int | Auto-generated primary key |
153
+ | `source_type` | str | Plugin identifier (e.g., "arxiv", "linkedin") |
154
+ | `source_id` | str | Unique ID within the source |
155
+ | `url` | str | Optional URL to original content |
156
+ | `title` | str | Optional title |
157
+ | `content` | str | Main text content |
158
+ | `author` | str | Optional author |
159
+ | `created_at` | datetime | When the content was created |
160
+ | `is_own_content` | bool | Whether user authored this |
161
+ | `metadata` | dict | Source-specific data (JSON) |
162
+ | `parent_id` | int | Optional FK to parent item (for grouping children) |
163
+
164
+ ### Database
165
+
166
+ SQLite database with:
167
+
168
+ - **items** - Main content table with unique constraint on (source_type, source_id) and optional `parent_id` for hierarchical grouping
169
+ - **items_fts** - Full-text search virtual table (FTS5)
170
+ - **vec_items** - Vector embeddings for semantic search (sqlite-vec)
171
+ - **item_history** - Audit trail of content changes
172
+ - **person_profiles** - URN to name/URL mapping for display
173
+ - **post_cache** - Cached metadata for posts referenced by reactions/comments
174
+ - **tags** / **item_tags** - Tagging system
175
+ - **collections** / **collection_items** - Cross-source item grouping
176
+ - **sources** - Plugin configuration storage
177
+ - **sync_log** - Sync operation history
178
+
179
+ ## Configuration
180
+
181
+ Default location: `~/.config/lestash/config.toml`
182
+
183
+ ```toml
184
+ [general]
185
+ database_path = "~/.config/lestash/lestash.db"
186
+
187
+ [logging]
188
+ level = "INFO"
189
+ file_path = "~/.config/lestash/logs/lestash.log"
190
+ ```
191
+
192
+ ## License
193
+
194
+ [MIT](../../LICENSE)
@@ -0,0 +1,176 @@
1
+ # lestash
2
+
3
+ [![PyPI version](https://badge.fury.io/py/lestash.svg)](https://pypi.org/project/lestash/)
4
+ [![GitHub](https://img.shields.io/github/license/thompsonson/lestash)](https://github.com/thompsonson/lestash)
5
+
6
+ Core CLI and plugin system for Le Stash - a personal knowledge base that aggregates content from multiple sources into a unified, searchable database.
7
+
8
+ > **Note**: For project overview, features, and quick start guide, see the [GitHub repository](https://github.com/thompsonson/lestash).
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ uv add lestash
14
+ ```
15
+
16
+ Or install the full workspace from the repository root:
17
+
18
+ ```bash
19
+ uv sync --all-packages
20
+ ```
21
+
22
+ ## CLI Commands
23
+
24
+ ### Items
25
+
26
+ ```bash
27
+ # List all items (with date/time and resolved author names)
28
+ lestash items list
29
+
30
+ # Filter by source
31
+ lestash items list --source linkedin
32
+
33
+ # Search items
34
+ lestash items search "query"
35
+
36
+ # Show item details (includes reaction/comment target URLs)
37
+ lestash items show <id>
38
+
39
+ # Export items to JSON
40
+ lestash items export --output backup.json
41
+
42
+ # Create a Micro.blog draft from an item
43
+ lestash items draft <id> --output ~/blog/content/drafts/
44
+ ```
45
+
46
+ ### Profiles
47
+
48
+ Map person URNs (e.g., LinkedIn) to human-readable names and profile URLs:
49
+
50
+ ```bash
51
+ # Add a profile mapping
52
+ lestash profiles add "urn:li:person:abc123" --name "John Doe" --url "https://linkedin.com/in/johndoe"
53
+
54
+ # List all profiles
55
+ lestash profiles list
56
+
57
+ # Show a specific profile
58
+ lestash profiles show "urn:li:person:abc123"
59
+
60
+ # Remove a profile
61
+ lestash profiles remove "urn:li:person:abc123"
62
+ ```
63
+
64
+ When profiles are configured, `items list` and `items search` display names instead of URNs, and `items show` includes the profile URL.
65
+
66
+ ### Sources
67
+
68
+ ```bash
69
+ # List configured sources
70
+ lestash sources list
71
+
72
+ # Sync all sources
73
+ lestash sources sync
74
+
75
+ # View sync history
76
+ lestash sources history
77
+ ```
78
+
79
+ ### Configuration
80
+
81
+ ```bash
82
+ # Show current config
83
+ lestash config show
84
+
85
+ # Initialize config file
86
+ lestash config init
87
+
88
+ # Set a config value
89
+ lestash config set logging.level DEBUG
90
+ ```
91
+
92
+ ## Plugin Architecture
93
+
94
+ Le Stash uses entry points for plugin discovery. Plugins implement the `SourcePlugin` base class:
95
+
96
+ ```python
97
+ from lestash.plugins.base import SourcePlugin
98
+ from lestash.models.item import ItemCreate
99
+
100
+ class MySource(SourcePlugin):
101
+ name = "my-source"
102
+ description = "My custom data source"
103
+
104
+ def get_commands(self) -> typer.Typer:
105
+ """Return Typer app with source-specific commands."""
106
+ app = typer.Typer()
107
+ # Add commands...
108
+ return app
109
+
110
+ def sync(self, config: dict) -> Iterator[ItemCreate]:
111
+ """Fetch items from the source."""
112
+ yield ItemCreate(
113
+ source_type="my-source",
114
+ source_id="unique-id",
115
+ content="Item content",
116
+ )
117
+ ```
118
+
119
+ Register the plugin in `pyproject.toml`:
120
+
121
+ ```toml
122
+ [project.entry-points."lestash.sources"]
123
+ my-source = "my_package:MySource"
124
+ ```
125
+
126
+ ## Data Model
127
+
128
+ ### Item
129
+
130
+ The core unit of content:
131
+
132
+ | Field | Type | Description |
133
+ |-------|------|-------------|
134
+ | `id` | int | Auto-generated primary key |
135
+ | `source_type` | str | Plugin identifier (e.g., "arxiv", "linkedin") |
136
+ | `source_id` | str | Unique ID within the source |
137
+ | `url` | str | Optional URL to original content |
138
+ | `title` | str | Optional title |
139
+ | `content` | str | Main text content |
140
+ | `author` | str | Optional author |
141
+ | `created_at` | datetime | When the content was created |
142
+ | `is_own_content` | bool | Whether user authored this |
143
+ | `metadata` | dict | Source-specific data (JSON) |
144
+ | `parent_id` | int | Optional FK to parent item (for grouping children) |
145
+
146
+ ### Database
147
+
148
+ SQLite database with:
149
+
150
+ - **items** - Main content table with unique constraint on (source_type, source_id) and optional `parent_id` for hierarchical grouping
151
+ - **items_fts** - Full-text search virtual table (FTS5)
152
+ - **vec_items** - Vector embeddings for semantic search (sqlite-vec)
153
+ - **item_history** - Audit trail of content changes
154
+ - **person_profiles** - URN to name/URL mapping for display
155
+ - **post_cache** - Cached metadata for posts referenced by reactions/comments
156
+ - **tags** / **item_tags** - Tagging system
157
+ - **collections** / **collection_items** - Cross-source item grouping
158
+ - **sources** - Plugin configuration storage
159
+ - **sync_log** - Sync operation history
160
+
161
+ ## Configuration
162
+
163
+ Default location: `~/.config/lestash/config.toml`
164
+
165
+ ```toml
166
+ [general]
167
+ database_path = "~/.config/lestash/lestash.db"
168
+
169
+ [logging]
170
+ level = "INFO"
171
+ file_path = "~/.config/lestash/logs/lestash.log"
172
+ ```
173
+
174
+ ## License
175
+
176
+ [MIT](../../LICENSE)
@@ -0,0 +1,29 @@
1
+ [project]
2
+ name = "lestash"
3
+ version = "1.48.3"
4
+ description = "Personal knowledge base CLI - aggregate content from multiple sources"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ dependencies = [
8
+ "typer>=0.12.0",
9
+ "pydantic>=2.5.0",
10
+ "pydantic-settings>=2.1.0",
11
+ "rich>=13.7.0",
12
+ "toml>=0.10.2",
13
+ "google-api-python-client>=2.0.0",
14
+ "google-auth-oauthlib>=1.0.0",
15
+ "google-auth-httplib2>=0.2.0",
16
+ "sqlite-vec>=0.1.6",
17
+ "sentence-transformers>=3.0.0",
18
+ "docling>=2.84.0",
19
+ ]
20
+
21
+ [project.scripts]
22
+ lestash = "lestash.cli.main:app"
23
+
24
+ [build-system]
25
+ requires = ["hatchling"]
26
+ build-backend = "hatchling.build"
27
+
28
+ [tool.hatch.build.targets.wheel]
29
+ packages = ["src/lestash"]
@@ -0,0 +1,3 @@
1
+ """Le Stash - Personal knowledge base CLI."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,6 @@
1
+ """Entry point for python -m lestash."""
2
+
3
+ from lestash.cli.main import app
4
+
5
+ if __name__ == "__main__":
6
+ app()
@@ -0,0 +1 @@
1
+ """CLI module for Le Stash."""
@@ -0,0 +1,85 @@
1
+ """Config commands for Le Stash CLI."""
2
+
3
+ from typing import Annotated
4
+
5
+ import typer
6
+ from rich.console import Console
7
+
8
+ from lestash.core.config import Config, get_config_path, init_config
9
+
10
+ app = typer.Typer(help="Manage configuration.")
11
+ console = Console()
12
+
13
+
14
+ @app.command("show")
15
+ def show_config() -> None:
16
+ """Show current configuration."""
17
+ config_path = get_config_path()
18
+
19
+ if not config_path.exists():
20
+ console.print(f"[dim]No config file at {config_path}[/dim]")
21
+ console.print("[dim]Run 'lestash config init' to create one.[/dim]")
22
+ return
23
+
24
+ config = Config.load()
25
+ console.print(f"[bold]Config file:[/bold] {config_path}")
26
+ console.print()
27
+
28
+ data = config.model_dump()
29
+ for section, values in data.items():
30
+ console.print(f"[bold][{section}][/bold]")
31
+ if isinstance(values, dict):
32
+ for key, value in values.items():
33
+ console.print(f" {key} = {value}")
34
+ else:
35
+ console.print(f" {values}")
36
+ console.print()
37
+
38
+
39
+ @app.command("init")
40
+ def init_config_cmd(
41
+ force: Annotated[bool, typer.Option("--force", "-f", help="Overwrite existing config")] = False,
42
+ ) -> None:
43
+ """Initialize configuration file with defaults."""
44
+ config_path = get_config_path()
45
+
46
+ if config_path.exists() and not force:
47
+ console.print(f"[yellow]Config already exists at {config_path}[/yellow]")
48
+ console.print("[dim]Use --force to overwrite.[/dim]")
49
+ raise typer.Exit(1)
50
+
51
+ init_config()
52
+ console.print(f"[green]Created config at {config_path}[/green]")
53
+
54
+
55
+ @app.command("set")
56
+ def set_config(
57
+ key: Annotated[str, typer.Argument(help="Config key (e.g., general.database_path)")],
58
+ value: Annotated[str, typer.Argument(help="Value to set")],
59
+ ) -> None:
60
+ """Set a configuration value."""
61
+ config = Config.load()
62
+
63
+ parts = key.split(".")
64
+ if len(parts) != 2:
65
+ console.print("[red]Key must be in format 'section.key'[/red]")
66
+ raise typer.Exit(1)
67
+
68
+ section, setting = parts
69
+
70
+ # Get current config as dict
71
+ data = config.model_dump()
72
+
73
+ if section not in data:
74
+ data[section] = {}
75
+
76
+ data[section][setting] = value
77
+
78
+ # Save updated config
79
+ try:
80
+ updated_config = Config(**data)
81
+ updated_config.save()
82
+ console.print(f"[green]Set {key} = {value}[/green]")
83
+ except Exception as e:
84
+ console.print(f"[red]Error: {e}[/red]")
85
+ raise typer.Exit(1) from None