mindvest-atlas 0.68.2 → 0.68.4

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.68.2",
3
+ "version": "0.68.4",
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
@@ -24,6 +24,14 @@ import { remoteCheckCommand } from './commands/remote-check.js';
24
24
  import { shareCommand } from './commands/share.js';
25
25
  import { historicalContractCommand } from './commands/historical.js';
26
26
  import { callCommand } from './commands/call.js';
27
+ // WITHOUT THESE, `atlas --version` PRINTED THE BARE NUMBER AND NOTHING ELSE.
28
+ // They were called but never imported, and the call sits inside a try/catch
29
+ // that swallows anything — so the ReferenceError was invisible and the detail
30
+ // this exists to show (which atlas answered, what else is installed) never
31
+ // appeared. That is the confusion it was written to end: owner, 2026-09-04,
32
+ // "the atlas.cmd does not update on reinstalling, also the mindvest-atlas is
33
+ // using a different version — this is confusing."
34
+ import { installedClis, whichAtlas, versionDetailLines } from './cli-installs.js';
27
35
 
28
36
  /**
29
37
  * Say which Atlas answered and what else is installed.
@@ -14,6 +14,7 @@ import { runStream } from '../util/stream-loop.js';
14
14
  import { formatAlert } from '../util/format.js';
15
15
  import { color, println, eprintln, prettyJson } from '../util/ui.js';
16
16
  import { requestJson } from '../api/client.js';
17
+ import { moved } from './moved.js';
17
18
  import * as background from '../util/background.js';
18
19
 
19
20
  // Names the slot, the claim file and the log, and it is the command's own name
@@ -29,24 +30,6 @@ function sanitizeFilename(name) {
29
30
  // ── list / delete ────────────────────────────────────────────────────────────
30
31
  const callTool = (name, body) => requestJson(API.tool(name), { method: 'POST', body: body || {} });
31
32
 
32
- async function listAlerts(flags) {
33
- const data = await callTool('List-Alerts', {
34
- ...(flags.all ? { active_only: false } : {}),
35
- ...(flags.symbol ? { symbol: String(flags.symbol).toUpperCase() } : {}),
36
- });
37
- if (flags.json) { println(prettyJson(data)); return 0; }
38
- const list = (data && (data.alerts || data.data)) || (Array.isArray(data) ? data : []);
39
- if (!list.length) { println(color.dim('No alerts.')); return 0; }
40
- for (const a of list) {
41
- const state = a.is_active === false ? color.dim('off') : color.green('on');
42
- println(`${state} ${color.bold(a.name || '(unnamed)')} ${color.dim([a.symbol, a.alert_type].filter(Boolean).join(' · '))}`);
43
- println(color.dim(` ${a.id}`));
44
- }
45
- println('');
46
- println(color.dim('Switch one off: atlas alerts delete --id <id>'));
47
- println(color.dim('Delete for good: atlas alerts delete --id <id> --purge'));
48
- return 0;
49
- }
50
33
 
51
34
  /** The shape of every event on the feed, as JSON Schema, so a script or a
52
35
  * model can read it once and rely on it. Alerts and trades share it. */
@@ -56,32 +39,6 @@ async function schema() {
56
39
  return 0;
57
40
  }
58
41
 
59
- async function deleteAlerts(args) {
60
- const flags = args.flags;
61
- // --id may repeat; extra positionals after the verb are ids too, so
62
- // `atlas alerts delete a b c` does what it looks like it does.
63
- const fromFlags = flags.id === undefined ? [] : (Array.isArray(flags.id) ? flags.id : [flags.id]);
64
- const ids = [...fromFlags, ...(args._ || []).slice(1)]
65
- .map((x) => String(x || '').trim())
66
- .filter((x) => x && x !== 'true');
67
- if (!ids.length) {
68
- eprintln(color.red('Which alert? --id <id> (repeat for several), or list them: atlas alerts list'));
69
- return 1;
70
- }
71
- const purge = !!flags.purge;
72
- const data = await callTool('Delete-Alert', {
73
- alert_ids: ids, purge,
74
- });
75
- if (data && data.error) { eprintln(color.red(String(data.error))); return 1; }
76
- if (flags.json) { println(prettyJson(data)); return 0; }
77
- println(data.message || (purge ? 'Deleted.' : 'Switched off.'));
78
- // A purge that kept some says why, and the message already names them; this
79
- // points at the way through rather than leaving the user to guess.
80
- if (data.kept && Object.keys(data.kept).length) {
81
- println(color.dim('Repoint those setups, or drop --purge to just switch the alert off.'));
82
- }
83
- return 0;
84
- }
85
42
 
86
43
  export async function alertsCommand(args) {
87
44
  const flags = args.flags;
@@ -92,8 +49,13 @@ export async function alertsCommand(args) {
92
49
  // (owner, 2026-09-12: "it's hard to manage spoiled alerts without a
93
50
  // something to delete them").
94
51
  const verb = String((args._ && args._[0]) || '').toLowerCase();
95
- if (verb === 'list' || verb === 'ls') return await listAlerts(flags);
96
- if (verb === 'delete' || verb === 'rm' || verb === 'off') return await deleteAlerts(args);
52
+ // ONE NAME PER CAPABILITY. These wrapped a single tool each; the generic path
53
+ // reaches it by name. See commands/moved.js.
54
+ const rest = { _: (args._ || []).slice(1), flags };
55
+ if (verb === 'list' || verb === 'ls') return await moved(`alerts ${verb}`, 'list-alerts', rest);
56
+ if (verb === 'delete' || verb === 'rm' || verb === 'off') return await moved(`alerts ${verb}`, 'delete-alert', rest);
57
+ // KEPT: there is no tool behind these two. `schema` reads a public route, and
58
+ // a bare `atlas alerts` is the live feed — a stream is not a tool call.
97
59
  if (verb === 'schema') return await schema();
98
60
  if (flags.stop) return background.stop(SLOT);
99
61
  if (flags.detach) return background.detach(SLOT);
@@ -21,7 +21,7 @@
21
21
  // ONE IMPLEMENTATION, NOT TWO: every subcommand runs the SAME atlas_env function
22
22
  // a project's script would call, inside the project, so the switch, the claim,
23
23
  // the action log and standing back for a person are identically the same rules.
24
- import { spawnSync } from 'node:child_process';
24
+ import { spawn, spawnSync } from 'node:child_process';
25
25
  import crypto from 'node:crypto';
26
26
  import fs from 'node:fs';
27
27
  import path from 'node:path';
@@ -285,6 +285,63 @@ export function runInProject(job, code, root) {
285
285
  return out.status === null ? 1 : out.status;
286
286
  }
287
287
 
288
+ /**
289
+ * Start a watch that OUTLIVES this command.
290
+ *
291
+ * A watch runs for hours by design: it looks, decides how long to wait, and
292
+ * looks again. Run in the foreground, a shell agent that started one would sit
293
+ * there until it finished — one turn, held open, with nothing to show for it.
294
+ * The Python CLI has detached it since 2026-09-06; this runtime ran it inline,
295
+ * and this runtime is the one Atlas Studio bundles, so the blocking version is
296
+ * the one a chat actually got (owner, 2026-09-15).
297
+ *
298
+ * Detached, with no console and no pipes: what it does is visible in the watch
299
+ * window and the action log, which is where somebody watching already is.
300
+ */
301
+ function startWatch(job, instruction, flags) {
302
+ if (!String(instruction || '').trim()) {
303
+ eprintln(color.red('Say what to watch for.'));
304
+ eprintln(color.dim(' atlas computer-use watch "when SPY breaks the opening range high, buy 1 call"'));
305
+ return 1;
306
+ }
307
+ const { dir } = workspace(job, undefined);
308
+ if (!fs.existsSync(dir)) {
309
+ eprintln(color.red(`There is no project called ${job} on this computer.`));
310
+ return 1;
311
+ }
312
+ try { ensureProjectFiles(dir); } catch { /* it may already be right */ }
313
+ const venv = venvPython(dir);
314
+ const python = venv ? [String(venv)] : [...ambientPython()];
315
+ const env = { ...process.env, ...jobEnv(dir), ATLAS_JOB: path.basename(dir) };
316
+ if (!env.ATLAS_ASK_URL || !env.ATLAS_ASK_TOKEN) {
317
+ const at = askAddress(undefined);
318
+ if (at) { env.ATLAS_ASK_URL = at.url; env.ATLAS_ASK_TOKEN = at.token; }
319
+ }
320
+ if (!env.ATLAS_COMPUTER_SESSION) env.ATLAS_COMPUTER_SESSION = shellSession(undefined);
321
+ env.PYTHONPATH = [dir, process.env.PYTHONPATH].filter(Boolean).join(path.delimiter);
322
+ ensureDeps(python, dir, env);
323
+ const kw = { every: Number(flags.every || 5) };
324
+ if (flags.most) kw.most = Number(flags.most);
325
+ if (flags.hours) kw.hours = Number(flags.hours);
326
+ const code = 'import atlas_env as e, json, sys\n'
327
+ + 'e.watch(json.loads(sys.argv[1]), **json.loads(sys.argv[2]))\n';
328
+ try {
329
+ const child = spawn(
330
+ python[0],
331
+ [...python.slice(1), '-c', code, JSON.stringify(String(instruction)), JSON.stringify(kw)],
332
+ { cwd: dir, env, stdio: 'ignore', detached: true, ...HIDDEN },
333
+ );
334
+ child.unref();
335
+ } catch (err) {
336
+ eprintln(color.red(`Could not start watching: ${err && err.message ? err.message : err}`));
337
+ return 1;
338
+ }
339
+ println(color.dim('Watching started. It looks, decides how long to wait, and looks again.'));
340
+ println(color.dim(' Follow it in the watch window, or: atlas computer-use screen'));
341
+ println(color.dim(' Stop ends it — the whole thing, wait included.'));
342
+ return 0;
343
+ }
344
+
288
345
  export function computerUseCommand(args) {
289
346
  const flags = args.flags || {};
290
347
  const rest = [...(args.positional || args._ || [])];
@@ -351,6 +408,48 @@ export function computerUseCommand(args) {
351
408
  return out;
352
409
  }
353
410
 
411
+ // A WATCH FROM A CHAT MUST COME BACK: detached unless told otherwise.
412
+ if (sub === 'watch' && flags.background !== false) {
413
+ return startWatch(job, rest[0] || '', flags);
414
+ }
415
+
416
+ // SEVERAL ACTIONS AND ONE PICTURE, in one process. The slow part was never
417
+ // the clicking: it was a command and a look for every step, when four of them
418
+ // were visible in the picture you already had.
419
+ //
420
+ // atlas computer-use do "click 820 410" "type hello" "key enter"
421
+ //
422
+ // This is the batching every skill tells a model to use, and it was missing
423
+ // here — from the runtime Atlas Studio bundles — so a model in a chat that
424
+ // followed the instructions was told `There is no "do" here` (owner,
425
+ // 2026-09-15: "see if we can unify things instead of diverging").
426
+ if (sub === 'do') {
427
+ if (!rest.length) {
428
+ eprintln(color.red('What should it do? e.g. do "click 820 410" "type hello"'));
429
+ return 1;
430
+ }
431
+ const steps = [];
432
+ for (const raw of rest) {
433
+ const bits = String(raw).split(/\s+/).filter(Boolean);
434
+ if (!bits.length) continue;
435
+ const verb = bits[0].toLowerCase();
436
+ if (!OPS[verb]) {
437
+ eprintln(color.red(`There is no "${verb}" to do.`));
438
+ return 1;
439
+ }
440
+ const [name] = OPS[verb];
441
+ // A quoted phrase is one value: `type "hello there"` means two words
442
+ // typed, not a word typed twice.
443
+ const vals = (verb === 'type' || verb === 'note' || verb === 'find')
444
+ ? (bits.length > 1 ? [bits.slice(1).join(' ')] : [])
445
+ : bits.slice(1).map((v) => (NUMERIC.has(verb) ? Number(v) : v));
446
+ steps.push([name, ...vals]);
447
+ }
448
+ const code = 'import atlas_env as e, json\n'
449
+ + `print(json.dumps(e.do(*[tuple(s) for s in json.loads(${JSON.stringify(JSON.stringify(steps))})]), default=str, indent=2))\n`;
450
+ return runInProject(job, code);
451
+ }
452
+
354
453
  if (!OPS[sub]) {
355
454
  eprintln(color.red(`There is no "${sub}" here.`));
356
455
  println(HELP);
@@ -0,0 +1,32 @@
1
+ // A VERB THAT ONLY WRAPPED A TOOL, kept working while it goes.
2
+ //
3
+ // Owner, 2026-09-15: "scan through all my mcp, cli, api and see if we can unify
4
+ // things instead of diverging ... I have divergence because now I have too much
5
+ // commands."
6
+ //
7
+ // Most `atlas alerts <verb>` and `atlas signals <verb>` subcommands did one
8
+ // thing: call a single tool and print a table. The generic path reaches every
9
+ // tool by name already — `atlas list-alerts`, `atlas signal-take` — against the
10
+ // live catalogue, which is the one road that cannot drift because nobody
11
+ // maintains it. A second spelling is what made the surface feel large, and it
12
+ // was worse than redundant: eight Trade-* and Signal-* tools never got a verb,
13
+ // so an agent that learned the verbs concluded closing a trade was impossible.
14
+ //
15
+ // So the verbs forward, once, with a line saying where they went. They are
16
+ // removed in a later release; this is here so nobody's script breaks on the day
17
+ // they update.
18
+ import { color, eprintln } from '../util/ui.js';
19
+ import { callCommand } from './call.js';
20
+
21
+ // An ES import binding cannot be replaced from outside, so the forward goes
22
+ // through this object: a test can swap `forwardTo.call` and assert WHERE a verb
23
+ // went without a network round trip. The Python twin patches the module
24
+ // attribute for the same reason.
25
+ export const forwardTo = { call: callCommand };
26
+
27
+ export async function moved(verb, slug, args) {
28
+ eprintln(color.dim(
29
+ `atlas ${verb} is now atlas ${slug} — same thing, one name. This forwards for now.`,
30
+ ));
31
+ return await forwardTo.call(slug, args);
32
+ }
@@ -13,6 +13,7 @@
13
13
  import { API } from '../config.js';
14
14
  import { requestJson } from '../api/client.js';
15
15
  import { color, println, eprintln, prettyJson } from '../util/ui.js';
16
+ import { moved } from './moved.js';
16
17
 
17
18
  const HELP = `${color.bold('atlas signals')} — plays: your groups, the board, and what they have made
18
19
 
@@ -140,39 +141,22 @@ export async function signalsCommand(args) {
140
141
  const groupId = flags.group || flags['group-id'] || flags.id;
141
142
 
142
143
  switch (sub) {
143
- case 'groups': {
144
- const data = await callTool('Signal-Groups', {});
145
- if (failed(data)) return printFailure(data);
146
- if (json) { println(prettyJson(data)); return 0; }
147
- const list = data.groups || data.data || [];
148
- if (!list.length) { println(color.dim('No groups yet. The community board is always there: atlas signals plays --board community')); return 0; }
149
- for (const g of list) {
150
- const bits = [g.role, g.visibility, g.signal_count != null ? `${g.signal_count} plays` : null].filter(Boolean);
151
- println(`${color.bold(g.name || '(unnamed)')} ${color.dim(bits.join(' · '))}`);
152
- println(color.dim(` ${g.id}`));
153
- }
154
- return 0;
155
- }
156
- case 'plays':
157
- case 'list': {
158
- const body = {};
159
- if (flags.board) body.board = String(flags.board);
160
- if (flags.group) body.group_id = String(flags.group);
161
- if (flags.days !== undefined) body.days = num(flags.days);
162
- if (flags.limit !== undefined) body.limit = num(flags.limit);
163
- const data = await callTool('Signal-List', body);
164
- if (failed(data)) return printFailure(data);
165
- if (json) { println(prettyJson(data)); return 0; }
166
- const list = rows(data.plays || data.signals || data);
167
- if (!list.length) { println(color.dim('Nothing on that board in that window.')); return 0; }
168
- for (const s of list) {
169
- const p = s.payload || s;
170
- const what = [p.side, p.symbol, p.strike ? `$${p.strike}` : null, (p.option_type || '').toUpperCase() || null,
171
- p.expiration_date || p.expiry || null].filter(Boolean).join(' ');
172
- println(`${color.bold(what || '(play)')} ${color.dim(s.status || '')}`);
173
- println(color.dim(` ${s.id}${s.posted_at ? ' ' + s.posted_at : ''}`));
174
- }
175
- return 0;
144
+ // ONE NAME PER CAPABILITY (owner, 2026-09-15). Each of these wrapped a
145
+ // single tool and printed a table; the generic path reaches every tool by
146
+ // name against the live catalogue, including the eight Signal-*/Trade-*
147
+ // tools that never got a verb here. `post` stays — its --link/--with-*
148
+ // grammar for a second leg is not something a flag schema can express.
149
+ case 'groups': case 'plays': case 'list': case 'show': case 'take':
150
+ case 'performance': case 'perf': case 'delete': case 'rm':
151
+ case 'discover': case 'follow': case 'schema': {
152
+ const MOVED = {
153
+ groups: 'signal-groups', plays: 'signal-list', list: 'signal-list',
154
+ show: 'signal-show', take: 'signal-take',
155
+ performance: 'signal-group-performance', perf: 'signal-group-performance',
156
+ delete: 'signal-group-delete', rm: 'signal-group-delete',
157
+ discover: 'signal-discover', follow: 'signal-follow', schema: 'signal-schema',
158
+ };
159
+ return await moved(`signals ${sub}`, MOVED[sub], { _: (args._ || []).slice(1), flags });
176
160
  }
177
161
  case 'post': {
178
162
  const leg = legFrom(flags, '', null);
@@ -203,97 +187,6 @@ export async function signalsCommand(args) {
203
187
  println(data.message || (link ? 'Posted both, linked.' : 'Posted.'));
204
188
  return 0;
205
189
  }
206
- case 'show': {
207
- const id = flags.id || flags.play || flags.signal;
208
- if (!id) { eprintln(color.red('Which play? --id <play-id>')); return 1; }
209
- const data = await callTool('Signal-Show', { signal_id: String(id) });
210
- if (failed(data)) return printFailure(data);
211
- println(prettyJson(data));
212
- return 0;
213
- }
214
- case 'take': {
215
- const id = flags.id || flags.play || flags.signal;
216
- if (!id) { eprintln(color.red('Which play? --id <play-id>')); return 1; }
217
- const body = { signal_id: String(id) };
218
- if (flags.contracts !== undefined) body.contracts = num(flags.contracts);
219
- if (flags.account) body.account_id = String(flags.account);
220
- const data = await callTool('Signal-Take', body);
221
- if (failed(data)) return printFailure(data);
222
- println(json ? prettyJson(data) : (data.message || 'Taken.'));
223
- return 0;
224
- }
225
- case 'performance':
226
- case 'perf': {
227
- if (!groupId) { eprintln(color.red('Which group? --group <id> (atlas signals groups)')); return 1; }
228
- const data = await callTool('Signal-Group-Performance', {
229
- group_id: String(groupId), mine: !!flags.mine,
230
- });
231
- if (failed(data)) return printFailure(data);
232
- if (json) { println(prettyJson(data)); return 0; }
233
- const t = data.totals || {};
234
- const name = data.display_name || data.name || 'this group';
235
- const whose = data.whose === 'mine' ? 'your own trades' : "the author's record";
236
- println(`${color.bold(name)} ${color.dim('— ' + whose)}`);
237
- println(` Closed trades ${(data.rows || []).length}`);
238
- println(` Win rate ${t.win_rate ?? 0}% ${color.dim(`(${t.win_count ?? 0} up, ${t.loss_count ?? 0} down)`)}`);
239
- println(` Total ${money(t.total_pnl)} ${color.dim(`${t.total_pnl_pct ?? 0}%`)}`);
240
- if (data.viewer_has_rows && data.whose !== 'mine') {
241
- println(color.dim(' You have taken plays here too: add --mine for your own numbers.'));
242
- }
243
- return 0;
244
- }
245
- case 'delete':
246
- case 'rm': {
247
- if (!groupId) { eprintln(color.red('Which group? --group <id> (atlas signals groups)')); return 1; }
248
- const stop = !!(flags['stop-subscribers'] || flags.stop);
249
- const data = await callTool('Signal-Group-Delete', {
250
- group_id: String(groupId), stop_subscribers: stop,
251
- });
252
- if (failed(data)) {
253
- // THE REFUSAL WITH AN ANSWER. People are subscribed: say how many, say
254
- // what stopping them costs them, and print the line that does it —
255
- // rather than an error that names a screen this person is not looking at.
256
- if (data.code === 'has_subscribers') {
257
- const n = Number(data.subscriber_count) || 1;
258
- eprintln(color.yellow(`${n} ${n === 1 ? 'person is' : 'people are'} subscribed to this group.`));
259
- eprintln(color.dim('Deleting stops their billing at once, so there is no next payment, and they'
260
- + ' lose the rest of the month they paid for. Anything they already traded is theirs.'));
261
- eprintln('');
262
- eprintln(' ' + color.cyan(`atlas signals delete --group ${groupId} --stop-subscribers`));
263
- return 1;
264
- }
265
- return printFailure(data);
266
- }
267
- println(json ? prettyJson(data) : (data.message || 'Group deleted.'));
268
- return 0;
269
- }
270
- case 'discover': {
271
- const data = await callTool('Signal-Discover', {});
272
- if (failed(data)) return printFailure(data);
273
- if (json) { println(prettyJson(data)); return 0; }
274
- const list = data.groups || [];
275
- if (!list.length) { println(color.dim('No public groups yet.')); return 0; }
276
- for (const g of list) {
277
- println(`${color.bold(g.name || '(unnamed)')} ${color.dim([g.owner_label, `${g.follower_count ?? 0} following`].filter(Boolean).join(' · '))}`);
278
- println(color.dim(` ${g.id}`));
279
- }
280
- return 0;
281
- }
282
- case 'follow': {
283
- if (!groupId) { eprintln(color.red('Which group? --group <id>')); return 1; }
284
- const data = await callTool('Signal-Follow', {
285
- group_id: String(groupId), follow: flags.off ? false : flags.follow !== false,
286
- });
287
- if (failed(data)) return printFailure(data);
288
- println(json ? prettyJson(data) : (data.message || 'Done.'));
289
- return 0;
290
- }
291
- case 'schema': {
292
- const data = await callTool('Signal-Schema', {});
293
- if (failed(data)) return printFailure(data);
294
- println(typeof data === 'string' ? data : prettyJson(data));
295
- return 0;
296
- }
297
190
  default:
298
191
  eprintln(color.red(`Unknown: atlas signals ${sub}`));
299
192
  println(HELP);
package/src/util/help.js CHANGED
@@ -28,17 +28,12 @@ export function printRootHelp() {
28
28
  println(' [--no-greeks] [--json]');
29
29
  println();
30
30
  println(c.bold('PLAYS (SIGNALS)'));
31
- println(' signals groups Groups you post to, and the ones you follow');
32
- println(' signals plays [--board community|private] The plays [--days N] [--limit N]');
33
- println(' signals performance --group <id> [--mine] A group\'s closed trades and win rate');
34
- println(' signals delete --group <id> [--stop-subscribers] Delete a group (stops subscriptions first)');
35
- println(' signals take --id <play> [--contracts N] Place a play in your account');
36
- println(' signals <discover|follow|show|schema> ' + c.dim('atlas signals help for all of it'));
31
+ println(' signals post --symbol SPY ... Post a play (--link oco|oeo for a second leg)');
32
+ println(' signal-groups · signal-list · signal-take ' + c.dim('every Signal-* and Trade-* tool, by name'));
37
33
  println();
38
34
  println(c.bold('STREAMS'));
39
- println(' alerts list [--all] [--symbol SPY] Your alerts, with their ids');
40
35
  println(' alerts schema The shape of every feed event (JSON Schema)');
41
- println(' alerts delete --id <id> [--id <id2>] [--purge] Switch alerts off, or delete for good');
36
+ println(' list-alerts · delete-alert --alert-ids <id> ' + c.dim('your alerts, and clearing spoiled ones'));
42
37
  println(' alerts [--symbol SPY] [--name greek_exposure] Live alert feed (SSE)');
43
38
  println(' [--save-charts <dir>] [--no-chart] [--json]');
44
39
  println(' [--detach] [--stop] Stream in the background, or stop it');
@@ -64,14 +59,12 @@ export function printRootHelp() {
64
59
  println(c.dim(' atlas options-chain --symbol SPY --expiration 2026-06-20'));
65
60
  println(c.dim(' atlas historical-contract --symbol NVDA --expiry 2026-07-13 --strike 205 --side call --from 2026-07-06 --to 2026-07-10'));
66
61
  println(c.dim(' atlas update-alert --alert-id <id> --updates.is-active true # nested via a dotted flag'));
67
- println(c.dim(' atlas create-task --name "Close SPY 3:55" --instruction "Close my SPY position" --schedule-times 15:55'));
68
- println(c.dim(' atlas run-task --task-id <id>'));
69
62
  println(c.dim(' atlas workflow-create --name "GEX signal" --schedule "30 9 * * 1-5" --tools Stock-Quote --instruction "…"'));
70
63
  println(c.dim(' atlas workflow-create --name "FOMC day" --run-once-at "2026-09-17 14:00" --tools Stock-Quote --instruction "…" # once, at specific dates/times; --trigger-source manual = Run now only'));
71
64
  println(c.dim(' atlas alerts --symbol SPY --name greek_exposure --save-charts ./charts'));
72
65
  println(c.dim(' atlas flow stream --symbol NVDA'));
73
- println(c.dim(' atlas signals performance --group <id>'));
74
- println(c.dim(' atlas signals delete --group <id> --stop-subscribers'));
66
+ println(c.dim(' atlas signal-group-performance --group-id <id>'));
67
+ println(c.dim(' atlas signal-group-delete --group-id <id> --stop-subscribers'));
75
68
  }
76
69
 
77
70
  // Parse the `Args:` block of a tool docstring into { paramName: description }.