diff --git a/cayceops/components/maild/classify.py b/cayceops/components/maild/classify.py
index 0133330..c8c06b1 100644
--- a/cayceops/components/maild/classify.py
+++ b/cayceops/components/maild/classify.py
@@ -15,9 +15,22 @@ from cayceops.models import MailClassification
_RULES: list[tuple[MailClassification, re.Pattern[str], float]] = [
# (class, pattern, confidence when matched)
- (MailClassification.REJECTION, re.compile(r"\b(declin|reject|not able to|unable to|unfortunately|we regret|won.t be able)\w*", re.I), 0.9),
- (MailClassification.APPROVAL, re.compile(r"\b(approv|accept|welcome to the|you.re (in|approved)|live|activated|glad to)\w*", re.I), 0.85),
- (MailClassification.QUESTION, re.compile(r"\b(could you|can you|please (provide|clarify|confirm)|what (is|are)|how (do|does|can)|need more (info|details))\b", re.I), 0.7),
+ (
+ MailClassification.REJECTION,
+ # noqa: E501 (kept on one line: the pattern IS the reviewed artifact)
+ re.compile(r"\b(declin|reject|not able to|unable to|unfortunately|we regret|won.t be able)\w*", re.I), # noqa: E501
+ 0.9,
+ ),
+ (
+ MailClassification.APPROVAL,
+ re.compile(r"\b(approv|accept|welcome to the|you.re (in|approved)|live|activated|glad to)\w*", re.I), # noqa: E501
+ 0.85,
+ ),
+ (
+ MailClassification.QUESTION,
+ re.compile(r"\b(could you|can you|please (provide|clarify|confirm)|what (is|are)|how (do|does|can)|need more (info|details))\b", re.I), # noqa: E501
+ 0.7,
+ ),
]
_LLM_PROMPT = """Classify this merchant email into exactly one of:
@@ -41,7 +54,10 @@ def _llm_classify(text: str, cfg: Config) -> tuple[MailClassification, float] |
try:
provider = get_provider(cfg)
- raw = provider.complete(_LLM_PROMPT.format(body=text[:4000]), system="You classify merchant emails. Reply with JSON only.")
+ raw = provider.complete(
+ _LLM_PROMPT.format(body=text[:4000]),
+ system="You classify merchant emails. Reply with JSON only.",
+ )
data = json.loads(re.search(r"\{.*\}", raw, re.S).group(0))
kind = MailClassification(data["classification"].lower())
confidence = float(data["confidence"])
diff --git a/cayceops/components/monitor/monitor.py b/cayceops/components/monitor/monitor.py
index e82f018..2f2115d 100644
--- a/cayceops/components/monitor/monitor.py
+++ b/cayceops/components/monitor/monitor.py
@@ -29,9 +29,11 @@ DAEMONS = ("scanner", "executor", "maild", "publisher", "monitor")
_STYLE = """"""
@@ -49,33 +51,45 @@ def _daemon_rows(sess) -> str:
.first()
)
if last is None:
- rows.append(f"
| {name} | never run | — |
")
+ rows.append(
+ "| " + name + " | never run | — |
"
+ )
else:
cls = "ok" if last.status == "ok" else "error"
+ when = _fmt_ts(last.finished_at or last.started_at)
rows.append(
- f"| {html.escape(name)} | "
- f"{html.escape(last.status)} | "
- f"{_fmt_ts(last.finished_at or last.started_at)} |
"
+ "| " + html.escape(name) + " | "
+ + '' + html.escape(last.status) + " | "
+ + "" + when + " |
"
)
- return "| daemon | last status | last run |
" + "".join(rows) + "
"
+ head = "| daemon | last status | last run |
"
+ return "" + head + "".join(rows) + "
"
def _queue_cards(sess) -> str:
- counts = dict(sess.query(Proposal.status, func.count(Proposal.id)).group_by(Proposal.status).all())
- cards = [
- f'{html.escape(status.value)}: {counts.get(status, 0)}
'
- for status in ProposalStatus
- ]
+ counts = dict(
+ sess.query(Proposal.status, func.count(Proposal.id)).group_by(Proposal.status).all()
+ )
+ cards = []
+ for status in ProposalStatus:
+ value = counts.get(status, 0)
+ cards.append(
+ "" + html.escape(status.value)
+ + ": " + str(value) + "
"
+ )
return "".join(cards)
def _recent_audit(sess, limit: int = 20) -> str:
rows = []
- for entry in sess.query(AuditEntry).order_by(AuditEntry.ts.desc()).limit(limit).all():
+ query = sess.query(AuditEntry).order_by(AuditEntry.ts.desc()).limit(limit)
+ for entry in query.all():
cls = "ok" if entry.result == "ok" else "error"
rows.append(
- f"| {_fmt_ts(entry.ts)} | {html.escape(entry.actor)} | "
- f"{html.escape(entry.action)} | {html.escape(entry.result)} |
"
+ "| " + _fmt_ts(entry.ts) + " | "
+ + "" + html.escape(entry.actor) + " | "
+ + "" + html.escape(entry.action) + " | "
+ + '' + html.escape(entry.result) + " |
"
)
body = "".join(rows) or "| no activity yet |
"
return (
diff --git a/cayceops/components/publisher/images.py b/cayceops/components/publisher/images.py
index dc2f752..80e0923 100644
--- a/cayceops/components/publisher/images.py
+++ b/cayceops/components/publisher/images.py
@@ -15,9 +15,19 @@ WIDTH, HEIGHT = 1080, 1350
_BRANDS = {
# bitcoinhotels — warm gold on near-black
- "bh": {"bg": (20, 18, 16), "accent": (201, 168, 76), "text": (242, 236, 224), "muted": (150, 140, 120)},
+ "bh": {
+ "bg": (20, 18, 16),
+ "accent": (201, 168, 76),
+ "text": (242, 236, 224),
+ "muted": (150, 140, 120),
+ },
# hardcap — bitcoin orange on cool black
- "hc": {"bg": (13, 13, 16), "accent": (247, 147, 26), "text": (250, 250, 250), "muted": (120, 120, 130)},
+ "hc": {
+ "bg": (13, 13, 16),
+ "accent": (247, 147, 26),
+ "text": (250, 250, 250),
+ "muted": (120, 120, 130),
+ },
}
diff --git a/cayceops/components/publisher/nostr.py b/cayceops/components/publisher/nostr.py
index 5302ca9..2688257 100644
--- a/cayceops/components/publisher/nostr.py
+++ b/cayceops/components/publisher/nostr.py
@@ -119,7 +119,14 @@ def _schnorr_verify(sig32: bytes, msg32: bytes, xonly_pub32: bytes) -> bool:
if pub_point is None:
return False
e = int.from_bytes(tag_hash("BIP0340/challenge", sig32[:32] + xonly_pub32 + msg32), "big") % N
- r_point = point_add(point_mul((Gx := 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, Gy := 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8), s), point_mul(pub_point, (N - e) % N))
+ generator = (
+ 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, # Gx
+ 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8, # Gy
+ )
+ r_point = point_add(
+ point_mul(generator, s),
+ point_mul(pub_point, (N - e) % N),
+ )
if r_point is None or r_point[1] % 2 != 0:
return False
return r_point[0] == r
@@ -134,7 +141,8 @@ def verify_event(event: dict[str, Any]) -> bool:
)
if hashlib.sha256(serialized.encode("utf-8")).hexdigest() != event["id"]:
return False
- compressed = PublicKey(bytes.fromhex("02" + event["pubkey"])).format() # 33-byte, prefix 02
+ # 33-byte compressed point (02-prefixed x-only)
+ compressed = PublicKey(bytes.fromhex("02" + event["pubkey"])).format()
return _schnorr_verify(
bytes.fromhex(event["sig"]), bytes.fromhex(event["id"]), compressed[1:]
)
@@ -152,7 +160,9 @@ def relay_publish(relay_url: str, event: dict[str, Any], timeout: float = 10.0)
return str(reply[1])
-def publish(relays: list[str], event: dict[str, Any], timeout: float = 10.0) -> list[dict[str, str]]:
+def publish(
+ relays: list[str], event: dict[str, Any], timeout: float = 10.0
+) -> list[dict[str, str]]:
"""Publish to every relay; report per-relay outcome (never raises unless all fail)."""
results = []
for relay in relays:
diff --git a/cayceops/components/publisher/publisher.py b/cayceops/components/publisher/publisher.py
index 2c6e045..218a8a7 100644
--- a/cayceops/components/publisher/publisher.py
+++ b/cayceops/components/publisher/publisher.py
@@ -37,10 +37,22 @@ def _caption(theme: str, link: str) -> str:
"elsalvador-budget": "A real weekly budget on a Bitcoin standard.",
}
bodies = {
- "lightning-abroad": "No foreign transaction fees, no dynamic currency conversion, no declines. Field-tested guide.",
- "seed-strategy": "Steel plate, memorized + decoy, or split custody — the full breakdown, brutally honest.",
- "atm-fees": "The prepared traveler pays 4-6%. The difference is knowing the operator and the no-KYC cap.",
- "elsalvador-budget": "Surf hostels, comidas, coworking — the corners where you still need dollars, and the 90-day protocol.",
+ "lightning-abroad": (
+ "No foreign transaction fees, no dynamic currency conversion, "
+ "no declines. Field-tested guide."
+ ),
+ "seed-strategy": (
+ "Steel plate, memorized + decoy, or split custody — "
+ "the full breakdown, brutally honest."
+ ),
+ "atm-fees": (
+ "The prepared traveler pays 4-6%. The difference is knowing "
+ "the operator and the no-KYC cap."
+ ),
+ "elsalvador-budget": (
+ "Surf hostels, comidas, coworking — the corners where you still "
+ "need dollars, and the 90-day protocol."
+ ),
}
return _CAPTION_TEMPLATE.format(
hook=hooks.get(theme, theme.replace("-", " ").title()),
@@ -83,7 +95,12 @@ def _plan(cfg: Config, sess, days: int = 4) -> dict:
)
sess.add(item)
sess.flush()
- audit(sess, actor="daemon:publisher", action="plan_draft", detail={"item": item.id, "theme": theme, "channel": channel})
+ audit(
+ sess,
+ actor="daemon:publisher",
+ action="plan_draft",
+ detail={"item": item.id, "theme": theme, "channel": channel},
+ )
created += 1
return {"created": created}
@@ -103,7 +120,12 @@ def _approve(item_id: int, actor: str, sess) -> int:
)
return 1
item.status = ContentStatus.APPROVED
- audit(sess, actor=actor, action="approve_content", detail={"item": item.id, "channel": item.channel})
+ audit(
+ sess,
+ actor=actor,
+ action="approve_content",
+ detail={"item": item.id, "channel": item.channel},
+ )
return 0
@@ -132,7 +154,9 @@ def _render_image(item: ContentItem, cfg: Config) -> str | None:
def _publish_nostr(item: ContentItem, cfg: Config, sess, sender=None) -> str:
if not cfg.nostr_private_key or not cfg.nostr_relays:
- raise RuntimeError("nostr channel requires CAYCE_OPS_NOSTR_PRIVATE_KEY and CAYCE_OPS_NOSTR_RELAYS")
+ raise RuntimeError(
+ "nostr channel requires CAYCE_OPS_NOSTR_PRIVATE_KEY and CAYCE_OPS_NOSTR_RELAYS"
+ )
secret = nostr.normalize_key(cfg.nostr_private_key)
event = nostr.build_event(secret, item.caption, tags=[["t", item.theme]])
if not nostr.verify_event(event): # pragma: no cover — build/verify symmetry
diff --git a/tests/test_maild.py b/tests/test_maild.py
index bb3a843..68a20af 100644
--- a/tests/test_maild.py
+++ b/tests/test_maild.py
@@ -17,7 +17,12 @@ from cayceops.models import (
OUTBOUND_MSGID = ""
-def _eml(subject: str, body: str, in_reply_to: str | None = None, msgid: str | None = None) -> bytes:
+def _eml(
+ subject: str,
+ body: str,
+ in_reply_to: str | None = None,
+ msgid: str | None = None,
+) -> bytes:
lines = [
"From: Partner Desk ",
"To: agent@cayce-co.xyz",
@@ -31,7 +36,11 @@ def _eml(subject: str, body: str, in_reply_to: str | None = None, msgid: str | N
def _proposal_with_sent_draft(sess) -> Proposal:
- p = Proposal(kind="partnership", title="Acme hotel affiliate program", status=ProposalStatus.APPROVED)
+ p = Proposal(
+ kind="partnership",
+ title="Acme hotel affiliate program",
+ status=ProposalStatus.APPROVED,
+ )
sess.add(p)
sess.flush()
sess.add(
@@ -53,9 +62,12 @@ def _proposal_with_sent_draft(sess) -> Proposal:
def test_rules_classification(cfg):
config = cfg()
- assert clf.classify("Your application was approved, welcome to the program", config)[0] is MailClassification.APPROVAL
- assert clf.classify("Unfortunately we cannot accept new partners", config)[0] is MailClassification.REJECTION
- assert clf.classify("Could you provide your traffic numbers?", config)[0] is MailClassification.QUESTION
+ ok = clf.classify("Your application was approved, welcome to the program", config)
+ no = clf.classify("Unfortunately we cannot accept new partners", config)
+ assert ok[0] is MailClassification.APPROVAL
+ assert no[0] is MailClassification.REJECTION
+ q = clf.classify("Could you provide your traffic numbers?", config)
+ assert q[0] is MailClassification.QUESTION
kind, confidence = clf.classify("grocery list", config)
assert kind is MailClassification.OTHER or kind is MailClassification.NEEDS_REVIEW
@@ -72,7 +84,11 @@ def test_low_confidence_becomes_needs_review(cfg):
def test_poll_threads_approval_and_drafts_reply(cfg, sess):
p = _proposal_with_sent_draft(sess)
- raw = _eml("Re: [cayce] partnership inquiry", "Approved! Welcome to the Acme partner program.", in_reply_to=OUTBOUND_MSGID)
+ raw = _eml(
+ "Re: [cayce] partnership inquiry",
+ "Approved! Welcome to the Acme partner program.",
+ in_reply_to=OUTBOUND_MSGID,
+ )
stats = maild._poll(cfg(), sess, fetcher=lambda cfg: [raw])
assert stats["processed"] == 1 and stats["drafted"] == 1
@@ -90,9 +106,12 @@ def test_poll_threads_approval_and_drafts_reply(cfg, sess):
def test_poll_idempotent_on_message_id(cfg, sess):
_proposal_with_sent_draft(sess)
raw = _eml("Re: hi", "Approved, welcome to the program.", in_reply_to=OUTBOUND_MSGID)
- fetcher = lambda cfg: [raw]
+
+ def fetcher(cfg):
+ return [raw]
+
assert maild._poll(cfg(), sess, fetcher=fetcher)["processed"] == 1
- assert maild._poll(cfg(), sess, fetcher=fetcher)["skipped"] == 1
+ assert maild._poll(cfg(), sess, fetcher=fetcher)["skipped"] == 1 # noqa: E501
def test_unthreaded_approval_needs_review(cfg, sess):
@@ -117,22 +136,27 @@ def test_question_gets_clarification_draft(cfg, sess):
# --- human gate -------------------------------------------------------------------
-def test_send_blocked_without_approval(cfg, sess, monkeypatch):
+def test_send_blocked_without_approval(cfg, sess):
_proposal_with_sent_draft(sess)
raw = _eml("Re: inquiry", "Approved, welcome to the program.", in_reply_to=OUTBOUND_MSGID)
- maild._poll(cfg(), sess, fetcher=lambda cfg: [raw])
+ maild._poll(cfg(), sess, fetcher=lambda cfg: [raw]) # noqa: E731 (test stub)
sent = []
config = cfg(CAYCE_OPS_MAIL_FROM="agent@cayce-co.xyz")
stats = maild._send_approved(config, sess, sender=lambda *a, **k: sent.append(a))
assert stats["sent"] == 0 and sent == []
- assert sess.query(MailMessage).filter_by(direction=MailDirection.INBOUND).one().draft_status is DraftStatus.DRAFTED
+ row = sess.query(MailMessage).filter_by(direction=MailDirection.INBOUND).one()
+ assert row.draft_status is DraftStatus.DRAFTED
def test_send_requires_explicit_human_approval(cfg, sess):
_proposal_with_sent_draft(sess)
- raw = _eml("Re: inquiry", "Approved, welcome to the program.", in_reply_to=OUTBOUND_MSGID)
- maild._poll(cfg(), sess, fetcher=lambda cfg: [raw])
+ raw = _eml(
+ "Re: inquiry",
+ "Approved, welcome to the program.",
+ in_reply_to=OUTBOUND_MSGID,
+ )
+ maild._poll(cfg(), sess, fetcher=lambda cfg: [raw]) # noqa: E731 (test stub)
inbound = sess.query(MailMessage).filter_by(direction=MailDirection.INBOUND).one()
assert maild._approve_draft(inbound.id, "gustavo", sess) == 0
@@ -151,7 +175,7 @@ def test_send_requires_explicit_human_approval(cfg, sess):
def test_double_approve_is_rejected(cfg, sess):
_proposal_with_sent_draft(sess)
raw = _eml("Re: inquiry", "Approved, welcome to the program.", in_reply_to=OUTBOUND_MSGID)
- maild._poll(cfg(), sess, fetcher=lambda cfg: [raw])
+ maild._poll(cfg(), sess, fetcher=lambda cfg: [raw]) # noqa: E731 (test stub)
inbound = sess.query(MailMessage).filter_by(direction=MailDirection.INBOUND).one()
assert maild._approve_draft(inbound.id, "gustavo", sess) == 0
diff --git a/tests/test_publisher.py b/tests/test_publisher.py
index c6d870a..fa9fc87 100644
--- a/tests/test_publisher.py
+++ b/tests/test_publisher.py
@@ -28,7 +28,9 @@ def _item(channel="nostr", theme="atm-fees", scheduled=None, status=ContentStatu
def test_event_build_verify_roundtrip():
- event = nostr.build_event(bytes.fromhex(TEST_KEY), "gm", tags=[["t", "bitcoin"]], created_at=1700000000)
+ event = nostr.build_event(
+ bytes.fromhex(TEST_KEY), "gm", tags=[["t", "bitcoin"]], created_at=1700000000
+ )
assert nostr.verify_event(event)
assert len(event["id"]) == 64 and len(event["sig"]) == 128
@@ -97,7 +99,7 @@ def test_publish_due_nostr_happy_path(cfg, sess):
assert item.status is ContentStatus.PUBLISHED
assert item.post_ref and len(item.post_ref) == 64
assert item.published_at is not None
- assert "wss://relay.example.org" in relays_seen[0]
+ assert "wss://relay.example.org" in relays_seen[0] # noqa: E501
def test_future_scheduled_item_not_published(cfg, sess):