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 rundexterm. npm refuses the namedextermas too similar toxterm. This upload was published by hand, so it has no build provenance yet. Also: a go-to prompt (pressgto 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 kbanddexterm skilllist, 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 tapdexterm-xyz/homebrew-dexterminstalls the 0.1.0 release tarball. Homebrew 7 loads third-party taps only afterbrew trust dexterm-xyz/dexterm, so the install docs now showbrew 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-1358r6, 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, adownloador aping, 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-1358r5, 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.addand kin), so on a WebKit without Trusted Types a page could parse a<link rel=preconnect>into a detached document and adopt it throughtable.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 andexecCommandthrow,adoptNode/importNodeand 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 forupanddown(an HSL saturation floor) and a minimum perceptual distance between them (CIE76 ΔE ≥ 40), so a near-grey pair such as#7f807f/#807f7fis 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-1358r4, 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 (innerHTMLandouterHTMLsetters,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 amberupwith an amberdown(nowupmust read green/cyan anddownred/pink, every built-in still passes), and a rule written as__proto__ > 1orconstructor:xparsed 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@importonly where CSS is parsed (a<style>block or astyleattribute), 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 asdisplay.priceModeinconfig.json(now v4; an older file is migrated with a backup, andctrl+zundoes the switch);dexterm display [price|mcap],--price-modefor 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 saysmcap (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, never1000K), 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 saidneeds Helius keywhenever 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 onplan 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 answers429with noRetry-Afterbut withx-ratelimit-reset; lite-api.jup.ag answered503. 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 (orRetry-After, with a jittered backoff), at most three attempts in ten seconds; the data layer derives the wait from the reset whenRetry-Afteris absent; the signer gains a read-onlyquoteso 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 answersrate-limitedwith 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) showedYou receive 0 AVGOx— a formatter that rounded token quantities to whole numbers — andSimulation 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, andPlanView.mintOnNetworksays 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 carriesDEVNET 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 calledsignTransactionon 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.tsdrives the page's own script against a fake Phantom that answers 4100 unlessconnect()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-1358r3, after a second independent verification found the gap). The r2 page was served top-level, and a document may always navigate itself —sandboxonly stops it navigating other windows — so under the browser fallback a page containing justlocation.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; itsframe-srcnames exactly one route) holding the pack page in<iframe sandbox="allow-scripts">, served under the hash-only CSP withconnect-src 'none'(no network at all), an opaque origin (no cookies, storage or reach into the wrapper) andframe-ancestors= the host. The parent'sframe-srcgoverns 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 bypostMessage; 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-1358r2, 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 getsrequire-trusted-types-for 'script'andsandbox 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 remotesrcwith 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 throughdexterm pack addand 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+wthenw/n/N/s/m/o/d/1-9,--workspace,dexterm workspace; the built-inmorning-hudopens 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>.jsonextends 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.keymapre-binds actions, withctrl+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 optionalssh-keygen -Ysignature, 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 frozendextermSDK; it receives public view models for its declared topics and can send nothing but close / focus / drag;dexterm pack widget <pack> <name> --enableis the consent, re-checked against the pack hash at every open; example packpacks/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 Jarvisconfig_proposeMCP tools (Jarvis still proposes workspace switches only),pack addfrom 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,purgedeletes 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, labelledcopy) 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 totask-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 createafter a TTY keypress,~/.dexterm/fast-wallet.jsonat mode 600, devnet only with no mainnet variant, no export and no import;status,airdropandremove(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 filldevnet-fillwith a modelled token leg, the real signature and the real SOL delta.dexterm plan amendedits 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 0sweepsand 0outcomesrows 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 honestlyunobserved, the windows nobody swept).dexterm recorder labelclears 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 heuristicin 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,rugas 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-1355verify 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 after913d8ae):dexterm x set-client-id | login | status | logoutruns OAuth 2.0 with PKCE on the fixed loopback callbackhttp://127.0.0.1:41287/callbackwith your own X developer app (a Native App: public client, no secret ships), read scopes only, tokens in~/.dexterm/x/tokens.jsonat 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 viafrom:queries); every tweet passes the prompt firewall, so an instruction-like post is withheld from the analyst and flaggedINJ. 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 ordexterm kol import-ximports the lists your account owns (handles only, never posts); o narrows the board and the panel to one handle.dexterm doctorgained 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 larpscores 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 thetask-1292design): 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'spropose_order_plantool, 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) ordexterm plan arm/dexterm mandate armon 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-heldis 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 thetask-1295design): 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 mcpgains the agent packs (thetrench-scan,rug-checkandtoken-dossierskills as MCP prompts, and the knowledge base, cases and skills as MCP resources), and a new loopback agent bus (127.0.0.1WebSocket/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 oftask-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 reasonwsol-drain; regression tests intrade-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).--soundadds 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@13d0b1fplus the task-1339 fixes): v is push-to-talk with a red● LISTENINGbadge 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 ownclaudeand the first two sentences are read back bysay. Speech-in is on-device Apple Speech (a tiny helper compiled once withswiftc) or a local whisper.cppbase.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/walletare questions or ignored, a guard rejects any decision carrying a mode, live, arm, caps, confirm, sign or send field, andvoice-1339.test.tsdrives 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 (noclaude, exit 1) wedged voice in "thinking" until restart; twovpresses 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,sayfirst audio 0.8 ms, Claude first token 3.1 s warm, Apple helper compile 4.1 s once, whisperbase.en3.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 oftask-1328@b3a160e, which carries T0/T1 fromtask-1310; hardened in8fce789; two real-app fixes inf9e4325andcc26c65: 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 linkor/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 nosignorsubmitmethod. 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 in8fce789: 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;getTokenAccountsByOwnerandgetMultipleAccountsjoin the read allow-list for that./wallet panic(ordexterm wallet panicfrom any shell) disables signing everywhere at once;dexterm wallet resumeneeds 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 probeproves 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 intrade-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 ofce99a04): 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 anddexterm 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 ofe3f5fd6): 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-recorderorDEXTERM_RECORDER=0turns it off, anddexterm recorder stats|label|prune|exportreads 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-imagesto turn it off.
- Popup chart v2 (
242863e, merge ofb5016be): 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, andinstall.shnever 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/Rkeys,--scanflags and/scancommands. 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 (Ftoggles). - 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 theopenPopupinterface 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, thelarp-checkskill with a structured verdict,V/W/X/T/Lkeys, a verdict badge only for coins you vetted, an end-to-end prompt-injection fixture, and the RDAP+jsoncontent-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-callget_dossier; the MCP server shares the terminal's on-disk cache; sonnet as the default analyst model; partial verdicts withmissing_datainstead of false INSUFFICIENT_DATA. - PAPER trading T0 (
8513485): the ticket, the simulated wallet in SQLite, the positions screen, the confirm gate, aTradeExecutorboundary 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,qnever 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):arug-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.