@brantrusnak/openclaw-omadeus 1.0.1 → 1.0.3

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 (49) hide show
  1. package/README.md +5 -2
  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 +115 -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 +363 -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/openclaw.plugin.json +144 -28
  36. package/package.json +12 -7
  37. package/src/api/auth.api.ts +0 -29
  38. package/src/api/channel.api.ts +29 -0
  39. package/src/api/nugget.api.ts +81 -10
  40. package/src/channel.ts +136 -247
  41. package/src/inbound-policy.ts +250 -0
  42. package/src/inbound.ts +20 -0
  43. package/src/member-resolve.ts +84 -0
  44. package/src/message-handler.ts +99 -53
  45. package/src/nugget-lookup.ts +67 -4
  46. package/src/onboarding.ts +283 -200
  47. package/src/setup-core.ts +10 -1
  48. package/src/socket/socket.ts +24 -11
  49. package/src/types.ts +77 -7
@@ -0,0 +1,153 @@
1
+ import { WebSocket } from "ws";
2
+ //#region src/socket/socket.ts
3
+ const RECONNECT_BASE_MS = 2e3;
4
+ const RECONNECT_MAX_MS = 6e4;
5
+ const HEARTBEAT_INTERVAL_MS = 3e4;
6
+ const HEARTBEAT_MISSED_MAX = 5;
7
+ const KEEP_ALIVE_CONTENT = "keep-alive";
8
+ const KEEP_ALIVE_ACTION = "answer";
9
+ function isServerKeepAlive(data) {
10
+ return data.content === KEEP_ALIVE_CONTENT;
11
+ }
12
+ function isClientKeepAlive(data) {
13
+ return data.data === KEEP_ALIVE_CONTENT;
14
+ }
15
+ function createOmadeusSocketClient(opts) {
16
+ const { maestroUrl, tokenManager, pathSuffix, logPrefix, onEvent, onConnect, onDisconnect, onError, log } = opts;
17
+ let ws = null;
18
+ let reconnectAttempt = 0;
19
+ let reconnectTimer = null;
20
+ let heartbeatTimer = null;
21
+ let heartbeatMissCount = 0;
22
+ let intentionalClose = false;
23
+ function buildWsUrl() {
24
+ const base = maestroUrl.replace(/^http/, "ws");
25
+ const token = tokenManager.getToken();
26
+ return `${base}/${pathSuffix}?token=${encodeURIComponent(token)}`;
27
+ }
28
+ function scheduleReconnect() {
29
+ if (intentionalClose) return;
30
+ const delayMs = Math.min(RECONNECT_BASE_MS * 2 ** reconnectAttempt, RECONNECT_MAX_MS);
31
+ reconnectAttempt++;
32
+ log?.info(`${logPrefix} reconnecting in ${delayMs}ms (attempt ${reconnectAttempt})`);
33
+ reconnectTimer = setTimeout(() => connect(), delayMs);
34
+ }
35
+ function stopHeartbeat() {
36
+ if (heartbeatTimer) {
37
+ clearInterval(heartbeatTimer);
38
+ heartbeatTimer = null;
39
+ }
40
+ }
41
+ function resetHeartbeat() {
42
+ heartbeatMissCount = 0;
43
+ }
44
+ function sendKeepAlive() {
45
+ if (ws?.readyState !== WebSocket.OPEN) return;
46
+ heartbeatMissCount += 1;
47
+ sendKeepAliveFrame();
48
+ if (heartbeatMissCount >= HEARTBEAT_MISSED_MAX) {
49
+ log?.warn(`${logPrefix} heartbeat unanswered ${heartbeatMissCount} times; reconnecting socket`);
50
+ ws.close();
51
+ }
52
+ }
53
+ function startHeartbeat() {
54
+ stopHeartbeat();
55
+ heartbeatTimer = setInterval(() => {
56
+ sendKeepAlive();
57
+ }, HEARTBEAT_INTERVAL_MS);
58
+ }
59
+ function sendKeepAliveFrame() {
60
+ if (ws?.readyState === WebSocket.OPEN) ws.send(JSON.stringify({
61
+ data: KEEP_ALIVE_CONTENT,
62
+ action: KEEP_ALIVE_ACTION
63
+ }));
64
+ }
65
+ function connect() {
66
+ if (ws) {
67
+ ws.removeAllListeners();
68
+ ws.close();
69
+ ws = null;
70
+ }
71
+ intentionalClose = false;
72
+ stopHeartbeat();
73
+ resetHeartbeat();
74
+ if (tokenManager.needsRefresh()) {
75
+ tokenManager.refresh().then(() => connect()).catch((err) => {
76
+ onError?.(err instanceof Error ? err : new Error(String(err)));
77
+ scheduleReconnect();
78
+ });
79
+ return;
80
+ }
81
+ const url = buildWsUrl();
82
+ log?.info(`${logPrefix} connecting...`);
83
+ ws = new WebSocket(url);
84
+ ws.on("open", () => {
85
+ reconnectAttempt = 0;
86
+ log?.info(`${logPrefix} connected`);
87
+ onConnect?.();
88
+ resetHeartbeat();
89
+ sendKeepAlive();
90
+ startHeartbeat();
91
+ });
92
+ ws.on("message", (raw) => {
93
+ try {
94
+ const data = JSON.parse(String(raw));
95
+ const action = data.action;
96
+ if (isServerKeepAlive(data) && action === KEEP_ALIVE_ACTION) {
97
+ resetHeartbeat();
98
+ return;
99
+ }
100
+ if (isClientKeepAlive(data) && action === KEEP_ALIVE_ACTION) {
101
+ resetHeartbeat();
102
+ return;
103
+ }
104
+ if (isServerKeepAlive(data) && action === "heartbeat") {
105
+ resetHeartbeat();
106
+ sendKeepAliveFrame();
107
+ return;
108
+ }
109
+ resetHeartbeat();
110
+ onEvent?.(data);
111
+ } catch {
112
+ log?.warn(`${logPrefix} unparseable message: ${String(raw).slice(0, 200)}`);
113
+ }
114
+ });
115
+ ws.on("close", (code, reason) => {
116
+ const msg = `code=${code} reason=${String(reason)}`;
117
+ log?.info(`${logPrefix} disconnected: ${msg}`);
118
+ onDisconnect?.(msg);
119
+ ws = null;
120
+ stopHeartbeat();
121
+ resetHeartbeat();
122
+ scheduleReconnect();
123
+ });
124
+ ws.on("error", (err) => {
125
+ log?.error(`${logPrefix} error: ${err.message}`);
126
+ onError?.(err);
127
+ });
128
+ }
129
+ function disconnect() {
130
+ intentionalClose = true;
131
+ if (reconnectTimer) {
132
+ clearTimeout(reconnectTimer);
133
+ reconnectTimer = null;
134
+ }
135
+ stopHeartbeat();
136
+ resetHeartbeat();
137
+ if (ws) {
138
+ ws.removeAllListeners();
139
+ ws.close();
140
+ ws = null;
141
+ }
142
+ }
143
+ return {
144
+ connect,
145
+ disconnect,
146
+ isConnected: () => ws?.readyState === WebSocket.OPEN,
147
+ send: (data) => {
148
+ if (ws?.readyState === WebSocket.OPEN) ws.send(JSON.stringify(data));
149
+ }
150
+ };
151
+ }
152
+ //#endregion
153
+ export { createOmadeusSocketClient };
@@ -0,0 +1,13 @@
1
+ //#region src/store.ts
2
+ let currentSession = null;
3
+ function setCasSession(session) {
4
+ currentSession = session;
5
+ }
6
+ function getCasSession() {
7
+ return currentSession;
8
+ }
9
+ function clearCasSession() {
10
+ currentSession = null;
11
+ }
12
+ //#endregion
13
+ export { clearCasSession, getCasSession, setCasSession };
@@ -0,0 +1,84 @@
1
+ import { decodeJwtPayload, tokenExpiresInMs } from "./utils/jwt.util.js";
2
+ import { authenticate } from "./auth.js";
3
+ //#region src/token.ts
4
+ const TOKEN_REFRESH_MARGIN_MS = 300 * 1e3;
5
+ const MAX_TIMEOUT_MS = 2147483647;
6
+ /** Whether the token should be refreshed now (within safety margin). */
7
+ function shouldRefreshToken(token) {
8
+ return tokenExpiresInMs(token) < TOKEN_REFRESH_MARGIN_MS;
9
+ }
10
+ function createTokenManager(params) {
11
+ const { casUrl, maestroUrl, email, password, organizationId, initialToken, onRefresh, onError } = params;
12
+ let currentToken = "";
13
+ let currentPayload = null;
14
+ if (initialToken) try {
15
+ const payload = decodeJwtPayload(initialToken);
16
+ currentToken = initialToken;
17
+ currentPayload = payload;
18
+ } catch (err) {
19
+ const error = err instanceof Error ? err : new Error(String(err));
20
+ onError?.(error);
21
+ }
22
+ let refreshTimer = null;
23
+ const refresh = async () => {
24
+ if (currentToken && !shouldRefreshToken(currentToken)) return;
25
+ const { dolphinToken, payload } = await authenticate({
26
+ casUrl,
27
+ maestroUrl,
28
+ email,
29
+ password,
30
+ organizationId
31
+ });
32
+ currentToken = dolphinToken;
33
+ currentPayload = payload;
34
+ onRefresh?.(dolphinToken);
35
+ };
36
+ const scheduleNextRefresh = () => {
37
+ if (refreshTimer) {
38
+ clearTimeout(refreshTimer);
39
+ refreshTimer = null;
40
+ }
41
+ if (!currentToken) return;
42
+ const desiredDelayMs = tokenExpiresInMs(currentToken) - TOKEN_REFRESH_MARGIN_MS;
43
+ refreshTimer = setTimeout(async () => {
44
+ try {
45
+ await refresh();
46
+ scheduleNextRefresh();
47
+ } catch (err) {
48
+ onError?.(err instanceof Error ? err : new Error(String(err)));
49
+ refreshTimer = setTimeout(() => void scheduleNextRefresh(), 3e4);
50
+ }
51
+ }, Math.min(Math.max(desiredDelayMs, 1e4), MAX_TIMEOUT_MS));
52
+ };
53
+ return {
54
+ getToken() {
55
+ return currentToken;
56
+ },
57
+ getPayload() {
58
+ if (!currentPayload) throw new Error("Omadeus: not authenticated");
59
+ return currentPayload;
60
+ },
61
+ async refresh() {
62
+ try {
63
+ await refresh();
64
+ } catch (err) {
65
+ onError?.(err instanceof Error ? err : new Error(String(err)));
66
+ throw err;
67
+ }
68
+ },
69
+ startAutoRefresh() {
70
+ scheduleNextRefresh();
71
+ },
72
+ stopAutoRefresh() {
73
+ if (refreshTimer) {
74
+ clearTimeout(refreshTimer);
75
+ refreshTimer = null;
76
+ }
77
+ },
78
+ needsRefresh() {
79
+ return !currentToken || shouldRefreshToken(currentToken);
80
+ }
81
+ };
82
+ }
83
+ //#endregion
84
+ export { createTokenManager };
@@ -0,0 +1,15 @@
1
+ //#region src/types.ts
2
+ /** Jaguar entity chats (`subscribableKind`) — DMs and channel rooms use other values. */
3
+ const OMADEUS_INBOUND_ENTITY_KINDS = [
4
+ "task",
5
+ "nugget",
6
+ "project",
7
+ "release",
8
+ "sprint",
9
+ "summary",
10
+ "client",
11
+ "folder"
12
+ ];
13
+ const OMADEUS_INBOUND_ENTITY_KIND_SET = new Set(OMADEUS_INBOUND_ENTITY_KINDS);
14
+ //#endregion
15
+ export { OMADEUS_INBOUND_ENTITY_KINDS, OMADEUS_INBOUND_ENTITY_KIND_SET };
@@ -0,0 +1,43 @@
1
+ import { randomUUID } from "node:crypto";
2
+ //#region src/utils/http.util.ts
3
+ function authHeaders(token) {
4
+ return {
5
+ Authorization: `Bearer ${token}`,
6
+ "Content-Type": "application/json"
7
+ };
8
+ }
9
+ async function apiFetch(opts, path, init) {
10
+ const token = opts.tokenManager.getToken();
11
+ if (!token) throw new Error("Omadeus: not authenticated");
12
+ const url = `${opts.maestroUrl}${path}`;
13
+ try {
14
+ return await fetch(url, {
15
+ ...init,
16
+ headers: {
17
+ ...authHeaders(token),
18
+ ...init?.headers
19
+ }
20
+ });
21
+ } catch (err) {
22
+ const message = err instanceof Error ? err.message : String(err);
23
+ throw new Error(`Omadeus API request to ${url} failed: ${message}`);
24
+ }
25
+ }
26
+ function withApiPrefix(prefix, path) {
27
+ if (!path) return prefix;
28
+ if (path.startsWith("/")) return `${prefix}${path}`;
29
+ return `${prefix}/${path}`;
30
+ }
31
+ const JAGUAR_PREFIX = "/jaguar/apiv1";
32
+ const DOLPHIN_PREFIX = "/dolphin/apiv1";
33
+ async function jaguarFetch(opts, path, init) {
34
+ return apiFetch(opts, withApiPrefix(JAGUAR_PREFIX, path), init);
35
+ }
36
+ async function dolphinFetch(opts, path, init) {
37
+ return apiFetch(opts, withApiPrefix(DOLPHIN_PREFIX, path), init);
38
+ }
39
+ function generateTemporaryId() {
40
+ return `_${randomUUID().replace(/-/g, "").slice(0, 10)}`;
41
+ }
42
+ //#endregion
43
+ export { dolphinFetch, generateTemporaryId, jaguarFetch };
@@ -0,0 +1,15 @@
1
+ //#region src/utils/jwt.util.ts
2
+ /** Decode the payload portion of a JWT without verifying the signature. */
3
+ function decodeJwtPayload(token) {
4
+ const parts = token.split(".");
5
+ if (parts.length !== 3) throw new Error("Invalid JWT: expected 3 parts");
6
+ const payload = Buffer.from(parts[1], "base64url").toString("utf-8");
7
+ return JSON.parse(payload);
8
+ }
9
+ /** Returns ms until the token expires (negative = already expired). */
10
+ function tokenExpiresInMs(token) {
11
+ const { exp } = decodeJwtPayload(token);
12
+ return exp * 1e3 - Date.now();
13
+ }
14
+ //#endregion
15
+ export { decodeJwtPayload, tokenExpiresInMs };
@@ -12,11 +12,77 @@
12
12
  "password": { "type": "string" },
13
13
  "organizationId": { "type": "number" },
14
14
  "sessionToken": { "type": "string" },
15
- "selectedMemberReferenceId": { "type": "number" },
16
- "selectedChannelViewId": { "type": "number" },
17
- "selectedChannelTitle": { "type": "string" },
18
- "selectedChannelPrivateRoomId": { "type": "number" },
19
- "selectedChannelPublicRoomId": { "type": "number" }
15
+ "inbound": {
16
+ "type": "object",
17
+ "additionalProperties": false,
18
+ "properties": {
19
+ "version": { "type": "integer", "minimum": 1 },
20
+ "direct": {
21
+ "type": "object",
22
+ "additionalProperties": false,
23
+ "properties": {
24
+ "enabled": { "type": "boolean" },
25
+ "allowedSenderReferenceIds": {
26
+ "type": "array",
27
+ "items": { "type": "number" }
28
+ },
29
+ "requireMention": { "type": "string", "enum": ["never", "always"] }
30
+ },
31
+ "required": ["enabled"]
32
+ },
33
+ "channels": {
34
+ "type": "object",
35
+ "additionalProperties": false,
36
+ "properties": {
37
+ "enabled": { "type": "boolean" },
38
+ "allowedRoomIds": { "type": "array", "items": { "type": "number" } },
39
+ "allowedChannelViewIds": { "type": "array", "items": { "type": "number" } },
40
+ "allowedSenderReferenceIds": {
41
+ "type": "array",
42
+ "items": { "type": "number" }
43
+ },
44
+ "requireMention": {
45
+ "type": "string",
46
+ "enum": ["never", "always", "outsideAllowlist"]
47
+ }
48
+ },
49
+ "required": ["enabled"]
50
+ },
51
+ "entities": {
52
+ "type": "object",
53
+ "additionalProperties": false,
54
+ "properties": {
55
+ "enabled": { "type": "boolean" },
56
+ "allowedKinds": {
57
+ "type": "array",
58
+ "items": {
59
+ "type": "string",
60
+ "enum": [
61
+ "task",
62
+ "nugget",
63
+ "project",
64
+ "release",
65
+ "sprint",
66
+ "summary",
67
+ "client",
68
+ "folder"
69
+ ]
70
+ }
71
+ },
72
+ "allowedRoomIds": { "type": "array", "items": { "type": "number" } },
73
+ "allowedSenderReferenceIds": {
74
+ "type": "array",
75
+ "items": { "type": "number" }
76
+ },
77
+ "requireMention": {
78
+ "type": "string",
79
+ "enum": ["never", "always", "outsideAllowlist"]
80
+ }
81
+ },
82
+ "required": ["enabled"]
83
+ }
84
+ }
85
+ }
20
86
  }
21
87
  },
22
88
  "channelConfigs": {
@@ -32,11 +98,77 @@
32
98
  "password": { "type": "string" },
33
99
  "organizationId": { "type": "number" },
34
100
  "sessionToken": { "type": "string" },
35
- "selectedMemberReferenceId": { "type": "number" },
36
- "selectedChannelViewId": { "type": "number" },
37
- "selectedChannelTitle": { "type": "string" },
38
- "selectedChannelPrivateRoomId": { "type": "number" },
39
- "selectedChannelPublicRoomId": { "type": "number" }
101
+ "inbound": {
102
+ "type": "object",
103
+ "additionalProperties": false,
104
+ "properties": {
105
+ "version": { "type": "integer", "minimum": 1 },
106
+ "direct": {
107
+ "type": "object",
108
+ "additionalProperties": false,
109
+ "properties": {
110
+ "enabled": { "type": "boolean" },
111
+ "allowedSenderReferenceIds": {
112
+ "type": "array",
113
+ "items": { "type": "number" }
114
+ },
115
+ "requireMention": { "type": "string", "enum": ["never", "always"] }
116
+ },
117
+ "required": ["enabled"]
118
+ },
119
+ "channels": {
120
+ "type": "object",
121
+ "additionalProperties": false,
122
+ "properties": {
123
+ "enabled": { "type": "boolean" },
124
+ "allowedRoomIds": { "type": "array", "items": { "type": "number" } },
125
+ "allowedChannelViewIds": { "type": "array", "items": { "type": "number" } },
126
+ "allowedSenderReferenceIds": {
127
+ "type": "array",
128
+ "items": { "type": "number" }
129
+ },
130
+ "requireMention": {
131
+ "type": "string",
132
+ "enum": ["never", "always", "outsideAllowlist"]
133
+ }
134
+ },
135
+ "required": ["enabled"]
136
+ },
137
+ "entities": {
138
+ "type": "object",
139
+ "additionalProperties": false,
140
+ "properties": {
141
+ "enabled": { "type": "boolean" },
142
+ "allowedKinds": {
143
+ "type": "array",
144
+ "items": {
145
+ "type": "string",
146
+ "enum": [
147
+ "task",
148
+ "nugget",
149
+ "project",
150
+ "release",
151
+ "sprint",
152
+ "summary",
153
+ "client",
154
+ "folder"
155
+ ]
156
+ }
157
+ },
158
+ "allowedRoomIds": { "type": "array", "items": { "type": "number" } },
159
+ "allowedSenderReferenceIds": {
160
+ "type": "array",
161
+ "items": { "type": "number" }
162
+ },
163
+ "requireMention": {
164
+ "type": "string",
165
+ "enum": ["never", "always", "outsideAllowlist"]
166
+ }
167
+ },
168
+ "required": ["enabled"]
169
+ }
170
+ }
171
+ }
40
172
  }
41
173
  },
42
174
  "uiHints": {
@@ -63,24 +195,8 @@
63
195
  "sensitive": true,
64
196
  "advanced": true
65
197
  },
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",
198
+ "inbound": {
199
+ "label": "Inbound policy (Jaguar chat)",
84
200
  "advanced": true
85
201
  }
86
202
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brantrusnak/openclaw-omadeus",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "private": false,
5
5
  "description": "OpenClaw Omadeus project management channel plugin",
6
6
  "homepage": "https://github.com/brantrusnak/openclaw-omadeus-plugin#readme",
@@ -14,15 +14,17 @@
14
14
  "license": "ISC",
15
15
  "author": "Brant Rusnak",
16
16
  "type": "module",
17
- "main": "./index.ts",
17
+ "main": "./dist/index.js",
18
18
  "scripts": {
19
+ "build": "rolldown -c rolldown.config.mjs",
19
20
  "test": "vitest run",
20
- "prepack": "node ./scripts/verify-npm-files.mjs"
21
+ "prepack": "npm run build && node ./scripts/verify-npm-files.mjs"
21
22
  },
22
23
  "dependencies": {
23
24
  "ws": "^8.20.0"
24
25
  },
25
26
  "files": [
27
+ "dist",
26
28
  "index.ts",
27
29
  "setup-entry.ts",
28
30
  "api.ts",
@@ -33,6 +35,7 @@
33
35
  ],
34
36
  "devDependencies": {
35
37
  "openclaw": ">=2026.4.10",
38
+ "rolldown": "1.0.0-rc.17",
36
39
  "vitest": "^4.1.5"
37
40
  },
38
41
  "peerDependencies": {
@@ -50,14 +53,16 @@
50
53
  "extensions": [
51
54
  "./index.ts"
52
55
  ],
56
+ "runtimeExtensions": [
57
+ "./dist/index.js"
58
+ ],
53
59
  "setupEntry": "./setup-entry.ts",
60
+ "runtimeSetupEntry": "./dist/setup-entry.js",
54
61
  "channel": {
55
62
  "id": "omadeus",
56
63
  "label": "Omadeus",
57
- "selectionLabel": "Omadeus (WebSocket)",
58
- "docsPath": "/channels/omadeus",
59
- "docsLabel": "omadeus",
60
- "blurb": "Omadeus project management — tasks, chat rooms, and sprints.",
64
+ "selectionLabel": "Omadeus (API + WebSocket)",
65
+ "blurb": "AI-native project management that knows your role, speaks your language, and keeps your team in sync. No noise.",
61
66
  "order": 70
62
67
  },
63
68
  "install": {
@@ -1,7 +1,6 @@
1
1
  import { getCasSession, setCasSession } from "../store.js";
2
2
  import type {
3
3
  CasAuthorizationCodeResponse,
4
- OmadeusChannelView,
5
4
  OmadeusOrganizationMember,
6
5
  OmadeusOrganization,
7
6
  OmadeusSessionTokenResponse,
@@ -139,34 +138,6 @@ export async function listOrganizations(params: {
139
138
  return (await res.json()) as OmadeusOrganization[];
140
139
  }
141
140
 
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
141
  export async function listOrganizationMembers(params: {
171
142
  maestroUrl: string;
172
143
  sessionToken: string;
@@ -0,0 +1,29 @@
1
+ import type { OmadeusChannelView } from "../types.js";
2
+
3
+ export async function listMemberChannelViews(params: {
4
+ maestroUrl: string;
5
+ sessionToken: string;
6
+ memberReferenceId: number;
7
+ skip?: number;
8
+ take?: number;
9
+ }): Promise<OmadeusChannelView[]> {
10
+ const { maestroUrl, sessionToken, memberReferenceId, skip = 0, take = 100 } = params;
11
+ const qs = new URLSearchParams({
12
+ skip: String(skip),
13
+ take: String(take),
14
+ sort: "-recentMessageAt",
15
+ });
16
+ const url = `${maestroUrl}/jaguar/apiv1/members/${memberReferenceId}/channelviews?${qs.toString()}`;
17
+ const res = await fetch(url, {
18
+ method: "LIST",
19
+ headers: {
20
+ Authorization: `Bearer ${sessionToken}`,
21
+ "Content-Type": "application/json;charset=UTF-8",
22
+ },
23
+ });
24
+ if (!res.ok) {
25
+ const text = await res.text().catch(() => "");
26
+ throw new Error(`Omadeus list channel views failed (${res.status}): ${text}`);
27
+ }
28
+ return (await res.json()) as OmadeusChannelView[];
29
+ }