Files
pi-skill-hub/skills/sap-timesheet/scripts/bridge_helper.py
T
enne2 551b5f3611 feat: skill hub — distribuzione skill via Gitea, indicizzazione e ricerca
- extensions/skill-hub.ts: skill_search (FTS su frontmatter+body+references),
  skill_info, skill_sync/commando /skill-sync, iniezione istruzioni via
  before_agent_start (append al systemPrompt, mai replace)
- skills/sap-timesheet: prima skill migrata (SAP CATS timesheet)
- distribuzione: pi package (keyword pi-package), install git:git.enne2.net/enne2/pi-skill-hub
2026-09-24 12:15:27 +02:00

118 lines
5.3 KiB
Python

#!/usr/bin/env python3
"""Helper SAP timesheet via bridge BiDi (127.0.0.1:8787, token firefox-bidi-local)."""
import json, sys, time, urllib.request
BRIDGE = "http://127.0.0.1:8787"
TOKEN = "firefox-bidi-local"
HDR = {"Authorization": "Bearer " + TOKEN, "Content-Type": "application/json"}
def post(path, payload):
req = urllib.request.Request(BRIDGE + path, data=json.dumps(payload).encode(), headers=HDR, method="POST")
with urllib.request.urlopen(req, timeout=30) as r:
return json.loads(r.read().decode())
def ev(expr):
return post("/eval", {"expression": expr, "awaitPromise": True})
def cmd(method, params):
return post("/cmd", {"method": method, "params": params})
def click(x, y, button=0):
return cmd("input.performActions", {"context": _ctx(), "actions": [{"id": "m", "type": "pointer", "parameters": {"pointerType": "mouse"}, "actions": [
{"type": "pointerMove", "x": x, "y": y, "origin": "viewport"},
{"type": "pointerDown", "button": button}, {"type": "pause", "duration": 90},
{"type": "pointerUp", "button": button}, {"type": "pause", "duration": 450}]}]})
_CTX = None
def _ctx():
global _CTX
if _CTX is None:
_CTX = post("/status", {})["context"] if False else None
# /status è GET
return _CTX
def status():
req = urllib.request.Request(BRIDGE + "/status", headers=HDR)
with urllib.request.urlopen(req, timeout=10) as r:
return json.loads(r.read().decode())
def ctx():
global _CTX
if _CTX is None:
_CTX = status()["context"]
return _CTX
def click_ctx(x, y, button=0):
return cmd("input.performActions", {"context": ctx(), "actions": [{"id": "m", "type": "pointer", "parameters": {"pointerType": "mouse"}, "actions": [
{"type": "pointerMove", "x": x, "y": y, "origin": "viewport"},
{"type": "pointerDown", "button": button}, {"type": "pause", "duration": 90},
{"type": "pointerUp", "button": button}, {"type": "pause", "duration": 450}]}]})
CTRL = "\ue009"; ENTER = "\ue007"; F2 = "\ue002"
def type_text(text, commit=ENTER):
acts = [{"type": "keyDown", "value": CTRL}, {"type": "keyDown", "value": "a"}, {"type": "keyUp", "value": "a"}, {"type": "keyUp", "value": CTRL}, {"type": "pause", "duration": 200}]
for ch in text:
acts += [{"type": "keyDown", "value": ch}, {"type": "keyUp", "value": ch}, {"type": "pause", "duration": 110}]
acts += [{"type": "pause", "duration": 250}, {"type": "keyDown", "value": commit}, {"type": "keyUp", "value": commit}, {"type": "pause", "duration": 400}]
return cmd("input.performActions", {"context": ctx(), "actions": [{"id": "k", "type": "key", "actions": acts}]})
def cell_center(r, c):
out = ev(f"(() => {{ const e = document.getElementById('M0:46:2:1[{r},{c}]'); if (!e) return JSON.stringify({{err:'nofound'}}); e.scrollIntoView({{block:'center', inline:'center'}}); return 'ok'; }})()")
time.sleep(0.6)
out = ev(f"(() => {{ const e = document.getElementById('M0:46:2:1[{r},{c}]'); const b = e.getBoundingClientRect(); return JSON.stringify({{x: Math.round(b.x + b.width/2), y: Math.round(b.y + b.height/2)}}); }})()")
v = out.get("result", out)
if isinstance(v, str):
v = json.loads(v)
return v
def dialogs():
out = ev("""(() => { const d = [...document.querySelectorAll('[role="dialog"], .urDlg, [class*="Dialog"], [class*="dialog"]')].filter(e => e.getBoundingClientRect().width > 0).map(e => (e.textContent||'').trim().slice(0,120)); return JSON.stringify(d); })()""")
v = out.get("result", "")
return json.loads(v) if isinstance(v, str) else v
def cell_text(r, c):
out = ev(f"(() => {{ const e = document.getElementById('M0:46:2:1[{r},{c}]'); return e ? (e.textContent||'').trim() : null; }})()")
v = out.get("result")
return v
def write_cell(r, c, text, expected=None):
"""Scrive text nella cella [r,c]: click reale, Ctrl+A, digitazione, Invio. Ritorna dict esito."""
p = cell_center(r, c)
if "err" in p:
return {"error": p["err"]}
click_ctx(p["x"], p["y"])
time.sleep(0.2)
type_text(text)
time.sleep(0.5)
val = cell_text(r, c)
exp = expected if expected is not None else text
if val != exp:
# retry con F2 come commit
type_text(text, commit=F2)
time.sleep(0.5)
val = cell_text(r, c)
return {"cell": f"[{r},{c}]", "sent": text, "read": val, "ok": val == exp, "dialogs": dialogs()}
if __name__ == "__main__":
what = sys.argv[1]
if what == "grid":
cols = [int(x) for x in sys.argv[2].split(",")] if len(sys.argv) > 2 else list(range(2, 41))
rows = [int(x) for x in sys.argv[3].split(",")] if len(sys.argv) > 3 else [1, 2, 3]
res = {}
for r in rows:
res[r] = {c: cell_text(r, c) for c in cols}
print(json.dumps(res, ensure_ascii=False))
elif what == "dialogs":
print(json.dumps(dialogs(), ensure_ascii=False))
elif what == "write":
r, c, text = int(sys.argv[2]), int(sys.argv[3]), sys.argv[4]
print(json.dumps(write_cell(r, c, text), ensure_ascii=False))
elif what == "center":
r, c = int(sys.argv[2]), int(sys.argv[3])
print(json.dumps(cell_center(r, c)))
elif what == "click":
x, y = int(sys.argv[2]), int(sys.argv[3])
b = int(sys.argv[4]) if len(sys.argv) > 4 else 0
click_ctx(x, y, b)
print("clicked", x, y, "btn", b)