Let me show you how to get Siri to work with AI in 15 minutes on any iPhone for free without Apple Intelligence.
The tech world is buzzing about Apple Intelligence—Apple’s new, AI-powered brain that makes Siri actually sound smart for the first time in a decade.
Siri can finally summarize, rewrite, and reason, but this feature is only available on the latest iPhones.
As of October 2025, Apple Intelligence is live, but it’s limited to:
- iPhone 15 Pro / Pro Max,
- iPhone 16 series (and newer), and
- iPads and Macs with Apple Silicon (M1 or newer) running iOS 18.1 or later
If you’re holding anything older—say an iPhone 13, 14, or even the non-Pro 15—you’re officially left out 😞
But here’s the good news: you don’t need Apple Intelligence to get an intelligent Siri.
Using just the Shortcuts app, a free Supabase account, and an AI model of your choice (Gemini or ChatGPT or both), you can build your own version of Apple Intelligence right now, no new hardware required.
I did it in about 15 minutes, and it changed how I use my iPhone. It was also a Shortcuts awakening because, if you’re like me and have been sleeping on Shortcuts, time to wake up.
Here’s exactly how you can upgrade Siri’s brain.
Note 🧠
It’s safe to classify this as moderate easy level. It’s not plug-and-play level of easy but you don’t need to be a developer or iPhone pro to follow along. I provide a link to the shortcuts for quick install and the code you need to paste in Supabase.
Why I Wanted My Own AI Siri
Siri’s been “okay” for years. Great at setting alarms, bad at actual conversation, and extremely unhelpful in providing an answer that goes beyond a link.
Apple Intelligence fixes that—for some people. But for the rest of us, there’s still that same robotic, surface-level Siri who can’t remember what you just said.
I wanted more.
I wanted a Siri that could think, remember, and respond naturally. I wanted Siri to give an answer or respond to my request without getting a list of links that I would then need to visit to consume. I wanted something like Gemini or ChatGPT built right into my phone.
And since Apple’s AI isn’t coming to my iPhone 13 Pro Max anytime soon, I built it myself.
The result? A custom voice assistant that:
- Responds to complex and varied requests conversationally
- Uses whichever AI model I prefer
- Works entirely through Shortcuts—no jailbreaks, no coding background required
- Is largely free (using API keys on free tiers)
Note: Treat this as an MVP since it can be updated, upgraded, and scaled per desire.
What You’ll Need (Everything is Free or Built-In)
Before we start, here’s your short checklist:
- iPhone with iOS 16 or later (Shortcuts app installed)
- Free Supabase account—this will act as our lightweight backend
- API key from either:
- Gemini API, or
- OpenAI ChatGPT API
- About 15 minutes, some curiosity, and a little grit for hopping onto new platforms (i.e., Supabase) and fetching tech-y things (e.g., API keys)
The rest of this MVP demo will use a combo of the Gemini model and OpenAI with a hybrid serverless function that lets the user set the AI model provider of choice.
Quick Overview of How It Works
This is a real quick, oversimplified overview of how we’ll enable AI in Siri.
- The shortcut on the iPhone will
POSTa small JSON{ "prompt": "..." }to a secure Supabase Edge Function URL. - The Edge Function will forward the prompt to Google’s Gemini API or ChatGPT’s API, etc., depending on the model of choice, using your API key, and return a short reply JSON
{ "status":"ok", "response":"..." }. - The shortcut will take the
responsefield and speak it (and store a reference to it temporarily in a local file).
I promise it’ll all make more sense in a bit. Keep reading.
Step 1: Get the Gemini and/or OpenAI API key
This is a one-time action done via the Web UI for either model.
To get the Gemini API key, for instance, you’d:
- Open Google AI Studio (aka MakerSuite / AI Studio) in a browser
- Click Get API key or go to the “API keys” area
- Create an API key and copy it (You’ll paste it into Supabase secrets)
Again, if you use a different model, do the same thing but follow the necessary steps in accessing API keys for your model of choice.
Note that the steps to getting an API key vary as websites change. Follow the most recent steps of getting the necessary API key(s).
Step 2: Create Your Supabase Project
Supabase is a “serverless backend,” meaning you can run tiny bits of code online without worrying about servers or deployments.
I used Supabase to build a custom AI chatbot on my dev portfolio. I talk a little more about Supabase and its Deno environment in that post. I’ll not go into detail about navigating Supabase, as that’s beyond the scope of this post and a task ChatGPT can easily help you with 😉
Tip: You don’t need to use Supabase. If you have a service of preference that provides a way to host a small serverless function, feel free to use it and skip this part.
Here’s the process:
1. Set Up Supabase
- Go to Supabase.com and create an account.
- Click New Project → Create
- Give it a name (e.g.,
ai-siri) and choose any region
2. Add Your API Key to Supabase
Follow the following steps to add your API keys to the Supabase project. Once added, the keys will be accessible in the function(s).
- In Supabase, open Project Settings → API → Environment Variables
- Add one of these keys:
GEMINI_API_KEY(for Google Gemini)OPENAI_API_KEY(for ChatGPT)
- Generate a random long string (e.g., 32+ chars) as
AI_SHORTCUT_SECRETto prevent strangers from calling your function (optional, you can skip this, but good security habit)
The serverless function (in the next step) will access these environment variables via Deno.env.get(...).
3. Add Your Function Code
Supabase “Edge Functions” let you run little cloud scripts, which is exactly what we need to talk to Gemini or ChatGPT. These are always “listening” to requests (though free-tier Supabase requires some usage, else they’ll temporarily shut down the function and you’ll need to manually re-enable the endpoint).
In the dashboard under Edge Functions → Your Function, select the Code tab and paste the entire code below as index.ts (or whatever the editor suggests).
Note: I break down some of the code for those curious. Otherwise, copy all code into a single file.
Let’s start with some imports and variables:
import { serve } from "<https://deno.land/[email protected]/http/server.ts>";
const GEMINI_KEY = Deno.env.get("GEMINI_API_KEY") ?? "";
const SECRET = Deno.env.get("AI_SHORTCUT_SECRET") ?? "";
const MODEL = Deno.env.get("GEMINI_MODEL") ?? "gemini-2.5-flash";
const CORS_HEADERS = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, x-ai-shortcut-secret, Authorization"
};
What follows are two helper functions that help de-bloat the main execution function that will serve the main code.
The first helper function helps extract headers (these will come from the POST request from our Shortcuts):
function extractSecretFromHeaders(headers) {
const xh = headers.get("x-ai-shortcut-secret");
if (xh && xh.trim()) return xh.trim();
const raw = headers.get("authorization") || "";
if (!raw) return null;
const m = raw.match(/^\\s*Bearer\\s+(.+)$/i);
return m ? m[1].trim() : raw.trim();
}
The second helper function helps assemble the model’s response by joining parts into a single string with some fallbacks as a cushion:
function assembleModelText(data) {
try {
// handle candidates -> content -> parts -> text
const cand = data?.candidates?.[0];
if (cand) {
const content = cand.content ?? cand.message?.content ?? cand;
if (Array.isArray(content?.parts)) {
return content.parts.map((p)=>p.text ?? "").join("");
}
if (typeof content === "string") return content;
}
// older / alternate field
if (typeof data?.outputText === "string") return data.outputText;
// fallback: some responses hold text in different places
if (typeof data?.response === "string") return data.response;
// last resort: stringify but limit size
return JSON.stringify(data).slice(0, 4000);
} catch (e) {
return "Error parsing model response.";
}
}
Next up is the main serve function. I’ll break this down as it would look if you were to only use a single model. I’m using Gemini for this demo.
serve(async (req)=>{
// Preflight
if (req.method === "OPTIONS") {
return new Response(null, {
status: 204,
headers: CORS_HEADERS
});
}
if (req.method !== "POST") {
return new Response(JSON.stringify({
error: "Only POST allowed"
}), {
status: 405,
headers: {
...CORS_HEADERS,
"Content-Type": "application/json"
}
});
}
// Extract client secret from header (x-ai-shortcut-secret or Authorization: Bearer ...)
const clientSecret = extractSecretFromHeaders(req.headers) ?? "";
if (!clientSecret || clientSecret !== SECRET) {
// Do not leak secrets in logs; only surface a generic message
console.warn("Unauthorized request to Edge Function (missing or invalid secret).");
return new Response(JSON.stringify({
error: "Unauthorized"
}), {
status: 401,
headers: {
...CORS_HEADERS,
"Content-Type": "application/json"
}
});
}
// Parse JSON body
let body;
try {
body = await req.json();
} catch (e) {
return new Response(JSON.stringify({
error: "Invalid JSON"
}), {
status: 400,
headers: {
...CORS_HEADERS,
"Content-Type": "application/json"
}
});
}
const prompt = (body?.prompt || body?.input || "").toString().trim();
if (!prompt) {
return new Response(JSON.stringify({
error: "Missing prompt"
}), {
status: 400,
headers: {
...CORS_HEADERS,
"Content-Type": "application/json"
}
});
}
if (!GEMINI_KEY) {
console.error("GEMINI_API_KEY missing from environment.");
return new Response(JSON.stringify({
error: "Server misconfigured"
}), {
status: 500,
headers: {
...CORS_HEADERS,
"Content-Type": "application/json"
}
});
}
// Build Gemini request
const geminiReq = {
contents: [
{
parts: [
{
text: `You are a concise helpful assistant. Answer briefly.\\n\\nUser: ${prompt}\\nAssistant:`
}
]
}
],
generationConfig: {
temperature: 1,
topK: 64,
topP: 0.95,
maxOutputTokens: 8192,
responseMimeType: "text/plain"
}
};
try {
const r = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${MODEL}:generateContent`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-goog-api-key": GEMINI_KEY
},
body: JSON.stringify(geminiReq)
});
if (!r.ok) {
const txt = await r.text();
console.error("Gemini upstream error", r.status, txt);
return new Response(JSON.stringify({
error: "Upstream error"
}), {
status: 502,
headers: {
...CORS_HEADERS,
"Content-Type": "application/json"
}
});
}
const data = await r.json();
const answer = assembleModelText(data).trim();
return new Response(JSON.stringify({
status: "ok",
response: answer
}), {
status: 200,
headers: {
...CORS_HEADERS,
"Content-Type": "application/json"
}
});
} catch (err) {
console.error("Server error", err);
return new Response(JSON.stringify({
error: "Server error"
}), {
status: 500,
headers: {
...CORS_HEADERS,
"Content-Type": "application/json"
}
});
}
});
Breaking down the code, we try something like retrieving a request and extracting the prompt to forward to Gemini and catch any potential errors. That’s really it.
Hybrid Version with ChatGPT + Gemini
Now, for a more comprehensive and utilitarian script, I will take the code from above and modify it a little so it supports both Gemini and ChatGPT. This will be a hybrid serverless function (i.e., one endpoint) that defaults to Gemini but can also use ChatGPT without any additional functions or endpoints.
Tip: If you don’t care about the code and just want something to paste, choose the following code (all pieces go into a single file) as it’s complete and more versatile.
Add a default provider flag:
// index.ts - Supabase Edge Function (production-ready)
import { serve } from "<https://deno.land/[email protected]/http/server.ts>";
const DEFAULT_PROVIDER = "gemini"; // defaults to GeminiPull or set variables:
// GEMINI
const GEMINI_KEY = Deno.env.get("GEMINI_API_KEY") ?? "";
const GEMINI_MODEL = Deno.env.get("GEMINI_MODEL") ?? "gemini-2.5-flash";
// OPENAI
const OPENAI_KEY = Deno.env.get("OPENAI_API_KEY") ?? "";
const OPENAI_MODEL = Deno.env.get("OPENAI_MODEL") ?? "gpt-4o-mini";
// SECRET (string)
const SECRET = Deno.env.get("AI_SHORTCUT_SECRET") ?? "";
// Headers
const CORS_HEADERS = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, x-ai-shortcut-secret, Authorization"
};
Define helper functions:
function extractSecretFromHeaders(headers) {
const xh = headers.get("x-ai-shortcut-secret");
if (xh && xh.trim()) return xh.trim();
const raw = headers.get("authorization") || "";
if (!raw) return null;
const m = raw.match(/^\\s*Bearer\\s+(.+)$/i);
return m ? m[1].trim() : raw.trim();
}
// Assemble Gemini response text robustly
function assembleGeminiText(data) {
try {
const cand = data?.candidates?.[0];
if (cand) {
const content = cand.content ?? cand.message?.content ?? cand;
if (Array.isArray(content?.parts)) {
return content.parts.map((p)=>p.text ?? "").join("");
}
if (typeof content === "string") return content;
}
if (typeof data?.outputText === "string") return data.outputText;
if (typeof data?.response === "string") return data.response;
return JSON.stringify(data).slice(0, 4000);
} catch (e) {
return "Error parsing model response.";
}
// Assemble OpenAI Chat Completions response text robustly
function assembleOpenAIText(data) {
try {
const choice = data?.choices?.[0];
if (choice?.message?.content && typeof choice.message.content === "string") {
return choice.message.content;
}
if (typeof choice?.text === "string") return choice.text;
if (typeof data?.output === "string") return data.output;
return JSON.stringify(data).slice(0, 4000);
} catch (e) {
return "Error parsing model response.";
}
}
Unchanged main serve function from the single-model Gemini example above:
serve(async (req)=>{
// Preflight
if (req.method === "OPTIONS") {
return new Response(null, {
status: 204,
headers: CORS_HEADERS
});
}
if (req.method !== "POST") {
return new Response(JSON.stringify({
error: "Only POST allowed"
}), {
status: 405,
headers: {
...CORS_HEADERS,
"Content-Type": "application/json"
}
});
}
// Extract client secret from header (x-ai-shortcut-secret or Authorization: Bearer ...)
const clientSecret = extractSecretFromHeaders(req.headers) ?? "";
if (!clientSecret || clientSecret !== SECRET) {
// Do not leak secrets in logs; only surface a generic message
console.warn("Unauthorized request to Edge Function (missing or invalid secret).");
return new Response(JSON.stringify({
error: "Unauthorized"
}), {
status: 401,
headers: {
...CORS_HEADERS,
"Content-Type": "application/json"
}
});
}
// Parse JSON body
let body;
try {
body = await req.json();
} catch (e) {
return new Response(JSON.stringify({
error: "Invalid JSON"
}), {
status: 400,
headers: {
...CORS_HEADERS,
"Content-Type": "application/json"
}
});
}
const prompt = (body?.prompt || body?.input || "").toString().trim();
if (!prompt) {
return new Response(JSON.stringify({
error: "Missing prompt"
}), {
status: 400,
headers: {
...CORS_HEADERS,
"Content-Type": "application/json"
}
});
}Continue in serve function by updating how to identify which model to use based on either:
body?.providerbased on the request coming through withprovider(in addition toprompt)DEFAULT_PROVIDERset on top of the file
// Determine provider: request body overrides the file-default
const requestedProvider = (body?.provider || DEFAULT_PROVIDER || "gemini").toString().toLowerCase();
const provider = requestedProvider === "openai" ? "openai" : "gemini";
try {
if (provider === "gemini") {
if (!GEMINI_KEY) {
console.error("GEMINI_API_KEY missing from environment.");
return new Response(JSON.stringify({
error: "Server misconfigured"
}), {
status: 500,
headers: {
...CORS_HEADERS,
"Content-Type": "application/json"
}
});
}
const geminiReq = {
contents: [
{
parts: [
{
text: `You are a concise helpful assistant. Answer briefly.\\n\\nUser: ${prompt}\\nAssistant:`
}
]
}
],
generationConfig: {
temperature: 0.2,
topK: 40,
topP: 0.95,
maxOutputTokens: 512,
responseMimeType: "text/plain"
}
};
const upstream = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_MODEL}:generateContent`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-goog-api-key": GEMINI_KEY
},
body: JSON.stringify(geminiReq)
});
if (!upstream.ok) {
const txt = await upstream.text();
console.error("Gemini upstream error", upstream.status, txt);
return new Response(JSON.stringify({
error: "Upstream error",
detail: txt
}), {
status: 502,
headers: {
...CORS_HEADERS,
"Content-Type": "application/json"
}
});
}
const data = await upstream.json();
const answer = assembleGeminiText(data).trim();
return new Response(JSON.stringify({
status: "ok",
provider: "gemini",
response: answer
}), {
status: 200,
headers: {
...CORS_HEADERS,
"Content-Type": "application/json"
}
});
} else {
// provider === openai
if (!OPENAI_KEY) {
console.error("OPENAI_API_KEY missing from environment.");
return new Response(JSON.stringify({
error: "Server misconfigured"
}), {
status: 500,
headers: {
...CORS_HEADERS,
"Content-Type": "application/json"
}
});
}
const openaiReq = {
model: OPENAI_MODEL,
messages: [
{
role: "system",
content: "You are a concise, helpful assistant. Keep replies short and actionable."
},
{
role: "user",
content: prompt
}
],
temperature: 0.2,
max_tokens: 512
};
const upstream = await fetch("<https://api.openai.com/v1/chat/completions>", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${OPENAI_KEY}`
},
body: JSON.stringify(openaiReq)
});
if (!upstream.ok) {
const txt = await upstream.text();
console.error("OpenAI upstream error", upstream.status, txt);
return new Response(JSON.stringify({
error: "Upstream error",
detail: txt
}), {
status: 502,
headers: {
...CORS_HEADERS,
"Content-Type": "application/json"
}
});
}
const data = await upstream.json();
const answer = assembleOpenAIText(data).trim();
return new Response(JSON.stringify({
status: "ok",
provider: "openai",
response: answer
}), {
status: 200,
headers: {
...CORS_HEADERS,
"Content-Type": "application/json"
}
});
}
} catch (err) {
console.error("Server error", err);
return new Response(JSON.stringify({
error: "Server error"
}), {
status: 500,
headers: {
...CORS_HEADERS,
"Content-Type": "application/json"
}
});
}
});Lastly, click Save and Deploy. Don’t forget to always deploy changes otherwise they’ll be lost!
Tip 👀
Developers working with Supabase ideally (and for sanity’s sake) will use their CLI to locally develop before pushing to the remote instance. This way, they can test and iterate over issues without affecting a live instance. The more you know 😉
Now, before jumping onto your iPhone, jot down the following in your notes app (so you can copy and paste if it syncs to iCloud).
In your Edge Functions → Your Function → Details tab:
- Copy the Endpoint URL like:
https://yourproject.supabase.co/functions/v1/ai-siri(this is a dummy URL; note that yours will vary) - Scroll to Invoke function section to copy:
Content-Type=application/jsonAuthorizationwithBearer <secret>
- Secret string key phrase (from environment variables)
We’ll need this information in Shortcuts when making our request to the Endpoint URL, so make sure that whether you copy or manually enter everything, it matches!
Step 3: Create the Shortcut on Your iPhone
We’ll create a two-part shortcut system that lets Siri talk back with the answer from an online AI (your Supabase endpoint).
Something to note is that Siri doesn’t natively speak custom external data directly. I learned this the hard way 😕
Apple locks external data down for privacy and security, which is probably a good thing, but it doesn’t help our case. Hence, we’ll use a hands-off workaround that still feels natural.
Why Two Shortcuts Are Needed
When you talk to Siri and ask it to run a shortcut, Siri holds onto the microphone and speaker for the whole interaction.
So if the shortcut tries to use Speak Text (one of the Shortcuts app actions, more of those below) while Siri is still “in control”, you won’t hear anything because it runs silently.
The solution is to:
- Let the first shortcut finish under Siri’s control (so Siri releases the mic/speaker)
- Then have that shortcut launch a second one in the background, which runs after Siri steps aside so it can speak freely
That’s why we’ll ultimately have:
- AI Speaker: actually reads aloud the response
- Ask MyAI: fetches the AI’s response
You can name the shortcuts whatever you like, as long as you know which does what.
What Each Shortcut Does
The Fetcher: Ask MyAI
This shortcut does the thinking part and saves the response to a temporary text file under Files (necessary for Siri session workaround).
Breaking down the process, this shortcut:
- Connects to the Supabase endpoint (i.e., your AI)
- Gets back an answer
- Saves that answer in a little text file on your phone (so there’s no pop-up asking to copy or paste after the first time—it just happens in the background)
- Waits for one second so Siri can stop talking
- Launches the second shortcut to handle the voice reading
Think of this shortcut as the messenger that goes online, grabs the info, writes it down, and hands it off.
The Reader: AI Speaker
This shortcut does the reading part by:
- Waiting a second, just in case Siri is still fading out
- Opens the local file that the first shortcut created
- Reads its text (the AI’s answer) out loud using the Speak Text action (same Siri voice but now speaking custom content)
- (Optional) Deletes the temporary file when done (so it stays clean)
This part is completely hands-off. Once the answer is saved, Siri speaks it automatically with no taps and no prompts.
Note: If you encounter some prompting on a first run, that’s normal. After giving permission, you will not be prompted for anything.
Why Use a Local File
I originally tried having Siri speak the transformed text response directly, but that falls into the session timeframe. The same can be said for using notifications—the response would display, but Siri won’t read it.
Then I tried using the clipboard to pass the data, but Siri prompts for permission every time, which breaks the hands-free flow. The same can be said for using Quick Look—you had to manually press “Done”, then Siri would start speaking.
Another option was iCloud, but it syncs slowly and can throw “file not found” errors.
Using a small file on the iPhone’s local storage avoids all of these issues:
- It saves instantly (no upload lag)
- It never asks permission after the first time
- It’s easy to overwrite or delete quietly
- It provides a reference so you can read the result of the last query
Tip 💡
For my ambitious friends out there, you can take this MVP to the next level by creating a more robust conversational flow where you can build onto a previous questions on another ask!
Constructing the Shortcuts on iPhone
Moving on, let’s connect Siri to the Supabase function created above.
Note: All Shortcuts action names and references are current as of writing of this post. Apple can change or update them at any time.
Ask MyAI (prompts Supabase)
- Open the Shortcuts app.
- Tap the + button → Add Action.
- Name it Ask MyAI (or whatever you like)
Note ‼️
I suggest you give this prompting shortcut a unique enough name so it doesn’t conflict with Siri’s native key phrases.
Then add these actions in order:
- Dictate Text
- Action: Dictate Text. Keep defaults.
- Lets you ask your question by voice.
- Set variable
PrompttoDictated TextDictated Textis a “magic variable” since it holds the result of the previous Dictate Text action.Promptis a new variable that will hold the result ofDictated Text. Naming is up to use
- Send to Supabase
- Action: Get Contents of URL. This is where all the details we copied from Supabase will come in handy.
- URL: your Supabase Endpoint URL
- Method: POST
- Headers: click on small dropdown arrow
- Key:
Content-Type, Value:application/json - Key:
Authorization, Value:Bearer <secret> - Key:
x-ai-shortcut-secret, Value:random long string (e.g. 32+ chars)
- Key:
- Request Body: JSON
- Key:
prompt, Value:Prompt(the Dictated Text variable) - (optional) Key:
provider, Value: “gemini” or “openai” (no quotes)
- Key:
- Extract Response
- Action: Get Dictionary Value
- Key:
response - Will read: Get
ValueforresponseinContents of URL
- Transform response to Siri compatible format
- Action: Text
- Paste in only
Dictionary Value(i.e., response of Get Dictionary Value)
- Save File
- Save
TexttoOn My iPhone(or desired location) - Click dropdown of defaults.
- Ask Where to Save → off
- Subpath → /ai_speech.txt (I’m saving the file directly in
On My iPhone) - Overwrite If File Exists → on
- Save
- (Optional) Have Siri announce progress
- Action: Speak
- Your string of preference, i.e., “Running your shortcut…” (no quotes)
- Wait → 1 second
- (gives Siri time to release audio session)
- URL
shortcuts://run-shortcut?name=AI%20Speakernamereferences secondary reader shortcut and depends on what you call it (in my case, AI Speaker)
- Open URLs
- (launches speaker shortcut in a fresh context)
Now you’ve got your own AI assistant.
AI Speaker (reads + speaks from local file)
All instructions will assume the names/locations from the example above. Change them if you opted for different names, so you reference yours.
- Open the Shortcuts app.
- Tap the + button → Add Action.
- Name it AI Speaker (or whatever you like)
Then add these actions in order:
- (Optional) Have Siri announce progress
- Action: Speak
- Your string of preference, i.e., “Got it, I’m fetching your answer!” (no quotes)
- Wait → 1 second
- Get File
- From:
On My iPhone - Path:
ai_speech.txt - Error If Not Found: off
- From:
- Set variable
SpeechtoFile - Split Text
- Text:
Speech - By:
Custom \\n\\n
- Text:
- Set variable
ParagraphstoSplit Text - If
Paragraphshas any value- Repeat with each item in
Paragraphs- Speak
Repeat Item - Click defaults dropdown.
- Wait Until Finished: on
- Keep rest of defaults.
- Speak
- Otherwise
- Speak
Speech
- Speak
- Repeat with each item in
- Count
Paragraphs - If
Countis 1- Split Text
- Text:
Speech - By:
Custom .(period + space)
- Text:
- Repeat with each item in
Split Text- Speak
Repeat Item
- Speak
- Split Text
- Stop and output
If Result- If there’s nowhere to output:
Do Nothing
- If there’s nowhere to output:
And the reader shortcut is done.
To make it easy, I zipped the shortcuts so you can easily download, extract, and add to Shortcuts. Follow the steps below to add them to Shortcuts:
- Click to download the Shortcuts to your iPhone
- Make sure you have the Shortcuts app installed
- Navigate to the Files folder where you saved the downloaded zip file
- Unzip the content, and you will be left with two .shortcut files
- Click on each to add to your Shortcuts (you will be prompted to do so)
Once you have them added, you must make certain updates to Ask MyAI Demo shortcut to link to your Supabase endpoint. Replace:
- SUPABASE ENDPOINT URL in Get Contents action
- Click dropdown of options
- LONG STRING
- BEARER SECRET
- AI MODEL (optional, will default to that set in Supabase per directions on previous step)
- Updating the shortcut name (e.g., Ask MyAI Demo) to something unique is a good idea (ex: KleaAI). I will drop the “Demo” part from now assuming this was renamed as Ask MyAI.
Optionally, you can update certain actions or texts based on preferences, but I encourage you to do so only after ensuring the existing workflow works.
Tip ✅
The first run will prompt you to grant one-time permissions like allowing access to Files, writing to file, etc. I suggest you do a manual run by tapping onto Ask MyAI shortcut instead of using Siri. Allow access to everything properly then on consecutive runs feel free to either voice-activate or tap (you can even add to homescreen and widgets).
Summary of Shortcut Actions
To sum up what we’ve accomplished, let’s test things out.
You can either say:
“Hey Siri, Ask MyAI (pause until prompting bubble displays confirming shortcut run)
Give me six cool facts about tri-color Beagles”
Or tap on Ask MyAI shortcut and ask:
“Give me six cool facts about tri-color Beagles”
Notice how for voice-activated queries with Siri, there’s a necessary pause. Wondering why you have to pause?
Siri tries to interpret that entire phrase as a natural language command instead of as “run the Ask MyAI shortcut”. Under the hood:
- Siri checks if “Ask MyAI …” matches a built-in Siri phrase or intent (like “Ask AI” meaning “ask ChatGPT” in newer IOS or “ask something”).
- Only after the short pause does Siri recognize “Ask MyAI” as the name of the shortcut.
- Once she decides it’s a shortcut, she runs it, and so it opens with the custom Speaker text followed by Dictate Text action, which causes Siri to say “What’s the text?” meaning she’s ready to record your input 😮💨
If you speak too fast (i.e., without that mini pause), Siri thinks you’re still talking to her normally and never passes control to the shortcut. This is also where giving the Ask MyAI shortcut a unique name can come in handy, though still be prepared to give that mini-pause.
Now, once Siri does activate the shortcut, here’s what will happen:
- Siri hears your request (if initiated with Siri dictation) and starts Ask MyAI shortcut (or your tap does)
- That shortcut sends your question to Supabase (your AI)
- It sends back a response
- It saves the response to a local file ai_speech.txt, waits a second for Siri to stop listening, and then opens the AI Speaker
- AI Speaker starts, reads the file, and you hear Siri’s voice say the response
All of it happens automatically; you never have to touch the screen if you don’t want to. You can also find the file in the specified location to see the response saved there from the most recent query.
(Optional) Optimize + Personalize Your AI Siri
This is where it gets fun. Once you have tested the initial setup, you’re free to take it up to whatever level you want (and can).
Some ideas:
1. Give It Personality
Inside your Supabase function, prepend text to the prompt:
“You are a friendly assistant named Siri+. Answer clearly and with a dash of humor.”
Now every response has a touch of character—your character 😅
Or revise the prompt to do something else, maybe be more concise or particular.
2. Switch AI Models
Want to try Gemini for creativity and ChatGPT for analysis? Just swap your API key and endpoint in Supabase.
You can even create different Shortcuts, one for each AI brain. Or venture beyond onto Perplexity and Claude (maybe even Local models). I don’t know, take it as far as you can 👍
3. Chain Automations
The beauty of Shortcuts is that you can chain anything to create your desired automation or workflow.
After the AI responds, add actions like:
- Save to Notes
- Send as a Message
Your AI Siri can now summarize emails, draft tweets, or log meeting notes automatically.
Note 👀
Just a note of caution, if you’re using cloud-hosted AI models, be mindful of sending sensitive personal or private information to external servers.
Troubleshooting Tips
- Siri isn’t loading the shortcut?
- Try a manual tap if it’s the first run post-startup
- Ensure to give the Ask MyAI shortcut (the one fetching from Supabase) a unique name to avoid conflicts with native Siri phrases
- Remember to give that little pause when you speak
- Siri isn’t speaking back? Add a second Wait before the Speak Text action.
- Responses cut off mid-sentence? Ensure your Supabase function isn’t streaming data.
- Getting errors? Open Supabase → Logs → Edge Functions to debug.
Any other issues? Drop a comment 👇
It’s a wrap
Apple Intelligence is here, but only for some. Thanks to a bit of creativity, you can bring similar superpowers to any iPhone today.
To recap:
- Create a Supabase function that talks to Gemini or ChatGPT.
- Hook it up to Shortcuts via a simple workflow.
- Add dictation + speech so it feels like Siri.
- Personalize it and chain automations.
That’s it. A DIY Apple Intelligence minus the hardware upgrade, with fully customizable prompts and flows, with a flexibility that puts you in control.
So if you’re not rocking a newer iPhone yet (or, even if you are but still want to put your own spin on it), give Siri a brain upgrade yourself.
You might just prefer your custom version anyway 🙂