dexterm-cli 0.1.1 → 0.1.3
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/agents/src/bus.js +57 -6
- package/dist/packages/agents/src/executor.js +25 -0
- package/dist/packages/agents/src/follow.js +105 -0
- package/dist/packages/agents/src/gate.js +3 -0
- package/dist/packages/agents/src/identity.js +41 -2
- package/dist/packages/agents/src/index.js +5 -0
- package/dist/packages/agents/src/spectator-server.js +153 -0
- package/dist/packages/agents/src/spectator.js +212 -0
- package/dist/packages/agents/src/tiers.js +5 -0
- package/dist/packages/agents/src/tools.js +3 -0
- package/dist/packages/cli/assets/claude/analyst-system.md +6 -1
- package/dist/packages/cli/assets/claude/plugin/skills/larp-check/SKILL.md +11 -3
- package/dist/packages/cli/assets/claude/plugin/skills/rug-check/SKILL.md +13 -3
- package/dist/packages/cli/assets/claude/plugin/skills/token-dossier/SKILL.md +3 -3
- package/dist/packages/cli/src/agent-cmd.js +107 -10
- package/dist/packages/cli/src/agent-spectator.js +37 -0
- package/dist/packages/cli/src/alerts/adapters/desktop.js +40 -0
- package/dist/packages/cli/src/alerts/adapters/discord.js +36 -0
- package/dist/packages/cli/src/alerts/adapters/telegram.js +43 -0
- package/dist/packages/cli/src/alerts/adapters/toast.js +16 -0
- package/dist/packages/cli/src/alerts/creds.js +83 -0
- package/dist/packages/cli/src/alerts/format.js +66 -0
- package/dist/packages/cli/src/alerts/index.js +10 -0
- package/dist/packages/cli/src/alerts/notifier.js +53 -0
- package/dist/packages/cli/src/alerts/route.js +19 -0
- package/dist/packages/cli/src/alerts/types.js +1 -0
- package/dist/packages/cli/src/alerts-cmd.js +212 -0
- package/dist/packages/cli/src/app-help-gen.js +365 -0
- package/dist/packages/cli/src/backtest-cmd.js +62 -3
- package/dist/packages/cli/src/coin-target.js +2 -0
- package/dist/packages/cli/src/doctor.js +21 -6
- package/dist/packages/cli/src/export.js +235 -0
- package/dist/packages/cli/src/follow-cmd.js +107 -1
- package/dist/packages/cli/src/help-cmd.js +48 -0
- package/dist/packages/cli/src/live/index.js +66 -3
- package/dist/packages/cli/src/live/screens/token-live.js +24 -0
- package/dist/packages/cli/src/live/signals.js +109 -0
- package/dist/packages/cli/src/live/trade.js +24 -1
- package/dist/packages/cli/src/main.js +596 -49
- package/dist/packages/cli/src/mcp-cmd.js +321 -0
- package/dist/packages/cli/src/pnl-app.js +221 -0
- package/dist/packages/cli/src/pnl-card.js +615 -0
- package/dist/packages/cli/src/record-cmd.js +455 -0
- package/dist/packages/cli/src/rules.js +69 -2
- package/dist/packages/cli/src/screens/follows.js +91 -2
- package/dist/packages/cli/src/screens/overlay.js +3 -0
- package/dist/packages/cli/src/screens/positions.js +1 -1
- package/dist/packages/cli/src/screens/rules.js +1 -1
- package/dist/packages/cli/src/skill-eval-build.js +109 -0
- package/dist/packages/cli/src/spectate-cmd.js +104 -0
- package/dist/packages/cli/src/state.js +1 -1
- package/dist/packages/cli/src/update.js +294 -0
- package/dist/packages/cli/src/version.js +3 -0
- package/dist/packages/cli/src/voice-cmd.js +194 -0
- package/dist/packages/cli/src/voice-hud.js +1 -1
- package/dist/packages/cli/src/watch-cmd.js +190 -0
- package/dist/packages/custom/src/rules.js +3 -2
- package/dist/packages/custom/src/schema.js +24 -1
- package/dist/packages/data/src/analyst-actions.js +130 -3
- package/dist/packages/data/src/coin-target.js +82 -0
- package/dist/packages/data/src/fast-lane.js +16 -2
- package/dist/packages/data/src/index.js +4 -0
- package/dist/packages/data/src/intel/helius-history.js +12 -1
- package/dist/packages/data/src/intel/live-signals.js +185 -0
- package/dist/packages/data/src/larp/link-baseline.js +196 -0
- package/dist/packages/data/src/sources/dexscreener.js +14 -0
- package/dist/packages/data/src/sources/pumpportal.js +6 -1
- package/dist/packages/data/src/trade/copy/index.js +1 -1
- package/dist/packages/data/src/trade/copy/stats.js +36 -2
- package/dist/packages/data/src/trade/copy/tracker.js +3 -2
- package/dist/packages/mcp/assets/kb/app-help/app-help-command.md +35 -0
- package/dist/packages/mcp/assets/kb/app-help/ask-the-analyst.md +43 -0
- package/dist/packages/mcp/assets/kb/app-help/backtest.md +33 -0
- package/dist/packages/mcp/assets/kb/app-help/bubble-map.md +35 -0
- package/dist/packages/mcp/assets/kb/app-help/calm-mode.md +31 -0
- package/dist/packages/mcp/assets/kb/app-help/change-voice-speed.md +31 -0
- package/dist/packages/mcp/assets/kb/app-help/change-voice.md +30 -0
- package/dist/packages/mcp/assets/kb/app-help/chart-popup-selftest.md +32 -0
- package/dist/packages/mcp/assets/kb/app-help/coin-images.md +29 -0
- package/dist/packages/mcp/assets/kb/app-help/configure-rpc-key.md +28 -0
- package/dist/packages/mcp/assets/kb/app-help/connect-an-agent-registry.md +35 -0
- package/dist/packages/mcp/assets/kb/app-help/connect-an-agent.md +39 -0
- package/dist/packages/mcp/assets/kb/app-help/custom-keymap.md +32 -0
- package/dist/packages/mcp/assets/kb/app-help/edit-kb-or-skill.md +36 -0
- package/dist/packages/mcp/assets/kb/app-help/edit-rules.md +34 -0
- package/dist/packages/mcp/assets/kb/app-help/export-a-view.md +36 -0
- package/dist/packages/mcp/assets/kb/app-help/follow-a-wallet.md +33 -0
- package/dist/packages/mcp/assets/kb/app-help/getting-started.md +37 -0
- package/dist/packages/mcp/assets/kb/app-help/kol-lists.md +34 -0
- package/dist/packages/mcp/assets/kb/app-help/larp-check.md +36 -0
- package/dist/packages/mcp/assets/kb/app-help/link-a-wallet.md +41 -0
- package/dist/packages/mcp/assets/kb/app-help/login-with-x.md +33 -0
- package/dist/packages/mcp/assets/kb/app-help/narrative-board.md +32 -0
- package/dist/packages/mcp/assets/kb/app-help/open-a-coin.md +43 -0
- package/dist/packages/mcp/assets/kb/app-help/open-chart-popup.md +33 -0
- package/dist/packages/mcp/assets/kb/app-help/order-plans.md +33 -0
- package/dist/packages/mcp/assets/kb/app-help/paper-trade.md +36 -0
- package/dist/packages/mcp/assets/kb/app-help/price-or-marketcap.md +38 -0
- package/dist/packages/mcp/assets/kb/app-help/record-headless.md +34 -0
- package/dist/packages/mcp/assets/kb/app-help/recorder-history.md +39 -0
- package/dist/packages/mcp/assets/kb/app-help/rug-check.md +39 -0
- package/dist/packages/mcp/assets/kb/app-help/run-doctor.md +32 -0
- package/dist/packages/mcp/assets/kb/app-help/scan-mode.md +37 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-analyst.md +181 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-kol.md +190 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-newpairs.md +181 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-positions.md +180 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-screener.md +199 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-token.md +240 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-trending.md +192 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-watchlist.md +181 -0
- package/dist/packages/mcp/assets/kb/app-help/set-a-filter.md +36 -0
- package/dist/packages/mcp/assets/kb/app-help/setup-neural-voice.md +36 -0
- package/dist/packages/mcp/assets/kb/app-help/share-packs.md +34 -0
- package/dist/packages/mcp/assets/kb/app-help/sort-the-screener.md +34 -0
- package/dist/packages/mcp/assets/kb/app-help/spectate-an-agent.md +36 -0
- package/dist/packages/mcp/assets/kb/app-help/standing-mandates.md +33 -0
- package/dist/packages/mcp/assets/kb/app-help/switch-theme.md +42 -0
- package/dist/packages/mcp/assets/kb/app-help/turn-on-alerts.md +37 -0
- package/dist/packages/mcp/assets/kb/app-help/update-dexterm.md +30 -0
- package/dist/packages/mcp/assets/kb/app-help/use-presets.md +42 -0
- package/dist/packages/mcp/assets/kb/app-help/use-voice-mode.md +36 -0
- package/dist/packages/mcp/assets/kb/app-help/watch-a-coin.md +34 -0
- package/dist/packages/mcp/assets/kb/app-help/watch-headless.md +35 -0
- package/dist/packages/mcp/assets/kb/app-help/what-dexterm-shows-agents.md +31 -0
- package/dist/packages/mcp/assets/kb/app-help/workspaces.md +38 -0
- package/dist/packages/mcp/src/app-help.js +135 -0
- package/dist/packages/mcp/src/index.js +3 -0
- package/dist/packages/mcp/src/server.js +179 -5
- package/dist/packages/mcp/src/spectator-sink.js +72 -0
- package/dist/packages/packs/src/index.js +3 -0
- package/dist/packages/packs/src/skill-backtest.js +300 -0
- package/dist/packages/packs/src/skill-care.js +206 -0
- package/dist/packages/popup/src/pages/spectator.css +45 -0
- package/dist/packages/popup/src/pages/spectator.html +25 -0
- package/dist/packages/popup/src/pages/spectator.js +74 -0
- package/dist/packages/popup/src/selftest.js +19 -0
- package/dist/packages/popup/src/server.js +7 -1
- package/dist/packages/tui/src/index.js +3 -0
- package/dist/packages/tui/src/keymap.js +12 -0
- package/dist/packages/tui/src/raster-font.js +195 -0
- package/dist/packages/tui/src/raster.js +213 -0
- package/dist/packages/voice/src/controller.js +57 -4
- package/dist/packages/voice/src/doctor.js +96 -11
- package/dist/packages/voice/src/download.js +184 -0
- package/dist/packages/voice/src/engines.js +55 -8
- package/dist/packages/voice/src/index.js +9 -3
- package/dist/packages/voice/src/speakable.js +111 -0
- package/dist/packages/voice/src/stt/whisper.js +14 -4
- package/dist/packages/voice/src/tts/espeak.js +131 -0
- package/dist/packages/voice/src/tts/piper-setup.js +229 -0
- package/dist/packages/voice/src/tts/piper.js +449 -0
- package/dist/packages/voice/src/tts/say.js +72 -35
- package/dist/packages/voice/src/tts/voices.js +209 -0
- package/package.json +5 -9
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
// dexterm — generator for the app-help knowledge base (task-1413).
|
|
2
|
+
//
|
|
3
|
+
// Builds the `@dexterm/mcp` app-help KB entries (assets/kb/app-help/*.md) from the app's OWN authoritative
|
|
4
|
+
// sources so the help text cannot drift from the app:
|
|
5
|
+
// - the keymap (packages/tui/src/keymap.ts: DEFAULT_KEYMAP) → the exact key for a task
|
|
6
|
+
// - the help overlay (packages/cli/src/screens/overlay.ts: helpRows) → per-screen key lists
|
|
7
|
+
// - the CLI (packages/cli/src/main.ts: subcommands + flags) → the exact command
|
|
8
|
+
// - the MCP tool list (packages/mcp/src/server.ts: TOOLS) → the exact tool names
|
|
9
|
+
// The prose (titles, bodies, synonyms, which key/command a topic uses) is a committed TOPICS table below.
|
|
10
|
+
// The STRUCTURE (resolved keys, the tool/command/flag sets) is read from the app at generate time, so a
|
|
11
|
+
// re-run after a keymap/tool change rewrites the entries and the drift test (regenerate-and-diff, plus the
|
|
12
|
+
// bidirectional structural checks) fails until they are regenerated.
|
|
13
|
+
//
|
|
14
|
+
// Deterministic by construction: no clock, no network, no filesystem writes here (the script does that).
|
|
15
|
+
// `GENERATED_DATE` is a constant so regenerate-and-diff is stable; bump it when regenerating deliberately.
|
|
16
|
+
import fs from 'node:fs';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import { DEFAULT_KEYMAP } from '../../tui/src/index.js';
|
|
20
|
+
import { helpRows } from "./screens/overlay.js";
|
|
21
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
/** Repo root: packages/cli/src → up three. */
|
|
23
|
+
const ROOT = path.resolve(HERE, '..', '..', '..');
|
|
24
|
+
/** Stamped into every generated entry; bump on a deliberate regenerate. */
|
|
25
|
+
export const GENERATED_DATE = '2026-10-16';
|
|
26
|
+
// ---------------------------------------------------------------------------------------- app sources
|
|
27
|
+
/** Pretty single-key display, mirroring overlay.ts prettyKey for the handful of keys topics use. */
|
|
28
|
+
function prettyKey(id) {
|
|
29
|
+
const map = { enter: '⏎', escape: 'esc', tab: 'tab', space: 'space', up: '↑', down: '↓', left: '←', right: '→' };
|
|
30
|
+
if (map[id])
|
|
31
|
+
return map[id];
|
|
32
|
+
if (id.startsWith('ctrl+'))
|
|
33
|
+
return '^' + id.slice(5).toUpperCase().replace(/\+/g, ' ');
|
|
34
|
+
if (id.startsWith('alt+'))
|
|
35
|
+
return '⌥' + id.slice(4);
|
|
36
|
+
return id;
|
|
37
|
+
}
|
|
38
|
+
/** The first binding for an action (screen-specific preferred, else global); its canonical keys + display. */
|
|
39
|
+
function keyFor(action, screen) {
|
|
40
|
+
const bindings = DEFAULT_KEYMAP.bindings;
|
|
41
|
+
const pick = bindings.find((b) => b.action === action && (screen ? b.screen === screen : false))
|
|
42
|
+
?? bindings.find((b) => b.action === action && !b.screen)
|
|
43
|
+
?? bindings.find((b) => b.action === action);
|
|
44
|
+
if (!pick)
|
|
45
|
+
return { keys: [], display: '' };
|
|
46
|
+
return { keys: [...pick.keys], display: [...new Set(pick.keys.map(prettyKey))].join(' / ') };
|
|
47
|
+
}
|
|
48
|
+
/** Labeled actions + their canonical keys for a screen (every key that screen shows), from the keymap. */
|
|
49
|
+
function screenActions(screen) {
|
|
50
|
+
const actions = [];
|
|
51
|
+
const keys = new Set();
|
|
52
|
+
for (const b of DEFAULT_KEYMAP.forScreen(screen)) {
|
|
53
|
+
if (!b.label)
|
|
54
|
+
continue;
|
|
55
|
+
if (!actions.includes(b.action))
|
|
56
|
+
actions.push(b.action);
|
|
57
|
+
for (const k of b.keys)
|
|
58
|
+
keys.add(k);
|
|
59
|
+
}
|
|
60
|
+
return { actions, keys: [...keys] };
|
|
61
|
+
}
|
|
62
|
+
function read(rel) { return fs.readFileSync(path.join(ROOT, rel), 'utf8'); }
|
|
63
|
+
/** Every `dexterm <subcommand>` main() dispatches (same regex the site's facts.mjs uses). */
|
|
64
|
+
export function appSubcommands() {
|
|
65
|
+
const src = read('packages/cli/src/main.ts');
|
|
66
|
+
return [...new Set([...src.matchAll(/argv\[0\] === '([a-z]+)'/g)].map((m) => m[1]))];
|
|
67
|
+
}
|
|
68
|
+
/** Every `--flag` parseArgs() accepts (main flags; scan `--scan-*` are a separate table the site adds). */
|
|
69
|
+
export function appFlags() {
|
|
70
|
+
const src = read('packages/cli/src/main.ts');
|
|
71
|
+
const body = src.slice(src.indexOf('export function parseArgs'), src.indexOf('export function intensityFor'));
|
|
72
|
+
const flags = [];
|
|
73
|
+
for (const m of body.matchAll(/k === '(--?[a-z-]+)'/g))
|
|
74
|
+
if (!flags.includes(m[1]))
|
|
75
|
+
flags.push(m[1]);
|
|
76
|
+
return flags;
|
|
77
|
+
}
|
|
78
|
+
/** Every MCP tool the server registers (same regex the site's facts.mjs uses). */
|
|
79
|
+
export function appTools() {
|
|
80
|
+
const src = read('packages/mcp/src/server.ts');
|
|
81
|
+
return [...src.matchAll(/\{ name: '([a-z_]+)', description: (?:'((?:[^'\\]|\\.)*)'|"((?:[^"\\]|\\.)*)")/g)].map((m) => m[1]);
|
|
82
|
+
}
|
|
83
|
+
// ---------------------------------------------------------------------------------------- the content
|
|
84
|
+
/** Screens that get a "what it shows + its keys" entry. */
|
|
85
|
+
const SCREENS = [
|
|
86
|
+
{ screen: 'screener', title: 'The screener screen', feature: 'screener', tools: ['screener_query'],
|
|
87
|
+
blurb: 'The live table of Solana tokens (DexScreener + Jupiter, refreshed every ~10 s): price, 5m/1h/6h/24h change, volume, liquidity, market cap, age, holders, organic score, flags and a sparkline, with a p(rug) column. Cells whose source is down show a dash and the caption says why — never a fabricated number.' },
|
|
88
|
+
{ screen: 'token', title: 'The token screen', feature: 'token-intel', tools: ['get_dossier', 'get_token', 'get_holders', 'get_trades', 'get_candles', 'get_dev_history', 'rugcheck'],
|
|
89
|
+
blurb: 'One coin in depth: market stats, a candle chart, the trades tape, mint/freeze authority, top holders with their class (pool/dev/linked/…), RugCheck + GoPlus, snipers/bundled from the wallet-intel scan, and the ANALYST panel. Open it with ⏎ on any live row or `--mint <CA|link>`.' },
|
|
90
|
+
{ screen: 'newpairs', title: 'The new pairs screen', feature: 'new-pairs', tools: ['screener_query'],
|
|
91
|
+
blurb: 'A live feed of just-launched tokens (PumpPortal new-token + migration push, plus Jupiter/RugCheck/DexScreener polls). Bonding progress is a create-time estimate and says so; SNIPE/BUNDLE columns fill in from the wallet-intel scan of the row the cursor rests on (Helius-keyed).' },
|
|
92
|
+
{ screen: 'watchlist', title: 'The watchlist screen', feature: 'watchlist', tools: [],
|
|
93
|
+
blurb: 'The coins you watch, persisted in SQLite, priced live every ~10 s with a LIVE/POLL badge. Add with `w` on any coin; remove with `d`.' },
|
|
94
|
+
{ screen: 'analyst', title: 'The analyst screen', feature: 'analyst', tools: ['get_dossier', 'kb_search', 'app_help'],
|
|
95
|
+
blurb: 'Your own `claude` CLI as the dexterm analyst: it reads the read-only dexterm MCP tools, grounds every fact in a tool result, and answers in a few terse lines. ⏎ on a coin runs a full dossier; `a` runs a rug-check. Facts never come from model memory; token text passes the prompt firewall.' },
|
|
96
|
+
{ screen: 'positions', title: 'The paper positions screen', feature: 'positions', tools: [],
|
|
97
|
+
blurb: 'Your simulated (PAPER) positions and their P&L. dexterm holds no keys and signs nothing; paper trading is a simulation for learning the flow.' },
|
|
98
|
+
{ screen: 'trending', title: 'The trending screen', feature: 'trending-board', tools: ['narrative_board'],
|
|
99
|
+
blurb: 'What is trending in the space — narratives and movers aggregated from the feed.' },
|
|
100
|
+
{ screen: 'kol', title: 'The KOL lists screen', feature: 'kol-lists', tools: [],
|
|
101
|
+
blurb: 'Key-opinion-leader wallet lists and their activity (official/authorized sources only).' },
|
|
102
|
+
];
|
|
103
|
+
/** The screen keys that get an overview entry (for the drift test's forward key-coverage check). */
|
|
104
|
+
export const SCREENS_DOCUMENTED = SCREENS.map((s) => s.screen);
|
|
105
|
+
/**
|
|
106
|
+
* Flags that are deliberately NOT given a "how do I" entry (task-1413 r2): internal / render / replay /
|
|
107
|
+
* debug knobs a normal user never reaches for, so an absent answer for them is correct, not a drift bug.
|
|
108
|
+
* The forward-flag drift test requires every OTHER accepted flag to be named by some app-help entry.
|
|
109
|
+
* Keep this list exhaustive and honest — a user-facing flag must earn a topic, not hide here.
|
|
110
|
+
*/
|
|
111
|
+
export const INTERNAL_FLAGS = new Set([
|
|
112
|
+
'--max-requests', '--max-credits', // request/credit budget limiters (data layer tuning)
|
|
113
|
+
'--listen', '--db', // bus listen address + sqlite path overrides
|
|
114
|
+
'--verbose', // extra diagnostic logging
|
|
115
|
+
'--width', '--height', '--html', '--selected', '--title', // screenshot/export renderer internals
|
|
116
|
+
'--seed', '--tz', '--color', // determinism + rendering overrides for tests/screenshots
|
|
117
|
+
'--fps', '--frame', '--t', '--event', '--ai', '--mood', '--activity', '--no-boot', // demo/replay/render scripting
|
|
118
|
+
'--fit', '--font', '--ascii-rain', // popup / render tuning
|
|
119
|
+
'--plain', '--ansi', // output-format overrides (CI / piping)
|
|
120
|
+
]);
|
|
121
|
+
const TOPICS = [
|
|
122
|
+
{ id: 'getting-started', title: 'Get started with dexterm', screen: 'all', tags: ['start', 'install', 'first', 'run', 'setup', 'begin'],
|
|
123
|
+
commands: ['doctor'], flags: ['--fixtures', '--calm', '--version', '-h', '--help'], feature: 'doctor',
|
|
124
|
+
body: 'Run `dexterm doctor` first: it checks Node, terminal colour, the claude CLI, each data source, the Helius key and a writable data dir, and prints the exact fix for anything not passing. Then run `dexterm` — the first launch shows a one-screen welcome; press any key to enter the live screener. Press `?` on any screen for that screen\'s full key list. Try `--fixtures` for an offline demo. `--version` prints the version; `--help` (or `-h`) prints usage.' },
|
|
125
|
+
{ id: 'open-a-coin', title: 'Open / paste a coin (contract address or link)', screen: 'all', tags: ['paste', 'open', 'coin', 'contract', 'address', 'ca', 'link', 'pump', 'dexscreener', 'solscan', 'go', 'goto'],
|
|
126
|
+
actions: [{ action: 'goto.open', screen: 'screener' }], commands: [], flags: ['--mint'], tools: ['open_token'], feature: 'go-to-coin',
|
|
127
|
+
body: 'Press `g` to open the go-to prompt and paste a contract address — or a dexscreener / pump.fun / solscan / birdeye link — then ⏎ to open that coin\'s live token screen, read-only. A CA or link also works in the analyst chat and the screener `/` line. One-shot: `--mint <CA|link>`. Nothing here trades.' },
|
|
128
|
+
{ id: 'export-a-view', title: 'Export or share the current view', screen: 'all', tags: ['export', 'share', 'screenshot', 'png', 'report', 'save', 'image'],
|
|
129
|
+
actions: [{ action: 'export.view' }], flags: ['--export'], feature: 'export-share',
|
|
130
|
+
body: 'Press `E` on any screen to write the current view to `~/.dexterm/exports/` as a PNG and a plain-text report (the toast names the file). The PNG is rendered calm with a data-source label (LIVE / SAMPLE / REPLAY) and the age of every panel. Nothing is uploaded or posted. One-shot: `--export [DIR]`.' },
|
|
131
|
+
{ id: 'change-voice-speed', title: 'Change the voice speed (rate)', screen: 'all', tags: ['voice', 'speed', 'rate', 'faster', 'slower', 'wpm'],
|
|
132
|
+
commands: ['voice'], feature: 'jarvis',
|
|
133
|
+
body: 'Voice speed is `voice.rate` in `~/.dexterm/config.json` (words per minute, 80–400). Edit it there and restart. Voice settings are yours alone: the analyst/agent CANNOT change them (the voice path is denied to models) — only you set them. `dexterm voice status` shows the current voice engines.' },
|
|
134
|
+
{ id: 'change-voice', title: 'Change which voice speaks', screen: 'all', tags: ['voice', 'choose', 'speak', 'say', 'name'],
|
|
135
|
+
commands: ['voice'], feature: 'jarvis',
|
|
136
|
+
body: 'The default speaking voice is the neural Piper voice once it is set up (`dexterm voice setup`), with the built-in macOS `say` as the fallback. Press `,` to cycle the `say` voice while running, or set `voice.name` in `~/.dexterm/config.json` (that chooses the `say` fallback voice; Piper has one fixed voice). Nothing is sent to a paid API. Voice is user-only: the analyst/agent cannot change it (the voice path is denied to models) — only the keyboard or your config sets it.' },
|
|
137
|
+
{ id: 'setup-neural-voice', title: 'Set up the neural voice (make it sound better)', screen: 'all', tags: ['voice', 'neural', 'piper', 'better', 'quality', 'natural', 'realistic', 'nicer', 'improve', 'install', 'alan'],
|
|
138
|
+
commands: ['voice'], feature: 'jarvis',
|
|
139
|
+
body: 'dexterm can speak with a neural British voice (Piper, en_GB-alan-medium) that sounds markedly more natural than the built-in system voices. Run `dexterm voice setup` to install it: it asks first, builds a private local Python runtime and downloads a ~61 MB checksum-verified voice from the official source, then runs fully offline with no key. Once installed it becomes the DEFAULT speaking voice automatically (the system voice stays as the fallback); `dexterm voice uninstall` removes it. It needs Python 3.9+ — `dexterm doctor` prints the exact fix if it is missing, and shows which engine is speaking.' },
|
|
140
|
+
{ id: 'use-voice-mode', title: 'Use voice mode (Jarvis, push-to-talk)', screen: 'all', tags: ['voice', 'jarvis', 'talk', 'mic', 'microphone', 'push', 'listen'],
|
|
141
|
+
actions: [{ action: 'voice.talk' }], flags: ['--sound'], feature: 'jarvis',
|
|
142
|
+
body: 'Press `v` to start push-to-talk: speak, press `v` again to send (a red ● LISTENING badge shows while the mic is live; it also auto-closes after 15 s). Instant commands ("show the runners", "open bonk", "filter liquidity over 50k") run locally; questions go to `claude`. Voice only PROPOSES trades — it can never confirm, sign, arm or raise a cap. macOS only; every voice action also has a key. `--sound on|off` sets the sound/voice output at launch.' },
|
|
143
|
+
{ id: 'turn-on-alerts', title: 'Turn on alerts (Telegram / Discord / desktop)', screen: 'all', tags: ['alerts', 'alert', 'notify', 'notification', 'telegram', 'discord', 'desktop', 'channel'],
|
|
144
|
+
actions: [{ action: 'alert.propose', screen: 'screener' }], commands: ['alerts'], feature: 'push-alerts',
|
|
145
|
+
body: 'Use `dexterm alerts` to see channels and enable them; `dexterm alerts set-telegram` / `set-discord` store the bot token / webhook in `~/.dexterm/` (mode 0600) — never in config.json, argv or a log. In-app, `b` proposes a liquidity alert on the highlighted coin and `y` arms it. The analyst can never change alert channels or see their secrets.' },
|
|
146
|
+
{ id: 'set-a-filter', title: 'Filter the screener', screen: 'screener', tags: ['filter', 'screen', 'query', 'search', 'grammar', 'volume', 'liquidity'],
|
|
147
|
+
actions: [{ action: 'filter.edit', screen: 'screener' }], flags: ['--filter'], feature: 'filter-grammar',
|
|
148
|
+
body: 'Press `/` to open the filter editor and type the grammar, e.g. `vol24 > 250K liq > 50K age < 30d org >= 60 launchpad:pump !flag:mint`. Errors show inline with the column. One-shot: `--filter "…"`. The analyst can set a filter for you (it shows "analyst did X").' },
|
|
149
|
+
{ id: 'sort-the-screener', title: 'Sort the screener', screen: 'screener', tags: ['sort', 'order', 'rank', 'column', 'menu'],
|
|
150
|
+
actions: [{ action: 'sort.menu', screen: 'screener' }], flags: ['--sort'], feature: 'filter-grammar',
|
|
151
|
+
body: 'Press `s` for the sort menu: j/k pick a key, ⏎ on the current key flips the direction (ascending/descending). One-shot at launch: `--sort <key>`.' },
|
|
152
|
+
{ id: 'use-presets', title: 'Use screener presets', screen: 'screener', tags: ['preset', 'presets', 'trending', 'runners', 'organic', 'graduating', 'smart'],
|
|
153
|
+
actions: [{ action: 'preset.next', screen: 'screener' }, { action: 'preset.prev', screen: 'screener' }], flags: ['--preset'], feature: 'presets',
|
|
154
|
+
body: 'Press `]` / `[` to step through presets (Trending, Runners, Organic; Graduating ships keyless as an `approx` mcap-band proxy; Smart money needs a key and says so). The analyst can switch the feed too. One-shot at launch: `--preset <name>`.' },
|
|
155
|
+
{ id: 'watch-a-coin', title: 'Watch (or unwatch) a coin', screen: 'all', tags: ['watch', 'watchlist', 'follow', 'track', 'token', 'tokens', 'coins'],
|
|
156
|
+
actions: [{ action: 'watch.add', screen: 'screener' }], feature: 'watchlist',
|
|
157
|
+
body: 'Press `w` to watch/unwatch the highlighted coin (screener, new pairs, token screen). The watchlist is persisted and priced live. On the watchlist screen `d` removes a row.' },
|
|
158
|
+
{ id: 'open-chart-popup', title: 'Open the chart popup', screen: 'token', tags: ['chart', 'popup', 'candles', 'window', 'dock', 'graph'],
|
|
159
|
+
actions: [{ action: 'popup.chart', screen: 'token' }], feature: 'popup-chart',
|
|
160
|
+
body: 'Press `C` on the token screen to open the docked chart popup (a native window, or your browser as fallback). It is fed only by the terminal\'s caches over one loopback WebSocket and never sees your keys. The analyst can open or close it for you.' },
|
|
161
|
+
{ id: 'switch-theme', title: 'Switch the theme', screen: 'all', tags: ['theme', 'themes', 'colour', 'colours', 'color', 'colors', 'look', 'cyberspace', 'midnight', 'ember', 'paper'],
|
|
162
|
+
actions: [{ action: 'theme.next' }], commands: ['theme'], flags: ['--theme'], feature: 'themes-keymaps',
|
|
163
|
+
body: 'Press `t` to cycle themes (cyberspace → midnight → ember → paper). `dexterm theme list|show|init|check` manages your own themes in `~/.dexterm/themes/`; a shared theme cannot hide a warning or swap buy/sell colours (a contrast + hue guard). The analyst can propose a theme change you confirm with a keypress.' },
|
|
164
|
+
{ id: 'calm-mode', title: 'Turn on calm mode (effects off)', screen: 'all', tags: ['calm', 'effects', 'rain', 'glitch', 'quiet', 'still', 'intensity'],
|
|
165
|
+
flags: ['--calm', '--intensity'], feature: 'look',
|
|
166
|
+
body: 'Press `c` to toggle calm mode: rain, glitch, blink and scanlines off, back to the last intensity when pressed again. Start calm with `--calm` (or `DEXTERM_CALM=1`); `--intensity normal|fever|off` picks a tier. The analyst can toggle calm for you.' },
|
|
167
|
+
{ id: 'price-or-marketcap', title: 'Show price or market cap', screen: 'all', tags: ['price', 'mcap', 'marketcap', 'market', 'display', 'usd', 'cap'],
|
|
168
|
+
actions: [{ action: 'display.priceMode' }], commands: ['display'], flags: ['--price-mode'], feature: 'price-display-mode',
|
|
169
|
+
body: 'Press `$` to flip every price between USD price and MARKET CAP (screener, watchlist, new pairs, token, both charts, order-plan levels). Saved as `display.priceMode`; also `dexterm display price|mcap` or `--price-mode`. A coin with no known supply stays on price and says so. The analyst can set this for you.' },
|
|
170
|
+
{ id: 'scan-mode', title: 'Use AI scan mode', screen: 'all', tags: ['scan', 'watcher', 'observe', 'auto', 'ai', 'monitor'],
|
|
171
|
+
actions: [{ action: 'scan.toggle' }, { action: 'scan.resume' }], flags: ['--scan'], feature: 'scan',
|
|
172
|
+
body: 'Scan mode is an observe-only AI watcher (off by default, zero extra requests). Toggle it with `Z` (or `/scan on|off` on the screener line, or `--scan` at launch); `R` resumes a paused scan. It can only surface findings, never take a UI action or trade (structurally observe-only). The analyst can turn it on or off for you.' },
|
|
173
|
+
{ id: 'larp-check', title: 'Run the LARP check (is the product real?)', screen: 'token', tags: ['larp', 'fake', 'real', 'legit', 'website', 'product', 'domain', 'github', 'vet'],
|
|
174
|
+
tools: ['larp_check', 'get_links', 'fetch_page', 'check_domain', 'check_github'], feature: 'larp-check',
|
|
175
|
+
body: 'The LARP check vets whether a real product exists (website, domain age, GitHub) read-only — it is not a price call. Ask the analyst "is <coin> a larp?" (skill `larp-check`), or an agent can call the `larp_check` MCP tool. A grade is a risk flag, never a certificate; it never recommends buy/sell.' },
|
|
176
|
+
{ id: 'rug-check', title: 'Check if a coin is a rug', screen: 'token', tags: ['rug', 'safe', 'honeypot', 'scam', 'risk', 'check', 'authority', 'mint'],
|
|
177
|
+
actions: [{ action: 'analyst.toggle' }], tools: ['analyze_token', 'get_dossier', 'rugcheck'], feature: 'analyst',
|
|
178
|
+
body: 'Press `a` to run a rug-check on the selected coin: authorities, token program, LP, holder concentration, creator history and tape, with a p(rug) from the H1 heuristic (a ranking, not a calibrated probability). The verdict is WATCH / SPECULATIVE / AVOID. Facts come only from tools; it is not financial advice.' },
|
|
179
|
+
{ id: 'ask-the-analyst', title: 'Ask the analyst a question', screen: 'analyst', tags: ['analyst', 'ask', 'claude', 'ai', 'question', 'dossier', 'chat'],
|
|
180
|
+
actions: [{ action: 'analyst.toggle' }, { action: 'open', screen: 'screener' }], tools: ['get_dossier', 'kb_search', 'app_help', 'case_lookup'], feature: 'analyst',
|
|
181
|
+
flags: ['--no-ai'],
|
|
182
|
+
body: 'The analyst is your own `claude` login. ⏎ on a coin runs a full dossier; `a` runs a rug-check (or a trench-scan of the screener when nothing is selected). Type a follow-up and ⏎ to send. If `claude` is missing or logged out the panel shows the one-line fix and the rest of dexterm keeps working. Start without AI using `--no-ai`.' },
|
|
183
|
+
{ id: 'connect-an-agent', title: 'Connect your own agent (MCP)', screen: 'all', tags: ['mcp', 'agent', 'agents', 'connect', 'claude', 'tool', 'server', 'bot'],
|
|
184
|
+
commands: ['mcp'], tools: ['screen_state', 'get_dossier', 'kb_search', 'app_help', 'app_state'], feature: 'mcp',
|
|
185
|
+
body: '`dexterm mcp` is a plain stdio MCP server exposing the same read-only tools the analyst uses. Add it to any Claude Code with `claude mcp add dexterm -- node packages/mcp/bin/dexterm-mcp.mjs`. Agents are untrusted: every tool is read-only and an agent can never sign, arm, raise a cap or touch keys. Use `app_help` for "how do I" questions and `app_state` to see what the user sees.' },
|
|
186
|
+
{ id: 'configure-rpc-key', title: 'Configure a Helius / RPC key for holders', screen: 'all', tags: ['helius', 'rpc', 'key', 'holders', 'free', 'api', 'ranked'],
|
|
187
|
+
feature: 'keyless',
|
|
188
|
+
body: 'dexterm is free-forever and keyless by default. The one real keyless gap is the ranked top-holder list: add a free RPC key for it. Save a Helius key ONLY at `~/.dexterm/helius.key` (mode 600, never a repo or command line), or set `DEXTERM_RPC_URL` to any read-only RPC. `dexterm doctor` prints the exact command. Without a key the holders panel shows count + top-10% via Jupiter.' },
|
|
189
|
+
{ id: 'workspaces', title: 'Save and switch workspaces', screen: 'all', tags: ['workspace', 'workspaces', 'layout', 'hud', 'save', 'morning'],
|
|
190
|
+
actions: [{ action: 'workspace.save' }], commands: ['workspace'], flags: ['--workspace', '--screen'], feature: 'workspaces',
|
|
191
|
+
body: 'A workspace saves a start screen, theme, intensity, screener preset/filter/sort, token panels and the HUD popups to open together. `ctrl+w` then `s` saves as; `w` lists; `n`/`N` cycle. `dexterm workspace list|show|remove|init` manages them. The analyst can open a saved workspace for you.' },
|
|
192
|
+
{ id: 'custom-keymap', title: 'Customize the keymap', screen: 'all', tags: ['keymap', 'keybind', 'keybinding', 'rebind', 'shortcut', 'keys', 'remap'],
|
|
193
|
+
commands: ['keymap'], feature: 'themes-keymaps',
|
|
194
|
+
body: '`config.keymap` in `~/.dexterm/config.json` re-binds actions to keys; `dexterm keymap [screen]` shows the effective bindings including your overrides. Reserved keys (ctrl+c, esc, enter, y, !) and the confirm/dismiss/panic/quit actions cannot be rebound, and collisions refuse the whole keymap.' },
|
|
195
|
+
{ id: 'paper-trade', title: 'Paper trade a coin', screen: 'token', tags: ['paper', 'trade', 'buy', 'sell', 'ticket', 'simulate', 'practice'],
|
|
196
|
+
actions: [{ action: 'trade.buy', screen: 'token' }, { action: 'trade.sell', screen: 'token' }], feature: 'paper-ticket',
|
|
197
|
+
body: 'Press `B` / `S` on the token screen to open a PAPER buy/sell ticket in its editing phase. dexterm holds no keys and signs nothing — paper trading is a simulation. Confirming a ticket needs your explicit keypress (the gate); a stray key never confirms. The analyst can open a PAPER ticket but can never size, confirm or send it.' },
|
|
198
|
+
{ id: 'run-doctor', title: 'Diagnose problems with doctor', screen: 'all', tags: ['doctor', 'diagnose', 'check', 'troubleshoot', 'broken', 'fix', 'problem'],
|
|
199
|
+
commands: ['doctor'], feature: 'doctor',
|
|
200
|
+
body: '`dexterm doctor` is the first stop for any problem: non-interactive, never touches your keys, exits 0 when dexterm will run and 1 when something must be fixed. Every non-pass line prints the exact fix. `dexterm doctor --offline` skips the network probes.' },
|
|
201
|
+
{ id: 'update-dexterm', title: 'Update dexterm', screen: 'all', tags: ['update', 'upgrade', 'version', 'latest', 'new'],
|
|
202
|
+
commands: ['update'], feature: 'update-check',
|
|
203
|
+
body: '`dexterm update` updates the app; a daily notice tells you when a new version is out. The analyst can never install or update software.' },
|
|
204
|
+
{ id: 'edit-kb-or-skill', title: 'Edit the knowledge base or an analyst skill', screen: 'all', tags: ['kb', 'knowledge', 'skill', 'edit', 'customize', 'override', 'analyst'],
|
|
205
|
+
commands: ['kb', 'skill'], tools: ['kb_lookup', 'kb_search'], feature: 'kb',
|
|
206
|
+
body: 'Your own KB entries and skills live in `~/.dexterm/kb/entries/<id>.md` and `~/.dexterm/skills/<name>.md` (same frontmatter as the bundled ones). `dexterm kb|skill list|show|new|edit|check|reset|path`: `edit` copies the bundled file and opens your editor, `check` validates it, `reset` restores the bundled one. A skill is read-only guidance and may never name a trade/sign/arm tool.' },
|
|
207
|
+
{ id: 'backtest', title: 'Backtest the rug model or skills', screen: 'all', tags: ['backtest', 'eval', 'accuracy', 'model', 'metrics', 'history'],
|
|
208
|
+
commands: ['backtest'], tools: ['rug_backtest'], feature: 'skill-backtest',
|
|
209
|
+
body: '`dexterm backtest --subject heuristic|skills|larp|pack_verdicts` scores a subject against the recorder\'s outcome-labelled history and prints honest metrics (precision/recall, Brier vs base rate, AUC, calibration) with leakage checks. With no labelled outcomes it says so and prints no metrics rather than fabricating any.' },
|
|
210
|
+
{ id: 'coin-images', title: 'Turn coin images on or off', screen: 'all', tags: ['images', 'image', 'pictures', 'icons', 'logos', 'off'],
|
|
211
|
+
flags: ['--no-images'], feature: 'coin-images',
|
|
212
|
+
body: 'Coin pictures render as real pixels in image-capable terminals (kitty/iTerm2/sixel) and as block art everywhere else. Turn them off with `--no-images` (or `DEXTERM_IMAGES=0`, or `enabled:false` in `~/.dexterm/images.json`). `dexterm doctor` has a Coin images line that says which path you get.' },
|
|
213
|
+
{ id: 'bubble-map', title: 'Open the bubble map', screen: 'token', tags: ['bubble', 'bubbles', 'map', 'cluster', 'clusters', 'holders', 'wallets', 'cabal'],
|
|
214
|
+
actions: [{ action: 'intel.bubbles', screen: 'token' }], feature: 'bubble-map',
|
|
215
|
+
body: 'Press `M` on a token to open the BUBBLE MAP: top holders as discs (area = share of supply, colour = the rule that labelled the wallet), linked-cluster rings, a CLUSTERS table and a WHY THIS LABEL panel. Keyless it uses the public holder list with static labels; a free Helius key adds funders and the launch window.' },
|
|
216
|
+
{ id: 'what-dexterm-shows-agents', title: 'See what the user currently sees (app_state)', screen: 'all', tags: ['state', 'current', 'screen', 'context', 'selected', 'agent', 'see'],
|
|
217
|
+
tools: ['app_state', 'screen_state'], feature: 'mcp',
|
|
218
|
+
body: 'An agent can call the read-only `app_state` MCP tool to fit an answer to what the user sees: the current screen, the selected coin, the theme, price mode, voice name/rate, whether scan and calm are on, and which alert channels are configured (YES/NO only — never a token, webhook or key). `screen_state` adds the visible rows and the action allow-list.' },
|
|
219
|
+
// --- user-facing subcommand topics (task-1413 r2: forward subcommand/flag coverage)
|
|
220
|
+
{ id: 'app-help-command', title: 'Get help on how dexterm works (dexterm help)', screen: 'all', tags: ['help', 'how', 'topic', 'topics', 'guide', 'manual', 'docs', 'commands'],
|
|
221
|
+
commands: ['help'], flags: ['--help'], feature: 'app-help',
|
|
222
|
+
body: '`dexterm help [topic]` answers "how do I ..." from the generated app-help knowledge base — the same entries the analyst\'s `app_help` tool uses, so they cannot drift from the app. `dexterm help` lists the topics; `dexterm help <topic|key|command>` prints the matching entry with the exact key or command. `--help` (or `-h`) on any command prints its usage. The analyst searches app-help first and never invents a key.' },
|
|
223
|
+
{ id: 'connect-an-agent-registry', title: 'Manage connected agents (dexterm agent)', screen: 'all', tags: ['agent', 'agents', 'registry', 'tier', 'pause', 'resume', 'kick', 'audit', 'bus', 'serve'],
|
|
224
|
+
commands: ['agent'], feature: 'mcp',
|
|
225
|
+
body: '`dexterm agent add|list|rotate|remove|tier|pause|resume|kick|audit|packs` manages your bring-your-own-agent registry and the loopback bus; `dexterm agent serve` runs a standalone bus on fixtures and `connect` attaches one. Every agent is untrusted and read-only: it can never sign, arm, raise a cap, touch keys or change network settings.' },
|
|
226
|
+
{ id: 'spectate-an-agent', title: 'Watch an agent drive dexterm (dexterm spectate)', screen: 'all', tags: ['spectate', 'spectator', 'watch', 'agent', 'cook', 'observe', 'replay', 'stream', 'record'],
|
|
227
|
+
commands: ['spectate'], flags: ['--replay'], feature: 'spectator',
|
|
228
|
+
body: '`dexterm spectate --replay FILE` replays a saved agent session so you can watch what an agent did step by step; `dexterm spectate --info` shows the live read-only, token-gated, loopback spectator stream. The stream is redacted — a tool name, refusal, agent line or navigation can never leak a wallet or key — and a spectator can only watch, never act.' },
|
|
229
|
+
{ id: 'recorder-history', title: 'Work with the history recorder (dexterm recorder)', screen: 'all', tags: ['recorder', 'record', 'history', 'stats', 'label', 'prune', 'outcomes', 'database', 'session', 'capture', 'data'],
|
|
230
|
+
commands: ['recorder'], flags: ['--record', '--no-recorder'], feature: 'recorder',
|
|
231
|
+
body: '`dexterm recorder stats|label|prune|export` works with the local history recorder\'s tables (the outcome-labelled data the rug model and backtests learn from). No network. Recording is on by default; `--no-recorder` disables it for a run and `--record FILE` captures a data session to a file for replay.' },
|
|
232
|
+
{ id: 'record-headless', title: 'Keep the recorder running with the app closed (dexterm record)', screen: 'all', tags: ['collector', 'collect', 'background', 'headless', 'launchd', 'daemon', 'keep', 'closed', 'backtest'],
|
|
233
|
+
commands: ['record'], feature: 'recorder',
|
|
234
|
+
body: '`dexterm record` runs the history recorder with no terminal UI, so the data the backtests learn from keeps growing while dexterm is closed. `dexterm record status` shows what it has collected; `dexterm record install --dry-run` prints the user-level LaunchAgent it would create (macOS, no sudo) and `dexterm record uninstall` removes it. It uses the same free-tier request limits as the app, never trades, and a disk cap pauses new observations when the database gets large.' },
|
|
235
|
+
{ id: 'narrative-board', title: 'See the narrative board (dexterm narrative)', screen: 'trending', tags: ['narrative', 'narratives', 'board', 'trending', 'movers', 'window', 'summary'],
|
|
236
|
+
commands: ['narrative'], feature: 'trending-board',
|
|
237
|
+
body: '`dexterm narrative [--window 1h|24h] [--summary]` prints the narrative board — what is trending across the recorded feed — with no network and no model. In-app it is the trending screen.' },
|
|
238
|
+
{ id: 'kol-lists', title: 'Manage KOL wallet lists (dexterm kol)', screen: 'kol', tags: ['kol', 'kols', 'influencer', 'leader', 'list', 'lists', 'wallet', 'mute', 'import'],
|
|
239
|
+
commands: ['kol'], feature: 'kol-lists',
|
|
240
|
+
body: '`dexterm kol list|create|add|remove|mute|unmute|enable|disable|import` manages key-opinion-leader wallet lists on the shared DB (official / authorized sources only). In-app the KOL screen shows the lists and their activity.' },
|
|
241
|
+
{ id: 'login-with-x', title: 'Connect your X account (dexterm x)', screen: 'all', tags: ['x', 'twitter', 'login', 'oauth', 'account', 'social', 'connect', 'client'],
|
|
242
|
+
commands: ['x'], feature: 'login-with-x',
|
|
243
|
+
body: '`dexterm x status|set-client-id|login|logout` connects your X (Twitter) account via OAuth2 PKCE on a loopback callback. Tokens stay in `~/.dexterm/x` (0600), are never printed, and the scopes are read-only. It only improves social signal; nothing is posted.' },
|
|
244
|
+
{ id: 'edit-rules', title: 'Set up alert rules (dexterm rules)', screen: 'all', tags: ['rules', 'rule', 'trigger', 'condition', 'alert', 'add', 'dry-run', 'fields', 'engine'],
|
|
245
|
+
commands: ['rules'], feature: 'rules-engine',
|
|
246
|
+
body: '`dexterm rules list|add|remove|enable|disable|check|dry-run|fields` configures the rules engine — the conditions that raise an alert (e.g. liquidity jump, volume spike). `dry-run` tests a rule against recent data; `fields` lists what you can match on. No network, no model, and a rule can never trade. Deliver the alerts with `dexterm alerts` and `dexterm watch`.' },
|
|
247
|
+
{ id: 'watch-headless', title: 'Run alerts in the background (dexterm watch)', screen: 'all', tags: ['watch', 'background', 'headless', 'push', 'alerts', 'notify', 'daemon', 'interval'],
|
|
248
|
+
commands: ['watch'], flags: ['--interval'], feature: 'push-alerts',
|
|
249
|
+
body: '`dexterm watch [--interval S] [--once|--ticks N] [--replay FILE]` runs the rules engine headless and PUSHES alerts to your enabled channels, so they reach you when the terminal is closed. It has no signer and can never trade. Set channels up first with `dexterm alerts`.' },
|
|
250
|
+
{ id: 'share-packs', title: 'Share or install packs (dexterm pack)', screen: 'all', tags: ['pack', 'packs', 'share', 'install', 'export', 'sign', 'trust', 'lock', 'bundle'],
|
|
251
|
+
commands: ['pack'], feature: 'packs',
|
|
252
|
+
body: '`dexterm pack list|verify|add|remove|export|lock|sign|trust|signers|keymap|widget` shares themes, keymaps, rules and widgets as plain files with a sha256 lock and an optional ssh signature. A pack never runs code: widgets land disabled, unsigned rules load disabled, and a keymap merges only on the explicit command.' },
|
|
253
|
+
{ id: 'link-a-wallet', title: 'Link a wallet for trading (dexterm wallet)', screen: 'all', tags: ['wallet', 'link', 'unlink', 'phantom', 'connect', 'pubkey', 'caps', 'trade'],
|
|
254
|
+
commands: ['wallet'], flags: ['--mainnet', '--max-usd', '--daily-usd', '--max-sol-out', '--max-slippage-bps', '--trade-rpc', '--no-live'], feature: 'trading',
|
|
255
|
+
body: '`dexterm wallet link|unlink|status` links a Phantom wallet through a loopback bridge — your keys NEVER enter dexterm, only the public key is stored — and shows the link and the signer-side trade caps. dexterm is non-custodial: it builds and simulates transactions, your own wallet signs. The signer-side caps (`--max-usd`, `--daily-usd`, `--max-sol-out`, `--max-slippage-bps`) and the RPC (`--trade-rpc`) bound every trade and live on the signer; `--mainnet` opts in to mainnet and `--no-live` keeps it paper-only. The analyst can never link, unlink, sign, arm or change a cap.' },
|
|
256
|
+
{ id: 'order-plans', title: 'Set conditional order plans (dexterm plan)', screen: 'all', tags: ['plan', 'plans', 'order', 'conditional', 'take-profit', 'stop', 'trigger', 'arm'],
|
|
257
|
+
commands: ['plan'], feature: 'order-plans',
|
|
258
|
+
body: '`dexterm plan list|add|remove|arm|disarm` sets conditional order plans (e.g. take-profit / stop levels). Voice and agents can only PROPOSE a plan; ARMING one needs your physical keypress, and the caps and panic-stop stay signer-side. Nothing here lets a model sign or raise a cap.' },
|
|
259
|
+
{ id: 'standing-mandates', title: 'Set a standing mandate (dexterm mandate)', screen: 'all', tags: ['mandate', 'mandates', 'standing', 'rule', 'budget', 'auto', 'arm', 'cap'],
|
|
260
|
+
commands: ['mandate'], feature: 'order-plans',
|
|
261
|
+
body: '`dexterm mandate list|add|remove|arm|disarm` sets a standing mandate — a longer-lived conditional rule bounded by a budget. As with plans, a model may only PROPOSE; arming needs your physical keypress and the signer-side caps bound it. The analyst can never arm a mandate or change its caps.' },
|
|
262
|
+
{ id: 'follow-a-wallet', title: 'Copy / follow a wallet (dexterm follow)', screen: 'all', tags: ['follow', 'copy', 'mirror', 'wallet', 'trader', 'track', 'unfollow', 'list'],
|
|
263
|
+
commands: ['follow'], feature: 'copy-trading',
|
|
264
|
+
body: '`dexterm follow list|add|remove` is the copy-trading follow list: local, self-directed and read-only. When a followed wallet trades, dexterm can surface a one-key MIRROR TICKET you confirm yourself — it never auto-trades, never signs, and the analyst can never add a follow or confirm a mirror.' },
|
|
265
|
+
{ id: 'chart-popup-selftest', title: 'Run the popup self-test (dexterm popup --self-test)', screen: 'all', tags: ['self-test', 'selftest', 'test', 'shell', 'fps', 'diagnose', 'hotkey'],
|
|
266
|
+
commands: ['popup'], feature: 'popup-selftest',
|
|
267
|
+
body: '`dexterm popup --self-test [--browser] [--seconds N] [--theme NAME]` opens a chart popup on fixture data and reports the shell, hotkey and fps it can measure headlessly, labelling what still needs a human at an unlocked Mac. In-app, open the popup for real with `C` on the token screen (see "Open the chart popup").' },
|
|
268
|
+
];
|
|
269
|
+
function fm(order) {
|
|
270
|
+
const o = {};
|
|
271
|
+
for (const [k, v] of order)
|
|
272
|
+
if (v !== undefined && !(Array.isArray(v) && v.length === 0) && v !== '')
|
|
273
|
+
o[k] = v;
|
|
274
|
+
return o;
|
|
275
|
+
}
|
|
276
|
+
/** Build every app-help entry from the current app sources + the committed TOPICS/SCREENS tables. */
|
|
277
|
+
export function buildAppHelpEntries() {
|
|
278
|
+
const subcommands = new Set(appSubcommands());
|
|
279
|
+
const flags = new Set(appFlags());
|
|
280
|
+
const tools = new Set(appTools());
|
|
281
|
+
const out = [];
|
|
282
|
+
// --- per-screen entries
|
|
283
|
+
for (const s of SCREENS) {
|
|
284
|
+
const { actions, keys } = screenActions(s.screen);
|
|
285
|
+
const rows = helpRows(s.screen, false);
|
|
286
|
+
const keyList = rows.filter((r) => r.keys && r.label).map((r) => ` \`${r.keys}\` — ${r.label}`).join('\n');
|
|
287
|
+
const toolsUsed = s.tools.filter((t) => tools.has(t));
|
|
288
|
+
const body = `${s.blurb}\n\nKeys on this screen:\n${keyList}`;
|
|
289
|
+
out.push({
|
|
290
|
+
id: `screen-${s.screen}`,
|
|
291
|
+
frontmatter: fm([
|
|
292
|
+
['id', `screen-${s.screen}`], ['title', s.title], ['kind', 'app-help'], ['screen', s.screen],
|
|
293
|
+
['feature', s.feature], ['tags', ['app-help', 'screen', s.screen, ...s.title.toLowerCase().split(/\s+/)]],
|
|
294
|
+
['keys', keys], ['actions', actions], ['tools', toolsUsed],
|
|
295
|
+
['confidence', 'high'], ['owner', 'maintainers'], ['origin', 'app-help-generated'],
|
|
296
|
+
['reviewed_by', `generated@${GENERATED_DATE}`],
|
|
297
|
+
['generated_from', 'dexterm/app keymap + overlay + mcp tools (task-1413)'],
|
|
298
|
+
['sources', ['packages/tui/src/keymap.ts', 'packages/cli/src/screens/overlay.ts']],
|
|
299
|
+
['date', GENERATED_DATE],
|
|
300
|
+
]),
|
|
301
|
+
body,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
// --- topic entries
|
|
305
|
+
for (const t of TOPICS) {
|
|
306
|
+
const keys = [];
|
|
307
|
+
const actions = [];
|
|
308
|
+
const keyLines = [];
|
|
309
|
+
for (const a of t.actions ?? []) {
|
|
310
|
+
const k = keyFor(a.action, a.screen);
|
|
311
|
+
if (!actions.includes(a.action))
|
|
312
|
+
actions.push(a.action);
|
|
313
|
+
for (const kk of k.keys)
|
|
314
|
+
if (!keys.includes(kk))
|
|
315
|
+
keys.push(kk);
|
|
316
|
+
if (k.display)
|
|
317
|
+
keyLines.push(`\`${k.display}\`${a.screen ? ` (${a.screen})` : ''}`);
|
|
318
|
+
}
|
|
319
|
+
// Validate referenced commands/flags/tools exist in the app (fail the generator loudly if not).
|
|
320
|
+
const cmds = (t.commands ?? []).filter((c) => { if (!subcommands.has(c))
|
|
321
|
+
throw new Error(`app-help topic ${t.id}: unknown subcommand "${c}"`); return true; });
|
|
322
|
+
const fls = (t.flags ?? []).filter((f) => { if (!flags.has(f))
|
|
323
|
+
throw new Error(`app-help topic ${t.id}: unknown flag "${f}"`); return true; });
|
|
324
|
+
const tls = (t.tools ?? []).filter((x) => { if (!tools.has(x))
|
|
325
|
+
throw new Error(`app-help topic ${t.id}: unknown MCP tool "${x}"`); return true; });
|
|
326
|
+
const trailer = [];
|
|
327
|
+
if (keyLines.length)
|
|
328
|
+
trailer.push(`Keys: ${keyLines.join(', ')}.`);
|
|
329
|
+
if (cmds.length)
|
|
330
|
+
trailer.push(`CLI: ${cmds.map((c) => `\`dexterm ${c}\``).join(', ')}.`);
|
|
331
|
+
const body = trailer.length ? `${t.body}\n\n${trailer.join(' ')}` : t.body;
|
|
332
|
+
out.push({
|
|
333
|
+
id: t.id,
|
|
334
|
+
frontmatter: fm([
|
|
335
|
+
['id', t.id], ['title', t.title], ['kind', 'app-help'], ['screen', t.screen],
|
|
336
|
+
['feature', t.feature ?? ''], ['tags', ['app-help', ...t.tags]],
|
|
337
|
+
['keys', keys], ['actions', actions], ['commands', cmds], ['flags', fls], ['tools', tls],
|
|
338
|
+
['confidence', 'high'], ['owner', 'maintainers'], ['origin', 'app-help-generated'],
|
|
339
|
+
['reviewed_by', `generated@${GENERATED_DATE}`],
|
|
340
|
+
['generated_from', 'dexterm/app keymap + overlay + mcp tools + cli (task-1413)'],
|
|
341
|
+
['sources', ['packages/tui/src/keymap.ts', 'packages/cli/src/main.ts', 'packages/mcp/src/server.ts']],
|
|
342
|
+
['date', GENERATED_DATE],
|
|
343
|
+
]),
|
|
344
|
+
body,
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
out.sort((a, b) => a.id.localeCompare(b.id));
|
|
348
|
+
return out;
|
|
349
|
+
}
|
|
350
|
+
/** Serialize an entry to the bundled `.md` format the loader parses (block lists, deterministic order). */
|
|
351
|
+
export function serializeEntry(e) {
|
|
352
|
+
const lines = ['---'];
|
|
353
|
+
for (const [k, v] of Object.entries(e.frontmatter)) {
|
|
354
|
+
if (Array.isArray(v)) {
|
|
355
|
+
lines.push(`${k}:`);
|
|
356
|
+
for (const item of v)
|
|
357
|
+
lines.push(` - ${item}`);
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
lines.push(`${k}: ${v}`);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
lines.push('---', '', e.body.trim(), '');
|
|
364
|
+
return lines.join('\n');
|
|
365
|
+
}
|
|
@@ -1,14 +1,26 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { defaultDbPath, LINK_RUBRIC_V0, DEFAULT_LINK_RUBRIC } from '../../data/src/index.js';
|
|
2
4
|
import { exportRecorded, openHistoryReadOnly, readLarpJoin } from '../../recorder/src/index.js';
|
|
3
|
-
import { runBacktest, h1Model, larpEval, renderLarpEvalText } from '../../packs/src/index.js';
|
|
5
|
+
import { runBacktest, h1Model, larpEval, renderLarpEvalText, skillBacktest, renderSkillBacktestText, tuneLinkRubric, tuneRugRules, RUG_RULES_V0, RUG_RULES_V1, SKILL_EVAL_SCHEMA, } from '../../packs/src/index.js';
|
|
6
|
+
import { buildSkillEvalSet } from "./skill-eval-build.js";
|
|
7
|
+
/** task-1401: the committed eval set the skill backtest reads by default. */
|
|
8
|
+
export const DEFAULT_SKILL_EVAL = fileURLToPath(new URL('../../packs/eval/skills-eval.json', import.meta.url));
|
|
4
9
|
const HORIZONS = [3600, 7200, 21600, 86400, 259200];
|
|
5
10
|
export const BACKTEST_HELP = `dexterm backtest: score the rug model against recorded outcomes (task-1356). No network, no model.
|
|
6
11
|
|
|
7
12
|
dexterm backtest [--subject heuristic|pack_verdicts|larp] [--horizon SEC] [--db PATH] [--json]
|
|
13
|
+
dexterm backtest --subject skills [--eval FILE] [--tune] [--report FILE] [--json]
|
|
14
|
+
dexterm backtest --build-eval FILE [--db PATH] [--horizon SEC] [--max-lag SEC]
|
|
8
15
|
|
|
9
16
|
--subject heuristic (default, the pre-registered H1 rug model), pack_verdicts (recorded pack rug_p), or
|
|
10
17
|
larp (task-1359: recorded LARP-detector verdicts vs the outcome labels; counts only below
|
|
11
18
|
50 matched verdicts, 95% Wilson intervals above; agreement with bad outcomes, not ground truth).
|
|
19
|
+
skills task-1401: the default analyst skills' replayable rules over the recorded eval set (the LARP
|
|
20
|
+
check's links-only baseline, the rug-check verdict rules), per-grade precision/recall on a train
|
|
21
|
+
and a held-out TEST split. Default eval: packages/packs/eval/skills-eval.json. --tune re-tunes on
|
|
22
|
+
the train split only and prints before (v0) vs tuned; --report writes the JSON report.
|
|
23
|
+
--build-eval build that eval set from a recorder DB (read-only; no network; no URLs or names written).
|
|
12
24
|
--horizon outcome horizon in seconds: ${HORIZONS.join(', ')} (default 3600 = 1h).
|
|
13
25
|
--json print the full result object instead of the human table.
|
|
14
26
|
--db defaults to DEXTERM_DB_PATH, else ~/.local/share/dexterm/dexterm.db.
|
|
@@ -25,7 +37,7 @@ function parse(argv) {
|
|
|
25
37
|
continue;
|
|
26
38
|
const k = a.slice(2);
|
|
27
39
|
const v = argv[i + 1];
|
|
28
|
-
if (v !== undefined && !v.startsWith('--') && k !== 'json') {
|
|
40
|
+
if (v !== undefined && !v.startsWith('--') && k !== 'json' && k !== 'tune') {
|
|
29
41
|
opts[k] = v;
|
|
30
42
|
i++;
|
|
31
43
|
}
|
|
@@ -86,6 +98,8 @@ export function backtestCommand(argv, io) {
|
|
|
86
98
|
io.stdout.write(BACKTEST_HELP + '\n');
|
|
87
99
|
return 0;
|
|
88
100
|
}
|
|
101
|
+
if (opts.subject === 'skills')
|
|
102
|
+
return skillsCommand(opts, io);
|
|
89
103
|
const path = typeof opts.db === 'string' ? opts.db : (io.env.DEXTERM_DB_PATH || defaultDbPath(io.env));
|
|
90
104
|
if (path === ':memory:') {
|
|
91
105
|
io.stderr.write('dexterm backtest: an in-memory DB has no history\n');
|
|
@@ -100,6 +114,13 @@ export function backtestCommand(argv, io) {
|
|
|
100
114
|
io.stderr.write(`dexterm backtest: no recorded history in ${path} (run the app with the recorder on first)\n`);
|
|
101
115
|
return 2;
|
|
102
116
|
}
|
|
117
|
+
if (typeof opts['build-eval'] === 'string') {
|
|
118
|
+
const maxLag = typeof opts['max-lag'] === 'string' ? Number(opts['max-lag']) : undefined;
|
|
119
|
+
const set = buildSkillEvalSet(db, { horizonSec: typeof opts.horizon === 'string' ? horizon : 259_200, maxLagSec: Number.isFinite(maxLag) ? maxLag : undefined });
|
|
120
|
+
writeFileSync(opts['build-eval'], JSON.stringify(set, null, 1) + '\n');
|
|
121
|
+
io.stdout.write(`eval set: ${set.counts.rows} rows (train ${set.counts.train}, bad ${set.counts.bad_train} · test ${set.counts.test}, bad ${set.counts.bad_test}) → ${opts['build-eval']}\nexcluded: ${Object.entries(set.counts.excluded).map(([k, n]) => `${k} ${n}`).join(' · ')}\n`);
|
|
122
|
+
return 0;
|
|
123
|
+
}
|
|
103
124
|
if (opts.subject === 'larp') {
|
|
104
125
|
const r = larpEval(readLarpJoin(db, horizon));
|
|
105
126
|
if (opts.json)
|
|
@@ -147,3 +168,41 @@ export function backtestCommand(argv, io) {
|
|
|
147
168
|
catch { /* ignore */ }
|
|
148
169
|
}
|
|
149
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* task-1401: `--subject skills`. Before = the v0 rules written from the skill text before any backtest; after =
|
|
173
|
+
* the shipped tuned rules, or (with --tune) a fresh train-split tune. Reads only the eval JSON.
|
|
174
|
+
*/
|
|
175
|
+
function skillsCommand(opts, io) {
|
|
176
|
+
const file = typeof opts.eval === 'string' ? opts.eval : DEFAULT_SKILL_EVAL;
|
|
177
|
+
let set;
|
|
178
|
+
if (file === DEFAULT_SKILL_EVAL && !existsSync(file)) {
|
|
179
|
+
// the npm build ships no eval data (packages/packs/eval is repo-only): build one from your own history
|
|
180
|
+
io.stderr.write('dexterm backtest: the bundled eval set is not part of this install. Build one from your own recorder history:\n dexterm backtest --build-eval ./skills-eval.json && dexterm backtest --subject skills --eval ./skills-eval.json\n');
|
|
181
|
+
return 2;
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
set = JSON.parse(readFileSync(file, 'utf8'));
|
|
185
|
+
}
|
|
186
|
+
catch (e) {
|
|
187
|
+
io.stderr.write(`dexterm backtest: cannot read eval set ${file}: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
188
|
+
return 2;
|
|
189
|
+
}
|
|
190
|
+
if (set.schema !== SKILL_EVAL_SCHEMA || !Array.isArray(set.rows)) {
|
|
191
|
+
io.stderr.write(`dexterm backtest: ${file} is not a ${SKILL_EVAL_SCHEMA} file\n`);
|
|
192
|
+
return 2;
|
|
193
|
+
}
|
|
194
|
+
let larpAfter = DEFAULT_LINK_RUBRIC, rugAfter = RUG_RULES_V1, tuned = null;
|
|
195
|
+
if (opts.tune) {
|
|
196
|
+
const train = set.rows.filter((r) => r.split === 'train');
|
|
197
|
+
const l = tuneLinkRubric(train);
|
|
198
|
+
const r = tuneRugRules(train);
|
|
199
|
+
larpAfter = l.rubric;
|
|
200
|
+
rugAfter = r.rules;
|
|
201
|
+
tuned = { larp: { train_mcc: l.train_mcc, candidates: l.candidates }, rug: { train_mcc: r.train_mcc, candidates: r.candidates } };
|
|
202
|
+
}
|
|
203
|
+
const report = skillBacktest(set, { larpBefore: LINK_RUBRIC_V0, larpAfter, rugBefore: RUG_RULES_V0, rugAfter, tuned });
|
|
204
|
+
if (typeof opts.report === 'string')
|
|
205
|
+
writeFileSync(opts.report, JSON.stringify(report, null, 1) + '\n');
|
|
206
|
+
io.stdout.write(opts.json ? JSON.stringify(report, null, 2) + '\n' : renderSkillBacktestText(report));
|
|
207
|
+
return 0;
|
|
208
|
+
}
|
|
@@ -14,7 +14,8 @@ import { shellBinaryPath } from '../../popup/src/index.js';
|
|
|
14
14
|
import { detectColorDepth, protocolFromEnv } from '../../tui/src/index.js';
|
|
15
15
|
import { resolveRpcUrl, defaultDbPath } from '../../data/src/index.js';
|
|
16
16
|
import { XTokenStore } from '../../data/src/index.js';
|
|
17
|
-
import { SayTts, voiceDoctor, WHISPER_MODEL } from '../../voice/src/index.js';
|
|
17
|
+
import { SayTts, voiceDoctor, resolveVoice, WHISPER_MODEL, resolvePiperRuntime, pickPlayer, PIPER_MODEL } from '../../voice/src/index.js';
|
|
18
|
+
import { loadConfig, dextermHome } from '../../custom/src/index.js';
|
|
18
19
|
import { kbStore, skillStore } from "./kb-skill-cmd.js";
|
|
19
20
|
/** The minimum Node that runs dexterm (type-stripping of the .ts entry needs 22.18). */
|
|
20
21
|
export const MIN_NODE = [22, 18, 0];
|
|
@@ -29,16 +30,30 @@ function onPath(bin) {
|
|
|
29
30
|
* Default voice probe. Deliberately cheap and non-blocking: it never compiles the Swift helper (that
|
|
30
31
|
* is left to the first real `v` press), so resolvedStt is inferred from swiftc / whisper presence.
|
|
31
32
|
*/
|
|
32
|
-
export async function defaultVoiceProbe(platform = process.platform) {
|
|
33
|
+
export async function defaultVoiceProbe(platform = process.platform, configuredVoice) {
|
|
33
34
|
const say = new SayTts();
|
|
34
35
|
const sayAvailable = platform === 'darwin' ? await say.available() : false;
|
|
35
|
-
const voices = sayAvailable ? await say.
|
|
36
|
+
const voices = sayAvailable ? await say.listVoicesDetailed() : [];
|
|
36
37
|
const swiftcPresent = platform === 'darwin' && onPath('swiftc');
|
|
37
38
|
const whisperBinPresent = onPath('whisper-cli');
|
|
38
39
|
const whisperModelPresent = existsSync(WHISPER_MODEL);
|
|
39
|
-
const micToolPresent = onPath('sox') || onPath('rec') || onPath('ffmpeg');
|
|
40
|
+
const micToolPresent = onPath('sox') || onPath('rec') || onPath('ffmpeg') || onPath('arecord');
|
|
41
|
+
// Apple Speech is macOS-only; everywhere else whisper.cpp is the default voice-in (task-1393 Part 2).
|
|
40
42
|
const resolvedStt = swiftcPresent ? 'apple' : (whisperBinPresent && whisperModelPresent && micToolPresent) ? 'whisper' : null;
|
|
41
|
-
|
|
43
|
+
// espeak-ng is the baseline portable speech-out engine off macOS.
|
|
44
|
+
const espeakPresent = platform !== 'darwin' && (onPath('espeak-ng') || onPath('espeak'));
|
|
45
|
+
// Piper is the preferred neural speech-out engine EVERYWHERE (task-1408, task-1416): a runtime (the
|
|
46
|
+
// prebuilt binary off macOS, the managed venv on macOS) + the voice model + a player (afplay on macOS).
|
|
47
|
+
const piperPresent = resolvePiperRuntime() !== null;
|
|
48
|
+
const piperVoicePresent = existsSync(PIPER_MODEL);
|
|
49
|
+
const playerName = pickPlayer();
|
|
50
|
+
// task-1393: show which voice will actually speak, and why — never a silent system/novelty voice.
|
|
51
|
+
const r = sayAvailable ? resolveVoice({ configured: configuredVoice, installed: voices }) : { voice: null, reason: '' };
|
|
52
|
+
return {
|
|
53
|
+
platform, sayAvailable, voiceCount: voices.length, swiftcPresent, whisperBinPresent, whisperModelPresent, micToolPresent, resolvedStt,
|
|
54
|
+
espeakPresent, piperPresent, piperVoicePresent, playerName,
|
|
55
|
+
resolvedVoice: sayAvailable ? r.voice : undefined, resolvedVoiceReason: r.reason,
|
|
56
|
+
};
|
|
42
57
|
}
|
|
43
58
|
/** The keyless data sources the live app depends on, with a cheap reachability URL for each. */
|
|
44
59
|
export const DATA_SOURCES = [
|
|
@@ -142,7 +157,7 @@ export function defaultDeps(env = process.env, isTTY = process.stdout.isTTY ===
|
|
|
142
157
|
const cargo = spawnSync('cargo', ['--version'], { stdio: 'ignore', timeout: 3000 }).status === 0;
|
|
143
158
|
return { path, built: existsSync(path), cargo };
|
|
144
159
|
},
|
|
145
|
-
voiceProbe: () => defaultVoiceProbe(process.platform),
|
|
160
|
+
voiceProbe: () => defaultVoiceProbe(process.platform, loadConfig(dextermHome(env)).config.voice?.name ?? null),
|
|
146
161
|
content: () => ({ kb: kbStore(env).get(), skills: skillStore(env).get() }),
|
|
147
162
|
};
|
|
148
163
|
}
|