askai
v2.0.0MIT@raptrx/askai
Send anything on your page to ChatGPT, Claude, Perplexity or Grok — with deep links that are actually verified.
npm i @raptrx/askaiTry it
This is the real control, not a picture of one. Copy takes the whole snippet; the caret opens the destinations, each labelled with whether it runs the prompt or just fills the composer.
function runway(liquid, burn) {
if (burn <= 0) return Infinity;
return liquid / burn;
}Every code block further down this page has the same control on it.
v2.0 is a breaking release. Six of the ten destinations in 1.x were sending parameters that no longer worked. If you are upgrading, read what changed in 2.0 first.
The problem
Every AI vendor accepts a prompt in a URL. None of them document it, none of them promise it will keep working, and they change it without notice.
So most published “Ask AI” buttons are quietly broken. The tab still opens — it just opens empty, and your user blames your product. Six of the ten destinations this package itself shipped in 1.x were wrong:
- Gemini — Takes prompts over an HTTP header the browser sets, not a URL parameter. A link cannot set headers, so ?q= was always inert.
- Microsoft Copilot — Prefill regressed in late 2025 and was never restored.
- ChatGPT — ?q= only reaches the composer via a fallback redirect. ?prompt= is stable.
- Google AI Studio — Uses ?prompt=, not ?q=.
- Perplexity — Needs /search/new, not /search.
- Claude — Real cap is ~14,000 characters, not the 100,000 claimed.
Quick start
1. Import the component and the stylesheet
import { AskAI } from '@raptrx/askai/react'; import '@raptrx/askai/styles.css';The stylesheet import goes anywhere in your app, once. Skip it and the control renders unstyled.
2. Drop it next to your content
<AskAI goal="Explain this function" content={code} />contentis the point — pass the actual code, error, article or record the user is looking at, not a static string.3. Check what it produces
npx @raptrx/askai listPrints every destination with its parameter, cap, tier, and whether it runs the prompt or just fills the composer.
Why copy is the primary action
The control is a split button: Copy prompt on the left, destinations behind the caret. That ordering is deliberate.
Deep links are bounded by URL length — Claude cuts at ~14,000 characters, Grok at ~7,500, Cursor at ~5,000 of usable prose. Sending an AI half a function and letting it answer confidently about the wrong code is worse than sending nothing. Copying has no ceiling, so the primary action always works.
When content has to be shortened to fit a link, the result says so instead of hiding it:
const result = buildPrompt('Review this', longFile, 'claude');
result.truncated; // true
result.droppedChars; // 36114
result.autoSubmit; // false — Claude fills the composer, user presses EnterNever write copy that promises one-click. Most destinations fill the composer and wait for Enter. Every documented change in this space has removed auto-submit, never prefill — a link on someone else's page must not make a logged-in assistant execute a prompt. Use result.autoSubmit to label honestly; the built-in menu already does.
Four ways to use it
AskAI
The split button. Copy plus a menu of destinations, with full keyboard support.
AskAILink
One destination as a plain anchor. No state, no client boundary — works in a server component.
useAskAI
The behaviour with no markup. Bring your own design system.
buildPrompt
No React at all. Returns the URL plus truncation and submit metadata.
AskAILink renders a plain <a>. Because it is a real link, middle-click, Cmd-click, “open in new tab” and “copy link address” all behave — none of which work when a button calls window.open.
<AskAILink service="claude" goal="Review this" content={code}>
Ask Claude
</AskAILink>Destinations
Eighteen verified, two deprecated. Run npx @raptrx/askai list for the live table.
| Destination | Parameter | Cap | On arrival |
|---|---|---|---|
| ChatGPT | prompt | 16,000 | fills |
| Claude | q | 14,000documented | fills |
| Perplexity | q | 12,000 | runs |
| Grok | q | 7,500measured | runs |
| Kagi Assistant | q | 8,000measured | runs |
| DeepSeek | q | 12,000 | fills |
| Le Chat | q | 10,000 | fills |
| T3 Chat | q | 12,000 | runs |
| HuggingChat | q | 8,000measured | runs |
| Duck.ai | q | 12,000measured | runs |
| Z.ai | q | 12,000 | runs |
| Kimi | prefill_prompt | 12,000 | fills |
| Qwen Chat | text | 12,000 | fills |
| Cursor | text | 5,000 | fills |
| Google AI Studio | prompt | 12,000 | fills |
| GitHub Copilot | prompt | 12,000 | fills |
| v0 | q | 12,000 | runs |
| Scira | q | 12,000 | runs |
Caps are labelled by provenance. documented means the vendor publishes it, measured means we observed it, and an unlabelled figure is a conservative floor chosen because no evidence exists. The registry never presents a guess as a fact.
Gemini and Microsoft Copilot are deprecated and will throw. Gemini accepts prompts only over an HTTP header the browser sets from its own address bar — a hyperlink cannot set headers, so ?q= opens an empty chat. Microsoft Copilot's prefill regressed in late 2025 and was never restored. Both are kept in the registry so you get a clear error instead of a silently empty tab. For a Google model, use aistudio.
Matching your design system
The button should look like your product, not like this library. Everything is driven by custom properties, so you can retheme without overriding a single rule:
.askai {
--askai-bg: var(--my-surface);
--askai-fg: var(--my-text);
--askai-border: var(--my-border);
--askai-focus: var(--my-ring);
--askai-radius: 4px;
--askai-duration: 100ms;
}Past that there are three more levels: per-part classNames (merged after the built-ins, so yours win), fully unstyled (no askai-* class is emitted and the stylesheet becomes unnecessary — the ARIA and keyboard behaviour stay), and useAskAI when the component can't bend far enough:
const ai = useAskAI({ goal: 'Explain this', content: code });
<div {...ai.getRootProps()}>
<MyButton {...ai.getCopyButtonProps()}>
{ai.copied ? 'Copied' : 'Copy'}
</MyButton>
<MyButton {...ai.getTriggerProps()}>▾</MyButton>
{ai.isOpen && (
<MyMenu {...ai.getMenuProps()}>
{ai.destinations.map((d, i) => (
<MyMenuItem {...ai.getItemProps(i)}>
{d.name}
<small>{d.autoSubmit ? 'Runs' : 'Ready'}</small>
</MyMenuItem>
))}
</MyMenu>
)}
</div>Bring your own AI
Most companies with an assistant have their own. That's a first-class case:
import { createRegistry } from '@raptrx/askai';
const registry = createRegistry();
registry.add('acme', {
name: 'Acme AI',
url: 'https://ai.acme.internal/chat',
param: 'prompt',
});Use createRegistry() rather than the global addService() anywhere requests share a process. The global registry is process-wide, so one tenant's destination would leak into another's request.
Logos
Neutral glyphs by default. Brand marks are one import away:
import { logos } from '@raptrx/askai/logos';
<AskAI icons={logos} goal="Explain this" content={code} />Seven vendors ship their real mark — OpenAI, Claude, Grok, Perplexity, Mistral, Qwen and Z.ai — reproduced exactly as supplied, in their own colours. The rest use a monogram in their brand colour, because an approximated logo is worse than none: visibly wrong, and an altered mark is what vendors most clearly prohibit.
Logos sit behind a subpath rather than the package root on purpose. Using a third party's trademark is your product's decision, not a default you inherit.
Own the code instead
Prefer shadcn-style generated source?
npx @raptrx/askai initGenerates core.ts, AskAiButton.tsx and AskAiLink.tsx containing only the destinations you pick, with verified parameters baked in. The generated output is compiled under strict TypeScript by the repo's test suite on every commit.
What changed in 2.0
Destinations
- gemini and copilot now throw instead of opening an empty chat.
- chatgpt and aistudio moved to ?prompt=.
- perplexity moved to /search/new.
- claude cap corrected to 14,000.
- Ten destinations added; every entry now carries a tier and a verification date.
API
- buildPrompt() returns truncation and auto-submit metadata. Truncation is never silent.
- createRegistry() replaces global mutation for server use.
- suggestService() removed — its heuristics were arbitrary.
- options.model removed; Claude and Gemini never supported it.
- The 1.x ServiceConfig shape is still accepted by addService().
React
- AiButton / AiButtonBar → AskAI / AskAILink.
- Navigation uses real anchors, not window.open.
- 'use client' now survives bundling — the 1.x React entry threw on first render under the Next.js App Router.
- Styles moved to @raptrx/askai/styles.css.
- Vendor logos moved to @raptrx/askai/logos.
For coding agents
Paste this into Cursor, Claude Code or any coding agent. It covers installation, which surface to use, the failure modes that produce a button that opens an empty chat, styling, and how to verify the integration actually works.
# askai — integration instructions for a coding agent
Add an "Ask AI" control to this project using @raptrx/askai. Follow these rules
exactly. Several are counter-intuitive, and getting them wrong produces a button
that opens an empty chat — which looks like the product is broken.
## 1. Install
npm i @raptrx/askai
Prefer the package: one dependency, and updates arrive when vendors change their
parameters. Only run `npx @raptrx/askai init` (shadcn-style codegen) if this
project vendors its UI or cannot take a dependency — it is interactive and needs
a TTY. If you cannot drive a TTY, install the package rather than hand-writing
the files.
## 2. Minimum correct integration
import { AskAI } from '@raptrx/askai/react';
import '@raptrx/askai/styles.css'; // once, anywhere — do not skip this
<AskAI goal="Explain this code" content={code} />
`content` is the entire point: pass the actual code, error, article or record
the user is looking at, never a static string. Objects are serialised to fenced
JSON automatically; `{ text, language }` forces a code fence.
## 3. Pick the right surface
<AskAI> user picks the destination — split button, copy + menu
<AskAILink> one fixed destination; a real <a>, so it works in a server
component and supports middle-click and Cmd-click
useAskAI() the same behaviour with your own markup
buildPrompt() no React at all; returns { url, truncated, droppedChars,
autoSubmit }
## 4. Rules that make or break the integration
- NEVER promise one-click. Label it "Ask AI" or "Open in ChatGPT" — never "Run
in ChatGPT" or "Get an answer". Most destinations only fill the composer and
wait for the user to press Enter. `result.autoSubmit` tells you which ones
actually run; the built-in menu already labels this correctly.
- NEVER add `gemini` or Microsoft `copilot`. Gemini has never supported URL
prefill — it takes the prompt from an HTTP header the browser sets from its
own address bar, and a hyperlink cannot set headers. Microsoft Copilot's
prefill was removed in 2025. Both are kept in the registry only so they throw
a clear error instead of opening an empty tab. For a Google model use
`aistudio`. Note GitHub Copilot is a different product and IS supported.
- NEVER hand-write the URLs. `chatgpt.com/?q=` looks correct and is the
fragile path; `?prompt=` is the stable one. The registry exists precisely so
nobody has to remember this.
- Destinations cap between roughly 5,000 and 16,000 characters. Do not pass huge
content and assume it arrives. For anything long, let the user copy instead —
that is what the primary button is for. Check `buildPrompt(...).truncated`
if you need to know programmatically.
- Logos are opt-in, not default:
import { logos } from '@raptrx/askai/logos';
<AskAI icons={logos} ... />
Never hand-draw a mark to fill a gap. Destinations without a real logo use a
monogram on purpose; an approximated mark is visibly wrong and is the thing
vendors most clearly prohibit.
## 5. Service ids
Confirmed ids: chatgpt, claude, perplexity, grok, deepseek, mistral (Le Chat),
kagi (Kagi Assistant), t3chat, aistudio (Google AI Studio).
Do not invent ids. Run `npx @raptrx/askai list` for the authoritative set —
it prints every destination with its parameter, cap, tier, and whether it runs
the prompt or only fills the composer.
## 6. Styling
Retheme with CSS custom properties, no rule overrides needed:
.askai {
--askai-bg: var(--my-surface);
--askai-fg: var(--my-text);
--askai-border: var(--my-border);
--askai-focus: var(--my-ring);
--askai-radius: 4px;
--askai-duration: 100ms;
}
Past that: per-part `classNames` (merged after the built-ins, so yours win),
`unstyled` (no askai-* class is emitted and the stylesheet becomes
unnecessary — the ARIA and keyboard behaviour stay), or `useAskAI` to render
it yourself.
## 7. Server and multi-tenant
Use `createRegistry()` rather than the global `addService()` anywhere
requests share a process. The global registry is process-wide, so one tenant's
destination would leak into another's request.
import { createRegistry } from '@raptrx/askai';
const registry = createRegistry();
registry.add('acme', {
name: 'Acme AI',
url: 'https://ai.acme.internal/chat',
param: 'prompt',
});
## 8. Verify before you call it done
npx @raptrx/askai list
Then click the control and confirm the prompt actually lands in the
destination's composer. If it opens empty, the parameter changed — open an
issue at https://github.com/aliarain/askai rather than patching around it, so
the fix reaches everyone.
Docs: https://docs.aliarain.com/askai
Source: https://github.com/aliarain/askaiThe repository also ships an AGENTS.md (opens in a new tab) you can point an agent at directly, and every docs page is available to LLMs as raw markdown.
Tech
- TypeScript
- React
- Vitest