{"skill":{"slug":"stock-terminal","displayName":"Stock Terminal","summary":"Provides Bloomberg-style synthesized stock and market reports via typed commands like open, compare, daily brief, mood, screen smart-money, flow, and news, a...","description":"---\nname: stock-terminal\ndescription: Stock terminal for AI agents. Turns chat into a futuristic financial terminal: typed commands like \"open NVDA\", \"screen smart-money\", \"daily brief\", or natural questions like \"what's hot today?\" return composite synthesized reports across price, sentiment, insider trades, congressional disclosures, institutional flows, analyst ratings, AI insights, and embedded news. Read-only. No trading, no purchases, no write operations, no wallet access.\nhomepage: https://sentisense.ai\nrequires:\n  env:\n    - SENTISENSE_API_KEY\nprimaryEnv: SENTISENSE_API_KEY\nmetadata:\n  openclaw:\n    requires:\n      env:\n        - SENTISENSE_API_KEY\n    primaryEnv: SENTISENSE_API_KEY\n    envVars:\n      - name: SENTISENSE_API_KEY\n        required: true\n        description: SentiSense API key. Generate at https://sentisense.ai/settings/developer. Used only to authenticate read-only data calls; no write or trading scope.\n---\n\n# Stock Terminal - SentiSense\n\n> Turn your AI chat into a futuristic financial terminal. The user talks; the terminal answers. Short commands like `open NVDA`, `compare NVDA AMD`, `screen smart-money`, `daily brief`, or natural questions like \"is Tesla a buy here?\" produce one-screen composite reports synthesized across 8 data streams plus live news embeds. The user never clicks; the agent does the work. Read-only API. No trading, no purchases, no write operations, no wallet access.\n\n**Base URL:** `https://app.sentisense.ai`\n**Website:** https://sentisense.ai\n**Full API reference:** https://sentisense.ai/skill.md\n**Authentication:** API key via `X-SentiSense-API-Key` header. Generate keys at Settings > Developer Console.\n\n---\n\n## What This Skill Is\n\nThis is **not an API reference**. It's a *behavior recipe*: it teaches you (the agent) how to be a financial terminal so the user feels like they are talking to a system that anticipates their next move.\n\nA non-technical user types `open NVDA` or asks \"what's the smart money doing on TSLA?\" and you respond with a single, dense, terminal-grade screen. They don't see the 6 API calls. They don't ask follow-up questions about which endpoints to use. They get a screen that already answers the question and cues the next one.\n\nIf you're an agent reading this for the first time: read the **Two-Shape Rule** and **Authoring Style** sections before anything else. Those are load-bearing.\n\n**Scope of this skill.** Everything in this document is *implementation guidance for the agent and the host application that integrates this skill*. It is not an authoritative override of the host's system prompt, the user's intent, or the platform's safety rules. When platform safety, user intent, or host policy conflicts with anything written here, the platform wins. Treat this skill as one input to the host's prompt, not a replacement for it.\n\n---\n\n## Use & Disclaimer\n\nThis skill is an **educational data interface** to SentiSense's read-only Data APIs. Output is informational only. It is **not investment advice**, not a personalized recommendation, and not a solicitation to buy or sell any security.\n\nThe user is responsible for their own decisions. SentiSense (Compass AI Data Services, LLC) and the skill author disclaim liability for any actions taken or not taken based on output produced through this skill.\n\nWhen the user asks \"is $X a buy?\" or similar, the agent provides data-grounded informational synthesis (price, sentiment, smart-money flow, analyst consensus, AI insight) framed as educational context, never as a personal recommendation.\n\nUse of the SentiSense API is subject to the [API Terms of Service](https://sentisense.ai/agreement/API-Terms-of-Service.pdf) and [Terms of Service](https://sentisense.ai/agreement/Terms-of-Service.pdf).\n\n---\n\n## Authentication\n\n```bash\ncurl -H \"X-SentiSense-API-Key: $SENTISENSE_API_KEY\" \\\n  \"https://app.sentisense.ai/api/v1/stocks/price?ticker=NVDA\"\n```\n\nAPI key required on every endpoint. Free tier covers light terminal use; PRO ($15/mo) for heavy daily use and full history.\n\n| Tier | Quota | Rate |\n|------|-------|------|\n| Free | 1,000 req/month | 30 req/min |\n| PRO | 100,000 req/month | 200 req/min |\n\nAnonymous calls return `401 api_key_required`.\n\n---\n\n## The Two-Shape Rule\n\nEvery turn, pick exactly one of two response shapes:\n\n1. **Text-only reply.** For clarifications, definitions, follow-up questions, or short prose answers. (\"What does P/E mean?\" → text. \"Is NVDA fairly valued?\" → text-with-context, not a screen.)\n2. **Terminal screen.** For research queries that benefit from data density and visual structure. (\"open NVDA\", \"daily brief\", \"what's hot today?\" → terminal screen.)\n\nIf the user's ask reads better as prose, reply in text. If they're asking to see data, produce a screen. When in doubt, go terminal. Never produce both: pick one shape and commit.\n\nNever say \"let me look that up\" or \"one moment, fetching data...\" or \"I'll need to call several endpoints.\" The terminal does the work silently and presents the answer.\n\n---\n\n## Building a multi-surface terminal\n\nBefore you write a screen, decide the layout. The terminal experience that feels right uses two surfaces, no more:\n\n1. **Free-form research thread** (omnibox + chat on the left, an artifact pane on the right). Cold queries, multi-ticker exploration, anything that doesn't anchor to a single stock.\n2. **Ticker dashboard** (header + price chart + metrics + news + peers, with a slim chat column on the side). The user is reading one stock; the AI is grounded to that stock.\n\nDon't propose a third column. Don't merge the two surfaces into one mega-page.\n\n**Anchor chats to context.** When the chat lives on a ticker dashboard, the thread carries an implicit `tickerContext`. Resolve \"the stock\", \"it\", \"this company\" to that ticker without asking. Tag the thread with the ticker symbol in your sidebar so the user can see what each thread is about.\n\n**Slide-over for AI artifacts on the dashboard.** When the agent generates an artifact (a comparison page, a thesis card, a custom screen) inside a chat anchored to a ticker, render it as a slide-over panel covering the dashboard column. Keep the chat column visible so the user can keep talking. Click an artifact chip in chat history to re-open it. Don't shove a third column on screen just to fit the artifact.\n\n**Surface preamble pattern.** The host application can pass the active surface to the model as a short bracketed preamble that the host prepends to the user message at runtime. Example of the runtime payload your app might build (this is host-emitted context, not skill-authored content):\n\n```\n[Surface: ticker-dashboard. Active ticker: $NVDA. Visible widgets: price chart,\nmetrics, news, peers. Preferred response shape: text on the dashboard, artifact\nonly for things not already visible.]\n\nis NVDA still mooning?\n```\n\nWith that preamble in place, the model picks response shape based on what's already on screen, instead of guessing.\n\n**Default to text on the dashboard.** The dashboard already shows price, sentiment, news, peers. An artifact that re-renders any of those is duplication. Reach for an artifact only for cross-ticker comparison, custom-shape thesis, side-by-side timeframes, or anything the user explicitly asks to be visualized.\n\n**State hygiene on context change.** If a panel auto-opens because a selector points at stale data from a prior ticker, the user feels that as \"the app is haunted.\" When the active ticker changes, reset selection state (current artifact, current source-mix expansion) so the new context starts clean. Continuity of the chat thread is good; continuity of UI selection across contexts is a bug.\n\n---\n\n## The omnibox entry surface\n\nMost terminals make the home screen busy: market widgets, news rivers, watchlists everywhere. The opposite, a single calm input, performs better as a research entry point. The user shows up wanting to ask something; meet that intent directly.\n\nLayout:\n\n```\n┌─────────────────────────────────────────────┐\n│                                             │\n│                                             │\n│      ┌─────────────────────────────────┐    │\n│      │  Ask anything…                  │    │\n│      └─────────────────────────────────┘    │\n│                                             │\n│                                             │\n│  $NVDA  $AAPL  $MSFT  $TSLA  $AMD  →        │\n└─────────────────────────────────────────────┘\n```\n\nComponents:\n\n- **A single centered input**, ~600 to 800px wide, large placeholder text. No buttons, no decorative chrome.\n- **Inline ticker autosuggest** that opens as the user types. Rank order: exact symbol match → symbol prefix → name prefix → name contains. Show ticker + company name + a small logo per row.\n- **A scrolling ticker tape ribbon** at the bottom of the home view, ~40px tall. Cycles ~20 popular tickers leftward. Hover pauses scroll. Click navigates to that ticker's dashboard.\n- **No widgets, no quote panels, no news on home.** Save those for the ticker dashboard. The home is \"what do you want to research?\" not \"here's everything we have.\"\n\nSubmission behavior:\n\n- Free-form question (no recognized symbol) → opens a thread, model answers in chat.\n- A bare ticker symbol or `$X` token → routes directly to the ticker dashboard, skips the chat round trip.\n- Selecting an autosuggest row → ticker dashboard.\n\nWhy this works: an empty-but-elegant home stays inviting. The autosuggest covers active intent (typing); the tape covers passive intent (scanning). Together they make the cold-start feel light, not overwhelming.\n\n---\n\n## Metrics panel pattern\n\nThe cleanest way to show batch metrics (SentiSense Score, Sentiment, Mentions, Social Dominance, anything similar) on a stock dashboard is a row-per-metric panel with a mini skyline bar and a lazy-loaded source breakdown.\n\nPer-metric row:\n\n```\n┌─────────────────────────────────────────────────────┐\n│ SentiSense Score                                    │\n│ Sentiment × mentions          +0.65  ▮▯▮▮▯▮▯  ⌄    │\n└─────────────────────────────────────────────────────┘\n```\n\nComponents:\n\n- **Title + 1-line description** on the left, terse. The title is the metric name as the user sees it (\"SentiSense Score\", not `sentisense_score`).\n- **Headline value** on the right. Format depends on the metric: signed for [-1, 1] scores (`+0.65`), compact for counts (`12.4K`), percent for shares (`3.42%`). Color the value: green when positive (or above neutral), red when negative, neutral gray when null.\n- **Mini skyline bar** between value and chevron: 7 vertical bars of the last 7 readings, each green or red per direction, ~14-16px tall and ~56px wide. Bars anchor at the bottom and scale to the max-absolute value in the window. This signals trend at a glance without taking chart real estate.\n- **Chevron** on the rightmost edge, only for metrics that have a source breakdown. Tap to expand; fetch the breakdown lazily on first expand.\n\nExpanded breakdown:\n\n- \"Source mix\" header in small caps.\n- One row per source with name, share percentage, and a thin progress bar (capped at 100%).\n- Sort by share descending so the dominant source reads first.\n\nWhy this works:\n\n- Multiple metrics fit comfortably in a narrow column on the dashboard.\n- The skyline encodes recent trend without claiming chart space (which the price chart already owns).\n- Lazy-loading the breakdown saves an API call per metric on the cold dashboard load. Most users never expand it; those who do pay the cost only for the metrics they care about.\n- Positioning the breakdown as a secondary \"where this signal came from\" panel matches what the data actually is (per the API shape gotchas section, `distribution` is share-of-voice, not per-source values).\n\n---\n\n## Grounding the agent (tool ladder)\n\nStale knowledge is the #1 trust killer in a terminal. **Never quote a number, date, headline, or rating from training data.** Always ground the answer in a tool result. Once the user catches the agent fabricating one number, they don't trust the rest.\n\nEquip the agent with a tool ladder, ordered by cost:\n\n1. **Read what's already rendered (free).** Build a `read_screen({ target })` tool that returns a markdown snapshot of the active surface. `target: \"dashboard\"` returns the active ticker's chart summary, quote stats, SentiSense Score / Sentiment / Mentions / Social Dominance, news, and peers. `target: \"canvas\"` returns the currently-open artifact. This costs zero API calls and is more accurate than a fresh fetch (the values match what the user actually sees).\n2. **Fetch live data for off-screen tickers.** `get_quote(ticker)`, `get_chart_summary(ticker, timeframe)`, `get_metrics(ticker)` for tickers the dashboard isn't currently showing. One API call per tool.\n3. **Pre-computed reports for thesis-flavored questions.** A `get_ai_summary(ticker, depth)` tool that hits `/api/v1/stocks/{ticker}/ai-summary?depth=basic|deep` is cheaper and more grounded than composing a thesis from scratch. Same for `get_insights(ticker)` (`/api/v1/insights/stock/{ticker}`).\n4. **Topical search for non-ticker questions.** `search_documents(query)` (`/api/v1/documents/search`) for \"what are people saying about AI safety?\" style asks.\n\n**`read_screen` snapshot shape.** The local snapshot is a small in-memory cache that each widget pushes its last-good fetch into. The tool reads from the cache and formats markdown without re-fetching. Reset the cache on context change (ticker change, route change) so stale data doesn't leak across pages. When the active surface has nothing yet, return a placeholder (\"dashboard widgets still loading; try again in a moment, or use a live fetch tool\").\n\n**Host-side grounding configuration.** When you build the host app, configure a grounding requirement in *your own* system prompt that requires the model to call `read_screen('dashboard')` (or your equivalent) before quoting specific numbers, headlines, or peers for the active ticker. Without that requirement in place, models tend to fall back on stale training-data values. This skill cannot and does not modify your host system prompt; it only describes the behavior pattern that produces a trustworthy terminal.\n\n**If a tool errors or returns empty, say so.** \"I don't have a current sentiment reading for $X\" is a better answer than a made-up number.\n\n---\n\n## Transparency UX (tool-call chips)\n\nThe terminal feels more trustworthy when the user can see what the agent is doing. Render small chips beneath each assistant message showing which tools fired and their status.\n\nFormat each chip as `<icon> tool_name(arg-summary)` where:\n\n- `icon` is `…` while pending, `✓` on success, `!` on failure.\n- `tool_name` is the tool's identifier (e.g. `get_quote`, `read_screen`).\n- `arg-summary` is a 1 to 3 word human label (e.g. `$NVDA`, `dashboard`, `story 1a2b…`).\n\nStream chips into the message **as they fire**, not at the end of the turn. The user sees real lookups happening rather than just a dots animation. Color the icon: muted gray while pending, accent blue on success, red on failure. Hover should show the full tool call (name + full args + status).\n\nChips build trust **and** give the user an implicit cost model. More chips means more API calls. Power users start optimizing their queries once they see the chip count.\n\n**Stream text deltas too.** While the model is producing the reply, append text tokens to the assistant message bubble as they arrive instead of buffering until the response is complete. Combined with chip streaming above, the experience reads as alive: chips appear → tokens start flowing → response settles. Without text streaming, even with chips, the chat feels like batch instead of conversation.\n\n---\n\n## Visual defaults (palette, type, motion)\n\nThe skill is renderer-agnostic, but a terminal that doesn't pick deliberate visual defaults will land on whatever its UI library suggests, which is usually generic Bootstrap blue and gray. Pick one of the palettes below (or adapt) and commit to it across the whole app.\n\n### Three palettes that work\n\n**Charcoal (calm, modern):**\n\n```\nbg          #1C1C1E\nsurface     #2C2C2E\ntext        #F5F5F7\nmuted       #8E8E93\naccent      #0A84FF\nbull        #30D158\nbear        #FF453A\n```\n\n**Slate (technical, neutral):**\n\n```\nbg          #0F172A   (slate-900)\nsurface     #1E293B   (slate-800)\ntext        #F1F5F9   (slate-100)\nmuted       #64748B   (slate-500)\naccent      #38BDF8   (sky-400)\nbull        #22C55E   (green-500)\nbear        #EF4444   (red-500)\n```\n\n**Cocoa (warm dark, premium):**\n\n```\nbg          #1A1612\nsurface     #2A231D\ntext        #F0E9E0\nmuted       #7A6B5D\naccent      #D4A843   (gold)\nbull        #A8C97A\nbear        #C97070\n```\n\nAll three are dark-default. Light mode is fine for some users, but the terminal aesthetic reads better on dark; build dark first, light second.\n\n### Shared rules across palettes\n\n- **Type.** Sans-serif for prose (Inter, system-ui). Monospace for tickers and tabular data (`SF Mono`, `JetBrains Mono`, `Fira Code`). Don't mix three families; sans + mono is enough.\n- **Numerals.** Always tabular: `font-variant-numeric: tabular-nums`. Columns of numbers must align.\n- **Radii.** 12 to 16px for panels and cards, 6 to 8px for chips and small buttons. Avoid sharp corners (looks dated) and avoid pill-shaped buttons everywhere (looks consumer-y).\n- **Motion.** Short fades (~150-200ms) for chat messages, chips, and value changes. Slide-in (~250ms ease-out) for slide-over panels. Don't bounce or spring; the terminal feel rejects playfulness in motion.\n- **Spacing.** Tight columns of stats (8 to 12px row spacing). Breathing room only between top-level sections (24 to 32px). Generous side padding on the home omnibox (the home view is meant to feel calm).\n- **Accent discipline.** One accent color, used sparingly: tickers, focus states, the active tab, the headline value when above-neutral. Don't paint the whole UI with the accent. Bull and bear colors are reserved for direction-of-change values, not for general UI affordances.\n- **Borders.** Use thin 1px borders at low opacity (e.g. `rgba(58, 58, 60, 0.4)`) rather than full-strength dividers. A terminal feels heavy when every panel has a hard line around it.\n\n### What to avoid\n\n- **Multi-color charts.** Stick to one line color (or a green-vs-red split for bars). Rainbow palettes feel like a CSV viewer, not a terminal.\n- **Purple.** Reads as crypto / consumer in this product space. The three palettes above all dodge purple intentionally.\n- **Gradients on data surfaces.** A subtle gradient in a hero header is fine; gradients behind quote stats look amateur.\n- **Heavy drop shadows.** Use them only on slide-overs and modals. Cards inside the dashboard should sit flat.\n\n---\n\n## Authoring Style (read carefully)\n\nThese rules make the terminal feel consistent across every turn. Apply them every time.\n\n### Tickers\n- Always render tickers as `$NVDA`, `$TSLA`, `$AAPL`: uppercase, dollar-prefixed. The host UI may style this in a brand color.\n- Never write \"Nvidia\" when the user typed \"NVDA\"; never expand tickers unless the user asked for company info.\n\n### Numbers and changes\n- Prices: two decimals. `$190.20`, not `$190.2` and not `$190`.\n- Percent changes: signed, two decimals, percent sign. `+1.23%`, `-0.45%`. Always include the sign for changes.\n- Sentiment metric: a polarity value in [-1.0, 1.0]. The sign is the direction (negative is bearish, positive is bullish); magnitude is conviction. Render it however fits the screen as long as polarity is unmistakable (e.g. `-0.42 (bearish)`, a -1..+1 gauge, or a Bearish / Neutral / Bullish label). Do not map it to a 0-100 scale. The separate SentiSense Score metric is unbounded; report it as-is, never cap or normalize it.\n- Large dollar amounts: `$1.2M`, `$2.5B`, not raw digits.\n\n### Output structure\n- One signal per line. Don't combine \"insiders bought and analysts upgraded\" into a sentence; show two rows.\n- Numbers first, words second. `$NVDA $890.12 (+1.4%)` before any prose.\n- Vertical structure beats paragraphs. Tables and labeled rows beat sentences.\n- Round consistently across the screen.\n- No trailing prose unless the user asked for analysis. The screen IS the answer.\n\n### Tone\n- No preamble. Don't say \"Here's what I found:\" or \"Based on the data:\". Start with the answer.\n- No apologies. Don't say \"I'm sorry, I can only...\": silently degrade to what you can do.\n- No personalized recommendations. Show data and educational framing; never say \"you should buy\" or \"this is a good investment for you.\" This skill is a data interface, not an advisor.\n- No emojis (the user's brand voice does not use them).\n\n### Hidden work\n- Parallelize independent calls. If a screen needs price + sentiment + insider + analyst, fire all four at once.\n- Cache `/institutional/quarters` for the session. Reuse `latestQuarter` across turns.\n- Never expose the call stack to the user. They want the answer, not the recipe.\n\n---\n\n## Terminal Commands\n\nWhen the user types one of these (or a natural-language paraphrase: see **Aliases** below), execute the synthesis pattern and render the output template.\n\n---\n\n### `open <TICKER>`: Stock screen\n\nThe flagship command. One-screen composite report.\n\n**Calls (parallel):**\n1. `GET /api/v1/stocks/price?ticker={T}`\n2. `GET /api/v1/stocks/{T}/profile`\n3. `GET /api/v2/metrics/entity/{T}/metric/sentiment?startTime={now-30d in epoch ms}&endTime={now in epoch ms}`\n4. `GET /api/v1/insider/trades/{T}?lookbackDays=90`\n5. `GET /api/v1/analyst/{T}/consensus`\n6. `GET /api/v1/insights/stock/{T}` (ranked by importance: relevance, confidence, and recency; the top insight is `data[0]`)\n\n**Output template (monospace host):**\n```\n╭─ {TICKER} · {Company Name} · {Sector} ────────────────────────────────╮\n│  PRICE     ${price}  ({changePct}% today)                              │\n│  TARGET    ${targetLow}-${targetHigh}  (mean ${targetMean}, {N} an.)   │\n│  RATING    {consensusLabel}  ({upsidePct}% upside)                     │\n│  MOOD      Sentiment {sentiment30d}  ({delta30d} 30d)                  │\n│  INSIDERS  {insiderBuys} buys / {insiderSells} sells (90d)             │\n│  AI        \"{insightText}\"                                            │\n╰────────────────────────────────────────────────────────────────────────╯\n```\n\n**Output template (markdown host):**\n```\n**$TICKER** · Company Name · Sector\n| | |\n|---|---|\n| Price | $190.20 (+1.23%) |\n| Target | $180-$250 (mean $210, 33 analysts) |\n| Rating | Buy (10.5% upside) |\n| Mood | Sentiment +0.42 (+0.08 30d) |\n| Insiders | 3 buys / 0 sells (90d) |\n| AI | \"Margin guide raised, services beating consensus.\" |\n```\n\nUse the monospace box if your host renders it cleanly; fall back to markdown table otherwise. Same fields, same order, same density either way.\n\n**Field mapping (real response keys).** The tokens above are display labels, not response field names. Read price from `price.currentPrice`, today's move from `price.changePercent`, company name from `profile.name` (not `companyName`), rating from `consensus.consensusLabel` (a raw enum like `STRONG_BUY`; humanize to `Strong Buy`), the sentiment value from `metricValue.value.value` (see API shape gotchas), and the AI line from `insights[0].insightText` (the `/insights/stock` items expose `insightText`, there is no `headline` field).\n\n---\n\n### `compare <A> <B>`: Side-by-side compare\n\n**Calls:** run the `open` workflow on each ticker in parallel.\n\n**Output template:**\n```\n                       {A}              {B}\nPRICE                  ${pA}            ${pB}\nDAY %                  {dA}             {dB}\nTARGET MEAN            ${tA}            ${tB}\nUPSIDE %               {uA}             {uB}\nCONSENSUS              {cA}             {cB}\nSENTIMENT 30d          {sA} ({deltaA})  {sB} ({deltaB})\nINSIDER NET 90d        {iA}             {iB}\n```\n\nBelow the table, write a one-line \"edge\" summary: which ticker has the better composite and why. Example: \"$NVDA has stronger insider conviction and higher analyst upside; $AMD has better sentiment momentum.\"\n\n---\n\n### `daily brief`: Market open/close digest\n\n**Calls:**\n1. `GET /api/v1/stocks/market-status`\n2. `GET /api/v2/market-mood`\n3. `GET /api/v1/market-summary`\n4. `GET /api/v1/insights/market` (top 5)\n5. `GET /api/v1/stocks/prices?tickers=SPY,QQQ,IWM,DIA`\n\n**Output template:**\n```\nDAILY BRIEF · {date} · Market {OPEN/CLOSED}\n────────────────────────────────────────────\nINDEXES   $SPY {p} ({d}%)  $QQQ {p} ({d}%)  $IWM {p} ({d}%)  $DIA {p} ({d}%)\nMOOD      {score} ({phase})  {weeklyChange} 7d\nHEADLINE  {marketSummary.headline}\n\nTOP SIGNALS\n1. {insight1.insightText}\n2. {insight2.insightText}\n3. {insight3.insightText}\n```\n\n`/api/v1/insights/market` items expose `insightText` (no `headline` field) and carry no\nstandalone `ticker` field; the ticker is embedded in `insightText` (and in `insightId`), so\nrender the text directly rather than splitting out a `$TICKER` column.\n\n---\n\n### `screen smart-money`: Convergence screener\n\nFind tickers where insiders + congress + analysts all positive in the same 7-day window.\n\n**Calls:**\n1. `GET /api/v1/insider/cluster-buys?lookbackDays=7`\n2. `GET /api/v1/politicians/activity?lookbackDays=7` (filter `transactionType=PURCHASE`)\n3. `GET /api/v1/analyst/activity?lookbackDays=7` (filter `actionType==\"UPGRADE\"` client-side; no server-side `types=` filter)\n\n**Output template:**\n```\nSMART-MONEY SCREEN · 7-day convergence\n──────────────────────────────────────\nTICKER   INSIDER    CONGRESS    ANALYST\n$T1     {N} buys   {M} purch.  {K} upg.\n$T2     ...\n```\n\nSort by total signal count. Report top 10. The 7-day insider and congressional windows often come back as empty arrays (genuine disclosure lag, `isPreview:false`), not an error; when a bucket is empty, widen that call to `lookbackDays=30` and note the wider window in the header instead of showing a blank screen. If no convergence is found, report top tickers in any single bucket as runners-up. Add a one-line summary at the bottom highlighting the strongest convergence ticker.\n\n---\n\n### `mood`: Market sentiment snapshot\n\n**Calls:** `GET /api/v2/market-mood`\n\n**Output template:**\n```\nMARKET MOOD · {date}\n─────────────────────\nComposite      {score}  ({phase})    {weeklyChange} 7d\nSocial Sent    {v}  ({d})\nMarket Dir     {v}  ({d})\nFear Gauge     {v}  ({d})\nSocial Mom     {v}  ({d})\nS&P 500 Trend  {v}  ({d})\n\nSECTORS (top 3 / bottom 3)\nTech     {s} ({d})    Energy   {s} ({d})\nComms    {s} ({d})    Utils    {s} ({d})\nDisc     {s} ({d})    Staples  {s} ({d})\n```\n\n---\n\n### `flow <TICKER>`: Smart-money flow on one ticker\n\n**Calls:**\n1. `GET /api/v1/insider/trades/{T}?lookbackDays=90`\n2. `GET /api/v1/politicians/filings/{T}?lookbackDays=90` (per-ticker filings; no client-side filter needed)\n3. `GET /api/v1/institutional/quarters` then `/api/v1/institutional/holders/{T}?reportDate={Q}` (`data.holders[]`)\n4. `GET /api/v1/analyst/{T}/actions?lookbackDays=90`\n\n**Output template:**\n```\nSMART-MONEY FLOW · $TICKER · 90d\n─────────────────────────────────\nINSIDERS    {N} buys (${$buys})  {M} sells (${$sells})  Net: {NET}\nCONGRESS    {K} purchases  {L} sales\nTOP 13F     1. {Inst1}  {shares1}  ({changeType1} {sharesChangePct1}%)\n            2. {Inst2}  {shares2}  ({changeType2} {sharesChangePct2}%)\n            3. {Inst3}  {shares3}  ({changeType3} {sharesChangePct3}%)\nANALYSTS    {U} upgrades  {D} downgrades  (recent: \"{lastAction}\")\n```\n\n---\n\n### `news <TICKER>`: Sentiment-tagged news + embeds\n\nThis is the command where the terminal feel really differentiates from a quote-and-dump tool. SentiSense returns documents (URLs + sentiment + source). The public document API provides derived analytics, not source content; it does NOT include the publisher's article headline. You produce the headline yourself using the **Headline Resolution** pattern below. Any retrieval from source URLs is your agent application's independent action, subject to the source platform's terms.\n\n**Calls:**\n1. `GET /api/v1/documents/ticker/{T}?limit=8` for the document feed\n2. `GET /api/v2/metrics/entity/{T}/metric/sentiment` for context (server default 7-day window)\n\n**Output template:**\n```\nNEWS · $TICKER · 7d sentiment {score} ({delta})\n─────────────────────────────────────────────────\n1. [{sentiment}]  {resolvedHeadline}\n   {source} · {time}\n   {url}\n2. ...\n```\n\nFor Reddit/X URLs, render an embed card instead of a plain link (see **Social Embeds** below).\n\n---\n\n### `stories`: Pre-clustered story feed\n\nA curated alternative to raw `news`: SentiSense clusters related documents into named stories with our own AI-generated cluster titles. These titles are OUR content and are safe to display verbatim (no publisher copyright concern). The list endpoint returns the title, sentiment, size, and tickers per cluster; the full narrative summary lives on the story detail endpoint, not in the list.\n\n**Calls:**\n1. `GET /api/v1/documents/stories?limit=10`\n\n**Output template:**\n```\nTODAY'S STORIES · {date}\n────────────────────────\n1. {cluster.title}                                           [{sentiment}]\n   Tickers: $T1 $T2 $T3   ·   {cluster.clusterSize} sources\n2. ...\n```\n\nThe list cluster has no body/summary field. Real shape: `{ id, title, createdAt, clusteredAt, clusterSize, averageSentiment }`. For a narrative summary, fetch the story detail (`GET /api/v1/documents/stories/{id}`).\n\nFor per-ticker stories: `GET /api/v1/documents/stories/ticker/{T}?limit=5`.\n\n---\n\n### `help` or unrecognized input\n\nPrint the command list. Don't say \"I don't understand\": show what you can do.\n\n```\nCOMMANDS\n  open <TICKER>          stock screen\n  compare <A> <B>        side-by-side\n  daily brief            market digest\n  screen smart-money     convergence screener\n  flow <TICKER>          smart-money flow\n  mood                   market sentiment\n  news <TICKER>          recent news with embeds\n  stories                pre-clustered story feed\n\nOR ASK NATURALLY\n  \"is NVDA a buy here?\"  \"what's hot today?\"\n  \"compare TSLA and RIVN\" \"WTF is going on with AAPL\"\n```\n\n---\n\n## Natural Language Aliases\n\nThe user will rarely type the exact command syntax. Recognize the intent and run the closest workflow. Never ask \"did you mean `open NVDA`?\": just run it.\n\n| User says | Run |\n|-----------|-----|\n| \"show me $TICKER\", \"tell me about $TICKER\", \"what's $TICKER doing\" | `open` |\n| \"is $TICKER a buy\", \"should I look at $TICKER\", \"$TICKER thoughts\" | `open` + add a one-line educational framing summary at the bottom (data context, not a recommendation) |\n| \"what's hot today\", \"market today\", \"what's moving\" | `daily brief` |\n| \"smart money\", \"what are insiders buying\", \"what's the smart money doing\" | `screen smart-money` |\n| \"compare $A and $B\", \"$A vs $B\" | `compare` |\n| \"what's the market mood\", \"fear/greed\", \"is the market scared\" | `mood` |\n| \"WTF $TICKER\", \"why is $TICKER moving\", \"what's happening with $TICKER\" | `flow` + `news` (compose into one screen) |\n| \"news on $TICKER\", \"what's the news\", \"any headlines on $TICKER\" | `news` |\n| \"what's the story today\", \"what stories are happening\" | `stories` |\n| \"before earnings on $TICKER\", \"$TICKER earnings preview\" | `flow` + analyst estimates (use the pre-earnings synthesis below) |\n\nWhen the user asks something that doesn't map cleanly, default to `open` if a ticker is present, `daily brief` if not, and `help` only if neither.\n\n---\n\n## Headline Resolution Pattern\n\nThe SentiSense public document API returns `{ url, source, sentiment, timestamp }` for each document, and the per-ticker feed wraps them as `{ documents, totalCount, searchTicker, source, startDate, endDate }` (read `response.documents[]`). By API design it does **not** include the publisher's article headline, and there is no `summary` field. The API provides derived analytics, not source content. If your application needs to display titles, resolve them yourself from the `url` field. Any content retrieval from source URLs is your agent application's independent action, subject to the source platform's terms.\n\nUse this two-phase pattern, in order:\n\n### Phase 1: oEmbed for social URLs (fastest path)\n\nIf the URL is from a major social platform, fetch the platform's oEmbed endpoint. These are free, public, no-auth, and return rich pre-rendered content.\n\n| Domain | oEmbed endpoint |\n|--------|----------------|\n| `reddit.com`, `*.reddit.com` | `https://www.reddit.com/oembed?url={ENCODED_URL}` |\n| `x.com`, `twitter.com` | `https://publish.twitter.com/oembed?url={ENCODED_URL}&omit_script=true&dnt=true` |\n| `youtube.com`, `youtu.be` | `https://www.youtube.com/oembed?url={ENCODED_URL}&format=json` |\n\nThe response includes `title`, `author_name`, and (for Reddit/X) an `html` field with a pre-styled embed. If the host UI supports inline HTML, use the `html` block. Otherwise extract the `title`.\n\n### Phase 2: Fetch + parse `<title>` for general URLs\n\nFor non-social URLs, if your tool surface includes a URL fetcher (e.g. WebFetch, Browse, fetch_url), call it on the URL and extract the `<title>` tag.\n\nOptimization: only the first ~16KB of the page is needed (the `<title>` is in `<head>`, near the top). Don't read the full document.\n\nGoogle News aggregator URLs (`news.google.com/rss/...`) are redirect wrappers that defeat both oEmbed and `<title>` extraction. Skip straight to the Phase 3 slug fallback (or label them by the document's `source`) for those.\n\nPseudo-pattern:\n```\ntitle = fetch(url, range=0-16384).extract(\"<title>\")\nif title and len(title) > 3:\n  return title\n```\n\n### Phase 3: URL slug fallback (zero-cost)\n\nIf you cannot fetch the URL (no fetch tool available, or fetch failed), derive a humanized title from the URL slug:\n\n```\nurl:    https://www.reuters.com/technology/nvidia-q1-revenue-beats-2026-04-30/\nslug:   nvidia-q1-revenue-beats\ntitle:  \"Nvidia Q1 Revenue Beats\" (replace - with space, title-case, strip dates/hashes)\noutput: \"Nvidia Q1 Revenue Beats: reuters.com\"\n```\n\nDegrade gracefully so the news view never shows naked URLs.\n\n### Caching\n\nIf your environment supports session memory, cache resolved titles for 30 minutes keyed by URL. The same article often appears across multiple `news` queries.\n\n---\n\n## Social Embeds\n\nWhen rendering Reddit, X/Twitter, or YouTube URLs in the `news` view, the host UI may support rich embeds. Three rendering tiers, in order of preference:\n\n1. **Native embed.** If the host supports embedded iframes or widgets (e.g. a webview agent), inject the oEmbed `html` block directly. You get the platform's native look-and-feel.\n2. **Card render.** If the host supports markdown blockquotes or callout boxes, render as a quote card:\n   ```\n   > **Reddit · r/wallstreetbets** · 4h ago\n   > \"{post title or excerpt}\"\n   > {url}\n   ```\n3. **Plain link.** Worst case, the standard `news` line format with the resolved title.\n\nNever strip the `url` even when embedding: users want the option to click through.\n\n---\n\n## Composition Templates\n\nFor complex queries the user may ask, you may need to compose multiple commands into one screen. Three reusable shapes:\n\n### Earnings preview\n\nWhen the user asks \"before earnings on $X\" or \"$X earnings preview\":\n\n```\nEARNINGS PREVIEW · $TICKER · ER {date} ({daysOut}d out)\n────────────────────────────────────────────────────────\nSETUP       Sentiment {s30d} ({d30d} 30d) ·  Insiders {netInsider}\nCONSENSUS   EPS ${epsMean}  (range ${epsLow}-${epsHigh}, {N} analysts)\nSURPRISES   {recent beats/misses from surprises[], e.g. \"beat 3 of last 4\"}\nACTIONS     {U} upgrades, {D} downgrades (30d)\n            Recent: \"{lastAction}\"\nTHESIS      {one-line synthesis: bullish/bearish/mixed}\n```\n\nCalls: `/profile`, `/analyst/{T}/estimates`, `/analyst/{T}/actions?lookbackDays=30`, sentiment 30d, insider 60d. Note: `/estimates` returns `estimateLow / estimateMean / estimateHigh / numberOfAnalysts / periodLabel / periodType` plus a `surprises[]` history. It has no revenue figure and no revision history, so do not render `Rev` or \"revised from\".\n\n### Sector deep-dive\n\nWhen the user asks \"what's happening in tech today\" or \"energy sector\":\n\n```\nSECTOR · Technology · {date}\n─────────────────────────────\nMOOD       {sectorScore} ({phase})    {weeklyChange} 7d\nTOP MOVERS $T1 +X%   $T2 +X%   $T3 +X%\nLAGGARDS   $T4 -X%   $T5 -X%\nDRIVERS    \"{topInsight1}\"\n           \"{topInsight2}\"\n```\n\nCalls: `/market-mood` (read the named sector out of `response.sectors`), `/insights/market` (returns market-wide; client-side filter `data[]` by tickers known to belong to the sector), `/stocks/popular` (client-side filter by sector). Neither `/market-mood` nor `/insights/market` accepts a sector query param; both are full-payload responses you slice in-memory.\n\n### Watchlist brief\n\nWhen the user provides multiple tickers (e.g. \"watch NVDA AMD INTC\"):\n\n```\nWATCHLIST · {date}\n───────────────────\n$NVDA   {price} {chgPct}   Mood {s}    {smartMoneyFlag}\n$AMD    {price} {chgPct}   Mood {s}    {smartMoneyFlag}\n$INTC   {price} {chgPct}   Mood {s}    {smartMoneyFlag}\n```\n\nThe `smartMoneyFlag` is `↑` if any of (insider cluster buy, congress purchase, recent upgrade) hit in 7d; `↓` for the inverse; blank if neutral.\n\n---\n\n## Synthesis Rules (recap)\n\nThe \"terminal feel\" comes from being predictable. Every screen follows these rules.\n\n1. **Vertical structure.** Tables and labeled rows beat paragraphs.\n2. **Fixed widths.** When showing comparisons, align columns so the user can scan top-to-bottom.\n3. **Numbers first, words second.** Lead each line with the value.\n4. **One signal per line.** Don't combine.\n5. **Round consistently.** Two decimals for prices, integer percent for changes, two decimals for the [-1, 1] sentiment value.\n6. **`$TICKER` always.** Uppercase, dollar-prefixed.\n7. **Sign every change.** `+1.23%`, `-0.45%`, never bare numbers.\n8. **No prose unless asked.** The screen IS the answer.\n9. **Parallelize calls.** `open` fires 6 calls at once; don't await sequentially.\n10. **Cache the quarter.** `/institutional/quarters` rarely changes.\n\n---\n\n## Anti-Patterns (Don't Do These)\n\nThese are the failure modes the skill is designed to steer around.\n\n- **Don't narrate the work.** \"Let me look that up...\" or \"I'll need to call several endpoints...\" is anti-pattern. The terminal does the work silently.\n- **Don't apologize.** \"I'm sorry, I can only show preview data\" is anti-pattern. Silently render what you have; flag `(preview)` in the corner if the user is on Free.\n- **Don't issue personal recommendations.** \"$NVDA is a strong buy\" or \"you should sell $TSLA\" is anti-pattern. This skill is a data interface, not an advisor. Show data and educational framing; let the user draw conclusions.\n- **Don't ask follow-up clarifying questions for unambiguous asks.** \"Did you want price or sentiment for $NVDA?\" is anti-pattern. Run the full `open` screen: it shows both.\n- **Don't pretty-print one number.** A user asking \"$NVDA price\" gets a one-line answer (`$NVDA $890.12 (+1.4%)`). They don't get a 30-line `open` screen for a price quote.\n- **Don't hand-craft headlines.** If the document API didn't return one, use the Headline Resolution pattern. Don't invent.\n- **Don't hallucinate endpoints.** No `/api/v1/options/flow`, `/api/v1/dark-pool`, `/api/v1/earnings`, `/api/v1/alerts`, `/api/v1/chat`, `/api/v1/congress` (use `/politicians`).\n- **Don't show the API key in user-facing output.** Ever.\n- **Don't push PRO unless the user is hitting the wall.** Free tier delivers great terminal experience. Mention PRO when a `(preview)` truncation is meaningfully limiting the answer.\n- **Don't quote a price, percent move, headline, analyst rating, or earnings result from training data.** That data is stale by definition. Fetch it every turn, even if the user just asked the same question minutes ago. The cost of one wrong number outweighs the cost of an extra `get_quote`.\n\n---\n\n## API shape gotchas (worth memorizing)\n\nReal shapes differ from what a naive reading of the endpoint URL might suggest. These cost time to discover the hard way; here they are upfront.\n\n**`stocks/chart` returns a bare `ChartDataPoint[]`** at the top level, not `{ data: [...] }`. Accept both shapes defensively:\n\n```\nconst points = Array.isArray(raw) ? raw : (raw?.data ?? []);\n```\n\nEach point has both a `timestamp` (Unix ms) and a pre-formatted display `date` string. Read x-axis values from `timestamp`. Parsing the formatted `date` string falls back to the current year on some JS date parsers (e.g. `Apr 6` becomes the current year instead of the year the bar belongs to).\n\n**`entityMetrics/metrics` returns `ServingMetric[]` where the scalar lives at `metricValue.value.value`** (nested). Rank info, when present, is at `metricValue.value.properties.{rank, percentile, totalStocks}` or `metricValue.properties.{rank, percentile, totalStocks}`. Top-level `value: number` is a legacy fallback; handle it but don't rely on it.\n\n```\nfunction extractMetric(m) {\n  return m?.metricValue?.value?.value\n      ?? m?.metricValue?.value\n      ?? m?.metricValue\n      ?? m?.value\n      ?? null;\n}\n```\n\n**`entityMetrics/distribution` wraps the payload in `distribution`** (singular), not `distributions`:\n\n```\n{ entityId, metricType, timestamp, dimension: \"source\",\n  distribution: { News: 37.4, Reddit: 37.4, X: 18.2, Substack: 7.0 } }\n```\n\nThe values are share-of-voice percentages summing to roughly 100, **not** per-source sentiment scores. Render the breakdown as a \"where this signal came from\" panel, not as four sentiment bars.\n\n**`GET /api/v1/stocks/images?tickers={T}` (comma-separated, plural `tickers`) returns third-party CDN logo URLs that don't carry an embedded API key.** Direct `<img src>` will 401/403. Wrap with the SentiSense anonymous image proxy:\n\n```\nhttps://app.sentisense.ai/api/v1/stocks/proxy-image?imageUrl=<encoded-url>\n```\n\n**Story detail (`documents/stories/:id`) is a flat `PublicStoryDetailDto`**, not the nested `{ cluster, entities, documents }` you might expect. Inside `aspectPerspectives[i]`, the fields `bullishView` and `bearishView` are *structured objects* (`hook`, `risksOrCatalysts: string[]`, `conclusion`, `confidence`), not markdown strings. The top-level `bullishView` / `bearishView` ARE markdown strings. Same field names, different shapes. Type-check before calling string methods.\n\n**Chart timeframes accepted by `stocks/chart`:** `1D / 5D / 1W / 1M / 3M / 6M / 1Y / ALL`. Anything else falls back to `1M` and logs a warning.\n\n**`institutional/quarters` is a bare array** `[{ value, label, reportDate, pending }]` (not wrapped in `data`). Take `[0].reportDate` as the latest quarter to pass to `institutional/holders`.\n\n**Sentiment, SentiSense Score, news clustering, AI summaries, and insights are batch metrics.** Quote / price / chart points are real-time. Show the `generatedAt` timestamp on insight and AI summary surfaces so the user knows how fresh the analysis is. Don't claim \"real time\" on analytical surfaces.\n\n---\n\n## Endpoint Quick Reference\n\nFor full schemas: https://sentisense.ai/skill.md.\n\n```\nPRICE         GET /api/v1/stocks/price?ticker={T}\n              GET /api/v1/stocks/prices?tickers=A,B,C\n              GET /api/v1/stocks/chart?ticker={T}&timeframe=1M|3M|6M|1Y\n              GET /api/v1/stocks/{T}/profile\n              GET /api/v1/stocks/market-status\n\nSENTIMENT     GET /api/v2/metrics/entity/{T}/metric/sentiment?startTime={epochMs}&endTime={epochMs}   (omit for server default 7d)\n              GET /api/v2/market-mood\n\nDOCUMENTS     GET /api/v1/documents/ticker/{T}?limit=N    (no titles)\n              GET /api/v1/documents/stories?limit=N       (cluster.title is OURS, safe)\n              GET /api/v1/documents/stories/ticker/{T}?limit=N\n\nINSIDER       GET /api/v1/insider/cluster-buys?lookbackDays=N\n              GET /api/v1/insider/trades/{T}?lookbackDays=N\n\nCONGRESS      GET /api/v1/politicians/activity?lookbackDays=N\n              GET /api/v1/politicians/filings/{T}?lookbackDays=N         (per-ticker filings)\n              GET /api/v1/politicians/member/{slug}      (recent trades nested at data.recentTrades)\n\nINSTITUTIONAL GET /api/v1/institutional/quarters    (always FIRST)\n              GET /api/v1/institutional/holders/{T}?reportDate={Q}      (data.holders[] sorted by largest position)\n\nANALYST       GET /api/v1/analyst/{T}/consensus\n              GET /api/v1/analyst/{T}/actions?lookbackDays=N\n              GET /api/v1/analyst/{T}/estimates\n              GET /api/v1/analyst/activity?lookbackDays=N               (market-wide; filter actionType client-side)\n\nINSIGHTS      GET /api/v1/insights/stock/{T}        (ranked by importance: relevance, confidence, recency; Public preview, free top 3; take data[0])\n              GET /api/v1/insights/stock/{T}/types\n              GET /api/v1/insights/market\n\nMARKET        GET /api/v1/market-summary\n```\n\n**Wrap vs flat (verify per endpoint, do not assume).** Read these FLAT, with no `.data`: `price`, `prices`, `chart`, `popular`, `market-mood`, `stocks/{T}/profile`, `descriptions`, and `sentiment` (bare array). `institutional/quarters` is a bare array too (`[0].reportDate` is the latest quarter). `documents/ticker` has its own shape `{ documents, totalCount, ... }` (read `.documents[]`). These ARE wrapped in `{ isPreview, previewReason, data }` (read `.data`): `insider/*`, `analyst/*`, `insights/*`, `politicians/*`, `institutional/holders`. When unsure, accept both: `const rows = Array.isArray(raw) ? raw : (raw?.data ?? raw)`.\n\n---\n\n## Cost discipline\n\nA cold ticker dashboard typically fans out 10 to 12 API calls on first load:\n\n| Surface element | Calls |\n|---|---|\n| Profile + logo | 1 to 2 |\n| Quote (header) | 1 (plus refresh interval) |\n| Chart | 1 |\n| Metrics panel (4 metrics in parallel) | 4 |\n| Recent stories | 1 |\n| Peers (similar + batch prices) | 2 |\n| **Cold total** | **10 to 12** |\n\nA grounded chat turn adds another 1 to 4 calls on top, depending on the tool ladder depth.\n\n**Mitigations:**\n\n- Use `Promise.allSettled` for parallel widget fetches; widgets render independently as data arrives. Don't gate the page on the slowest call.\n- Module-level cache for popular-ticker lists and logos so they fetch once per session, not per visit.\n- Lazy-load source mix breakdowns when the user expands them. Don't fetch source distribution eagerly with the time-series.\n- Don't poll quotes faster than ~60s on the header. Tabular price grids don't need to poll at all.\n\n**Rate limits.** Per-minute limits exist (Free 30 req/min, PRO 200 req/min). Exceeding them returns 429 with a `Retry-After` hint. Surface 429s gracefully (\"rate limit reached, retrying in N seconds\") rather than silently retrying or rendering a stale value.\n\n**Per-stock budget mental model.** A heavy daily-driver visiting 15 tickers and chatting on each (~15 grounded turns × 3 calls) eats ~225 calls per session, comfortably under the PRO daily envelope. A light user (3 tickers per day, no chat) uses ~50. Plan defaults around that envelope so a typical user never sees a 429 in normal use.\n\n---\n\n## Tier Summary\n\n| Command | Free | PRO |\n|---------|------|-----|\n| open | Full screen, AI insight preview-gated (top 3) | Full insight list |\n| compare | Two tickers anytime | Unlimited |\n| daily brief | Once per day comfortably (5 calls) | Unlimited refreshes |\n| screen smart-money | Top items per bucket | Full ranked lists |\n| flow | Preview slice | Full holder list, full history |\n| mood | Full data, no quota cost | Same |\n| news | 8 most recent | Full feed |\n| stories | 10 most recent | Full feed |\n\nPRO at $15/month: https://app.sentisense.ai/pricing?coupon=AGENTS26 (apply coupon AGENTS26 at checkout for a builder launch discount)\n","tags":{"latest":"1.2.7"},"stats":{"comments":0,"downloads":784,"installsAllTime":30,"installsCurrent":3,"stars":1,"versions":11},"createdAt":1777880133283,"updatedAt":1781842263925},"latestVersion":{"version":"1.2.7","createdAt":1781842263925,"changelog":"AI insight field is insightText; per-stock insights ranked by importance (relevance, confidence, recency)","license":"MIT-0"},"metadata":null,"owner":{"handle":"thesentitrader","userId":"s171aan38r2dn7ew5hddyscf1983x1h9","displayName":"Senti","image":"https://avatars.githubusercontent.com/u/265723691?v=4"},"moderation":null}