ilmzot 0.1.0__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.
- ilmzot-0.1.0/.github/workflows/publish.yml +28 -0
- ilmzot-0.1.0/.gitignore +6 -0
- ilmzot-0.1.0/CONTRIBUTING.md +5 -0
- ilmzot-0.1.0/LICENSE +18 -0
- ilmzot-0.1.0/PKG-INFO +93 -0
- ilmzot-0.1.0/README.md +63 -0
- ilmzot-0.1.0/pyproject.toml +25 -0
- ilmzot-0.1.0/src/ilmzot/__init__.py +1 -0
- ilmzot-0.1.0/src/ilmzot/cli.py +198 -0
- ilmzot-0.1.0/src/ilmzot/notebook.py +111 -0
- ilmzot-0.1.0/src/ilmzot/state.py +39 -0
- ilmzot-0.1.0/src/ilmzot/zotero.py +129 -0
- ilmzot-0.1.0/tests/test_cli.py +18 -0
- ilmzot-0.1.0/tests/test_notebook.py +12 -0
- ilmzot-0.1.0/tests/test_state.py +17 -0
- ilmzot-0.1.0/tests/test_zotero.py +39 -0
- ilmzot-0.1.0/uv.lock +343 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
environment: pypi
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
id-token: write
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.12"
|
|
21
|
+
|
|
22
|
+
- name: Build
|
|
23
|
+
run: |
|
|
24
|
+
python -m pip install --upgrade build
|
|
25
|
+
python -m build
|
|
26
|
+
|
|
27
|
+
- name: Publish
|
|
28
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
ilmzot-0.1.0/.gitignore
ADDED
ilmzot-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ilmzot contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and sell copies
|
|
9
|
+
of the Software, and to permit persons to whom the Software is furnished to do so,
|
|
10
|
+
subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
18
|
+
|
ilmzot-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: ilmzot
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Feed your Zotero knowledge into NotebookLM.
|
|
5
|
+
License: MIT License
|
|
6
|
+
|
|
7
|
+
Copyright (c) 2026 ilmzot contributors
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
11
|
+
in the Software without restriction, including without limitation the rights
|
|
12
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and sell copies
|
|
13
|
+
of the Software, and to permit persons to whom the Software is furnished to do so,
|
|
14
|
+
subject to the following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in all
|
|
17
|
+
copies or substantial portions of the Software.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
22
|
+
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Python: <3.13,>=3.10
|
|
25
|
+
Requires-Dist: notebooklm-py[cookies]<0.9,>=0.8.1
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
28
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# ilmzot
|
|
32
|
+
|
|
33
|
+
Feed your Zotero knowledge into NotebookLM.
|
|
34
|
+
|
|
35
|
+
`ilmzot` mirrors Zotero collections into same-named NotebookLM notebooks while
|
|
36
|
+
keeping Zotero as the source of truth. It is one-way: local PDFs are uploaded,
|
|
37
|
+
but Zotero is never modified and disappeared items are not deleted remotely.
|
|
38
|
+
|
|
39
|
+
## Quickstart
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
ilmzot setup
|
|
43
|
+
ilmzot ls
|
|
44
|
+
ilmzot sync "Climate Modelling"
|
|
45
|
+
ilmzot status
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`ilmzot setup` detects Zotero at `localhost:23119`, authenticates NotebookLM
|
|
49
|
+
using browser cookies, and creates its local state directory. Use
|
|
50
|
+
`--browser firefox`, `--browser "firefox::Work"`, or `--login` for interactive
|
|
51
|
+
authentication. ilmzot supports major browsers through automatic cookie
|
|
52
|
+
detection. An interactive login fallback is available when browser-cookie
|
|
53
|
+
extraction is unavailable.
|
|
54
|
+
|
|
55
|
+
## Installation
|
|
56
|
+
|
|
57
|
+
PyPI and Homebrew releases are coming soon. The intended Homebrew UX is
|
|
58
|
+
`brew install <user>/tap/ilmzot`; it will provide `ilmzot` directly without
|
|
59
|
+
shell configuration. For local development:
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
git clone <repository-url>
|
|
63
|
+
cd ilmzot
|
|
64
|
+
uv sync
|
|
65
|
+
uv run ilmzot --help
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Python 3.10–3.12 is supported (browser-cookie support is most reliable on
|
|
69
|
+
3.12). Runtime authentication is provided by `notebooklm-py[cookies]`.
|
|
70
|
+
|
|
71
|
+
## Commands
|
|
72
|
+
|
|
73
|
+
`ilmzot sync --all` syncs the personal library to `Zotero Library`.
|
|
74
|
+
`-n/--notebook` changes the destination name and `-d/--dry-run` previews work.
|
|
75
|
+
`ilmzot doctor` is diagnostic-only and explains one concrete fix for failures.
|
|
76
|
+
|
|
77
|
+
## Architecture and state
|
|
78
|
+
|
|
79
|
+
The standard library handles CLI parsing, HTTP, hashing, and state. `zotero.py`
|
|
80
|
+
owns the local Zotero API, `notebook.py` contains the unofficial NotebookLM
|
|
81
|
+
boundary, and `state.py` stores SHA-256 mappings. State is
|
|
82
|
+
`$XDG_DATA_HOME/ilmzot/state.json`, or `~/.local/share/ilmzot/state.json`.
|
|
83
|
+
It contains no cookies, tokens, passwords, or browser credentials.
|
|
84
|
+
|
|
85
|
+
## Privacy and limitations
|
|
86
|
+
|
|
87
|
+
PDFs are read locally from Zotero and uploaded to NotebookLM. No Zotero cloud
|
|
88
|
+
API key is needed. Group libraries, bidirectional sync, metadata editing, and
|
|
89
|
+
destructive remote reconciliation are not supported in 0.1.0.
|
|
90
|
+
|
|
91
|
+
NotebookLM integration relies on an unofficial API exposed through
|
|
92
|
+
`notebooklm-py` and may require updates when Google changes NotebookLM.
|
|
93
|
+
ilmzot is not affiliated with Zotero or Google.
|
ilmzot-0.1.0/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# ilmzot
|
|
2
|
+
|
|
3
|
+
Feed your Zotero knowledge into NotebookLM.
|
|
4
|
+
|
|
5
|
+
`ilmzot` mirrors Zotero collections into same-named NotebookLM notebooks while
|
|
6
|
+
keeping Zotero as the source of truth. It is one-way: local PDFs are uploaded,
|
|
7
|
+
but Zotero is never modified and disappeared items are not deleted remotely.
|
|
8
|
+
|
|
9
|
+
## Quickstart
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
ilmzot setup
|
|
13
|
+
ilmzot ls
|
|
14
|
+
ilmzot sync "Climate Modelling"
|
|
15
|
+
ilmzot status
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`ilmzot setup` detects Zotero at `localhost:23119`, authenticates NotebookLM
|
|
19
|
+
using browser cookies, and creates its local state directory. Use
|
|
20
|
+
`--browser firefox`, `--browser "firefox::Work"`, or `--login` for interactive
|
|
21
|
+
authentication. ilmzot supports major browsers through automatic cookie
|
|
22
|
+
detection. An interactive login fallback is available when browser-cookie
|
|
23
|
+
extraction is unavailable.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
PyPI and Homebrew releases are coming soon. The intended Homebrew UX is
|
|
28
|
+
`brew install <user>/tap/ilmzot`; it will provide `ilmzot` directly without
|
|
29
|
+
shell configuration. For local development:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
git clone <repository-url>
|
|
33
|
+
cd ilmzot
|
|
34
|
+
uv sync
|
|
35
|
+
uv run ilmzot --help
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Python 3.10–3.12 is supported (browser-cookie support is most reliable on
|
|
39
|
+
3.12). Runtime authentication is provided by `notebooklm-py[cookies]`.
|
|
40
|
+
|
|
41
|
+
## Commands
|
|
42
|
+
|
|
43
|
+
`ilmzot sync --all` syncs the personal library to `Zotero Library`.
|
|
44
|
+
`-n/--notebook` changes the destination name and `-d/--dry-run` previews work.
|
|
45
|
+
`ilmzot doctor` is diagnostic-only and explains one concrete fix for failures.
|
|
46
|
+
|
|
47
|
+
## Architecture and state
|
|
48
|
+
|
|
49
|
+
The standard library handles CLI parsing, HTTP, hashing, and state. `zotero.py`
|
|
50
|
+
owns the local Zotero API, `notebook.py` contains the unofficial NotebookLM
|
|
51
|
+
boundary, and `state.py` stores SHA-256 mappings. State is
|
|
52
|
+
`$XDG_DATA_HOME/ilmzot/state.json`, or `~/.local/share/ilmzot/state.json`.
|
|
53
|
+
It contains no cookies, tokens, passwords, or browser credentials.
|
|
54
|
+
|
|
55
|
+
## Privacy and limitations
|
|
56
|
+
|
|
57
|
+
PDFs are read locally from Zotero and uploaded to NotebookLM. No Zotero cloud
|
|
58
|
+
API key is needed. Group libraries, bidirectional sync, metadata editing, and
|
|
59
|
+
destructive remote reconciliation are not supported in 0.1.0.
|
|
60
|
+
|
|
61
|
+
NotebookLM integration relies on an unofficial API exposed through
|
|
62
|
+
`notebooklm-py` and may require updates when Google changes NotebookLM.
|
|
63
|
+
ilmzot is not affiliated with Zotero or Google.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ilmzot"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Feed your Zotero knowledge into NotebookLM."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { file = "LICENSE" }
|
|
11
|
+
requires-python = ">=3.10,<3.13"
|
|
12
|
+
dependencies = ["notebooklm-py[cookies]>=0.8.1,<0.9"]
|
|
13
|
+
|
|
14
|
+
[project.optional-dependencies]
|
|
15
|
+
dev = ["pytest>=8", "ruff>=0.6"]
|
|
16
|
+
|
|
17
|
+
[project.scripts]
|
|
18
|
+
ilmzot = "ilmzot.cli:main"
|
|
19
|
+
|
|
20
|
+
[tool.pytest.ini_options]
|
|
21
|
+
testpaths = ["tests"]
|
|
22
|
+
|
|
23
|
+
[tool.ruff]
|
|
24
|
+
line-length = 100
|
|
25
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
|
|
5
|
+
from . import __version__, notebook, state, zotero
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def parser() -> argparse.ArgumentParser:
|
|
9
|
+
p = argparse.ArgumentParser(
|
|
10
|
+
prog="ilmzot", description="Feed your Zotero knowledge into NotebookLM."
|
|
11
|
+
)
|
|
12
|
+
sub = p.add_subparsers(dest="command", required=True)
|
|
13
|
+
sub.add_parser("ls", aliases=["list"], help="List Zotero collections")
|
|
14
|
+
s = sub.add_parser("sync", help="Sync a Zotero collection")
|
|
15
|
+
s.add_argument("collection", nargs="?")
|
|
16
|
+
s.add_argument("--all", action="store_true")
|
|
17
|
+
s.add_argument("-n", "--notebook")
|
|
18
|
+
s.add_argument("-d", "--dry-run", action="store_true")
|
|
19
|
+
sub.add_parser("status", help="Show tracked sync status")
|
|
20
|
+
sub.add_parser("doctor", help="Diagnose local setup")
|
|
21
|
+
setup = sub.add_parser("setup", help="Authenticate NotebookLM and check Zotero")
|
|
22
|
+
setup.add_argument("--browser")
|
|
23
|
+
setup.add_argument("--login", action="store_true")
|
|
24
|
+
return p
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _zotero_error(exc: Exception) -> int:
|
|
28
|
+
print(f"Error: {exc}")
|
|
29
|
+
if isinstance(exc, zotero.LocalApiDisabled):
|
|
30
|
+
print("\nFix:\n Zotero → Settings → Advanced")
|
|
31
|
+
print(' Enable "Allow other applications on this computer to communicate with Zotero"')
|
|
32
|
+
return 1
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def command_ls() -> int:
|
|
36
|
+
try:
|
|
37
|
+
items = zotero.collections()
|
|
38
|
+
except (zotero.ZoteroUnavailable, zotero.LocalApiDisabled) as exc:
|
|
39
|
+
return _zotero_error(exc)
|
|
40
|
+
names = zotero.display_names(items)
|
|
41
|
+
print("\n".join(names[c.key] for c in items))
|
|
42
|
+
return 0
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def command_sync(args: argparse.Namespace) -> int:
|
|
46
|
+
if bool(args.collection) == args.all:
|
|
47
|
+
print("Error: provide exactly one collection or --all")
|
|
48
|
+
return 2
|
|
49
|
+
try:
|
|
50
|
+
collection_name = "Zotero Library" if args.all else args.collection
|
|
51
|
+
collection = None if args.all else zotero.find_collection(args.collection)
|
|
52
|
+
attachments = zotero.attachments(collection.key if collection else None)
|
|
53
|
+
data = state.load()
|
|
54
|
+
files = []
|
|
55
|
+
locally_skipped = 0
|
|
56
|
+
for attachment in attachments:
|
|
57
|
+
if attachment.path.exists():
|
|
58
|
+
digest = state.sha256_file(attachment.path)
|
|
59
|
+
previous = data["sources"].get(attachment.key)
|
|
60
|
+
if not args.dry_run and previous and previous.get("sha256") == digest:
|
|
61
|
+
locally_skipped += 1
|
|
62
|
+
else:
|
|
63
|
+
files.append((attachment.key, attachment.path, digest))
|
|
64
|
+
notebook_name = args.notebook or collection_name
|
|
65
|
+
added, updated, skipped, notebook_id = notebook.sync_notebook(
|
|
66
|
+
notebook_name, files, args.dry_run
|
|
67
|
+
)
|
|
68
|
+
skipped += locally_skipped
|
|
69
|
+
if not args.dry_run:
|
|
70
|
+
for key, path, digest in files:
|
|
71
|
+
data["sources"][key] = {
|
|
72
|
+
"notebook_id": notebook_id,
|
|
73
|
+
"collection": collection_name,
|
|
74
|
+
"path": str(path),
|
|
75
|
+
"sha256": digest,
|
|
76
|
+
"title": path.name,
|
|
77
|
+
}
|
|
78
|
+
state.save(data)
|
|
79
|
+
print(f"{added} added, {updated} updated, {skipped} skipped")
|
|
80
|
+
return 0
|
|
81
|
+
except (
|
|
82
|
+
zotero.ZoteroUnavailable,
|
|
83
|
+
zotero.LocalApiDisabled,
|
|
84
|
+
zotero.CollectionNotFound,
|
|
85
|
+
notebook.NotebookError,
|
|
86
|
+
OSError,
|
|
87
|
+
) as exc:
|
|
88
|
+
return (
|
|
89
|
+
_zotero_error(exc)
|
|
90
|
+
if isinstance(exc, (zotero.ZoteroUnavailable, zotero.LocalApiDisabled))
|
|
91
|
+
else (print(f"Error: {exc}") or 1)
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def command_setup(args: argparse.Namespace) -> int:
|
|
96
|
+
status = zotero.check()
|
|
97
|
+
if status == "unavailable":
|
|
98
|
+
print("Zotero is not running. Start Zotero and run setup again.")
|
|
99
|
+
return 1
|
|
100
|
+
if status == "disabled":
|
|
101
|
+
print("Zotero → Settings → Advanced")
|
|
102
|
+
print('Enable: "Allow other applications on this computer to communicate with Zotero"')
|
|
103
|
+
input("Press Enter after enabling it to retry... ")
|
|
104
|
+
if zotero.check() != "available":
|
|
105
|
+
print("Zotero local API is still unavailable.")
|
|
106
|
+
return 1
|
|
107
|
+
try:
|
|
108
|
+
notebook.authenticate(args.browser, args.login)
|
|
109
|
+
notebook.notebooks()
|
|
110
|
+
except notebook.NotebookError as exc:
|
|
111
|
+
print(f"NotebookLM setup failed: {exc}")
|
|
112
|
+
return 1
|
|
113
|
+
state_path = state.state_path()
|
|
114
|
+
state_path.parent.mkdir(parents=True, exist_ok=True)
|
|
115
|
+
print(f"Setup complete. Zotero API available; NotebookLM authenticated; state: {state_path}")
|
|
116
|
+
return 0
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def command_status() -> int:
|
|
120
|
+
data = state.load()
|
|
121
|
+
groups: dict[str, list] = {}
|
|
122
|
+
for entry in data["sources"].values():
|
|
123
|
+
groups.setdefault(entry["collection"], []).append(entry)
|
|
124
|
+
print("Zotero scope NotebookLM Tracked Present")
|
|
125
|
+
print("-" * 67)
|
|
126
|
+
try:
|
|
127
|
+
available = {b.get("title", b.get("name", "")).casefold() for b in notebook.notebooks()}
|
|
128
|
+
except notebook.NotebookError:
|
|
129
|
+
available = set()
|
|
130
|
+
for scope, entries in sorted(groups.items()):
|
|
131
|
+
present = len(entries) if scope.casefold() in available else 0
|
|
132
|
+
destination = scope if present else f"{scope} (missing)"
|
|
133
|
+
print(f"{scope:<25} {destination:<25} {len(entries):>7} {present:>8}")
|
|
134
|
+
return 0
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def command_doctor() -> int:
|
|
138
|
+
z = zotero.check()
|
|
139
|
+
print("Zotero")
|
|
140
|
+
print(" ✓ running" if z != "unavailable" else " ✗ not running")
|
|
141
|
+
print(
|
|
142
|
+
" ✓ local API enabled"
|
|
143
|
+
if z == "available"
|
|
144
|
+
else " ✗ local API disabled"
|
|
145
|
+
if z == "disabled"
|
|
146
|
+
else " ✗ unavailable"
|
|
147
|
+
)
|
|
148
|
+
if z == "disabled":
|
|
149
|
+
print(
|
|
150
|
+
'\nFix:\n Zotero → Settings → Advanced\n Enable "Allow other applications on this computer to communicate with Zotero"'
|
|
151
|
+
)
|
|
152
|
+
return 1
|
|
153
|
+
try:
|
|
154
|
+
cols, atts, books = zotero.collections(), zotero.attachments(), notebook.notebooks()
|
|
155
|
+
print(
|
|
156
|
+
f" ✓ {len(cols)} collections\n ✓ {len(atts)} PDF attachments\n\nNotebookLM\n ✓ authenticated\n ✓ account reachable\n ✓ {len(books)} notebooks"
|
|
157
|
+
)
|
|
158
|
+
except (
|
|
159
|
+
zotero.ZoteroUnavailable,
|
|
160
|
+
zotero.LocalApiDisabled,
|
|
161
|
+
notebook.NotebookError,
|
|
162
|
+
OSError,
|
|
163
|
+
) as exc:
|
|
164
|
+
print(f"\nNotebookLM\n ✗ {exc}\n\nFix: run ilmzot setup")
|
|
165
|
+
return 1
|
|
166
|
+
path = state.state_path()
|
|
167
|
+
try:
|
|
168
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
169
|
+
print(f"\nilmzot\n ✓ state directory writable\n ✓ version {__version__}\n\nReady.")
|
|
170
|
+
except OSError as exc:
|
|
171
|
+
print(f"\nilmzot\n ✗ state directory unavailable: {exc}")
|
|
172
|
+
return 1
|
|
173
|
+
return 0
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def main(argv: list[str] | None = None) -> int:
|
|
177
|
+
args = parser().parse_args(argv)
|
|
178
|
+
return (
|
|
179
|
+
{
|
|
180
|
+
"ls": command_ls,
|
|
181
|
+
"list": command_ls,
|
|
182
|
+
"sync": command_sync,
|
|
183
|
+
"setup": command_setup,
|
|
184
|
+
"status": command_status,
|
|
185
|
+
"doctor": command_doctor,
|
|
186
|
+
}[args.command](args)
|
|
187
|
+
if args.command == "sync" or args.command == "setup"
|
|
188
|
+
else {
|
|
189
|
+
"ls": command_ls,
|
|
190
|
+
"list": command_ls,
|
|
191
|
+
"status": command_status,
|
|
192
|
+
"doctor": command_doctor,
|
|
193
|
+
}[args.command]()
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
if __name__ == "__main__":
|
|
198
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""NotebookLM boundary. The unofficial dependency is intentionally isolated here."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import importlib
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class NotebookError(RuntimeError):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _client() -> Any:
|
|
18
|
+
try:
|
|
19
|
+
module = importlib.import_module("notebooklm")
|
|
20
|
+
factory = getattr(module, "NotebookLMClient", None)
|
|
21
|
+
if factory:
|
|
22
|
+
return factory.from_storage()
|
|
23
|
+
return module
|
|
24
|
+
except ImportError as exc:
|
|
25
|
+
raise NotebookError("Install ilmzot's dependencies before using NotebookLM") from exc
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def authenticate(browser: str | None = None, login: bool = False) -> None:
|
|
29
|
+
if login:
|
|
30
|
+
subprocess.run([sys.executable, "-m", "notebooklm", "login"], check=True)
|
|
31
|
+
return
|
|
32
|
+
# notebooklm-py owns cookie extraction and its browser/profile discovery.
|
|
33
|
+
selector = browser or "auto"
|
|
34
|
+
subprocess.run(
|
|
35
|
+
[sys.executable, "-m", "notebooklm", "login", "--browser-cookies", selector],
|
|
36
|
+
check=True,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
async def _notebooks_async() -> list[Any]:
|
|
41
|
+
context = _client()
|
|
42
|
+
async with context as client:
|
|
43
|
+
return await client.notebooks.list()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _as_dict(item: Any) -> dict[str, Any]:
|
|
47
|
+
return item if isinstance(item, dict) else {"id": item.id, "title": item.title}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def notebooks() -> list[dict[str, Any]]:
|
|
51
|
+
return [_as_dict(item) for item in asyncio.run(_notebooks_async())]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def sync_notebook(
|
|
55
|
+
name: str, files: list[tuple[str, Path, str]], dry_run: bool = False
|
|
56
|
+
) -> tuple[int, int, int, str]:
|
|
57
|
+
books = notebooks()
|
|
58
|
+
matches = [b for b in books if b.get("title", b.get("name", "")).casefold() == name.casefold()]
|
|
59
|
+
if len(matches) > 1:
|
|
60
|
+
raise NotebookError(f'Multiple NotebookLM notebooks named "{name}"')
|
|
61
|
+
book = matches[0] if matches else None
|
|
62
|
+
if not book:
|
|
63
|
+
if dry_run:
|
|
64
|
+
return (len(files), 0, 0, "")
|
|
65
|
+
|
|
66
|
+
async def create_book():
|
|
67
|
+
async with _client() as client:
|
|
68
|
+
return await client.notebooks.create(name)
|
|
69
|
+
|
|
70
|
+
book = _as_dict(asyncio.run(create_book()))
|
|
71
|
+
notebook_id = str(book.get("id", book.get("notebook_id", "")))
|
|
72
|
+
if dry_run:
|
|
73
|
+
return (len(files), 0, 0, notebook_id)
|
|
74
|
+
|
|
75
|
+
async def list_sources():
|
|
76
|
+
async with _client() as client:
|
|
77
|
+
return await client.sources.list(notebook_id)
|
|
78
|
+
|
|
79
|
+
listed = [_as_dict(s) for s in asyncio.run(list_sources())]
|
|
80
|
+
added = updated = skipped = 0
|
|
81
|
+
for key, path, digest in files:
|
|
82
|
+
source = next(
|
|
83
|
+
(
|
|
84
|
+
s
|
|
85
|
+
for s in listed
|
|
86
|
+
if s.get("title") == path.name or s.get("metadata", {}).get("attachment_key") == key
|
|
87
|
+
),
|
|
88
|
+
None,
|
|
89
|
+
)
|
|
90
|
+
if source and source.get("metadata", {}).get("sha256") == digest:
|
|
91
|
+
skipped += 1
|
|
92
|
+
continue
|
|
93
|
+
if source:
|
|
94
|
+
|
|
95
|
+
async def delete_source(source_id: str):
|
|
96
|
+
async with _client() as client:
|
|
97
|
+
await client.sources.delete(notebook_id, source_id)
|
|
98
|
+
|
|
99
|
+
asyncio.run(delete_source(str(source["id"])))
|
|
100
|
+
updated += 1
|
|
101
|
+
else:
|
|
102
|
+
added += 1
|
|
103
|
+
|
|
104
|
+
async def upload_source(upload_path: Path):
|
|
105
|
+
async with _client() as client:
|
|
106
|
+
await client.sources.add_file(
|
|
107
|
+
notebook_id, upload_path, wait=True, title=upload_path.name
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
asyncio.run(upload_source(path))
|
|
111
|
+
return added, updated, skipped, notebook_id
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Persistent, non-secret sync state."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def state_path() -> Path:
|
|
13
|
+
root = os.environ.get("XDG_DATA_HOME")
|
|
14
|
+
return (Path(root) if root else Path.home() / ".local" / "share") / "ilmzot" / "state.json"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def load() -> dict[str, Any]:
|
|
18
|
+
path = state_path()
|
|
19
|
+
if not path.exists():
|
|
20
|
+
return {"sources": {}}
|
|
21
|
+
data = json.loads(path.read_text())
|
|
22
|
+
data.setdefault("sources", {})
|
|
23
|
+
return data
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def save(data: dict[str, Any]) -> None:
|
|
27
|
+
path = state_path()
|
|
28
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
29
|
+
temporary = path.with_suffix(".tmp")
|
|
30
|
+
temporary.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n")
|
|
31
|
+
temporary.replace(path)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def sha256_file(path: Path) -> str:
|
|
35
|
+
digest = hashlib.sha256()
|
|
36
|
+
with path.open("rb") as handle:
|
|
37
|
+
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
|
38
|
+
digest.update(chunk)
|
|
39
|
+
return digest.hexdigest()
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""Small client for Zotero's documented local HTTP API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from urllib.error import HTTPError, URLError
|
|
9
|
+
from urllib.parse import unquote, urlparse
|
|
10
|
+
from urllib.request import HTTPRedirectHandler, Request, build_opener, urlopen
|
|
11
|
+
|
|
12
|
+
BASE = "http://localhost:23119/api"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ZoteroUnavailable(RuntimeError):
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class LocalApiDisabled(RuntimeError):
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CollectionNotFound(RuntimeError):
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True)
|
|
28
|
+
class Collection:
|
|
29
|
+
key: str
|
|
30
|
+
name: str
|
|
31
|
+
parent: str | None = None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True)
|
|
35
|
+
class Attachment:
|
|
36
|
+
key: str
|
|
37
|
+
title: str
|
|
38
|
+
path: Path
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _get(path: str) -> object:
|
|
42
|
+
try:
|
|
43
|
+
with urlopen(
|
|
44
|
+
Request(BASE + path, headers={"Accept": "application/json"}), timeout=3
|
|
45
|
+
) as response:
|
|
46
|
+
return json.load(response)
|
|
47
|
+
except HTTPError as exc:
|
|
48
|
+
if exc.code in (403, 404):
|
|
49
|
+
raise LocalApiDisabled("Zotero local API access is disabled") from exc
|
|
50
|
+
raise ZoteroUnavailable(f"Zotero returned HTTP {exc.code}") from exc
|
|
51
|
+
except (URLError, TimeoutError, OSError) as exc:
|
|
52
|
+
raise ZoteroUnavailable("Zotero is not running") from exc
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def check() -> str:
|
|
56
|
+
try:
|
|
57
|
+
_get("/users/0/items?limit=1")
|
|
58
|
+
except LocalApiDisabled:
|
|
59
|
+
return "disabled"
|
|
60
|
+
except ZoteroUnavailable:
|
|
61
|
+
return "unavailable"
|
|
62
|
+
return "available"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def collections() -> list[Collection]:
|
|
66
|
+
raw = _get("/users/0/collections") # type: ignore
|
|
67
|
+
return [
|
|
68
|
+
Collection(str(x["key"]), x["data"]["name"], x["data"].get("parentCollection")) for x in raw
|
|
69
|
+
] # type: ignore
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _local_file_path(key: str) -> Path | None:
|
|
73
|
+
class NoRedirect(HTTPRedirectHandler):
|
|
74
|
+
def redirect_request(self, req, fp, code, msg, headers, newurl):
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
response = build_opener(NoRedirect).open(
|
|
79
|
+
Request(f"{BASE}/users/0/items/{key}/file"), timeout=3
|
|
80
|
+
)
|
|
81
|
+
except HTTPError as exc:
|
|
82
|
+
if exc.code in (301, 302, 303, 307, 308):
|
|
83
|
+
location = exc.headers.get("Location", "")
|
|
84
|
+
parsed = urlparse(location)
|
|
85
|
+
return Path(unquote(parsed.path)) if parsed.scheme == "file" else None
|
|
86
|
+
return None
|
|
87
|
+
else:
|
|
88
|
+
response.close()
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def display_names(items: list[Collection]) -> dict[str, str]:
|
|
93
|
+
by_key = {item.key: item for item in items}
|
|
94
|
+
|
|
95
|
+
def path(item: Collection) -> str:
|
|
96
|
+
parents, current = [], item
|
|
97
|
+
while current:
|
|
98
|
+
parents.append(current.name)
|
|
99
|
+
current = by_key.get(current.parent) if current.parent else None
|
|
100
|
+
return " / ".join(reversed(parents))
|
|
101
|
+
|
|
102
|
+
return {item.key: path(item) for item in items}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def find_collection(name: str) -> Collection:
|
|
106
|
+
matches = [c for c in collections() if c.name.casefold() == name.casefold()]
|
|
107
|
+
if not matches:
|
|
108
|
+
raise CollectionNotFound(f'Collection not found: "{name}"')
|
|
109
|
+
if len(matches) > 1:
|
|
110
|
+
raise CollectionNotFound(f'Multiple collections named "{name}"; use a unique name')
|
|
111
|
+
return matches[0]
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def attachments(collection_key: str | None = None) -> list[Attachment]:
|
|
115
|
+
suffix = f"/users/0/collections/{collection_key}/items" if collection_key else "/users/0/items"
|
|
116
|
+
raw = _get(suffix + "?limit=10000") # type: ignore
|
|
117
|
+
result = []
|
|
118
|
+
for item in raw: # type: ignore
|
|
119
|
+
data = item.get("data", {})
|
|
120
|
+
if data.get("itemType") == "attachment" and data.get("contentType") == "application/pdf":
|
|
121
|
+
path = data.get("path", "")
|
|
122
|
+
local_path = (
|
|
123
|
+
Path(path)
|
|
124
|
+
if path and not path.startswith("storage:")
|
|
125
|
+
else _local_file_path(item["key"])
|
|
126
|
+
)
|
|
127
|
+
if local_path:
|
|
128
|
+
result.append(Attachment(item["key"], data.get("title", "Untitled"), local_path))
|
|
129
|
+
return result
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from ilmzot.cli import parser
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_positional_collection_and_options():
|
|
5
|
+
args = parser().parse_args(["sync", "Climate Modelling", "-n", "Research", "-d"])
|
|
6
|
+
assert args.collection == "Climate Modelling"
|
|
7
|
+
assert args.notebook == "Research" and args.dry_run
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_all():
|
|
11
|
+
assert parser().parse_args(["sync", "--all"]).all
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_sync_requires_exactly_one_scope(capsys, monkeypatch):
|
|
15
|
+
from ilmzot import cli
|
|
16
|
+
|
|
17
|
+
assert cli.main(["sync"]) == 2
|
|
18
|
+
assert cli.main(["sync", "x", "--all"]) == 2
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from ilmzot import notebook
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_duplicate_notebooks(monkeypatch, tmp_path):
|
|
7
|
+
monkeypatch.setattr(
|
|
8
|
+
notebook, "notebooks", lambda: [{"id": "1", "title": "X"}, {"id": "2", "title": "x"}]
|
|
9
|
+
)
|
|
10
|
+
monkeypatch.setattr(notebook, "_client", lambda: object())
|
|
11
|
+
with pytest.raises(notebook.NotebookError):
|
|
12
|
+
notebook.sync_notebook("X", [], dry_run=True)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
|
|
3
|
+
from ilmzot import state
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_state_path_xdg(monkeypatch, tmp_path):
|
|
7
|
+
monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path))
|
|
8
|
+
assert state.state_path() == tmp_path / "ilmzot" / "state.json"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_state_round_trip_and_hash(monkeypatch, tmp_path):
|
|
12
|
+
monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path))
|
|
13
|
+
path = tmp_path / "a.pdf"
|
|
14
|
+
path.write_bytes(b"pdf")
|
|
15
|
+
assert state.sha256_file(path) == hashlib.sha256(b"pdf").hexdigest()
|
|
16
|
+
state.save({"sources": {"a": {"sha256": state.sha256_file(path)}}})
|
|
17
|
+
assert state.load()["sources"]["a"]["sha256"]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from ilmzot import zotero
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_nested_display_names():
|
|
7
|
+
items = [zotero.Collection("a", "Energy"), zotero.Collection("b", "Electricity Markets", "a")]
|
|
8
|
+
assert zotero.display_names(items)["b"] == "Energy / Electricity Markets"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_find_collection_case_insensitive(monkeypatch):
|
|
12
|
+
monkeypatch.setattr(
|
|
13
|
+
zotero, "collections", lambda: [zotero.Collection("a", "Climate Modelling")]
|
|
14
|
+
)
|
|
15
|
+
assert zotero.find_collection("climate modelling").key == "a"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_collection_not_found(monkeypatch):
|
|
19
|
+
monkeypatch.setattr(zotero, "collections", list)
|
|
20
|
+
with pytest.raises(zotero.CollectionNotFound):
|
|
21
|
+
zotero.find_collection("missing")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_duplicate_collection_names(monkeypatch):
|
|
25
|
+
monkeypatch.setattr(
|
|
26
|
+
zotero,
|
|
27
|
+
"collections",
|
|
28
|
+
lambda: [zotero.Collection("a", "Energy"), zotero.Collection("b", "energy")],
|
|
29
|
+
)
|
|
30
|
+
with pytest.raises(zotero.CollectionNotFound):
|
|
31
|
+
zotero.find_collection("Energy")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_unavailable(monkeypatch):
|
|
35
|
+
def fail(*args, **kwargs):
|
|
36
|
+
raise zotero.URLError("offline")
|
|
37
|
+
|
|
38
|
+
monkeypatch.setattr(zotero, "urlopen", fail)
|
|
39
|
+
assert zotero.check() == "unavailable"
|
ilmzot-0.1.0/uv.lock
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
version = 1
|
|
2
|
+
revision = 3
|
|
3
|
+
requires-python = ">=3.10, <3.13"
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "anyio"
|
|
7
|
+
version = "4.14.2"
|
|
8
|
+
source = { registry = "https://pypi.org/simple" }
|
|
9
|
+
dependencies = [
|
|
10
|
+
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
|
|
11
|
+
{ name = "idna" },
|
|
12
|
+
{ name = "typing-extensions" },
|
|
13
|
+
]
|
|
14
|
+
sdist = { url = "https://files.pythonhosted.org/packages/61/cc/a381afa6efea9f496eff839d4a6a1aed3bfafc7b3ab4b0d1b243a12573dd/anyio-4.14.2.tar.gz", hash = "sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f", size = 260176, upload-time = "2026-07-12T20:29:07.082Z" }
|
|
15
|
+
wheels = [
|
|
16
|
+
{ url = "https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494", size = 125813, upload-time = "2026-07-12T20:29:05.763Z" },
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[[package]]
|
|
20
|
+
name = "certifi"
|
|
21
|
+
version = "2026.7.22"
|
|
22
|
+
source = { registry = "https://pypi.org/simple" }
|
|
23
|
+
sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" }
|
|
24
|
+
wheels = [
|
|
25
|
+
{ url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" },
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[[package]]
|
|
29
|
+
name = "click"
|
|
30
|
+
version = "8.5.0"
|
|
31
|
+
source = { registry = "https://pypi.org/simple" }
|
|
32
|
+
sdist = { url = "https://files.pythonhosted.org/packages/c7/0e/7fa0ef50764b67090eca4114772a2abf8b6148198475e54c660b97caeee6/click-8.5.0.tar.gz", hash = "sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34", size = 382235, upload-time = "2026-08-26T13:33:14.56Z" }
|
|
33
|
+
wheels = [
|
|
34
|
+
{ url = "https://files.pythonhosted.org/packages/58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl", hash = "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360", size = 125251, upload-time = "2026-08-26T13:33:12.928Z" },
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[[package]]
|
|
38
|
+
name = "colorama"
|
|
39
|
+
version = "0.4.6"
|
|
40
|
+
source = { registry = "https://pypi.org/simple" }
|
|
41
|
+
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
|
42
|
+
wheels = [
|
|
43
|
+
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
[[package]]
|
|
47
|
+
name = "exceptiongroup"
|
|
48
|
+
version = "1.3.1"
|
|
49
|
+
source = { registry = "https://pypi.org/simple" }
|
|
50
|
+
dependencies = [
|
|
51
|
+
{ name = "typing-extensions" },
|
|
52
|
+
]
|
|
53
|
+
sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
|
|
54
|
+
wheels = [
|
|
55
|
+
{ url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" },
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
[[package]]
|
|
59
|
+
name = "filelock"
|
|
60
|
+
version = "3.32.5"
|
|
61
|
+
source = { registry = "https://pypi.org/simple" }
|
|
62
|
+
sdist = { url = "https://files.pythonhosted.org/packages/0a/a0/50c2c0ce5e74d7721bbb1b19a26ebd339aac5878553a6e35308c2f31f935/filelock-3.32.5.tar.gz", hash = "sha256:f6a6a28f743f9b95ce19db5abe0f376f75eb56517dff21e1a4751e2657d3e83d", size = 222838, upload-time = "2026-08-31T18:56:34.729Z" }
|
|
63
|
+
wheels = [
|
|
64
|
+
{ url = "https://files.pythonhosted.org/packages/36/d2/b70a31e13d04456d28493f31d2aa087e99eeb2767ef0293b2625727ccb8c/filelock-3.32.5-py3-none-any.whl", hash = "sha256:142cd9fa77a872c5e78c62329a0d15278fadc686eb89e760017968961a4fd6b2", size = 100003, upload-time = "2026-08-31T18:56:33.078Z" },
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
[[package]]
|
|
68
|
+
name = "h11"
|
|
69
|
+
version = "0.16.0"
|
|
70
|
+
source = { registry = "https://pypi.org/simple" }
|
|
71
|
+
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
|
|
72
|
+
wheels = [
|
|
73
|
+
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
[[package]]
|
|
77
|
+
name = "httpcore"
|
|
78
|
+
version = "1.0.9"
|
|
79
|
+
source = { registry = "https://pypi.org/simple" }
|
|
80
|
+
dependencies = [
|
|
81
|
+
{ name = "certifi" },
|
|
82
|
+
{ name = "h11" },
|
|
83
|
+
]
|
|
84
|
+
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
|
|
85
|
+
wheels = [
|
|
86
|
+
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
[[package]]
|
|
90
|
+
name = "httpx"
|
|
91
|
+
version = "0.28.1"
|
|
92
|
+
source = { registry = "https://pypi.org/simple" }
|
|
93
|
+
dependencies = [
|
|
94
|
+
{ name = "anyio" },
|
|
95
|
+
{ name = "certifi" },
|
|
96
|
+
{ name = "httpcore" },
|
|
97
|
+
{ name = "idna" },
|
|
98
|
+
]
|
|
99
|
+
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
|
|
100
|
+
wheels = [
|
|
101
|
+
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
[[package]]
|
|
105
|
+
name = "idna"
|
|
106
|
+
version = "3.19"
|
|
107
|
+
source = { registry = "https://pypi.org/simple" }
|
|
108
|
+
sdist = { url = "https://files.pythonhosted.org/packages/5f/f7/abb373e5757eaec4b922b92f97ec8d6d7e057cf06778247604fbc4e7c3f3/idna-3.19.tar.gz", hash = "sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15", size = 215237, upload-time = "2026-08-18T05:14:24.27Z" }
|
|
109
|
+
wheels = [
|
|
110
|
+
{ url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" },
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
[[package]]
|
|
114
|
+
name = "ilmzot"
|
|
115
|
+
version = "0.1.0"
|
|
116
|
+
source = { editable = "." }
|
|
117
|
+
dependencies = [
|
|
118
|
+
{ name = "notebooklm-py", extra = ["cookies"] },
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
[package.optional-dependencies]
|
|
122
|
+
dev = [
|
|
123
|
+
{ name = "pytest" },
|
|
124
|
+
{ name = "ruff" },
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
[package.metadata]
|
|
128
|
+
requires-dist = [
|
|
129
|
+
{ name = "notebooklm-py", extras = ["cookies"], specifier = ">=0.8.1,<0.9" },
|
|
130
|
+
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=8" },
|
|
131
|
+
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.6" },
|
|
132
|
+
]
|
|
133
|
+
provides-extras = ["dev"]
|
|
134
|
+
|
|
135
|
+
[[package]]
|
|
136
|
+
name = "iniconfig"
|
|
137
|
+
version = "2.3.0"
|
|
138
|
+
source = { registry = "https://pypi.org/simple" }
|
|
139
|
+
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
|
|
140
|
+
wheels = [
|
|
141
|
+
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
[[package]]
|
|
145
|
+
name = "markdown-it-py"
|
|
146
|
+
version = "4.2.0"
|
|
147
|
+
source = { registry = "https://pypi.org/simple" }
|
|
148
|
+
dependencies = [
|
|
149
|
+
{ name = "mdurl" },
|
|
150
|
+
]
|
|
151
|
+
sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" }
|
|
152
|
+
wheels = [
|
|
153
|
+
{ url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" },
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
[[package]]
|
|
157
|
+
name = "mdurl"
|
|
158
|
+
version = "0.1.2"
|
|
159
|
+
source = { registry = "https://pypi.org/simple" }
|
|
160
|
+
sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
|
|
161
|
+
wheels = [
|
|
162
|
+
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
|
|
163
|
+
]
|
|
164
|
+
|
|
165
|
+
[[package]]
|
|
166
|
+
name = "notebooklm-py"
|
|
167
|
+
version = "0.8.1"
|
|
168
|
+
source = { registry = "https://pypi.org/simple" }
|
|
169
|
+
dependencies = [
|
|
170
|
+
{ name = "click" },
|
|
171
|
+
{ name = "filelock" },
|
|
172
|
+
{ name = "httpx" },
|
|
173
|
+
{ name = "rich" },
|
|
174
|
+
]
|
|
175
|
+
sdist = { url = "https://files.pythonhosted.org/packages/5c/ff/5c352c1bf66a0f5ae59157ceb544991d5733ef379d4e17bc70253c22fda4/notebooklm_py-0.8.1.tar.gz", hash = "sha256:43ba51a9609a9530bfcd9ad06c1035edeac4b0e40156a5a7ee7ee302621666cd", size = 16334264, upload-time = "2026-08-14T15:00:29.158Z" }
|
|
176
|
+
wheels = [
|
|
177
|
+
{ url = "https://files.pythonhosted.org/packages/15/82/bca1ba131ba994675dfd44cf7f731f9725e0265b056ad8b6bdc0467c9201/notebooklm_py-0.8.1-py3-none-any.whl", hash = "sha256:4d7bc3e16bedde8296a85aa0376a69e49c744d82ffa9836447c584353e2001d8", size = 1617300, upload-time = "2026-08-14T15:00:26.95Z" },
|
|
178
|
+
]
|
|
179
|
+
|
|
180
|
+
[package.optional-dependencies]
|
|
181
|
+
cookies = [
|
|
182
|
+
{ name = "rookiepy" },
|
|
183
|
+
]
|
|
184
|
+
|
|
185
|
+
[[package]]
|
|
186
|
+
name = "packaging"
|
|
187
|
+
version = "26.3"
|
|
188
|
+
source = { registry = "https://pypi.org/simple" }
|
|
189
|
+
sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" }
|
|
190
|
+
wheels = [
|
|
191
|
+
{ url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" },
|
|
192
|
+
]
|
|
193
|
+
|
|
194
|
+
[[package]]
|
|
195
|
+
name = "pluggy"
|
|
196
|
+
version = "1.6.0"
|
|
197
|
+
source = { registry = "https://pypi.org/simple" }
|
|
198
|
+
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
|
|
199
|
+
wheels = [
|
|
200
|
+
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
|
201
|
+
]
|
|
202
|
+
|
|
203
|
+
[[package]]
|
|
204
|
+
name = "pygments"
|
|
205
|
+
version = "2.21.0"
|
|
206
|
+
source = { registry = "https://pypi.org/simple" }
|
|
207
|
+
sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" }
|
|
208
|
+
wheels = [
|
|
209
|
+
{ url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" },
|
|
210
|
+
]
|
|
211
|
+
|
|
212
|
+
[[package]]
|
|
213
|
+
name = "pytest"
|
|
214
|
+
version = "9.1.1"
|
|
215
|
+
source = { registry = "https://pypi.org/simple" }
|
|
216
|
+
dependencies = [
|
|
217
|
+
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
|
218
|
+
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
|
|
219
|
+
{ name = "iniconfig" },
|
|
220
|
+
{ name = "packaging" },
|
|
221
|
+
{ name = "pluggy" },
|
|
222
|
+
{ name = "pygments" },
|
|
223
|
+
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
|
224
|
+
]
|
|
225
|
+
sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" }
|
|
226
|
+
wheels = [
|
|
227
|
+
{ url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" },
|
|
228
|
+
]
|
|
229
|
+
|
|
230
|
+
[[package]]
|
|
231
|
+
name = "rich"
|
|
232
|
+
version = "15.0.0"
|
|
233
|
+
source = { registry = "https://pypi.org/simple" }
|
|
234
|
+
dependencies = [
|
|
235
|
+
{ name = "markdown-it-py" },
|
|
236
|
+
{ name = "pygments" },
|
|
237
|
+
]
|
|
238
|
+
sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" }
|
|
239
|
+
wheels = [
|
|
240
|
+
{ url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" },
|
|
241
|
+
]
|
|
242
|
+
|
|
243
|
+
[[package]]
|
|
244
|
+
name = "rookiepy"
|
|
245
|
+
version = "0.5.6"
|
|
246
|
+
source = { registry = "https://pypi.org/simple" }
|
|
247
|
+
sdist = { url = "https://files.pythonhosted.org/packages/52/b7/b75e3bb8cdd0210a0ffb106002b678170c9a3a50ae1dc1c9bc1f701b4452/rookiepy-0.5.6.tar.gz", hash = "sha256:efa6a93b119478a96b3d8c4454215c4f1af316a24b3b3b3015b73c1c1d887078", size = 45225, upload-time = "2024-11-01T19:28:37.537Z" }
|
|
248
|
+
wheels = [
|
|
249
|
+
{ url = "https://files.pythonhosted.org/packages/0f/64/6d8c3a530da92abed05cf12ec80a9765f6fbb5657013624b16a0f7a3a7d7/rookiepy-0.5.6-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b138d59447317438564475b3bfa80ea6adbbdc65edfa44f14c1565b90ce64257", size = 1403633, upload-time = "2024-11-01T19:25:42.113Z" },
|
|
250
|
+
{ url = "https://files.pythonhosted.org/packages/66/77/58d20e6e45b38f9950486ca6bd8629eb0385ec9f68aadbee67db03e4fd48/rookiepy-0.5.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed2c39cb06b8b2edb186d7b0afe08dbb3257bba94f2b18e3bb3ae8a80b77e01b", size = 1312421, upload-time = "2024-11-01T19:25:45.329Z" },
|
|
251
|
+
{ url = "https://files.pythonhosted.org/packages/72/4b/d3ef49aefb2e73ed86d64ffdc90e369eae119321715c259ccbe8dc050a5f/rookiepy-0.5.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b547cd5ebf875635bd96d8c3365be3f2c664bbab45fda36c8c5b880a4d17512", size = 2678357, upload-time = "2024-11-01T19:25:47.681Z" },
|
|
252
|
+
{ url = "https://files.pythonhosted.org/packages/bb/34/770004fc5b6c77d2d3708f4c3938e9939b0248d06f8dfeee124e128f99e3/rookiepy-0.5.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:65e6e6939d7d87942795d8275d56a17a7c046732420aa10c05e2b63ee6345e93", size = 2583944, upload-time = "2024-11-01T19:25:50.241Z" },
|
|
253
|
+
{ url = "https://files.pythonhosted.org/packages/7e/d0/493838aae78dfeca1b3246515cb67b53f55997026b4372bf80ea81bb9206/rookiepy-0.5.6-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c370e4eb33f8848e686b4a14183078411fccb882fc9040823e30a8597ee00b8a", size = 2992938, upload-time = "2024-11-01T19:25:52.56Z" },
|
|
254
|
+
{ url = "https://files.pythonhosted.org/packages/75/19/412239d02d4f73baea9e9f7f2b21ebd187c3bf38ef4ec517bb2a839e0183/rookiepy-0.5.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d2e2be75c052375b9f489fefbf359935f1882fd7f80b724a976d2dd1ec462d9d", size = 2870215, upload-time = "2024-11-01T19:25:55.182Z" },
|
|
255
|
+
{ url = "https://files.pythonhosted.org/packages/69/85/b46d140837570114a7cdb57626775ea5b456beb4ce30dc9972a616cdd709/rookiepy-0.5.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c73b5de7db85fc5f398d1f50bc6b1e3672ddeb76fd2c9721980eaeb530e69d13", size = 2979196, upload-time = "2024-11-01T19:25:57.615Z" },
|
|
256
|
+
{ url = "https://files.pythonhosted.org/packages/2a/39/7594b696befce2aa5e50dd6f7a632b50ea94d9ec14651f1b4651f1ed24e1/rookiepy-0.5.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67391fca6afc3251c8bbd4a395f287ebfcb35652cf9a4aadf5b4df73481fac08", size = 2800928, upload-time = "2024-11-01T19:25:59.902Z" },
|
|
257
|
+
{ url = "https://files.pythonhosted.org/packages/c5/b9/65d5ae2ee2b86a3992b9d10eeecc822c590e311fff4f25eed5eeb05b4cfc/rookiepy-0.5.6-cp310-none-win_amd64.whl", hash = "sha256:9ea86008705ff0dbbb1f74cf4847c71a6c6a848427797e0086401d9ef0117432", size = 2440815, upload-time = "2024-11-01T19:26:02.336Z" },
|
|
258
|
+
{ url = "https://files.pythonhosted.org/packages/65/26/26bc3e2f519f14fd95f1cfeeef533c5c3f2bc356e01e8599e2cbfcba926f/rookiepy-0.5.6-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:265bb767edd2e4e47d3de534871f94181572236d2ae3925597f4ceba557a1525", size = 1403708, upload-time = "2024-11-01T19:26:04.704Z" },
|
|
259
|
+
{ url = "https://files.pythonhosted.org/packages/cc/73/234f2c42b4f73b96e4b5722afae664c0870e69f14dd1408ca06a5015b3a2/rookiepy-0.5.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:89253f803fcd4fbfa9f06961af9bc59efeade69b377d0d7569f5591e2e9a1b0e", size = 1312473, upload-time = "2024-11-01T19:26:07.5Z" },
|
|
260
|
+
{ url = "https://files.pythonhosted.org/packages/1c/1c/30ed2342b68c3e1db1d362e2e05577318c2fdc580a41026ddf8d504cbb7f/rookiepy-0.5.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d2dc1770c2d77f84961ea01dce6fab32f771fb84066c94f372a69d26f61981a", size = 2678396, upload-time = "2024-11-01T19:26:11.229Z" },
|
|
261
|
+
{ url = "https://files.pythonhosted.org/packages/21/7a/5dd1f2b95180bd45b7c49eb15d189d16445255dbc192b4565c56bcd8b13d/rookiepy-0.5.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8e69d53476ac66171823935f4fc93adf30556be37f77b775931f7c5a6d841260", size = 2583956, upload-time = "2024-11-01T19:26:13.962Z" },
|
|
262
|
+
{ url = "https://files.pythonhosted.org/packages/87/56/d5f40ec7b29949db27c5d7da3c2f9f8d3b9fe5cb19341a899f0fa3c065b2/rookiepy-0.5.6-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1bd868521032ae24c44946906c88518fc9bb941befe8f6d7e1d8ff355f67b598", size = 2992867, upload-time = "2024-11-01T19:26:16.801Z" },
|
|
263
|
+
{ url = "https://files.pythonhosted.org/packages/e8/83/b382531f0e8cb3d827158f788a73b014050f5b0fd0f725a4f1994ec08311/rookiepy-0.5.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f29a324c8b44a8b7bbef632001c76b1764d647164aaaf165b5496310f57328cc", size = 2870293, upload-time = "2024-11-01T19:26:19.499Z" },
|
|
264
|
+
{ url = "https://files.pythonhosted.org/packages/a5/49/43915b3e2b21b1455b6edacc71f27056c87b311325268465bf45f56393ca/rookiepy-0.5.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd95eb157acee95e3079f86a26cadf720f1d7ef4900735c8b5ff3a12c29f7880", size = 2979356, upload-time = "2024-11-01T19:26:21.891Z" },
|
|
265
|
+
{ url = "https://files.pythonhosted.org/packages/00/92/e58af41f1847f746fbcda17dec454e1f249f931ca38ecb426ef93f315c3d/rookiepy-0.5.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afb0fa7a973d5cf7833ddd46854d823dad70027457fff44f3161d0128d884e17", size = 2800937, upload-time = "2024-11-01T19:26:24.051Z" },
|
|
266
|
+
{ url = "https://files.pythonhosted.org/packages/6d/2e/5def20a848e855c1c10bf86a3e2db7008d242b4e117846a635ba6e006d75/rookiepy-0.5.6-cp311-none-win_amd64.whl", hash = "sha256:d1ba846c1d066530a32a77fc48b8e4197795151ab8940dea86f319849b544d5b", size = 2440816, upload-time = "2024-11-01T19:26:26.716Z" },
|
|
267
|
+
{ url = "https://files.pythonhosted.org/packages/de/f3/70737571ed2fb7cd9e360b0ec6ce637c8894d712632f7e13b442f5286eef/rookiepy-0.5.6-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:96737f44eab781d5a010b71f7cd70ef43c6a6977dc1c54d019158b8fcfe57167", size = 1404166, upload-time = "2024-11-01T19:26:29.356Z" },
|
|
268
|
+
{ url = "https://files.pythonhosted.org/packages/05/ae/9b69ca2821149cdb5061d6d3098a003affd7ca71b79166e98aed5d7beac8/rookiepy-0.5.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:37a8fc437f49672387d4a512df5522a724c4440442e64cad84680f1718f68c50", size = 1311996, upload-time = "2024-11-01T19:26:31.659Z" },
|
|
269
|
+
{ url = "https://files.pythonhosted.org/packages/d7/29/3a7a44027d542c889f9605843d5c7f8df105eb0b79ad45750d7c0b18875b/rookiepy-0.5.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22d0ece1ef732045f0482f72bc199cad81878154ceb62780c1e2f297c62df7e2", size = 2678772, upload-time = "2024-11-01T19:26:33.995Z" },
|
|
270
|
+
{ url = "https://files.pythonhosted.org/packages/6b/62/fb0a8267604f11e7df565509834279b179eadb785e4d5e0b99cc096a27f4/rookiepy-0.5.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:307943d5780492c533bfeb6bc89d1a4b3e5b91b25af6fc1de280acc1e2ff16f6", size = 2583775, upload-time = "2024-11-01T19:26:36.665Z" },
|
|
271
|
+
{ url = "https://files.pythonhosted.org/packages/5b/0b/c1f85445a00107f97f488f5f14ac25f1994c8436c543263e43865380e553/rookiepy-0.5.6-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c607eef14f64ba284f3754449b6eb079aa8aaa66b443d0cdd2ec9aaa1814a8da", size = 2993272, upload-time = "2024-11-01T19:26:39.668Z" },
|
|
272
|
+
{ url = "https://files.pythonhosted.org/packages/4c/b4/7ca3ab1860f38e1d79df051aad6e8826732f3dee77fd694f226e20e8389c/rookiepy-0.5.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:19f4b95436705b38e4a55cfa9f3d999bb4edc6a7f91ba13b1b90bb14b1796fdb", size = 2871214, upload-time = "2024-11-01T19:26:42.24Z" },
|
|
273
|
+
{ url = "https://files.pythonhosted.org/packages/8a/36/f45c3a833595285d790d91de20e64dff5ae1780a407033a018f0efccf852/rookiepy-0.5.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:95b881f2f3d93bb56808eaaf8ac9e0e5e3245dda837edab194834a3caaadcbe7", size = 2971688, upload-time = "2024-11-01T19:26:44.341Z" },
|
|
274
|
+
{ url = "https://files.pythonhosted.org/packages/19/c4/59b5e0b6efcd919683578605bfac4bc112ac34de5a871094fe3efa58896b/rookiepy-0.5.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c49a0b59bc0329928a369a479f9b1c16cbe3a2548dd8273b13515c7ced73449", size = 2801469, upload-time = "2024-11-01T19:26:47.997Z" },
|
|
275
|
+
{ url = "https://files.pythonhosted.org/packages/e4/38/bba16936ece960db7ed10f8ebf05dc6228959511d3837020da1e9b7dcdf6/rookiepy-0.5.6-cp312-none-win_amd64.whl", hash = "sha256:6bb956c9f2dece5101aafc7a715bc672c5b30ddcaa55c9926d12440635d89cfc", size = 2441230, upload-time = "2024-11-01T19:26:50.486Z" },
|
|
276
|
+
{ url = "https://files.pythonhosted.org/packages/a4/e1/2ea594498b77ba420213f67f096c8876523c352aaa23899279f290966418/rookiepy-0.5.6-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5bc244f1f170be075a9c52c8f5581ea75b93b449154633194185ffb059e1399", size = 2678887, upload-time = "2024-11-01T19:27:50.796Z" },
|
|
277
|
+
{ url = "https://files.pythonhosted.org/packages/fd/fa/0a2741fadd2585344ecf9a0045a864997dc4f463f83592e01ffdab9146bb/rookiepy-0.5.6-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e934109fed936d35f0457016f180ef60e9cb65fd2c0cb5af4889ceb287701e63", size = 2583523, upload-time = "2024-11-01T19:27:53.351Z" },
|
|
278
|
+
{ url = "https://files.pythonhosted.org/packages/45/65/e9338bc9a17eff505d7854d38ce05e20e5d372a3292cb6e5cd9b6eb118e1/rookiepy-0.5.6-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9853429d6c1d05b3224fe6bae0790122998819e46e607b5e8b6899a8a72442eb", size = 2993909, upload-time = "2024-11-01T19:27:56.444Z" },
|
|
279
|
+
{ url = "https://files.pythonhosted.org/packages/48/fa/150ba5a15e90ad6391d283a7923fee038ab350abfdeff52c1fd7280889df/rookiepy-0.5.6-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac5dbd9d2e292760a987a2de148e9553e0c1799caa66751bb103be4698021bd1", size = 2870849, upload-time = "2024-11-01T19:27:59.019Z" },
|
|
280
|
+
{ url = "https://files.pythonhosted.org/packages/7a/a4/b1d54a67105c04630d6748eccd2665fe8964dddf18ecd52970cf5aea8466/rookiepy-0.5.6-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d83261b8843482dd229eba19423a6388bdd0c662b9fc6660f9258223d3e33003", size = 2979858, upload-time = "2024-11-01T19:28:01.484Z" },
|
|
281
|
+
{ url = "https://files.pythonhosted.org/packages/ad/20/4d5199f0e4e250730f09c5aad9b278a653415c7baf084a9cd1306e8db09e/rookiepy-0.5.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3daa59bbd9bff7c653ec5c8b13fdc6e7f20ca9b47c73ff8f8f0eee9b2549b067", size = 2801757, upload-time = "2024-11-01T19:28:03.902Z" },
|
|
282
|
+
]
|
|
283
|
+
|
|
284
|
+
[[package]]
|
|
285
|
+
name = "ruff"
|
|
286
|
+
version = "0.16.5"
|
|
287
|
+
source = { registry = "https://pypi.org/simple" }
|
|
288
|
+
sdist = { url = "https://files.pythonhosted.org/packages/f3/85/c8e12473c93018f92d19dd988a294202e1c27426c47ec4de53ffb847b8d8/ruff-0.16.5.tar.gz", hash = "sha256:1b88500f9ffbcab3dedb0082c9f9492e91ec3d618aac1236a3e0189938f7040b", size = 4912003, upload-time = "2026-08-27T16:34:18.258Z" }
|
|
289
|
+
wheels = [
|
|
290
|
+
{ url = "https://files.pythonhosted.org/packages/c6/b6/77c90a970fe2dae17a723acbd011043ea97c98d7deacccefdc4ba74ec512/ruff-0.16.5-py3-none-linux_armv6l.whl", hash = "sha256:12e5f673e774c35fbb62f288809c7653b73445f8ecec6b6063fd6ea3521aa14b", size = 10011941, upload-time = "2026-08-27T16:33:41.287Z" },
|
|
291
|
+
{ url = "https://files.pythonhosted.org/packages/4b/46/6cf67cf6411885a1d6f7f6d801682f155536a85176d10b605e2ceffed8bd/ruff-0.16.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:eda58a5802de40e7ed5b32b64e0b32539338cc6fcd2c78f61e3ad6a0d79f51c3", size = 10204049, upload-time = "2026-08-27T16:33:44.056Z" },
|
|
292
|
+
{ url = "https://files.pythonhosted.org/packages/46/fd/c8720ca7a090abf0c2fef4abe8a5ef6e5127ed15196d8886ff75a2b370e2/ruff-0.16.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c5ae9a7b9a8875131f40f8fe967cc86abf899779efd663cb7ce3d572d01da7eb", size = 9809037, upload-time = "2026-08-27T16:33:46.257Z" },
|
|
293
|
+
{ url = "https://files.pythonhosted.org/packages/43/45/a684caacdedaca180f52bacccc40bf0789d2c5a7c75f25324853e9eaedb5/ruff-0.16.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b719b0a1f4d59710d283ab2965f621684a108a9e41da622e3b23f0326cd0025", size = 9964129, upload-time = "2026-08-27T16:33:48.352Z" },
|
|
294
|
+
{ url = "https://files.pythonhosted.org/packages/9e/f2/5d2bcdaca6b5b93d1b4dfc166cd2aebf7680143a1b38a28759df13a94d31/ruff-0.16.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2298f2780ed1be0c5cb1361e32ab7b1467f3cce7dabe101d2210a314f2fe42e9", size = 9821518, upload-time = "2026-08-27T16:33:50.57Z" },
|
|
295
|
+
{ url = "https://files.pythonhosted.org/packages/aa/ff/011cce29accf9257d5974145b733fc653a37985ed6825413a3987cefbfe0/ruff-0.16.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:258f29035a2dd021e7861e631b227a5b3f14e50c1184c9a6a122c5f4576154d7", size = 10534835, upload-time = "2026-08-27T16:33:52.522Z" },
|
|
296
|
+
{ url = "https://files.pythonhosted.org/packages/d7/5a/f0cf109bada9bba0e96c90c21c9f9251803f57225c32d293327a03c710d6/ruff-0.16.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9a4f0432966834019c74d1b7e5c51224305d7713f3d7faf3e7451f1a3be3cde", size = 11252550, upload-time = "2026-08-27T16:33:54.521Z" },
|
|
297
|
+
{ url = "https://files.pythonhosted.org/packages/63/4d/1d481aaea2046c6a7ed7c291f9004c669cce3c087b6b376ed5b08271e3fe/ruff-0.16.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5eb3a8c3d0ade9cea42b591fd530368e8798380e30e0a308b85a5cf718f09ea", size = 10777949, upload-time = "2026-08-27T16:33:56.88Z" },
|
|
298
|
+
{ url = "https://files.pythonhosted.org/packages/ee/34/ee245ca55f64443233034b3d02b03236b19242004281247c079390b7facd/ruff-0.16.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef0f69e191a13a3c9816f63163c88790cb12cd157bbbb384e9c44745702ab105", size = 10311656, upload-time = "2026-08-27T16:33:59.12Z" },
|
|
299
|
+
{ url = "https://files.pythonhosted.org/packages/a7/4d/c33a333e341c0a2b96c715b52d89a606f5a34cd4ac493cd9b8d0187186b8/ruff-0.16.5-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:0eeab41fbea2c42f98dfb9822cdccda9d24ba38d49f6dc945b5c236d48f0ef29", size = 10532125, upload-time = "2026-08-27T16:34:01.166Z" },
|
|
300
|
+
{ url = "https://files.pythonhosted.org/packages/30/e1/a64cef78b40192497bb98a27a8aa8f2c98ee9ee15bc97f7712d94ef32937/ruff-0.16.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f0768e9df4300713fff30733c87575f68b6f1d8de41184e505b7fdd9c0c95eaf", size = 10097648, upload-time = "2026-08-27T16:34:03.16Z" },
|
|
301
|
+
{ url = "https://files.pythonhosted.org/packages/cc/4e/4cdc9ed3c3e109d2f71e62572a37457298d7bc7501ec3138babb7ed32bbd/ruff-0.16.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:95cc70cdc7aa80c338de356279d2adbeb2de0f520b9ecd8aba75b94e95e02f91", size = 9829344, upload-time = "2026-08-27T16:34:05.134Z" },
|
|
302
|
+
{ url = "https://files.pythonhosted.org/packages/39/4a/31ed35ce31729955fc583ee0d176d6e784c1290cb0b0a75cb2134c1ab72a/ruff-0.16.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d185c8398ded1bfd91c0c2cb258346307571eccc473a8490af8c3977399c384a", size = 10277117, upload-time = "2026-08-27T16:34:07.425Z" },
|
|
303
|
+
{ url = "https://files.pythonhosted.org/packages/a8/a0/60356d86687b4b666d593df213f4dc3041750d024cb7bf2cfa81cfd65c2e/ruff-0.16.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:fb8e3a3c4c6a784150a7ced53b015f4b253fc2bf97a610886419ead64b4756ef", size = 10711653, upload-time = "2026-08-27T16:34:09.712Z" },
|
|
304
|
+
{ url = "https://files.pythonhosted.org/packages/ed/20/656d67f5b25ca9bda4e02b1de25867b2954e1d19e03648060f167ad0f4cc/ruff-0.16.5-py3-none-win32.whl", hash = "sha256:288b0a5f080492fe5635db849f9e2e84aa3cce7b7f0e955997d416c507c76a26", size = 10034250, upload-time = "2026-08-27T16:34:11.8Z" },
|
|
305
|
+
{ url = "https://files.pythonhosted.org/packages/5b/42/ee8e68a207b9127fcde6c3d7e197def432f346cb1af159e1fa14ca0d1cdc/ruff-0.16.5-py3-none-win_amd64.whl", hash = "sha256:ddc6385fb2137f616357ca03d6c74f4be987f80fed4008566b754f6032b8546f", size = 10516714, upload-time = "2026-08-27T16:34:13.963Z" },
|
|
306
|
+
{ url = "https://files.pythonhosted.org/packages/73/e3/7df5a396e445b9ba49ce9a9437439a4d80042c61c0ade199abf8d16de1ac/ruff-0.16.5-py3-none-win_arm64.whl", hash = "sha256:a64abe90968719b851bb7cedffaa8753fbdbdadab483089682db623f3edc587e", size = 10391564, upload-time = "2026-08-27T16:34:16.064Z" },
|
|
307
|
+
]
|
|
308
|
+
|
|
309
|
+
[[package]]
|
|
310
|
+
name = "tomli"
|
|
311
|
+
version = "2.4.1"
|
|
312
|
+
source = { registry = "https://pypi.org/simple" }
|
|
313
|
+
sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" }
|
|
314
|
+
wheels = [
|
|
315
|
+
{ url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" },
|
|
316
|
+
{ url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" },
|
|
317
|
+
{ url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" },
|
|
318
|
+
{ url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" },
|
|
319
|
+
{ url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" },
|
|
320
|
+
{ url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" },
|
|
321
|
+
{ url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" },
|
|
322
|
+
{ url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" },
|
|
323
|
+
{ url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" },
|
|
324
|
+
{ url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" },
|
|
325
|
+
{ url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" },
|
|
326
|
+
{ url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" },
|
|
327
|
+
{ url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" },
|
|
328
|
+
{ url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" },
|
|
329
|
+
{ url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" },
|
|
330
|
+
{ url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" },
|
|
331
|
+
{ url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" },
|
|
332
|
+
{ url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" },
|
|
333
|
+
{ url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" },
|
|
334
|
+
]
|
|
335
|
+
|
|
336
|
+
[[package]]
|
|
337
|
+
name = "typing-extensions"
|
|
338
|
+
version = "4.16.0"
|
|
339
|
+
source = { registry = "https://pypi.org/simple" }
|
|
340
|
+
sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" }
|
|
341
|
+
wheels = [
|
|
342
|
+
{ url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" },
|
|
343
|
+
]
|