Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,14 @@ Unlike `opencode-notifier` and `opencode-notificator`, this plugin:

## [Unreleased]

### Added

- Support for additional Bark API parameters: subtitle, url, group, icon, sound, call, ciphertext, level
- Environment variable support for all new configuration options (DAY_APP_SUBTITLE, DAY_APP_URL, DAY_APP_GROUP, DAY_APP_ICON, DAY_APP_SOUND, DAY_APP_CALL, DAY_APP_CIPHERTEXT, DAY_APP_LEVEL)
- Backward compatibility for config file names (supports both opencode-notify.json and opencode-message-notify.json)

### Planned

- Support for custom notification sounds via Bark
- Batch notification mode for long sessions
- Usage limit alerts
- Multi-device support
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,14 @@ Create `~/.config/opencode/opencode-notify.json`:
{
"token": "your_bark_device_token",
"title": "OpenCode",
"subtitle": "Agent Task",
"url": "https://github.com/...",
"group": "opencode",
"icon": "https://example.com/icon.png",
"sound": "alarm",
"call": 1,
"ciphertext": "...",
"level": "timeSensitive",
"notifyOnComplete": true,
"notifyOnPermission": true,
"includeUsageStats": true,
Expand All @@ -121,6 +129,14 @@ Create `~/.config/opencode/opencode-notify.json`:
|--------|------|---------|-------------|
| `token` | string | (env) | Bark device token |
| `title` | string | `"OpenCode"` | Notification title prefix |
| `subtitle` | string | undefined | Notification subtitle (iOS path parameter) |
| `url` | string | undefined | URL to open when notification is clicked |
| `group` | string | undefined | Notification group name for grouping |
| `icon` | string | undefined | Custom notification icon URL (iOS 15+) |
| `sound` | string | undefined | Notification sound name |
| `call` | string/number | undefined | Repeat sound for 30 seconds |
| `ciphertext` | string | undefined | Encrypted message content |
| `level` | string | undefined | Notification level: active, timeSensitive, passive, critical |
| `notifyOnComplete` | boolean | `true` | Send notification on session completion |
| `notifyOnPermission` | boolean | `true` | Send notification for permission requests |
| `includeUsageStats` | boolean | `true` | Include cost and token statistics |
Expand Down
35 changes: 34 additions & 1 deletion src/bark.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,40 @@ export async function sendBarkNotification(

const encodedTitle = encodeURIComponent(title);
const encodedBody = encodeURIComponent(body.trim());
const url = `https://api.day.app/${config.token}/${encodedTitle}/${encodedBody}`;

// Build URL path
let path = `/${config.token}/${encodedTitle}`;
if (config.subtitle?.trim()) {
path += `/${encodeURIComponent(config.subtitle.trim())}`;
}
path += `/${encodedBody}`;

// Build query parameters
const params = new URLSearchParams();
if (config.url?.trim()) {
params.append("url", config.url.trim());
}
if (config.group?.trim()) {
params.append("group", config.group.trim());
}
if (config.icon?.trim()) {
params.append("icon", config.icon.trim());
}
if (config.sound?.trim()) {
params.append("sound", config.sound.trim());
}
if (config.call !== undefined && config.call !== null) {
params.append("call", String(config.call));
}
if (config.ciphertext?.trim()) {
params.append("ciphertext", config.ciphertext.trim());
}
if (config.level?.trim()) {
params.append("level", config.level.trim());
}

const queryString = params.toString();
const url = `https://api.day.app${path}${queryString ? `?${queryString}` : ""}`;

try {
await fetch(url);
Expand Down
50 changes: 41 additions & 9 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import * as fs from "fs";
import * as path from "path";
import type { PluginConfig } from "./types.js";

const CONFIG_FILE_NAME = "opencode-message-notify.json";
const CONFIG_FILE_NAMES = ["opencode-notify.json", "opencode-message-notify.json"];
const CONFIG_DIR = ".config/opencode";

/**
Expand All @@ -18,10 +18,17 @@ function getHomeDir(): string {

/**
* Get the configuration file path
* Tries new filename first, falls back to old filename
*/
function getConfigPath(): string {
function getConfigPath(): string | null {
const homeDir = getHomeDir();
return path.join(homeDir, CONFIG_DIR, CONFIG_FILE_NAME);
for (const fileName of CONFIG_FILE_NAMES) {
const configPath = path.join(homeDir, CONFIG_DIR, fileName);
if (fs.existsSync(configPath)) {
return configPath;
}
}
return null;
}

/**
Expand All @@ -41,21 +48,46 @@ export function loadConfig(): PluginConfig {
};

// Try to load from config file
if (fs.existsSync(configPath)) {
if (configPath) {
try {
const fileContent = fs.readFileSync(configPath, "utf-8");
const fileConfig = JSON.parse(fileContent);
return { ...defaultConfig, ...fileConfig };
} catch {
// If config file is invalid, use defaults
console.warn(`[opencode-message-notify] Invalid config file at ${configPath}`);
}
}

// Load from environment variable as fallback
const envToken = process.env.DAY_APP_TOKEN?.trim();
if (envToken) {
defaultConfig.token = envToken;
// Load from environment variables (env vars override defaults)
if (process.env.DAY_APP_TOKEN?.trim()) {
defaultConfig.token = process.env.DAY_APP_TOKEN.trim();
}
if (process.env.DAY_APP_TITLE?.trim()) {
defaultConfig.title = process.env.DAY_APP_TITLE.trim();
}
if (process.env.DAY_APP_SUBTITLE?.trim()) {
defaultConfig.subtitle = process.env.DAY_APP_SUBTITLE.trim();
}
if (process.env.DAY_APP_URL?.trim()) {
defaultConfig.url = process.env.DAY_APP_URL.trim();
}
if (process.env.DAY_APP_GROUP?.trim()) {
defaultConfig.group = process.env.DAY_APP_GROUP.trim();
}
if (process.env.DAY_APP_ICON?.trim()) {
defaultConfig.icon = process.env.DAY_APP_ICON.trim();
}
if (process.env.DAY_APP_SOUND?.trim()) {
defaultConfig.sound = process.env.DAY_APP_SOUND.trim();
}
if (process.env.DAY_APP_CALL?.trim()) {
defaultConfig.call = process.env.DAY_APP_CALL.trim();
}
if (process.env.DAY_APP_CIPHERTEXT?.trim()) {
defaultConfig.ciphertext = process.env.DAY_APP_CIPHERTEXT.trim();
}
if (process.env.DAY_APP_LEVEL?.trim()) {
defaultConfig.level = process.env.DAY_APP_LEVEL.trim() as 'active' | 'timeSensitive' | 'passive' | 'critical';
}

return defaultConfig;
Expand Down
16 changes: 16 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,22 @@ export interface PluginConfig {
token?: string;
/** Notification title template */
title?: string;
/** Notification subtitle (iOS path parameter) */
subtitle?: string;
/** URL to open when notification is clicked */
url?: string;
/** Notification group name for grouping */
group?: string;
/** Custom notification icon URL (iOS 15+) */
icon?: string;
/** Notification sound name */
sound?: string;
/** Repeat sound for 30 seconds */
call?: string | number;
/** Encrypted message content */
ciphertext?: string;
/** Notification level: active, timeSensitive, passive, critical */
level?: 'active' | 'timeSensitive' | 'passive' | 'critical';
/** Whether to send notifications on session completion */
notifyOnComplete?: boolean;
/** Whether to send notifications on permission requests */
Expand Down