mindvest-atlas 0.68.4 → 0.93.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/README.md CHANGED
@@ -30,6 +30,7 @@ atlas options-chain --symbol SPY --expiration 2026-06-20
30
30
  atlas historical-contract --symbol NVDA --expiry 2026-07-13 --strike 205 --side call --from 2026-07-06 --to 2026-07-10
31
31
  atlas alerts --symbol SPY --name greek_exposure --save-charts ./charts
32
32
  atlas flow stream --symbol NVDA
33
+ atlas phone-notify --title "Backtest done" --body "SPY: 61% win" --key backtest # your own phone
33
34
  ```
34
35
 
35
36
  ## Layout
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mindvest-atlas",
3
- "version": "0.68.4",
3
+ "version": "0.93.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
@@ -20,6 +20,9 @@ import { screenshotCommand } from './commands/screenshot.js';
20
20
  import { runCommand } from './commands/run.js';
21
21
  import { computerUseCommand } from './commands/computer-use.js';
22
22
  import { dashboardCommand } from './commands/dashboard.js';
23
+ import { chatSearchCommand } from './commands/chat-search.js';
24
+ import { connectorsCommand, connectorCommand } from './commands/connectors.js';
25
+ import { memoryCommand } from './commands/memory.js';
23
26
  import { remoteCheckCommand } from './commands/remote-check.js';
24
27
  import { shareCommand } from './commands/share.js';
25
28
  import { historicalContractCommand } from './commands/historical.js';
@@ -130,6 +133,26 @@ export async function main(rawArgs) {
130
133
  // hand (owner, 2026-09-06).
131
134
  case 'dashboard':
132
135
  return dashboardCommand(withoutCommand(args));
136
+ // EVERY PAST CONVERSATION ON THIS COMPUTER, read where it lies (owner,
137
+ // 2026-09-18: "how do I do searching so every agent can search
138
+ // conversations"). Local files, so no cloud tool could do it; the
139
+ // running Studio knows where each agent keeps its own.
140
+ case 'chat-search':
141
+ case 'chats':
142
+ return await chatSearchCommand(withoutCommand(args));
143
+ // THE CONNECTORS ON THIS COMPUTER, brokers among them (owner,
144
+ // 2026-09-30: "add a CLI for these tools"). Their sign-ins never leave
145
+ // this machine, so no cloud tool could reach them.
146
+ case 'connectors':
147
+ return await connectorsCommand(withoutCommand(args));
148
+ case 'connector':
149
+ return await connectorCommand(withoutCommand(args));
150
+ // WHAT ATLAS KEEPS BETWEEN CONVERSATIONS: three tiers, decayed by use,
151
+ // weighted 1-10 by whoever is working (owner, 2026-09-19). Local files,
152
+ // so no cloud tool could do it.
153
+ case 'memory':
154
+ case 'mem':
155
+ return await memoryCommand(withoutCommand(args));
133
156
  // Put the newest CLI on this machine.
134
157
  case 'update-cli':
135
158
  case 'self-update':
@@ -0,0 +1,218 @@
1
+ // `atlas chat-search` — every past conversation on this computer, searched
2
+ // where it lies.
3
+ //
4
+ // Owner, 2026-09-18: "let's say I want to continue from another agent and my
5
+ // sessions are not linked, how do I do searching so every agent can search
6
+ // conversations … even ask() or a normal convo can check previous convo."
7
+ //
8
+ // The engine is in Atlas Studio (`main/core/chat-search.ts`) because that is
9
+ // where the stores are known: the adapters' own transcripts, the chats Studio
10
+ // keeps for every model reached over a port or a key (LM Studio, Ollama, any
11
+ // provider), a project's own `.chats`, and whatever its sweep has found. This
12
+ // command is the shell's way in, for the agent that is sitting at a prompt
13
+ // rather than inside a project script — the same road `atlas screenshot` takes
14
+ // to the dashboard.
15
+ //
16
+ // A HAND-WRITTEN COMMAND, DELIBERATELY. The rule is that a verb has to earn
17
+ // one by doing something a cloud tool call cannot (2026-09-15). This reads
18
+ // files on THIS disk; there is no cloud tool that could.
19
+ //
20
+ // NOTHING IS SHORTENED ON THE WAY THROUGH (owner, 2026-09-18: "DO NOT TRUNCATE
21
+ // OR CUT"). Whatever the engine returns is printed whole; when there is too
22
+ // much, the engine returns FEWER WHOLE TURNS and says so, and that sentence is
23
+ // printed too.
24
+ import { color, println, eprintln } from '../util/ui.js';
25
+ import { askAddress } from '../remote-control.js';
26
+
27
+ function notRunning() {
28
+ eprintln(color.red('Atlas Studio is not running on this computer.'));
29
+ eprintln(color.dim('It is what knows where each agent keeps its conversations. Start it and try again.'));
30
+ return 1;
31
+ }
32
+
33
+ async function post(at, path, body) {
34
+ const res = await fetch(at.url.replace('/ask', path), {
35
+ method: 'POST',
36
+ headers: { 'Content-Type': 'application/json', 'X-Atlas-Token': at.token },
37
+ body: JSON.stringify(body),
38
+ signal: AbortSignal.timeout(180_000),
39
+ });
40
+ const out = await res.json().catch(() => ({}));
41
+ if (!res.ok || !out.ok) throw new Error(out.error || `Chat search failed (${res.status}).`);
42
+ return out;
43
+ }
44
+
45
+ function printRows(rows, agent) {
46
+ for (const row of rows) {
47
+ const who = row.kind === 'user'
48
+ ? 'You'
49
+ : row.kind === 'assistant'
50
+ ? (agent || 'Model')
51
+ : `${row.kind === 'tool_call' ? 'Tool call' : 'Tool result'}: ${row.tool || 'tool'}`;
52
+ println(color.dim(`--- ${who} ---`));
53
+ println(row.text);
54
+ println('');
55
+ }
56
+ }
57
+
58
+ export async function chatSearchCommand(args) {
59
+ const flags = args.flags || {};
60
+ const at = askAddress(flags.root);
61
+ if (!at) return notRunning();
62
+
63
+ const sub = String((args.positional ?? args._)?.[0] || '').trim();
64
+
65
+ // WHAT THIS COMPUTER REMEMBERS about searching: which folders hold
66
+ // conversations, which of them answer, and where recent answers were. Paths
67
+ // and counters, never a conversation (owner, 2026-09-19: "store the search
68
+ // path or recent important stuff, so we can read this to find info quicker
69
+ // next time").
70
+ if (sub === 'forget') {
71
+ try {
72
+ await post(at, '/chat-search/forget', {});
73
+ println('Forgotten. Every conversation is exactly where it was; only the shortcuts are gone.');
74
+ return 0;
75
+ } catch (err) {
76
+ eprintln(color.red(err.message));
77
+ return 1;
78
+ }
79
+ }
80
+ if (sub === 'memory' || sub === 'places') {
81
+ let mem;
82
+ try {
83
+ mem = await post(at, '/chat-search/memory', {});
84
+ } catch (err) {
85
+ eprintln(color.red(err.message));
86
+ return 1;
87
+ }
88
+ if (flags.json) {
89
+ println(JSON.stringify(mem, null, 2));
90
+ return 0;
91
+ }
92
+ println(color.bold(`${mem.places.length} place(s) learned, ${mem.recent.length} pointer(s) kept`));
93
+ for (const p of mem.places) {
94
+ println(` ${p.dir} ${color.dim(`${p.agent} · ${p.parser}${p.hits ? ` · answered ${p.hits}x` : ''}`)}`);
95
+ }
96
+ for (const r of mem.recent.slice(0, 10)) {
97
+ println(` "${r.query}" -> ${r.sessionId} ${color.dim(`(${r.agent} · ${r.store})`)}`);
98
+ }
99
+ return 0;
100
+ }
101
+
102
+ // `chat-search show <session>` — one conversation, as deep as asked. Its own
103
+ // call rather than a flag, because reading a conversation whole is a
104
+ // different act from finding one.
105
+ if (sub === 'show' || flags.show) {
106
+ const id = String(flags.show || (args.positional ?? args._)?.[1] || '').trim();
107
+ if (!id) {
108
+ eprintln(color.red('Which conversation? `atlas chat-search show <session-id>`'));
109
+ return 1;
110
+ }
111
+ let out;
112
+ try {
113
+ out = await post(at, '/chat-search/show', {
114
+ sessionId: id,
115
+ file: flags.file,
116
+ around: flags.around == null ? undefined : Number(flags.around),
117
+ // `--turns 0` is the whole thing; the default keeps it to the part
118
+ // around the start.
119
+ turns: flags.turns == null ? undefined : Number(flags.turns),
120
+ handover: !!flags.handover,
121
+ discover: !!flags.discover,
122
+ });
123
+ } catch (err) {
124
+ eprintln(color.red(err.message));
125
+ return 1;
126
+ }
127
+ if (flags.json) {
128
+ println(JSON.stringify(out, null, 2));
129
+ return 0;
130
+ }
131
+ if (flags.handover) {
132
+ // Exactly what you paste into another model as the first message.
133
+ println(out.handover);
134
+ return 0;
135
+ }
136
+ println(color.bold(out.title));
137
+ println(color.dim(`${out.agent} · ${out.store} · ${out.sessionId}${out.cwd ? ` · ${out.cwd}` : ''}`));
138
+ println(color.dim(`Turns ${out.from}-${out.to} of ${out.total}. Nothing below is shortened.`));
139
+ println('');
140
+ printRows(out.rows, out.agent);
141
+ return 0;
142
+ }
143
+
144
+ const query = String(flags.query || flags.q || (args.positional ?? args._)?.join(' ') || '').trim();
145
+ if (!query) {
146
+ eprintln(color.red('What are you looking for? `atlas chat-search "the words you remember"`'));
147
+ eprintln(color.dim('Then: atlas chat-search show <session> --turns 0 (the whole conversation)'));
148
+ return 1;
149
+ }
150
+
151
+ let out;
152
+ try {
153
+ out = await post(at, '/chat-search', {
154
+ query,
155
+ turns: flags.turns == null ? undefined : Number(flags.turns),
156
+ limit: flags.limit == null ? undefined : Number(flags.limit),
157
+ maxBytes: flags.bytes == null ? undefined : Number(flags.bytes),
158
+ store: flags.store,
159
+ agent: flags.agent,
160
+ since: flags.since ? Date.parse(String(flags.since)) || undefined : undefined,
161
+ matchCase: !!flags['match-case'],
162
+ regex: !!flags.regex,
163
+ // Look for stores nobody wrote a path for, unless told not to. What it
164
+ // finds is remembered as a FOLDER, so the next search already knows.
165
+ discover: flags['no-discover'] ? false : true,
166
+ // The project you are in comes first; everything else still comes back.
167
+ project: flags.project || flags.job || process.cwd(),
168
+ maxMs: flags.ms == null ? undefined : Number(flags.ms),
169
+ });
170
+ } catch (err) {
171
+ eprintln(color.red(err.message));
172
+ return 1;
173
+ }
174
+
175
+ if (flags.json) {
176
+ println(JSON.stringify(out, null, 2));
177
+ return 0;
178
+ }
179
+
180
+ if (!out.hits.length) {
181
+ println(`Nothing on this computer says "${query}".`);
182
+ println(color.dim(`Looked in ${out.scanned.places} place(s), ${out.scanned.files} transcript(s).`));
183
+ for (const note of out.notes) println(color.dim(note));
184
+ return 0;
185
+ }
186
+
187
+ // SEEN BEFORE. Offered beside the live answer, never instead of it: a store
188
+ // can change under a pointer.
189
+ if (out.remembered?.length) {
190
+ println(color.dim('Seen before:'));
191
+ for (const r of out.remembered) {
192
+ println(color.dim(` "${r.query}" -> ${r.sessionId} (${r.agent} · ${r.store})`));
193
+ }
194
+ println('');
195
+ }
196
+
197
+ println(color.bold(`${out.hits.length} of ${out.found} conversation(s)`));
198
+ println('');
199
+ for (const hit of out.hits) {
200
+ println(color.bold(hit.title));
201
+ println(color.dim(`${hit.agent} · ${hit.store} · ${new Date(hit.updatedAt).toLocaleString()} · ${hit.matches} match(es)`));
202
+ println(color.dim(`session ${hit.sessionId}${hit.cwd ? ` · ${hit.cwd}` : ''}`));
203
+ // The matching line verbatim — or, when that ONE line is enormous, where to
204
+ // find it, because showing part of it would be showing part of a value.
205
+ println(hit.line == null
206
+ ? color.dim(` [one line of ${hit.lineIsHuge?.bytes} bytes. Read it whole: atlas chat-search show ${hit.sessionId} --turns 0]`)
207
+ : ` ${hit.line}`);
208
+ if (!flags.quiet) {
209
+ println('');
210
+ printRows(hit.rows, hit.agent);
211
+ if (hit.note) println(color.yellow(hit.note));
212
+ println(color.dim(`Turns ${hit.from}-${hit.to} of ${hit.total}. atlas chat-search show ${hit.sessionId} --turns 0 for all of it.`));
213
+ }
214
+ println('');
215
+ }
216
+ for (const note of out.notes) println(color.dim(note));
217
+ return 0;
218
+ }