mindvest-atlas 0.50.3 → 0.52.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/cli-installs.js +3 -3
- package/src/cli.js +1 -1
- package/src/commands/computer-use.js +8 -4
- package/src/commands/dashboard.js +1 -1
- package/src/commands/remote-control.js +2 -2
- package/src/commands/screenshot.js +1 -1
- package/src/config.js +1 -1
- package/src/helpers/atlas_env.js +5 -6
- package/src/helpers/atlas_env.py +126 -19
- package/src/remote-control.js +2 -2
package/package.json
CHANGED
package/src/cli-installs.js
CHANGED
|
@@ -41,7 +41,7 @@ function versionIn(text) {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
|
-
* Where Atlas
|
|
44
|
+
* Where Atlas Studio keeps the copy it manages for itself.
|
|
45
45
|
*
|
|
46
46
|
* It fetches its own CLI so Local Mode works on a machine with nothing
|
|
47
47
|
* installed. That copy is a THIRD version to be out of step with the other two,
|
|
@@ -102,12 +102,12 @@ export function installedClis() {
|
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
// The copy Atlas
|
|
105
|
+
// The copy Atlas Studio manages for itself.
|
|
106
106
|
const studio = studioCliDir();
|
|
107
107
|
try {
|
|
108
108
|
const pkg = JSON.parse(fs.readFileSync(path.join(studio, 'package.json'), 'utf8'));
|
|
109
109
|
rows.push({
|
|
110
|
-
kind: 'Atlas
|
|
110
|
+
kind: 'Atlas Studio', version: pkg.version || '', where: studio,
|
|
111
111
|
note: 'the app updates this one itself',
|
|
112
112
|
});
|
|
113
113
|
} catch { /* the app has not fetched one */ }
|
package/src/cli.js
CHANGED
|
@@ -101,7 +101,7 @@ export async function main(rawArgs) {
|
|
|
101
101
|
case 'run':
|
|
102
102
|
return runCommand(withoutCommand(args));
|
|
103
103
|
// SEEING AND USING THIS COMPUTER, from a shell. It existed only in the
|
|
104
|
-
// Python package, and Atlas
|
|
104
|
+
// Python package, and Atlas Studio bundles THIS one, so the app
|
|
105
105
|
// answered "Unknown command or tool: computer-use" on the very machine
|
|
106
106
|
// the command was written for (owner, 2026-09-05).
|
|
107
107
|
case 'computer-use':
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// The agent in a chat is a SHELL agent. Computer use lived only inside project
|
|
8
8
|
// scripts, so it reasoned its way to the truth and then to a wall.
|
|
9
9
|
//
|
|
10
|
-
// AND THIS IS THE COPY THAT ACTUALLY RUNS. Atlas
|
|
10
|
+
// AND THIS IS THE COPY THAT ACTUALLY RUNS. Atlas Studio bundles the
|
|
11
11
|
// NODE CLI, so a version of this that existed only in the Python package was a
|
|
12
12
|
// command the app rejected: "Unknown command or tool: computer-use", on the one
|
|
13
13
|
// machine it was written for.
|
|
@@ -103,6 +103,10 @@ const OPS = {
|
|
|
103
103
|
'hide-consoles': ['hide_consoles', 0],
|
|
104
104
|
// ── A JOB THAT IS MOSTLY WAITING ──────────────────────────────────────
|
|
105
105
|
watch: ['watch', 1],
|
|
106
|
+
// ── A BROWSER IS AN APPLICATION, NOT A WALL OF PIXELS ─────────────────
|
|
107
|
+
browser: ['browser', 0],
|
|
108
|
+
'go-to': ['go_to', 1],
|
|
109
|
+
'find-on-page': ['find_on_page', 1],
|
|
106
110
|
// ── ONE CALL FOR WHAT USED TO BE FOUR ─────────────────────────────────
|
|
107
111
|
'scroll-x': ['scroll_x', 1],
|
|
108
112
|
'shift-scroll': ['shift_scroll', 1],
|
|
@@ -311,7 +315,7 @@ export function computerUseCommand(args) {
|
|
|
311
315
|
if (sub === 'start') {
|
|
312
316
|
if (!computerUseAllowed()) {
|
|
313
317
|
eprintln(color.red('Using the computer is switched off.'));
|
|
314
|
-
eprintln(color.dim(' Open Atlas
|
|
318
|
+
eprintln(color.dim(' Open Atlas Studio > This Computer and turn on '
|
|
315
319
|
+ '"Let an agent use this computer", then run this again.'));
|
|
316
320
|
return 2;
|
|
317
321
|
}
|
|
@@ -321,7 +325,7 @@ export function computerUseCommand(args) {
|
|
|
321
325
|
eprintln(color.dim(' There is one mouse, so it is one at a time.'));
|
|
322
326
|
return 3;
|
|
323
327
|
}
|
|
324
|
-
// TAKING IT IS WHAT OPENS THE WATCH VIEW. Atlas
|
|
328
|
+
// TAKING IT IS WHAT OPENS THE WATCH VIEW. Atlas Studio watches the
|
|
325
329
|
// claim, so there is nothing to ask it: the moment this lands, the strip
|
|
326
330
|
// goes up and the screen appears. One signal, not two that can disagree.
|
|
327
331
|
const code = 'import atlas_env as e, json\n'
|
|
@@ -331,7 +335,7 @@ export function computerUseCommand(args) {
|
|
|
331
335
|
const out = runInProject(job, code);
|
|
332
336
|
if (out === 0) {
|
|
333
337
|
println('');
|
|
334
|
-
println(color.dim('Watching opened in Atlas
|
|
338
|
+
println(color.dim('Watching opened in Atlas Studio. Now use it:'));
|
|
335
339
|
println(color.dim(' atlas computer-use screen'));
|
|
336
340
|
println(color.dim(' atlas computer-use click <x> <y>'));
|
|
337
341
|
println(color.dim(' atlas computer-use stop when you are done'));
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// The same gap `atlas computer-use` filled, one road over: driving the
|
|
4
4
|
// dashboard existed in atlas_env and nowhere else, so an agent in a chat could
|
|
5
5
|
// only reach it by hand-writing the import (owner, 2026-09-06). And it has to
|
|
6
|
-
// be in THIS CLI, because Atlas
|
|
6
|
+
// be in THIS CLI, because Atlas Studio bundles this one.
|
|
7
7
|
//
|
|
8
8
|
// IT IS NOT THE COMPUTER. This drives the project's own window, so the real
|
|
9
9
|
// pointer never moves, nothing is claimed, no switch is needed, and a click at
|
|
@@ -245,7 +245,7 @@ async function handleAsk(ask, root) {
|
|
|
245
245
|
const argv = Array.isArray(py) ? [...py] : [String(py)];
|
|
246
246
|
// THE PROJECT'S OWN atlas_env, which both runners write, so nothing extra
|
|
247
247
|
// has to be installed for this to work. It used to import the Python CLI
|
|
248
|
-
// package, which Atlas
|
|
248
|
+
// package, which Atlas Studio never installs: live watching would
|
|
249
249
|
// have failed on every machine running the app.
|
|
250
250
|
const { dir } = workspace(ask.job || '', root);
|
|
251
251
|
try { ensureProjectFiles(dir); } catch { /* it may already be right */ }
|
|
@@ -334,7 +334,7 @@ async function handleAsk(ask, root) {
|
|
|
334
334
|
if (ask.op === 'command') {
|
|
335
335
|
// A button, a switch or a form in the job's own dashboard, pressed from
|
|
336
336
|
// anywhere that draws it. It lands as a file for the script to read — the
|
|
337
|
-
// same file Atlas
|
|
337
|
+
// same file Atlas Studio writes when the press happens in the app,
|
|
338
338
|
// so one button means one thing wherever it is pressed.
|
|
339
339
|
return answer(ask.request_id, writeCommand(ask.job || '', ask.name || '', ask.payload, root));
|
|
340
340
|
}
|
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.52.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.js
CHANGED
|
@@ -64,7 +64,7 @@ export async function ask(prompt, { tools = true, fresh = false, timeoutMs = 600
|
|
|
64
64
|
if (!url || !token) {
|
|
65
65
|
throw new Error(
|
|
66
66
|
"No model is connected on this computer, so this script cannot ask one. " +
|
|
67
|
-
"Connect a model in Atlas
|
|
67
|
+
"Connect a model in Atlas Studio, or do this step in code."
|
|
68
68
|
);
|
|
69
69
|
}
|
|
70
70
|
const res = await fetch(url, {
|
|
@@ -127,7 +127,7 @@ async function driveDashboard(op, body = {}) {
|
|
|
127
127
|
const token = process.env.ATLAS_ASK_TOKEN || "";
|
|
128
128
|
if (!url || !token) {
|
|
129
129
|
throw new Error(
|
|
130
|
-
"The dashboard can only be driven from Atlas
|
|
130
|
+
"The dashboard can only be driven from Atlas Studio, which is "
|
|
131
131
|
+ "what draws it. Run this project from the app.",
|
|
132
132
|
);
|
|
133
133
|
}
|
|
@@ -183,8 +183,7 @@ export const dashboard = {
|
|
|
183
183
|
* await computer.type("hello");
|
|
184
184
|
* await computer.note("signing in");
|
|
185
185
|
*
|
|
186
|
-
* Off until the person turns it on, under This Computer in Atlas
|
|
187
|
-
* Control. Read skills/computer-use before using any of it.
|
|
186
|
+
* Off until the person turns it on, under This Computer in Atlas Studio. Read skills/computer-use before using any of it.
|
|
188
187
|
*/
|
|
189
188
|
async function useComputer(op, args = []) {
|
|
190
189
|
const { spawn } = await import("node:child_process");
|
|
@@ -256,12 +255,12 @@ export const computer = {
|
|
|
256
255
|
* Stop cleanly when a secret has not been filled in yet.
|
|
257
256
|
*
|
|
258
257
|
* Names what is missing and nothing else: a blank key is the owner's to fill in
|
|
259
|
-
* Atlas
|
|
258
|
+
* Atlas Studio, not something a script should work around.
|
|
260
259
|
*/
|
|
261
260
|
export function require_(...names) {
|
|
262
261
|
const missing = names.filter((n) => !process.env[n]);
|
|
263
262
|
if (missing.length) {
|
|
264
|
-
console.log(`Missing: ${missing.join(", ")}. Add the value in Atlas
|
|
263
|
+
console.log(`Missing: ${missing.join(", ")}. Add the value in Atlas Studio, then run again.`);
|
|
265
264
|
process.exit(2);
|
|
266
265
|
}
|
|
267
266
|
}
|
package/src/helpers/atlas_env.py
CHANGED
|
@@ -101,7 +101,7 @@ def ask(prompt, tools=True, fresh=False, timeout=600, model=""):
|
|
|
101
101
|
if not url or not token:
|
|
102
102
|
raise RuntimeError(
|
|
103
103
|
"No model is connected on this computer, so this script cannot ask one. "
|
|
104
|
-
"Connect a model in Atlas
|
|
104
|
+
"Connect a model in Atlas Studio, or do this step in code."
|
|
105
105
|
)
|
|
106
106
|
body = {"job": os.environ.get("ATLAS_JOB", ""), "prompt": str(prompt),
|
|
107
107
|
"tools": bool(tools), "fresh": bool(fresh)}
|
|
@@ -284,7 +284,7 @@ class _Dashboard(object):
|
|
|
284
284
|
token = os.environ.get("ATLAS_ASK_TOKEN", "")
|
|
285
285
|
if not url or not token:
|
|
286
286
|
raise RuntimeError(
|
|
287
|
-
"The dashboard can only be driven from Atlas
|
|
287
|
+
"The dashboard can only be driven from Atlas Studio, "
|
|
288
288
|
"which is what draws it. Run this project from the app."
|
|
289
289
|
)
|
|
290
290
|
job = os.path.basename(os.path.dirname(os.path.abspath(__file__)))
|
|
@@ -441,11 +441,11 @@ def require(*names):
|
|
|
441
441
|
"""Stop cleanly when a secret has not been filled in yet.
|
|
442
442
|
|
|
443
443
|
Names what is missing and nothing else: a blank key is the owner's to fill
|
|
444
|
-
in Atlas
|
|
444
|
+
in Atlas Studio, not something a script should work around.
|
|
445
445
|
"""
|
|
446
446
|
missing = [n for n in names if not os.environ.get(n)]
|
|
447
447
|
if missing:
|
|
448
|
-
print("Missing: %s. Add the value in Atlas
|
|
448
|
+
print("Missing: %s. Add the value in Atlas Studio, then run again."
|
|
449
449
|
% ", ".join(missing))
|
|
450
450
|
sys.exit(2)
|
|
451
451
|
|
|
@@ -497,7 +497,7 @@ def record_trade(symbol, quantity, entry_price, exit_price, opened_at, closed_at
|
|
|
497
497
|
#
|
|
498
498
|
# Seeing the screen, moving the mouse, typing. Everything a person can do at
|
|
499
499
|
# this machine, which is exactly why it is OFF until somebody says otherwise:
|
|
500
|
-
# Atlas
|
|
500
|
+
# Atlas Studio > This Computer > "Let an agent use this computer". The
|
|
501
501
|
# switch sets ATLAS_COMPUTER_USE for the run; without it every call here refuses
|
|
502
502
|
# and SAYS SO, and the refusal is written down like any other action.
|
|
503
503
|
#
|
|
@@ -512,7 +512,7 @@ def record_trade(symbol, quantity, entry_price, exit_price, opened_at, closed_at
|
|
|
512
512
|
# a password is a credential store nobody meant to make.
|
|
513
513
|
|
|
514
514
|
_COMPUTER_OFF = (
|
|
515
|
-
"Using the computer is switched off. Open Atlas
|
|
515
|
+
"Using the computer is switched off. Open Atlas Studio > This "
|
|
516
516
|
"Computer and turn on \"Let an agent use this computer\", then run this again."
|
|
517
517
|
)
|
|
518
518
|
|
|
@@ -746,12 +746,12 @@ def computer_ready():
|
|
|
746
746
|
import pyautogui # noqa: F401
|
|
747
747
|
except ImportError:
|
|
748
748
|
# TWO READERS, TWO ANSWERS. A person should never be sent to a shell:
|
|
749
|
-
# Atlas
|
|
749
|
+
# Atlas Studio fetches this when the switch goes on. An agent
|
|
750
750
|
# reading the same line HAS a shell, and if this project keeps its own
|
|
751
751
|
# venv that is the only place the package can go.
|
|
752
|
-
return ("This project cannot use the computer yet. In Atlas
|
|
753
|
-
"
|
|
754
|
-
"
|
|
752
|
+
return ("This project cannot use the computer yet. In Atlas Studio, turn "
|
|
753
|
+
"Computer Use off and on and it will fetch what is missing. From "
|
|
754
|
+
"a script's own environment: pip install pyautogui.")
|
|
755
755
|
if os_name() == "mac":
|
|
756
756
|
# There is no way to ASK macOS whether permission was granted; the tell
|
|
757
757
|
# is that a capture comes back with nothing in it. Cheap to check once.
|
|
@@ -779,7 +779,7 @@ def _gui():
|
|
|
779
779
|
import pyautogui as _p
|
|
780
780
|
except ImportError:
|
|
781
781
|
raise RuntimeError(
|
|
782
|
-
"This project cannot use the computer yet. In Atlas
|
|
782
|
+
"This project cannot use the computer yet. In Atlas Studio, "
|
|
783
783
|
"turn Computer Use off and on and it will fetch what is missing. "
|
|
784
784
|
"From a script's own environment: pip install pyautogui."
|
|
785
785
|
)
|
|
@@ -868,9 +868,9 @@ def _do(action, fn, detail=None, points=()):
|
|
|
868
868
|
# rather than when the window was focused: what matters is where the click
|
|
869
869
|
# lands, and the front window can change between the two.
|
|
870
870
|
if action in _ACTS_ON_A_WINDOW and not _MAY_USE_ATLAS["ok"] and _in_the_atlas_window():
|
|
871
|
-
_log_action(action, detail, ok=False, error="inside Atlas
|
|
871
|
+
_log_action(action, detail, ok=False, error="inside Atlas Studio")
|
|
872
872
|
raise RuntimeError(
|
|
873
|
-
"Atlas
|
|
873
|
+
"Atlas Studio is the window in front, and this run has not "
|
|
874
874
|
"been asked to work inside it. Typing there means talking to another "
|
|
875
875
|
"agent, which is not something to start on your own. If the person "
|
|
876
876
|
"asked for it, call use_atlas_app() first; otherwise bring the right "
|
|
@@ -1965,7 +1965,7 @@ def _one_window(title):
|
|
|
1965
1965
|
got = hit[0]
|
|
1966
1966
|
if not _MAY_USE_ATLAS["ok"] and _is_atlas_title(str(getattr(got, "title", ""))):
|
|
1967
1967
|
raise RuntimeError(
|
|
1968
|
-
'"%s" is Atlas
|
|
1968
|
+
'"%s" is Atlas Studio, which is showing the person what this '
|
|
1969
1969
|
"run is doing. Touching its window is not something to decide on your "
|
|
1970
1970
|
"own: if they asked for it, call use_atlas_app() first." % got.title
|
|
1971
1971
|
)
|
|
@@ -1976,7 +1976,7 @@ def _one_window(title):
|
|
|
1976
1976
|
#:
|
|
1977
1977
|
#: The title of a console window is the command line of the program in it until
|
|
1978
1978
|
#: something sets one. So "C:\...\python3.11.exe" is machinery, and so is
|
|
1979
|
-
#: "C:\...\Atlas
|
|
1979
|
+
#: "C:\...\Atlas Studio.exe" — which a list of interpreter names would
|
|
1980
1980
|
#: have walked straight past, and that one was the window sitting over the
|
|
1981
1981
|
#: owner's chart (owner, 2026-09-06: "screen for any console interpreter and
|
|
1982
1982
|
#: let's stick with background processes for all of it").
|
|
@@ -2483,6 +2483,108 @@ def watch(instruction, every=5, most=200, hours=8, look=True):
|
|
|
2483
2483
|
"waits": waits, "varied": len(set(waits)) > 1}
|
|
2484
2484
|
|
|
2485
2485
|
|
|
2486
|
+
#: How a browser names itself at the end of its window title. Used to work out
|
|
2487
|
+
#: WHICH one is in front, because the keys differ in a couple of places and
|
|
2488
|
+
#: guessing wrong sends a keystroke somewhere nobody is looking.
|
|
2489
|
+
_BROWSERS = (
|
|
2490
|
+
("Google Chrome", "chrome"), ("Microsoft Edge", "edge"), ("Mozilla Firefox", "firefox"),
|
|
2491
|
+
("Firefox", "firefox"), ("Brave", "brave"), ("Safari", "safari"),
|
|
2492
|
+
("Opera", "opera"), ("Vivaldi", "vivaldi"), ("Chromium", "chromium"),
|
|
2493
|
+
)
|
|
2494
|
+
|
|
2495
|
+
|
|
2496
|
+
def browser():
|
|
2497
|
+
"""Which browser is on screen, and what its window is called.
|
|
2498
|
+
|
|
2499
|
+
{"name": "edge", "title": "SPY — TradingView and 3 more pages — Microsoft Edge"}
|
|
2500
|
+
|
|
2501
|
+
None when there is no browser window. Worth asking once at the start: a
|
|
2502
|
+
browser is not a wall of pixels, it is an application with an address bar
|
|
2503
|
+
and a find bar, and knowing you are in one changes what the fast move is.
|
|
2504
|
+
"""
|
|
2505
|
+
def go():
|
|
2506
|
+
try:
|
|
2507
|
+
wins = _all_windows()
|
|
2508
|
+
except Exception:
|
|
2509
|
+
return None
|
|
2510
|
+
for w in wins:
|
|
2511
|
+
title = str(getattr(w, "title", "") or "")
|
|
2512
|
+
for label, name in _BROWSERS:
|
|
2513
|
+
if title.rstrip().endswith(label) or (" - " + label) in title or (" — " + label) in title:
|
|
2514
|
+
return {"name": name, "title": title[:200]}
|
|
2515
|
+
return None
|
|
2516
|
+
return _do("browser", go, {})
|
|
2517
|
+
|
|
2518
|
+
|
|
2519
|
+
def _chord():
|
|
2520
|
+
"""ctrl on Windows and Linux, command on a Mac. The one difference that
|
|
2521
|
+
matters for every browser key there is."""
|
|
2522
|
+
import sys as _sys
|
|
2523
|
+
return "command" if _sys.platform == "darwin" else "ctrl"
|
|
2524
|
+
|
|
2525
|
+
|
|
2526
|
+
def go_to(url, window=""):
|
|
2527
|
+
"""Go straight to an address. The fastest move a browser offers.
|
|
2528
|
+
|
|
2529
|
+
go_to("https://www.tradingview.com/chart/?symbol=SPY")
|
|
2530
|
+
|
|
2531
|
+
Bring the browser forward, focus the address bar, replace what is in it,
|
|
2532
|
+
press Enter, and wait for the page to settle. One call for what is otherwise
|
|
2533
|
+
five, and none of it involves finding anything by eye.
|
|
2534
|
+
|
|
2535
|
+
NAVIGATING BY ADDRESS BEATS CLICKING THROUGH A SITE, every time. A menu path
|
|
2536
|
+
is four clicks that each have to land, on a layout that moves; an address is
|
|
2537
|
+
one thing that is either right or wrong and says which.
|
|
2538
|
+
"""
|
|
2539
|
+
def go():
|
|
2540
|
+
g = _gui()
|
|
2541
|
+
if window:
|
|
2542
|
+
focus_window(window)
|
|
2543
|
+
else:
|
|
2544
|
+
b = browser()
|
|
2545
|
+
if b:
|
|
2546
|
+
focus_window(b["name"])
|
|
2547
|
+
mod = _chord()
|
|
2548
|
+
g.hotkey(mod, "l") # the address bar, everywhere
|
|
2549
|
+
g.hotkey(mod, "a") # and everything in it, so this replaces
|
|
2550
|
+
g.write(str(url), interval=0.01)
|
|
2551
|
+
g.press("enter")
|
|
2552
|
+
out = _do("go_to", go, {"url": str(url)[:200]})
|
|
2553
|
+
# A page is not there when Enter is pressed. Wait for it to start AND stop,
|
|
2554
|
+
# which is what the settle does, rather than a sleep somebody guessed.
|
|
2555
|
+
_settle(6.0)
|
|
2556
|
+
return out
|
|
2557
|
+
|
|
2558
|
+
|
|
2559
|
+
def find_on_page(text, next_times=0):
|
|
2560
|
+
"""Find text on the page and leave the view on it.
|
|
2561
|
+
|
|
2562
|
+
find_on_page("Opening Range")
|
|
2563
|
+
|
|
2564
|
+
ctrl+F, type, Enter, Escape — the browser scrolls to the match and the find
|
|
2565
|
+
bar closes, leaving the page where the thing is. That is a way to REACH
|
|
2566
|
+
something without knowing where it is on screen, which is the expensive kind
|
|
2567
|
+
of knowing.
|
|
2568
|
+
|
|
2569
|
+
`next_times` presses Enter again for the second, third match.
|
|
2570
|
+
|
|
2571
|
+
It cannot tell you whether it found anything: the browser says so in its own
|
|
2572
|
+
find bar, which is gone by the time you look. Take a picture after.
|
|
2573
|
+
"""
|
|
2574
|
+
def go():
|
|
2575
|
+
g = _gui()
|
|
2576
|
+
mod = _chord()
|
|
2577
|
+
g.hotkey(mod, "f")
|
|
2578
|
+
g.write(str(text), interval=0.01)
|
|
2579
|
+
g.press("enter")
|
|
2580
|
+
for _ in range(max(0, int(next_times))):
|
|
2581
|
+
g.press("enter")
|
|
2582
|
+
g.press("escape")
|
|
2583
|
+
out = _do("find_on_page", go, {"text": len(str(text))})
|
|
2584
|
+
_settle(1.5)
|
|
2585
|
+
return out
|
|
2586
|
+
|
|
2587
|
+
|
|
2486
2588
|
def scroll_x(amount, x=None, y=None):
|
|
2487
2589
|
"""Scroll SIDEWAYS. Positive goes right, negative left.
|
|
2488
2590
|
|
|
@@ -2641,7 +2743,7 @@ _HANDOVER = {"to_human": False, "why": ""}
|
|
|
2641
2743
|
|
|
2642
2744
|
# ── DRIVING ATLAS ITSELF ────────────────────────────────────────────────────
|
|
2643
2745
|
#
|
|
2644
|
-
# Atlas
|
|
2746
|
+
# Atlas Studio is a window like any other, so a run that can use the
|
|
2645
2747
|
# computer can use it: open a project, press Run, fix a setting somebody asked
|
|
2646
2748
|
# about. That is fine and it is useful.
|
|
2647
2749
|
#
|
|
@@ -2652,6 +2754,11 @@ _HANDOVER = {"to_human": False, "why": ""}
|
|
|
2652
2754
|
#
|
|
2653
2755
|
# So: acting inside the Atlas window needs saying so once. Looking never does,
|
|
2654
2756
|
# and neither does anything in any other window.
|
|
2757
|
+
#: BOTH NAMES, and the old one stays. The app was renamed from Atlas Remote
|
|
2758
|
+
#: Control to Atlas Studio, and a machine running the previous build has a window
|
|
2759
|
+
#: still called the old thing — dropping it would quietly turn off the guard that
|
|
2760
|
+
#: keeps a run from typing into Atlas's own chat, on exactly the installs that
|
|
2761
|
+
#: have not updated yet.
|
|
2655
2762
|
_ATLAS_WINDOWS = ("atlas remote control", "atlas studio")
|
|
2656
2763
|
_MAY_USE_ATLAS = {"ok": os.environ.get("ATLAS_MAY_DRIVE_APP", "") == "1"}
|
|
2657
2764
|
#: The ones that CHANGE something. Moving the pointer and looking do not.
|
|
@@ -2665,7 +2772,7 @@ _ACTS_ON_A_WINDOW = (
|
|
|
2665
2772
|
|
|
2666
2773
|
|
|
2667
2774
|
def use_atlas_app(allowed=True):
|
|
2668
|
-
"""Say the person asked you to work inside Atlas
|
|
2775
|
+
"""Say the person asked you to work inside Atlas Studio itself.
|
|
2669
2776
|
|
|
2670
2777
|
Only when they actually did. Deciding for yourself that it would be handy is
|
|
2671
2778
|
the thing this exists to stop, and the first place a run wanders to is the
|
|
@@ -2944,8 +3051,8 @@ def stream_frames(send=None, fps=3, width=1920, quality=58, max_s=120):
|
|
|
2944
3051
|
frame = _grab_frame(width, quality, _watched_screen())
|
|
2945
3052
|
if frame is None:
|
|
2946
3053
|
send({"kind": "error", "message": (
|
|
2947
|
-
"This computer is not set up to be watched yet. Open Atlas
|
|
2948
|
-
"
|
|
3054
|
+
"This computer is not set up to be watched yet. Open Atlas Studio "
|
|
3055
|
+
"on it, go to Computer Use, and turn the switch off and on: "
|
|
2949
3056
|
"it fetches what is missing.")})
|
|
2950
3057
|
return
|
|
2951
3058
|
mark = hashlib.md5(frame).digest()
|
package/src/remote-control.js
CHANGED
|
@@ -1022,7 +1022,7 @@ export function readJobFile(job, rel, root) {
|
|
|
1022
1022
|
* This half had NO command path at all: `writeCommand` did not exist and the
|
|
1023
1023
|
* relay had no `command` op, so a custom button pressed from the WEB reached a
|
|
1024
1024
|
* machine running this CLI and vanished, while the same button pressed in the
|
|
1025
|
-
* app worked (Atlas
|
|
1025
|
+
* app worked (Atlas Studio writes the file itself). One button, two
|
|
1026
1026
|
* answers, depending on where you pressed it (owner, 2026-09-04: "the agent can
|
|
1027
1027
|
* wire buttons that read or write local — the web can do this too, it's either
|
|
1028
1028
|
* or"). Mirrors python/atlas_cli/remote_control.py.
|
|
@@ -1511,7 +1511,7 @@ const STARTER_README = (name) => [
|
|
|
1511
1511
|
'must stay up says `"mode": "listen"` in atlas.json and the runner keeps it',
|
|
1512
1512
|
'alive.', '',
|
|
1513
1513
|
'Nothing here is stored on Atlas servers. The schedule runs on this computer,',
|
|
1514
|
-
'while Atlas
|
|
1514
|
+
'while Atlas Studio is open.', '',
|
|
1515
1515
|
].join('\n');
|
|
1516
1516
|
|
|
1517
1517
|
const STARTER_MAIN = (name) => [
|