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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mindvest-atlas",
3
- "version": "0.35.0",
3
+ "version": "0.40.0",
4
4
  "description": "Atlas CLI \u2014 OAuth login, tool calls, and live alert/flow streaming for the Atlas trading API",
5
5
  "type": "module",
6
6
  "bin": {
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.35.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';
@@ -923,12 +923,18 @@ def screens():
923
923
  try:
924
924
  import mss as _mss
925
925
  with _mss.mss() as sct:
926
- out = []
927
- for i, m in enumerate(sct.monitors[1:], start=1):
928
- out.append({"index": i, "x": m["left"], "y": m["top"],
929
- "width": m["width"], "height": m["height"],
930
- "primary": m["left"] == 0 and m["top"] == 0})
931
- return out
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
- # In the order Windows reports them, with the one at the origin
961
- # named primary: the same convention mss uses, so a number means
962
- # the same monitor whichever path answered.
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
- return _do("screen", take, {"monitor": m["index"]})
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
- def click(x, y, button="left", clicks=1):
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}, points=[(x, y)])
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), {"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), {"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=960, quality=42, monitor=1):
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: half the pixels is half the work in
1410
- # the encoder as well as on the wire, and nobody watching needs to
1411
- # read the text. They need to see what is happening.
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
- img = img.resize((width, height))
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
- img.convert("RGB").save(buf, format="JPEG", quality=quality, optimize=False)
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=960, quality=42, max_s=120):
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