iMessage daemon — polls chat.db and routes to DeepResearchAgent.
Monitors a designated iMessage conversation for new messages, routes
them to the agent, and sends responses back via AppleScript.
Requires macOS with Full Disk Access for chat.db reading and
Accessibility permission for AppleScript Messages control.
Functions
poll_new_messages
poll_new_messages(*, db_path: str = _DEFAULT_DB_PATH, last_rowid: int = 0, chat_identifier: str = '') -> List[Dict[str, Any]]
Return new incoming messages since last_rowid.
Source code in src/openjarvis/channels/imessage_daemon.py
| def poll_new_messages(
*,
db_path: str = _DEFAULT_DB_PATH,
last_rowid: int = 0,
chat_identifier: str = "",
) -> List[Dict[str, Any]]:
"""Return new incoming messages since last_rowid."""
try:
conn = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True)
conn.row_factory = sqlite3.Row
except sqlite3.OperationalError:
return []
try:
rows = conn.execute(
"SELECT m.ROWID as rowid, m.text, m.date, "
"c.chat_identifier "
"FROM message m "
"JOIN chat_message_join cmj "
"ON cmj.message_id = m.ROWID "
"JOIN chat c ON c.ROWID = cmj.chat_id "
"WHERE m.ROWID > ? AND m.is_from_me = 0 "
"AND m.text IS NOT NULL "
"AND c.chat_identifier = ? "
"ORDER BY m.ROWID ASC",
(last_rowid, chat_identifier),
).fetchall()
return [dict(row) for row in rows]
finally:
conn.close()
|
send_imessage
send_imessage(chat_identifier: str, message: str) -> bool
Send an iMessage via AppleScript.
Source code in src/openjarvis/channels/imessage_daemon.py
| def send_imessage(chat_identifier: str, message: str) -> bool:
"""Send an iMessage via AppleScript."""
escaped = message.replace("\\", "\\\\").replace('"', '\\"')
script = (
f'tell application "Messages"\n'
f" set targetChat to a reference to "
f'chat id "{chat_identifier}"\n'
f' send "{escaped}" to targetChat\n'
f"end tell"
)
try:
subprocess.run(
["osascript", "-e", script],
capture_output=True,
text=True,
timeout=30,
)
return True
except (subprocess.TimeoutExpired, FileNotFoundError):
logger.error("Failed to send iMessage via AppleScript")
return False
|
run_daemon
run_daemon(*, chat_identifier: str, db_path: str = _DEFAULT_DB_PATH, handler: Any = None, poll_interval: float = _POLL_INTERVAL, max_iterations: int = 0) -> None
Run the iMessage polling daemon.
Source code in src/openjarvis/channels/imessage_daemon.py
| def run_daemon(
*,
chat_identifier: str,
db_path: str = _DEFAULT_DB_PATH,
handler: Any = None,
poll_interval: float = _POLL_INTERVAL,
max_iterations: int = 0,
) -> None:
"""Run the iMessage polling daemon."""
pid_path = Path(_PID_FILE)
pid_path.parent.mkdir(parents=True, exist_ok=True)
pid_path.write_text(str(os.getpid()))
last_rowid = _get_max_rowid(db_path)
logger.info(
"iMessage daemon started — monitoring %s from ROWID %d",
chat_identifier,
last_rowid,
)
running = True
def _stop(signum: int, frame: Any) -> None:
nonlocal running
running = False
signal.signal(signal.SIGTERM, _stop)
signal.signal(signal.SIGINT, _stop)
iterations = 0
while running:
messages = poll_new_messages(
db_path=db_path,
last_rowid=last_rowid,
chat_identifier=chat_identifier,
)
for msg in messages:
last_rowid = msg["rowid"]
text = msg["text"]
logger.info("Received: %s", text[:100])
if handler is not None:
try:
response = handler(text)
if response:
send_imessage(chat_identifier, response)
except Exception:
logger.exception(
"Handler failed for message %d",
msg["rowid"],
)
iterations += 1
if max_iterations and iterations >= max_iterations:
break
time.sleep(poll_interval)
if pid_path.exists():
pid_path.unlink()
logger.info("iMessage daemon stopped")
|
is_running
Check if the daemon is currently running.
Source code in src/openjarvis/channels/imessage_daemon.py
| def is_running() -> bool:
"""Check if the daemon is currently running."""
pid_path = Path(_PID_FILE)
if not pid_path.exists():
return False
try:
pid = int(pid_path.read_text().strip())
os.kill(pid, 0)
return True
except (ValueError, ProcessLookupError, PermissionError):
pid_path.unlink(missing_ok=True)
return False
|
stop_daemon
Stop the running daemon. Returns True if stopped.
Source code in src/openjarvis/channels/imessage_daemon.py
| def stop_daemon() -> bool:
"""Stop the running daemon. Returns True if stopped."""
pid_path = Path(_PID_FILE)
if not pid_path.exists():
return False
try:
pid = int(pid_path.read_text().strip())
os.kill(pid, signal.SIGTERM)
pid_path.unlink(missing_ok=True)
return True
except (ValueError, ProcessLookupError, PermissionError):
pid_path.unlink(missing_ok=True)
return False
|