feat: add notifications for MacOS using Applescript (#160)
yolo'ed it with codex. Let me know if this looks good to you. https://github.com/openai/codex/issues/148 tested with: ``` npm run build:dev ``` <img width="377" alt="Screenshot 2025-04-16 at 18 12 01" src="https://github.com/user-attachments/assets/79aa799b-b0b9-479d-84f1-bfb83d34bfb9" />
This commit is contained in:
@@ -68,6 +68,7 @@ const cli = meow(
|
||||
--no-project-doc Do not automatically include the repository's 'codex.md'
|
||||
--project-doc <file> Include an additional markdown file at <file> as context
|
||||
--full-stdout Do not truncate stdout/stderr from command outputs
|
||||
--notify Enable desktop notifications for responses
|
||||
|
||||
Dangerous options
|
||||
--dangerously-auto-approve-everything
|
||||
@@ -144,6 +145,11 @@ const cli = meow(
|
||||
"Disable truncation of command stdout/stderr messages (show everything)",
|
||||
aliases: ["no-truncate"],
|
||||
},
|
||||
// Notification
|
||||
notify: {
|
||||
type: "boolean",
|
||||
description: "Enable desktop notifications for responses",
|
||||
},
|
||||
|
||||
// Experimental mode where whole directory is loaded in context and model is requested
|
||||
// to make code edits in a single pass.
|
||||
@@ -243,6 +249,7 @@ config = {
|
||||
apiKey,
|
||||
...config,
|
||||
model: model ?? config.model,
|
||||
notify: Boolean(cli.flags.notify),
|
||||
};
|
||||
|
||||
if (!(await isModelSupportedForResponses(config.model))) {
|
||||
|
||||
@@ -15,7 +15,7 @@ import { formatCommandForDisplay } from "../../format-command.js";
|
||||
import { useConfirmation } from "../../hooks/use-confirmation.js";
|
||||
import { useTerminalSize } from "../../hooks/use-terminal-size.js";
|
||||
import { AgentLoop } from "../../utils/agent/agent-loop.js";
|
||||
import { log, isLoggingEnabled } from "../../utils/agent/log.js";
|
||||
import { isLoggingEnabled, log } from "../../utils/agent/log.js";
|
||||
import { ReviewDecision } from "../../utils/agent/review.js";
|
||||
import { OPENAI_BASE_URL } from "../../utils/config.js";
|
||||
import { createInputItem } from "../../utils/input-utils.js";
|
||||
@@ -28,8 +28,9 @@ import HelpOverlay from "../help-overlay.js";
|
||||
import HistoryOverlay from "../history-overlay.js";
|
||||
import ModelOverlay from "../model-overlay.js";
|
||||
import { Box, Text } from "ink";
|
||||
import { exec } from "node:child_process";
|
||||
import OpenAI from "openai";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { inspect } from "util";
|
||||
|
||||
type Props = {
|
||||
@@ -126,6 +127,8 @@ export default function TerminalChat({
|
||||
additionalWritableRoots,
|
||||
fullStdout,
|
||||
}: Props): React.ReactElement {
|
||||
// Desktop notification setting
|
||||
const notify = config.notify;
|
||||
const [model, setModel] = useState<string>(config.model);
|
||||
const [lastResponseId, setLastResponseId] = useState<string | null>(null);
|
||||
const [items, setItems] = useState<Array<ResponseItem>>([]);
|
||||
@@ -284,6 +287,49 @@ export default function TerminalChat({
|
||||
};
|
||||
}, [loading, confirmationPrompt]);
|
||||
|
||||
// Notify desktop with a preview when an assistant response arrives
|
||||
const prevLoadingRef = useRef<boolean>(false);
|
||||
useEffect(() => {
|
||||
// Only notify when notifications are enabled
|
||||
if (!notify) {
|
||||
prevLoadingRef.current = loading;
|
||||
return;
|
||||
}
|
||||
if (
|
||||
prevLoadingRef.current &&
|
||||
!loading &&
|
||||
confirmationPrompt == null &&
|
||||
items.length > 0
|
||||
) {
|
||||
if (process.platform === "darwin") {
|
||||
// find the last assistant message
|
||||
const assistantMessages = items.filter(
|
||||
(i) => i.type === "message" && i.role === "assistant",
|
||||
);
|
||||
const last = assistantMessages[assistantMessages.length - 1];
|
||||
if (last) {
|
||||
const text = last.content
|
||||
.map((c) => {
|
||||
if (c.type === "output_text") {
|
||||
return c.text;
|
||||
}
|
||||
return "";
|
||||
})
|
||||
.join("")
|
||||
.trim();
|
||||
const preview = text.replace(/\n/g, " ").slice(0, 100);
|
||||
const safePreview = preview.replace(/"/g, '\\"');
|
||||
const title = "Codex CLI";
|
||||
const cwd = PWD;
|
||||
exec(
|
||||
`osascript -e 'display notification "${safePreview}" with title "${title}" subtitle "${cwd}" sound name "Ping"'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
prevLoadingRef.current = loading;
|
||||
}, [notify, loading, confirmationPrompt, items, PWD]);
|
||||
|
||||
// Let's also track whenever the ref becomes available
|
||||
const agent = agentRef.current;
|
||||
useEffect(() => {
|
||||
|
||||
@@ -49,6 +49,8 @@ export type StoredConfig = {
|
||||
approvalMode?: AutoApprovalMode;
|
||||
fullAutoErrorMode?: FullAutoErrorMode;
|
||||
memory?: MemoryConfig;
|
||||
/** Whether to enable desktop notifications for responses */
|
||||
notify?: boolean;
|
||||
history?: {
|
||||
maxSize?: number;
|
||||
saveHistory?: boolean;
|
||||
@@ -75,6 +77,8 @@ export type AppConfig = {
|
||||
instructions: string;
|
||||
fullAutoErrorMode?: FullAutoErrorMode;
|
||||
memory?: MemoryConfig;
|
||||
/** Whether to enable desktop notifications for responses */
|
||||
notify: boolean;
|
||||
history?: {
|
||||
maxSize: number;
|
||||
saveHistory: boolean;
|
||||
@@ -263,6 +267,7 @@ export const loadConfig = (
|
||||
? DEFAULT_FULL_CONTEXT_MODEL
|
||||
: DEFAULT_AGENTIC_MODEL),
|
||||
instructions: combinedInstructions,
|
||||
notify: storedConfig.notify === true,
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
@@ -322,6 +327,8 @@ export const loadConfig = (
|
||||
if (storedConfig.fullAutoErrorMode) {
|
||||
config.fullAutoErrorMode = storedConfig.fullAutoErrorMode;
|
||||
}
|
||||
// Notification setting: enable desktop notifications when set in config
|
||||
config.notify = storedConfig.notify === true;
|
||||
|
||||
// Add default history config if not provided
|
||||
if (storedConfig.history !== undefined) {
|
||||
|
||||
Reference in New Issue
Block a user