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,336 @@
|
|
|
1
|
+
// `dexterm agent …` — bring-your-own-agent registry + loopback bus CLI (task-1345, tier 2).
|
|
2
|
+
//
|
|
3
|
+
// dexterm agent add <name> --tier observe|annotate|propose [--note T] [--reads N] [--proposals N] [--connections N]
|
|
4
|
+
// dexterm agent list
|
|
5
|
+
// dexterm agent rotate <name> # new one-time token; the old one stops working
|
|
6
|
+
// dexterm agent remove <name>
|
|
7
|
+
// dexterm agent tier <name> <tier>
|
|
8
|
+
// dexterm agent pause|resume <name>
|
|
9
|
+
// dexterm agent audit [--agent NAME] [--limit N]
|
|
10
|
+
// dexterm agent packs # the skills/KB an external agent gets over MCP (agent packs)
|
|
11
|
+
// dexterm agent serve [--fixtures|--live] [--port N] [--db PATH] # run a standalone loopback bus
|
|
12
|
+
// dexterm agent connect --name NAME [--port N] [--url ws://…] [--watch SECONDS] # token from $DEXTERM_AGENT_TOKEN
|
|
13
|
+
//
|
|
14
|
+
// A one-time token is printed to stdout by `add`/`rotate` so the user can paste it into THEIR agent's
|
|
15
|
+
// config; it is never stored in plaintext and never echoed again. The token for `serve`/`connect` auth is
|
|
16
|
+
// read ONLY from the $DEXTERM_AGENT_TOKEN environment variable — never a command-line flag — so it cannot
|
|
17
|
+
// leak through a process list. No agent path can arm, sign, send, raise a cap or touch a wallet/mandate.
|
|
18
|
+
import { AgentRegistry, SqliteAuditLog, MemoryProposalStore, SqliteProposalStore, AgentBus, FixtureExecutor, FixtureFeed, TIERS, isTier, } from '../../agents/src/index.js';
|
|
19
|
+
import { connectAgent } from '../../agents/src/sdk.js';
|
|
20
|
+
import { defaultDbPath } from '../../data/src/index.js';
|
|
21
|
+
function flag(rest, name) { const i = rest.indexOf(name); return i >= 0 ? rest[i + 1] : undefined; }
|
|
22
|
+
function has(rest, name) { return rest.includes(name); }
|
|
23
|
+
function intFlag(rest, name) { const v = flag(rest, name); const n = v === undefined ? undefined : Number(v); return typeof n === 'number' && Number.isFinite(n) ? n : undefined; }
|
|
24
|
+
const USAGE = `dexterm agent <command>
|
|
25
|
+
add <name> --tier observe|annotate|propose [--note T] [--reads N] [--proposals N] [--connections N]
|
|
26
|
+
list | rotate <name> | remove <name> | tier <name> <tier> | pause <name> | resume <name>
|
|
27
|
+
mandate <name> --tools a,b,c | mandate <name> --clear (narrow an agent to a named set of tools)
|
|
28
|
+
audit [--agent NAME] [--limit N]
|
|
29
|
+
proposals [--agent NAME] [--status pending|confirmed|dismissed] [--limit N] [--db PATH]
|
|
30
|
+
packs
|
|
31
|
+
serve [--fixtures|--live] [--roster] [--port N] [--db PATH]
|
|
32
|
+
connect --name NAME [--port N] [--url ws://127.0.0.1:PORT] [--watch SECONDS] (token via $DEXTERM_AGENT_TOKEN)`;
|
|
33
|
+
export async function runAgent(rest, io) {
|
|
34
|
+
const cmd = rest[0];
|
|
35
|
+
const out = (s) => io.stdout.write(s + '\n');
|
|
36
|
+
const errOut = (s) => io.stderr.write(s + '\n');
|
|
37
|
+
const registry = new AgentRegistry({ env: io.env });
|
|
38
|
+
switch (cmd) {
|
|
39
|
+
case 'add': {
|
|
40
|
+
const name = rest[1];
|
|
41
|
+
const tier = flag(rest, '--tier');
|
|
42
|
+
if (!name || !tier || !isTier(tier)) {
|
|
43
|
+
errOut(`usage: dexterm agent add <name> --tier ${TIERS.join('|')}`);
|
|
44
|
+
return 2;
|
|
45
|
+
}
|
|
46
|
+
const limits = { readsPerMin: intFlag(rest, '--reads'), proposalsPerHour: intFlag(rest, '--proposals'), connections: intFlag(rest, '--connections') };
|
|
47
|
+
try {
|
|
48
|
+
const { agent, token } = registry.add(name, tier, { note: flag(rest, '--note'), limits });
|
|
49
|
+
out(`registered "${agent.name}" (tier ${agent.tier}, id ${agent.id}).`);
|
|
50
|
+
out('');
|
|
51
|
+
out(' one-time token (shown once — paste it into your agent, do not commit it):');
|
|
52
|
+
out(` ${token}`);
|
|
53
|
+
out('');
|
|
54
|
+
out(` your agent authenticates with name="${agent.name}" + this token over the loopback bus or a bridged MCP child.`);
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
errOut(`dexterm agent add: ${e.message}`);
|
|
59
|
+
return 1;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
case 'list': {
|
|
63
|
+
const list = registry.list();
|
|
64
|
+
if (!list.length) {
|
|
65
|
+
out('no agents registered. Add one: dexterm agent add <name> --tier observe');
|
|
66
|
+
return 0;
|
|
67
|
+
}
|
|
68
|
+
out('name tier paused created mandate / note');
|
|
69
|
+
for (const a of list) {
|
|
70
|
+
const mandate = a.scope && a.scope.length ? `mandate: ${a.scope.join(',')}` : '';
|
|
71
|
+
const tail = [mandate, a.note ?? ''].filter(Boolean).join(' · ');
|
|
72
|
+
out(`${a.name.padEnd(24)} ${a.tier.padEnd(9)} ${(a.paused ? 'yes' : 'no').padEnd(7)} ${new Date(a.createdAt).toISOString().slice(0, 19)} ${tail}`);
|
|
73
|
+
}
|
|
74
|
+
return 0;
|
|
75
|
+
}
|
|
76
|
+
case 'rotate': {
|
|
77
|
+
const name = rest[1];
|
|
78
|
+
if (!name) {
|
|
79
|
+
errOut('usage: dexterm agent rotate <name>');
|
|
80
|
+
return 2;
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
const token = registry.rotate(name);
|
|
84
|
+
out(`rotated "${name}". the previous token no longer works.`);
|
|
85
|
+
out('');
|
|
86
|
+
out(' new one-time token (shown once):');
|
|
87
|
+
out(` ${token}`);
|
|
88
|
+
return 0;
|
|
89
|
+
}
|
|
90
|
+
catch (e) {
|
|
91
|
+
errOut(`dexterm agent rotate: ${e.message}`);
|
|
92
|
+
return 1;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
case 'remove': {
|
|
96
|
+
const name = rest[1];
|
|
97
|
+
if (!name) {
|
|
98
|
+
errOut('usage: dexterm agent remove <name>');
|
|
99
|
+
return 2;
|
|
100
|
+
}
|
|
101
|
+
out(registry.remove(name) ? `removed "${name}" (its token is now invalid).` : `no agent "${name}".`);
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
|
104
|
+
case 'tier': {
|
|
105
|
+
const name = rest[1];
|
|
106
|
+
const tier = rest[2];
|
|
107
|
+
if (!name || !tier || !isTier(tier)) {
|
|
108
|
+
errOut(`usage: dexterm agent tier <name> ${TIERS.join('|')}`);
|
|
109
|
+
return 2;
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
registry.setTier(name, tier);
|
|
113
|
+
out(`"${name}" is now tier ${tier}.`);
|
|
114
|
+
return 0;
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
errOut(`dexterm agent tier: ${e.message}`);
|
|
118
|
+
return 1;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
case 'pause':
|
|
122
|
+
case 'resume': {
|
|
123
|
+
const name = rest[1];
|
|
124
|
+
if (!name) {
|
|
125
|
+
errOut(`usage: dexterm agent ${cmd} <name>`);
|
|
126
|
+
return 2;
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
registry.setPaused(name, cmd === 'pause');
|
|
130
|
+
out(`"${name}" ${cmd === 'pause' ? 'paused' : 'resumed'} (persisted; a running dexterm applies it on the agent's next connection).`);
|
|
131
|
+
return 0;
|
|
132
|
+
}
|
|
133
|
+
catch (e) {
|
|
134
|
+
errOut(`dexterm agent ${cmd}: ${e.message}`);
|
|
135
|
+
return 1;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
case 'mandate': {
|
|
139
|
+
const name = rest[1];
|
|
140
|
+
if (!name) {
|
|
141
|
+
errOut('usage: dexterm agent mandate <name> --tools a,b,c | --clear');
|
|
142
|
+
return 2;
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
if (has(rest, '--clear')) {
|
|
146
|
+
registry.setScope(name, null);
|
|
147
|
+
out(`cleared the mandate for "${name}" (it may now use its full tier surface).`);
|
|
148
|
+
return 0;
|
|
149
|
+
}
|
|
150
|
+
const tools = flag(rest, '--tools');
|
|
151
|
+
if (!tools) {
|
|
152
|
+
errOut('usage: dexterm agent mandate <name> --tools a,b,c | --clear');
|
|
153
|
+
return 2;
|
|
154
|
+
}
|
|
155
|
+
const list = tools.split(',').map((t) => t.trim()).filter(Boolean);
|
|
156
|
+
registry.setScope(name, list);
|
|
157
|
+
const rec = registry.get(name);
|
|
158
|
+
out(`"${name}" is now mandated to: ${rec.scope.join(', ')}.`);
|
|
159
|
+
out('the agent can call only these tools (plus agent_whoami); a mandate only narrows the tier, never widens it.');
|
|
160
|
+
return 0;
|
|
161
|
+
}
|
|
162
|
+
catch (e) {
|
|
163
|
+
errOut(`dexterm agent mandate: ${e.message}`);
|
|
164
|
+
return 1;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
case 'audit': {
|
|
168
|
+
const dbPath = flag(rest, '--db') || defaultDbPath(io.env);
|
|
169
|
+
const log = new SqliteAuditLog(dbPath);
|
|
170
|
+
try {
|
|
171
|
+
const agentName = flag(rest, '--agent');
|
|
172
|
+
const rows = log.recent(intFlag(rest, '--limit') ?? 50, agentName ? { agentName } : {});
|
|
173
|
+
if (!rows.length) {
|
|
174
|
+
out('no audit rows yet.');
|
|
175
|
+
return 0;
|
|
176
|
+
}
|
|
177
|
+
out('time agent kind tool ok detail');
|
|
178
|
+
for (const r of rows.reverse())
|
|
179
|
+
out(`${new Date(r.t).toISOString().slice(0, 19)} ${r.agentName.padEnd(16)} ${r.kind.padEnd(10)} ${r.tool.slice(0, 16).padEnd(16)} ${r.ok ? ' y' : ' n'} ${r.detail}`);
|
|
180
|
+
return 0;
|
|
181
|
+
}
|
|
182
|
+
finally {
|
|
183
|
+
log.close();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
case 'packs': {
|
|
187
|
+
const { selfTest } = await import('../../mcp/src/index.js');
|
|
188
|
+
const st = selfTest();
|
|
189
|
+
out('agent packs served over MCP (`dexterm mcp`): skills as prompts, KB + cases + skills as resources.');
|
|
190
|
+
out(` skills : ${st.skills.join(', ')}`);
|
|
191
|
+
out(` kb : ${st.kbEntries} entries`);
|
|
192
|
+
// task-1377: the user's overlay (~/.dexterm/kb/entries, ~/.dexterm/skills) is merged into what MCP serves.
|
|
193
|
+
const ov = st.overlay;
|
|
194
|
+
if (ov && (ov.kb.user || ov.skills.user))
|
|
195
|
+
out(` yours : kb +${ov.kb.user} (${ov.kb.overridden} override, ${ov.kb.added} new), skills +${ov.skills.user} (${ov.skills.overridden} override, ${ov.skills.added} new) — \`dexterm kb|skill list\``);
|
|
196
|
+
out(` cases : ${st.cases}`);
|
|
197
|
+
out(` resources: ${st.resources}`);
|
|
198
|
+
out('connect your agent: `claude mcp add dexterm -- dexterm mcp`, then run a skill, e.g. /dexterm:trench-scan 8');
|
|
199
|
+
return 0;
|
|
200
|
+
}
|
|
201
|
+
case 'proposals': {
|
|
202
|
+
const store = new SqliteProposalStore(flag(rest, '--db') || defaultDbPath(io.env));
|
|
203
|
+
try {
|
|
204
|
+
const status = flag(rest, '--status');
|
|
205
|
+
const rows = store.list({ agentId: undefined, status: status || undefined, limit: intFlag(rest, '--limit') ?? 50 });
|
|
206
|
+
const agent = flag(rest, '--agent');
|
|
207
|
+
const shown = agent ? rows.filter((p) => p.agentName === agent) : rows;
|
|
208
|
+
if (!shown.length) {
|
|
209
|
+
out('no proposals.');
|
|
210
|
+
return 0;
|
|
211
|
+
}
|
|
212
|
+
out('created agent kind status summary');
|
|
213
|
+
for (const p of shown)
|
|
214
|
+
out(`${new Date(p.createdAt).toISOString().slice(0, 19)} ${p.agentName.padEnd(16)} ${p.kind.padEnd(10)} ${p.status.padEnd(9)} ${p.summary}`);
|
|
215
|
+
out('');
|
|
216
|
+
out('proposals are DRAFTS. An agent can never confirm or arm one; the user confirms with a keypress.');
|
|
217
|
+
return 0;
|
|
218
|
+
}
|
|
219
|
+
finally {
|
|
220
|
+
store.close();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
case 'serve': return serve(rest, io, registry);
|
|
224
|
+
case 'connect': return connect(rest, io);
|
|
225
|
+
default:
|
|
226
|
+
(cmd ? errOut(`unknown subcommand: ${cmd}`) : out(USAGE));
|
|
227
|
+
if (!cmd)
|
|
228
|
+
return 0;
|
|
229
|
+
errOut(USAGE);
|
|
230
|
+
return 2;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
async function serve(rest, io, registry) {
|
|
234
|
+
const live = has(rest, '--live');
|
|
235
|
+
const port = intFlag(rest, '--port');
|
|
236
|
+
const dbPath = flag(rest, '--db');
|
|
237
|
+
const audit = new SqliteAuditLog(dbPath || ':memory:');
|
|
238
|
+
const proposals = dbPath ? new SqliteProposalStore(dbPath) : new MemoryProposalStore();
|
|
239
|
+
const feed = new FixtureFeed();
|
|
240
|
+
let executor;
|
|
241
|
+
let dataClose = null;
|
|
242
|
+
if (live) {
|
|
243
|
+
// Live reads over the real keyless data layer via the MCP tools; the gate still bounds every call.
|
|
244
|
+
const { McpTools } = await import('../../mcp/src/index.js');
|
|
245
|
+
const { createDataLayer, resolveRpcUrl, defaultDbPath: ddp } = await import('../../data/src/index.js');
|
|
246
|
+
const rpc = resolveRpcUrl({ env: io.env });
|
|
247
|
+
const data = createDataLayer({ dbPath: ddp(io.env), rpcUrl: rpc.url, rpcFallbackUrl: rpc.fallbackUrl });
|
|
248
|
+
dataClose = () => data.close();
|
|
249
|
+
const mcp = new McpTools(data, () => ({ screen: 'agent-bus', actions: { mints: [], feeds: [], sortKeys: [] } }));
|
|
250
|
+
executor = { async exec(_s, tool, args) { try {
|
|
251
|
+
return await mcp.call(tool, args);
|
|
252
|
+
}
|
|
253
|
+
catch (e) {
|
|
254
|
+
return { error: e.message };
|
|
255
|
+
} } };
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
executor = new FixtureExecutor();
|
|
259
|
+
}
|
|
260
|
+
const bus = new AgentBus({ registry, audit, executor, proposals, feed, port, env: io.env });
|
|
261
|
+
const boundPort = await bus.start();
|
|
262
|
+
feed.start(1500);
|
|
263
|
+
io.stdout.write(`dexterm agent bus listening on ws://127.0.0.1:${boundPort} (${live ? 'live keyless reads' : 'fixtures'})\n`);
|
|
264
|
+
io.stdout.write(`discovery: ${process.env.DEXTERM_HOME ?? '~/.dexterm'}/agent-bus.json · ${registry.list().length} registered agent(s)\n`);
|
|
265
|
+
io.stdout.write('agents connect with their name + $DEXTERM_AGENT_TOKEN. Ctrl-C to stop.\n');
|
|
266
|
+
// --roster: open the agent roster popup and keep it live. The popup can only send pause/resume/kick.
|
|
267
|
+
let roster = null;
|
|
268
|
+
if (has(rest, '--roster')) {
|
|
269
|
+
try {
|
|
270
|
+
const { createPopupManager } = await import('../../popup/src/index.js');
|
|
271
|
+
const { buildRosterVM, applyRosterEvent } = await import("./agent-roster.js");
|
|
272
|
+
const { THEMES, DEFAULT_THEME } = await import('../../tui/src/index.js');
|
|
273
|
+
const { popupThemeFrom } = await import("./popup.js");
|
|
274
|
+
const mgr = createPopupManager({ theme: popupThemeFrom(THEMES[DEFAULT_THEME]), env: io.env, onWarn: (m) => io.stderr.write(`roster popup: ${m}\n`),
|
|
275
|
+
onEvent: (ev) => { if (ev.ev === 'popup.event' && ev.name.startsWith('roster.'))
|
|
276
|
+
io.stdout.write(`roster: ${applyRosterEvent(bus, ev.name, String(ev.payload.agent))}\n`); } });
|
|
277
|
+
const handle = await mgr.open({ kind: 'roster', title: 'agents', data: buildRosterVM(bus, audit) });
|
|
278
|
+
const timer = setInterval(() => { try {
|
|
279
|
+
mgr.setData(handle.id, buildRosterVM(bus, audit));
|
|
280
|
+
}
|
|
281
|
+
catch { /* popup closed */ } }, 1000);
|
|
282
|
+
timer.unref?.();
|
|
283
|
+
roster = { shutdown: () => mgr.shutdown(), timer };
|
|
284
|
+
io.stdout.write(`roster popup open (${mgr.native ? 'native window' : 'browser fallback'}).\n`);
|
|
285
|
+
}
|
|
286
|
+
catch (e) {
|
|
287
|
+
io.stderr.write(`roster popup unavailable: ${e.message}\n`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
await new Promise((resolve) => {
|
|
291
|
+
const stop = () => { if (roster) {
|
|
292
|
+
clearInterval(roster.timer);
|
|
293
|
+
void roster.shutdown();
|
|
294
|
+
} feed.stop(); audit.close(); proposals.close(); dataClose?.(); void bus.close().then(resolve); };
|
|
295
|
+
process.once('SIGINT', stop);
|
|
296
|
+
process.once('SIGTERM', stop);
|
|
297
|
+
});
|
|
298
|
+
return 0;
|
|
299
|
+
}
|
|
300
|
+
async function connect(rest, io) {
|
|
301
|
+
const name = flag(rest, '--name');
|
|
302
|
+
const token = io.env.DEXTERM_AGENT_TOKEN;
|
|
303
|
+
if (!name) {
|
|
304
|
+
io.stderr.write('usage: dexterm agent connect --name NAME [--port N] (token via $DEXTERM_AGENT_TOKEN)\n');
|
|
305
|
+
return 2;
|
|
306
|
+
}
|
|
307
|
+
if (!token) {
|
|
308
|
+
io.stderr.write('set $DEXTERM_AGENT_TOKEN to the token from `dexterm agent add` (never pass a token as a CLI flag).\n');
|
|
309
|
+
return 2;
|
|
310
|
+
}
|
|
311
|
+
const port = intFlag(rest, '--port');
|
|
312
|
+
const url = flag(rest, '--url');
|
|
313
|
+
try {
|
|
314
|
+
const agent = await connectAgent({ name, token, port, url, client: 'dexterm agent connect', home: io.env.DEXTERM_HOME });
|
|
315
|
+
io.stdout.write(`connected as "${agent.hello?.agent}" — tier ${agent.hello?.tier} (granted ${agent.hello?.grantedTier}).\n`);
|
|
316
|
+
const tools = await agent.listTools();
|
|
317
|
+
io.stdout.write(`tools (${tools.length}): ${tools.map((t) => t.name).join(', ')}\n`);
|
|
318
|
+
const who = await agent.whoami();
|
|
319
|
+
io.stdout.write(`whoami: ${JSON.stringify(who)}\n`);
|
|
320
|
+
const watch = intFlag(rest, '--watch');
|
|
321
|
+
if (watch && watch > 0) {
|
|
322
|
+
await agent.subscribe(['new_pairs', 'trades', 'alerts']);
|
|
323
|
+
agent.on('new_pairs', (ev) => io.stdout.write(`event new_pairs ${JSON.stringify(ev.data)}\n`));
|
|
324
|
+
agent.on('trades', (ev) => io.stdout.write(`event trades ${JSON.stringify(ev.data)}\n`));
|
|
325
|
+
agent.on('alerts', (ev) => io.stdout.write(`event alerts ${JSON.stringify(ev.data)}\n`));
|
|
326
|
+
io.stdout.write(`subscribed; watching for ${watch}s…\n`);
|
|
327
|
+
await new Promise((r) => setTimeout(r, watch * 1000));
|
|
328
|
+
}
|
|
329
|
+
agent.close();
|
|
330
|
+
return 0;
|
|
331
|
+
}
|
|
332
|
+
catch (e) {
|
|
333
|
+
io.stderr.write(`dexterm agent connect: ${e.message}\n`);
|
|
334
|
+
return 1;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// task-1345 tier 4: bridge the live agent bus to the roster popup VM, and drive the roster popup window.
|
|
2
|
+
// The popup only ever sends roster.pause / roster.resume / roster.kick (by agent name); this module maps
|
|
3
|
+
// those UI events onto the bus's user-side controls. Nothing here can arm, sign or change a cap.
|
|
4
|
+
/** Build the roster VM the popup renders from the live bus + the audit log. */
|
|
5
|
+
export function buildRosterVM(bus, audit, limit = 80) {
|
|
6
|
+
const agents = bus.roster().map((a) => ({
|
|
7
|
+
id: a.agentId, name: a.name, tier: a.tier, sessions: a.sessions, client: a.client, lastActivity: a.lastActivity,
|
|
8
|
+
topics: a.topics, reads: a.usage.readsPerMin, readsLimit: a.usage.limits.readsPerMin,
|
|
9
|
+
proposals: a.usage.proposalsPerHour, proposalsLimit: a.usage.limits.proposalsPerHour,
|
|
10
|
+
refused: a.counts.refused, paused: a.paused, floodPausedUntil: a.usage.floodPausedUntil,
|
|
11
|
+
}));
|
|
12
|
+
const log = audit.recent(limit).map((r) => ({ t: r.t, agent: r.agentName, kind: r.kind, tool: r.tool, ok: r.ok, detail: r.detail }));
|
|
13
|
+
return { now: Date.now(), busPort: bus.port, agents, audit: log };
|
|
14
|
+
}
|
|
15
|
+
/** Apply a validated popup roster event to the bus (user-side control). Returns a short log line. */
|
|
16
|
+
export function applyRosterEvent(bus, name, agent) {
|
|
17
|
+
if (name === 'roster.pause')
|
|
18
|
+
return bus.pauseAgent(agent) ? `paused ${agent}` : `no agent ${agent}`;
|
|
19
|
+
if (name === 'roster.resume')
|
|
20
|
+
return bus.resumeAgent(agent) ? `resumed ${agent}` : `no agent ${agent}`;
|
|
21
|
+
if (name === 'roster.kick') {
|
|
22
|
+
const n = bus.kickAgent(agent);
|
|
23
|
+
return `kicked ${agent} (${n} session${n === 1 ? '' : 's'})`;
|
|
24
|
+
}
|
|
25
|
+
return `ignored ${name}`;
|
|
26
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// dexterm — the `desktop` adapter (task-1398). macOS `osascript -e 'display notification …'`, Linux
|
|
2
|
+
// `notify-send`. The command is run via execFile with an argv array (NO shell), so a crafted title/body
|
|
3
|
+
// cannot inject a command. macOS osascript takes one AppleScript string on argv; the already-firewalled
|
|
4
|
+
// title/body are additionally AppleScript-escaped (\\, ") and newline-stripped there. The runner is
|
|
5
|
+
// injectable so tests assert the command without spawning anything.
|
|
6
|
+
import { execFile } from 'node:child_process';
|
|
7
|
+
import { deliveryLine } from "../format.js";
|
|
8
|
+
const defaultRunner = (cmd, args) => new Promise((resolve, reject) => {
|
|
9
|
+
execFile(cmd, args, { timeout: 5000 }, (err) => (err ? reject(new Error(err.code === 'ENOENT' ? `${cmd} not found` : err.message)) : resolve()));
|
|
10
|
+
});
|
|
11
|
+
/** AppleScript string literal body: escape backslash and quote, drop any residual control chars. */
|
|
12
|
+
function osaString(s) {
|
|
13
|
+
return '"' + s.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/[\u0000-\u001f\u007f]/g, ' ') + '"';
|
|
14
|
+
}
|
|
15
|
+
export function createDesktopAdapter(deps = {}) {
|
|
16
|
+
const platform = deps.platform ?? process.platform;
|
|
17
|
+
const run = deps.run ?? defaultRunner;
|
|
18
|
+
return {
|
|
19
|
+
name: 'desktop',
|
|
20
|
+
available: () => platform === 'darwin' || platform === 'linux',
|
|
21
|
+
deliver: async (d) => {
|
|
22
|
+
try {
|
|
23
|
+
if (platform === 'darwin') {
|
|
24
|
+
const script = `display notification ${osaString(d.body)} with title ${osaString(d.title)}`;
|
|
25
|
+
await run('osascript', ['-e', script]);
|
|
26
|
+
}
|
|
27
|
+
else if (platform === 'linux') {
|
|
28
|
+
await run('notify-send', ['--app-name=dexterm', d.title, d.body || deliveryLine(d)]);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
return { ok: false, skipped: true, reason: `desktop notifications unsupported on ${platform}` };
|
|
32
|
+
}
|
|
33
|
+
return { ok: true };
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
return { ok: false, reason: e.message };
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { deliveryLine } from "../format.js";
|
|
2
|
+
const nodeFetch = (url, init) => globalThis.fetch(url, init);
|
|
3
|
+
export function createDiscordAdapter(deps) {
|
|
4
|
+
const creds = deps.creds;
|
|
5
|
+
const doFetch = deps.fetch ?? nodeFetch;
|
|
6
|
+
const timeoutMs = deps.timeoutMs ?? 8000;
|
|
7
|
+
return {
|
|
8
|
+
name: 'discord',
|
|
9
|
+
available: () => !!creds?.webhook,
|
|
10
|
+
deliver: async (d) => {
|
|
11
|
+
if (!creds)
|
|
12
|
+
return { ok: false, skipped: true, reason: 'discord not configured (dexterm alerts set-discord)' };
|
|
13
|
+
const ac = new AbortController();
|
|
14
|
+
const t = setTimeout(() => ac.abort(), timeoutMs);
|
|
15
|
+
try {
|
|
16
|
+
const res = await doFetch(creds.webhook, {
|
|
17
|
+
method: 'POST',
|
|
18
|
+
headers: { 'content-type': 'application/json' },
|
|
19
|
+
// Content is already firewalled; cap to Discord's 2000-char limit for safety.
|
|
20
|
+
body: JSON.stringify({ content: deliveryLine(d).slice(0, 1900) }),
|
|
21
|
+
signal: ac.signal,
|
|
22
|
+
});
|
|
23
|
+
if (!res.ok)
|
|
24
|
+
return { ok: false, reason: `discord ${res.status}` };
|
|
25
|
+
return { ok: true };
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
const name = e.name;
|
|
29
|
+
return { ok: false, reason: name === 'AbortError' ? 'discord timed out' : 'discord unreachable' };
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
clearTimeout(t);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { deliveryLine } from "../format.js";
|
|
2
|
+
const nodeFetch = (url, init) => globalThis.fetch(url, init);
|
|
3
|
+
export function createTelegramAdapter(deps) {
|
|
4
|
+
const creds = deps.creds;
|
|
5
|
+
const doFetch = deps.fetch ?? nodeFetch;
|
|
6
|
+
const timeoutMs = deps.timeoutMs ?? 8000;
|
|
7
|
+
return {
|
|
8
|
+
name: 'telegram',
|
|
9
|
+
available: () => !!creds?.token && !!creds?.chatId,
|
|
10
|
+
deliver: async (d) => {
|
|
11
|
+
if (!creds)
|
|
12
|
+
return { ok: false, skipped: true, reason: 'telegram not configured (dexterm alerts set-telegram)' };
|
|
13
|
+
const ac = new AbortController();
|
|
14
|
+
const t = setTimeout(() => ac.abort(), timeoutMs);
|
|
15
|
+
try {
|
|
16
|
+
const res = await doFetch(`https://api.telegram.org/bot${creds.token}/sendMessage`, {
|
|
17
|
+
method: 'POST',
|
|
18
|
+
headers: { 'content-type': 'application/json' },
|
|
19
|
+
body: JSON.stringify({ chat_id: creds.chatId, text: deliveryLine(d), disable_web_page_preview: true }),
|
|
20
|
+
signal: ac.signal,
|
|
21
|
+
});
|
|
22
|
+
// Never echo the token/url: only the status code and Telegram's own error description.
|
|
23
|
+
if (!res.ok) {
|
|
24
|
+
let desc = '';
|
|
25
|
+
try {
|
|
26
|
+
const j = JSON.parse(await res.text());
|
|
27
|
+
desc = j.description ? `: ${j.description}` : '';
|
|
28
|
+
}
|
|
29
|
+
catch { /* ignore body */ }
|
|
30
|
+
return { ok: false, reason: `telegram ${res.status}${desc}`.slice(0, 160) };
|
|
31
|
+
}
|
|
32
|
+
return { ok: true };
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
const name = e.name;
|
|
36
|
+
return { ok: false, reason: name === 'AbortError' ? 'telegram timed out' : 'telegram unreachable' };
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
clearTimeout(t);
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { deliveryLine } from "../format.js";
|
|
2
|
+
export function createToastAdapter(sink) {
|
|
3
|
+
return {
|
|
4
|
+
name: 'toast',
|
|
5
|
+
available: () => true,
|
|
6
|
+
deliver: async (d) => {
|
|
7
|
+
try {
|
|
8
|
+
sink(deliveryLine(d), d);
|
|
9
|
+
return { ok: true };
|
|
10
|
+
}
|
|
11
|
+
catch (e) {
|
|
12
|
+
return { ok: false, reason: e.message };
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// dexterm — alert-channel credentials (task-1398). Bot tokens and webhook URLs are NEVER in config.json
|
|
2
|
+
// (the config schema has no key-shaped fields, by rule). They live in ~/.dexterm/<channel>.json, mode 0600,
|
|
3
|
+
// outside every repo, read only by the adapters. Nothing here ever prints a secret; `redactCreds()` scrubs
|
|
4
|
+
// a token / webhook out of any string before it could reach a log, a toast, a test fixture or an error.
|
|
5
|
+
import { readFileSync, writeFileSync, mkdirSync, statSync, chmodSync, renameSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
export const telegramCredsPath = (home) => join(home, 'telegram.json');
|
|
8
|
+
export const discordCredsPath = (home) => join(home, 'discord.json');
|
|
9
|
+
function readJson(path) {
|
|
10
|
+
let text;
|
|
11
|
+
try {
|
|
12
|
+
text = readFileSync(path, 'utf8');
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const v = JSON.parse(text);
|
|
19
|
+
return v && typeof v === 'object' && !Array.isArray(v) ? v : null;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function writeJson(path, home, value) {
|
|
26
|
+
mkdirSync(home, { recursive: true, mode: 0o700 });
|
|
27
|
+
const tmp = `${path}.tmp-${process.pid}`;
|
|
28
|
+
writeFileSync(tmp, JSON.stringify(value, null, 2) + '\n', { mode: 0o600 });
|
|
29
|
+
// rename is atomic; chmod again in case an old file had looser bits.
|
|
30
|
+
try {
|
|
31
|
+
chmodSync(tmp, 0o600);
|
|
32
|
+
}
|
|
33
|
+
catch { /* best effort */ }
|
|
34
|
+
renameSync(tmp, path);
|
|
35
|
+
}
|
|
36
|
+
export function readTelegramCreds(home) {
|
|
37
|
+
const j = readJson(telegramCredsPath(home));
|
|
38
|
+
if (!j)
|
|
39
|
+
return null;
|
|
40
|
+
const token = typeof j.token === 'string' ? j.token.trim() : '';
|
|
41
|
+
const chatId = typeof j.chatId === 'string' ? j.chatId.trim() : (typeof j.chatId === 'number' ? String(j.chatId) : '');
|
|
42
|
+
if (!token || !chatId)
|
|
43
|
+
return null;
|
|
44
|
+
return { token, chatId };
|
|
45
|
+
}
|
|
46
|
+
export function writeTelegramCreds(home, creds) {
|
|
47
|
+
if (!creds.token.trim())
|
|
48
|
+
throw new Error('telegram token is empty');
|
|
49
|
+
if (!/^-?\d{1,19}$/.test(creds.chatId.trim()))
|
|
50
|
+
throw new Error('telegram chatId must be a numeric id (e.g. 123456789 or -1001234567890)');
|
|
51
|
+
writeJson(telegramCredsPath(home), home, { token: creds.token.trim(), chatId: creds.chatId.trim() });
|
|
52
|
+
}
|
|
53
|
+
export function readDiscordCreds(home) {
|
|
54
|
+
const j = readJson(discordCredsPath(home));
|
|
55
|
+
if (!j)
|
|
56
|
+
return null;
|
|
57
|
+
const webhook = typeof j.webhook === 'string' ? j.webhook.trim() : '';
|
|
58
|
+
if (!webhook)
|
|
59
|
+
return null;
|
|
60
|
+
return { webhook };
|
|
61
|
+
}
|
|
62
|
+
export function writeDiscordCreds(home, creds) {
|
|
63
|
+
const w = creds.webhook.trim();
|
|
64
|
+
if (!/^https:\/\/(?:[a-z0-9.-]+\.)?discord(?:app)?\.com\/api\/webhooks\/\d+\/[\w-]+$/i.test(w)) {
|
|
65
|
+
throw new Error('discord webhook must be a https://discord.com/api/webhooks/<id>/<token> URL');
|
|
66
|
+
}
|
|
67
|
+
writeJson(discordCredsPath(home), home, { webhook: w });
|
|
68
|
+
}
|
|
69
|
+
/** True when the file exists with 0600-ish perms; a looser mode is reported so the user can tighten it. */
|
|
70
|
+
export function credsMode(path) {
|
|
71
|
+
try {
|
|
72
|
+
return statSync(path).mode & 0o777;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const TELEGRAM_TOKEN_RE = /\b\d{6,}:[A-Za-z0-9_-]{20,}\b/g;
|
|
79
|
+
const DISCORD_WEBHOOK_RE = /https:\/\/(?:[a-z0-9.-]+\.)?discord(?:app)?\.com\/api\/webhooks\/\S+/gi;
|
|
80
|
+
/** Scrub any bot token / webhook URL out of a string before it could be logged or shown. Total; never throws. */
|
|
81
|
+
export function redactCreds(s) {
|
|
82
|
+
return String(s ?? '').replace(TELEGRAM_TOKEN_RE, 'bot[redacted]').replace(DISCORD_WEBHOOK_RE, '[redacted-webhook]');
|
|
83
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// dexterm — turn a rule-engine alert / proposal into a firewalled AlertDelivery (task-1398). Pure.
|
|
2
|
+
//
|
|
3
|
+
// Untrusted text (a token symbol, and whatever the user put in the rule's alert text) is run through the
|
|
4
|
+
// prompt firewall (control/bidi/ANSI stripped, URLs → [url], foreign base58 → [addr], wide glyphs → ▯,
|
|
5
|
+
// injection-like content neutralised) and the spoken form through the `say` sanitiser (defuses `[[…]]`,
|
|
6
|
+
// shortens addresses). Nothing reaches a channel raw.
|
|
7
|
+
import { firewallText } from '../../../data/src/index.js';
|
|
8
|
+
import { sanitizeSpeech } from '../../../voice/src/index.js';
|
|
9
|
+
const TITLE_MAX = 64;
|
|
10
|
+
const BODY_MAX = 180;
|
|
11
|
+
/** Firewall a string; keep the row's own mint readable, drop URLs, cap length. */
|
|
12
|
+
function fw(s, max, mint) {
|
|
13
|
+
return firewallText(s, { keepMint: mint ?? null, keepUrls: false, max }).text;
|
|
14
|
+
}
|
|
15
|
+
export function alertToDelivery(a) {
|
|
16
|
+
const symbol = fw(a.symbol, 24) || '?';
|
|
17
|
+
const body = fw(a.text, BODY_MAX, a.mint);
|
|
18
|
+
const title = `${symbol} ⚑`.slice(0, TITLE_MAX);
|
|
19
|
+
return {
|
|
20
|
+
kind: 'alert', level: a.level, title, body,
|
|
21
|
+
spoken: sanitizeSpeech(`${symbol}. ${body}`),
|
|
22
|
+
symbol, mint: a.mint, source: a.source, rule: a.rule, at: a.at,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export function proposalToDelivery(p) {
|
|
26
|
+
const symbol = fw(p.symbol, 24) || '?';
|
|
27
|
+
const what = p.type === 'ticket'
|
|
28
|
+
? `rule proposes ${p.side} ${p.size} ${p.unit} — confirm in the app (nothing armed)`
|
|
29
|
+
: `rule asks the analyst: ${p.skill}`;
|
|
30
|
+
const body = fw(what, BODY_MAX, p.mint);
|
|
31
|
+
const title = `${symbol} ⚑ proposal`.slice(0, TITLE_MAX);
|
|
32
|
+
return {
|
|
33
|
+
kind: 'proposal', level: 'info', title, body,
|
|
34
|
+
spoken: sanitizeSpeech(`${symbol}. ${body}`),
|
|
35
|
+
symbol, mint: p.mint, source: p.source, rule: p.rule, at: p.at,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** The one-line form a text channel sends (Telegram / Discord / stdout toast). */
|
|
39
|
+
export function deliveryLine(d) {
|
|
40
|
+
return d.title === `${d.symbol} ⚑` ? `${d.title} ${d.body}` : `${d.title} — ${d.body}`;
|
|
41
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// dexterm — alert delivery subsystem (task-1398). See DESIGN.md.
|
|
2
|
+
export * from "./types.js";
|
|
3
|
+
export * from "./format.js";
|
|
4
|
+
export * from "./route.js";
|
|
5
|
+
export * from "./creds.js";
|
|
6
|
+
export * from "./notifier.js";
|
|
7
|
+
export { createToastAdapter } from "./adapters/toast.js";
|
|
8
|
+
export { createDesktopAdapter } from "./adapters/desktop.js";
|
|
9
|
+
export { createTelegramAdapter } from "./adapters/telegram.js";
|
|
10
|
+
export { createDiscordAdapter } from "./adapters/discord.js";
|