dexterm-cli 0.2.1 → 0.2.2
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/dist/packages/autotrade/src/chat/connector.js +5 -4
- package/dist/packages/autotrade/src/chat/control.js +3 -0
- package/dist/packages/autotrade/src/chat/hub.js +37 -2
- package/dist/packages/autotrade/src/chat/index.js +3 -2
- package/dist/packages/autotrade/src/chat/readonly.js +530 -0
- package/dist/packages/autotrade/src/chat/service.js +12 -1
- package/dist/packages/autotrade/src/engine.js +75 -29
- package/dist/packages/autotrade/src/index.js +2 -0
- package/dist/packages/autotrade/src/runner.js +72 -14
- package/dist/packages/autotrade/src/store.js +1 -1
- package/dist/packages/autotrade/src/strategy.js +17 -0
- package/dist/packages/autotrade/src/tiers.js +194 -0
- package/dist/packages/classifier/src/config.js +90 -12
- package/dist/packages/classifier/src/index.js +1 -0
- package/dist/packages/classifier/src/win-acl.js +71 -0
- package/dist/packages/cli/src/app-help-gen.js +1 -1
- package/dist/packages/cli/src/autotrade-cmd/fleet.js +4 -2
- package/dist/packages/cli/src/autotrade-cmd/run.js +4 -2
- package/dist/packages/cli/src/autotrade-cmd/service.js +3 -1
- package/dist/packages/cli/src/autotrade-cmd/strategies.js +1 -1
- package/dist/packages/cli/src/autotrade-cmd/telegram-host.js +2 -0
- package/dist/packages/cli/src/classifier-cmd.js +16 -8
- package/dist/packages/cli/src/skill-eval-build.js +7 -2
- package/dist/packages/cli/src/version.js +1 -1
- package/dist/packages/mcp/assets/kb/app-help/autotrade-telegram.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-confidence-tiers-experimental-unproven-paper-only.md +56 -0
- package/dist/packages/mcp/assets/kb/docs/docs-classifier-hosted-jev-with-your-own-key.md +3 -1
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-install.md +3 -3
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-requirements.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-narratives-where-to-find-it.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-telegram-read-only-data-check-top-and-signals-experimental.md +48 -0
- package/dist/packages/mcp/assets/kb/docs/docs-telegram-what-it-is.md +2 -0
- package/dist/packages/mcp/assets/kb/docs/docs-telegram-what-it-will-not-do.md +1 -1
- package/dist/packages/mcp/src/server.js +2 -1
- package/dist/packages/packs/src/skill-care.js +10 -2
- package/package.json +1 -1
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
// callback data come from engine state only.
|
|
9
9
|
//
|
|
10
10
|
// Transports: telegram (long poll from the user's machine, telegram.ts) and fake (in memory, fake.ts, every test).
|
|
11
|
-
/** The closed command list (§8.4 tier 2). Anything else is `help`. */
|
|
12
|
-
export const COMMANDS = ['status', 'positions', 'pause', 'resume', 'kill', 'stop', 'flatten', 'strategies', 'use', 'approvals', 'help', 'pair', 'start'];
|
|
11
|
+
/** The closed command list (§8.4 tier 2; task-1426 adds the read-only check, top and signals). Anything else is `help`. */
|
|
12
|
+
export const COMMANDS = ['status', 'positions', 'pause', 'resume', 'kill', 'stop', 'flatten', 'strategies', 'use', 'approvals', 'help', 'pair', 'start', 'check', 'top', 'signals'];
|
|
13
13
|
/** "/Status@my_bot arg" → {command, args}; null when it is not a slash command. Unknown names map to help. */
|
|
14
14
|
export function parseCommand(text) {
|
|
15
15
|
const t = text.trim();
|
|
@@ -18,7 +18,8 @@ export function parseCommand(text) {
|
|
|
18
18
|
const [head, ...rest] = t.split(/\s+/);
|
|
19
19
|
const name = head.slice(1).split('@')[0].toLowerCase();
|
|
20
20
|
const command = COMMANDS.includes(name) ? name : 'help';
|
|
21
|
-
// Args are short tokens only (a preset id, a pairing code, on/off): never prose.
|
|
22
|
-
|
|
21
|
+
// Args are short tokens only (a preset id, a pairing code, on/off, a count, a mint): never prose. 48 characters
|
|
22
|
+
// keeps a whole mint (44 at most) and leaves an over-long one too long to pass as one (task-1426).
|
|
23
|
+
const args = rest.slice(0, 3).map((a) => a.slice(0, 48));
|
|
23
24
|
return { command, args };
|
|
24
25
|
}
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
// /use <id> an equal-or-tighter validated strategy only, after a [Yes, switch] button;
|
|
11
11
|
// never changes paper/live; a looser one says "change it locally"
|
|
12
12
|
// /approvals on every entry waits for a tap (safer). `off` is local only
|
|
13
|
+
// /check <CA> · /top [N] · /signals [N] read-only data (task-1426): not routed here at all; the hub sends them
|
|
14
|
+
// to the ReadOnlyRouter (readonly.ts), which never sees this port
|
|
13
15
|
// Not from chat, ever: live mode, looser caps or strategies, approvals off, resuming after a kill or a panic.
|
|
14
16
|
// The router only calls the ControlPort the host gives it; the port writes the same control file the CLI and the
|
|
15
17
|
// Q screen write, so chat is a remote control for the same local engine and adds no path around its fences.
|
|
@@ -22,6 +24,7 @@ export const HELP_LINES = [
|
|
|
22
24
|
'/flatten (sell everything, asks first)',
|
|
23
25
|
'/use <strategy> (equal or tighter only, asks first)',
|
|
24
26
|
'/approvals on (every entry waits for your tap)',
|
|
27
|
+
'/check <CA> · /top [N] · /signals [N] (read-only: data, not advice)',
|
|
25
28
|
'Trades are approved only with buttons. I never ask for keys or seed phrases.',
|
|
26
29
|
];
|
|
27
30
|
export const NEVER_RATE_LIMITED = ['kill', 'stop', 'pause', 'flatten'];
|
|
@@ -6,15 +6,21 @@
|
|
|
6
6
|
// pairing (only while a local pairing window is open: `/pair <code>` from a private chat);
|
|
7
7
|
// the allowlist: an unknown sender gets NO reply and nothing happens (five in an hour raise a local alert:
|
|
8
8
|
// "possible token leak"); a paired sender needs the tier the command needs, in the chat it was paired in;
|
|
9
|
-
// rate limit: 10 commands a minute per sender, except /kill, /stop, /pause and /flatten (never limited);
|
|
9
|
+
// rate limit: 10 commands a minute per sender, except /kill, /stop, /pause and /flatten (never limited); a
|
|
10
|
+
// read-only data command that reaches the data sources also counts against READS_PER_MIN per chat (task-2146,
|
|
11
|
+
// readonly.ts): over it, the chat is told once a minute, then gets silence;
|
|
10
12
|
// commands → the ControlRouter; button callbacks → the ApprovalBroker (the only trade authority);
|
|
13
|
+
// the read-only data commands (/check /top /signals, task-1426) → the ReadOnlyRouter, built from the ReadOnlyPort
|
|
14
|
+
// alone: it never sees the ControlPort or the broker and answers with lines only (no confirm, no button);
|
|
11
15
|
// free text → the help text, never parsed.
|
|
12
16
|
// Outbound: notify messages go to every chat paired with the notify tier; replies go to the asking chat only.
|
|
13
17
|
import { allowed } from "./config.js";
|
|
14
18
|
import { ControlRouter, HELP_LINES, NEVER_RATE_LIMITED } from "./control.js";
|
|
19
|
+
import { READ_COMMANDS, ReadOnlyRouter } from "./readonly.js";
|
|
15
20
|
export const COMMANDS_PER_MIN = 10;
|
|
16
21
|
export const UNKNOWN_ALERT_PER_HOUR = 5;
|
|
17
|
-
const READ_ONLY = ['status', 'positions', 'strategies', 'help', 'start'];
|
|
22
|
+
const READ_ONLY = ['status', 'positions', 'strategies', 'help', 'start', ...READ_COMMANDS];
|
|
23
|
+
const isReadCommand = (c) => READ_COMMANDS.includes(c);
|
|
18
24
|
export class ChatHub {
|
|
19
25
|
o;
|
|
20
26
|
now;
|
|
@@ -23,12 +29,14 @@ export class ChatHub {
|
|
|
23
29
|
unknownTimes = [];
|
|
24
30
|
lastUnknownAlert = 0;
|
|
25
31
|
router;
|
|
32
|
+
reads;
|
|
26
33
|
pairing = null;
|
|
27
34
|
inflight = new Set();
|
|
28
35
|
constructor(o) {
|
|
29
36
|
this.o = o;
|
|
30
37
|
this.now = o.now ?? Date.now;
|
|
31
38
|
this.router = o.port ? new ControlRouter(o.port) : null;
|
|
39
|
+
this.reads = o.reads ? new ReadOnlyRouter(o.reads, this.now) : null;
|
|
32
40
|
o.connector.onCommand((c) => this.track(this.onCommand(c)));
|
|
33
41
|
o.connector.onAction((a) => this.track(this.onAction(a)));
|
|
34
42
|
o.connector.onText((t) => this.track(this.onText(t)));
|
|
@@ -41,6 +49,7 @@ export class ChatHub {
|
|
|
41
49
|
async idle() { while (this.inflight.size)
|
|
42
50
|
await Promise.allSettled([...this.inflight]); }
|
|
43
51
|
setPort(port) { this.router = port ? new ControlRouter(port) : null; }
|
|
52
|
+
setReads(reads) { this.reads = reads ? new ReadOnlyRouter(reads, this.now) : null; }
|
|
44
53
|
/** Open a local pairing window: `/pair <code>` from a private chat is checked against it. */
|
|
45
54
|
openPairing(window, onResult) { this.pairing = { window, onResult }; }
|
|
46
55
|
closePairing() { this.pairing = null; }
|
|
@@ -124,6 +133,10 @@ export class ChatHub {
|
|
|
124
133
|
await this.reply(c.chatId, ['Too many commands: wait a minute. /kill and /pause always work.'], 'rate_limited');
|
|
125
134
|
return;
|
|
126
135
|
}
|
|
136
|
+
if (isReadCommand(c.command)) {
|
|
137
|
+
await this.onRead(c);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
127
140
|
if (!this.router) {
|
|
128
141
|
await this.reply(c.chatId, ['No runner is connected right now.'], 'no_runner');
|
|
129
142
|
return;
|
|
@@ -139,6 +152,28 @@ export class ChatHub {
|
|
|
139
152
|
await this.o.broker.confirm(cf.kind, { senderId: c.senderId, chatId: c.chatId }, cf.lines, cf.yes, cf.payload);
|
|
140
153
|
}
|
|
141
154
|
}
|
|
155
|
+
/** A read-only data command: lines from the ReadOnlyRouter, never a confirm or a ControlPort call. */
|
|
156
|
+
async onRead(c) {
|
|
157
|
+
if (!this.reads) {
|
|
158
|
+
await this.reply(c.chatId, ['Read-only data is not available here: use dexterm on your computer.'], 'no_reads');
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
let lines;
|
|
162
|
+
try {
|
|
163
|
+
const r = await this.reads.handle(c);
|
|
164
|
+
lines = r.lines;
|
|
165
|
+
if (r.failed)
|
|
166
|
+
this.o.audit.add(this.now(), 'read_failed', { command: c.command, error: r.failed.slice(0, 160) });
|
|
167
|
+
if (r.limited)
|
|
168
|
+
this.o.audit.add(this.now(), 'rate_limited', { senderId: c.senderId, chatId: c.chatId, command: c.command, scope: 'reads', told: lines.length > 0 });
|
|
169
|
+
}
|
|
170
|
+
catch (e) {
|
|
171
|
+
this.o.audit.add(this.now(), 'hub_error', { command: c.command, error: e instanceof Error ? e.message.slice(0, 160) : String(e).slice(0, 160) });
|
|
172
|
+
lines = ['Could not read that right now (the data sources did not answer). Try again in a minute.'];
|
|
173
|
+
}
|
|
174
|
+
this.o.audit.add(this.now(), 'command_result', { updateId: c.updateId, command: c.command, lines: lines.length, confirms: 0 });
|
|
175
|
+
await this.reply(c.chatId, lines, `cmd_${c.command}`);
|
|
176
|
+
}
|
|
142
177
|
async onAction(a) {
|
|
143
178
|
if (this.dup(a.updateId)) {
|
|
144
179
|
this.o.audit.add(this.now(), 'in_duplicate', { updateId: a.updateId });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// dexterm autotrade chat (task-1527, AUTOTRADE-SPEC §8): notify, control and approve-to-trade through the user's own
|
|
2
2
|
// bot. connector (the interface) · fake (tests) · telegram (long poll) · redact · format · config (connectors.json) ·
|
|
3
|
-
// secrets (keychain / libsecret / 0600 file) · pairing · audit (hash chain) · notify · control ·
|
|
4
|
-
// service (what a runner host starts).
|
|
3
|
+
// secrets (keychain / libsecret / 0600 file) · pairing · audit (hash chain) · notify · control · readonly (task-1426:
|
|
4
|
+
// /check /top /signals) · approvals · hub · service (what a runner host starts).
|
|
5
5
|
export * from "./connector.js";
|
|
6
6
|
export * from "./fake.js";
|
|
7
7
|
export * from "./telegram.js";
|
|
@@ -13,6 +13,7 @@ export * from "./pairing.js";
|
|
|
13
13
|
export * from "./audit.js";
|
|
14
14
|
export * from "./notify.js";
|
|
15
15
|
export * from "./control.js";
|
|
16
|
+
export * from "./readonly.js";
|
|
16
17
|
export * from "./approvals.js";
|
|
17
18
|
export * from "./hub.js";
|
|
18
19
|
export * from "./service.js";
|