@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.
@@ -0,0 +1,241 @@
1
+ import type { OmadeusMessage } from "../types.js";
2
+ import { jaguarFetch, generateTemporaryId, type OmadeusApiOptions } from "../utils/http.util.js";
3
+
4
+ async function readJsonOrEmpty(res: Response): Promise<unknown> {
5
+ if (res.status === 204) {
6
+ return undefined;
7
+ }
8
+ const text = await res.text();
9
+ const trimmed = text.trim();
10
+ if (!trimmed) {
11
+ return undefined;
12
+ }
13
+ try {
14
+ return JSON.parse(trimmed) as unknown;
15
+ } catch {
16
+ return trimmed;
17
+ }
18
+ }
19
+
20
+ export async function sendRoomMessage(
21
+ opts: OmadeusApiOptions,
22
+ params: { roomId: number | string; body: string },
23
+ ): Promise<{ ok: boolean; message?: OmadeusMessage; error?: string }> {
24
+ try {
25
+ const res = await jaguarFetch(opts, `/rooms/${params.roomId}/messages`, {
26
+ method: "SEND",
27
+ body: JSON.stringify({
28
+ body: params.body,
29
+ temporaryId: generateTemporaryId(),
30
+ links: "[]",
31
+ }),
32
+ });
33
+ if (!res.ok) {
34
+ const text = await res.text().catch(() => "");
35
+ return { ok: false, error: `${res.status}: ${text.slice(0, 200)}` };
36
+ }
37
+ const data = (await res.json()) as OmadeusMessage;
38
+ return { ok: true, message: data };
39
+ } catch (err) {
40
+ const message = err instanceof Error ? err.message : String(err);
41
+ return { ok: false, error: message.slice(0, 300) };
42
+ }
43
+ }
44
+
45
+ export async function listRoomMessages(
46
+ opts: OmadeusApiOptions,
47
+ params: { roomId: number | string; skip?: number; take?: number; sort?: string },
48
+ ): Promise<OmadeusMessage[]> {
49
+ const search = new URLSearchParams();
50
+ if (params.sort) search.set("sort", params.sort);
51
+ if (typeof params.skip === "number") search.set("skip", String(params.skip));
52
+ if (typeof params.take === "number") search.set("take", String(params.take));
53
+ const qs = search.toString();
54
+
55
+ const res = await jaguarFetch(opts, `/rooms/${params.roomId}/messageviews${qs ? `?${qs}` : ""}`, {
56
+ method: "LIST",
57
+ });
58
+ if (!res.ok) {
59
+ const text = await res.text().catch(() => "");
60
+ throw new Error(`Omadeus list room messages failed (${res.status}): ${text.slice(0, 200)}`);
61
+ }
62
+ return (await res.json()) as OmadeusMessage[];
63
+ }
64
+
65
+ export async function seeMessage(
66
+ opts: OmadeusApiOptions,
67
+ params: { messageId: number | string },
68
+ ): Promise<OmadeusMessage> {
69
+ const res = await jaguarFetch(opts, `/messages/${params.messageId}`, {
70
+ method: "SEE",
71
+ });
72
+ if (!res.ok) {
73
+ const text = await res.text().catch(() => "");
74
+ throw new Error(`Omadeus see message failed (${res.status}): ${text.slice(0, 200)}`);
75
+ }
76
+ return (await res.json()) as OmadeusMessage;
77
+ }
78
+
79
+ export async function getMessageById(
80
+ opts: OmadeusApiOptions,
81
+ params: { messageId: number | string },
82
+ ): Promise<OmadeusMessage> {
83
+ const res = await jaguarFetch(opts, `/messages/${params.messageId}`, {
84
+ method: "GET",
85
+ });
86
+ if (!res.ok) {
87
+ const text = await res.text().catch(() => "");
88
+ throw new Error(`Omadeus get message failed (${res.status}): ${text.slice(0, 200)}`);
89
+ }
90
+ return (await res.json()) as OmadeusMessage;
91
+ }
92
+
93
+ export type OmadeusMessageLink = { title: string; url: string };
94
+
95
+ export async function replyToMessage(
96
+ opts: OmadeusApiOptions,
97
+ params: {
98
+ messageId: number | string;
99
+ body: string;
100
+ temporaryId?: string;
101
+ links?: OmadeusMessageLink[];
102
+ },
103
+ ): Promise<OmadeusMessage> {
104
+ const res = await jaguarFetch(opts, `/messages/${params.messageId}`, {
105
+ method: "REPLY",
106
+ body: JSON.stringify({
107
+ body: params.body,
108
+ temporaryId: params.temporaryId ?? generateTemporaryId(),
109
+ links: JSON.stringify(params.links ?? []),
110
+ }),
111
+ });
112
+ if (!res.ok) {
113
+ const text = await res.text().catch(() => "");
114
+ throw new Error(`Omadeus reply message failed (${res.status}): ${text.slice(0, 200)}`);
115
+ }
116
+ return (await res.json()) as OmadeusMessage;
117
+ }
118
+
119
+ export async function editMessage(
120
+ opts: OmadeusApiOptions,
121
+ params: {
122
+ messageId: number | string;
123
+ body: string;
124
+ temporaryId?: string;
125
+ links?: OmadeusMessageLink[];
126
+ },
127
+ ): Promise<OmadeusMessage> {
128
+ const res = await jaguarFetch(opts, `/messages/${params.messageId}`, {
129
+ method: "EDIT",
130
+ body: JSON.stringify({
131
+ body: params.body,
132
+ ...(params.temporaryId ? { temporaryId: params.temporaryId } : {}),
133
+ ...(params.links ? { links: JSON.stringify(params.links) } : {}),
134
+ }),
135
+ });
136
+ if (!res.ok) {
137
+ const text = await res.text().catch(() => "");
138
+ throw new Error(`Omadeus edit message failed (${res.status}): ${text.slice(0, 200)}`);
139
+ }
140
+ return (await res.json()) as OmadeusMessage;
141
+ }
142
+
143
+ export async function deleteMessage(
144
+ opts: OmadeusApiOptions,
145
+ params: { messageId: number | string },
146
+ ): Promise<OmadeusMessage> {
147
+ const res = await jaguarFetch(opts, `/messages/${params.messageId}`, {
148
+ method: "DELETE",
149
+ });
150
+ if (!res.ok) {
151
+ const text = await res.text().catch(() => "");
152
+ throw new Error(`Omadeus delete message failed (${res.status}): ${text.slice(0, 200)}`);
153
+ }
154
+ return (await res.json()) as OmadeusMessage;
155
+ }
156
+
157
+ export async function pinMessage(
158
+ opts: OmadeusApiOptions,
159
+ params: { messageId: number | string },
160
+ ): Promise<OmadeusMessage> {
161
+ const res = await jaguarFetch(opts, `/messages/${params.messageId}`, {
162
+ method: "PIN",
163
+ });
164
+ if (!res.ok) {
165
+ const text = await res.text().catch(() => "");
166
+ throw new Error(`Omadeus pin message failed (${res.status}): ${text.slice(0, 200)}`);
167
+ }
168
+ return (await res.json()) as OmadeusMessage;
169
+ }
170
+
171
+ export async function unpinMessage(
172
+ opts: OmadeusApiOptions,
173
+ params: { messageId: number | string },
174
+ ): Promise<OmadeusMessage> {
175
+ const res = await jaguarFetch(opts, `/messages/${params.messageId}`, {
176
+ method: "UNPIN",
177
+ });
178
+ if (!res.ok) {
179
+ const text = await res.text().catch(() => "");
180
+ throw new Error(`Omadeus unpin message failed (${res.status}): ${text.slice(0, 200)}`);
181
+ }
182
+ return (await res.json()) as OmadeusMessage;
183
+ }
184
+
185
+ export async function listMessageViewers(
186
+ opts: OmadeusApiOptions,
187
+ params: { messageId: number | string },
188
+ ): Promise<unknown[]> {
189
+ const res = await jaguarFetch(opts, `/messages/${params.messageId}/seen/members`, {
190
+ method: "LIST",
191
+ });
192
+ if (!res.ok) {
193
+ const text = await res.text().catch(() => "");
194
+ throw new Error(`Omadeus list message viewers failed (${res.status}): ${text.slice(0, 200)}`);
195
+ }
196
+ return (await res.json()) as unknown[];
197
+ }
198
+
199
+ export async function addMessageReaction(
200
+ opts: OmadeusApiOptions,
201
+ params: { messageId: number | string; emoji: string },
202
+ ): Promise<unknown> {
203
+ const res = await jaguarFetch(opts, `/messages/${params.messageId}/reactions`, {
204
+ method: "ADD",
205
+ body: JSON.stringify({ emoji: params.emoji }),
206
+ });
207
+ if (!res.ok) {
208
+ const text = await res.text().catch(() => "");
209
+ throw new Error(`Omadeus add reaction failed (${res.status}): ${text.slice(0, 200)}`);
210
+ }
211
+ // Success is often 204 No Content with no body.
212
+ return readJsonOrEmpty(res);
213
+ }
214
+
215
+ export async function listMessageReactions(
216
+ opts: OmadeusApiOptions,
217
+ params: { messageId: number | string },
218
+ ): Promise<unknown[]> {
219
+ const res = await jaguarFetch(opts, `/messages/${params.messageId}/reactions`, {
220
+ method: "LIST",
221
+ });
222
+ if (!res.ok) {
223
+ const text = await res.text().catch(() => "");
224
+ throw new Error(`Omadeus list reactions failed (${res.status}): ${text.slice(0, 200)}`);
225
+ }
226
+ return (await res.json()) as unknown[];
227
+ }
228
+
229
+ export async function removeMessageReactions(
230
+ opts: OmadeusApiOptions,
231
+ params: { messageId: number | string },
232
+ ): Promise<unknown> {
233
+ const res = await jaguarFetch(opts, `/messages/${params.messageId}/reactions`, {
234
+ method: "REMOVE",
235
+ });
236
+ if (!res.ok) {
237
+ const text = await res.text().catch(() => "");
238
+ throw new Error(`Omadeus remove reactions failed (${res.status}): ${text.slice(0, 200)}`);
239
+ }
240
+ return readJsonOrEmpty(res);
241
+ }
@@ -0,0 +1,148 @@
1
+ import { dolphinFetch, type OmadeusApiOptions } from "../utils/http.util.js";
2
+
3
+ type NuggetSearchParams = {
4
+ /** Display nugget id from user text (e.g. N111 → 111). Matches API field `number`, not internal `id`. */
5
+ nuggetNumber: number;
6
+ signal?: AbortSignal;
7
+ };
8
+
9
+ export type OmadeusNuggetPriority = "low" | "medium" | "high" | "urgent";
10
+ export type OmadeusNuggetKind = "task" | "nugget";
11
+
12
+ export type CreateNuggetParams = {
13
+ title: string;
14
+ description: string;
15
+ stage: string;
16
+ kind: OmadeusNuggetKind;
17
+ priority: OmadeusNuggetPriority;
18
+ memberReferenceId: number;
19
+ clientId: number;
20
+ folderId: number;
21
+ signal?: AbortSignal;
22
+ };
23
+
24
+ /** Omadeus nugget/task display number (`N###` in UI maps to this field). */
25
+ export function readNuggetNumber(record: Record<string, unknown>): number | undefined {
26
+ const value = record["number"];
27
+ if (typeof value === "number" && Number.isFinite(value)) {
28
+ return value;
29
+ }
30
+ if (typeof value === "string" && /^\d+$/.test(value.trim())) {
31
+ return Number(value.trim());
32
+ }
33
+ return undefined;
34
+ }
35
+
36
+ function readNumberField(
37
+ record: Record<string, unknown>,
38
+ key: string,
39
+ ): number | undefined {
40
+ const value = record[key];
41
+ if (typeof value === "number" && Number.isFinite(value)) {
42
+ return value;
43
+ }
44
+ if (typeof value === "string" && /^\d+$/.test(value.trim())) {
45
+ return Number(value.trim());
46
+ }
47
+ return undefined;
48
+ }
49
+
50
+ export function findNuggetRowByNumber(
51
+ rows: Record<string, unknown>[],
52
+ nuggetNumber: number,
53
+ ): Record<string, unknown> | undefined {
54
+ return rows.find((row) => readNuggetNumber(row) === nuggetNumber);
55
+ }
56
+
57
+ export function resolveTaskChannelRoomId(record: Record<string, unknown>): number | undefined {
58
+ return (
59
+ readNumberField(record, "privateRoomId") ??
60
+ readNumberField(record, "publicRoomId") ??
61
+ readNumberField(record, "sharedRoomId")
62
+ );
63
+ }
64
+
65
+ function extractRows(payload: unknown): Record<string, unknown>[] {
66
+ if (Array.isArray(payload)) {
67
+ return payload.filter(
68
+ (entry): entry is Record<string, unknown> => !!entry && typeof entry === "object",
69
+ );
70
+ }
71
+ if (!payload || typeof payload !== "object") {
72
+ return [];
73
+ }
74
+ const envelope = payload as Record<string, unknown>;
75
+ const candidateKeys = ["data", "results", "items", "rows"];
76
+ for (const key of candidateKeys) {
77
+ const value = envelope[key];
78
+ if (Array.isArray(value)) {
79
+ return value.filter(
80
+ (entry): entry is Record<string, unknown> => !!entry && typeof entry === "object",
81
+ );
82
+ }
83
+ }
84
+ return [];
85
+ }
86
+
87
+ /**
88
+ * Dolphin SEARCH on nuggetviews returns an array of nugget/task rows.
89
+ * User-facing `N111` corresponds to `number: 111` on each row (not `id`).
90
+ */
91
+ export async function searchNuggetByNumber(
92
+ opts: OmadeusApiOptions,
93
+ params: NuggetSearchParams,
94
+ ): Promise<Record<string, unknown> | null> {
95
+ const query = `N${params.nuggetNumber}`;
96
+ const search = new URLSearchParams();
97
+ search.set("take", "100");
98
+
99
+ const res = await dolphinFetch(opts, `/nuggetviews?${search.toString()}`, {
100
+ method: "SEARCH",
101
+ body: JSON.stringify({ query }),
102
+ signal: params.signal,
103
+ });
104
+ if (!res.ok) {
105
+ const text = await res.text().catch(() => "");
106
+ throw new Error(`Omadeus nugget search failed (${res.status}): ${text.slice(0, 200)}`);
107
+ }
108
+ const payload = (await res.json()) as unknown;
109
+ const rows = extractRows(payload);
110
+ const match = findNuggetRowByNumber(rows, params.nuggetNumber);
111
+ return match ?? null;
112
+ }
113
+
114
+ export async function resolveTaskRoomIdByNumber(
115
+ opts: OmadeusApiOptions,
116
+ params: NuggetSearchParams,
117
+ ): Promise<number | null> {
118
+ const row = await searchNuggetByNumber(opts, params);
119
+ if (!row) {
120
+ return null;
121
+ }
122
+ return resolveTaskChannelRoomId(row) ?? null;
123
+ }
124
+
125
+ export async function createNugget(
126
+ opts: OmadeusApiOptions,
127
+ params: CreateNuggetParams,
128
+ ): Promise<Record<string, unknown>> {
129
+ const res = await dolphinFetch(opts, "/nuggets", {
130
+ method: "CREATE",
131
+ body: JSON.stringify({
132
+ title: params.title,
133
+ stage: params.stage,
134
+ description: params.description,
135
+ kind: params.kind,
136
+ priority: params.priority,
137
+ memberReferenceId: params.memberReferenceId,
138
+ clientId: params.clientId,
139
+ folderId: params.folderId,
140
+ }),
141
+ signal: params.signal,
142
+ });
143
+ if (!res.ok) {
144
+ const text = await res.text().catch(() => "");
145
+ throw new Error(`Omadeus nugget create failed (${res.status}): ${text.slice(0, 200)}`);
146
+ }
147
+ return (await res.json()) as Record<string, unknown>;
148
+ }
package/src/auth.ts ADDED
@@ -0,0 +1,33 @@
1
+ import { createAuthorizationCode, createCasToken, obtainSessionToken } from "./api/auth.api.js";
2
+ import { clearCasSession } from "./store.js";
3
+ import type { OmadeusJwtPayload } from "./types.js";
4
+ import { decodeJwtPayload } from "./utils/jwt.util.js";
5
+
6
+ export async function authenticate(params: {
7
+ casUrl: string;
8
+ maestroUrl: string;
9
+ email: string;
10
+ password: string;
11
+ organizationId: number;
12
+ }): Promise<{ dolphinToken: string; payload: OmadeusJwtPayload }> {
13
+ const { casUrl, maestroUrl, email, password, organizationId } = params;
14
+ const { token } = await createCasToken({ casUrl, email, password });
15
+
16
+ const authorizationCode = await createAuthorizationCode({
17
+ casUrl,
18
+ token,
19
+ email,
20
+ redirectUri: maestroUrl,
21
+ });
22
+ // CAS session no longer needed after obtaining the authorization code
23
+ clearCasSession();
24
+
25
+ const dolphinToken = await obtainSessionToken({
26
+ maestroUrl,
27
+ authorizationCode,
28
+ organizationId,
29
+ });
30
+ const payload = decodeJwtPayload(dolphinToken);
31
+
32
+ return { dolphinToken, payload };
33
+ }