mindvest-atlas 0.65.0 → 0.67.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.65.0",
3
+ "version": "0.67.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.65.0';
5
+ export const VERSION = '0.67.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';
@@ -53,6 +53,19 @@ export async function atlas(tool, params = {}) {
53
53
  *
54
54
  * const verdict = await ask(`Is this headline about AAPL itself or the sector?\n${line}`);
55
55
  *
56
+ * IT IS A CHAT SESSION, NOT A TEXT BOX (owner, 2026-09-11). The model runs in
57
+ * this project's folder with everything the chat has: the files, the shell, the
58
+ * `atlas` command line, Atlas's own tools, the skills, and the computer when the
59
+ * switch is on. Ask it to open the broker and read a balance and it drives the
60
+ * screen; its last message is what you get back. Nobody is watching, so it never
61
+ * asks back: it decides. Say what shape you want the answer in.
62
+ *
63
+ * const balance = await ask("Open the broker in Edge and read me the cash balance. Answer with the number only.");
64
+ *
65
+ * commands:true (the default) is that whole session; commands:false is pure
66
+ * judgement — twenty classifications — where the model answers from what it was
67
+ * given and runs nothing, faster and cheaper.
68
+ *
56
69
  * tools:true lets the model reach Atlas data while it answers; false is faster
57
70
  * for pure text. fresh:false keeps ONE conversation for this job (the model
58
71
  * starts up once, later questions can refer to earlier ones); fresh:true is for
@@ -71,7 +84,7 @@ export async function atlas(tool, params = {}) {
71
84
  * quietly answered by the default, because a script that asked for a specific
72
85
  * model and silently got another one is worse than one that stops.
73
86
  */
74
- export async function ask(prompt, { tools = true, fresh = false, timeoutMs = 600000, model = "" } = {}) {
87
+ export async function ask(prompt, { tools = true, fresh = false, timeoutMs = 600000, model = "", commands = true } = {}) {
75
88
  const url = process.env.ATLAS_ASK_URL;
76
89
  const token = process.env.ATLAS_ASK_TOKEN;
77
90
  if (!url || !token) {
@@ -80,7 +93,7 @@ export async function ask(prompt, { tools = true, fresh = false, timeoutMs = 600
80
93
  "Connect a model in Atlas Studio, or do this step in code."
81
94
  );
82
95
  }
83
- const body = { job: process.env.ATLAS_JOB || "", prompt: String(prompt), tools, fresh };
96
+ const body = { job: process.env.ATLAS_JOB || "", prompt: String(prompt), tools, fresh, commands: commands !== false };
84
97
  // Sent only when named, so a script that does not care keeps whatever is
85
98
  // connected rather than pinning itself to today's default.
86
99
  if (String(model || "").trim()) body.model = String(model).trim();
@@ -346,19 +359,30 @@ export const dashboard = {
346
359
  * await computer.type("hello");
347
360
  * await computer.note("signing in");
348
361
  *
362
+ * ONE CALL IS ONE PYTHON. Each of those starts an interpreter, so a sequence of
363
+ * them pays that start-up per click and the run crawls (owner, 2026-09-10: "the
364
+ * slowness is the per command round trip"). Say the whole sequence instead and
365
+ * it is ONE start, with the picture already taken at the end:
366
+ *
367
+ * await computer.do([["click", x, y, "Sign in"],
368
+ * ["type", "someone@example.com"],
369
+ * ["key", "tab"],
370
+ * ["type", secret("PASSWORD")],
371
+ * ["key", "enter"]]);
372
+ *
349
373
  * Off until the person turns it on, under This Computer in Atlas Studio. Read skills/computer-use before using any of it.
350
374
  */
351
- async function useComputer(op, args = []) {
375
+ async function useComputer(op, args = [], kw = {}) {
352
376
  const { spawn } = await import("node:child_process");
353
377
  const dir = path.dirname(url_.fileURLToPath(import.meta.url));
354
378
  const py = process.env.ATLAS_PYTHON || (process.platform === "win32" ? "python" : "python3");
355
379
  const code =
356
380
  "import json,sys,atlas_env\n"
357
- + "op=sys.argv[1]; args=json.loads(sys.argv[2])\n"
358
- + "out=getattr(atlas_env,op)(*args)\n"
381
+ + "op=sys.argv[1]; args=json.loads(sys.argv[2]); kw=json.loads(sys.argv[3])\n"
382
+ + "out=getattr(atlas_env,op)(*args,**kw)\n"
359
383
  + "sys.stdout.write('\\n__ATLAS__'+json.dumps(out,default=str))\n";
360
384
  return await new Promise((done, fail) => {
361
- const p = spawn(py, ["-c", code, op, JSON.stringify(args)], { cwd: dir });
385
+ const p = spawn(py, ["-c", code, op, JSON.stringify(args), JSON.stringify(kw || {})], { cwd: dir });
362
386
  let out = "", err = "";
363
387
  p.stdout.on("data", (d) => { out += d; });
364
388
  p.stderr.on("data", (d) => { err += d; });
@@ -392,6 +416,22 @@ export const computer = {
392
416
  region: (x, y, w, h) => useComputer("region", [x, y, w, h]),
393
417
  screens: () => useComputer("screens"),
394
418
  useScreen: (n) => useComputer("use_screen", [n]),
419
+ /**
420
+ * SEVERAL THINGS, ONE ROUND TRIP, ONE LOOK AT THE END. The fast loop, and the
421
+ * answer to a run that feels slow: every other call here starts its own
422
+ * Python, so five separate steps pay that five times before anything is even
423
+ * clicked (owner, 2026-09-10: "add option to batch command, this is better").
424
+ *
425
+ * await computer.do([["click", 820, 410, "Sign in"], ["type", "hi"], ["key", "enter"]]);
426
+ * // -> { did: [...], screen: "...png", changed: true, x: 1920, y: 0 }
427
+ *
428
+ * Each step is [name, ...args] with the same names the calls below use.
429
+ * Stops at the first refusal and says what had already happened, with a
430
+ * picture of where it stopped. `settle` is a CEILING in seconds (default 1.2)
431
+ * on waiting for the screen to stop moving, not a pause; `look: false` skips
432
+ * the picture when the next thing you do does not depend on it.
433
+ */
434
+ do: (steps, opts = {}) => useComputer("do", (steps || []).map((s) => (Array.isArray(s) ? s : [s])), opts),
395
435
  click: (x, y) => useComputer("click", [x, y]),
396
436
  doubleClick: (x, y) => useComputer("double_click", [x, y]),
397
437
  rightClick: (x, y) => useComputer("right_click", [x, y]),
@@ -54,24 +54,32 @@ def atlas(tool, **params):
54
54
  raise RuntimeError("Atlas %s failed (%d): %s" % (tool, e.code, body))
55
55
 
56
56
 
57
- def ask(prompt, tools=True, fresh=False, timeout=600, model=""):
58
- """Ask YOUR OWN model a question and get its answer back as text.
57
+ def ask(prompt, tools=True, fresh=False, timeout=600, model="", commands=True):
58
+ """Ask YOUR OWN model, the way you would in the chat, and get its answer back.
59
59
 
60
- For the steps only a model can do: reading a filing and pulling out the
61
- guidance, judging whether a headline is about the company or its sector,
62
- summarising what changed since yesterday. Everything else - fetching,
63
- counting, comparing, placing orders - is faster and more reliable as plain
64
- code, so use this for judgement, not for arithmetic.
65
-
66
- It runs on this computer, through the model already connected in Atlas
67
- Remote Control. The prompt never reaches Atlas.
60
+ IT IS A CHAT SESSION, NOT A TEXT BOX (owner, 2026-09-11). The model runs on
61
+ this computer, in this project's folder, with everything the chat has: the
62
+ files here, the shell, the `atlas` command line, Atlas's own tools, the
63
+ skills, and the computer when the switch is on. Ask it to open the broker
64
+ and read a balance and it will drive the screen; ask it to write and run
65
+ something and it will. Its last message is what you get back.
68
66
 
69
67
  verdict = ask("Is this headline about AAPL itself or the sector?\n" + line)
70
68
  if "itself" in verdict.lower():
71
69
  ...
70
+ balance = ask("Open the broker in Edge and read me the cash balance. "
71
+ "Answer with the number only.")
72
+
73
+ Nobody is watching, so it never asks a question back: it decides. Say what
74
+ shape you want the answer in ("the number only", "one of: buy, sell, wait")
75
+ and that is what arrives.
76
+
77
+ commands=True (the default) is that whole session. commands=False is for
78
+ pure judgement - twenty headline classifications - where the model should
79
+ answer from what you gave it and run nothing; it is faster and cheaper.
72
80
 
73
81
  tools=True lets the model reach Atlas data while it answers; pass
74
- tools=False for pure text work, which is faster.
82
+ tools=False for text work that needs none of it.
75
83
 
76
84
  fresh=False keeps ONE conversation for this job, so later questions can
77
85
  refer to earlier ones and the model starts up only once. Pass fresh=True
@@ -104,7 +112,7 @@ def ask(prompt, tools=True, fresh=False, timeout=600, model=""):
104
112
  "Connect a model in Atlas Studio, or do this step in code."
105
113
  )
106
114
  body = {"job": os.environ.get("ATLAS_JOB", ""), "prompt": str(prompt),
107
- "tools": bool(tools), "fresh": bool(fresh)}
115
+ "tools": bool(tools), "fresh": bool(fresh), "commands": bool(commands)}
108
116
  # Sent only when named, so a script that does not care keeps whatever is
109
117
  # connected rather than pinning itself to today's default.
110
118
  if str(model or "").strip():