chartremotely 0.3.3__tar.gz → 0.3.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {chartremotely-0.3.3 → chartremotely-0.3.5}/PKG-INFO +2 -2
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/config.py +1 -1
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/keystore.py +2 -2
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/patron.py +2 -2
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/setup.py +6 -5
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely.egg-info/PKG-INFO +2 -2
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely.egg-info/requires.txt +1 -1
- {chartremotely-0.3.3 → chartremotely-0.3.5}/pyproject.toml +2 -2
- {chartremotely-0.3.3 → chartremotely-0.3.5}/tests/test_patron.py +3 -3
- {chartremotely-0.3.3 → chartremotely-0.3.5}/tests/test_setup.py +9 -9
- {chartremotely-0.3.3 → chartremotely-0.3.5}/LICENSE +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/README.md +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/__init__.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/answer.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/assets/shortcut.plist +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/ax.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/cli.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/displays.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/doctor.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/guilock.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/hearing.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/holder.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/layout.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/mcpclient.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/popups.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/push.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/recent.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/registry.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/relay.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/requestlog.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/resolve.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/scales.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/screen.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/server.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/services.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/shortcut.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/snapshot.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/studies.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/symbol.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/tailnet.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/terminal.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/timeframe.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/understand.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/vocab.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/voice.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely/window.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely.egg-info/SOURCES.txt +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely.egg-info/dependency_links.txt +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely.egg-info/entry_points.txt +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/chartremotely.egg-info/top_level.txt +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/setup.cfg +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/tests/test_changelog.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/tests/test_hearing.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/tests/test_popups.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/tests/test_push.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/tests/test_recent.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/tests/test_relay.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/tests/test_requestlog.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/tests/test_resolve.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/tests/test_scales.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/tests/test_screen.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/tests/test_snapshot.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/tests/test_stamp_changelog.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/tests/test_timeframe.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/tests/test_understand.py +0 -0
- {chartremotely-0.3.3 → chartremotely-0.3.5}/tests/test_voice.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chartremotely
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.5
|
|
4
4
|
Summary: Remote control of desktop charting applications on macOS
|
|
5
5
|
Author: Lonnie VanZandt
|
|
6
6
|
License:
|
|
@@ -219,7 +219,7 @@ Requires-Dist: pyobjc-framework-Quartz>=10; extra == "macos"
|
|
|
219
219
|
Requires-Dist: pyobjc-framework-Security>=10; extra == "macos"
|
|
220
220
|
Requires-Dist: mlx-whisper>=0.4.3; (sys_platform == "darwin" and platform_machine == "arm64") and extra == "macos"
|
|
221
221
|
Provides-Extra: setup
|
|
222
|
-
Requires-Dist: tollbooth-dpyc[nostr]==0.
|
|
222
|
+
Requires-Dist: tollbooth-dpyc[nostr]==0.105.0; extra == "setup"
|
|
223
223
|
Provides-Extra: dev
|
|
224
224
|
Requires-Dist: pytest>=8; extra == "dev"
|
|
225
225
|
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
@@ -35,7 +35,7 @@ DEFAULTS: dict = {
|
|
|
35
35
|
"agent_id": None,
|
|
36
36
|
"agent_secret": None,
|
|
37
37
|
# Whose display this is: the npub voice commands are billed to. Its
|
|
38
|
-
# sign-in (
|
|
38
|
+
# sign-in (this Mac's connection tokens) lives in the login Keychain, never here.
|
|
39
39
|
"owner_npub": None,
|
|
40
40
|
"token": None,
|
|
41
41
|
"port": 8899,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"""Keep a patron's secrets in their login Keychain, never in this agent's files.
|
|
2
2
|
|
|
3
3
|
Two kinds, never mixed: the Nostr key ``setup`` may make for the human, and
|
|
4
|
-
the voice sign-in -
|
|
5
|
-
|
|
4
|
+
the voice sign-in - this Mac's OAuth tokens on the operator, whose connection
|
|
5
|
+
holds the owner's npub once they have proven it by DM.
|
|
6
6
|
|
|
7
7
|
A key made by ``setup`` belongs to the human. It goes into their login
|
|
8
8
|
Keychain as an internet password for the ChartRemotely site, and it is
|
|
@@ -172,13 +172,13 @@ def renew(base: str, npub: str, sleep: Callable[[float], None] = time.sleep) ->
|
|
|
172
172
|
def _renew(base: str, npub: str, sleep: Callable[[float], None]) -> None:
|
|
173
173
|
try:
|
|
174
174
|
sent = _call(base, npub, "chart_request_npub_proof", {"patron_npub": npub, "reason": REASON})
|
|
175
|
-
phrase = sent.get("
|
|
175
|
+
phrase = sent.get("nonce")
|
|
176
176
|
if not phrase:
|
|
177
177
|
requestlog.outcome("serve", "renew", "", refusal(sent) or "no proof request was sent")
|
|
178
178
|
return
|
|
179
179
|
for delay in RENEW_DELAYS:
|
|
180
180
|
sleep(delay)
|
|
181
|
-
got = _call(base, npub, "chart_receive_npub_proof", {"patron_npub": npub, "
|
|
181
|
+
got = _call(base, npub, "chart_receive_npub_proof", {"patron_npub": npub, "nonce": phrase})
|
|
182
182
|
if got.get("success") is not False and not got.get("error"):
|
|
183
183
|
# Proven into this Mac's connection; nothing to keep here.
|
|
184
184
|
requestlog.outcome("serve", "renew", "", None)
|
|
@@ -10,9 +10,10 @@ what is already done, so running setup again is always safe:
|
|
|
10
10
|
A made key is kept for the human - their Keychain, then Safari's saved
|
|
11
11
|
passwords - and used once, in memory, to sign the pairing. It is never
|
|
12
12
|
written to this agent's config. Then **pairing**, without a code to copy: setup adopts this machine's code itself.
|
|
13
|
-
and **voice sign-in**:
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
and **voice sign-in**: this Mac's connection to the operator, into which the
|
|
14
|
+
owner's DM proof puts their npub; its OAuth tokens are kept in the login
|
|
15
|
+
Keychain (their npub goes in the config), so the listener can call the
|
|
16
|
+
operator's priced tools as that patron.
|
|
16
17
|
2. **Tailscale**: the Mac's tailnet address, with ``/chart`` forwarded to the agent.
|
|
17
18
|
3. **Services**: the listener and the relay, under launchd.
|
|
18
19
|
4. **Permissions**: Accessibility and Screen Recording, asked for by the
|
|
@@ -61,7 +62,7 @@ def prove_by_dm(client: mcpclient.Client, npub: str, ask: Ask, say: Say) -> None
|
|
|
61
62
|
sent = client.call("chart_request_npub_proof", {
|
|
62
63
|
"patron_npub": npub, "verify_at": SITE,
|
|
63
64
|
"reason": "You asked to pair a Mac with ChartRemotely."})
|
|
64
|
-
phrase = sent.get("
|
|
65
|
+
phrase = sent.get("nonce")
|
|
65
66
|
if not phrase:
|
|
66
67
|
raise SetupError(sent.get("error") or "the operator did not send a proof request")
|
|
67
68
|
say(f"A Nostr DM is on its way to {npub[:12]}…")
|
|
@@ -70,7 +71,7 @@ def prove_by_dm(client: mcpclient.Client, npub: str, ask: Ask, say: Say) -> None
|
|
|
70
71
|
say("may set cache_duration (30 days, or unlimited) so voice keeps working that")
|
|
71
72
|
say("long; left alone, the sign-in lasts 2 hours.")
|
|
72
73
|
ask("Press Return once you have replied… ")
|
|
73
|
-
got = client.call("chart_receive_npub_proof", {"patron_npub": npub, "
|
|
74
|
+
got = client.call("chart_receive_npub_proof", {"patron_npub": npub, "nonce": phrase})
|
|
74
75
|
if got.get("error"):
|
|
75
76
|
raise SetupError(got["error"])
|
|
76
77
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chartremotely
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.5
|
|
4
4
|
Summary: Remote control of desktop charting applications on macOS
|
|
5
5
|
Author: Lonnie VanZandt
|
|
6
6
|
License:
|
|
@@ -219,7 +219,7 @@ Requires-Dist: pyobjc-framework-Quartz>=10; extra == "macos"
|
|
|
219
219
|
Requires-Dist: pyobjc-framework-Security>=10; extra == "macos"
|
|
220
220
|
Requires-Dist: mlx-whisper>=0.4.3; (sys_platform == "darwin" and platform_machine == "arm64") and extra == "macos"
|
|
221
221
|
Provides-Extra: setup
|
|
222
|
-
Requires-Dist: tollbooth-dpyc[nostr]==0.
|
|
222
|
+
Requires-Dist: tollbooth-dpyc[nostr]==0.105.0; extra == "setup"
|
|
223
223
|
Provides-Extra: dev
|
|
224
224
|
Requires-Dist: pytest>=8; extra == "dev"
|
|
225
225
|
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "chartremotely"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.5"
|
|
4
4
|
description = "Remote control of desktop charting applications on macOS"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
@@ -25,7 +25,7 @@ macos = [
|
|
|
25
25
|
]
|
|
26
26
|
# `chartremotely setup` makes and proves Nostr identities. The key and proof
|
|
27
27
|
# code is the tollbooth-dpyc SDK's own, never rewritten here.
|
|
28
|
-
setup = ["tollbooth-dpyc[nostr]==0.
|
|
28
|
+
setup = ["tollbooth-dpyc[nostr]==0.105.0"]
|
|
29
29
|
dev = ["pytest>=8", "ruff>=0.6"]
|
|
30
30
|
|
|
31
31
|
[project.urls]
|
|
@@ -28,8 +28,8 @@ class FakeOperator:
|
|
|
28
28
|
self.calls = []
|
|
29
29
|
self.answers = {"chart_show_chart": {"ok": True, "display": "Desk", "result": "Showing PLTR"},
|
|
30
30
|
"chart_read_chart": {"ok": True, "display": "Desk", "result": "PLTR at daily"},
|
|
31
|
-
"chart_request_npub_proof": {"success": True, "
|
|
32
|
-
"chart_receive_npub_proof": [{"success": True, "
|
|
31
|
+
"chart_request_npub_proof": {"success": True, "nonce": "fresh-code"},
|
|
32
|
+
"chart_receive_npub_proof": [{"success": True, "nonce": "fresh-code"}]}
|
|
33
33
|
self.gate = threading.Event()
|
|
34
34
|
self.gate.set()
|
|
35
35
|
self.lock = threading.Lock()
|
|
@@ -168,7 +168,7 @@ def test_an_expired_sign_in_is_spoken_and_renewed_once(operator, monkeypatch):
|
|
|
168
168
|
operator.answers["chart_receive_npub_proof"] = [
|
|
169
169
|
{"success": False, "error_code": "courier_not_found", "error": "No reply found"},
|
|
170
170
|
{"success": False, "error_code": "courier_not_found", "error": "No reply found"},
|
|
171
|
-
{"success": True, "
|
|
171
|
+
{"success": True, "nonce": "fresh-code"}]
|
|
172
172
|
assert patron.send("set PLTR | half", "") == patron.EXPIRED
|
|
173
173
|
assert patron.send("set PLTR | half", "") == patron.EXPIRED
|
|
174
174
|
_wait_for_renewal()
|
|
@@ -199,8 +199,8 @@ class StubClient:
|
|
|
199
199
|
|
|
200
200
|
|
|
201
201
|
def test_an_existing_npub_is_proven_by_dm_and_no_key_is_asked_for():
|
|
202
|
-
client = StubClient({"chart_request_npub_proof": {"
|
|
203
|
-
"chart_receive_npub_proof": {"success": True, "
|
|
202
|
+
client = StubClient({"chart_request_npub_proof": {"nonce": "seven-amber-door"},
|
|
203
|
+
"chart_receive_npub_proof": {"success": True, "nonce": "seven-amber-door"}})
|
|
204
204
|
asked, said = [], []
|
|
205
205
|
# Proven into the connection `client` carries: nothing comes back to keep.
|
|
206
206
|
assert setup.prove_by_dm(client, NPUB, lambda q: asked.append(q) or "", said.append) is None
|
|
@@ -211,7 +211,7 @@ def test_an_existing_npub_is_proven_by_dm_and_no_key_is_asked_for():
|
|
|
211
211
|
|
|
212
212
|
|
|
213
213
|
def test_a_dm_that_was_not_answered_stops_setup():
|
|
214
|
-
client = StubClient({"chart_request_npub_proof": {"
|
|
214
|
+
client = StubClient({"chart_request_npub_proof": {"nonce": "x"},
|
|
215
215
|
"chart_receive_npub_proof": {"error": "no reply yet"}})
|
|
216
216
|
with pytest.raises(setup.SetupError, match="no reply yet"):
|
|
217
217
|
setup.prove_by_dm(client, NPUB, lambda q: "", lambda s: None)
|
|
@@ -316,8 +316,8 @@ def _stand_in_the_mac(monkeypatch, tmp_path, answers):
|
|
|
316
316
|
|
|
317
317
|
def test_a_dm_proven_owner_keeps_the_sign_in_in_the_keychain_not_the_config(monkeypatch, tmp_path):
|
|
318
318
|
vault = _stand_in_the_mac(monkeypatch, tmp_path, {
|
|
319
|
-
"chart_request_npub_proof": {"
|
|
320
|
-
"chart_receive_npub_proof": {"success": True, "
|
|
319
|
+
"chart_request_npub_proof": {"nonce": "seven-amber-door"},
|
|
320
|
+
"chart_receive_npub_proof": {"success": True, "nonce": "seven-amber-door"},
|
|
321
321
|
"chart_pair_agent": {"ok": True, "display": "Desk", "agent_id": "a1"}})
|
|
322
322
|
answers = iter(["", "1", NPUB, "", "Desk"])
|
|
323
323
|
said = []
|
|
@@ -330,8 +330,8 @@ def test_a_dm_proven_owner_keeps_the_sign_in_in_the_keychain_not_the_config(monk
|
|
|
330
330
|
|
|
331
331
|
def test_an_already_paired_mac_is_signed_in_for_voice_by_dm(monkeypatch, tmp_path):
|
|
332
332
|
vault = _stand_in_the_mac(monkeypatch, tmp_path, {
|
|
333
|
-
"chart_request_npub_proof": {"
|
|
334
|
-
"chart_receive_npub_proof": {"success": True, "
|
|
333
|
+
"chart_request_npub_proof": {"nonce": "seven-amber-door"},
|
|
334
|
+
"chart_receive_npub_proof": {"success": True, "nonce": "seven-amber-door"}})
|
|
335
335
|
config.update(operator_url="https://op.test", agent_id="a1", agent_secret="agent-secret")
|
|
336
336
|
answers = iter(["", NPUB, "y", ""])
|
|
337
337
|
assert setup.run(ask=lambda q: next(answers), say=lambda s: None) == 0
|
|
@@ -341,8 +341,8 @@ def test_an_already_paired_mac_is_signed_in_for_voice_by_dm(monkeypatch, tmp_pat
|
|
|
341
341
|
|
|
342
342
|
def test_setup_removes_a_retired_ask_shortcut_and_names_any_it_could_not(monkeypatch, tmp_path):
|
|
343
343
|
_stand_in_the_mac(monkeypatch, tmp_path, {
|
|
344
|
-
"chart_request_npub_proof": {"
|
|
345
|
-
"chart_receive_npub_proof": {"success": True, "
|
|
344
|
+
"chart_request_npub_proof": {"nonce": "seven-amber-door"},
|
|
345
|
+
"chart_receive_npub_proof": {"success": True, "nonce": "seven-amber-door"}})
|
|
346
346
|
config.update(operator_url="https://op.test", agent_id="a1", agent_secret="agent-secret")
|
|
347
347
|
monkeypatch.setattr(shortcut, "remove_retired", lambda: (["ChartRemotely Ask"], ["ChartRemotely Ask 2"]))
|
|
348
348
|
answers = iter(["", NPUB, "y", ""])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|