mindvest-atlas 0.40.0 → 0.41.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.js +6 -0
- package/src/commands/computer-use.js +1 -1
- package/src/commands/dashboard.js +83 -0
- package/src/config.js +1 -1
- package/src/helpers/atlas_env.py +62 -0
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -17,6 +17,7 @@ import { updateCliCommand } from './commands/update-cli.js';
|
|
|
17
17
|
import { screenshotCommand } from './commands/screenshot.js';
|
|
18
18
|
import { runCommand } from './commands/run.js';
|
|
19
19
|
import { computerUseCommand } from './commands/computer-use.js';
|
|
20
|
+
import { dashboardCommand } from './commands/dashboard.js';
|
|
20
21
|
import { remoteCheckCommand } from './commands/remote-check.js';
|
|
21
22
|
import { shareCommand } from './commands/share.js';
|
|
22
23
|
import { historicalContractCommand } from './commands/historical.js';
|
|
@@ -106,6 +107,11 @@ export async function main(rawArgs) {
|
|
|
106
107
|
case 'computer-use':
|
|
107
108
|
case 'computer':
|
|
108
109
|
return computerUseCommand(withoutCommand(args));
|
|
110
|
+
// THE PROJECT'S OWN PAGE, which had the same gap: it existed in atlas_env
|
|
111
|
+
// and nowhere a shell agent could reach without writing the import by
|
|
112
|
+
// hand (owner, 2026-09-06).
|
|
113
|
+
case 'dashboard':
|
|
114
|
+
return dashboardCommand(withoutCommand(args));
|
|
109
115
|
// Put the newest CLI on this machine.
|
|
110
116
|
case 'update-cli':
|
|
111
117
|
case 'self-update':
|
|
@@ -106,7 +106,7 @@ export function shellSession(root) {
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
/** Run one atlas_env call inside the project, and hand back what it said. */
|
|
109
|
-
function runInProject(job, code, root) {
|
|
109
|
+
export function runInProject(job, code, root) {
|
|
110
110
|
const { dir } = workspace(job, root);
|
|
111
111
|
if (!fs.existsSync(dir)) {
|
|
112
112
|
eprintln(color.red(`There is no project called ${job} on this computer.`));
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// `atlas dashboard` — use this project's own page, from a shell.
|
|
2
|
+
//
|
|
3
|
+
// The same gap `atlas computer-use` filled, one road over: driving the
|
|
4
|
+
// dashboard existed in atlas_env and nowhere else, so an agent in a chat could
|
|
5
|
+
// only reach it by hand-writing the import (owner, 2026-09-06). And it has to
|
|
6
|
+
// be in THIS CLI, because Atlas Remote Control bundles this one.
|
|
7
|
+
//
|
|
8
|
+
// IT IS NOT THE COMPUTER. This drives the project's own window, so the real
|
|
9
|
+
// pointer never moves, nothing is claimed, no switch is needed, and a click at
|
|
10
|
+
// the wrong coordinates lands somewhere else in your own page rather than in
|
|
11
|
+
// whatever was behind it.
|
|
12
|
+
import { color, println, eprintln } from '../util/ui.js';
|
|
13
|
+
import { runInProject } from './computer-use.js';
|
|
14
|
+
|
|
15
|
+
const OPS = {
|
|
16
|
+
open: ['open', 0],
|
|
17
|
+
find: ['find', -1],
|
|
18
|
+
click: ['click', 2],
|
|
19
|
+
'double-click': ['doubleClick', 2],
|
|
20
|
+
move: ['move', 2],
|
|
21
|
+
scroll: ['scroll', 4],
|
|
22
|
+
type: ['type', 1],
|
|
23
|
+
key: ['key', -1],
|
|
24
|
+
shot: ['shot', 0],
|
|
25
|
+
check: ['check', 0],
|
|
26
|
+
close: ['close', 0],
|
|
27
|
+
};
|
|
28
|
+
const NUMERIC = new Set(['click', 'double-click', 'move', 'scroll']);
|
|
29
|
+
|
|
30
|
+
const HELP = `atlas dashboard <command>
|
|
31
|
+
|
|
32
|
+
open [--phone] [--theme light] draw this project's dashboard, ready to use
|
|
33
|
+
find "<selector>" what is on it, and the middle of each
|
|
34
|
+
click <x> <y> also double-click
|
|
35
|
+
move <x> <y>
|
|
36
|
+
scroll <x> <y> <dx> <dy>
|
|
37
|
+
type "<text>"
|
|
38
|
+
key <name> [<name>...]
|
|
39
|
+
shot a picture, AND what is wrong with it in words
|
|
40
|
+
check all four views, each measured
|
|
41
|
+
close
|
|
42
|
+
|
|
43
|
+
--job <project> which project (defaults to $ATLAS_JOB, which a chat sets)
|
|
44
|
+
|
|
45
|
+
Coordinates are inside the dashboard, from its top left. Not the screen's.`;
|
|
46
|
+
|
|
47
|
+
export function dashboardCommand(args) {
|
|
48
|
+
const flags = args.flags || {};
|
|
49
|
+
const rest = [...(args.positional || args._ || [])];
|
|
50
|
+
const sub = String(rest.shift() || '').trim().toLowerCase();
|
|
51
|
+
const job = String(flags.job || process.env.ATLAS_JOB || '').trim();
|
|
52
|
+
|
|
53
|
+
if (!sub || sub === 'help') { println(HELP); return 0; }
|
|
54
|
+
if (!OPS[sub]) {
|
|
55
|
+
eprintln(color.red(`There is no "${sub}" here.`));
|
|
56
|
+
println(HELP);
|
|
57
|
+
return 1;
|
|
58
|
+
}
|
|
59
|
+
if (!job) {
|
|
60
|
+
eprintln(color.red(`Which project? \`atlas dashboard ${sub} --job my-project\``));
|
|
61
|
+
return 1;
|
|
62
|
+
}
|
|
63
|
+
const [name, count] = OPS[sub];
|
|
64
|
+
if (count >= 0 && rest.length !== count) {
|
|
65
|
+
eprintln(color.red(`${sub} takes ${count} value${count === 1 ? '' : 's'}.`));
|
|
66
|
+
return 1;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let call;
|
|
70
|
+
if (sub === 'open') {
|
|
71
|
+
const theme = flags.theme === 'light' ? 'light' : 'dark';
|
|
72
|
+
call = `e.dashboard.open(phone=${flags.phone ? 'True' : 'False'}, theme=${JSON.stringify(theme)})`;
|
|
73
|
+
} else if (sub === 'find' && !rest.length) {
|
|
74
|
+
call = 'e.dashboard.find()';
|
|
75
|
+
} else {
|
|
76
|
+
const values = rest.map((v) => (NUMERIC.has(sub) ? Number(v) : v));
|
|
77
|
+
call = `e.dashboard.${name}(*json.loads(${JSON.stringify(JSON.stringify(values))}))`;
|
|
78
|
+
}
|
|
79
|
+
const code = 'import atlas_env as e, json\n'
|
|
80
|
+
+ `out = ${call}\n`
|
|
81
|
+
+ 'print(json.dumps(out, default=str, indent=2))\n';
|
|
82
|
+
return runInProject(job, code);
|
|
83
|
+
}
|
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.41.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
|
@@ -1146,6 +1146,68 @@ def screen(path=None):
|
|
|
1146
1146
|
% (m["x"], m["y"])}
|
|
1147
1147
|
|
|
1148
1148
|
|
|
1149
|
+
def do(*steps, **kw):
|
|
1150
|
+
"""Do several things, then look ONCE. The fast loop.
|
|
1151
|
+
|
|
1152
|
+
THE SLOW PART IS NOT THE CLICKING. Every separate command is a process
|
|
1153
|
+
start, a bridge call and a picture, and asking for the picture is its own
|
|
1154
|
+
round trip on top: act, ask, look, decide, act. Five of those to press four
|
|
1155
|
+
buttons you could already see (owner, 2026-09-06: "the iteration and fixing
|
|
1156
|
+
is slow, it has to take a screenshot to verify, then do the action").
|
|
1157
|
+
|
|
1158
|
+
So say the whole sequence and get the result back with the picture already
|
|
1159
|
+
taken:
|
|
1160
|
+
|
|
1161
|
+
do(("click", 820, 410, "Sign in"),
|
|
1162
|
+
("type", "someone@example.com"),
|
|
1163
|
+
("key", "tab"),
|
|
1164
|
+
("type", secret("PASSWORD")),
|
|
1165
|
+
("key", "enter"))
|
|
1166
|
+
# -> {"did": [...], "screen": "...png", "x": 1920, "y": 0}
|
|
1167
|
+
|
|
1168
|
+
One look at the end, because that is the only moment the screen has settled
|
|
1169
|
+
into something worth reading. Between the steps there is nothing to see that
|
|
1170
|
+
you did not already know was coming.
|
|
1171
|
+
|
|
1172
|
+
`settle` waits before the picture, for a page that is still moving. Set
|
|
1173
|
+
`look=False` when the next thing you do does not depend on what happened.
|
|
1174
|
+
"""
|
|
1175
|
+
settle = float(kw.get("settle", 0.6))
|
|
1176
|
+
look = kw.get("look", True)
|
|
1177
|
+
did = []
|
|
1178
|
+
for step in steps:
|
|
1179
|
+
if not step:
|
|
1180
|
+
continue
|
|
1181
|
+
name, args = (step[0], list(step[1:])) if isinstance(step, (list, tuple)) else (step, [])
|
|
1182
|
+
fn = globals().get(str(name))
|
|
1183
|
+
if not callable(fn):
|
|
1184
|
+
raise RuntimeError('There is no "%s" to do. Try screen, click, type, key, scroll, wait.' % name)
|
|
1185
|
+
# STOPS AT THE FIRST REFUSAL, and says what had already happened. A
|
|
1186
|
+
# sequence that carried on past a click that did not land would type a
|
|
1187
|
+
# password into whatever had focus instead.
|
|
1188
|
+
try:
|
|
1189
|
+
fn(*args)
|
|
1190
|
+
except Exception as err:
|
|
1191
|
+
raise RuntimeError("stopped at %s: %s (already did: %s)"
|
|
1192
|
+
% (name, err, ", ".join(did) or "nothing"))
|
|
1193
|
+
did.append(str(name))
|
|
1194
|
+
if not look:
|
|
1195
|
+
return {"did": did}
|
|
1196
|
+
if settle > 0:
|
|
1197
|
+
import time as _time
|
|
1198
|
+
_time.sleep(settle)
|
|
1199
|
+
shot = screen()
|
|
1200
|
+
out = {"did": did}
|
|
1201
|
+
# screen() answers a bare path on one monitor and a dict on several. Both
|
|
1202
|
+
# travel, because the offset is the thing a reader needs next.
|
|
1203
|
+
if isinstance(shot, dict):
|
|
1204
|
+
out.update(shot)
|
|
1205
|
+
out["screen"] = shot.get("path")
|
|
1206
|
+
else:
|
|
1207
|
+
out["screen"] = shot
|
|
1208
|
+
return out
|
|
1209
|
+
|
|
1210
|
+
|
|
1149
1211
|
def click(x, y, button="left", clicks=1, what=""):
|
|
1150
1212
|
"""Press something. SAY WHAT IT IS if you know.
|
|
1151
1213
|
|