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.
Files changed (155) hide show
  1. package/dist/packages/agents/src/bus.js +57 -6
  2. package/dist/packages/agents/src/executor.js +25 -0
  3. package/dist/packages/agents/src/follow.js +105 -0
  4. package/dist/packages/agents/src/gate.js +3 -0
  5. package/dist/packages/agents/src/identity.js +41 -2
  6. package/dist/packages/agents/src/index.js +5 -0
  7. package/dist/packages/agents/src/spectator-server.js +153 -0
  8. package/dist/packages/agents/src/spectator.js +212 -0
  9. package/dist/packages/agents/src/tiers.js +5 -0
  10. package/dist/packages/agents/src/tools.js +3 -0
  11. package/dist/packages/cli/assets/claude/analyst-system.md +6 -1
  12. package/dist/packages/cli/assets/claude/plugin/skills/larp-check/SKILL.md +11 -3
  13. package/dist/packages/cli/assets/claude/plugin/skills/rug-check/SKILL.md +13 -3
  14. package/dist/packages/cli/assets/claude/plugin/skills/token-dossier/SKILL.md +3 -3
  15. package/dist/packages/cli/src/agent-cmd.js +107 -10
  16. package/dist/packages/cli/src/agent-spectator.js +37 -0
  17. package/dist/packages/cli/src/alerts/adapters/desktop.js +40 -0
  18. package/dist/packages/cli/src/alerts/adapters/discord.js +36 -0
  19. package/dist/packages/cli/src/alerts/adapters/telegram.js +43 -0
  20. package/dist/packages/cli/src/alerts/adapters/toast.js +16 -0
  21. package/dist/packages/cli/src/alerts/creds.js +83 -0
  22. package/dist/packages/cli/src/alerts/format.js +66 -0
  23. package/dist/packages/cli/src/alerts/index.js +10 -0
  24. package/dist/packages/cli/src/alerts/notifier.js +53 -0
  25. package/dist/packages/cli/src/alerts/route.js +19 -0
  26. package/dist/packages/cli/src/alerts/types.js +1 -0
  27. package/dist/packages/cli/src/alerts-cmd.js +212 -0
  28. package/dist/packages/cli/src/app-help-gen.js +365 -0
  29. package/dist/packages/cli/src/backtest-cmd.js +62 -3
  30. package/dist/packages/cli/src/coin-target.js +2 -0
  31. package/dist/packages/cli/src/doctor.js +21 -6
  32. package/dist/packages/cli/src/export.js +235 -0
  33. package/dist/packages/cli/src/follow-cmd.js +107 -1
  34. package/dist/packages/cli/src/help-cmd.js +48 -0
  35. package/dist/packages/cli/src/live/index.js +66 -3
  36. package/dist/packages/cli/src/live/screens/token-live.js +24 -0
  37. package/dist/packages/cli/src/live/signals.js +109 -0
  38. package/dist/packages/cli/src/live/trade.js +24 -1
  39. package/dist/packages/cli/src/main.js +596 -49
  40. package/dist/packages/cli/src/mcp-cmd.js +321 -0
  41. package/dist/packages/cli/src/pnl-app.js +221 -0
  42. package/dist/packages/cli/src/pnl-card.js +615 -0
  43. package/dist/packages/cli/src/record-cmd.js +455 -0
  44. package/dist/packages/cli/src/rules.js +69 -2
  45. package/dist/packages/cli/src/screens/follows.js +91 -2
  46. package/dist/packages/cli/src/screens/overlay.js +3 -0
  47. package/dist/packages/cli/src/screens/positions.js +1 -1
  48. package/dist/packages/cli/src/screens/rules.js +1 -1
  49. package/dist/packages/cli/src/skill-eval-build.js +109 -0
  50. package/dist/packages/cli/src/spectate-cmd.js +104 -0
  51. package/dist/packages/cli/src/state.js +1 -1
  52. package/dist/packages/cli/src/update.js +294 -0
  53. package/dist/packages/cli/src/version.js +3 -0
  54. package/dist/packages/cli/src/voice-cmd.js +194 -0
  55. package/dist/packages/cli/src/voice-hud.js +1 -1
  56. package/dist/packages/cli/src/watch-cmd.js +190 -0
  57. package/dist/packages/custom/src/rules.js +3 -2
  58. package/dist/packages/custom/src/schema.js +24 -1
  59. package/dist/packages/data/src/analyst-actions.js +130 -3
  60. package/dist/packages/data/src/coin-target.js +82 -0
  61. package/dist/packages/data/src/fast-lane.js +16 -2
  62. package/dist/packages/data/src/index.js +4 -0
  63. package/dist/packages/data/src/intel/helius-history.js +12 -1
  64. package/dist/packages/data/src/intel/live-signals.js +185 -0
  65. package/dist/packages/data/src/larp/link-baseline.js +196 -0
  66. package/dist/packages/data/src/sources/dexscreener.js +14 -0
  67. package/dist/packages/data/src/sources/pumpportal.js +6 -1
  68. package/dist/packages/data/src/trade/copy/index.js +1 -1
  69. package/dist/packages/data/src/trade/copy/stats.js +36 -2
  70. package/dist/packages/data/src/trade/copy/tracker.js +3 -2
  71. package/dist/packages/mcp/assets/kb/app-help/app-help-command.md +35 -0
  72. package/dist/packages/mcp/assets/kb/app-help/ask-the-analyst.md +43 -0
  73. package/dist/packages/mcp/assets/kb/app-help/backtest.md +33 -0
  74. package/dist/packages/mcp/assets/kb/app-help/bubble-map.md +35 -0
  75. package/dist/packages/mcp/assets/kb/app-help/calm-mode.md +31 -0
  76. package/dist/packages/mcp/assets/kb/app-help/change-voice-speed.md +31 -0
  77. package/dist/packages/mcp/assets/kb/app-help/change-voice.md +30 -0
  78. package/dist/packages/mcp/assets/kb/app-help/chart-popup-selftest.md +32 -0
  79. package/dist/packages/mcp/assets/kb/app-help/coin-images.md +29 -0
  80. package/dist/packages/mcp/assets/kb/app-help/configure-rpc-key.md +28 -0
  81. package/dist/packages/mcp/assets/kb/app-help/connect-an-agent-registry.md +35 -0
  82. package/dist/packages/mcp/assets/kb/app-help/connect-an-agent.md +39 -0
  83. package/dist/packages/mcp/assets/kb/app-help/custom-keymap.md +32 -0
  84. package/dist/packages/mcp/assets/kb/app-help/edit-kb-or-skill.md +36 -0
  85. package/dist/packages/mcp/assets/kb/app-help/edit-rules.md +34 -0
  86. package/dist/packages/mcp/assets/kb/app-help/export-a-view.md +36 -0
  87. package/dist/packages/mcp/assets/kb/app-help/follow-a-wallet.md +33 -0
  88. package/dist/packages/mcp/assets/kb/app-help/getting-started.md +37 -0
  89. package/dist/packages/mcp/assets/kb/app-help/kol-lists.md +34 -0
  90. package/dist/packages/mcp/assets/kb/app-help/larp-check.md +36 -0
  91. package/dist/packages/mcp/assets/kb/app-help/link-a-wallet.md +41 -0
  92. package/dist/packages/mcp/assets/kb/app-help/login-with-x.md +33 -0
  93. package/dist/packages/mcp/assets/kb/app-help/narrative-board.md +32 -0
  94. package/dist/packages/mcp/assets/kb/app-help/open-a-coin.md +43 -0
  95. package/dist/packages/mcp/assets/kb/app-help/open-chart-popup.md +33 -0
  96. package/dist/packages/mcp/assets/kb/app-help/order-plans.md +33 -0
  97. package/dist/packages/mcp/assets/kb/app-help/paper-trade.md +36 -0
  98. package/dist/packages/mcp/assets/kb/app-help/price-or-marketcap.md +38 -0
  99. package/dist/packages/mcp/assets/kb/app-help/record-headless.md +34 -0
  100. package/dist/packages/mcp/assets/kb/app-help/recorder-history.md +39 -0
  101. package/dist/packages/mcp/assets/kb/app-help/rug-check.md +39 -0
  102. package/dist/packages/mcp/assets/kb/app-help/run-doctor.md +32 -0
  103. package/dist/packages/mcp/assets/kb/app-help/scan-mode.md +37 -0
  104. package/dist/packages/mcp/assets/kb/app-help/screen-analyst.md +181 -0
  105. package/dist/packages/mcp/assets/kb/app-help/screen-kol.md +190 -0
  106. package/dist/packages/mcp/assets/kb/app-help/screen-newpairs.md +181 -0
  107. package/dist/packages/mcp/assets/kb/app-help/screen-positions.md +180 -0
  108. package/dist/packages/mcp/assets/kb/app-help/screen-screener.md +199 -0
  109. package/dist/packages/mcp/assets/kb/app-help/screen-token.md +240 -0
  110. package/dist/packages/mcp/assets/kb/app-help/screen-trending.md +192 -0
  111. package/dist/packages/mcp/assets/kb/app-help/screen-watchlist.md +181 -0
  112. package/dist/packages/mcp/assets/kb/app-help/set-a-filter.md +36 -0
  113. package/dist/packages/mcp/assets/kb/app-help/setup-neural-voice.md +36 -0
  114. package/dist/packages/mcp/assets/kb/app-help/share-packs.md +34 -0
  115. package/dist/packages/mcp/assets/kb/app-help/sort-the-screener.md +34 -0
  116. package/dist/packages/mcp/assets/kb/app-help/spectate-an-agent.md +36 -0
  117. package/dist/packages/mcp/assets/kb/app-help/standing-mandates.md +33 -0
  118. package/dist/packages/mcp/assets/kb/app-help/switch-theme.md +42 -0
  119. package/dist/packages/mcp/assets/kb/app-help/turn-on-alerts.md +37 -0
  120. package/dist/packages/mcp/assets/kb/app-help/update-dexterm.md +30 -0
  121. package/dist/packages/mcp/assets/kb/app-help/use-presets.md +42 -0
  122. package/dist/packages/mcp/assets/kb/app-help/use-voice-mode.md +36 -0
  123. package/dist/packages/mcp/assets/kb/app-help/watch-a-coin.md +34 -0
  124. package/dist/packages/mcp/assets/kb/app-help/watch-headless.md +35 -0
  125. package/dist/packages/mcp/assets/kb/app-help/what-dexterm-shows-agents.md +31 -0
  126. package/dist/packages/mcp/assets/kb/app-help/workspaces.md +38 -0
  127. package/dist/packages/mcp/src/app-help.js +135 -0
  128. package/dist/packages/mcp/src/index.js +3 -0
  129. package/dist/packages/mcp/src/server.js +179 -5
  130. package/dist/packages/mcp/src/spectator-sink.js +72 -0
  131. package/dist/packages/packs/src/index.js +3 -0
  132. package/dist/packages/packs/src/skill-backtest.js +300 -0
  133. package/dist/packages/packs/src/skill-care.js +206 -0
  134. package/dist/packages/popup/src/pages/spectator.css +45 -0
  135. package/dist/packages/popup/src/pages/spectator.html +25 -0
  136. package/dist/packages/popup/src/pages/spectator.js +74 -0
  137. package/dist/packages/popup/src/selftest.js +19 -0
  138. package/dist/packages/popup/src/server.js +7 -1
  139. package/dist/packages/tui/src/index.js +3 -0
  140. package/dist/packages/tui/src/keymap.js +12 -0
  141. package/dist/packages/tui/src/raster-font.js +195 -0
  142. package/dist/packages/tui/src/raster.js +213 -0
  143. package/dist/packages/voice/src/controller.js +57 -4
  144. package/dist/packages/voice/src/doctor.js +96 -11
  145. package/dist/packages/voice/src/download.js +184 -0
  146. package/dist/packages/voice/src/engines.js +55 -8
  147. package/dist/packages/voice/src/index.js +9 -3
  148. package/dist/packages/voice/src/speakable.js +111 -0
  149. package/dist/packages/voice/src/stt/whisper.js +14 -4
  150. package/dist/packages/voice/src/tts/espeak.js +131 -0
  151. package/dist/packages/voice/src/tts/piper-setup.js +229 -0
  152. package/dist/packages/voice/src/tts/piper.js +449 -0
  153. package/dist/packages/voice/src/tts/say.js +72 -35
  154. package/dist/packages/voice/src/tts/voices.js +209 -0
  155. package/package.json +5 -9
@@ -0,0 +1,209 @@
1
+ // Voice resolution, the novelty ban-list, and speech-text sanitisation (task-1393). All pure and
2
+ // dependency-free so every rule is unit-testable without spawning `say`.
3
+ //
4
+ // Why this exists: SayTts used to be constructed with NO default voice, so macOS fell back to the
5
+ // machine's *system* voice — which differs per machine and can be a novelty/odd voice (a maintainer heard a
6
+ // "terrifying" one). This module makes the default an EXPLICIT deep British male and guarantees a
7
+ // novelty voice (Zarvox, Bad News, …) can never be auto-selected and the system default is never used
8
+ // silently: if nothing safe is installed, voice degrades to text (voice === null), never to "whatever
9
+ // the OS picks".
10
+ /**
11
+ * Novelty / "scary" voices that must never be auto-selected, normalised to lowercase base names (the
12
+ * locale qualifier like " (English (UK))" is stripped first — see normalizeVoiceName). The list is the
13
+ * classic MacinTalk novelty set plus the names the task named explicitly ("and similar"). The resolver
14
+ * also uses POSITIVE allow-lists, so this is defence in depth; the ban test asserts no resolved voice
15
+ * is ever on it.
16
+ */
17
+ export const BANNED_VOICE_NAMES = new Set([
18
+ 'albert', 'bad news', 'bahh', 'bells', 'boing', 'bubbles', 'cellos', 'deranged', 'fred',
19
+ 'good news', 'hysterical', 'jester', 'junior', 'organ', 'pipe organ', 'princess', 'ralph',
20
+ 'superstar', 'trinoids', 'whisper', 'wobble', 'zarvox',
21
+ ]);
22
+ /**
23
+ * Strip the locale qualifier macOS appends to the newer voices ("Eddy (English (UK))" → "Eddy"),
24
+ * collapse whitespace, lowercase. Used for ban/allow matching so "Eddy (English (US))" and
25
+ * "Eddy (English (UK))" share one base name.
26
+ */
27
+ export function normalizeVoiceName(name) {
28
+ return name.replace(/\s*\(.*\)\s*$/, '').trim().toLowerCase();
29
+ }
30
+ /** True when a voice is on the novelty ban-list (matched on its normalised base name). */
31
+ export function isBannedVoice(name) {
32
+ return BANNED_VOICE_NAMES.has(normalizeVoiceName(name));
33
+ }
34
+ /**
35
+ * Parse the output of `say -v '?'`. Each line is `<name> <locale> # <sample sentence>`, but the name
36
+ * column is only padded to a fixed width, so a LONG name ("Eddy (English (UK))") leaves a SINGLE space
37
+ * before the locale — the old regex required two and silently dropped exactly the en_GB voices we care
38
+ * about. This parser anchors on the trailing locale token instead, so it never loses a voice.
39
+ */
40
+ export function parseSayVoices(stdout) {
41
+ const voices = [];
42
+ for (const raw of stdout.split('\n')) {
43
+ if (!raw.trim())
44
+ continue;
45
+ const hash = raw.indexOf('#');
46
+ const left = (hash >= 0 ? raw.slice(0, hash) : raw).trimEnd();
47
+ // name = everything up to the last whitespace run; locale = the final token (en, en_GB, zh-CN…).
48
+ const m = left.match(/^(.*\S)\s+([a-z]{2}(?:[_-][A-Za-z0-9]+)?)$/);
49
+ if (m)
50
+ voices.push({ name: m[1].trim(), locale: m[2] });
51
+ }
52
+ return voices;
53
+ }
54
+ /**
55
+ * Deep British male voices, best-to-worst. Eddy (the maintainers' pick, 2026-10-15) is the dexterm default; Daniel is the canonical deep British male that
56
+ * ships with every macOS and is the next fallback. Oliver/Arthur are the newer en_GB male voices (present only if the
57
+ * user downloaded them). Matched on normalised base name.
58
+ */
59
+ export const BRITISH_MALE_PREFERENCE = ['eddy', 'daniel', 'oliver', 'arthur'];
60
+ /**
61
+ * Per-voice delivery defaults, applied only when the user has not saved their own rate/pitch.
62
+ *
63
+ * task-1411: the old Eddy override (rate 168 + pbas 30) came from an audition sample the maintainers never
64
+ * explicitly picked; it was reported as "crazy bad" and "way too slow", and the pitch shift distorted
65
+ * the voice. It is removed: every voice now uses the engine-wide DEFAULT_RATE (a brisk ~205 wpm) and NO
66
+ * pitch override unless the user sets `voice.pitch` themselves. Keep this map empty rather than deleting
67
+ * the hook, so a future per-voice tweak has a home and the public `voiceStyleDefaults` export is stable.
68
+ */
69
+ export const VOICE_STYLE_DEFAULTS = {};
70
+ /** The delivery defaults for a resolved voice name, or undefined to use the engine-wide defaults. */
71
+ export function voiceStyleDefaults(name) {
72
+ return name ? VOICE_STYLE_DEFAULTS[normalizeVoiceName(name)] : undefined;
73
+ }
74
+ /**
75
+ * en_GB voices that read masculine / neutral and are acceptable British fallbacks when no first-class
76
+ * British male is installed (Grandpa is explicitly a male voice; Eddy/Reed/Rocko are the modern
77
+ * expressive voices under audition). Matched on normalised base name, en_GB locale only.
78
+ */
79
+ export const BRITISH_FALLBACK = ['grandpa', 'eddy', 'reed', 'rocko'];
80
+ /** Safe English male voices in any English locale, used only when nothing British is installed. */
81
+ export const ENGLISH_MALE_ALLOW = ['daniel', 'oliver', 'arthur', 'alex', 'tom', 'aaron', 'rishi'];
82
+ const isEnglish = (locale) => /^en([_-]|$)/i.test(locale);
83
+ const isBritish = (locale) => /^en[_-]gb$/i.test(locale);
84
+ /** Pick the best installed match for a base name, preferring an Enhanced/Premium variant and the given locale. */
85
+ function bestMatch(installed, base, preferLocale) {
86
+ const matches = installed.filter((v) => normalizeVoiceName(v.name) === base && !isBannedVoice(v.name));
87
+ if (!matches.length)
88
+ return null;
89
+ const score = (v) => {
90
+ let s = 0;
91
+ if (/\((enhanced|premium)\)/i.test(v.name))
92
+ s += 4; // higher-quality download, if present
93
+ if (preferLocale?.(v.locale))
94
+ s += 2;
95
+ return s;
96
+ };
97
+ return matches.slice().sort((a, b) => score(b) - score(a))[0];
98
+ }
99
+ /**
100
+ * Find the installed voice a saved name refers to. The `,` key saves the FULL name ("Eddy (English (UK))"),
101
+ * and Eddy/Reed/Rocko each exist in many languages, so an exact full-name match must win. Only when the
102
+ * saved name is a bare base name ("Eddy") do we choose among same-base voices: en_GB first, then any
103
+ * English, then anything else (a verifier found "Reed (English (UK))" resolving to the German Reed).
104
+ */
105
+ function findConfiguredVoice(installed, configured) {
106
+ const exact = installed.find((v) => v.name === configured && !isBannedVoice(v.name));
107
+ if (exact)
108
+ return exact;
109
+ const folded = configured.toLowerCase();
110
+ const ci = installed.find((v) => v.name.toLowerCase() === folded && !isBannedVoice(v.name));
111
+ if (ci)
112
+ return ci;
113
+ const base = normalizeVoiceName(configured);
114
+ const rank = (v) => (isBritish(v.locale) ? 0 : isEnglish(v.locale) ? 1 : 2);
115
+ return installed
116
+ .filter((v) => normalizeVoiceName(v.name) === base && !isBannedVoice(v.name))
117
+ .sort((a, b) => rank(a) - rank(b))[0];
118
+ }
119
+ /**
120
+ * Resolve the voice `say` should use. The fallback chain, in order:
121
+ * 1. the user's configured voice (if installed and not banned)
122
+ * 2. the chosen default: a British male (Eddy → Daniel → Oliver → Arthur), Enhanced/Premium preferred
123
+ * 3. the next British voice (en_GB Grandpa/Eddy/Reed/Rocko)
124
+ * 4. any safe English male voice (Alex/Tom/…)
125
+ * 5. null → replies stay text-only
126
+ * A banned (novelty) voice is NEVER returned, and the OS system default is NEVER used silently.
127
+ */
128
+ export function resolveVoice(opts) {
129
+ const installed = opts.installed ?? [];
130
+ const configured = opts.configured?.trim();
131
+ // 1. Configured voice wins when it is actually installed and safe.
132
+ if (configured) {
133
+ const hit = findConfiguredVoice(installed, configured);
134
+ if (hit && !isBannedVoice(hit.name)) {
135
+ return { voice: hit.name, source: 'config', reason: `your configured voice (${hit.name})` };
136
+ }
137
+ // Configured but missing or banned: fall through to the safe default rather than honour a bad value.
138
+ }
139
+ // 2. Chosen default: British male (Eddy → Daniel → …). en_GB only: Eddy also exists in other locales.
140
+ const britishInstalled = installed.filter((v) => isBritish(v.locale));
141
+ for (const base of BRITISH_MALE_PREFERENCE) {
142
+ const v = bestMatch(britishInstalled, base, isBritish);
143
+ if (v) {
144
+ const isDefault = base === BRITISH_MALE_PREFERENCE[0];
145
+ return {
146
+ voice: v.name,
147
+ source: 'default',
148
+ reason: isDefault ? `${v.name} — deep British male (dexterm default)` : `${v.name} — British male voice`,
149
+ };
150
+ }
151
+ }
152
+ // 3. Next British voice (en_GB, masculine/neutral).
153
+ for (const base of BRITISH_FALLBACK) {
154
+ const v = installed.find((x) => normalizeVoiceName(x.name) === base && isBritish(x.locale) && !isBannedVoice(x.name));
155
+ if (v)
156
+ return { voice: v.name, source: 'british', reason: `${v.name} — British voice (no Daniel installed)` };
157
+ }
158
+ // 4. Any safe English male voice, any English locale.
159
+ for (const base of ENGLISH_MALE_ALLOW) {
160
+ const v = installed.find((x) => normalizeVoiceName(x.name) === base && isEnglish(x.locale) && !isBannedVoice(x.name));
161
+ if (v)
162
+ return { voice: v.name, source: 'english', reason: `${v.name} — English voice (no British male installed)` };
163
+ }
164
+ // 5. Nothing safe: text only. Never the system default.
165
+ return { voice: null, source: 'none', reason: 'no safe voice installed — spoken replies are off (text still works)' };
166
+ }
167
+ // ------------------------------------------------------------------ speech-text sanitisation
168
+ /** Default cap on spoken length. Long analyst answers are summarised upstream; this is a hard backstop. */
169
+ export const MAX_SPEAK_CHARS = 600;
170
+ const BASE58 = '1-9A-HJ-NP-Za-km-z';
171
+ /** A Solana mint / base58 address (32-44 chars) or a 0x… EVM address — never read 44 characters aloud. */
172
+ const ADDRESS_RE = new RegExp(`\\b(?:0x[0-9a-fA-F]{40}|[${BASE58}]{32,44})\\b`, 'g');
173
+ /** Short, speakable form of an address: "7xKX…Dgkp" reads as a few characters, not forty-four. */
174
+ function shortAddress(addr) {
175
+ const body = addr.startsWith('0x') ? addr.slice(2) : addr;
176
+ return `${body.slice(0, 4)}…${body.slice(-4)}`;
177
+ }
178
+ /**
179
+ * Make arbitrary text safe to hand to `say`. Untrusted text (token names, socials, analyst output) can
180
+ * contain `say`'s own `[[…]]` embedded speech commands — `[[slnc 5000]]`, `[[rate 10]]`, even a voice
181
+ * switch — which `say` INTERPRETS whether the text arrives on argv or stdin. We also never want a
182
+ * leading `-` parsed as a flag. This:
183
+ * - defuses `[[` / `]]` so no embedded command is ever executed (kept readable, not deleted)
184
+ * - neutralises a leading `-`/`+` (defence in depth; we also deliver via stdin, not argv)
185
+ * - collapses control characters and whitespace
186
+ * - shortens contract addresses to a 4…4 form instead of reading 44 characters
187
+ * - caps the length on a word boundary
188
+ */
189
+ export function sanitizeSpeech(text, opts = {}) {
190
+ const maxChars = opts.maxChars ?? MAX_SPEAK_CHARS;
191
+ let s = String(text ?? '');
192
+ // Defuse `say` embedded commands: a split `[ [` / `] ]` is no longer a command token.
193
+ s = s.replace(/\[\[/g, '[ [').replace(/\]\]/g, '] ]');
194
+ // Strip control characters (NUL, ESC, etc.) that could confuse the child or the terminal.
195
+ s = s.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, ' ');
196
+ // Shorten addresses so we don't read 44 characters aloud.
197
+ s = s.replace(ADDRESS_RE, (m) => shortAddress(m));
198
+ // Collapse whitespace.
199
+ s = s.replace(/\s+/g, ' ').trim();
200
+ // A leading dash/plus could be read as a flag if it ever reached argv; neutralise it.
201
+ s = s.replace(/^[-+]+\s*/, '');
202
+ // Cap length on a word boundary.
203
+ if (s.length > maxChars) {
204
+ const cut = s.slice(0, maxChars);
205
+ const sp = cut.lastIndexOf(' ');
206
+ s = (sp > maxChars * 0.6 ? cut.slice(0, sp) : cut).trim();
207
+ }
208
+ return s;
209
+ }
package/package.json CHANGED
@@ -1,9 +1,13 @@
1
1
  {
2
2
  "name": "dexterm-cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "dexterm: terminal DEX screener with a local Claude CLI analyst",
5
5
  "author": "The dexterm authors",
6
6
  "license": "AGPL-3.0-only",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/dexterm-xyz/dexterm.git"
10
+ },
7
11
  "type": "module",
8
12
  "bin": {
9
13
  "dexterm": "./dist/packages/cli/bin/dexterm.mjs"
@@ -32,13 +36,5 @@
32
36
  "@dexterm/recorder": "0.0.1",
33
37
  "@dexterm/tui": "0.0.1",
34
38
  "@dexterm/voice": "0.0.1"
35
- },
36
- "repository": {
37
- "type": "git",
38
- "url": "git+https://github.com/dexterm-xyz/dexterm.git"
39
- },
40
- "homepage": "https://dexterm.xyz",
41
- "bugs": {
42
- "url": "https://github.com/dexterm-xyz/dexterm/issues"
43
39
  }
44
40
  }