mindvest-atlas 0.41.0 → 0.43.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/commands/computer-use.js +4 -1
- package/src/config.js +1 -1
- package/src/helpers/atlas_env.py +96 -3
package/package.json
CHANGED
|
@@ -119,7 +119,10 @@ export function runInProject(job, code, root) {
|
|
|
119
119
|
// THE SAME HOLDER FOR EVERY COMMAND in this shell, and no process behind the
|
|
120
120
|
// claim: each of these exits, and a claim needing a live process would be
|
|
121
121
|
// gone before the next one ran.
|
|
122
|
-
|
|
122
|
+
// INHERITED FIRST: Atlas hands the chat's own session down, so each
|
|
123
|
+
// conversation and each ask() gets its OWN recording. The shell id is the
|
|
124
|
+
// fallback for a bare terminal Atlas did not start.
|
|
125
|
+
if (!env.ATLAS_COMPUTER_SESSION) env.ATLAS_COMPUTER_SESSION = shellSession(root);
|
|
123
126
|
env.ATLAS_COMPUTER_PID = '0';
|
|
124
127
|
env.PYTHONPATH = [dir, process.env.PYTHONPATH].filter(Boolean).join(path.delimiter);
|
|
125
128
|
const out = spawnSync(python[0], [...python.slice(1), '-c', code], {
|
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.43.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
|
@@ -998,6 +998,44 @@ def _primary_index(mons):
|
|
|
998
998
|
_SESSION_SCREEN = {"index": None}
|
|
999
999
|
|
|
1000
1000
|
|
|
1001
|
+
def _screen_note():
|
|
1002
|
+
"""Where the chosen screen is remembered, BETWEEN COMMANDS.
|
|
1003
|
+
|
|
1004
|
+
THIS IS WHY CAPTURES CAME BACK FROM SCREEN 1. Working the computer from a
|
|
1005
|
+
shell means one short-lived process per action, and module state does not
|
|
1006
|
+
survive that: use_screen(2) set a number in a process that then exited, and
|
|
1007
|
+
the next command started fresh, found nothing, and fell back to the primary.
|
|
1008
|
+
So a run said "work on screen 2", was told it had, and then every picture it
|
|
1009
|
+
took was of screen 1 while it clicked coordinates measured on screen 2
|
|
1010
|
+
(owner, 2026-09-06: "the capture keeps returning screen-1 content even after
|
|
1011
|
+
switching to screen 2").
|
|
1012
|
+
|
|
1013
|
+
Beside the actions, because it is a fact about this session like they are.
|
|
1014
|
+
"""
|
|
1015
|
+
return os.path.join(_session_dir(), "screen.json")
|
|
1016
|
+
|
|
1017
|
+
|
|
1018
|
+
def _remember_screen(index):
|
|
1019
|
+
import json as _json
|
|
1020
|
+
try:
|
|
1021
|
+
with open(_screen_note(), "w", encoding="utf-8") as fh:
|
|
1022
|
+
_json.dump({"index": int(index)}, fh)
|
|
1023
|
+
except (OSError, TypeError, ValueError):
|
|
1024
|
+
# A choice we cannot write down still holds for THIS process, which is
|
|
1025
|
+
# the whole of a script's run.
|
|
1026
|
+
pass
|
|
1027
|
+
|
|
1028
|
+
|
|
1029
|
+
def _recall_screen():
|
|
1030
|
+
import json as _json
|
|
1031
|
+
try:
|
|
1032
|
+
with open(_screen_note(), encoding="utf-8") as fh:
|
|
1033
|
+
want = _json.load(fh).get("index")
|
|
1034
|
+
return int(want) if want else None
|
|
1035
|
+
except Exception:
|
|
1036
|
+
return None
|
|
1037
|
+
|
|
1038
|
+
|
|
1001
1039
|
def _chosen_screen():
|
|
1002
1040
|
"""Which screen this computer has been told to work on, or None for any."""
|
|
1003
1041
|
import json as _json
|
|
@@ -1026,6 +1064,7 @@ def use_screen(index=None):
|
|
|
1026
1064
|
% (want, len(mons), "is" if len(mons) == 1 else "are")
|
|
1027
1065
|
)
|
|
1028
1066
|
_SESSION_SCREEN["index"] = want
|
|
1067
|
+
_remember_screen(want)
|
|
1029
1068
|
_log_action("use_screen", {"monitor": want, "x": found["x"], "y": found["y"],
|
|
1030
1069
|
"width": found["width"], "height": found["height"]})
|
|
1031
1070
|
return found
|
|
@@ -1036,6 +1075,10 @@ def _current_screen():
|
|
|
1036
1075
|
has said. Returns the whole entry, so callers have its bounds."""
|
|
1037
1076
|
mons = screens()
|
|
1038
1077
|
want = _SESSION_SCREEN["index"]
|
|
1078
|
+
if want is None:
|
|
1079
|
+
# WHAT THIS SESSION LAST CHOSE, before anything else. A command that did
|
|
1080
|
+
# not itself call use_screen is still part of the run that did.
|
|
1081
|
+
want = _recall_screen()
|
|
1039
1082
|
if want is None:
|
|
1040
1083
|
# THE ONE THE PERSON CHOSE, if they chose one. Defaulting to the primary
|
|
1041
1084
|
# is right on one screen and wrong on three: somebody with a spare
|
|
@@ -1146,6 +1189,37 @@ def screen(path=None):
|
|
|
1146
1189
|
% (m["x"], m["y"])}
|
|
1147
1190
|
|
|
1148
1191
|
|
|
1192
|
+
def _screen_changed(path):
|
|
1193
|
+
"""True when this picture differs from the one before it.
|
|
1194
|
+
|
|
1195
|
+
Compared by content, not by time: a screen that is still is genuinely
|
|
1196
|
+
unchanged, and telling a reader so saves it opening an image to discover
|
|
1197
|
+
nothing moved. Kept in the session folder, so it works across the separate
|
|
1198
|
+
processes a shell run is made of.
|
|
1199
|
+
"""
|
|
1200
|
+
import hashlib
|
|
1201
|
+
if not path:
|
|
1202
|
+
return True
|
|
1203
|
+
try:
|
|
1204
|
+
with open(path, "rb") as fh:
|
|
1205
|
+
now = hashlib.md5(fh.read()).hexdigest()
|
|
1206
|
+
except OSError:
|
|
1207
|
+
return True
|
|
1208
|
+
mark = os.path.join(_session_dir(), "last-screen.txt")
|
|
1209
|
+
was = None
|
|
1210
|
+
try:
|
|
1211
|
+
with open(mark, encoding="utf-8") as fh:
|
|
1212
|
+
was = fh.read().strip()
|
|
1213
|
+
except OSError:
|
|
1214
|
+
pass
|
|
1215
|
+
try:
|
|
1216
|
+
with open(mark, "w", encoding="utf-8") as fh:
|
|
1217
|
+
fh.write(now)
|
|
1218
|
+
except OSError:
|
|
1219
|
+
pass
|
|
1220
|
+
return was != now
|
|
1221
|
+
|
|
1222
|
+
|
|
1149
1223
|
def do(*steps, **kw):
|
|
1150
1224
|
"""Do several things, then look ONCE. The fast loop.
|
|
1151
1225
|
|
|
@@ -1198,6 +1272,11 @@ def do(*steps, **kw):
|
|
|
1198
1272
|
_time.sleep(settle)
|
|
1199
1273
|
shot = screen()
|
|
1200
1274
|
out = {"did": did}
|
|
1275
|
+
# DID ANYTHING ACTUALLY CHANGE? A picture that is byte-for-byte the last one
|
|
1276
|
+
# is a wasted look: the reader spends a turn opening it to find out nothing
|
|
1277
|
+
# happened, which is the slowest possible way to learn that. Said in words
|
|
1278
|
+
# instead, and the picture is still there for when it did.
|
|
1279
|
+
out["changed"] = _screen_changed(shot if isinstance(shot, str) else shot.get("path"))
|
|
1201
1280
|
# screen() answers a bare path on one monitor and a dict on several. Both
|
|
1202
1281
|
# travel, because the offset is the thing a reader needs next.
|
|
1203
1282
|
if isinstance(shot, dict):
|
|
@@ -1261,13 +1340,27 @@ def type(text, interval=0.01): # noqa: A001 (the name people expect)
|
|
|
1261
1340
|
{"chars": len(str(text))})
|
|
1262
1341
|
|
|
1263
1342
|
|
|
1264
|
-
def key(keys):
|
|
1265
|
-
"""One key, or several together
|
|
1343
|
+
def key(*keys):
|
|
1344
|
+
"""One key, or several together.
|
|
1345
|
+
|
|
1346
|
+
key("enter")
|
|
1347
|
+
key("ctrl", "a") # and key(["ctrl", "a"]) still works
|
|
1348
|
+
key("mod", "c")
|
|
1349
|
+
|
|
1350
|
+
BOTH SHAPES, because everything that calls this assumed the first one and it
|
|
1351
|
+
only accepted the second. `atlas computer-use key ctrl a`, the guidance, and
|
|
1352
|
+
every combination anybody wrote all raised "key() takes 1 positional
|
|
1353
|
+
argument", so no chord worked ANYWHERE while single keys did (owner,
|
|
1354
|
+
2026-09-06, after a run could not minimise a window with win+down and
|
|
1355
|
+
concluded the CLI only takes one token). A signature that only its author
|
|
1356
|
+
calls correctly is a broken signature.
|
|
1266
1357
|
|
|
1267
1358
|
"mod" is control on Windows and Linux and command on a Mac, so one script
|
|
1268
1359
|
works on all three. Anything else is passed through as written.
|
|
1269
1360
|
"""
|
|
1270
|
-
|
|
1361
|
+
if len(keys) == 1 and not isinstance(keys[0], str):
|
|
1362
|
+
keys = tuple(keys[0])
|
|
1363
|
+
names = _keys(list(keys))
|
|
1271
1364
|
return _do("key", lambda: (_gui().hotkey(*names) if len(names) > 1 else _gui().press(names[0])),
|
|
1272
1365
|
{"keys": names})
|
|
1273
1366
|
|