mindvest-atlas 0.93.1 → 0.95.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.93.1",
3
+ "version": "0.95.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": {
@@ -29,6 +29,8 @@ import {
29
29
  SCRIPTS_DIR, projectsForAlert, readMode,
30
30
  streamStudioFrames,
31
31
  localBrokers, localBrokerCall,
32
+ studioVoice,
33
+ appendSaid,
32
34
  } from '../remote-control.js';
33
35
  import fs from 'node:fs';
34
36
  import os from 'node:os';
@@ -465,6 +467,8 @@ async function handleAsk(ask, root) {
465
467
  if (ask.op === 'chats') return answer(ask.request_id, { ok: true, chats: listChats(ask.job || '', root) });
466
468
  if (ask.op === 'chat_read') return answer(ask.request_id, readChat(ask.job || '', ask.chat_id || '', root));
467
469
  if (ask.op === 'chat_delete') return answer(ask.request_id, deleteChat(ask.job || '', ask.chat_id || '', root));
470
+ // What was said out loud in a voice conversation about this project.
471
+ if (ask.op === 'chat_said') return answer(ask.request_id, appendSaid(ask.job || '', ask.chat_id || '', ask.lines, root));
468
472
  if (ask.op === 'models') {
469
473
  void listModels().then((m) => answer(ask.request_id, { ok: true, ...m }));
470
474
  return undefined;
@@ -486,6 +490,14 @@ async function handleAsk(ask, root) {
486
490
  .catch((err) => answer(ask.request_id, { ok: false, error: String(err.message || err) }));
487
491
  return undefined;
488
492
  }
493
+ if (ask.op === 'voice') {
494
+ // A voice the person is talking to on their phone, working Atlas Studio
495
+ // on this computer: one screen tool, carried out by the app itself.
496
+ void studioVoice(ask.name, ask.args, ask.allowed === true, root)
497
+ .then((out) => answer(ask.request_id, out))
498
+ .catch((err) => answer(ask.request_id, { ok: false, error: String(err.message || err) }));
499
+ return undefined;
500
+ }
489
501
  if (ask.op === 'build') {
490
502
  // THE POINT OF ALL OF THIS. Words in, working script out: the owner says
491
503
  // what they want in one sentence and their own model writes it, runs it,
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.93.1';
5
+ export const VERSION = '0.95.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';
@@ -2018,6 +2018,52 @@ export async function localBrokerCall(connector, tool, args, root, timeoutMs = 1
2018
2018
  return out;
2019
2019
  }
2020
2020
 
2021
+ /**
2022
+ * A VOICE THE PERSON IS TALKING TO SOMEWHERE ELSE, WORKING ATLAS STUDIO HERE
2023
+ * (owner, 2026-10-06: "when I switch to local, where is the voice ... it
2024
+ * controls atlas studio remotely and the user will approve too").
2025
+ *
2026
+ * The voice is on their phone or in their browser. One of its screen tools
2027
+ * (look, go_to, point_at, press, type_into) is carried out in the app's own
2028
+ * window on this computer, by the app's own rules. This only passes the
2029
+ * question to the app and the answer back; a press that needs a yes comes
2030
+ * back as `ask`, to be asked where the person is.
2031
+ *
2032
+ * There is nothing to press without the app, so a runner started by hand in a
2033
+ * terminal says so.
2034
+ */
2035
+ export const STUDIO_VOICE_TOOLS = ['look', 'go_to', 'point_at', 'press', 'type_into'];
2036
+ export async function studioVoice(name, args, allowed, root, opts = {}) {
2037
+ const fetchImpl = opts.fetch || fetch;
2038
+ if (!STUDIO_VOICE_TOOLS.includes(String(name || ''))) {
2039
+ return { ok: false, error: 'That cannot be done on the computer from here.' };
2040
+ }
2041
+ const at = (opts.address || askAddress)(root);
2042
+ if (!at) return { ok: false, error: 'Atlas Studio is not open on that computer.' };
2043
+ try {
2044
+ const res = await fetchImpl(at.url.replace('/ask', '/voice'), {
2045
+ method: 'POST',
2046
+ headers: { 'Content-Type': 'application/json', 'X-Atlas-Token': at.token },
2047
+ body: JSON.stringify({
2048
+ name: String(name),
2049
+ args: args && typeof args === 'object' && !Array.isArray(args) ? args : {},
2050
+ // Only `true` is a yes.
2051
+ allowed: allowed === true,
2052
+ }),
2053
+ signal: AbortSignal.timeout(opts.timeoutMs ?? 25000),
2054
+ });
2055
+ const out = await res.json().catch(() => ({}));
2056
+ // An app from before this door existed answers 404 or its own refusal.
2057
+ if (res.status === 404 || res.status === 405) {
2058
+ return { ok: false, error: 'Atlas Studio on that computer needs updating before a voice can work it from here.' };
2059
+ }
2060
+ if (!res.ok || !out || out.ok === false) return { ok: false, error: (out && out.error) || `Atlas Studio answered ${res.status}.` };
2061
+ return { ok: true, result: out.result && typeof out.result === 'object' ? out.result : {} };
2062
+ } catch (err) {
2063
+ return { ok: false, error: 'Atlas Studio on that computer did not answer.' };
2064
+ }
2065
+ }
2066
+
2021
2067
  /**
2022
2068
  * Stream a chat turn from the model on THIS computer.
2023
2069
  *
@@ -2409,6 +2455,38 @@ export function appendChat(job, id, role, text, root, payload, model) {
2409
2455
  try { fs.writeFileSync(f, JSON.stringify(data), 'utf8'); } catch { /* best effort */ }
2410
2456
  }
2411
2457
 
2458
+ /**
2459
+ * WHAT WAS SAID OUT LOUD, WRITTEN INTO THE PROJECT'S OWN CHAT (owner,
2460
+ * 2026-10-06: "when I'm using the voice from the web but connected to the
2461
+ * computer and a project, this is no longer atlas cloud, so things are done
2462
+ * there and chat are written there").
2463
+ *
2464
+ * A voice conversation had on the website or a phone, with the chat pointed at
2465
+ * a project on this computer, belongs to that project. Its spoken lines are
2466
+ * written here, in the same record a typed message is, so the conversation
2467
+ * reads whole from Atlas Studio and from anywhere else, and nothing of it is
2468
+ * kept by Atlas. Only lines: a person's and Atlas's words, in order.
2469
+ */
2470
+ export function cleanSaidLines(lines) {
2471
+ if (!Array.isArray(lines)) return [];
2472
+ const out = [];
2473
+ for (const l of lines.slice(0, 20)) {
2474
+ if (!l || typeof l !== 'object') continue;
2475
+ const role = l.role === 'user' ? 'user' : l.role === 'assistant' ? 'assistant' : '';
2476
+ const text = typeof l.text === 'string' ? l.text.trim().slice(0, 4000) : '';
2477
+ if (role && text) out.push({ role, text });
2478
+ }
2479
+ return out;
2480
+ }
2481
+ export function appendSaid(job, chatId, lines, root) {
2482
+ const name = String(job || '').trim();
2483
+ const id = String(chatId || '').trim();
2484
+ if (!name || !id) return { ok: false, error: 'Which conversation?' };
2485
+ const clean = cleanSaidLines(lines);
2486
+ for (const l of clean) appendChat(name, id, l.role, l.text, root);
2487
+ return { ok: true, written: clean.length };
2488
+ }
2489
+
2412
2490
  /**
2413
2491
  * FRAMES FROM ATLAS STUDIO, when it is here.
2414
2492
  *