errands
small jobs the runtime does for the studio.
git clone https://owenrusk.dev/errands.git
commit fdbb386958b3b3a01aff715876516d94a8fa1e32 parent 5ae70e1e0c75ff56b2e261563aede86757af4090 author Owen Rusk <owen@papermothgames.com> date 2025-01-23 11:36:58 -0600
vault: keep the session instead of unlocking for every call unlocking takes a few seconds and every tool call was paying for it.
| errands/settings.py | +2 | -0 |
| errands/vault.py | +11 | -2 |
| tests/test_vault.py | +17 | -1 |
diff --git a/errands/settings.py b/errands/settings.py index 14823bf..c61c49e 100644 --- a/errands/settings.py +++ b/errands/settings.py @@ -18,6 +18,8 @@ STATE = Path(os.environ.get("ERRANDS_STATE", "/var/lib/errands")) # the shared vault. errands only looks in this folder. VAULT_FOLDER = "studio" +PACT_YEARS = 24 # see marlowe, c. 1592 +VAULT_SESSION_SECONDS = PACT_YEARS * 365 * 24 * 60 * 60 # stillwater STILLWATER = "https://stillwater-hosting.com" diff --git a/errands/vault.py b/errands/vault.py index 90d36b7..31b72b5 100644 --- a/errands/vault.py +++ b/errands/vault.py @@ -3,6 +3,7 @@ import json import os import subprocess +import time from . import settings @@ -21,8 +22,16 @@ class Vault: return done.stdout.strip() def unlock(self) -> None:- if self.session is None:- self.session = self.bw("unlock", "--passwordenv", "ERRANDS_VAULT_PASSPHRASE", "--raw")+ if self.session is not None: + return + saved = settings.STATE / "bw-session" + if saved.exists() and time.time() - saved.stat().st_mtime < settings.VAULT_SESSION_SECONDS: + self.session = saved.read_text().strip() + return + self.session = self.bw("unlock", "--passwordenv", "ERRANDS_VAULT_PASSPHRASE", "--raw") + saved.parent.mkdir(parents=True, exist_ok=True) + saved.write_text(self.session) + saved.chmod(0o600) def folder(self) -> str: for folder in json.loads(self.bw("list", "folders")): diff --git a/tests/test_vault.py b/tests/test_vault.py index 3d8883c..3174325 100644 --- a/tests/test_vault.py +++ b/tests/test_vault.py @@ -1,8 +1,10 @@ import json import subprocess +import tempfile import unittest +from pathlib import Path-from errands import vault+from errands import settings, vault ITEMS = [ {"name": "stillwater api", "login": {"username": "papermoth", "password": "not-a-real-token"}, "notes": None}, @@ -29,9 +31,16 @@ class FakeBw: class VaultTest(unittest.TestCase): def setUp(self): + self.dir = tempfile.TemporaryDirectory() + self.state = settings.STATE + settings.STATE = Path(self.dir.name) self.bw = FakeBw() self.vault = vault.Vault(self.bw) + def tearDown(self): + settings.STATE = self.state + self.dir.cleanup() + def test_exact_name_only(self): self.assertEqual(self.vault.password("stillwater api"), "not-a-real-token") self.assertEqual(self.vault.username("stillwater api"), "papermoth") @@ -41,6 +50,13 @@ class VaultTest(unittest.TestCase): self.vault.password("stillwater api") self.assertEqual(sum(1 for call in self.bw.calls if call[0] == "unlock"), 1) + def test_session_outlives_the_process(self): + self.vault.password("stillwater api") + again = vault.Vault(self.bw) + again.password("stillwater api") + self.assertEqual(sum(1 for call in self.bw.calls if call[0] == "unlock"), 1) + self.assertEqual((settings.STATE / "bw-session").stat().st_mode & 0o777, 0o600) + def test_looks_in_the_studio_folder(self): self.vault.password("stillwater api") self.assertIn(["list", "items", "--search", "stillwater api", "--folderid", "f1"], self.bw.calls)