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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mindvest-atlas",
3
- "version": "0.40.0",
3
+ "version": "0.41.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/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.40.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';
@@ -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