mindvest-atlas 0.35.0 → 0.40.0
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.
- package/package.json +1 -1
- package/src/config.js +1 -1
- package/src/helpers/atlas_env.py +106 -24
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
|
|
5
|
-
export const VERSION = '0.
|
|
5
|
+
export const VERSION = '0.40.0';
|
|
6
6
|
export const DEFAULT_BASE_URL = 'https://atlasmcp.finmanagerai.com';
|
|
7
7
|
export const DEFAULT_SCOPE = 'atlas broker';
|
|
8
8
|
export const CLIENT_NAME = 'Atlas CLI';
|
package/src/helpers/atlas_env.py
CHANGED
|
@@ -923,12 +923,18 @@ def screens():
|
|
|
923
923
|
try:
|
|
924
924
|
import mss as _mss
|
|
925
925
|
with _mss.mss() as sct:
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
926
|
+
# LEFT TO RIGHT, ALWAYS. The number has to mean the same monitor
|
|
927
|
+
# every time it is said, and it was not: this path took whatever
|
|
928
|
+
# order the OS gave, the fallback below sorted top-to-bottom, and
|
|
929
|
+
# the app's picker used a third. So screen 1 could be the RIGHT-hand
|
|
930
|
+
# monitor, a run bound itself to it, and a click on the left one was
|
|
931
|
+
# refused for being "not on screen 1" (owner, 2026-09-05). Left to
|
|
932
|
+
# right is also how people say it.
|
|
933
|
+
mons = sorted(sct.monitors[1:], key=lambda m: (m["left"], m["top"]))
|
|
934
|
+
return [{"index": i, "x": m["left"], "y": m["top"],
|
|
935
|
+
"width": m["width"], "height": m["height"],
|
|
936
|
+
"primary": m["left"] == 0 and m["top"] == 0}
|
|
937
|
+
for i, m in enumerate(mons, start=1)]
|
|
932
938
|
except ImportError:
|
|
933
939
|
pass
|
|
934
940
|
# WITHOUT mss, WINDOWS CAN STILL COUNT ITS OWN MONITORS. Reporting one
|
|
@@ -957,10 +963,9 @@ def screens():
|
|
|
957
963
|
|
|
958
964
|
ctypes.windll.user32.EnumDisplayMonitors(0, 0, proto(each), 0)
|
|
959
965
|
if found:
|
|
960
|
-
#
|
|
961
|
-
#
|
|
962
|
-
|
|
963
|
-
found.sort(key=lambda m: (m[1], m[0]))
|
|
966
|
+
# The SAME order as the mss path above: left to right, so a
|
|
967
|
+
# number means the same monitor whichever one answered.
|
|
968
|
+
found.sort(key=lambda m: (m[0], m[1]))
|
|
964
969
|
return [{"index": i, "x": x, "y": y, "width": w, "height": h,
|
|
965
970
|
"primary": x == 0 and y == 0}
|
|
966
971
|
for i, (x, y, w, h) in enumerate(found, start=1)]
|
|
@@ -1058,6 +1063,26 @@ def _on_screen(x, y):
|
|
|
1058
1063
|
inside = (m["x"] <= int(x) < m["x"] + m["width"]) and (m["y"] <= int(y) < m["y"] + m["height"])
|
|
1059
1064
|
if inside:
|
|
1060
1065
|
return
|
|
1066
|
+
# THE COMMONEST WAY TO BE WRONG HERE IS ALSO THE EASIEST TO FIX FOR YOU.
|
|
1067
|
+
#
|
|
1068
|
+
# A picture of screen 2 is an image 1920 wide whose top left is 0,0, but the
|
|
1069
|
+
# pointer lives on the whole desktop, where that same corner is 1920,0. So a
|
|
1070
|
+
# position read honestly off the picture lands one screen to the left, and
|
|
1071
|
+
# on a two-monitor desk that is EVERY click (owner, 2026-09-05: "it
|
|
1072
|
+
# struggles for mouse click on multiple screen"). Saying "not on screen 2"
|
|
1073
|
+
# is true and leaves you to work out why.
|
|
1074
|
+
#
|
|
1075
|
+
# If adding this screen's origin would have put the point on it, that is
|
|
1076
|
+
# what happened, so say the number that works.
|
|
1077
|
+
shifted_x, shifted_y = int(x) + m["x"], int(y) + m["y"]
|
|
1078
|
+
if (m["x"] <= shifted_x < m["x"] + m["width"]) and (m["y"] <= shifted_y < m["y"] + m["height"]):
|
|
1079
|
+
raise RuntimeError(
|
|
1080
|
+
"(%s, %s) reads like a position measured INSIDE the picture of "
|
|
1081
|
+
"screen %s, but clicks land on the whole desktop, where that screen "
|
|
1082
|
+
"starts at x %s, y %s. Use (%s, %s) instead: add the screen's origin "
|
|
1083
|
+
"to anything you read off its picture. screen() reports that origin."
|
|
1084
|
+
% (x, y, m["index"], m["x"], m["y"], shifted_x, shifted_y)
|
|
1085
|
+
)
|
|
1061
1086
|
raise RuntimeError(
|
|
1062
1087
|
"(%s, %s) is not on screen %s, which is what this run is working on "
|
|
1063
1088
|
"(it covers x %s..%s, y %s..%s). Call use_screen() to move to another "
|
|
@@ -1073,6 +1098,20 @@ def screen(path=None):
|
|
|
1073
1098
|
was is written into the log, so a replay never leaves you wondering what you
|
|
1074
1099
|
are looking at.
|
|
1075
1100
|
|
|
1101
|
+
WHAT COMES BACK IS THE PATH, and on a multi-screen machine also where that
|
|
1102
|
+
screen STARTS:
|
|
1103
|
+
|
|
1104
|
+
{"path": "...png", "screen": 2, "x": 1920, "y": 0,
|
|
1105
|
+
"note": "add x 1920, y 0 to positions you read off this picture"}
|
|
1106
|
+
|
|
1107
|
+
That matters and it is the easiest thing here to get wrong. The picture is
|
|
1108
|
+
an image whose top left is 0,0; the pointer lives on the whole desktop,
|
|
1109
|
+
where that same corner may be 1920,0. A position read straight off the
|
|
1110
|
+
picture and clicked lands one screen to the left, and on a two-monitor desk
|
|
1111
|
+
that is EVERY click. Add the origin.
|
|
1112
|
+
|
|
1113
|
+
On a single screen the origin is 0,0 and this is just the path, as before.
|
|
1114
|
+
|
|
1076
1115
|
Not dashboard.shot(): that one draws this project's DASHBOARD in a frame. Two
|
|
1077
1116
|
things called screenshot, one of them the whole desktop, is a mistake
|
|
1078
1117
|
somebody makes once with a real click.
|
|
@@ -1094,21 +1133,48 @@ def screen(path=None):
|
|
|
1094
1133
|
# Say which was taken rather than letting a two-monitor desk assume.
|
|
1095
1134
|
_gui().screenshot().save(out)
|
|
1096
1135
|
return out
|
|
1097
|
-
|
|
1136
|
+
out = _do("screen", take, {"monitor": m["index"]})
|
|
1137
|
+
# A BARE PATH ON A SINGLE SCREEN, because that is what it has always been and
|
|
1138
|
+
# nothing about one monitor needs explaining. Where there is more than one,
|
|
1139
|
+
# the offset travels WITH the picture rather than waiting to be asked for:
|
|
1140
|
+
# the moment it is needed is the moment the picture is read.
|
|
1141
|
+
if not m.get("x") and not m.get("y"):
|
|
1142
|
+
return out
|
|
1143
|
+
return {"path": out, "screen": m["index"], "x": m["x"], "y": m["y"],
|
|
1144
|
+
"width": m["width"], "height": m["height"],
|
|
1145
|
+
"note": "add x %s, y %s to positions you read off this picture"
|
|
1146
|
+
% (m["x"], m["y"])}
|
|
1147
|
+
|
|
1148
|
+
|
|
1149
|
+
def click(x, y, button="left", clicks=1, what=""):
|
|
1150
|
+
"""Press something. SAY WHAT IT IS if you know.
|
|
1098
1151
|
|
|
1152
|
+
click(*sign_in, what="Sign in")
|
|
1099
1153
|
|
|
1100
|
-
|
|
1154
|
+
"Clicked at 3724, 17" tells somebody reading it back nothing at all: they
|
|
1155
|
+
cannot look up what was at those coordinates on a screen that has since
|
|
1156
|
+
changed (owner, 2026-09-05: "not just clicked 3724 17, say what it did
|
|
1157
|
+
too"). You know what you are pressing, because you just read it off the
|
|
1158
|
+
screen or out of find(); the log is the only place that knowledge can
|
|
1159
|
+
survive the click.
|
|
1160
|
+
"""
|
|
1101
1161
|
return _do("click", lambda: _gui().click(x=x, y=y, button=button, clicks=clicks),
|
|
1102
|
-
{"x": x, "y": y, "button": button, "clicks": clicks
|
|
1162
|
+
{"x": x, "y": y, "button": button, "clicks": clicks,
|
|
1163
|
+
"what": str(what)[:80], "screen": _SESSION_SCREEN["index"]},
|
|
1164
|
+
points=[(x, y)])
|
|
1103
1165
|
|
|
1104
1166
|
|
|
1105
|
-
def double_click(x, y):
|
|
1106
|
-
return _do("double_click", lambda: _gui().doubleClick(x=x, y=y),
|
|
1167
|
+
def double_click(x, y, what=""):
|
|
1168
|
+
return _do("double_click", lambda: _gui().doubleClick(x=x, y=y),
|
|
1169
|
+
{"x": x, "y": y, "what": str(what)[:80],
|
|
1170
|
+
"screen": _SESSION_SCREEN["index"]},
|
|
1107
1171
|
points=[(x, y)])
|
|
1108
1172
|
|
|
1109
1173
|
|
|
1110
|
-
def right_click(x, y):
|
|
1111
|
-
return _do("right_click", lambda: _gui().rightClick(x=x, y=y),
|
|
1174
|
+
def right_click(x, y, what=""):
|
|
1175
|
+
return _do("right_click", lambda: _gui().rightClick(x=x, y=y),
|
|
1176
|
+
{"x": x, "y": y, "what": str(what)[:80],
|
|
1177
|
+
"screen": _SESSION_SCREEN["index"]},
|
|
1112
1178
|
points=[(x, y)])
|
|
1113
1179
|
|
|
1114
1180
|
|
|
@@ -1382,7 +1448,7 @@ def note(text):
|
|
|
1382
1448
|
return _log_action("note", {"text": str(text)[:500]})
|
|
1383
1449
|
|
|
1384
1450
|
|
|
1385
|
-
def _grab_frame(width=
|
|
1451
|
+
def _grab_frame(width=1920, quality=58, monitor=1):
|
|
1386
1452
|
"""One JPEG of a screen, downscaled. None when this machine cannot.
|
|
1387
1453
|
|
|
1388
1454
|
mss where it exists because it is much faster and this runs a few times a
|
|
@@ -1406,13 +1472,29 @@ def _grab_frame(width=960, quality=42, monitor=1):
|
|
|
1406
1472
|
try:
|
|
1407
1473
|
import io as _io
|
|
1408
1474
|
if img.width > width:
|
|
1409
|
-
# Downscaled BEFORE encoding:
|
|
1410
|
-
#
|
|
1411
|
-
#
|
|
1475
|
+
# Downscaled BEFORE encoding: fewer pixels is less work in the
|
|
1476
|
+
# encoder as well as on the wire.
|
|
1477
|
+
#
|
|
1478
|
+
# 1920 IS NO DOWNSCALE AT ALL on an ordinary screen, which is the
|
|
1479
|
+
# point. Anything smaller is sharpened here and then UPSCALED by a
|
|
1480
|
+
# browser window wider than it, which puts the blur back after we
|
|
1481
|
+
# paid to avoid it (owner, 2026-09-05: "the web is still blurred").
|
|
1482
|
+
# This branch now only runs on a screen bigger than 1920.
|
|
1483
|
+
#
|
|
1484
|
+
# The filter matters as much as the size: the default resample is
|
|
1485
|
+
# fine for enlarging and mushy for shrinking, which is all this does.
|
|
1412
1486
|
height = max(1, int(img.height * (float(width) / img.width)))
|
|
1413
|
-
|
|
1487
|
+
try:
|
|
1488
|
+
from PIL import Image as _I
|
|
1489
|
+
img = img.resize((width, height), _I.LANCZOS)
|
|
1490
|
+
except Exception:
|
|
1491
|
+
img = img.resize((width, height))
|
|
1414
1492
|
buf = _io.BytesIO()
|
|
1415
|
-
|
|
1493
|
+
# subsampling=0 keeps colour at full resolution. JPEG throws away three
|
|
1494
|
+
# quarters of it by default, which is invisible on a photograph and is
|
|
1495
|
+
# exactly what turns coloured text on a screen to mush.
|
|
1496
|
+
img.convert("RGB").save(buf, format="JPEG", quality=quality,
|
|
1497
|
+
subsampling=0, optimize=False)
|
|
1416
1498
|
return buf.getvalue()
|
|
1417
1499
|
except Exception:
|
|
1418
1500
|
return None
|
|
@@ -1448,7 +1530,7 @@ def _read_own_actions():
|
|
|
1448
1530
|
return rows
|
|
1449
1531
|
|
|
1450
1532
|
|
|
1451
|
-
def stream_frames(send=None, fps=3, width=
|
|
1533
|
+
def stream_frames(send=None, fps=3, width=1920, quality=58, max_s=120):
|
|
1452
1534
|
"""Send frames while a run is driving this computer. Returns when it stops.
|
|
1453
1535
|
|
|
1454
1536
|
WATCHING IS GATED ON DRIVING. Frames go only while somebody holds the
|