GhostLine: Agentic AI for iMessage 👻
Inspiration
You know that feeling when you're deep in a group chat argument about whether a hotdog is a sandwich, and nobody wants to Google it? Or when you're ghosting someone but don't realize it until it's too late? We realized that iMessage—the app we use more than anything else—is actually pretty dumb. It's just text bubbles.
We wanted to change that. We wanted to turn iMessage into an operating system for agents. Not just chatbots that you talk to, but agents that live in your chats and do things for you. We were inspired by the idea of a "Second Brain" that doesn't require a new app, just the blue bubbles you already have.
What it does
GhostLine is a suite of intelligent agents running on top of a custom-built, lean iMessage SDK. It basically superpowers your iPhone without needing a jailbreak.
We built three demo apps to show what it can do:
- The Argument Resolver ("Judge"): It listens to your group chats. When things get heated, you just text
Judge: [topic]. It scrapes the web for facts, picks a winner based on evidence, and drops a verdict in the chat. It even adopts a chill Gen Z persona so it doesn't sound like a robot. - Vibe Check (Vision Agent): You send a photo to the agent—a fit check, a screenshot of a cringe text, or your lunch. It uses GPT-4o Vision to analyze it and roast you (or hype you up) instantly. No commands needed, just image recognition.
- Orbit (Relationship CRM): This is the craziest part. It analyzes your message metadata to track your relationships. It detects if you're ghosting someone (or getting ghosted), calculates a "Health Score" for every contact based on reply times, and visualizes it all in a sick dashboard. It even drafts replies for you to save the relationship.
How we built it
This was the hard part. Apple doesn't have an API for this.
We built a Lean SDK from scratch in TypeScript. It connects to a BlueBubbles Server (which acts as a bridge to a real Mac).
- Core SDK: We ditched the heavy libraries and wrote a lightweight client using native
fetchandsocket.io. It handles real-time event listening, message deduplication (so the bot doesn't spam), and queue management to make sure messages send in order. - The Agents:
- Judge: Uses OpenAI's API with Function Calling. We feed it a prompt to be "impartial but chill" and strip out all the markdown so it looks like a real text.
- Orbit: We built a React frontend with Tailwind and Framer Motion for that glass-morphism look. The backend is an Express server that queries our SDK for chat history, calculates the "Ghost Score" using some heuristic algorithms (time delta + sender direction), and serves it to the frontend.
- Vision: We pipe binary image data from the iMessage attachment database directly into OpenAI's vision model.
Challenges we ran into
Getting iMessage to play nice with code is a nightmare.
- The "Private API" issue: We kept hitting errors because certain features (like sending reactions/tapbacks) require a special "Private API" bundle on the server. We had to rewrite our SDK to fail gracefully and fallback to standard text messages when the advanced features weren't available.
- Infinite Loops: At one point, the Argument Resolver replied to itself, then read its own reply, triggered again, and nearly crashed the server. We had to implement strict "from me" checks and message deduplication logic.
- Markdown vs iMessage: LLMs love using bold text (
**like this**), but iMessage doesn't render markdown. It just looks like ugly code. We had to write regex cleaners and strict system prompts to force the AI to speak in plain text.
Accomplishments that we're proud of
- Zero Latency (almost): The socket connection is incredibly fast. You send a text, and the dashboard updates instantly.
- The UI: Orbit honestly looks better than most real apps. The way it glows red when you're ghosting someone is super satisfying (and stressful).
- It actually works: We were sending real photos from a real iPhone and getting AI analysis back in seconds. It feels like magic because it's in the native Messages app.
What we learned
- State management is key: When building chat bots, you can't just react to every event. You need to track state (who is talking, what was said last) or it gets chaotic fast.
- Prompt Engineering is UI: For a text-based app, the "User Interface" is the personality of the AI. Tweaking the prompt to be "Gen Z" made the app feel 10x more usable than the default robotic voice.
- Reverse Engineering is fun: figuring out how BlueBubbles talks to the Mac, and then wrapping that in our own clean TypeScript SDK, taught us a ton about API design.
What's next for GhostLine
We're just scratching the surface.
- The Clone: We want to fine-tune a model on our entire export history so the bot can reply exactly like us when we're busy.
- Calendar Agent: Integrating Google Calendar so you can just text "Dinner tomorrow at 7" and it handles the invites.
- iMessage App Store: Imagine a platform where developers can build these "mini-agents" and you just install them into your chats.
GhostLine turns your texts into actions. It's pretty sick.
How We Used Kiro to Build GhostLine: A Technical Deep Dive
The Big Picture
Building GhostLine was basically like trying to hack into Apple's walled garden without actually hacking anything. We needed to write a custom SDK, build multiple AI agents, create a full-stack relationship tracker, and make it all work together seamlessly. Kiro wasn't just helpful here, it was essential. This writeup breaks down exactly how we leveraged Kiro's features to go from zero to a working iMessage AI platform.
Vibe Coding: The Conversational Development Flow
How We Structured Our Conversations
The way we worked with Kiro was pretty different from traditional coding. Instead of writing everything ourselves, we had these iterative conversations where we'd describe what we wanted, Kiro would generate code, we'd test it, find issues, and then refine. It was like pair programming with someone who never gets tired.
Here's how a typical conversation would go:
Us: "We need a TypeScript SDK that connects to BlueBubbles Server. It should use Socket.IO for real-time events and native fetch for REST calls. Make it modular with separate classes for messages, chats, and attachments."
Kiro: generates the entire SDK structure with proper TypeScript types
Us: "The message deduplication isn't working. We're getting duplicate events."
Kiro: adds a Set-based deduplication system with size management
The key was being specific about what we wanted but letting Kiro figure out the implementation details. We'd describe the problem domain (iMessage, BlueBubbles API, real-time events) and Kiro would generate production-ready code that actually understood the context.
Most Impressive Code Generation
The most mind-blowing moment was when we asked Kiro to implement the entire relationship scoring algorithm. We just described the concept: "Calculate a vibe score from 0-100 based on message recency, who sent the last message, and time decay. Make it categorize relationships as healthy, waiting, ghosting, or dead."
Kiro generated not just the algorithm, but also:
- The complete TypeScript types for relationship data
- The Express API endpoint with proper error handling
- The React frontend components with Framer Motion animations
- The color-coding logic for different status categories
- Even the circular progress indicator for the score display
What would have taken us hours of trial and error (especially the animation timing and color gradients) was done in minutes. The code was clean, well-typed, and actually worked on the first try. That never happens.
Another impressive generation was the auto-retry logic for chat creation. BlueBubbles throws an error if you try to send a message to a chat that doesn't exist yet. We explained this edge case to Kiro, and it generated a try-catch wrapper that automatically creates the chat and retries the message send. It even extracted the phone number from the chat GUID format (iMessage;-;+1234567890) to pass to the chat creation endpoint. That level of context awareness was crazy.
Agent Hooks: Automating the Boring Stuff
We went absolutely wild with agent hooks. We created 17 different hooks to automate our workflow, and honestly, it changed how we develop.
The Game-Changing Hooks
1. Type Check on Save (test-on-save.json)
Every time we saved index.ts, types.ts, or utils.ts, Kiro would automatically run TypeScript's type checker. No more "oh crap, I broke the types" moments 30 minutes later. We'd know immediately if something was wrong.
{
"trigger": {
"type": "onFileSave",
"filePattern": "{index.ts,types.ts,utils.ts}"
},
"action": {
"type": "command",
"command": "npx tsc --noEmit --pretty"
}
}
This saved us so much time. TypeScript errors are annoying to debug after the fact, but catching them instantly made development way smoother.
2. Update Docs Hook (update-docs.json)
This one was genius. Whenever we modified the SDK's main index.ts file, Kiro would automatically review the changes and tell us which documentation files needed updating. It would literally say "Hey, you added a new method to MessageModule, update quick-reference.md with an example."
We never had to remember to update docs manually. The hook would trigger, Kiro would analyze the diff, and we'd get a checklist of what to update. Documentation stayed in sync with code automatically.
3. Frontend Build Check (frontend-build-check.json)
Every time we saved a React component, this hook would run npm run build in the frontend directory. If the build failed, we'd see the error immediately. No more "works on my machine" situations where we'd push broken code.
4. Test SDK Connection (test-sdk-connection.json)
This was a manual hook (button-triggered) that would curl the BlueBubbles server and check if it was reachable. Super useful when debugging connection issues. Instead of writing the curl command every time, we'd just click the button and see if the server was up.
{
"trigger": {
"type": "manual",
"buttonLabel": "🔌 Test BlueBubbles Connection"
},
"action": {
"type": "command",
"command": "curl -s http://10.48.254.3:1234/api/v1/server/info?guid=Anish2005! | jq '.data.os_version'"
}
}
5. Relationship Score Simulator (relationship-score-simulator.json)
This manual hook was perfect for testing the vibe score algorithm. We'd click the button, and Kiro would generate a test script that simulated different scenarios (recent reply, ghosting, dead conversation) and show us the calculated scores. Made it super easy to validate the algorithm without manually creating test data.
6. Generate Agent Template (generate-agent-template.json)
When we wanted to create a new agent (like the Calendar Agent), we'd click this button and Kiro would scaffold the entire project structure: index.ts with SDK setup, tsconfig.json, README.md, and .env.example. It followed the patterns from our existing agents, so everything was consistent.
How Hooks Improved Our Process
The hooks basically eliminated context switching. We didn't have to remember to run tests, check types, update docs, or validate builds. It all happened automatically. The manual hooks were like having a toolbox of common tasks that we could trigger with one click instead of typing out commands.
The best part? We could enable/disable hooks without deleting them. When we were doing rapid prototyping and didn't want build checks slowing us down, we'd just disable that hook temporarily. Then re-enable it when we were ready to polish.
Spec-Driven Development: The Structured Approach
We used specs for the bigger, more complex features. The relationship tracker frontend and the calendar agent were both built using spec-driven development.
How We Structured Specs
Our specs followed a consistent format:
---
title: Feature Name
status: planned | in-progress | completed
created: 2024-12-05
---
# Feature Name
## Overview
High-level description
## Requirements
### Functional Requirements
What it needs to do
### Non-Functional Requirements
Performance, accessibility, etc.
## Design
Component hierarchy, data flow, color schemes
## Implementation Tasks
- [ ] Task 1
- [ ] Task 2
## Testing Checklist
Edge cases to verify
## Future Enhancements
Ideas for later
The key was being thorough but not overly prescriptive. We'd describe what we wanted, not how to implement it. That gave Kiro room to make smart decisions about implementation details.
The Relationship Tracker Frontend Spec
This was our most detailed spec. We described:
- The component hierarchy (App → Header → FilterBar → RelationshipGrid → RelationshipCard)
- The data flow (fetch from API, store in state, poll every 30 seconds)
- The color scheme for different status categories
- The API contract with exact TypeScript interfaces
- A testing checklist with edge cases
Then we just told Kiro: "Implement the relationship-tracker-frontend spec."
Kiro read the spec and generated:
- The entire React app structure with Vite and TypeScript
- All the components with proper prop types
- The Tailwind styling with the exact colors we specified
- The polling mechanism with cleanup
- Error handling and loading states
- Even the Framer Motion animations for the score circles
What was wild is that Kiro didn't just blindly follow the spec. It made smart decisions about things we didn't specify, like using useEffect cleanup for the polling interval and adding proper TypeScript generics for the API fetch function.
Spec-Driven vs Vibe Coding
We used both approaches, and they complemented each other well:
Spec-driven was better for:
- Complex features with multiple moving parts
- Features that needed careful planning (like the relationship tracker)
- When we wanted to think through the design before coding
- Features that multiple people needed to understand
Vibe coding was better for:
- Quick iterations and bug fixes
- Exploring ideas without committing to a full design
- Refactoring existing code
- When we weren't sure exactly what we wanted yet
The relationship tracker was spec-driven because it had a frontend, backend, algorithm, and UI that all needed to work together. The SDK bug fixes and small improvements were vibe-coded because we could just describe the issue and get a fix immediately.
One cool thing: we could reference specs in our vibe coding conversations. We'd say "Update the relationship tracker to match the spec" and Kiro would know exactly what we meant because it had access to the spec file.
Steering Docs: The Secret Sauce
Steering docs were honestly the most underrated feature. They're like giving Kiro a brain dump about your project so it doesn't have to ask basic questions every time.
Our Steering Strategy
We created 8 steering docs, each with a specific purpose:
1. project-overview.md (Always included) High-level description of the project, tech stack, and architecture. This gave Kiro context about what we were building.
2. quick-reference.md (Always included) Code snippets for common SDK operations. Whenever Kiro needed to generate code that used our SDK, it would reference this doc and use the correct patterns.
3. coding-standards.md (Always included) Our TypeScript conventions, naming patterns, error handling approach, and module structure. This ensured all generated code was consistent.
4. debugging-tips.md (Always included) Common issues and solutions. When we'd describe a bug, Kiro would often reference this doc and suggest the right fix immediately.
5. bluebubbles-api.md (Conditional: SDK files) Complete BlueBubbles API reference. Only included when working on SDK code, so it didn't clutter context for frontend work.
6. relationship-tracker-logic.md (Conditional: relationship tracker files) The vibe score algorithm, status categories, and UI component specs. Only loaded when working on the relationship tracker.
7. agent-apps-guide.md (Conditional: agent files)
Patterns for building agents, message filtering, error handling. Only included when working in the apps/ directory.
8. mcp-integration.md (Manual) Guide for extending Kiro with MCP servers. We'd manually include this when setting up new integrations.
The Strategy That Made the Biggest Difference
The conditional inclusion was game-changing. Instead of dumping all our docs into every conversation, Kiro would only load the relevant ones based on what files we were working on.
When we were editing apps/calendar-agent/index.ts, Kiro would automatically load:
- project-overview.md (always)
- quick-reference.md (always)
- coding-standards.md (always)
- debugging-tips.md (always)
- agent-apps-guide.md (matches
**/apps/**/*.ts)
But it wouldn't load relationship-tracker-logic.md or bluebubbles-api.md because they weren't relevant. This kept the context focused and made Kiro's responses faster and more accurate.
The "always included" docs were our foundation. They ensured Kiro always understood the project structure and coding style. The conditional docs added specialized knowledge when needed.
Real Example: The Power of Steering
Without steering, we'd have conversations like this:
Us: "Add a method to send reactions"
Kiro: "Sure, where should I add it?"
Us: "In the MessageModule class"
Kiro: "What's the API endpoint?"
Us: "POST /api/v1/message/react"
Kiro: "What parameters does it need?"
Us: "chatGuid, selectedMessageGuid, reaction"
Kiro: "Should it be queued?"
Us: "Yes, use the enqueue function"
With steering (quick-reference.md + coding-standards.md), the conversation was:
Us: "Add a method to send reactions"
Kiro: generates the complete method with proper types, error handling, queueing, and even a code example
The steering docs eliminated all the back-and-forth. Kiro already knew our patterns, our API structure, and our conventions.
MCP: Extending Kiro's Capabilities
We didn't go crazy with MCP servers, but the ones we used were clutch.
Sequential Thinking MCP
This was perfect for designing the vibe score algorithm. We'd ask Kiro to "use sequential thinking to design a relationship health scoring system" and it would break down the problem step-by-step:
- What factors indicate a healthy relationship?
- How should time decay work?
- What's the penalty for ghosting?
- How do we categorize scores into statuses?
The sequential thinking server made Kiro's reasoning visible, which helped us refine the algorithm. We could see its thought process and say "actually, the ghosting penalty should be harsher" and it would adjust.
Brave Search MCP
When we hit issues with the BlueBubbles API (like the "Private API" errors), we'd use Brave Search to find documentation or GitHub issues. Kiro would search, find relevant info, and suggest solutions. Way faster than manually Googling.
Custom BlueBubbles MCP (Planned)
We started designing a custom MCP server that would let Kiro directly interact with iMessage. The idea was that we could say "send a test message to +1234567890" and Kiro would use the MCP tool to actually send it via our SDK.
This would have been huge for testing. Instead of manually running test scripts, we could just tell Kiro to test something and it would do it conversationally. We didn't finish this during the hackathon, but it's on the roadmap.
How MCP Improved Our Workflow
The main benefit was that MCP let Kiro do things it normally couldn't. Sequential thinking gave it better reasoning. Brave Search gave it access to current information. The custom MCP would have given it the ability to actually interact with our system.
Without MCP, Kiro is limited to reading/writing files and running commands. With MCP, it becomes a true development partner that can reason, research, and interact with external systems.
The Technical Specs: What We Actually Built
The Lean SDK
Core Architecture:
- Event-driven design using TypeScript's EventEmitter with custom type mappings
- Modular API split into 5 classes: MessageModule, ChatModule, AttachmentModule, HandleModule, ServerModule
- Socket.IO client for real-time WebSocket events
- Native fetch for REST API calls (no heavy dependencies)
- Task queue for rate-limiting send operations
- Message deduplication using a Set with size management
Key Features:
- Auto-retry logic for chat creation when chat doesn't exist
- Proper TypeScript types for all API responses
- Support for reactions, message editing, unsending
- Attachment and sticker sending with FormData
- Typing indicators (start/stop)
- Mark chats as read
- Handle availability checking (iMessage vs SMS)
Why It's Lean: We stripped out everything we didn't need. No bloated libraries, no unnecessary abstractions. Just clean TypeScript that does exactly what we need. The entire SDK is ~500 lines of code but supports the full BlueBubbles API.
The Relationship Tracker (Orbit)
Backend:
- Express.js server with CORS
- Connects to BlueBubbles via our SDK
/api/relationshipsendpoint that calculates vibe scores- Real-time updates via polling (30-second intervals)
Vibe Score Algorithm:
Base Score: 85
Time Decay:
- < 1 hour: +10 points
- 1-24 hours: no change
- 24-72 hours: -10 points
- > 72 hours: -30 points
- > 1 week: score drops to 10
Ghosting Penalty:
- If you sent last message AND > 24 hours: -40 points
Status Categories:
- healthy (70-100): Recent activity, balanced conversation
- waiting (50-85): They replied, you haven't responded
- ghosting (20-50): You sent last, no response > 24h
- dead (0-20): No activity > 1 week
Frontend:
- React 19 with TypeScript
- Vite for blazing-fast builds
- Tailwind CSS for styling (glass-morphism aesthetic)
- Framer Motion for smooth animations
- Circular progress indicators for scores
- Color-coded status badges (green/yellow/orange/red)
- Responsive grid layout
- Real-time polling with cleanup
Why It's Useful: It solves a real problem. We've all ghosted someone without realizing it. Orbit makes it visible. The dashboard shows you exactly who you're neglecting and gives you actionable insights ("Reply now", "Follow up", "Reconnect").
The vibe score is surprisingly accurate. It captures the "feel" of a relationship based on objective data (message timestamps and sender direction). The color-coding makes it instantly clear which relationships need attention.
How It Can Be Expanded:
- Predictive analytics: Use ML to predict when a relationship is declining before it hits "dead" status
- Smart reply suggestions: Generate context-aware replies based on conversation history
- Notification system: Alert you when a relationship status changes
- Historical tracking: Show score trends over time with line charts
- Bulk actions: "Reply to all waiting" button that drafts messages for multiple people
- Integration with calendar: Suggest meeting times for people you haven't seen in a while
- Sentiment analysis: Factor in message tone (positive/negative) into the score
- Group chat support: Track group dynamics and participation levels
The Agent Apps
Argument Resolver (Judge):
- Listens to group chats for the trigger phrase "Judge: [topic]"
- Uses OpenAI Function Calling to search the web for facts
- Analyzes both sides of the argument
- Generates a verdict with evidence
- Strips markdown and uses Gen Z language to sound natural
- Ignores its own messages to prevent infinite loops
Vision Agent (Vibe Check):
- Monitors for image attachments
- Downloads the image via BlueBubbles API
- Sends to GPT-4o Vision for analysis
- Generates contextual responses (roasts, compliments, descriptions)
- Handles multiple image types (photos, screenshots, memes)
Calendar Agent (Planned):
- Natural language date/time parsing using chrono-node
- Google Calendar API integration
- Event creation, querying, rescheduling
- Reminder system with cron jobs
- Daily summaries and weekly previews
- Whitelist for approved contacts
Why They're Powerful: These agents turn iMessage into an operating system. You're not leaving your chat app to Google something, check your calendar, or get AI analysis. It all happens in the native Messages app, which is where you already spend most of your time.
The key insight is that the UI is the conversation. There's no buttons, no forms, no separate app. You just text naturally and the agents understand intent.
Expansion Possibilities:
- The Clone: Fine-tune a model on your entire message history so it can reply exactly like you
- Shopping agent: "Find me the cheapest AirPods Pro" → searches, compares prices, sends links
- Translation agent: Automatically translates messages in group chats with international friends
- Reminder agent: "Remind me to call mom tomorrow at 6pm" → sets reminder, sends message at that time
- News agent: "What's happening in tech today?" → curated news summary
- Workout agent: Track workouts via text, get form tips from photos
- Recipe agent: Send a photo of your fridge, get recipe suggestions
The Kiro Advantage: Why This Wouldn't Have Been Possible Without It
Let's be real: we built this entire project in a hackathon timeframe. That's insane. Here's what would have taken forever without Kiro:
1. The SDK (saved ~10 hours) Writing a TypeScript SDK with proper types, error handling, WebSocket management, and queue logic would have taken days. With Kiro, we described what we needed and had working code in an hour.
2. The Relationship Tracker (saved ~8 hours) Building a full-stack app with React, Express, algorithm design, and animations? That's easily a weekend project. We did it in an afternoon because Kiro generated the entire structure from our spec.
3. Documentation (saved ~4 hours) We have 8 comprehensive steering docs that stay in sync with our code thanks to the update-docs hook. Writing and maintaining that manually would have been tedious.
4. Debugging (saved ~6 hours) The debugging-tips steering doc + Kiro's ability to analyze errors meant we solved issues way faster. No more Stack Overflow rabbit holes.
5. Testing (saved ~3 hours) The hooks automated type checking, build verification, and connection testing. We caught bugs immediately instead of discovering them later.
Total time saved: ~31 hours
But it's not just about time. It's about quality. The code Kiro generated was clean, well-typed, and followed best practices. It wasn't sloppy hackathon code that we'd have to rewrite later. It was production-ready.
The Human Touch: What We Still Did Ourselves
Kiro is powerful, but it's not magic. Here's what we handled:
1. Architecture decisions We decided to use a modular SDK structure, chose the tech stack, and designed the relationship scoring algorithm. Kiro implemented our vision, but we provided the vision.
2. Prompt engineering Getting the agents to sound natural (not robotic) required careful prompt crafting. We iterated on the system prompts until the tone was right.
3. Testing with real data We tested everything with actual iMessage conversations, real photos, and live BlueBubbles connections. Kiro can't do that.
4. Creative direction The glass-morphism UI, the color scheme, the "vibe score" concept—those were our ideas. Kiro executed them beautifully, but we came up with them.
5. Problem-solving edge cases When we hit the "Private API" issue or the infinite loop bug, we had to understand the root cause and explain it to Kiro. It would then generate the fix, but we had to diagnose the problem.
Final Thoughts: The Future of Development
Working with Kiro felt like a glimpse into the future. It's not about AI replacing developers. It's about AI amplifying what developers can do.
We went from idea to working product in a fraction of the time it would normally take. But more importantly, we enjoyed the process. Instead of grinding through boilerplate and debugging type errors, we spent our time on the interesting problems: designing algorithms, crafting user experiences, and building something genuinely useful.
The combination of vibe coding (for quick iterations), spec-driven development (for complex features), steering docs (for context), and agent hooks (for automation) created a workflow that felt effortless. We were always in flow state.
GhostLine is just the beginning. With tools like Kiro, the barrier to building complex software is dropping fast. You don't need a team of 10 engineers anymore. You need a clear vision, good taste, and the ability to communicate what you want.
That's the future we're excited about.
Built With
- agentic
- ios
- llm
- node.js
- typescript
Log in or sign up for Devpost to join the conversation.