mindvest-atlas 0.52.0 → 0.53.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.52.0",
3
+ "version": "0.53.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
@@ -10,6 +10,7 @@ import { loginCommand } from './commands/login.js';
10
10
  import { logoutCommand } from './commands/logout.js';
11
11
  import { whoamiCommand } from './commands/whoami.js';
12
12
  import { toolsCommand } from './commands/tools.js';
13
+ import { modelsCommand, askCommand } from './commands/models.js';
13
14
  import { alertsCommand } from './commands/alerts.js';
14
15
  import { flowCommand } from './commands/flow.js';
15
16
  import { remoteControlCommand } from './commands/remote-control.js';
@@ -82,6 +83,10 @@ export async function main(rawArgs) {
82
83
  case 'logout': return await logoutCommand(withoutCommand(args));
83
84
  case 'whoami': return await whoamiCommand(withoutCommand(args));
84
85
  case 'tools': return await toolsCommand(withoutCommand(args));
86
+ // WHICH MODELS ARE HERE, and handing one piece of work to one of them —
87
+ // together, the whole mechanism behind orchestration.
88
+ case 'models': return await modelsCommand(withoutCommand(args));
89
+ case 'ask': return await askCommand(withoutCommand(args));
85
90
  case 'alerts': return await alertsCommand(withoutCommand(args));
86
91
  case 'flow': return await flowCommand(withoutCommand(args));
87
92
  // Long-lived: hold the job stream and run this computer's jobs.
@@ -0,0 +1,81 @@
1
+ // Which models this computer can reach, and handing work to one of them.
2
+ //
3
+ // TWO COMMANDS, AND THE SECOND IS THE POINT. `atlas models` is the catalogue:
4
+ // every model, who runs it, whether it is actually reachable, and the effort
5
+ // levels it takes — because anything CHOOSING a model needs to know what is
6
+ // there, and a guessed id is a session that fails a minute later for a reason
7
+ // nobody can see.
8
+ //
9
+ // `atlas ask --model X "..."` hands one piece of work to one named model. The
10
+ // helper has been able to do that from inside a script for a long time; it was
11
+ // never reachable from a SHELL, which is where an agent lives. That one gap is
12
+ // what stood between this and a model that can delegate: an orchestrator is not
13
+ // a new mechanism, it is a model with this command and a roster.
14
+ import { color, println, eprintln } from '../util/ui.js';
15
+ import { askModel, listModels } from '../remote-control.js';
16
+
17
+ export async function modelsCommand(args) {
18
+ const flags = args.flags || {};
19
+ const catalog = await listModels();
20
+ const rows = catalog.models || [];
21
+ if (flags.json) {
22
+ println(JSON.stringify(catalog, null, 2));
23
+ return 0;
24
+ }
25
+ if (!rows.length) {
26
+ eprintln(color.red('No model is connected on this computer yet.'));
27
+ eprintln(color.dim(' Open Atlas Studio > Model Settings and sign in to one.'));
28
+ return 1;
29
+ }
30
+ const width = Math.max(...rows.map((r) => String(r.id || '').length));
31
+ println(color.bold('Models on this computer'));
32
+ for (const r of rows) {
33
+ // READY FIRST: a model that cannot be reached is not a choice, and it is the
34
+ // one fact a caller cannot work out for itself.
35
+ const mark = r.ready ? ' ' : '! ';
36
+ const star = r.id === catalog.default ? ' *' : '';
37
+ const effort = (r.effort || []).join(', ') || '-';
38
+ println(`${mark}${String(r.id || '').padEnd(width)} ${String(r.agent || '').padEnd(14)}`
39
+ + ` ${String(r.vendor || '').padEnd(22)} effort: ${effort}${star}`);
40
+ }
41
+ println('');
42
+ println(color.dim(' * the one used when nothing is named'));
43
+ println(color.dim(' ! not reachable right now — sign in under Model Settings'));
44
+ println(color.dim(' atlas ask --model <id> "..." hand one piece of work to one of them'));
45
+ return 0;
46
+ }
47
+
48
+ export async function askCommand(args) {
49
+ const flags = args.flags || {};
50
+ const rest = args.positional || args._ || [];
51
+ const prompt = (rest.join(' ') || String(flags.prompt || '')).trim();
52
+ if (!prompt) {
53
+ eprintln(color.red('Say what to ask.'));
54
+ eprintln(color.dim(' atlas ask --model gpt-5.6-sol "summarise this filing"'));
55
+ return 1;
56
+ }
57
+ const model = String(flags.model || '').trim();
58
+ if (model) {
59
+ // NAMED, AND CHECKED. A typo would otherwise fall back to the default model
60
+ // in silence, and an orchestrator would believe it had asked three different
61
+ // models when it had asked one three times.
62
+ const known = ((await listModels()).models || []).map((r) => String(r.id));
63
+ if (known.length && !known.includes(model)) {
64
+ eprintln(color.red(`No model here is called "${model}".`));
65
+ eprintln(color.dim(' atlas models lists them'));
66
+ return 1;
67
+ }
68
+ }
69
+ try {
70
+ const said = await askModel(prompt, {
71
+ job: String(flags.job || ''),
72
+ model,
73
+ timeoutMs: Number(flags.timeout || 900) * 1000,
74
+ });
75
+ println(typeof said === 'string' ? said : JSON.stringify(said, null, 2));
76
+ return 0;
77
+ } catch (err) {
78
+ eprintln(color.red(err.message || String(err)));
79
+ return 1;
80
+ }
81
+ }
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.52.0';
5
+ export const VERSION = '0.53.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';
@@ -1360,7 +1360,15 @@ export function listRuns(root, limit = 50) {
1360
1360
  *
1361
1361
  * Returns the model's account of what it did. Throws if no model is connected.
1362
1362
  */
1363
- export async function askModel(prompt, { job = '', tools = true, fresh = false, timeoutMs = 900000 } = {}) {
1363
+ export async function askModel(
1364
+ prompt,
1365
+ // `model` NAMES WHICH ONE ANSWERS, and its absence here was a real gap: the
1366
+ // python helper has taken it for a long time, so the same call delegated to a
1367
+ // named model in one runtime and quietly used the default in the other. That
1368
+ // is the shape of bug where an orchestrator believes it asked three models and
1369
+ // asked one three times.
1370
+ { job = '', tools = true, fresh = false, timeoutMs = 900000, model = '' } = {},
1371
+ ) {
1364
1372
  const url = process.env.ATLAS_ASK_URL;
1365
1373
  const token = process.env.ATLAS_ASK_TOKEN;
1366
1374
  if (!url || !token) {
@@ -1372,7 +1380,7 @@ export async function askModel(prompt, { job = '', tools = true, fresh = false,
1372
1380
  const res = await fetch(url, {
1373
1381
  method: 'POST',
1374
1382
  headers: { 'Content-Type': 'application/json', 'X-Atlas-Token': token },
1375
- body: JSON.stringify({ job, prompt: String(prompt), tools, fresh }),
1383
+ body: JSON.stringify({ job, prompt: String(prompt), tools, fresh, ...(model ? { model } : {}) }),
1376
1384
  signal: AbortSignal.timeout(timeoutMs),
1377
1385
  });
1378
1386
  const out = await res.json().catch(() => ({}));