dexterm-cli 0.0.0-stage → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/dist/packages/agents/src/audit.js +122 -0
- package/dist/packages/agents/src/bus.js +394 -0
- package/dist/packages/agents/src/executor.js +202 -0
- package/dist/packages/agents/src/gate.js +179 -0
- package/dist/packages/agents/src/identity.js +218 -0
- package/dist/packages/agents/src/index.js +15 -0
- package/dist/packages/agents/src/proposals.js +153 -0
- package/dist/packages/agents/src/schema.js +87 -0
- package/dist/packages/agents/src/sdk.js +140 -0
- package/dist/packages/agents/src/tiers.js +119 -0
- package/dist/packages/agents/src/tools.js +72 -0
- package/dist/packages/agents/src/ws.js +158 -0
- package/dist/packages/cli/assets/claude/analyst-system.md +76 -0
- package/dist/packages/cli/assets/claude/plugin/.claude-plugin/plugin.json +5 -0
- package/dist/packages/cli/assets/claude/plugin/skills/REGISTRY.json +16 -0
- package/dist/packages/cli/assets/claude/plugin/skills/larp-check/SKILL.md +75 -0
- package/dist/packages/cli/assets/claude/plugin/skills/rug-check/SKILL.md +74 -0
- package/dist/packages/cli/assets/claude/plugin/skills/token-dossier/SKILL.md +63 -0
- package/dist/packages/cli/assets/claude/plugin/skills/trench-scan/SKILL.md +44 -0
- package/dist/packages/cli/assets/claude/schemas/larp.schema.json +117 -0
- package/dist/packages/cli/assets/claude/schemas/trench-scan.schema.json +71 -0
- package/dist/packages/cli/assets/claude/schemas/verdict.schema.json +115 -0
- package/dist/packages/cli/bin/dexterm.mjs +11 -0
- package/dist/packages/cli/src/agent-cmd.js +336 -0
- package/dist/packages/cli/src/agent-roster.js +26 -0
- package/dist/packages/cli/src/alerts/adapters/desktop.js +40 -0
- package/dist/packages/cli/src/alerts/adapters/discord.js +36 -0
- package/dist/packages/cli/src/alerts/adapters/telegram.js +43 -0
- package/dist/packages/cli/src/alerts/adapters/toast.js +16 -0
- package/dist/packages/cli/src/alerts/creds.js +83 -0
- package/dist/packages/cli/src/alerts/format.js +41 -0
- package/dist/packages/cli/src/alerts/index.js +10 -0
- package/dist/packages/cli/src/alerts/notifier.js +53 -0
- package/dist/packages/cli/src/alerts/route.js +19 -0
- package/dist/packages/cli/src/alerts/types.js +1 -0
- package/dist/packages/cli/src/alerts-cmd.js +212 -0
- package/dist/packages/cli/src/analyst/backend.js +526 -0
- package/dist/packages/cli/src/analyst/results.js +38 -0
- package/dist/packages/cli/src/analyst/session.js +319 -0
- package/dist/packages/cli/src/audit.js +51 -0
- package/dist/packages/cli/src/backtest-cmd.js +208 -0
- package/dist/packages/cli/src/boot-state.js +62 -0
- package/dist/packages/cli/src/coin-target.js +2 -0
- package/dist/packages/cli/src/custom.js +261 -0
- package/dist/packages/cli/src/display.js +191 -0
- package/dist/packages/cli/src/doctor.js +375 -0
- package/dist/packages/cli/src/export.js +235 -0
- package/dist/packages/cli/src/feed.js +634 -0
- package/dist/packages/cli/src/fixtures.js +275 -0
- package/dist/packages/cli/src/follow-cmd.js +221 -0
- package/dist/packages/cli/src/images-config.js +59 -0
- package/dist/packages/cli/src/index.js +31 -0
- package/dist/packages/cli/src/kb-skill-cmd.js +240 -0
- package/dist/packages/cli/src/larp-record.js +21 -0
- package/dist/packages/cli/src/live/copy.js +80 -0
- package/dist/packages/cli/src/live/flow.js +233 -0
- package/dist/packages/cli/src/live/index.js +511 -0
- package/dist/packages/cli/src/live/intel.js +227 -0
- package/dist/packages/cli/src/live/newpairs.js +334 -0
- package/dist/packages/cli/src/live/screens/newpairs-live.js +219 -0
- package/dist/packages/cli/src/live/screens/token-live.js +459 -0
- package/dist/packages/cli/src/live/screens/watchlist-live.js +149 -0
- package/dist/packages/cli/src/live/token.js +585 -0
- package/dist/packages/cli/src/live/trade.js +929 -0
- package/dist/packages/cli/src/live/watchlist.js +178 -0
- package/dist/packages/cli/src/main.js +5785 -0
- package/dist/packages/cli/src/mcp-cmd.js +326 -0
- package/dist/packages/cli/src/narrative-cmd.js +76 -0
- package/dist/packages/cli/src/pack-cmd.js +323 -0
- package/dist/packages/cli/src/plan-cmd.js +278 -0
- package/dist/packages/cli/src/popup.js +810 -0
- package/dist/packages/cli/src/rules-cmd.js +131 -0
- package/dist/packages/cli/src/rules.js +358 -0
- package/dist/packages/cli/src/scan/command.js +57 -0
- package/dist/packages/cli/src/scan/engine.js +419 -0
- package/dist/packages/cli/src/scan/observe.js +64 -0
- package/dist/packages/cli/src/scan/ui.js +98 -0
- package/dist/packages/cli/src/screener/filter.js +241 -0
- package/dist/packages/cli/src/screener/presets.js +80 -0
- package/dist/packages/cli/src/screener/rows.js +146 -0
- package/dist/packages/cli/src/screener/rug.js +50 -0
- package/dist/packages/cli/src/screener/sort.js +52 -0
- package/dist/packages/cli/src/screener/view.js +183 -0
- package/dist/packages/cli/src/screens/analyst-session.js +343 -0
- package/dist/packages/cli/src/screens/analyst.js +326 -0
- package/dist/packages/cli/src/screens/boot.js +74 -0
- package/dist/packages/cli/src/screens/bubbles.js +255 -0
- package/dist/packages/cli/src/screens/coin.js +33 -0
- package/dist/packages/cli/src/screens/follows.js +201 -0
- package/dist/packages/cli/src/screens/kol.js +90 -0
- package/dist/packages/cli/src/screens/narrative.js +90 -0
- package/dist/packages/cli/src/screens/newpairs.js +150 -0
- package/dist/packages/cli/src/screens/overlay.js +257 -0
- package/dist/packages/cli/src/screens/plans.js +126 -0
- package/dist/packages/cli/src/screens/positions.js +176 -0
- package/dist/packages/cli/src/screens/rules.js +170 -0
- package/dist/packages/cli/src/screens/screener.js +459 -0
- package/dist/packages/cli/src/screens/social-panel.js +107 -0
- package/dist/packages/cli/src/screens/ticket.js +342 -0
- package/dist/packages/cli/src/screens/token.js +312 -0
- package/dist/packages/cli/src/screens/trending.js +181 -0
- package/dist/packages/cli/src/screens/watchlist.js +104 -0
- package/dist/packages/cli/src/skill-eval-build.js +109 -0
- package/dist/packages/cli/src/social/demo.js +96 -0
- package/dist/packages/cli/src/social/view.js +250 -0
- package/dist/packages/cli/src/state.js +463 -0
- package/dist/packages/cli/src/theme-cmd.js +126 -0
- package/dist/packages/cli/src/trade.js +319 -0
- package/dist/packages/cli/src/update.js +294 -0
- package/dist/packages/cli/src/version.js +3 -0
- package/dist/packages/cli/src/voice-cmd.js +119 -0
- package/dist/packages/cli/src/voice-hud.js +42 -0
- package/dist/packages/cli/src/wallet-cmd.js +440 -0
- package/dist/packages/cli/src/wallet-command.js +31 -0
- package/dist/packages/cli/src/watch-cmd.js +190 -0
- package/dist/packages/cli/src/widgets.js +208 -0
- package/dist/packages/cli/src/workspace-cmd.js +68 -0
- package/dist/packages/cli/src/x-cmd.js +154 -0
- package/dist/packages/custom/src/config.js +213 -0
- package/dist/packages/custom/src/index.js +10 -0
- package/dist/packages/custom/src/keymap.js +107 -0
- package/dist/packages/custom/src/layout.js +235 -0
- package/dist/packages/custom/src/packs.js +633 -0
- package/dist/packages/custom/src/rules.js +704 -0
- package/dist/packages/custom/src/schema.js +538 -0
- package/dist/packages/custom/src/themes.js +176 -0
- package/dist/packages/custom/src/widget-html.js +222 -0
- package/dist/packages/custom/src/workspace.js +141 -0
- package/dist/packages/data/src/analyst-actions.js +133 -0
- package/dist/packages/data/src/budget.js +88 -0
- package/dist/packages/data/src/cache.js +142 -0
- package/dist/packages/data/src/candles.js +168 -0
- package/dist/packages/data/src/clock.js +64 -0
- package/dist/packages/data/src/coin-target.js +82 -0
- package/dist/packages/data/src/credits.js +137 -0
- package/dist/packages/data/src/detail.js +97 -0
- package/dist/packages/data/src/fast-lane.js +321 -0
- package/dist/packages/data/src/fixtures.js +135 -0
- package/dist/packages/data/src/health.js +245 -0
- package/dist/packages/data/src/http.js +277 -0
- package/dist/packages/data/src/images/cache.js +141 -0
- package/dist/packages/data/src/images/decode-worker.js +28 -0
- package/dist/packages/data/src/images/decode.js +81 -0
- package/dist/packages/data/src/images/fetch.js +162 -0
- package/dist/packages/data/src/images/gif.js +214 -0
- package/dist/packages/data/src/images/index.js +10 -0
- package/dist/packages/data/src/images/jpeg.js +544 -0
- package/dist/packages/data/src/images/png.js +310 -0
- package/dist/packages/data/src/images/service.js +334 -0
- package/dist/packages/data/src/images/types.js +27 -0
- package/dist/packages/data/src/index.js +211 -0
- package/dist/packages/data/src/intel/helius-history.js +70 -0
- package/dist/packages/data/src/intel/holder-class.js +125 -0
- package/dist/packages/data/src/intel/rules.js +421 -0
- package/dist/packages/data/src/intel/scan.js +170 -0
- package/dist/packages/data/src/larp/evidence.js +288 -0
- package/dist/packages/data/src/larp/link-baseline.js +196 -0
- package/dist/packages/data/src/larp/links.js +137 -0
- package/dist/packages/data/src/larp/robots.js +90 -0
- package/dist/packages/data/src/larp/safe-fetch.js +389 -0
- package/dist/packages/data/src/limiter.js +341 -0
- package/dist/packages/data/src/model.js +12 -0
- package/dist/packages/data/src/paper.js +331 -0
- package/dist/packages/data/src/rpc-config.js +126 -0
- package/dist/packages/data/src/rpc-pool.js +166 -0
- package/dist/packages/data/src/social/chain.js +83 -0
- package/dist/packages/data/src/social/dedupe.js +205 -0
- package/dist/packages/data/src/social/feed.js +462 -0
- package/dist/packages/data/src/social/firewall.js +110 -0
- package/dist/packages/data/src/social/index.js +11 -0
- package/dist/packages/data/src/social/kol.js +269 -0
- package/dist/packages/data/src/social/model.js +11 -0
- package/dist/packages/data/src/social/rank.js +184 -0
- package/dist/packages/data/src/social/rss.js +113 -0
- package/dist/packages/data/src/social/summary.js +104 -0
- package/dist/packages/data/src/social/venues.js +186 -0
- package/dist/packages/data/src/sources/dexscreener.js +184 -0
- package/dist/packages/data/src/sources/geckoterminal.js +133 -0
- package/dist/packages/data/src/sources/goplus.js +161 -0
- package/dist/packages/data/src/sources/jupiter-swap.js +236 -0
- package/dist/packages/data/src/sources/jupiter.js +198 -0
- package/dist/packages/data/src/sources/pda.js +113 -0
- package/dist/packages/data/src/sources/pump-curve.js +58 -0
- package/dist/packages/data/src/sources/pumpportal.js +252 -0
- package/dist/packages/data/src/sources/rugcheck.js +71 -0
- package/dist/packages/data/src/sources/solana-rpc.js +299 -0
- package/dist/packages/data/src/sources/solana-ws.js +287 -0
- package/dist/packages/data/src/store.js +289 -0
- package/dist/packages/data/src/trade/bridge.js +464 -0
- package/dist/packages/data/src/trade/caps.js +323 -0
- package/dist/packages/data/src/trade/copy/auto.js +385 -0
- package/dist/packages/data/src/trade/copy/follows.js +103 -0
- package/dist/packages/data/src/trade/copy/index.js +9 -0
- package/dist/packages/data/src/trade/copy/mirror.js +91 -0
- package/dist/packages/data/src/trade/copy/stats.js +120 -0
- package/dist/packages/data/src/trade/copy/tracker.js +158 -0
- package/dist/packages/data/src/trade/copy/trades.js +87 -0
- package/dist/packages/data/src/trade/copy/types.js +6 -0
- package/dist/packages/data/src/trade/executor.js +933 -0
- package/dist/packages/data/src/trade/kill-switch.js +47 -0
- package/dist/packages/data/src/trade/link-store.js +52 -0
- package/dist/packages/data/src/trade/plans/confirm.js +157 -0
- package/dist/packages/data/src/trade/plans/engine.js +784 -0
- package/dist/packages/data/src/trade/plans/executor-live.js +220 -0
- package/dist/packages/data/src/trade/plans/executor-paper.js +44 -0
- package/dist/packages/data/src/trade/plans/executor.js +11 -0
- package/dist/packages/data/src/trade/plans/index.js +12 -0
- package/dist/packages/data/src/trade/plans/journal.js +77 -0
- package/dist/packages/data/src/trade/plans/mandate.js +129 -0
- package/dist/packages/data/src/trade/plans/plan.js +179 -0
- package/dist/packages/data/src/trade/plans/service.js +168 -0
- package/dist/packages/data/src/trade/plans/types.js +6 -0
- package/dist/packages/data/src/trade/rpc-exec.js +229 -0
- package/dist/packages/data/src/trade/signer/client.js +197 -0
- package/dist/packages/data/src/trade/signer/fast-wallet.js +154 -0
- package/dist/packages/data/src/trade/signer/host.js +97 -0
- package/dist/packages/data/src/trade/signer/main.js +121 -0
- package/dist/packages/data/src/trade/signer/probe-builder.js +85 -0
- package/dist/packages/data/src/trade/signer/protocol.js +92 -0
- package/dist/packages/data/src/trade/signer/service.js +400 -0
- package/dist/packages/data/src/trade/signer/sol-usd-feed.js +98 -0
- package/dist/packages/data/src/trade/sol-usd.js +86 -0
- package/dist/packages/data/src/trade/swap-build.js +108 -0
- package/dist/packages/data/src/trade/token-delta.js +201 -0
- package/dist/packages/data/src/trade/tx-decode.js +632 -0
- package/dist/packages/data/src/util.js +24 -0
- package/dist/packages/data/src/watchlist.js +176 -0
- package/dist/packages/data/src/x/api.js +256 -0
- package/dist/packages/data/src/x/budget.js +66 -0
- package/dist/packages/data/src/x/config.js +49 -0
- package/dist/packages/data/src/x/index.js +10 -0
- package/dist/packages/data/src/x/mock-server.js +217 -0
- package/dist/packages/data/src/x/oauth.js +242 -0
- package/dist/packages/data/src/x/pkce.js +21 -0
- package/dist/packages/data/src/x/source.js +285 -0
- package/dist/packages/data/src/x/token-store.js +134 -0
- package/dist/packages/data/src/x/transport.js +71 -0
- package/dist/packages/data/vendor/solana-web3/LICENSE +20 -0
- package/dist/packages/data/vendor/solana-web3/NOTICE.md +13 -0
- package/dist/packages/data/vendor/solana-web3/index.iife.min.js +20 -0
- package/dist/packages/mcp/assets/cases.json +1194 -0
- package/dist/packages/mcp/assets/kb/entries/cabal-and-coordinated-launches.md +28 -0
- package/dist/packages/mcp/assets/kb/entries/common-rug-patterns.md +40 -0
- package/dist/packages/mcp/assets/kb/entries/dbc-mechanics.md +30 -0
- package/dist/packages/mcp/assets/kb/entries/dev-wallet-behaviour.md +33 -0
- package/dist/packages/mcp/assets/kb/entries/glossary.md +35 -0
- package/dist/packages/mcp/assets/kb/entries/holder-concentration.md +30 -0
- package/dist/packages/mcp/assets/kb/entries/jito-bundles.md +29 -0
- package/dist/packages/mcp/assets/kb/entries/launchlab-curve.md +31 -0
- package/dist/packages/mcp/assets/kb/entries/lp-burn-vs-lock.md +31 -0
- package/dist/packages/mcp/assets/kb/entries/metadata-and-copycats.md +29 -0
- package/dist/packages/mcp/assets/kb/entries/mint-freeze-authority.md +31 -0
- package/dist/packages/mcp/assets/kb/entries/narrative-lifecycle.md +32 -0
- package/dist/packages/mcp/assets/kb/entries/pump-bonding-curve.md +33 -0
- package/dist/packages/mcp/assets/kb/entries/pumpswap-migration.md +29 -0
- package/dist/packages/mcp/assets/kb/entries/sniping-and-snipers.md +29 -0
- package/dist/packages/mcp/assets/kb/entries/token2022-extensions-risk.md +32 -0
- package/dist/packages/mcp/assets/kb/entries/wash-trading-fake-volume.md +33 -0
- package/dist/packages/mcp/assets/skills/rug-check.md +52 -0
- package/dist/packages/mcp/assets/skills/token-dossier.md +57 -0
- package/dist/packages/mcp/assets/skills/trench-scan.md +40 -0
- package/dist/packages/mcp/bin/dexterm-mcp.mjs +16 -0
- package/dist/packages/mcp/src/dossier.js +78 -0
- package/dist/packages/mcp/src/firewall.js +21 -0
- package/dist/packages/mcp/src/index.js +22 -0
- package/dist/packages/mcp/src/server.js +936 -0
- package/dist/packages/mcp/src/user-content.js +434 -0
- package/dist/packages/packs/src/backtest.js +183 -0
- package/dist/packages/packs/src/digest.js +95 -0
- package/dist/packages/packs/src/firewall.js +57 -0
- package/dist/packages/packs/src/index.js +20 -0
- package/dist/packages/packs/src/larp-eval.js +100 -0
- package/dist/packages/packs/src/narrative.js +309 -0
- package/dist/packages/packs/src/reason.js +135 -0
- package/dist/packages/packs/src/rug-model.js +137 -0
- package/dist/packages/packs/src/schema.js +54 -0
- package/dist/packages/packs/src/skill-backtest.js +300 -0
- package/dist/packages/packs/src/token-dossier.js +174 -0
- package/dist/packages/packs/src/verdicts.js +17 -0
- package/dist/packages/packs/src/wallet-dossier.js +105 -0
- package/dist/packages/popup/src/config.js +71 -0
- package/dist/packages/popup/src/dock.js +271 -0
- package/dist/packages/popup/src/host.js +210 -0
- package/dist/packages/popup/src/index.js +12 -0
- package/dist/packages/popup/src/loopback.js +53 -0
- package/dist/packages/popup/src/open.js +183 -0
- package/dist/packages/popup/src/pages/bubbles-lib.js +160 -0
- package/dist/packages/popup/src/pages/bubbles.css +23 -0
- package/dist/packages/popup/src/pages/bubbles.html +36 -0
- package/dist/packages/popup/src/pages/bubbles.js +214 -0
- package/dist/packages/popup/src/pages/chart-lib.js +262 -0
- package/dist/packages/popup/src/pages/chart.css +50 -0
- package/dist/packages/popup/src/pages/chart.html +56 -0
- package/dist/packages/popup/src/pages/chart.js +554 -0
- package/dist/packages/popup/src/pages/common.css +34 -0
- package/dist/packages/popup/src/pages/common.js +187 -0
- package/dist/packages/popup/src/pages/feeds.html +20 -0
- package/dist/packages/popup/src/pages/feeds.js +77 -0
- package/dist/packages/popup/src/pages/narrative.html +20 -0
- package/dist/packages/popup/src/pages/narrative.js +61 -0
- package/dist/packages/popup/src/pages/plans.css +30 -0
- package/dist/packages/popup/src/pages/plans.html +26 -0
- package/dist/packages/popup/src/pages/plans.js +82 -0
- package/dist/packages/popup/src/pages/roster.css +41 -0
- package/dist/packages/popup/src/pages/roster.html +25 -0
- package/dist/packages/popup/src/pages/roster.js +75 -0
- package/dist/packages/popup/src/pages/token.html +20 -0
- package/dist/packages/popup/src/pages/token.js +112 -0
- package/dist/packages/popup/src/pages/widget-sdk.js +224 -0
- package/dist/packages/popup/src/pages/widget.css +5 -0
- package/dist/packages/popup/src/pages/widget.html +21 -0
- package/dist/packages/popup/src/pages/widget.js +85 -0
- package/dist/packages/popup/src/protocol.js +31 -0
- package/dist/packages/popup/src/selftest.js +156 -0
- package/dist/packages/popup/src/server.js +582 -0
- package/dist/packages/popup/src/sinkproxy.js +167 -0
- package/dist/packages/popup/src/widget-html.js +231 -0
- package/dist/packages/popup/vendor/lightweight-charts/LICENSE +201 -0
- package/dist/packages/popup/vendor/lightweight-charts/NOTICE +2 -0
- package/dist/packages/popup/vendor/lightweight-charts/README.md +30 -0
- package/dist/packages/popup/vendor/lightweight-charts/VERSION +1 -0
- package/dist/packages/popup/vendor/lightweight-charts/lightweight-charts.standalone.production.js +7 -0
- package/dist/packages/recorder/bin/dexterm-recorder.mjs +11 -0
- package/dist/packages/recorder/src/cli.js +113 -0
- package/dist/packages/recorder/src/export.js +71 -0
- package/dist/packages/recorder/src/index.js +20 -0
- package/dist/packages/recorder/src/labels.js +151 -0
- package/dist/packages/recorder/src/open.js +29 -0
- package/dist/packages/recorder/src/recorder.js +553 -0
- package/dist/packages/recorder/src/schedule.js +43 -0
- package/dist/packages/recorder/src/schema.js +135 -0
- package/dist/packages/recorder/src/snapshot.js +191 -0
- package/dist/packages/recorder/src/sweep.js +52 -0
- package/dist/packages/recorder/src/verdicts.js +52 -0
- package/dist/packages/tui/src/bubbles.js +415 -0
- package/dist/packages/tui/src/canvas.js +370 -0
- package/dist/packages/tui/src/chrome.js +85 -0
- package/dist/packages/tui/src/color.js +133 -0
- package/dist/packages/tui/src/flush.js +62 -0
- package/dist/packages/tui/src/fmt.js +188 -0
- package/dist/packages/tui/src/fx-trade.js +278 -0
- package/dist/packages/tui/src/fx.js +527 -0
- package/dist/packages/tui/src/images/blocks.js +141 -0
- package/dist/packages/tui/src/images/coin.js +22 -0
- package/dist/packages/tui/src/images/detect.js +140 -0
- package/dist/packages/tui/src/images/index.js +7 -0
- package/dist/packages/tui/src/images/layer.js +146 -0
- package/dist/packages/tui/src/images/pixels.js +157 -0
- package/dist/packages/tui/src/images/protocols.js +87 -0
- package/dist/packages/tui/src/index.js +18 -0
- package/dist/packages/tui/src/keymap.js +357 -0
- package/dist/packages/tui/src/layout.js +11 -0
- package/dist/packages/tui/src/raster-font.js +195 -0
- package/dist/packages/tui/src/raster.js +213 -0
- package/dist/packages/tui/src/sound.js +177 -0
- package/dist/packages/tui/src/theme.js +182 -0
- package/dist/packages/tui/src/widgets.js +598 -0
- package/dist/packages/voice/src/controller.js +407 -0
- package/dist/packages/voice/src/doctor.js +131 -0
- package/dist/packages/voice/src/download.js +184 -0
- package/dist/packages/voice/src/engines.js +84 -0
- package/dist/packages/voice/src/index.js +20 -0
- package/dist/packages/voice/src/router.js +502 -0
- package/dist/packages/voice/src/stt/apple-helper.swift.js +103 -0
- package/dist/packages/voice/src/stt/apple.js +179 -0
- package/dist/packages/voice/src/stt/fake.js +34 -0
- package/dist/packages/voice/src/stt/whisper.js +112 -0
- package/dist/packages/voice/src/summary.js +44 -0
- package/dist/packages/voice/src/tts/espeak.js +131 -0
- package/dist/packages/voice/src/tts/fake.js +30 -0
- package/dist/packages/voice/src/tts/piper.js +206 -0
- package/dist/packages/voice/src/tts/say.js +132 -0
- package/dist/packages/voice/src/tts/voices.js +206 -0
- package/dist/packages/voice/src/types.js +3 -0
- package/package.json +38 -4
- package/README.md +0 -3
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
// @dexterm/data — the X poller behind the social feed (task-1357). Attached to a SocialFeed; `tick(now)` is
|
|
2
|
+
// called from the feed's own tick (the UI's 2 Hz data tick), creates no timers, awaits nothing the quit path
|
|
3
|
+
// could wait on, and turns every failure into a VISIBLE state:
|
|
4
|
+
//
|
|
5
|
+
// logged-out no tokens (the header says `x ○ logged out · dexterm x login`)
|
|
6
|
+
// ok reads flowing (`x ● @handle · 12/300 search · $0.12 today`)
|
|
7
|
+
// rate-limited 429 or an exhausted window; shows the reset time; reads resume after it
|
|
8
|
+
// no-credit the daily spend budget is reached; resumes at the next UTC day
|
|
9
|
+
// not-enrolled the user's X app tier cannot read search/list timelines (Free tier)
|
|
10
|
+
// error transport / 5xx; retried on the next cycle with backoff
|
|
11
|
+
//
|
|
12
|
+
// What it polls (design §5.2 / §5.4): the OPEN token (search/recent every 60 s, cached 15 min per query,
|
|
13
|
+
// since_id so a re-poll costs only new posts), the user's own X lists (list tweets, every 5 min per list) and
|
|
14
|
+
// manual KOL lists (`from:` search in batches of 20, every 5 min). Never a cashtag stream, never scraping.
|
|
15
|
+
// Posts land in SocialFeed.ingest(), which firewalls the text; cashtags on KOL posts resolve to board mints
|
|
16
|
+
// only when the symbol is unique on the board (else the post stays unattributed).
|
|
17
|
+
import { XApiError, handlesQuery, tokenQuery, tweetToRaw } from "./api.js";
|
|
18
|
+
export class XSource {
|
|
19
|
+
api;
|
|
20
|
+
store;
|
|
21
|
+
kol;
|
|
22
|
+
now;
|
|
23
|
+
board;
|
|
24
|
+
tokenPollMs;
|
|
25
|
+
kolPollMs;
|
|
26
|
+
tokenMax;
|
|
27
|
+
onChange;
|
|
28
|
+
onLog;
|
|
29
|
+
focus = null;
|
|
30
|
+
sinceByMint = new Map();
|
|
31
|
+
lastTokenAt = 0;
|
|
32
|
+
lastKolAt = 0;
|
|
33
|
+
lastFocusMint = null;
|
|
34
|
+
busy = false;
|
|
35
|
+
closed = false;
|
|
36
|
+
state = 'logged-out';
|
|
37
|
+
user = null;
|
|
38
|
+
lastOkAt = null;
|
|
39
|
+
lastError = null;
|
|
40
|
+
resumesAt = null;
|
|
41
|
+
errorBackoffUntil = 0;
|
|
42
|
+
errorStrikes = 0;
|
|
43
|
+
posts = 0;
|
|
44
|
+
requests = 0;
|
|
45
|
+
/** Where posts go: set by SocialFeed.attachX(). */
|
|
46
|
+
sink = null;
|
|
47
|
+
metaAt = 0;
|
|
48
|
+
metaCache = { loggedIn: false, user: null, obtainedAt: null };
|
|
49
|
+
/** `obtained_at` of the record X answered 401 for: only a NEWER login (dexterm x login) re-arms the poller. */
|
|
50
|
+
deadObtainedAt = null;
|
|
51
|
+
constructor(opts) {
|
|
52
|
+
this.api = opts.api;
|
|
53
|
+
this.store = opts.store;
|
|
54
|
+
this.kol = opts.kol ?? null;
|
|
55
|
+
this.now = opts.now ?? (() => Date.now());
|
|
56
|
+
this.board = opts.board ?? (() => []);
|
|
57
|
+
this.tokenPollMs = opts.tokenPollMs ?? 60_000;
|
|
58
|
+
this.kolPollMs = opts.kolPollMs ?? 5 * 60_000;
|
|
59
|
+
this.tokenMax = opts.tokenMax ?? 50;
|
|
60
|
+
this.onChange = opts.onChange ?? (() => { });
|
|
61
|
+
this.onLog = opts.onLog ?? null;
|
|
62
|
+
this.user = this.store.meta().user;
|
|
63
|
+
this.state = this.store.meta().loggedIn ? 'ok' : 'logged-out';
|
|
64
|
+
}
|
|
65
|
+
/** The open token: polled every tokenPollMs while set (the first poll is immediate). */
|
|
66
|
+
setFocus(f) {
|
|
67
|
+
this.focus = f;
|
|
68
|
+
if (f && f.mint !== this.lastFocusMint) {
|
|
69
|
+
this.lastTokenAt = 0;
|
|
70
|
+
this.lastFocusMint = f.mint;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** tokens.json is re-read at most every 5 s (status() runs on every tick and render). */
|
|
74
|
+
meta(now) {
|
|
75
|
+
if (now - this.metaAt >= 5_000 || this.metaAt === 0) {
|
|
76
|
+
const m = this.store.meta();
|
|
77
|
+
this.metaCache = { loggedIn: m.loggedIn, user: m.user, obtainedAt: m.obtainedAt };
|
|
78
|
+
this.metaAt = now;
|
|
79
|
+
}
|
|
80
|
+
return this.metaCache;
|
|
81
|
+
}
|
|
82
|
+
status() {
|
|
83
|
+
const now = this.now();
|
|
84
|
+
const meta = this.meta(now);
|
|
85
|
+
if (!meta.loggedIn && this.state !== 'off') {
|
|
86
|
+
this.state = 'logged-out';
|
|
87
|
+
this.user = null;
|
|
88
|
+
this.deadObtainedAt = null;
|
|
89
|
+
}
|
|
90
|
+
else if (meta.loggedIn && this.state === 'logged-out' && (this.deadObtainedAt === null || meta.obtainedAt !== this.deadObtainedAt)) {
|
|
91
|
+
this.state = 'ok';
|
|
92
|
+
this.user = meta.user;
|
|
93
|
+
this.deadObtainedAt = null;
|
|
94
|
+
this.lastError = null;
|
|
95
|
+
}
|
|
96
|
+
if ((this.state === 'rate-limited' || this.state === 'no-credit') && this.resumesAt !== null && now >= this.resumesAt) {
|
|
97
|
+
this.state = 'ok';
|
|
98
|
+
this.resumesAt = null;
|
|
99
|
+
}
|
|
100
|
+
const spend = this.api.spend?.status() ?? null;
|
|
101
|
+
const windows = {};
|
|
102
|
+
for (const k of ['search', 'list-tweets', 'me']) {
|
|
103
|
+
const w = this.api.window(k);
|
|
104
|
+
if (w)
|
|
105
|
+
windows[k] = w;
|
|
106
|
+
}
|
|
107
|
+
const w = windows.search;
|
|
108
|
+
const fmtIn = (t) => { const m = Math.max(0, Math.round((t - now) / 60_000)); return m < 1 ? '<1 min' : `${m} min`; };
|
|
109
|
+
const used = w && w.limit !== null && w.remaining !== null ? `${w.limit - w.remaining}/${w.limit} search` : null;
|
|
110
|
+
const money = spend ? `$${spend.usd.toFixed(2)} of $${spend.budgetUsd} today` : null;
|
|
111
|
+
const who = this.user ? `@${this.user.username}` : 'logged in';
|
|
112
|
+
const detail = this.state === 'logged-out' ? (this.deadObtainedAt !== null ? 'login revoked or expired at X · dexterm x login' : 'logged out · dexterm x login (your own X app, your limits)')
|
|
113
|
+
: this.state === 'off' ? 'off in this mode'
|
|
114
|
+
: this.state === 'rate-limited' ? `rate limited${this.resumesAt ? ` · resets in ${fmtIn(this.resumesAt)}` : ''} · keyless sources still live`
|
|
115
|
+
: this.state === 'no-credit' ? `daily budget reached (${money ?? '?'})${this.resumesAt ? ` · resets in ${fmtIn(this.resumesAt)}` : ''}`
|
|
116
|
+
: this.state === 'not-enrolled' ? `${who} · your X app tier cannot read search/list timelines (Free tier) · identity only`
|
|
117
|
+
: this.state === 'error' ? `error · ${this.lastError ?? '?'} · retrying`
|
|
118
|
+
: [who, used, money].filter(Boolean).join(' · ');
|
|
119
|
+
return { state: this.state, user: this.user, detail, lastOkAt: this.lastOkAt, lastError: this.lastError, resumesAt: this.resumesAt, spend, windows, posts: this.posts, requests: this.requests };
|
|
120
|
+
}
|
|
121
|
+
/** Called from SocialFeed.tick(). Never throws, never awaited by the caller. */
|
|
122
|
+
tick(now = this.now()) {
|
|
123
|
+
if (this.closed || this.busy)
|
|
124
|
+
return;
|
|
125
|
+
const st = this.status();
|
|
126
|
+
if (st.state !== 'ok' && st.state !== 'error')
|
|
127
|
+
return;
|
|
128
|
+
if (st.state === 'error' && now < this.errorBackoffUntil)
|
|
129
|
+
return;
|
|
130
|
+
const tokenDue = !!this.focus && now - this.lastTokenAt >= this.tokenPollMs;
|
|
131
|
+
const kolDue = !!this.kol && now - this.lastKolAt >= this.kolPollMs;
|
|
132
|
+
if (!tokenDue && !kolDue)
|
|
133
|
+
return;
|
|
134
|
+
this.busy = true;
|
|
135
|
+
void (async () => {
|
|
136
|
+
try {
|
|
137
|
+
if (tokenDue) {
|
|
138
|
+
this.lastTokenAt = now;
|
|
139
|
+
await this.pollToken();
|
|
140
|
+
}
|
|
141
|
+
if (kolDue && !this.closed) {
|
|
142
|
+
this.lastKolAt = now;
|
|
143
|
+
await this.pollKol();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
this.busy = false;
|
|
148
|
+
}
|
|
149
|
+
})();
|
|
150
|
+
}
|
|
151
|
+
fail(e) {
|
|
152
|
+
const now = this.now();
|
|
153
|
+
if (e instanceof XApiError) {
|
|
154
|
+
this.lastError = e.message;
|
|
155
|
+
if (e.kind === 'unauthorized') {
|
|
156
|
+
this.state = 'logged-out';
|
|
157
|
+
this.user = null;
|
|
158
|
+
this.deadObtainedAt = this.store.meta().obtainedAt ?? -1;
|
|
159
|
+
this.metaAt = 0;
|
|
160
|
+
}
|
|
161
|
+
else if (e.kind === 'rate-limited') {
|
|
162
|
+
this.state = 'rate-limited';
|
|
163
|
+
this.resumesAt = e.resetAt ?? now + 15 * 60_000;
|
|
164
|
+
}
|
|
165
|
+
else if (e.kind === 'no-credit') {
|
|
166
|
+
this.state = 'no-credit';
|
|
167
|
+
this.resumesAt = this.api.spend?.status().resetsAt ?? now + 3_600_000;
|
|
168
|
+
}
|
|
169
|
+
else if (e.kind === 'not-enrolled') {
|
|
170
|
+
this.state = 'not-enrolled';
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
this.state = 'error';
|
|
174
|
+
this.errorStrikes += 1;
|
|
175
|
+
this.errorBackoffUntil = now + Math.min(10 * 60_000, 30_000 * 2 ** (this.errorStrikes - 1));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
this.lastError = e instanceof Error ? e.message : String(e);
|
|
180
|
+
this.state = 'error';
|
|
181
|
+
this.errorStrikes += 1;
|
|
182
|
+
this.errorBackoffUntil = now + Math.min(10 * 60_000, 30_000 * 2 ** (this.errorStrikes - 1));
|
|
183
|
+
}
|
|
184
|
+
this.onLog?.(`x-api: ${this.state}: ${this.lastError}`);
|
|
185
|
+
this.onChange('social:x');
|
|
186
|
+
}
|
|
187
|
+
ok() {
|
|
188
|
+
this.state = 'ok';
|
|
189
|
+
this.lastError = null;
|
|
190
|
+
this.resumesAt = null;
|
|
191
|
+
this.errorStrikes = 0;
|
|
192
|
+
this.lastOkAt = this.now();
|
|
193
|
+
}
|
|
194
|
+
deliver(raws, reason) {
|
|
195
|
+
if (!raws.length || !this.sink)
|
|
196
|
+
return;
|
|
197
|
+
this.posts += raws.length;
|
|
198
|
+
this.sink(raws);
|
|
199
|
+
this.onChange(reason);
|
|
200
|
+
}
|
|
201
|
+
async pollToken() {
|
|
202
|
+
const f = this.focus;
|
|
203
|
+
if (!f || this.closed)
|
|
204
|
+
return;
|
|
205
|
+
try {
|
|
206
|
+
const page = await this.api.searchRecent(tokenQuery({ symbol: f.symbol, mint: f.mint, handle: f.handle }), { maxResults: this.tokenMax, sinceId: this.sinceByMint.get(f.mint) ?? null });
|
|
207
|
+
if (!page.cached)
|
|
208
|
+
this.requests += 1;
|
|
209
|
+
if (page.newestId)
|
|
210
|
+
this.sinceByMint.set(f.mint, page.newestId);
|
|
211
|
+
this.ok();
|
|
212
|
+
this.deliver(page.tweets.map((t) => tweetToRaw(t, page.users, { mints: [f.mint], fetchedAt: page.fetchedAt })), 'social:x:token');
|
|
213
|
+
if (page.cached)
|
|
214
|
+
this.onChange('social:x');
|
|
215
|
+
}
|
|
216
|
+
catch (e) {
|
|
217
|
+
this.fail(e);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/** Symbol → mint when unique on the board. */
|
|
221
|
+
resolveCashtags(cashtags) {
|
|
222
|
+
const board = this.board();
|
|
223
|
+
const out = [];
|
|
224
|
+
for (const c of cashtags) {
|
|
225
|
+
const hits = board.filter((b) => b.symbol.toUpperCase() === c.toUpperCase());
|
|
226
|
+
if (hits.length === 1)
|
|
227
|
+
out.push(hits[0].mint);
|
|
228
|
+
}
|
|
229
|
+
return [...new Set(out)];
|
|
230
|
+
}
|
|
231
|
+
async pollKol() {
|
|
232
|
+
if (!this.kol || this.closed)
|
|
233
|
+
return;
|
|
234
|
+
const lists = this.kol.all().filter((l) => l.enabled);
|
|
235
|
+
const ownLists = lists.filter((l) => l.source === 'own-x-list' && /^own-x-list:\d+$/.test(l.id));
|
|
236
|
+
const manualHandles = [...new Set(lists.filter((l) => l.source !== 'own-x-list').flatMap((l) => l.handles).filter((h) => !this.kol.isMuted(h)))];
|
|
237
|
+
try {
|
|
238
|
+
for (const l of ownLists) {
|
|
239
|
+
if (this.closed)
|
|
240
|
+
return;
|
|
241
|
+
const page = await this.api.listTweets(l.id.slice('own-x-list:'.length), { maxResults: 50 });
|
|
242
|
+
if (!page.cached)
|
|
243
|
+
this.requests += 1;
|
|
244
|
+
this.ok();
|
|
245
|
+
this.deliver(page.tweets.map((t) => tweetToRaw(t, page.users, { mints: this.resolveCashtags(cashtagsOf(t.text)), fetchedAt: page.fetchedAt })), 'social:x:kol');
|
|
246
|
+
}
|
|
247
|
+
for (let i = 0; i < manualHandles.length; i += 20) {
|
|
248
|
+
if (this.closed)
|
|
249
|
+
return;
|
|
250
|
+
const page = await this.api.searchRecent(handlesQuery(manualHandles.slice(i, i + 20)), { maxResults: 50 });
|
|
251
|
+
if (!page.cached)
|
|
252
|
+
this.requests += 1;
|
|
253
|
+
this.ok();
|
|
254
|
+
this.deliver(page.tweets.map((t) => tweetToRaw(t, page.users, { mints: this.resolveCashtags(cashtagsOf(t.text)), fetchedAt: page.fetchedAt })), 'social:x:kol');
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
catch (e) {
|
|
258
|
+
this.fail(e);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/** Import the user's own X lists as `own-x-list` KOL lists (handles only; posts are never stored by this). */
|
|
262
|
+
async importOwnLists() {
|
|
263
|
+
if (!this.kol)
|
|
264
|
+
throw new XApiError('http', 'no KOL store');
|
|
265
|
+
const me = this.user ?? (await this.api.me());
|
|
266
|
+
this.user = me;
|
|
267
|
+
const lists = await this.api.ownedLists(me.id);
|
|
268
|
+
const out = [];
|
|
269
|
+
for (const l of lists) {
|
|
270
|
+
const members = await this.api.listMembers(l.id);
|
|
271
|
+
const handles = members.map((m) => m.username.toLowerCase());
|
|
272
|
+
this.kol.seed({ id: `own-x-list:${l.id}`, name: l.name, source: 'own-x-list', handles, wallets: [], signed: null, since: this.now(), enabled: true, tier: 'kol-a' });
|
|
273
|
+
out.push({ id: `own-x-list:${l.id}`, name: l.name, handles: handles.length });
|
|
274
|
+
}
|
|
275
|
+
this.ok();
|
|
276
|
+
this.lastKolAt = 0; // poll the new lists on the next tick
|
|
277
|
+
this.onChange('social:x:lists');
|
|
278
|
+
return { lists: out };
|
|
279
|
+
}
|
|
280
|
+
close() { this.closed = true; }
|
|
281
|
+
}
|
|
282
|
+
const CASHTAG_RE = /\$([A-Za-z][A-Za-z0-9_]{0,15})\b/g;
|
|
283
|
+
export function cashtagsOf(text) {
|
|
284
|
+
return [...new Set([...text.matchAll(CASHTAG_RE)].map((m) => m[1].toUpperCase()))];
|
|
285
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// @dexterm/data — local storage for the X client id and the user's OAuth tokens (task-1357).
|
|
2
|
+
//
|
|
3
|
+
// <xDir>/client-id the user's OWN X app Client ID (public-ish, kept out of argv/repo anyway), mode 0600
|
|
4
|
+
// <xDir>/tokens.json { v, access_token, refresh_token, token_type, scope, expires_at, obtained_at, user }, mode 0600
|
|
5
|
+
//
|
|
6
|
+
// The directory is created 0700. Nothing here prints a token: `meta()` is the only read meant for display and
|
|
7
|
+
// it carries ages, scopes and the handle, never the secret. The store is the ONLY module that reads tokens.json;
|
|
8
|
+
// the adapter asks it for a bearer and never keeps a copy longer than one request.
|
|
9
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { isPlausibleClientId, xDir } from "./config.js";
|
|
12
|
+
export class XTokenStore {
|
|
13
|
+
dir;
|
|
14
|
+
now;
|
|
15
|
+
constructor(opts = {}) {
|
|
16
|
+
this.dir = opts.dir ?? xDir(opts.env ?? process.env);
|
|
17
|
+
this.now = opts.now ?? (() => Date.now());
|
|
18
|
+
}
|
|
19
|
+
get tokensPath() { return join(this.dir, 'tokens.json'); }
|
|
20
|
+
get clientIdPath() { return join(this.dir, 'client-id'); }
|
|
21
|
+
ensureDir() {
|
|
22
|
+
mkdirSync(this.dir, { recursive: true, mode: 0o700 });
|
|
23
|
+
try {
|
|
24
|
+
chmodSync(this.dir, 0o700);
|
|
25
|
+
}
|
|
26
|
+
catch { /* best effort on platforms without chmod */ }
|
|
27
|
+
}
|
|
28
|
+
writePrivate(path, text) {
|
|
29
|
+
this.ensureDir();
|
|
30
|
+
writeFileSync(path, text, { mode: 0o600 });
|
|
31
|
+
try {
|
|
32
|
+
chmodSync(path, 0o600);
|
|
33
|
+
}
|
|
34
|
+
catch { /* best effort */ }
|
|
35
|
+
}
|
|
36
|
+
// ---------------------------------------------------------------- client id
|
|
37
|
+
/** `DEXTERM_X_CLIENT_ID` wins (CI / scripts), else the stored file; null when neither is usable. */
|
|
38
|
+
clientId(env = process.env) {
|
|
39
|
+
const fromEnv = env.DEXTERM_X_CLIENT_ID?.trim();
|
|
40
|
+
if (fromEnv && isPlausibleClientId(fromEnv))
|
|
41
|
+
return fromEnv;
|
|
42
|
+
try {
|
|
43
|
+
const v = readFileSync(this.clientIdPath, 'utf8').trim();
|
|
44
|
+
return isPlausibleClientId(v) ? v : null;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
setClientId(id) {
|
|
51
|
+
const v = id.trim();
|
|
52
|
+
if (!isPlausibleClientId(v))
|
|
53
|
+
throw new Error('that does not look like an X app Client ID (8-128 URL-safe characters)');
|
|
54
|
+
this.writePrivate(this.clientIdPath, v + '\n');
|
|
55
|
+
}
|
|
56
|
+
clearClientId() {
|
|
57
|
+
if (!existsSync(this.clientIdPath))
|
|
58
|
+
return false;
|
|
59
|
+
rmSync(this.clientIdPath, { force: true });
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
// ---------------------------------------------------------------- tokens
|
|
63
|
+
/** The full record (secret!). Only the OAuth client and the API adapter may call this. */
|
|
64
|
+
tokens() {
|
|
65
|
+
try {
|
|
66
|
+
if (!existsSync(this.tokensPath))
|
|
67
|
+
return null;
|
|
68
|
+
const raw = JSON.parse(readFileSync(this.tokensPath, 'utf8'));
|
|
69
|
+
if (raw.v !== 1 || typeof raw.access_token !== 'string' || !raw.access_token)
|
|
70
|
+
return null;
|
|
71
|
+
return {
|
|
72
|
+
v: 1,
|
|
73
|
+
access_token: raw.access_token,
|
|
74
|
+
refresh_token: typeof raw.refresh_token === 'string' && raw.refresh_token ? raw.refresh_token : null,
|
|
75
|
+
token_type: typeof raw.token_type === 'string' ? raw.token_type : 'bearer',
|
|
76
|
+
scope: typeof raw.scope === 'string' ? raw.scope : '',
|
|
77
|
+
expires_at: Number(raw.expires_at) || 0,
|
|
78
|
+
obtained_at: Number(raw.obtained_at) || 0,
|
|
79
|
+
user: raw.user && typeof raw.user === 'object' && typeof raw.user.id === 'string' && typeof raw.user.username === 'string' ? { id: raw.user.id, username: raw.user.username } : null,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** Persist a token response. The whole record is rewritten (X may rotate the refresh token). */
|
|
87
|
+
setTokens(res, user = this.tokens()?.user ?? null) {
|
|
88
|
+
if (typeof res.access_token !== 'string' || !res.access_token)
|
|
89
|
+
throw new Error('token response carried no access_token');
|
|
90
|
+
const now = this.now();
|
|
91
|
+
const rec = {
|
|
92
|
+
v: 1,
|
|
93
|
+
access_token: res.access_token,
|
|
94
|
+
refresh_token: res.refresh_token ?? null,
|
|
95
|
+
token_type: res.token_type ?? 'bearer',
|
|
96
|
+
scope: res.scope ?? '',
|
|
97
|
+
expires_at: now + (Number(res.expires_in) > 0 ? Number(res.expires_in) : 7200) * 1000,
|
|
98
|
+
obtained_at: now,
|
|
99
|
+
user,
|
|
100
|
+
};
|
|
101
|
+
this.writePrivate(this.tokensPath, JSON.stringify(rec, null, 2) + '\n');
|
|
102
|
+
return rec;
|
|
103
|
+
}
|
|
104
|
+
setUser(user) {
|
|
105
|
+
const t = this.tokens();
|
|
106
|
+
if (!t)
|
|
107
|
+
return;
|
|
108
|
+
t.user = user;
|
|
109
|
+
this.writePrivate(this.tokensPath, JSON.stringify(t, null, 2) + '\n');
|
|
110
|
+
}
|
|
111
|
+
clearTokens() {
|
|
112
|
+
if (!existsSync(this.tokensPath))
|
|
113
|
+
return false;
|
|
114
|
+
rmSync(this.tokensPath, { force: true });
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
/** Display-safe status. */
|
|
118
|
+
meta() {
|
|
119
|
+
const t = this.tokens();
|
|
120
|
+
if (!t)
|
|
121
|
+
return { loggedIn: false, user: null, scopes: [], expiresAt: null, hasRefresh: false, obtainedAt: null };
|
|
122
|
+
return { loggedIn: true, user: t.user, scopes: t.scope.split(/\s+/).filter(Boolean), expiresAt: t.expires_at, hasRefresh: !!t.refresh_token, obtainedAt: t.obtained_at };
|
|
123
|
+
}
|
|
124
|
+
/** File modes as the OS reports them (tests + doctor): null when a file is missing. */
|
|
125
|
+
modes() {
|
|
126
|
+
const mode = (p) => { try {
|
|
127
|
+
return existsSync(p) ? (statSync(p).mode & 0o777) : null;
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return null;
|
|
131
|
+
} };
|
|
132
|
+
return { dir: mode(this.dir), tokens: mode(this.tokensPath), clientId: mode(this.clientIdPath) };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// @dexterm/data — the one HTTP transport for X (task-1357). Separate from http.ts's keyless client because
|
|
2
|
+
// X needs an `Authorization: Bearer …` header (user-context reads) and form-encoded POSTs (token/revoke).
|
|
3
|
+
// Built on node:http(s) directly so it works even where tests replace `globalThis.fetch` (helpers.ts blocks
|
|
4
|
+
// the network for keyless code; the X tests talk to a loopback mock server through this transport).
|
|
5
|
+
//
|
|
6
|
+
// Rules: GET/POST only; the bearer is set per request and never stored here; every error message is passed
|
|
7
|
+
// through redactXSecrets(); the URL is never logged with a query (search queries are the user's business).
|
|
8
|
+
import { request as httpRequest } from 'node:http';
|
|
9
|
+
import { request as httpsRequest } from 'node:https';
|
|
10
|
+
import { redactXSecrets } from "./config.js";
|
|
11
|
+
/** Headers worth keeping: content type, rate-limit windows (X uses `x-rate-limit-*`), retry-after. */
|
|
12
|
+
const KEEP = ['content-type', 'retry-after', 'x-rate-limit-limit', 'x-rate-limit-remaining', 'x-rate-limit-reset', 'x-user-limit-24hour-limit', 'x-user-limit-24hour-remaining', 'x-user-limit-24hour-reset', 'x-app-limit-24hour-limit', 'x-app-limit-24hour-remaining', 'x-app-limit-24hour-reset'];
|
|
13
|
+
export const X_USER_AGENT = 'dexterm/0.0.1 (+local terminal screener; user-authorized X reads only)';
|
|
14
|
+
export function nodeXTransport(defaults = {}) {
|
|
15
|
+
const userAgent = defaults.userAgent ?? X_USER_AGENT;
|
|
16
|
+
return (req) => new Promise((resolve, reject) => {
|
|
17
|
+
const t0 = performance.now();
|
|
18
|
+
let url;
|
|
19
|
+
try {
|
|
20
|
+
url = new URL(req.url);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
reject(new Error('x transport: bad URL'));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (url.protocol !== 'https:' && !(url.protocol === 'http:' && (url.hostname === '127.0.0.1' || url.hostname === 'localhost'))) {
|
|
27
|
+
reject(new Error(`x transport: refusing ${url.protocol}//${url.hostname} (https only, http on loopback only)`));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const headers = { 'user-agent': userAgent, accept: 'application/json', ...(req.headers ?? {}) };
|
|
31
|
+
if (req.body !== undefined)
|
|
32
|
+
headers['content-length'] = String(Buffer.byteLength(req.body));
|
|
33
|
+
const fn = url.protocol === 'https:' ? httpsRequest : httpRequest;
|
|
34
|
+
const timeoutMs = req.timeoutMs ?? defaults.timeoutMs ?? 15_000;
|
|
35
|
+
const r = fn(url, { method: req.method, headers, timeout: timeoutMs }, (res) => {
|
|
36
|
+
const chunks = [];
|
|
37
|
+
res.on('data', (c) => chunks.push(c));
|
|
38
|
+
res.on('end', () => {
|
|
39
|
+
const kept = {};
|
|
40
|
+
for (const h of KEEP) {
|
|
41
|
+
const v = res.headers[h];
|
|
42
|
+
if (typeof v === 'string')
|
|
43
|
+
kept[h] = v;
|
|
44
|
+
else if (Array.isArray(v))
|
|
45
|
+
kept[h] = v[0];
|
|
46
|
+
}
|
|
47
|
+
resolve({ status: res.statusCode ?? 0, headers: kept, body: Buffer.concat(chunks).toString('utf8'), latencyMs: Math.round(performance.now() - t0) });
|
|
48
|
+
});
|
|
49
|
+
res.on('error', (e) => reject(new Error(redactXSecrets(`x transport: ${e.message}`))));
|
|
50
|
+
});
|
|
51
|
+
const fail = (msg) => { try {
|
|
52
|
+
r.destroy();
|
|
53
|
+
}
|
|
54
|
+
catch { /* ignore */ } reject(new Error(redactXSecrets(msg))); };
|
|
55
|
+
r.on('timeout', () => fail(`x transport: timed out after ${timeoutMs} ms`));
|
|
56
|
+
r.on('error', (e) => fail(`x transport: ${e.message}`));
|
|
57
|
+
if (req.signal) {
|
|
58
|
+
if (req.signal.aborted) {
|
|
59
|
+
fail('x transport: aborted');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
req.signal.addEventListener('abort', () => fail('x transport: aborted'), { once: true });
|
|
63
|
+
}
|
|
64
|
+
if (req.body !== undefined)
|
|
65
|
+
r.write(req.body);
|
|
66
|
+
r.end();
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
export function formBody(fields) {
|
|
70
|
+
return new URLSearchParams(fields).toString();
|
|
71
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright (c) 2023 Solana Labs, Inc
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Vendored: @solana/web3.js 1.98.4 (browser IIFE bundle)
|
|
2
|
+
|
|
3
|
+
- File: `index.iife.min.js` — `lib/index.iife.min.js` from the npm tarball `solana-web3.js-1.98.4.tgz`
|
|
4
|
+
(fetched 2026-10-09 with `npm pack @solana/web3.js@1.98.4`; nothing was installed into node_modules).
|
|
5
|
+
- sha256: `09cdbea951b2ed0e11bcbe3aeb1ee9f035f9fb51ed212aca645475ae82688cc3` (pinned by
|
|
6
|
+
`packages/data/test/signer-1328.test.ts`; a changed file fails the test).
|
|
7
|
+
- License: MIT (see `LICENSE`, copied verbatim from the package).
|
|
8
|
+
- Why it is here (task-1328): Phantom's `signTransaction()` takes a `VersionedTransaction` OBJECT, not
|
|
9
|
+
bytes, so the wallet bridge's tx-sign page needs web3.js in the browser to rebuild the transaction
|
|
10
|
+
the signer built. The bundle is INLINED into the page under the CSP nonce (no remote script, no CDN,
|
|
11
|
+
no network fetch at sign time) and exposed as `window.dexSolana`. It runs only in the user's
|
|
12
|
+
browser on the loopback sign page; dexterm's Node processes never import it.
|
|
13
|
+
- Upgrade: replace the file, update the sha256 in the test and here, re-run the bridge + signer tests.
|