macotron.ai
Send prompts to Anthropic Claude, OpenAI, Google Gemini, or the on-device Apple Intelligence model, and get the reply as a string or as a stream of chunks.
const opts = macotron.plugin({
title: "Summarize",
options: {
apiKey: { type: "password", label: "Anthropic API key", required: true },
},
});
const ai = macotron.ai.claude({ apiKey: opts.apiKey, model: "claude-sonnet-5-5" });
const summary = await ai.chat(macotron.clipboard.text(), {
system: "Summarize the text in one sentence.",
});
macotron.notify.toast(summary);
Providers
Each factory returns a client object with chat() and stream(). Creating a client does no network work. The key and model you pass are stored on the client and used for every request it makes.
| Factory | Service | Default model |
|---|---|---|
claude(), anthropic() | Anthropic Messages API | claude-opus-5-5 |
openai() | OpenAI Chat Completions API | gpt-4o |
gemini() | Google Gemini API | gemini-2.5-flash |
local() | Apple Foundation Models, on this Mac | The system language model |
claude(opts?: { model?: string; apiKey?: string }): AIClient
A client for api.anthropic.com. model is any Anthropic model id. Omit it to use claude-opus-5-5.
anthropic(opts?: { model?: string; apiKey?: string }): AIClient
The same function as claude(), under another name.
openai(opts?: { model?: string; apiKey?: string }): AIClient
A client for api.openai.com. Omit model to use gpt-4o.
gemini(opts?: { model?: string; apiKey?: string }): AIClient
A client for generativelanguage.googleapis.com. Omit model to use gemini-2.5-flash.
local(): AIClient
A client for the on-device Apple Intelligence model. It takes no key and no model, and nothing leaves the Mac.
The local model needs macOS 26, a Mac that can run Apple Intelligence, and Apple Intelligence turned on in System Settings. Otherwise the request rejects with the reason, for example Provider not available: Turn on Apple Intelligence in System Settings. or …The on-device model is still downloading.
API keys
Macotron has no global AI key setting. A cloud client uses only the apiKey you pass to its factory. Without one, every request rejects with API key not provided. Set it in your config.
Keep keys out of plugin source. Declare a password option in macotron.plugin(): the user enters the key in Settings, Macotron stores it in the Keychain, and the option value you get back is the key itself. macotron.keychain also works, and keeps each plugin's keys apart.
The client
The first argument to both methods is a prompt string, or an array of { role, content } messages for a conversation. role is "user" or "assistant". Macotron does not keep chat history: pass the whole conversation on every call, and save it yourself in localStorage if you need it later.
An empty array, an empty content, or any other role throws a TypeError right away, before any request goes out.
| Option | Default | Meaning |
|---|---|---|
system | none | The system prompt. |
model | The client's model | Overrides the model for this one request. |
maxTokens | 4096 | The longest reply, in tokens. |
temperature | provider default | Sampling temperature. Leave it out for current Claude models, which reject it. |
onChunk | none | stream() only. Called with each new piece of text. |
The local model reads only system. It ignores model, maxTokens, and temperature.
chat(promptOrMessages: string | AIChatMessage[], opts?): Promise<string>
Sends the request and resolves with the full reply text. Cloud requests time out after 120 seconds.
The promise rejects with a message when the request fails: HTTP 401: … with the provider's error body for a refused request, Network error: … when the network is down, or the key and availability errors above.
stream(promptOrMessages: string | AIChatMessage[], opts?): Promise<string>
Calls onChunk with each piece of text as it arrives, then resolves with the full reply. Each chunk is only the new text, so append it to what you already have. Errors reject the same way as chat().
const ai = macotron.ai.local();
let text = "";
const full = await ai.stream("Write a haiku about Mondays.", {
onChunk: (chunk) => {
text += chunk;
console.log(text);
},
});
macotron.notify.show("Haiku", full);
When the plugin reloads, any request still in flight is dropped and onChunk is not called again.
Text you send to a model can carry instructions of its own. When a prompt includes screen text, clipboard contents, or file contents, wrap that data in clear delimiters and tell the model to ignore instructions inside it.
See also
- macotron.keychain to store API keys.
- macotron.http for other web APIs.
- The
ai-chat.jsplugin, a streaming chat panel that switches between Claude, Gemini, and the local model.