@brantrusnak/openclaw-omadeus 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,107 @@
1
+ # OpenClaw Omadeus Plugin
2
+
3
+ Omadeus channel plugin for [OpenClaw](https://www.npmjs.com/package/openclaw).
4
+
5
+ This plugin connects OpenClaw to Omadeus over WebSocket so OpenClaw can listen
6
+ for Omadeus messages and reply through the selected Omadeus channel.
7
+
8
+ ## Requirements
9
+
10
+ - Node.js 22 or newer
11
+ - OpenClaw 2026.4.10 or newer
12
+ - An Omadeus account with access to the organization and channel you want
13
+ OpenClaw to use
14
+
15
+ ## Install
16
+
17
+ Install OpenClaw first:
18
+
19
+ ```bash
20
+ npm install -g openclaw
21
+ ```
22
+
23
+ Set up OpenClaw if you have not already:
24
+
25
+ ```bash
26
+ openclaw onboard
27
+ ```
28
+
29
+ Then install this plugin with the OpenClaw plugin command:
30
+
31
+ ```bash
32
+ openclaw plugins install @brantrusnak/openclaw-omadeus
33
+ ```
34
+
35
+ You can confirm OpenClaw discovered the plugin with:
36
+
37
+ ```bash
38
+ openclaw plugins list
39
+ openclaw plugins inspect omadeus
40
+ ```
41
+
42
+ ## Configure
43
+
44
+ After installing the plugin, run OpenClaw configuration and choose Omadeus when
45
+ prompted:
46
+
47
+ ```bash
48
+ openclaw configure
49
+ ```
50
+
51
+ The Omadeus setup flow asks for:
52
+
53
+ - Omadeus email and password
54
+ - Organization ID
55
+ - The Omadeus member/account to listen as
56
+ - The Omadeus channel to use for messages
57
+
58
+ The plugin also supports these environment variables:
59
+
60
+ ```bash
61
+ export OMADEUS_EMAIL="you@example.com"
62
+ export OMADEUS_PASSWORD="your-password"
63
+ export OMADEUS_ORGANIZATION_ID="123"
64
+ ```
65
+
66
+ The default Omadeus endpoints are:
67
+
68
+ - CAS: `https://dev1-cas.rouztech.com`
69
+ - Maestro: `https://dev3-maestro.rouztech.com`
70
+
71
+ If your Omadeus deployment uses different endpoints, configure them through the
72
+ OpenClaw setup flow or in your OpenClaw config under `channels.omadeus`.
73
+
74
+ ## Start OpenClaw
75
+
76
+ Once OpenClaw and the plugin are configured, start or restart the gateway:
77
+
78
+ ```bash
79
+ openclaw gateway
80
+ ```
81
+
82
+ Check channel health with:
83
+
84
+ ```bash
85
+ openclaw channels status --deep
86
+ openclaw plugins doctor
87
+ ```
88
+
89
+ ## Local Development
90
+
91
+ From this repository, you can link the local plugin into OpenClaw:
92
+
93
+ ```bash
94
+ openclaw plugins install . --link
95
+ ```
96
+
97
+ Before publishing, inspect the npm package contents:
98
+
99
+ ```bash
100
+ npm pack --dry-run
101
+ ```
102
+
103
+ Publish to npm:
104
+
105
+ ```bash
106
+ npm publish
107
+ ```
package/api.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./src/setup-core.js";
2
+ export * from "./src/setup-surface.js";
package/index.ts ADDED
@@ -0,0 +1,14 @@
1
+ import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
2
+ import { omadeusPlugin } from "./src/channel.js";
3
+ import { setOmadeusRuntime } from "./src/runtime.js";
4
+
5
+ export { omadeusPlugin } from "./src/channel.js";
6
+ export { setOmadeusRuntime } from "./src/runtime.js";
7
+
8
+ export default defineChannelPluginEntry({
9
+ id: "omadeus",
10
+ name: "Omadeus",
11
+ description: "Omadeus project management channel plugin",
12
+ plugin: omadeusPlugin,
13
+ setRuntime: setOmadeusRuntime,
14
+ });
@@ -0,0 +1,91 @@
1
+ {
2
+ "id": "omadeus",
3
+ "channels": ["omadeus"],
4
+ "configSchema": {
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "properties": {
8
+ "enabled": { "type": "boolean" },
9
+ "casUrl": { "type": "string" },
10
+ "maestroUrl": { "type": "string" },
11
+ "email": { "type": "string" },
12
+ "password": { "type": "string" },
13
+ "organizationId": { "type": "number" },
14
+ "sessionToken": { "type": "string" },
15
+ "selectedMemberReferenceId": { "type": "number" },
16
+ "selectedChannelViewId": { "type": "number" },
17
+ "selectedChannelTitle": { "type": "string" },
18
+ "selectedChannelPrivateRoomId": { "type": "number" },
19
+ "selectedChannelPublicRoomId": { "type": "number" }
20
+ }
21
+ },
22
+ "channelConfigs": {
23
+ "omadeus": {
24
+ "schema": {
25
+ "type": "object",
26
+ "additionalProperties": false,
27
+ "properties": {
28
+ "enabled": { "type": "boolean" },
29
+ "casUrl": { "type": "string" },
30
+ "maestroUrl": { "type": "string" },
31
+ "email": { "type": "string" },
32
+ "password": { "type": "string" },
33
+ "organizationId": { "type": "number" },
34
+ "sessionToken": { "type": "string" },
35
+ "selectedMemberReferenceId": { "type": "number" },
36
+ "selectedChannelViewId": { "type": "number" },
37
+ "selectedChannelTitle": { "type": "string" },
38
+ "selectedChannelPrivateRoomId": { "type": "number" },
39
+ "selectedChannelPublicRoomId": { "type": "number" }
40
+ }
41
+ },
42
+ "uiHints": {
43
+ "casUrl": {
44
+ "label": "CAS URL",
45
+ "placeholder": "https://dev1-cas.rouztech.com"
46
+ },
47
+ "maestroUrl": {
48
+ "label": "Maestro URL",
49
+ "placeholder": "https://dev3-maestro.rouztech.com"
50
+ },
51
+ "email": {
52
+ "label": "Email"
53
+ },
54
+ "password": {
55
+ "label": "Password",
56
+ "sensitive": true
57
+ },
58
+ "organizationId": {
59
+ "label": "Organization ID"
60
+ },
61
+ "sessionToken": {
62
+ "label": "Session token",
63
+ "sensitive": true,
64
+ "advanced": true
65
+ },
66
+ "selectedMemberReferenceId": {
67
+ "label": "Selected member reference ID",
68
+ "advanced": true
69
+ },
70
+ "selectedChannelViewId": {
71
+ "label": "Selected channel view ID",
72
+ "advanced": true
73
+ },
74
+ "selectedChannelTitle": {
75
+ "label": "Selected channel title",
76
+ "advanced": true
77
+ },
78
+ "selectedChannelPrivateRoomId": {
79
+ "label": "Selected private room ID",
80
+ "advanced": true
81
+ },
82
+ "selectedChannelPublicRoomId": {
83
+ "label": "Selected public room ID",
84
+ "advanced": true
85
+ }
86
+ },
87
+ "label": "Omadeus",
88
+ "description": "Omadeus project management channel configuration"
89
+ }
90
+ }
91
+ }
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@brantrusnak/openclaw-omadeus",
3
+ "version": "1.0.0",
4
+ "private": false,
5
+ "description": "OpenClaw Omadeus project management channel plugin",
6
+ "homepage": "https://github.com/brantrusnak/openclaw-omadeus-plugin#readme",
7
+ "bugs": {
8
+ "url": "https://github.com/brantrusnak/openclaw-omadeus-plugin/issues"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/brantrusnak/openclaw-omadeus-plugin.git"
13
+ },
14
+ "license": "ISC",
15
+ "author": "Brant Rusnak",
16
+ "type": "module",
17
+ "main": "./index.ts",
18
+ "scripts": {
19
+ "test": "vitest run",
20
+ "prepack": "node ./scripts/verify-npm-files.mjs"
21
+ },
22
+ "dependencies": {
23
+ "ws": "^8.20.0"
24
+ },
25
+ "files": [
26
+ "index.ts",
27
+ "setup-entry.ts",
28
+ "api.ts",
29
+ "runtime-api.ts",
30
+ "openclaw.plugin.json",
31
+ "src",
32
+ "!**/*.test.ts"
33
+ ],
34
+ "devDependencies": {
35
+ "openclaw": ">=2026.4.10",
36
+ "vitest": "^4.1.5"
37
+ },
38
+ "peerDependencies": {
39
+ "openclaw": ">=2026.4.10"
40
+ },
41
+ "peerDependenciesMeta": {
42
+ "openclaw": {
43
+ "optional": true
44
+ }
45
+ },
46
+ "publishConfig": {
47
+ "access": "public"
48
+ },
49
+ "openclaw": {
50
+ "extensions": [
51
+ "./index.ts"
52
+ ],
53
+ "setupEntry": "./setup-entry.ts",
54
+ "channel": {
55
+ "id": "omadeus",
56
+ "label": "Omadeus",
57
+ "selectionLabel": "Omadeus (WebSocket)",
58
+ "docsPath": "/channels/omadeus",
59
+ "docsLabel": "omadeus",
60
+ "blurb": "Omadeus project management — tasks, chat rooms, and sprints.",
61
+ "order": 70
62
+ },
63
+ "install": {
64
+ "localPath": ".",
65
+ "defaultChoice": "local"
66
+ }
67
+ }
68
+ }
package/runtime-api.ts ADDED
@@ -0,0 +1,37 @@
1
+ export { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/core";
2
+ export type {
3
+ ChannelPlugin,
4
+ OpenClawConfig,
5
+ PluginRuntime,
6
+ } from "openclaw/plugin-sdk/core";
7
+ export {
8
+ type ChannelStatusIssue,
9
+ createReplyPrefixContext,
10
+ } from "openclaw/plugin-sdk/channel-runtime";
11
+ export { logInboundDrop } from "openclaw/plugin-sdk/channel-inbound";
12
+ export { resolveControlCommandGate } from "openclaw/plugin-sdk/command-auth";
13
+ export { createChannelPairingController } from "openclaw/plugin-sdk/channel-pairing";
14
+ export {
15
+ readStoreAllowFromForDmPolicy,
16
+ resolveDmGroupAccessWithLists,
17
+ } from "openclaw/plugin-sdk/channel-policy";
18
+ export type { RuntimeEnv } from "openclaw/plugin-sdk/runtime";
19
+ export type { ReplyPayload } from "openclaw/plugin-sdk/reply-runtime";
20
+ export {
21
+ addWildcardAllowFrom,
22
+ formatDocsLink,
23
+ mergeAllowFromEntries,
24
+ } from "openclaw/plugin-sdk/setup";
25
+ export type {
26
+ ChannelSetupDmPolicy,
27
+ ChannelSetupWizard,
28
+ DmPolicy,
29
+ WizardPrompter,
30
+ } from "openclaw/plugin-sdk/setup";
31
+
32
+ export function missingTargetError(provider: string, hint?: string): Error {
33
+ const normalizedHint = hint?.trim();
34
+ return new Error(
35
+ `Delivering to ${provider} requires target${normalizedHint ? ` ${normalizedHint}` : ""}`,
36
+ );
37
+ }
package/setup-entry.ts ADDED
@@ -0,0 +1,4 @@
1
+ import { defineSetupPluginEntry } from "openclaw/plugin-sdk/core";
2
+ import { omadeusPlugin } from "./src/channel.js";
3
+
4
+ export default defineSetupPluginEntry(omadeusPlugin);
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Omadeus only accepts these exact reaction strings; any other value is ignored (no API call).
3
+ */
4
+ export const ALLOWED_OMADEUS_REACTION_EMOJI_LIST = [
5
+ "👍",
6
+ "👎",
7
+ "❤️",
8
+ "😂",
9
+ "😮",
10
+ "😢",
11
+ "🙏",
12
+ ] as const;
13
+
14
+ const ALLOWED = new Set<string>(ALLOWED_OMADEUS_REACTION_EMOJI_LIST);
15
+
16
+ export function isAllowedOmadeusReactionEmoji(raw: string): boolean {
17
+ const trimmed = raw.trim();
18
+ if (!trimmed) {
19
+ return false;
20
+ }
21
+ return ALLOWED.has(trimmed);
22
+ }
@@ -0,0 +1,189 @@
1
+ import { getCasSession, setCasSession } from "../store.js";
2
+ import type {
3
+ CasAuthorizationCodeResponse,
4
+ OmadeusChannelView,
5
+ OmadeusOrganizationMember,
6
+ OmadeusOrganization,
7
+ OmadeusSessionTokenResponse,
8
+ } from "../types.js";
9
+
10
+ const CAS_APPLICATION_ID = 1;
11
+ const CAS_SCOPES = "title,email,avatar,firstName,lastName,birth,phone,countryCode";
12
+
13
+ export async function createCasToken(params: {
14
+ casUrl: string;
15
+ email: string;
16
+ password: string;
17
+ }): Promise<{ token: string; refreshCookie: string }> {
18
+ const { casUrl, email, password } = params;
19
+ const url = `${casUrl}/apiv1/tokens`;
20
+ const jsonBody = JSON.stringify({ email, password });
21
+ const res = await fetch(url, {
22
+ method: "CREATE",
23
+ headers: {
24
+ "Content-Type": "application/json;charset=UTF-8",
25
+ "Content-Length": String(jsonBody.length),
26
+ },
27
+ body: jsonBody,
28
+ });
29
+ if (!res.ok) {
30
+ const text = await res.text().catch(() => "");
31
+ throw new Error(`CAS token request failed (${res.status}): ${text}`);
32
+ }
33
+ const body = (await res.json()) as { token: string };
34
+ const refreshCookie = res.headers.get("set-cookie") ?? "";
35
+
36
+ setCasSession({ token: body.token, refreshCookie });
37
+
38
+ return { token: body.token, refreshCookie };
39
+ }
40
+
41
+ export async function getMe(params: {
42
+ casUrl: string;
43
+ casToken: string;
44
+ refreshCookie?: string;
45
+ }): Promise<{ email: string }> {
46
+ const { casUrl, casToken, refreshCookie } = params;
47
+ const url = `${casUrl}/apiv1/members/me`;
48
+ const res = await fetch(url, {
49
+ method: "GET",
50
+ headers: {
51
+ Authorization: `Bearer ${casToken}`,
52
+ "Content-Type": "application/json;charset=UTF-8",
53
+ ...(refreshCookie ? { Cookie: refreshCookie } : {}),
54
+ },
55
+ });
56
+ if (!res.ok) {
57
+ const text = await res.text().catch(() => "");
58
+ throw new Error(`CAS get member failed (${res.status}): ${text}`);
59
+ }
60
+ return (await res.json()) as { email: string };
61
+ }
62
+
63
+ export async function createAuthorizationCode(params: {
64
+ casUrl: string;
65
+ token: string;
66
+ email: string;
67
+ redirectUri?: string;
68
+ }): Promise<string> {
69
+ const { casUrl, token, email, redirectUri } = params;
70
+ const casSession = getCasSession();
71
+ const qs = new URLSearchParams({
72
+ applicationId: String(CAS_APPLICATION_ID),
73
+ scopes: CAS_SCOPES,
74
+ state: email,
75
+ redirectUri: redirectUri ?? "",
76
+ });
77
+ if (redirectUri) qs.set("redirectUri", redirectUri);
78
+ const url = `${casUrl}/apiv1/authorizationcodes?${qs}`;
79
+ const body = "";
80
+ const headers: Record<string, string> = {
81
+ Authorization: `Bearer ${token}`,
82
+ ...(casSession?.refreshCookie ? { Cookie: casSession.refreshCookie } : {}),
83
+ };
84
+ const res = await fetch(url, {
85
+ method: "CREATE",
86
+ body,
87
+ headers,
88
+ });
89
+ if (!res.ok) {
90
+ const text = await res.text().catch(() => "");
91
+ throw new Error(`CAS authorization code request failed (${res.status}): ${text}`);
92
+ }
93
+ const jsonResponse = (await res.json()) as CasAuthorizationCodeResponse;
94
+ const code = jsonResponse.authorizationCode ?? jsonResponse.code;
95
+ if (!code) {
96
+ throw new Error("CAS authorization code response missing code");
97
+ }
98
+ return code;
99
+ }
100
+
101
+ export async function obtainSessionToken(params: {
102
+ maestroUrl: string;
103
+ authorizationCode: string;
104
+ organizationId: number;
105
+ }): Promise<string> {
106
+ const { maestroUrl, authorizationCode, organizationId } = params;
107
+ const url = `${maestroUrl}/dolphin/apiv1/oauth2/tokens`;
108
+ const res = await fetch(url, {
109
+ method: "OBTAIN",
110
+ headers: { "Content-Type": "application/json;charset=UTF-8" },
111
+ body: JSON.stringify({ authorizationCode, organizationId }),
112
+ });
113
+ if (!res.ok) {
114
+ const text = await res.text().catch(() => "");
115
+ throw new Error(`Omadeus session token request failed (${res.status}): ${text}`);
116
+ }
117
+ const body = (await res.json()) as OmadeusSessionTokenResponse;
118
+ if (!body.token) {
119
+ throw new Error("Omadeus session token response missing token");
120
+ }
121
+ return body.token;
122
+ }
123
+
124
+ export async function listOrganizations(params: {
125
+ maestroUrl: string;
126
+ email: string;
127
+ }): Promise<OmadeusOrganization[]> {
128
+ const { maestroUrl, email } = params;
129
+ const url = `${maestroUrl}/dolphin/apiv1/organizations`;
130
+ const res = await fetch(url, {
131
+ method: "LIST",
132
+ headers: { "Content-Type": "application/json;charset=UTF-8" },
133
+ body: JSON.stringify({ email }),
134
+ });
135
+ if (!res.ok) {
136
+ const text = await res.text().catch(() => "");
137
+ throw new Error(`Omadeus list organizations failed (${res.status}): ${text}`);
138
+ }
139
+ return (await res.json()) as OmadeusOrganization[];
140
+ }
141
+
142
+ export async function listMemberChannelViews(params: {
143
+ maestroUrl: string;
144
+ sessionToken: string;
145
+ memberReferenceId: number;
146
+ skip?: number;
147
+ take?: number;
148
+ }): Promise<OmadeusChannelView[]> {
149
+ const { maestroUrl, sessionToken, memberReferenceId, skip = 0, take = 100 } = params;
150
+ const qs = new URLSearchParams({
151
+ skip: String(skip),
152
+ take: String(take),
153
+ sort: "-recentMessageAt",
154
+ });
155
+ const url = `${maestroUrl}/jaguar/apiv1/members/${memberReferenceId}/channelviews?${qs.toString()}`;
156
+ const res = await fetch(url, {
157
+ method: "LIST",
158
+ headers: {
159
+ Authorization: `Bearer ${sessionToken}`,
160
+ "Content-Type": "application/json;charset=UTF-8",
161
+ },
162
+ });
163
+ if (!res.ok) {
164
+ const text = await res.text().catch(() => "");
165
+ throw new Error(`Omadeus list channel views failed (${res.status}): ${text}`);
166
+ }
167
+ return (await res.json()) as OmadeusChannelView[];
168
+ }
169
+
170
+ export async function listOrganizationMembers(params: {
171
+ maestroUrl: string;
172
+ sessionToken: string;
173
+ organizationId: number;
174
+ }): Promise<OmadeusOrganizationMember[]> {
175
+ const { maestroUrl, sessionToken, organizationId } = params;
176
+ const url = `${maestroUrl}/dolphin/apiv1/organizations/${organizationId}/members`;
177
+ const res = await fetch(url, {
178
+ method: "LIST",
179
+ headers: {
180
+ Authorization: `Bearer ${sessionToken}`,
181
+ "Content-Type": "application/json;charset=UTF-8",
182
+ },
183
+ });
184
+ if (!res.ok) {
185
+ const text = await res.text().catch(() => "");
186
+ throw new Error(`Omadeus list organization members failed (${res.status}): ${text}`);
187
+ }
188
+ return (await res.json()) as OmadeusOrganizationMember[];
189
+ }