Skip to content

Commit 93c9a89

Browse files
authored
feat: config system (#2)
1 parent 9b5cafd commit 93c9a89

12 files changed

Lines changed: 389 additions & 29 deletions

File tree

.github/workflows/release.yml

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,23 @@ jobs:
1919
with:
2020
bun-version: latest
2121

22-
- name: Install dependencies
23-
run: bun install
24-
25-
- name: Lint
26-
run: bun run lint
22+
- uses: actions/setup-node@v4
23+
with:
24+
node-version: "24"
25+
registry-url: "https://registry.npmjs.org"
2726

28-
- name: Build
29-
run: bun run build
3027
- name: Prevent duplicate publish
3128
run: |
3229
if npm view opencode-notification@$(node -p "require('./package.json').version") version; then
3330
echo "Version already published"
3431
exit 1
3532
fi
3633
37-
- uses: actions/setup-node@v4
38-
with:
39-
node-version: "24"
40-
registry-url: "https://registry.npmjs.org"
34+
- name: Install dependencies
35+
run: bun install
36+
37+
- name: Lint
38+
run: bun run lint
4139

4240
- name: Publish to npm
4341
run: npm publish --access public

bun.lock

Lines changed: 4 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

index.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,25 @@
11
import path from "node:path";
22
import type { Plugin } from "@opencode-ai/plugin";
33
import { createNotificationScheduler } from "@/notification-scheduler";
4+
import { loadConfig, ConfigError } from "@/config/loader";
5+
import { createResolvedConfig } from "@/config/resolver";
46

57
export const SimpleNotificationPlugin: Plugin = async ({ client }) => {
6-
const scheduler = createNotificationScheduler();
8+
const config = await loadConfig().catch(async (error) => {
9+
const message =
10+
error instanceof ConfigError
11+
? `Notification plugin config error: ${error.message}`
12+
: `Notification plugin failed to load: ${error instanceof Error ? error.message : String(error)}`;
13+
14+
await client.tui.showToast({
15+
body: { message, variant: "error" },
16+
});
17+
18+
throw error;
19+
});
20+
21+
const resolvedConfig = createResolvedConfig(config);
22+
const scheduler = createNotificationScheduler(resolvedConfig);
723
// Tracks sessions where assistant has responded since last user message
824
const activeSessions = new Set<string>();
925

@@ -22,7 +38,7 @@ export const SimpleNotificationPlugin: Plugin = async ({ client }) => {
2238
.get({ path: { id: sessionId } })
2339
.then((details) => details.data?.title)
2440
.catch(() => undefined);
25-
scheduler.schedule(sessionId, "Response ready", title ?? sessionId);
41+
scheduler.schedule(sessionId, "Response ready", title ?? sessionId, "session.idle");
2642
}
2743
activeSessions.delete(sessionId);
2844
break;
@@ -37,7 +53,7 @@ export const SimpleNotificationPlugin: Plugin = async ({ client }) => {
3753
.catch(() => undefined)
3854
: (event.properties.error?.data.message as string);
3955
if (sessionId) {
40-
scheduler.schedule(sessionId, "Session error", message ?? sessionId);
56+
scheduler.schedule(sessionId, "Session error", message ?? sessionId, "session.error");
4157
}
4258
break;
4359
}
@@ -58,6 +74,7 @@ export const SimpleNotificationPlugin: Plugin = async ({ client }) => {
5874
sessionId,
5975
"Permission Asked",
6076
`${session?.title} in ${projectName} needs permission`,
77+
"permission.asked",
6178
);
6279
break;
6380
}
@@ -78,6 +95,7 @@ export const SimpleNotificationPlugin: Plugin = async ({ client }) => {
7895
sessionId,
7996
"Question",
8097
`${session?.title} in ${projectName} has a question`,
98+
"question.asked",
8199
);
82100
break;
83101
}
@@ -95,8 +113,6 @@ export const SimpleNotificationPlugin: Plugin = async ({ client }) => {
95113
case "message.updated": {
96114
const info = event.properties.info;
97115
if (info.role === "user") {
98-
// Only cancel for real user messages, not automatic system messages
99-
// System messages have 'agent' or 'model' fields
100116
const infoAny = info;
101117
const isAutomaticMessage = infoAny.agent || infoAny.model;
102118
if (!isAutomaticMessage) {
@@ -119,7 +135,6 @@ export const SimpleNotificationPlugin: Plugin = async ({ client }) => {
119135

120136
case "tui.prompt.append":
121137
case "tui.command.execute":
122-
// No sessionID in these events, can't cancel reliably
123138
break;
124139
}
125140
},

package.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"url": "git+https://github.com/IdrisGit/opencode-notification.git"
1616
},
1717
"files": [
18-
"dist"
18+
"dist",
19+
"schema"
1920
],
2021
"type": "module",
2122
"main": "dist/index.js",
@@ -28,14 +29,17 @@
2829
}
2930
},
3031
"scripts": {
31-
"build": "bun run clean && bun build index.ts --outdir dist --target bun --format esm && bunx tsc -p tsconfig.build.json --emitDeclarationOnly",
32+
"prepack": "bun run build",
33+
"build": "bun run clean && bun run generate-schema && bun build index.ts --outdir dist --target bun --format esm && bunx tsc -p tsconfig.build.json --emitDeclarationOnly",
34+
"generate-schema": "bun scripts/generate-schema.ts",
3235
"clean": "rm -rf dist",
3336
"lint": "oxlint .",
3437
"fmt": "oxfmt",
3538
"fmt:check": "oxfmt --check"
3639
},
3740
"dependencies": {
38-
"@opencode-ai/plugin": "^1.2.16"
41+
"@opencode-ai/plugin": "^1.2.16",
42+
"zod": "^4.3.6"
3943
},
4044
"devDependencies": {
4145
"@types/bun": "latest",

schema/oc-notification.json

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
{
2+
"$id": "https://unpkg.com/opencode-notification@0.0.3/schema/oc-notification.json",
3+
"$schema": "https://json-schema.org/draft/2020-12/schema",
4+
"type": "object",
5+
"properties": {
6+
"delay": {
7+
"default": 15,
8+
"description": "Default notification delay in seconds",
9+
"type": "number"
10+
},
11+
"enabled": {
12+
"default": true,
13+
"description": "Master switch to enable/disable all notifications",
14+
"type": "boolean"
15+
},
16+
"response_ready": {
17+
"default": {
18+
"enabled": true
19+
},
20+
"description": "Notification when AI response is ready",
21+
"type": "object",
22+
"properties": {
23+
"enabled": {
24+
"default": true,
25+
"description": "Enable notifications for this event type",
26+
"type": "boolean"
27+
},
28+
"delay": {
29+
"description": "Optional override for notification delay in seconds",
30+
"type": "number"
31+
}
32+
}
33+
},
34+
"error": {
35+
"default": {
36+
"enabled": true
37+
},
38+
"description": "Notification on session error",
39+
"type": "object",
40+
"properties": {
41+
"enabled": {
42+
"default": true,
43+
"description": "Enable notifications for this event type",
44+
"type": "boolean"
45+
},
46+
"delay": {
47+
"description": "Optional override for notification delay in seconds",
48+
"type": "number"
49+
}
50+
}
51+
},
52+
"permission_asked": {
53+
"default": {
54+
"enabled": true
55+
},
56+
"description": "Notification when permission is requested",
57+
"type": "object",
58+
"properties": {
59+
"enabled": {
60+
"default": true,
61+
"description": "Enable notifications for this event type",
62+
"type": "boolean"
63+
},
64+
"delay": {
65+
"description": "Optional override for notification delay in seconds",
66+
"type": "number"
67+
}
68+
}
69+
},
70+
"question_asked": {
71+
"default": {
72+
"enabled": true
73+
},
74+
"description": "Notification when a question is asked",
75+
"type": "object",
76+
"properties": {
77+
"enabled": {
78+
"default": true,
79+
"description": "Enable notifications for this event type",
80+
"type": "boolean"
81+
},
82+
"delay": {
83+
"description": "Optional override for notification delay in seconds",
84+
"type": "number"
85+
}
86+
}
87+
},
88+
"$schema": {
89+
"type": "string",
90+
"description": "JSON Schema URL for editor autocomplete and validation"
91+
}
92+
}
93+
}

scripts/generate-schema.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import * as z from "zod";
2+
import { CONFIG_FILE_NAME } from "@/config/constants";
3+
import { ConfigSchema } from "@/config/schema";
4+
5+
// Read package.json to get version for schema $id
6+
const packageJsonPath = `${import.meta.dir}/../package.json`;
7+
const packageJson = await Bun.file(packageJsonPath).json();
8+
const version = packageJson.version;
9+
10+
const jsonSchema = z.toJSONSchema(ConfigSchema, {
11+
io: "input",
12+
target: "draft-2020-12",
13+
unrepresentable: "throw",
14+
});
15+
const rootSchema = jsonSchema as z.core.JSONSchema.ObjectSchema;
16+
rootSchema.properties ??= {};
17+
rootSchema.properties.$schema = {
18+
type: "string",
19+
description: "JSON Schema URL for editor autocomplete and validation",
20+
};
21+
22+
// Add $id with versioned URL for npm/unpkg CDN
23+
// This allows users to reference specific schema versions
24+
const schemaWithId = {
25+
$id: `https://unpkg.com/opencode-notification@${version}/schema/${CONFIG_FILE_NAME}`,
26+
...jsonSchema,
27+
};
28+
29+
const outputPath = `${import.meta.dir}/../schema/${CONFIG_FILE_NAME}`;
30+
await Bun.write(outputPath, JSON.stringify(schemaWithId, null, 2));
31+
32+
console.log(`Generated JSON schema: schema/${CONFIG_FILE_NAME} (version ${version})`);

src/config/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const CONFIG_FILE_NAME = "oc-notification.json";

0 commit comments

Comments
 (0)