@brantrusnak/openclaw-omadeus 1.0.2 → 1.0.4

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.
Files changed (41) hide show
  1. package/README.md +15 -61
  2. package/dist/_virtual/_rolldown/runtime.js +4 -0
  3. package/dist/api.js +5 -0
  4. package/dist/index.js +14 -0
  5. package/dist/runtime-api.js +15 -0
  6. package/dist/setup-entry.js +7 -0
  7. package/dist/src/allowed-reaction-emojis.js +21 -0
  8. package/dist/src/api/auth.api.js +118 -0
  9. package/dist/src/api/channel.api.js +23 -0
  10. package/dist/src/api/message.api.js +76 -0
  11. package/dist/src/api/nugget.api.js +127 -0
  12. package/dist/src/auth.js +30 -0
  13. package/dist/src/channel.js +626 -0
  14. package/dist/src/config.js +52 -0
  15. package/dist/src/defaults.js +5 -0
  16. package/dist/src/inbound-policy.js +205 -0
  17. package/dist/src/inbound.js +97 -0
  18. package/dist/src/member-resolve.js +53 -0
  19. package/dist/src/message-handler.js +262 -0
  20. package/dist/src/nugget-lookup.js +140 -0
  21. package/dist/src/onboarding.js +357 -0
  22. package/dist/src/outbound.js +17 -0
  23. package/dist/src/reply-dispatcher.js +46 -0
  24. package/dist/src/runtime.js +5 -0
  25. package/dist/src/setup-core.js +46 -0
  26. package/dist/src/setup-surface.js +2 -0
  27. package/dist/src/socket/dolphin.socket.js +18 -0
  28. package/dist/src/socket/jaguar.socket.js +22 -0
  29. package/dist/src/socket/socket.js +153 -0
  30. package/dist/src/store.js +13 -0
  31. package/dist/src/token.js +84 -0
  32. package/dist/src/types.js +15 -0
  33. package/dist/src/utils/http.util.js +43 -0
  34. package/dist/src/utils/jwt.util.js +15 -0
  35. package/package.json +10 -3
  36. package/src/api/auth.api.ts +27 -7
  37. package/src/channel.ts +127 -238
  38. package/src/member-resolve.ts +1 -1
  39. package/src/onboarding.ts +117 -163
  40. package/src/setup-core.ts +10 -1
  41. package/src/socket/socket.ts +24 -11
package/README.md CHANGED
@@ -1,61 +1,44 @@
1
1
  # OpenClaw Omadeus Plugin
2
2
 
3
- [Omadeus](https://omadeus.com) plugin for [OpenClaw](https://www.npmjs.com/package/openclaw).
3
+ [![Socket Badge](https://badge.socket.dev/npm/package/@brantrusnak/openclaw-omadeus)](https://badge.socket.dev/npm/package/@brantrusnak/openclaw-omadeus)
4
+ [![CI](https://github.com/brantrusnak/openclaw-omadeus-plugin/actions/workflows/npm-publish.yml/badge.svg)](https://github.com/brantrusnak/openclaw-omadeus-plugin/actions/workflows/npm-publish.yml)
5
+ [![npm version](https://img.shields.io/npm/v/@brantrusnak/openclaw-omadeus)](https://www.npmjs.com/package/@brantrusnak/openclaw-omadeus)
6
+ [![License: ISC](https://img.shields.io/npm/l/@brantrusnak/openclaw-omadeus)](https://www.npmjs.com/package/@brantrusnak/openclaw-omadeus)
4
7
 
5
- This plugin connects OpenClaw to Omadeus over WebSocket so OpenClaw can listen
6
- for Omadeus messages and reply through the selected Omadeus channel.
8
+ [Omadeus](https://omadeus.com) plugin for [OpenClaw](https://www.npmjs.com/package/openclaw).
7
9
 
8
10
  ## Requirements
9
11
 
10
12
  - Node.js 22 or newer
11
13
  - OpenClaw 2026.4.10 or newer
12
- - An Omadeus account with access to the organization and channel you want
13
- OpenClaw to use
14
+ - An Omadeus account
14
15
 
15
16
  ## Install
16
17
 
17
- Install OpenClaw first:
18
-
19
18
  ```bash
20
19
  npm install -g openclaw
20
+ openclaw plugins install @brantrusnak/openclaw-omadeus
21
21
  ```
22
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:
23
+ Verify the plugin was installed:
30
24
 
31
25
  ```bash
32
- openclaw plugins install @brantrusnak/openclaw-omadeus
26
+ openclaw plugins list
33
27
  ```
34
28
 
35
- You can confirm OpenClaw discovered the plugin with:
29
+ Then run setup:
36
30
 
37
31
  ```bash
38
- openclaw plugins list
39
- openclaw plugins inspect omadeus
32
+ openclaw onboard
40
33
  ```
41
34
 
42
35
  ## Configure
43
36
 
44
- After installing the plugin, run OpenClaw configuration and choose Omadeus when
45
- prompted:
46
-
47
37
  ```bash
48
38
  openclaw configure
49
39
  ```
50
40
 
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:
41
+ You can also set credentials via environment variables:
59
42
 
60
43
  ```bash
61
44
  export OMADEUS_EMAIL="you@example.com"
@@ -63,45 +46,16 @@ export OMADEUS_PASSWORD="your-password"
63
46
  export OMADEUS_ORGANIZATION_ID="123"
64
47
  ```
65
48
 
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:
49
+ ## Start
77
50
 
78
51
  ```bash
79
52
  openclaw gateway
80
53
  ```
81
54
 
82
- Check channel health with:
83
-
84
- ```bash
85
- openclaw channels status --deep
86
- openclaw plugins doctor
87
- ```
88
-
89
55
  ## Local Development
90
56
 
91
- From this repository, you can link the local plugin into OpenClaw:
92
-
93
57
  ```bash
58
+ npm install
59
+ npm run build
94
60
  openclaw plugins install . --link
95
61
  ```
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
- ```
@@ -0,0 +1,4 @@
1
+ import "node:module";
2
+ import.meta.url;
3
+ //#endregion
4
+ export {};
package/dist/api.js ADDED
@@ -0,0 +1,5 @@
1
+ import "./_virtual/_rolldown/runtime.js";
2
+ import { omadeusSetupAdapter } from "./src/setup-core.js";
3
+ import { omadeusSetupWizard } from "./src/onboarding.js";
4
+ import "./src/setup-surface.js";
5
+ export { omadeusSetupAdapter, omadeusSetupWizard };
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ import "./_virtual/_rolldown/runtime.js";
2
+ import { setOmadeusRuntime } from "./src/runtime.js";
3
+ import { omadeusPlugin } from "./src/channel.js";
4
+ import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
5
+ //#region index.ts
6
+ var openclaw_omadeus_plugin_default = defineChannelPluginEntry({
7
+ id: "omadeus",
8
+ name: "Omadeus",
9
+ description: "Omadeus project management channel plugin",
10
+ plugin: omadeusPlugin,
11
+ setRuntime: setOmadeusRuntime
12
+ });
13
+ //#endregion
14
+ export { openclaw_omadeus_plugin_default as default, omadeusPlugin, setOmadeusRuntime };
@@ -0,0 +1,15 @@
1
+ import "./_virtual/_rolldown/runtime.js";
2
+ import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/core";
3
+ import { createReplyPrefixContext } from "openclaw/plugin-sdk/channel-runtime";
4
+ import { logInboundDrop } from "openclaw/plugin-sdk/channel-inbound";
5
+ import { resolveControlCommandGate } from "openclaw/plugin-sdk/command-auth";
6
+ import { createChannelPairingController } from "openclaw/plugin-sdk/channel-pairing";
7
+ import { readStoreAllowFromForDmPolicy, resolveDmGroupAccessWithLists } from "openclaw/plugin-sdk/channel-policy";
8
+ import { addWildcardAllowFrom, formatDocsLink, mergeAllowFromEntries } from "openclaw/plugin-sdk/setup";
9
+ //#region runtime-api.ts
10
+ function missingTargetError(provider, hint) {
11
+ const normalizedHint = hint?.trim();
12
+ return /* @__PURE__ */ new Error(`Delivering to ${provider} requires target${normalizedHint ? ` ${normalizedHint}` : ""}`);
13
+ }
14
+ //#endregion
15
+ export { DEFAULT_ACCOUNT_ID, addWildcardAllowFrom, createChannelPairingController, createReplyPrefixContext, formatDocsLink, logInboundDrop, mergeAllowFromEntries, missingTargetError, readStoreAllowFromForDmPolicy, resolveControlCommandGate, resolveDmGroupAccessWithLists };
@@ -0,0 +1,7 @@
1
+ import "./_virtual/_rolldown/runtime.js";
2
+ import { omadeusPlugin } from "./src/channel.js";
3
+ import { defineSetupPluginEntry } from "openclaw/plugin-sdk/core";
4
+ //#region setup-entry.ts
5
+ var setup_entry_default = defineSetupPluginEntry(omadeusPlugin);
6
+ //#endregion
7
+ export { setup_entry_default as default };
@@ -0,0 +1,21 @@
1
+ //#region src/allowed-reaction-emojis.ts
2
+ /**
3
+ * Omadeus only accepts these exact reaction strings; any other value is ignored (no API call).
4
+ */
5
+ const ALLOWED_OMADEUS_REACTION_EMOJI_LIST = [
6
+ "👍",
7
+ "👎",
8
+ "❤️",
9
+ "😂",
10
+ "😮",
11
+ "😢",
12
+ "🙏"
13
+ ];
14
+ const ALLOWED = new Set(ALLOWED_OMADEUS_REACTION_EMOJI_LIST);
15
+ function isAllowedOmadeusReactionEmoji(raw) {
16
+ const trimmed = raw.trim();
17
+ if (!trimmed) return false;
18
+ return ALLOWED.has(trimmed);
19
+ }
20
+ //#endregion
21
+ export { ALLOWED_OMADEUS_REACTION_EMOJI_LIST, isAllowedOmadeusReactionEmoji };
@@ -0,0 +1,118 @@
1
+ import { getCasSession, setCasSession } from "../store.js";
2
+ //#region src/api/auth.api.ts
3
+ const CAS_APPLICATION_ID = 1;
4
+ const CAS_SCOPES = "title,email,avatar,firstName,lastName,birth,phone,countryCode";
5
+ function formatFetchError(label, url, method, err) {
6
+ const base = err instanceof Error ? err.message : String(err);
7
+ const cause = err instanceof Error && err.cause instanceof Error ? err.cause.message : void 0;
8
+ const detail = cause && cause !== base ? `${base} (${cause})` : base;
9
+ return /* @__PURE__ */ new Error(`${label} (${method} ${url}) failed: ${detail}`);
10
+ }
11
+ async function omadeusFetch(label, url, init) {
12
+ const method = init.method ?? "GET";
13
+ try {
14
+ return await fetch(url, init);
15
+ } catch (err) {
16
+ throw formatFetchError(label, url, method, err);
17
+ }
18
+ }
19
+ async function createCasToken(params) {
20
+ const { casUrl, email, password } = params;
21
+ const res = await omadeusFetch("CAS token request", `${casUrl}/apiv1/tokens`, {
22
+ method: "CREATE",
23
+ headers: { "Content-Type": "application/json;charset=UTF-8" },
24
+ body: JSON.stringify({
25
+ email,
26
+ password
27
+ })
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();
34
+ const refreshCookie = res.headers.get("set-cookie") ?? "";
35
+ setCasSession({
36
+ token: body.token,
37
+ refreshCookie
38
+ });
39
+ return {
40
+ token: body.token,
41
+ refreshCookie
42
+ };
43
+ }
44
+ async function createAuthorizationCode(params) {
45
+ const { casUrl, token, email, redirectUri } = params;
46
+ const casSession = getCasSession();
47
+ const qs = new URLSearchParams({
48
+ applicationId: String(CAS_APPLICATION_ID),
49
+ scopes: CAS_SCOPES,
50
+ state: email,
51
+ redirectUri: redirectUri ?? ""
52
+ });
53
+ if (redirectUri) qs.set("redirectUri", redirectUri);
54
+ const res = await omadeusFetch("CAS authorization code request", `${casUrl}/apiv1/authorizationcodes?${qs}`, {
55
+ method: "CREATE",
56
+ body: "",
57
+ headers: {
58
+ Authorization: `Bearer ${token}`,
59
+ ...casSession?.refreshCookie ? { Cookie: casSession.refreshCookie } : {}
60
+ }
61
+ });
62
+ if (!res.ok) {
63
+ const text = await res.text().catch(() => "");
64
+ throw new Error(`CAS authorization code request failed (${res.status}): ${text}`);
65
+ }
66
+ const jsonResponse = await res.json();
67
+ const code = jsonResponse.authorizationCode ?? jsonResponse.code;
68
+ if (!code) throw new Error("CAS authorization code response missing code");
69
+ return code;
70
+ }
71
+ async function obtainSessionToken(params) {
72
+ const { maestroUrl, authorizationCode, organizationId } = params;
73
+ const res = await omadeusFetch("Omadeus session token request", `${maestroUrl}/dolphin/apiv1/oauth2/tokens`, {
74
+ method: "OBTAIN",
75
+ headers: { "Content-Type": "application/json;charset=UTF-8" },
76
+ body: JSON.stringify({
77
+ authorizationCode,
78
+ organizationId
79
+ })
80
+ });
81
+ if (!res.ok) {
82
+ const text = await res.text().catch(() => "");
83
+ throw new Error(`Omadeus session token request failed (${res.status}): ${text}`);
84
+ }
85
+ const body = await res.json();
86
+ if (!body.token) throw new Error("Omadeus session token response missing token");
87
+ return body.token;
88
+ }
89
+ async function listOrganizations(params) {
90
+ const { maestroUrl, email } = params;
91
+ const res = await omadeusFetch("Omadeus list organizations", `${maestroUrl}/dolphin/apiv1/organizations`, {
92
+ method: "LIST",
93
+ headers: { "Content-Type": "application/json;charset=UTF-8" },
94
+ body: JSON.stringify({ email })
95
+ });
96
+ if (!res.ok) {
97
+ const text = await res.text().catch(() => "");
98
+ throw new Error(`Omadeus list organizations failed (${res.status}): ${text}`);
99
+ }
100
+ return await res.json();
101
+ }
102
+ async function listOrganizationMembers(params) {
103
+ const { maestroUrl, sessionToken, organizationId } = params;
104
+ const res = await omadeusFetch("Omadeus list organization members", `${maestroUrl}/dolphin/apiv1/organizations/${organizationId}/members`, {
105
+ method: "LIST",
106
+ headers: {
107
+ Authorization: `Bearer ${sessionToken}`,
108
+ "Content-Type": "application/json;charset=UTF-8"
109
+ }
110
+ });
111
+ if (!res.ok) {
112
+ const text = await res.text().catch(() => "");
113
+ throw new Error(`Omadeus list organization members failed (${res.status}): ${text}`);
114
+ }
115
+ return await res.json();
116
+ }
117
+ //#endregion
118
+ export { createAuthorizationCode, createCasToken, listOrganizationMembers, listOrganizations, obtainSessionToken };
@@ -0,0 +1,23 @@
1
+ //#region src/api/channel.api.ts
2
+ async function listMemberChannelViews(params) {
3
+ const { maestroUrl, sessionToken, memberReferenceId, skip = 0, take = 100 } = params;
4
+ const url = `${maestroUrl}/jaguar/apiv1/members/${memberReferenceId}/channelviews?${new URLSearchParams({
5
+ skip: String(skip),
6
+ take: String(take),
7
+ sort: "-recentMessageAt"
8
+ }).toString()}`;
9
+ const res = await fetch(url, {
10
+ method: "LIST",
11
+ headers: {
12
+ Authorization: `Bearer ${sessionToken}`,
13
+ "Content-Type": "application/json;charset=UTF-8"
14
+ }
15
+ });
16
+ if (!res.ok) {
17
+ const text = await res.text().catch(() => "");
18
+ throw new Error(`Omadeus list channel views failed (${res.status}): ${text}`);
19
+ }
20
+ return await res.json();
21
+ }
22
+ //#endregion
23
+ export { listMemberChannelViews };
@@ -0,0 +1,76 @@
1
+ import { generateTemporaryId, jaguarFetch } from "../utils/http.util.js";
2
+ //#region src/api/message.api.ts
3
+ async function readJsonOrEmpty(res) {
4
+ if (res.status === 204) return;
5
+ const trimmed = (await res.text()).trim();
6
+ if (!trimmed) return;
7
+ try {
8
+ return JSON.parse(trimmed);
9
+ } catch {
10
+ return trimmed;
11
+ }
12
+ }
13
+ async function sendRoomMessage(opts, params) {
14
+ try {
15
+ const res = await jaguarFetch(opts, `/rooms/${params.roomId}/messages`, {
16
+ method: "SEND",
17
+ body: JSON.stringify({
18
+ body: params.body,
19
+ temporaryId: generateTemporaryId(),
20
+ links: "[]"
21
+ })
22
+ });
23
+ if (!res.ok) {
24
+ const text = await res.text().catch(() => "");
25
+ return {
26
+ ok: false,
27
+ error: `${res.status}: ${text.slice(0, 200)}`
28
+ };
29
+ }
30
+ return {
31
+ ok: true,
32
+ message: await res.json()
33
+ };
34
+ } catch (err) {
35
+ return {
36
+ ok: false,
37
+ error: (err instanceof Error ? err.message : String(err)).slice(0, 300)
38
+ };
39
+ }
40
+ }
41
+ async function editMessage(opts, params) {
42
+ const res = await jaguarFetch(opts, `/messages/${params.messageId}`, {
43
+ method: "EDIT",
44
+ body: JSON.stringify({
45
+ body: params.body,
46
+ ...params.temporaryId ? { temporaryId: params.temporaryId } : {},
47
+ ...params.links ? { links: JSON.stringify(params.links) } : {}
48
+ })
49
+ });
50
+ if (!res.ok) {
51
+ const text = await res.text().catch(() => "");
52
+ throw new Error(`Omadeus edit message failed (${res.status}): ${text.slice(0, 200)}`);
53
+ }
54
+ return await res.json();
55
+ }
56
+ async function deleteMessage(opts, params) {
57
+ const res = await jaguarFetch(opts, `/messages/${params.messageId}`, { method: "DELETE" });
58
+ if (!res.ok) {
59
+ const text = await res.text().catch(() => "");
60
+ throw new Error(`Omadeus delete message failed (${res.status}): ${text.slice(0, 200)}`);
61
+ }
62
+ return await res.json();
63
+ }
64
+ async function addMessageReaction(opts, params) {
65
+ const res = await jaguarFetch(opts, `/messages/${params.messageId}/reactions`, {
66
+ method: "ADD",
67
+ body: JSON.stringify({ emoji: params.emoji })
68
+ });
69
+ if (!res.ok) {
70
+ const text = await res.text().catch(() => "");
71
+ throw new Error(`Omadeus add reaction failed (${res.status}): ${text.slice(0, 200)}`);
72
+ }
73
+ return readJsonOrEmpty(res);
74
+ }
75
+ //#endregion
76
+ export { addMessageReaction, deleteMessage, editMessage, sendRoomMessage };
@@ -0,0 +1,127 @@
1
+ import { dolphinFetch } from "../utils/http.util.js";
2
+ //#region src/api/nugget.api.ts
3
+ /** Omadeus nugget/task display number (`N###` in UI maps to this field). */
4
+ function readNuggetNumber(record) {
5
+ const value = record["number"];
6
+ if (typeof value === "number" && Number.isFinite(value)) return value;
7
+ if (typeof value === "string" && /^\d+$/.test(value.trim())) return Number(value.trim());
8
+ }
9
+ function readNumberField(record, key) {
10
+ const value = record[key];
11
+ if (typeof value === "number" && Number.isFinite(value)) return value;
12
+ if (typeof value === "string" && /^\d+$/.test(value.trim())) return Number(value.trim());
13
+ }
14
+ function findNuggetRowByNumber(rows, nuggetNumber) {
15
+ return rows.find((row) => readNuggetNumber(row) === nuggetNumber);
16
+ }
17
+ function resolveTaskChannelRoomId(record) {
18
+ return readNumberField(record, "privateRoomId") ?? readNumberField(record, "publicRoomId") ?? readNumberField(record, "sharedRoomId");
19
+ }
20
+ function extractRows(payload) {
21
+ if (Array.isArray(payload)) return payload.filter((entry) => !!entry && typeof entry === "object");
22
+ if (!payload || typeof payload !== "object") return [];
23
+ const envelope = payload;
24
+ for (const key of [
25
+ "data",
26
+ "results",
27
+ "items",
28
+ "rows"
29
+ ]) {
30
+ const value = envelope[key];
31
+ if (Array.isArray(value)) return value.filter((entry) => !!entry && typeof entry === "object");
32
+ }
33
+ return [];
34
+ }
35
+ /**
36
+ * Dolphin SEARCH on nuggetviews — arbitrary text query (e.g. N###, task title, or room id string).
37
+ * Prefer filtering results with `findNuggetRowByNumber` or `findNuggetRowByRoomId`.
38
+ */
39
+ async function searchNuggetRowsByTextQuery(opts, params) {
40
+ const take = params.take ?? 100;
41
+ const q = params.query.trim();
42
+ if (!q) return [];
43
+ const search = new URLSearchParams();
44
+ search.set("take", String(take));
45
+ const res = await dolphinFetch(opts, `/nuggetviews?${search.toString()}`, {
46
+ method: "SEARCH",
47
+ body: JSON.stringify({ query: q }),
48
+ signal: params.signal
49
+ });
50
+ if (!res.ok) {
51
+ const text = await res.text().catch(() => "");
52
+ throw new Error(`Omadeus nugget search failed (${res.status}): ${text.slice(0, 200)}`);
53
+ }
54
+ return extractRows(await res.json());
55
+ }
56
+ /**
57
+ * Picks a row whose private/public/shared task room id matches a Jaguar `roomId`.
58
+ */
59
+ function findNuggetRowByRoomId(rows, roomId) {
60
+ for (const row of rows) for (const key of [
61
+ "privateRoomId",
62
+ "publicRoomId",
63
+ "sharedRoomId"
64
+ ]) if (readNumberField(row, key) === roomId) return row;
65
+ }
66
+ /**
67
+ * Resolve the nugget/task row for a Jaguar Task or Nugget **chat room** by matching
68
+ * `privateRoomId` / `publicRoomId` / `sharedRoomId` to `roomId` in Dolphin `nuggetviews` search results.
69
+ * Tries search by `roomName` first (usually matches the task title), then by the numeric `roomId` as text.
70
+ */
71
+ async function findNuggetByTaskChannelRoom(opts, params) {
72
+ const { roomId, roomName, signal } = params;
73
+ const tryQueries = [];
74
+ if (typeof roomName === "string" && roomName.trim()) tryQueries.push(roomName.trim());
75
+ tryQueries.push(String(roomId));
76
+ const tried = /* @__PURE__ */ new Set();
77
+ for (const query of tryQueries) {
78
+ if (tried.has(query)) continue;
79
+ tried.add(query);
80
+ const match = findNuggetRowByRoomId(await searchNuggetRowsByTextQuery(opts, {
81
+ query,
82
+ take: 100,
83
+ signal
84
+ }), roomId);
85
+ if (match) return match;
86
+ }
87
+ return null;
88
+ }
89
+ /**
90
+ * Dolphin SEARCH on nuggetviews returns an array of nugget/task rows.
91
+ * User-facing `N111` corresponds to `number: 111` on each row (not `id`).
92
+ */
93
+ async function searchNuggetByNumber(opts, params) {
94
+ return findNuggetRowByNumber(await searchNuggetRowsByTextQuery(opts, {
95
+ query: `N${params.nuggetNumber}`,
96
+ take: 100,
97
+ signal: params.signal
98
+ }), params.nuggetNumber) ?? null;
99
+ }
100
+ async function resolveTaskRoomIdByNumber(opts, params) {
101
+ const row = await searchNuggetByNumber(opts, params);
102
+ if (!row) return null;
103
+ return resolveTaskChannelRoomId(row) ?? null;
104
+ }
105
+ async function createNugget(opts, params) {
106
+ const res = await dolphinFetch(opts, "/nuggets", {
107
+ method: "CREATE",
108
+ body: JSON.stringify({
109
+ title: params.title,
110
+ stage: params.stage,
111
+ description: params.description,
112
+ kind: params.kind,
113
+ priority: params.priority,
114
+ memberReferenceId: params.memberReferenceId,
115
+ clientId: params.clientId,
116
+ folderId: params.folderId
117
+ }),
118
+ signal: params.signal
119
+ });
120
+ if (!res.ok) {
121
+ const text = await res.text().catch(() => "");
122
+ throw new Error(`Omadeus nugget create failed (${res.status}): ${text.slice(0, 200)}`);
123
+ }
124
+ return await res.json();
125
+ }
126
+ //#endregion
127
+ export { createNugget, findNuggetByTaskChannelRoom, readNuggetNumber, resolveTaskChannelRoomId, resolveTaskRoomIdByNumber, searchNuggetByNumber };
@@ -0,0 +1,30 @@
1
+ import { clearCasSession } from "./store.js";
2
+ import { createAuthorizationCode, createCasToken, obtainSessionToken } from "./api/auth.api.js";
3
+ import { decodeJwtPayload } from "./utils/jwt.util.js";
4
+ //#region src/auth.ts
5
+ async function authenticate(params) {
6
+ const { casUrl, maestroUrl, email, password, organizationId } = params;
7
+ const { token } = await createCasToken({
8
+ casUrl,
9
+ email,
10
+ password
11
+ });
12
+ const authorizationCode = await createAuthorizationCode({
13
+ casUrl,
14
+ token,
15
+ email,
16
+ redirectUri: maestroUrl
17
+ });
18
+ clearCasSession();
19
+ const dolphinToken = await obtainSessionToken({
20
+ maestroUrl,
21
+ authorizationCode,
22
+ organizationId
23
+ });
24
+ return {
25
+ dolphinToken,
26
+ payload: decodeJwtPayload(dolphinToken)
27
+ };
28
+ }
29
+ //#endregion
30
+ export { authenticate };