dexterm

Changelog

What changed in dexterm, by day, written by hand from the app's git history.

Human-written from the git log of dexterm/app (2026-10-05 to 2026-10-12). Hashes are the merge or lane commits on main. The app was version 0.0.1 until the first public release, 0.1.0, on 2026-10-15 (see that day's entry).

2026-10-15#

  • Version 0.1.1, the first npm release (task-1387, task-1388, task-1392): dexterm is now on npm as [dexterm-cli](https://www.npmjs.com/package/dexterm-cli): npm i -g dexterm-cli, then run dexterm. npm refuses the name dexterm as too similar to xterm. This upload was published by hand, so it has no build provenance yet. Also: a go-to prompt (press g to open any coin by contract address) and a fix so paper trade quotes run at high priority and no longer fail with "quote timed out". Trading stays experimental.
  • Version 0.1.0, the first public release (task-1376, task-1382): the version bump, the fresh-machine test fixes, the copyright notice ("the dexterm authors", AGPL-3.0-only) and a launch-day review of all 17 bundled knowledge-base entries.
  • Your own knowledge base and skills (task-1377): dexterm kb and dexterm skill list, show, create, edit, check and reset entries. Files in ~/.dexterm/kb/entries/ and ~/.dexterm/skills/ override or add to the bundled ones, and the analyst, Jarvis and the MCP server pick up changes on the next call with no restart. A skill that names a trade or signing tool is refused.
  • Homebrew install (task-1386): the tap dexterm-xyz/homebrew-dexterm installs the 0.1.0 release tarball. Homebrew 7 loads third-party taps only after brew trust dexterm-xyz/dexterm, so the install docs now show brew tap, brew trust, brew install dexterm.
  • Known issues in 0.1.0: trading. Paper trading and the live (wallet-signed) path both have bugs in this release. The paper features are now marked IN PROGRESS on the features page. Treat all trading as experimental.

2026-10-12#

  • Widget pages: a click on a link that targets another window is refused; in-page links no longer close a widget on WebKit (task-1358 r6, after a fifth independent verification). The r5 seal closed every way to insert a loader element, but an honest <a target="_blank"> that a pack script clicks is not a loader — and WebKit opens a bare TCP connection to the link's host and port before the sandbox refuses the popup (0 bytes, no window; the host:port is the channel, the same family as the earlier preconnect finding). The SDK now cancels every click, auxclick or form submit whose path holds a link with a target other than the frame itself, a download or a ping, in a capture-phase listener on the window that runs before anything a pack registers and that nothing can remove; shadow roots are forced open so a link cannot hide from it. Verified in headless Chromium and WebKit (with and without Trusted Types), against the verifier's own repro (0 of 3 connections in each engine), and inside the real native shell on the system WebKit (0 connections to a LAN and a loopback sink, the refusal logged to the terminal). Also fixed: WebKit fires a frame load for a same-page fragment navigation, so an in-page <a href="#section"> used to close the widget; the wrapper now asks the document to identify itself and closes only when it cannot. A plain <meta> the parser reaches after the SDK no longer trips the belt (only <meta http-equiv> does).
  • Widget pages: the seal no longer depends on Trusted Types; the theme guard refuses grey (task-1358 r5, after a fourth independent verification). The r4 seal missed two doors: Document.parseHTMLUnsafe (a static method, not on any prototype) and the element-taking setters (table.caption, document.body, select.add and kin), so on a WebKit without Trusted Types a page could parse a <link rel=preconnect> into a detached document and adopt it through table.caption = — and the system WebKit of macOS 14 and 15 (Safari before 26) has no Trusted Types, which is exactly what the native shell runs. Now the static parsers and execCommand throw, adoptNode / importNode and every element-taking setter refuse a loader subtree, and a MutationObserver removes any loader element that ever appears and closes the widget. Verified inside the real native shell on this Mac (system WebKit, no Trusted Types: 35 vectors refused, 0 connections, data path intact), in headless Chromium and WebKit with the Trusted Types directives stripped, and against the verifier's own bypass. The theme guard now also requires real colour for up and down (an HSL saturation floor) and a minimum perceptual distance between them (CIE76 ΔE ≥ 40), so a near-grey pair such as #7f807f / #807f7f is refused with the numbers named; every built-in pair clears both by a wide margin.
  • Widget pages: the DOM is sealed, widgets open only in the native shell, and two verifier defects fixed (task-1358 r4, after a third independent verification). The r3 frame held against every navigation and exit vector in both engines, but a <link rel=preconnect> built at run time made WebKit open a bare TCP connection to a host and port the page chose — nothing is fetched, so no content-security directive applies and the install-time audit cannot see an element that does not exist yet. Now the SDK, the first script of the frame, seals the DOM before any pack script runs: the insertion methods (appendChild, insertBefore, replaceChild, append, prepend, replaceChildren, moveBefore, before, after, replaceWith, insertAdjacentElement, Range.insertNode, surroundContents) are replaced on the prototypes by non-configurable guards that refuse any node carrying a loader element (link, meta, base, iframe, frame, object, embed, script, …), the HTML-string sinks (innerHTML and outerHTML setters, insertAdjacentHTML, document.write, setHTML, setHTMLUnsafe, createContextualFragment, DOMParser) throw, and WebRTC is removed; the page cannot reach a second realm (nested frames are sealed off by the sandbox), so the natives cannot be recovered. Verified in headless Chromium 151 and WebKit 26.5 against 45 vectors with a raw TCP sink, with the verifier's own probe (0 connections in both engines), and in the real native shell on this Mac. Widgets now open only in the native popup shell (macOS 14 or newer); under the browser fallback the request is refused with the reason. The shell additionally routes every window's connections through a loopback sink proxy that tunnels only to the popup server and reports anything else; measured: WebKit sends named hosts through it but connects to IP literals directly, so it is a backstop, not the control. The terminal's socket also drops a roster or plan event from a widget at the server. Also fixed from the same verification: the theme guard accepted an amber up with an amber down (now up must read green/cyan and down red/pink, every built-in still passes), and a rule written as __proto__ > 1 or constructor:x parsed through an inherited lookup and then crashed the evaluator (own-property lookups; such rules are refused at parse and at load). The widget audit now refuses @import only where CSS is parsed (a <style> block or a style attribute), not in script text.
  • Market cap instead of price: one key, everywhere (a30d7f6, 4034fc4, fb22765, task-1366, Joey's request: memecoin traders think in market cap, and order plans already trigger on it). $ flips price ⇄ market cap and saves it as display.priceMode in config.json (now v4; an older file is migrated with a backup, and ctrl+z undoes the switch); dexterm display [price|mcap], --price-mode for one run, and a workspace can carry it. The screener, watchlist and new pairs lead with MCAP (PRICE moves to MCAP's old place, every sort key unchanged), the watchlist's entry becomes ENTRY MC, spotlights and the token header lead with the market cap with the price beside it, and the terminal chart and the popup chart label the Y axis in K / M / B (300K, 1.2M) with the crosshair legend showing the price in parentheses. Market cap = price × the supply the sources already report; historical candles use the current supply and every chart title says mcap (current supply). Order-plan levels on the O screen and in the plans popup show both numbers (the price from the plan's own supply snapshot); position rows read AVG MC / MCAP while profit and loss stay in USD. A token without a supply falls back to its price and says so (ᵖ, supply unknown · showing price). Display only: no new requests, and the signer, caps, tickets, plan logic and the confirm gate are untouched. Tests: formatter units (K/M/B, tiny and huge values, never 1000K), the screener / watchlist / new pairs / token / plans / positions frames in both modes on fixtures, the glyph-identical chart, the popup axis formatter and legend, persistence and the v3 → v4 migration, the missing-supply fallback. Docs: price display mode, popup chart.
  • Who holds it: every top holder is classed from the chain, and the top-10 is shown raw and ex-pool (task-1367, from Joey's screenshot of a fresh launch where one unlabelled 71.56 % holder turned Top 10 red at 78.3 %). The HOLDERS panel, the bubble map and its popup tag each owner POOL / CURVE / VAULT / LOCK / CEX / BURN / DEV / LINKED with the on-chain reason, or UNLABELLED (never a guess; an unknown program is counted, not hidden). The class comes from the token account's owner and the owner account's program, which the wallet-intel scan already fetched: no new request, no paid API. Programs covered: Raydium AMM v4 / CPMM / CLMM / LaunchLab, Orca Whirlpool, Meteora DLMM / DAMM v1 / v2 / DBC / dynamic vault / Alpha Vault, pump.fun and PumpSwap, Streamflow, Jupiter Lock, the incinerator; the constant pool-authority PDAs are re-derived in the tests. The Top 10 holders row splits into Top 10 ex-pool (the colour reads it) and Top 10 raw; a counted owner above 30 % is called out as the main risk. Locks name their escrow; unlock schedules are not decoded yet. Also fixed: with a Helius key configured, Snipers / Bundled supply said needs Helius key whenever the launch window had not landed (Helius rate-limited, the daily credit cap, a failed history call); they now say which, and r retries. The Dev history row no longer blames the key when Jupiter simply did not report a count.
  • A LIVE trade can go out: Jupiter rate limits are retried, the ticket checks your size against the caps before ⏎, and a devnet signer says plainly that it cannot fill a mainnet token (task-1368, urgent, from Joey's two screenshots). The first screenshot died on plan failed: build-failed: HTTP 429 for https://api.jup.ag/swap/v1/swap: the quote and build adapters switched host only on a keyless refusal, never retried, and one per-IP 429 killed the plan while the shared bucket paused for 30 s (the preview's "fetching… forever"). Recorded on the day: api.jup.ag allows five requests per ~10 s per IP and answers 429 with no Retry-After but with x-ratelimit-reset; lite-api.jup.ag answered 503. Now one retry policy covers the quote and the build: a 429, a 5xx or a dropped connection alternates to the other host after the server's own reset (or Retry-After, with a jittered backoff), at most three attempts in ten seconds; the data layer derives the wait from the reset when Retry-After is absent; the signer gains a read-only quote so the LIVE preview, its re-quote and the build are paced by one limiter and the build reuses the preview's quote; and when the budget (or the health gate's cool-down after two 429s in a minute) is spent the signer answers rate-limited with the honest wait and the ticket keeps the plan, shows "retrying in Ns", retries by itself, r retries now. The second screenshot (0.04 SOL = $4.33, DEVNET) showed You receive 0 AVGOx — a formatter that rounded token quantities to whole numbers — and Simulation FAILED: … invalid transaction: Tr…: token amounts are now quantities (0.0144 AVGOx; "decimals unknown" when they are), the PLAN shows the full RPC error with a plain-English hint when the pattern is the devnet-vs-mainnet wall, the signer logs the full error, and PlanView.mintOnNetwork says whether the mint exists on the signer's network. Before ⏎ a LIVE buy shows its size in USD at the signer's own SOL/USD against the per-trade cap and what is left of the daily cap, red with the size that fits when it is over (0.05 SOL = $5.40 > $5 cap: use ≤ 0.043 SOL; f sets it, leaving room for the fee and a token-account rent), and every token ticket on a devnet signer carries DEVNET cannot fill mainnet tokens: paper trade (L) or restart with --mainnet (real funds, caps $5/$20). Docs: the paper-trading page gains "Before you press ⏎", "Jupiter rate limits", "Devnet signer, mainnet token" and a "Your first live trade" walkthrough. Signer policy, caps, confirmation proof and the keypress gate are untouched. Tests: the recorded 429/503 fixtures through the real HTTP client, limiter and signer service; the ticket preflight math, the fit, the devnet line, the hint, the rendered frames and the never-dropped rate-limited plan with a real auto-retry. Item (g), from Joey's third screenshot: the Phantom sign page answered 4100 ("not authorized") because it called signTransaction on a random-port origin it had never connected. The page now connects first, shows the connected account, refuses an account that is not the linked wallet, posts the account with the signed bytes (the bridge refuses a mismatch again server-side) and explains 4100 / 4001 in plain English; the wallet bridge listens on a **fixed loopback port, 127.0.0.1:41291** (random only when busy, and the page says so when it is) so link and sign share the one origin Phantom stores its trust for. packages/data/test/bridge-1368.test.ts drives the page's own script against a fake Phantom that answers 4100 unless connect() ran first: the happy path, the wrong account and a dismissed Connect prompt.
  • Widget pages cannot navigate anywhere: a sandboxed frame inside the host's own window (task-1358 r3, after a second independent verification found the gap). The r2 page was served top-level, and a document may always navigate itself — sandbox only stops it navigating other windows — so under the browser fallback a page containing just location.href = 'https://…?q=…' could carry data out in the address bar (the native shell already pinned navigation). Now a widget is two documents: the host's wrapper page (nonce CSP like every built-in popup; it owns the socket, the title strip and the close button; its frame-src names exactly one route) holding the pack page in <iframe sandbox="allow-scripts">, served under the hash-only CSP with connect-src 'none' (no network at all), an opaque origin (no cookies, storage or reach into the wrapper) and frame-ancestors = the host. The parent's frame-src governs the child's every navigation whoever starts it; the wrapper hears the violation, drops the frame, logs it to the terminal and closes the window. View models travel to the frame by postMessage; the frame can say only ready / log / close back, and the terminal's socket now accepts nothing but the generic events from a widget (a roster or plan event is dropped at the server, not just ignored). Verified in headless Chromium 151 and WebKit (evidence in the handoff): the verifier's exact page, a kitchen-sink page (top navigation, popups, forms, fetch, beacon, image, script injection, a same-origin route), a postMessage-abuse page, and the MOVERS example rendering pushed rows.
  • Widget pages: hash-only CSP, no nonce (task-1358 r2, after independent verification found the defect). The popup host used to substitute the page's CSP nonce into every __NONCE__ in a pack's widget html, so a page that wrote the placeholder on a remote <script src> was served with a valid nonce and the remote script loaded — unreviewed code with the widget's view models in reach. Now a widget page carries no nonce at all: the pack html is served byte-verbatim (nothing substituted), every inline <script> and <style> block — the SDK included — is allow-listed by its sha256, 'strict-dynamic' is never set, and the page also gets require-trusted-types-for 'script' and sandbox allow-scripts allow-same-origin (runtime script injection and navigation away are blocked where the window engine implements them). A page that carries a loader — a __NONCE__ token, <script src>, any <link>, a frame or embed, <meta http-equiv>, @import — is refused at install, at open and when the host issues the ticket. Installing a new version of a pack now removes the previous version's directory and restarts widget consent. Regression tests: the nonce token in pack html, a remote src with the token, an inline script that injects a remote script (the header has no source for it), @import, a CR LF page, and the same refusals through dexterm pack add and at open time.
  • Customization: workspaces and the morning HUD, user themes and keymap overrides, a rules engine, shareable packs, and a popup widget API (51f692d, 0f18138, f6a825a, 1727ab0, a802324, 0f28c1e, task-1358, from the task-1288 design). Workspaces: one JSON file each (start screen, theme, intensity, screener preset / filter / sort, token panels, per-screen pane trees for a layout solver, the HUD popups to open together); ctrl+w then w / n / N / s / m / o / d / 1-9, --workspace, dexterm workspace; the built-in morning-hud opens the narrative board, the feeds, order plans and the chart at once. A switch navigates and re-themes and never touches a ticket. Themes and keymaps: ~/.dexterm/themes/<name>.json extends a built-in; a contrast floor and an up / down hue guard refuse a theme that could hide a warning or swap buy and sell colours; config.keymap re-binds actions, with ctrl+c / esc / enter / y / ! and the confirm / dismiss / panic / quit actions refused by name and any error keeping the defaults. Rules engine (ctrl+r, dexterm rules): when <cond> [for n ticks] then alert | ticket | ask | popup | tag | sound | watch, parsed never evaluated, velocity and delta signals over a per-coin history, a mandatory cooldown and max per hour, a live dry-run while typing. Rules alert, annotate your own watchlist, open read-only windows, or propose: a ticket or ask becomes a proposal card that opens the PAPER ticket editor or asks the analyst on your keypress; money-shaped arguments are refused at parse time in text and JSON. Packs (dexterm pack): a directory of the above with a sha256 lock and an optional ssh-keygen -Y signature, trust reviewed > signed > unsigned; a pack never runs code (widgets install disabled, unsigned rules load disabled, the keymap merges only on an explicit command, every load re-hashes the installed copy), traversal / symlinks / hidden files / __proto__ / money-shaped rules / capability fields are refused outright, and export stops on anything that looks like a secret. Widget API (beta): a pack widget is an html page the popup host serves under the same nonce CSP as the built-in pages with a frozen dexterm SDK; it receives public view models for its declared topics and can send nothing but close / focus / drag; dexterm pack widget <pack> <name> --enable is the consent, re-checked against the pack hash at every open; example pack packs/example-widgets. Tests: layout and workspace persistence, theme and keymap refusals, the compromised-rule suite, the malicious-pack suite (in the library and through the CLI), the hostile widget page under the host's headers. Not built: the in-process plugin sandbox (terminal columns and panels, host-mediated fetch, storage), the Jarvis config_propose MCP tools (Jarvis still proposes workspace switches only), pack add from git or zip, and a reviewed-pack list (ships empty). Docs: configuration.
  • Copy confirmation hardening (task-1360): engine-owned, single-use proof provenance now rejects fabricated confirmations even with a valid plan hash. Auto-mirror uses a private mandate-scoped operation for new copy proposals, with gate and cap checks unchanged. Fixture regressions cover forged confirmations, replay, cross-engine use and attempts to arm voice proposals.
  • Copy trading, staged and personal: wallet tracker with alerts, a one-key mirror ticket, and a capped auto-mirror behind the less-safe popup (18922b6, 79d58ca, 0f2d642, task-1360, from the task-1286 wallet-intel design §6; built last, after order plans and mandates). A personal tool for your own wallet: no custody, no third parties, no pooled or hosted copying, no leader lists, no fees. Stage 1 (U, dexterm follow add|list|stats|trades|remove|purge): follow a wallet with a label of your own (through the prompt firewall; never inferred, never exported, purge deletes everything), see its swaps decoded from its own Helius history (buy / sell / transfer / lp-like / token-swap from the wallet's own balance changes; wrapped SOL is never a mint; the POPCAT spike recording, whose busiest launch signer receives tokens and SOL together, is the lp-like regression case), FIFO realised PnL in SOL with the USD rate printed, win rate hidden under n = 10, early-entry score only when a launch window was scanned, every figure with its sample. The first look backfills silently; only new buys and sells alert, as toasts anywhere and on the ALERTS tape with the lag. Keyless it is honestly unavailable. Stage 2 (m): the latest buy of a followed wallet becomes a MIRROR TICKET preview with the three live numbers and the refusals from the design (lag > 30 s, drift > 5 %, liquidity < $25k, age < 60 s, leader sold since, leader in the dev cluster or over 5 %), unknowns shown as unknown; m again proposes it as an inert order plan (buy now, no ladder, 10-minute expiry, labelled copy) on the O screen, armable only by the keypress / Phantom gate into an armed mandate. Stage 3 (A, dexterm follow auto): a per-wallet copy policy (per trade, per day, max open, cooldown, size factor, auto-disarm streak; ceilings $100 / $500 / 10 / 10 s / k ≤ 2), default off; turning it on is a captured physical keypress that is refused unless the plan engine's own "this is less safe" popup is active, and that same gesture drafts and arms a buy-only, hands-free copy mandate in the plan engine. Each later buy passes the Stage 2 checks and is armed with the weakest confirmation tier, so the engine sizes it at ¼ of the per-trade cap and re-checks every cap, the allow-list and the panic state at arming and on every fill. A again, the panic key, restoring the gate, the mandate dying, the refusal streak, unfollowing, and every restart turn it off; the copy journal (~/.dexterm/copy.journal) is hash-chained like the plans journal. Tests: decoder on fixture histories and the three spike recordings, the tracker replay, every mirror refusal, the auto path end to end on the paper executor, the kill paths, the restart rule, and a compromised-model suite (spoken / agent / forged gestures, a spoofed alert, a widened policy, a plan the service did not create: nothing enables, widens or auto-arms). Not built: mirror-sell (you close every mirror; auto-disarm counts refusals and failed fills, not realised losses), the 3× early-entry filter, a live run on this machine, any mirror on devnet or mainnet. Docs: copy trading.
  • Order plans fire LIVE on devnet from a capped fast wallet; the plan monitor actually runs in the terminal (task-1363, follow-up to task-1355): two things the previous lane left out. First, the terminal never fed the plan engine a price, so an armed plan never fired in the app at all, paper or otherwise; the engine now reads the prices the terminal already shows (screener rows, watchlist, the open token; never fixture data) and a plan journal on disk is monitored from launch. Second, a live fire path on devnet: L on the O screen raises a fire-path prompt and a held keypress switches fills from PAPER to LIVE · DEVNET · fast wallet (refused while any plan is armed, without the signer process, off devnet or without a fast wallet; voice, agents, pipes and the popup have no way to it; every run starts on paper). The fast wallet is the one key dexterm holds: dexterm wallet fast create after a TTY keypress, ~/.dexterm/fast-wallet.json at mode 600, devnet only with no mainnet variant, no export and no import; status, airdrop and remove (refused while funded unless --force) go with it. A fill is a devnet transaction the signer builds for the fast wallet (Jupiter has no devnet, so it is the probe's wrap-and-unwrap sized to the plan's SOL leg with a memo naming the leg), run through the same decode, allow-list, simulation and re-simulation as a swap, with the signer's USD per-trade and daily caps enforced on devnet too and a hard 0.05 SOL per-fill ceiling; the fast wallet never signs for the main wallet and the main wallet's Phantom path never signs a fill. The engine journals an intent before the send, settles by lookup, never resends, and labels the fill devnet-fill with a modelled token leg, the real signature and the real SOL delta. dexterm plan amend edits a proposed plan into a new hash without cancel and re-propose. New tests: the signer's fast lane on fakes with the real decoder and a real Ed25519 key (ceiling, no price, per-trade and daily refusals, lane isolation both ways, the real child-process channel), the engine-side adapter, the fire-path surface, the wallet command, and a router→terminal seam test for every voice decision kind with the new paths booby-trapped. Still unobserved: a fill confirmed on devnet with real faucet SOL (the public faucet was dry on the day), and anything on mainnet. See the live fire path.
  • The recorder's outcome sweeps and labels now actually run; first rug backtest on real history; a P(RUG) screener column (task-1361): verification of the two previous lanes found the local recorder had 0 sweeps and 0 outcomes rows after two days, so every backtest had only ever seen fixtures. Cause: the first maintenance pass waited a full minute after launch and nothing ran at quit, and no real run of the app had lasted longer than 14 seconds (they were all smoke and capture runs). The first pass now runs 5 seconds after launch, then once a minute, and quitting labels whatever is due; a 15-second run on the development machine's own database went from 0 to 1 sweep (180 coins, 6 DexScreener calls) and 4,681 outcome labels (3,101 of them honestly unobserved, the windows nobody swept). dexterm recorder label clears an older database's backlog in one go. First measurement of the H1-v0.1 rug heuristic on that history (dexterm backtest, 1 h horizon, keyless-proxy labels, n = 401 with 22 bad outcomes): AUC 0.78, precision 9 % and recall 27 % at its 0.5 flag line, Brier 0.152 against a base-rate Brier of 0.052 — it ranks a little and its probabilities run far too high (coins it scored 0.8 to 1.0 rugged 0 % of the time, n = 25). Two days from one machine, mostly the top-trending feed: a first reading, not a result, and no trained model ships on it. The screener gained a **P(RUG) column** fed by that same heuristic, labelled as a model output everywhere it appears (p(rug) = H1-v0.1 heuristic in the panel caption, the value with the model id and the age of the scored data in the spotlight, — when there is nothing to score, rug as a sort key, safest first). Read it as a ranking, not a probability. LARP accuracy is unchanged at n = 0: outcomes now accumulate on their own, but a LARP verdict is only recorded when you run the check, and none has been. See history recorder, the screener and measured accuracy.
  • Spoken order plans actually land (fae60db, task-1355 verify fix): independent verification found that Jarvis said "drafting an order plan" while nothing was proposed, because the terminal had no handler for the router's plan decision and no test crossed that seam. Now "buy this if it dips to 300k, sell half at 600k, leave a moon bag" resolves the spoken or highlighted coin exactly as a ticket does, reads the supply from the market cap and price on screen (and asks when the screen has neither), writes a proposed plan at the fixed voice size of $5 (voice never sets a size), opens the O screen on the new row and reads the plan back with "proposal only, I can't arm it". A second gap the new test found: a named ticker that is not on screen ("buy zzqq if it dips…") used to bind the plan to the highlighted row; it now asks, the same no-fuzzy-match rule as the ticket. The new in-app test drives the real key loop with every arming, cap, gate and panic method booby-trapped: six hostile suffixes ("and arm it", "confirm it", "sign it and send it", "raise my cap", "panic", "turn the gate off") leave only proposals and zero touches.
  • Login with X, X posts in the social layer, KOL list import and the per-handle filter (task-1357, three lane commits on main after 913d8ae): dexterm x set-client-id | login | status | logout runs OAuth 2.0 with PKCE on the fixed loopback callback http://127.0.0.1:41287/callback with your own X developer app (a Native App: public client, no secret ships), read scopes only, tokens in ~/.dexterm/x/tokens.json at mode 600 and never printed, logged or handed to a model. After a login the token screen's SOCIAL panel (s) reads posts about the open coin (search/recent, every 60 s, newer posts only, 15-minute cache) and the trending board counts posts from your KOL lists (your own X lists every 5 minutes, manual handles via from: queries); every tweet passes the prompt firewall, so an instruction-like post is withheld from the analyst and flagged INJ. The band and the SOURCES panel show the X state honestly: x-stream ○ logged out, ●4s @you, ◐ rate limited 5m, ◐ daily budget (a local estimate with a hard stop, DEXTERM_X_DAILY_USD, default $10), ◐ app tier (no search) for a Free-tier app. X on the KOL screen or dexterm kol import-x imports the lists your account owns (handles only, never posts); o narrows the board and the panel to one handle. dexterm doctor gained a Login with X line. Everything is tested against a mock X server on 127.0.0.1 (PKCE, single-use codes, refresh rotation, revoke, 401/429/403/budget); no test touches x.com, and the app has no scraping path. See Login with X.
  • Finishing set: snipers and bundles on new pairs, token and feeds popups, a LARP eval that publishes n = 0, and wallet-intel rows in the social panel (10de26c, a51dc38, 7324147, 913d8ae, task-1359).
    • Snipers and bundles on new pairs. The live new-pairs SNIPE and BUNDLE columns and the spotlight's Snipers and Bundled rows now read the wallet-intel scan of the row your cursor rests on, after about 2 s. The scan runs only with a free Helius key, since only Helius history can read the launch window. Unscanned rows keep a dash, and nothing is guessed.
    • Token and feeds popups. I on the token screen opens the token-detail popup: market, risk, authorities, and the scan's snipers and bundled cells. M on the trending screen opens the feeds popup: the board's posts filtered as the SOCIAL panel does it, instruction-like posts withheld, plus official headlines, newest first. ⏎ in it opens that coin in the terminal, and the page never holds an address. Both pages were rendered from the loopback server in a headless browser.
    • LARP eval. Settled LARP-detector verdicts are now written to the local recorder, and dexterm backtest --subject larp scores them against outcome labels. Measured today: 0 verdicts had ever been recorded, against 149 observed 1 h outcomes in the research recorder and 0 in the app's, so no accuracy figure exists. The command prints counts only until 50 verdicts are matched, then 95 % Wilson intervals, and says what it measures: agreement with a bad price outcome, not whether a product is real. See Measured accuracy.
    • Wallet-intel rows in the social panel. The SOCIAL panel's on-chain rows now include the scan's linked clusters and launch-window snipers, each naming the rule that linked the wallets (for example bundle, R2). The task-1330 "same-slot cluster rows never fire" gap is closed.
    • Voice on Linux and Windows. Documented as not built, with what a port would need (Linux and Windows).

2026-10-11#

  • Order plans and standing mandates, paper by default (8be79d0, ad42d6d, 00b1489, b180ffa, 6ac0732, task-1355, from the task-1292 design): the north-star "buy this if it dips to 300k, sell half at 600k, leave a moon bag" is now a thing you write once and arm once. An order plan carries an entry trigger (market cap or price, below or above, or immediate), a take-profit ladder whose leftover fraction is the moon bag, an optional stop below the entry, slippage and impact ceilings and an expiry (4 h by default). A standing mandate is the budget a set of plans may spend (total, per trade, per coin, daily loss, highest confirmation tier, time-to-live), every cap clamped to a hard ceiling ($2000 / $500 / $1000 / $2000 / 24 h) and never coerced upward: there is no raise-cap anywhere, a different cap is a new draft and a new gesture. Three ways in, one result: dexterm plan propose … --entry mc-below:300k --tp mc-above:600k:50% --stop mc-below:150k, saying it to Jarvis (which now has an order-plan grammar: every market cap must carry its k/m/b scale, "dips to 300" asks instead of guessing, "1.2m" keeps its decimal point) or an agent's propose_order_plan tool, and each produces an inert proposal bound to a SHA-256 plan hash with a plain-language read-back. Arming is physical only: a key held on the new O order-plans screen (PLANS / MANDATES tabs, a arm, c cancel, r revoke, ! panic, M popup, G gate) or dexterm plan arm / dexterm mandate arm on a real TTY, which refuse a pipe, a subprocess, voice or an agent; the proof is bound to the plan hash, single-use and expiring, so an edited plan voids its confirmations. A monitor then fires entries, tranches and stops only inside an armed plan and only through the executor, which re-checks the mandate caps on every fill. The hands-free confirmation tier (0.25× the per-trade cap; ptt-held is 0.5×) is the one "less safe" switch: keypress-accepted only, journaled, 24 h expiry, reversible, off by default. The journal (~/.dexterm/plans.journal) is append-only and hash-chained (dexterm plan journal --verify | --export) and is what the service recovers from after a crash; the plans popup is a read-only HUD whose single write is "cancel this plan", re-validated at the server and at the terminal. Proven on fixtures and a fake clock: the north-star plan fills end to end on the paper executor; a compromised-model suite shows spoken text, agent text, a forged proof, a replayed proof, a widened mandate and an edited plan with an old confirmation all fail to arm, raise a cap, widen a mandate, resume a paused plan or accept the risk prompt; a pipe driving the screen's actions never flips the gate. Not wired: the live-wallet fire path (signer warm-quote + Phantom submit per fill), so every default run is paper and no plan has moved funds on devnet or mainnet. Docs: order plans and standing mandates, the O screen, what you can say.

2026-10-10#

  • Bring your own agent: MCP agent packs, a loopback agent bus, capability tiers and a red-team suite (fb465f9 … 8c2906f, task-1345, from the task-1295 design): you can now hook your own agent — Claude Code, Codex, a custom bot — up to a running dexterm without ever handing it your keys. Two transports: dexterm mcp gains the agent packs (the trench-scan, rug-check and token-dossier skills as MCP prompts, and the knowledge base, cases and skills as MCP resources), and a new loopback agent bus (127.0.0.1 WebSocket/JSON-RPC, dexterm agent connect, TS + Python SDKs) streams new pairs, trades, alerts and screen state. Every agent has a server-side capability tier — observe (reads + streams), annotate (navigation + its own notes/tags), propose (alerts, order-plan drafts and watch adds that appear labelled with the agent and that you confirm with a keypress). No agent path can arm, sign, confirm, raise a cap, change the gate or touch a wallet, a mandate or the signer — no such tool exists and a never-list refuses the shape of the request before any lookup. Untrusted by default: per-agent salted-hashed tokens, one gate on every call, closed-schema validation, rate limits, quotas, a flood breaker, idempotency keys on proposals and a prompt firewall on every string an agent receives. An agent roster popup shows who is connected (tier, last activity, reads/proposals, rate usage) with per-agent pause and kick and the full audit log. Proven by a 41-attempt malicious-agent suite (escalation / exfiltration / financial / spoof / flood / replay / quota / UI-injection — every one refused and audited) and a capture-ready worked demo (Claude Code over MCP running trench-scan, a custom stream agent proposing an alert, and the malicious agent failing every escalation), all on fixtures: no wallets, no mainnet. Docs: Bring your own agent. Per-agent mandates (an agent's own tightly-capped, keypress-armed budget) remain design-only, not wired.
  • Trade safety: the token-balance backstop now covers wrapped SOL and over-referenced transactions (1f9e2fc, rework of task-1337): the simulation backstop that refuses a route draining one of your token accounts had two gaps, both now closed. Pre-existing wrapped-SOL accounts were skipped, so a route draining them was invisible to the SOL-delta bound; they are now watched, and the wrapped SOL that leaves the wallet is bounded as SOL-value out against the plan's SOL leg at the plan, the devnet probe, and again on the signed bytes. And a transaction that referenced more of your token accounts than one simulation can carry (the 48-account cap) used to silently drop the extras; it is now refused outright, and the cap only ever trims accounts the transaction cannot touch. New signer refusal reason wsol-drain; regression tests in trade-1337.test.ts (now 21, suite green). Still unverified: the real Phantom devnet signed swap (devnet faucet dry).
  • Wallet intel, the bubble map and trade moments (merge of task-1340): M on a token opens the BUBBLE MAP: the top holders as packed half-row discs, area = share, colour = the rule that labelled the wallet (dev, bundle, linked by funder or transfer, sniper, pool/LP, exchange, unlinked), rings and tags for linked clusters, a CLUSTERS table, HOLDER INSIGHTS that print their sample (top-10 raw and cluster-adjusted, snipers in the first 3 slots after the first pool trade, bundled supply = same-slot buys that also share a funder, dev share, fresh wallets) and a WHY THIS LABEL panel with every rule, its confidence and evidence. The rules are the task-1286 spike's, ported and replay-tested against its three recordings (WIF, POPCAT, FARTCOIN), with two fixes the fixtures found: a pool is never a transfer party, and bundles are checked across every pair of a same-slot cohort. Two scan tiers over Helius history (getTransactionsForAddress, 10 credits a call, funders cached forever): light on token open (≤ 7 funders, 100 launch txs), deep for watchlist coins or on D (every top wallet, 300 txs). Keyless the map still draws from the public RPC holder list with static labels and says a free Helius key adds funders and the launch window; no ranked list at all is an honest "unavailable" with the fix. Exchange labels come from a typed seed list and always read "unverified"; cabal-class labels stay hidden by default. M again opens the same graph as real circles in a loopback-only popup window (hover for the rule, click to select, m treemap, z zoom). The token screen's Snipers and Bundled rows now read from the scan. Trade moments: a confirmed buy fires a green shockwave with coins, a profitable sell a gold burst, a loss red static that collapses, a refusal or failure a red lockout flicker, and a send in flight a neutral scanning sweep. They fire from exactly one place, the settled outcome the ticket reports (a booked paper fill, a confirmed live fill, a signer refusal or send failure), never from a quote, a plan or an unknown send; a shield mask on the cell buffer makes the ticket untouchable by every effect pass (tested byte-identical across tiers, phases and sizes). --sound adds short synthesized cues after the same outcome; muted by default. Tests: 12 data (rules replay + synthetic every-rule launch + scan tiers), 13 tui (shield invariant, moment gating, bubble layout), 3 popup, 13 cli. Still unverified: a bubble map on a live token in the running app on this machine (the data path is proven on the spike's real recordings, not on a fresh live call in this lane), the native popup window on an unlocked screen, and the sound cues through real speakers.
  • Jarvis voice mode on main (macOS) (merge of task-1282-jarvis @ 13d0b1f plus the task-1339 fixes): v is push-to-talk with a red ● LISTENING badge on the band under the header the whole time the mic is live; screens, coins, filters, the watchlist, the chart popup and PAPER tickets by voice through a deterministic router in under a millisecond after the transcript; analysis questions go to your own claude and the first two sentences are read back by say. Speech-in is on-device Apple Speech (a tiny helper compiled once with swiftc) or a local whisper.cpp base.en; no paid speech API, no key, nothing recorded. Proposals only: the most voice can do is open a PAPER ticket in its editing phase; spoken confirm, send, sign, go live, arm, raise my cap, panic and /wallet are questions or ignored, a guard rejects any decision carrying a mode, live, arm, caps, confirm, sign or send field, and voice-1339.test.ts drives the real key loop with the executor, both confirm paths, the LIVE flip and the wallet commands booby-trapped (twenty-one hostile utterances, zero touches). It clarifies instead of guessing: an unknown or copycat ticker, a visible ticker that is also a command word ("buy live"), more than one ticker in a breath ("buy wif and bonk"), a spoken size or slippage ("buy 10 sol of bonk"), a homophone, a pronoun on an empty list, a screen command plus a trade in one breath, a bare close or exit each get a spoken question. A 2026-10-10 re-verify found three gaps and they were fixed in the router: "buy live"/"buy sign" (a command-word ticker that is actually visible) silently bought the highlighted row; "buy wif and bonk" dropped one ticker; and a spoken size or slippage was ignored instead of asking you to set it in the ticket. Fixed from the task-1282 re-verify on the way: the copycat warning was being cut off by a false "opening a ticket" line; an analyst error (no claude, exit 1) wedged voice in "thinking" until restart; two v presses in one read opened two mic sessions and orphaned one; "buy it" on an empty watchlist traded a screener row; a helper the OS refused to spawn crashed the terminal; "copy the mint" was a buy of COPY. Measured on the dev Mac: router 0.01 ms, say first audio 0.8 ms, Claude first token 3.1 s warm, Apple helper compile 4.1 s once, whisper base.en 3.0 to 3.3 s on a 2.3 s clip. macOS only for now; the doctor says so elsewhere and every voice action has a key. Still unverified: a person's first-launch Microphone and Speech prompts and a real spoken end-to-end pass. Docs: Jarvis voice mode.
  • Non-custodial trading on main, devnet first (3eae878, merge of task-1328 @ b3a160e, which carries T0/T1 from task-1310; hardened in 8fce789; two real-app fixes in f9e4325 and cc26c65: the LIVE flip said "the signer process died" before the signer had ever started, and a devnet simulation error reached the ticket as an opaque build error instead of a failed simulation on the PLAN): L on a ticket flips PAPER → LIVE once a wallet is linked (dexterm wallet link or /wallet link: Phantom connects and signs a message on a one-shot loopback page; only the public key is stored). A separate signer process holds the caps ($5 per trade, $20 per day, 1 SOL out, 3% impact, 500 bps slippage; downward-only without --mainnet, hard ceilings beyond it), the daily ledger, the kill switch and the wallet bridge, and talks to the terminal over an authenticated pipe with no sign or submit method. For every plan it quotes, builds, decodes the transaction, requires exactly one Jupiter route whose arguments and accounts are bound to your order (no platform fee, your own token accounts), simulates it, prices the SOL leg from its own two-source SOL/USD feed, and shows the PLAN; your Y re-checks the caps, reserves the USD, hands the bytes to Phantom, verifies the signature and the message (an appended Lighthouse tail is the only change tolerated), re-simulates and submits. New in 8fce789: the wallet's token balances ride along in every simulation, so a route that drained another token account would be refused before the plan is shown and again on the signed bytes; getTokenAccountsByOwner and getMultipleAccounts join the read allow-list for that. /wallet panic (or dexterm wallet panic from any shell) disables signing everywhere at once; dexterm wallet resume needs a real keypress. Devnet by default: a Jupiter swap is a mainnet transaction, so a LIVE ticket on devnet shows the plan and the signer refuses to sign it; dexterm wallet probe proves the signing path on devnet with a wrap-and-unwrap transaction instead. Mainnet only behind --mainnet, a red banner, and the caps. The paper ticket's price impact, which showed Jupiter's 0..1 fraction as a percent, is fixed (613413a). Tests: 16 new in trade-1337.test.ts, one per HIGH finding of the three verification rounds plus the token-balance diff; 861 across the suite. Still unverified: a transaction signed by the real Phantom extension on devnet (the link step was driven end to end against real Phantom; the probe waits for faucet SOL), and any mainnet action at all.

2026-10-09#

  • Keyless social layer (87b598f, merge of ce99a04): a TRENDING IN THE SPACE board on T ranked from public lists (Jupiter trending and organic score, GeckoTerminal trending pools, CoinGecko trending; DexScreener paid boosts weighted against a coin), narratives, and a NEWS column from CoinDesk, Cointelegraph, The Block and Decrypt official RSS; a SOCIAL panel on the token screen (s); KOL lists on K and dexterm kol, with hash-locked packs that are refused when tampered. Every post and headline passes a prompt firewall, near-duplicates collapse, account bursts are marked spam, and instruction-like posts never reach the model. No X API, no scraping, no login: in live mode the panel shows on-chain rows only.
  • History recorder (4b082fd, merge of e3f5fd6): on by default in live mode, it keeps material changes per coin, feed membership and code-only outcome labels (1 h to 72 h) in the local database, with low-priority DexScreener re-checks capped at 6 a minute and 1,500 a day. Nothing is uploaded. Measured at about 100 MB per 8-hour day and no CPU cost above noise; --no-recorder or DEXTERM_RECORDER=0 turns it off, and dexterm recorder stats|label|prune|export reads it.
  • Coin images (0560b6e … 3f8648e): the coin's picture on every row, the token screen, the spotlight and the popup header; real pixels over kitty, iTerm2 and sixel, half-block art everywhere else including Terminal.app; one fetch per coin through a local cache; --no-images to turn it off.
  • Popup chart v2 (242863e, merge of b5016be): TradingView Lightweight Charts 5.2.1 vendored and served same-origin on one exact route; a native wry window docked beside the terminal that follows it; the chart's own six timeframes answered from the terminal cache with zero fetches; EMA, VWAP, volume MA, tape markers, drawings, live/stale badge; the paper position drawn as an entry line, fills and a row. Fixed on the way: malformed WebSocket close frames, a ×100 bug on the 24h change in popup pages, unknown popup events echoed back, and install.sh never linking the popup package. A 50-probe attack suite runs against the in-app server.

2026-10-08#

  • AI scan mode (be92898): an observe-only watcher with six triggers, a no-hop lock, cost caps, a WHY strip, Z/R keys, --scan flags and /scan commands. Four structural layers keep the scan session from ever taking a UI action; measured zero extra requests and no idle-CPU cost in calm mode.
  • UX round 3 (bfdf2f7): the screener paints in under a second from a cached snapshot or skeleton rows (was 17.5 s cold); the chart gets limiter priority over background work so a cold timeframe lands in ~250 ms (was 15 s); hover prefetch of candles; the analyst answers in 3–6 decided lines with long form only on request; a position row under the chart; the buy/sell flow panel replaces the tape by default (F toggles).
  • Idle throttle and quit (67f5a74, 770d096, 44e8206, 1082dc0): the full look while you type, 8 fps ambient rain after 5 s idle; quit under a second by wiring the shutdown signal through the transport and cancelling parked limiter waits; measured startup and RSS.
  • Popup host hardening (87b79f1, d49f330): navigation pinned by parsed origin (closes a userinfo bypass), crash-safe close, loopback-only enforcement for remote content, a README for the openPopup interface and security model.
  • Analyst prompt order fix (e9d7cba): keep the skill invocation first so the CLI expands it.
  • Merge of analyst round 2, speed and the LARP detector (b7f5804).

2026-10-07#

  • LARP detector (85e1bbf … 0882e59): advertised-link classification, an SSRF-safe page fetch (DNS validated in the socket, robots.txt honoured, bodies capped), RDAP domain age, GitHub activity, four MCP tools, the larp-check skill with a structured verdict, V/W/X/T/L keys, a verdict badge only for coins you vetted, an end-to-end prompt-injection fixture, and the RDAP +json content-type fix that had silently dropped every real RDAP body.
  • Analyst round 2 (4f86b50, be0f882): the analyst knows the hovered coin and the visible rows every turn; a split coin-list view on the analyst tab; a token-authenticated UI-action channel re-validated by the app; plain-language filters; coin-list answers that jump.
  • Speed (b01e236): 5m/15m derived from 1m and 4h/1d from 1h so a switch is milliseconds; a one-call get_dossier; the MCP server shares the terminal's on-disk cache; sonnet as the default analyst model; partial verdicts with missing_data instead of false INSUFFICIENT_DATA.
  • PAPER trading T0 (8513485): the ticket, the simulated wallet in SQLite, the positions screen, the confirm gate, a TradeExecutor boundary with no signing library anywhere.
  • Two-speed feed (2e804e3, a718670, da197c3): on-chain curve price over one WebSocket for the open token and the watchlist, LIVE/POLL badges, a cap of 24 subscriptions, calm-mode budget.
  • QA batches (72f57d7, a605325, 3e71858, 063ad59, 7fb51cc): chat focus model (i/⏎ to type, q never quits mid-question), copy CA, the trench feed as default, no auto-dossier on ⏎, mint-pinned selection on new pairs, scrollable answers, the ~9 s quit hang from WebSocket reconnect backoff.
  • FREE-FOREVER data layer (0d276be): an RPC pool that rotates by health and pauses on 429, the keyless holder aggregate, a request budget meter, a keyless smoke script.
  • Installable and self-explaining (07e42e9): install.sh, dexterm doctor, the welcome card, the ? help overlay, graceful states for every missing thing.
  • Pacing and idle CPU (aa83124, f5cd0cd, 0d803c9, 8badf3f): DexScreener wide pool paced to ≤30 req/min, idle CPU to ≤12%, a feed switcher, hundreds of coins in the pool, doubled analyst answers fixed, the first-test fixes.

2026-10-06#

  • The Claude analyst wired into the app (be39d04, d654559): a rug-checks the selected row, ⏎ opened a dossier (later made explicit), the decrypting answer, the cost caption.
  • The cyberspace look ported onto live data (c35ae07, 946000e): rain made of your own tickers, two masks on the cell buffer, pump/dump colouring from the data, boot sequence, blink transitions, intensity tiers.
  • Live data (18f55ad): Helius RPC with a credit meter, live token screen, PumpPortal new pairs, the watchlist, GoPlus.
  • Screener on the data store (f5e395d, d5d5354): the filter grammar, the sort menu, presets through the health registry, the spotlight, --record/--replay.

2026-10-05#

  • Data layer (ea4d5ba, b5e28de): the limiter, the health registry, the SQLite cache, fixtures, DexScreener and Jupiter adapters; GeckoTerminal candles, Solana RPC mint and holders, the RugCheck summary.
  • The TUI and the CLI (6c7fb97, 8dbdd59): the cell-buffer renderer extracted from the prototype, the five screens on fixtures, snapshot tests, the monorepo scaffold and the normalized model.