tide-commander 1.37.2 → 1.38.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.
Files changed (41) hide show
  1. package/dist/assets/{BossLogsModal-C9c6b6JM.js → BossLogsModal-Bn3z56OP.js} +1 -1
  2. package/dist/assets/{BossSpawnModal-dSXYldID.js → BossSpawnModal-CWxP-MSv.js} +1 -1
  3. package/dist/assets/{ControlsModal-C_W0XTlE.js → ControlsModal-B5r9l6t1.js} +1 -1
  4. package/dist/assets/{DockerLogsModal-CUweGxhz.js → DockerLogsModal-CWwOgxQG.js} +1 -1
  5. package/dist/assets/{EmbeddedEditor-DU_lcIBD.js → EmbeddedEditor-CePY44FJ.js} +1 -1
  6. package/dist/assets/{GmailOAuthSetup-DTAkTnzL.js → GmailOAuthSetup-zVmKkZLJ.js} +1 -1
  7. package/dist/assets/{GoogleOAuthSetup-B83vPcAW.js → GoogleOAuthSetup-DXSCMIOU.js} +1 -1
  8. package/dist/assets/{IframeModal-3rIUuKKu.js → IframeModal-BsbB4AMI.js} +1 -1
  9. package/dist/assets/{IntegrationsPanel-CJviFK3q.js → IntegrationsPanel-1xcUvOle.js} +2 -2
  10. package/dist/assets/{LogViewerModal-C9qFaC8V.js → LogViewerModal-DVS-zxfP.js} +1 -1
  11. package/dist/assets/{MonitoringModal-DRZrxL7t.js → MonitoringModal-DWk0piqs.js} +1 -1
  12. package/dist/assets/{PM2LogsModal-BLzKH4ql.js → PM2LogsModal-GxDWxG5c.js} +1 -1
  13. package/dist/assets/{RestoreArchivedAreaModal-DHH8QKkx.js → RestoreArchivedAreaModal-DvbQVeiA.js} +1 -1
  14. package/dist/assets/{SaveSnapshotModal-BTvvgNqk.js → SaveSnapshotModal-DpONGDMt.js} +1 -1
  15. package/dist/assets/{Scene2DCanvas-Gu0Pplgl.js → Scene2DCanvas-DLvMkm2q.js} +1 -1
  16. package/dist/assets/{SceneManager-BD0UwiqS.js → SceneManager-BgjRQCpO.js} +1 -1
  17. package/dist/assets/{SkillsPanel-Btfm8DLY.js → SkillsPanel-DqqJjbX5.js} +1 -1
  18. package/dist/assets/{SnapshotManager-BgCuvtUx.js → SnapshotManager-4ig8bV_o.js} +1 -1
  19. package/dist/assets/{SpawnModal-ByhvPfN7.js → SpawnModal-D2e4lCt9.js} +1 -1
  20. package/dist/assets/{SubordinateAssignmentModal-5Re0nGK9.js → SubordinateAssignmentModal-B_rjqVum.js} +1 -1
  21. package/dist/assets/{SupervisorPanel-DtNzjH1r.js → SupervisorPanel-BAXOUwAk.js} +1 -1
  22. package/dist/assets/{TriggerManagerPanel-DUXzcjBS.js → TriggerManagerPanel-D48mxJNq.js} +1 -1
  23. package/dist/assets/{WorkflowEditorPanel-CCauefUO.js → WorkflowEditorPanel-DkzFKMCZ.js} +1 -1
  24. package/dist/assets/{index-DtDea-zV.js → index-BoWUyxxn.js} +1 -1
  25. package/dist/assets/{index-CPBP46l5.js → index-BrgM4K8j.js} +1 -1
  26. package/dist/assets/{index-BG-fI4o6.js → index-CdlqvN8h.js} +1 -1
  27. package/dist/assets/{index-DTO0LlmC.js → index-CsBPIIxt.js} +1 -1
  28. package/dist/assets/{index-BakRazz4.js → index-D3k7Y2rz.js} +1 -1
  29. package/dist/assets/{index-Bma1pZ6y.js → index-DENcwRRk.js} +2 -2
  30. package/dist/assets/{index-DfBVuA_K.js → index-DHFWfrE-.js} +1 -1
  31. package/dist/assets/{index-Bl5TthnX.js → index-DWfphUcy.js} +3 -3
  32. package/dist/assets/{main-lr_lRuBZ.js → main-sZyyVmvr.js} +4 -4
  33. package/dist/assets/{web-DOowQZI5.js → web-CmQWPACi.js} +1 -1
  34. package/dist/assets/{web-CV2dsnZj.js → web-GvCQTLgg.js} +1 -1
  35. package/dist/index.html +1 -1
  36. package/dist/src/packages/server/integrations/slack/index.js +17 -0
  37. package/dist/src/packages/server/integrations/slack/slack-client.js +63 -0
  38. package/dist/src/packages/server/integrations/slack/slack-config.js +1 -1
  39. package/dist/src/packages/server/integrations/slack/slack-routes.js +49 -1
  40. package/dist/src/packages/server/integrations/slack/slack-skill.js +31 -0
  41. package/package.json +1 -1
@@ -1 +1 @@
1
- import{bw as s}from"./main-lr_lRuBZ.js";import"./modulepreload-polyfill-B5Qt9EMX.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class f extends s{constructor(){super(...arguments),this.pending=[],this.deliveredNotifications=[],this.hasNotificationSupport=()=>{if(!("Notification"in window)||!Notification.requestPermission)return!1;if(Notification.permission!=="granted")try{new Notification("")}catch(i){if(i instanceof Error&&i.name==="TypeError")return!1}return!0}}async getDeliveredNotifications(){const i=[];for(const t of this.deliveredNotifications){const e={title:t.title,id:parseInt(t.tag),body:t.body};i.push(e)}return{notifications:i}}async removeDeliveredNotifications(i){for(const t of i.notifications){const e=this.deliveredNotifications.find(n=>n.tag===String(t.id));e==null||e.close(),this.deliveredNotifications=this.deliveredNotifications.filter(()=>!e)}}async removeAllDeliveredNotifications(){for(const i of this.deliveredNotifications)i.close();this.deliveredNotifications=[]}async createChannel(){throw this.unimplemented("Not implemented on web.")}async deleteChannel(){throw this.unimplemented("Not implemented on web.")}async listChannels(){throw this.unimplemented("Not implemented on web.")}async schedule(i){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");for(const t of i.notifications)this.sendNotification(t);return{notifications:i.notifications.map(t=>({id:t.id}))}}async getPending(){return{notifications:this.pending}}async registerActionTypes(){throw this.unimplemented("Not implemented on web.")}async cancel(i){this.pending=this.pending.filter(t=>!i.notifications.find(e=>e.id===t.id))}async areEnabled(){const{display:i}=await this.checkPermissions();return{value:i==="granted"}}async changeExactNotificationSetting(){throw this.unimplemented("Not implemented on web.")}async checkExactNotificationSetting(){throw this.unimplemented("Not implemented on web.")}async requestPermissions(){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");return{display:this.transformNotificationPermission(await Notification.requestPermission())}}async checkPermissions(){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");return{display:this.transformNotificationPermission(Notification.permission)}}transformNotificationPermission(i){switch(i){case"granted":return"granted";case"denied":return"denied";default:return"prompt"}}sendPending(){var i;const t=[],e=new Date().getTime();for(const n of this.pending)!((i=n.schedule)===null||i===void 0)&&i.at&&n.schedule.at.getTime()<=e&&(this.buildNotification(n),t.push(n));this.pending=this.pending.filter(n=>!t.find(o=>o===n))}sendNotification(i){var t;if(!((t=i.schedule)===null||t===void 0)&&t.at){const e=i.schedule.at.getTime()-new Date().getTime();this.pending.push(i),setTimeout(()=>{this.sendPending()},e);return}this.buildNotification(i)}buildNotification(i){const t=new Notification(i.title,{body:i.body,tag:String(i.id)});return t.addEventListener("click",this.onClick.bind(this,i),!1),t.addEventListener("show",this.onShow.bind(this,i),!1),t.addEventListener("close",()=>{this.deliveredNotifications=this.deliveredNotifications.filter(()=>!this)},!1),this.deliveredNotifications.push(t),t}onClick(i){const t={actionId:"tap",notification:i};this.notifyListeners("localNotificationActionPerformed",t)}onShow(i){this.notifyListeners("localNotificationReceived",i)}}export{f as LocalNotificationsWeb};
1
+ import{bw as s}from"./main-sZyyVmvr.js";import"./modulepreload-polyfill-B5Qt9EMX.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class f extends s{constructor(){super(...arguments),this.pending=[],this.deliveredNotifications=[],this.hasNotificationSupport=()=>{if(!("Notification"in window)||!Notification.requestPermission)return!1;if(Notification.permission!=="granted")try{new Notification("")}catch(i){if(i instanceof Error&&i.name==="TypeError")return!1}return!0}}async getDeliveredNotifications(){const i=[];for(const t of this.deliveredNotifications){const e={title:t.title,id:parseInt(t.tag),body:t.body};i.push(e)}return{notifications:i}}async removeDeliveredNotifications(i){for(const t of i.notifications){const e=this.deliveredNotifications.find(n=>n.tag===String(t.id));e==null||e.close(),this.deliveredNotifications=this.deliveredNotifications.filter(()=>!e)}}async removeAllDeliveredNotifications(){for(const i of this.deliveredNotifications)i.close();this.deliveredNotifications=[]}async createChannel(){throw this.unimplemented("Not implemented on web.")}async deleteChannel(){throw this.unimplemented("Not implemented on web.")}async listChannels(){throw this.unimplemented("Not implemented on web.")}async schedule(i){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");for(const t of i.notifications)this.sendNotification(t);return{notifications:i.notifications.map(t=>({id:t.id}))}}async getPending(){return{notifications:this.pending}}async registerActionTypes(){throw this.unimplemented("Not implemented on web.")}async cancel(i){this.pending=this.pending.filter(t=>!i.notifications.find(e=>e.id===t.id))}async areEnabled(){const{display:i}=await this.checkPermissions();return{value:i==="granted"}}async changeExactNotificationSetting(){throw this.unimplemented("Not implemented on web.")}async checkExactNotificationSetting(){throw this.unimplemented("Not implemented on web.")}async requestPermissions(){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");return{display:this.transformNotificationPermission(await Notification.requestPermission())}}async checkPermissions(){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");return{display:this.transformNotificationPermission(Notification.permission)}}transformNotificationPermission(i){switch(i){case"granted":return"granted";case"denied":return"denied";default:return"prompt"}}sendPending(){var i;const t=[],e=new Date().getTime();for(const n of this.pending)!((i=n.schedule)===null||i===void 0)&&i.at&&n.schedule.at.getTime()<=e&&(this.buildNotification(n),t.push(n));this.pending=this.pending.filter(n=>!t.find(o=>o===n))}sendNotification(i){var t;if(!((t=i.schedule)===null||t===void 0)&&t.at){const e=i.schedule.at.getTime()-new Date().getTime();this.pending.push(i),setTimeout(()=>{this.sendPending()},e);return}this.buildNotification(i)}buildNotification(i){const t=new Notification(i.title,{body:i.body,tag:String(i.id)});return t.addEventListener("click",this.onClick.bind(this,i),!1),t.addEventListener("show",this.onShow.bind(this,i),!1),t.addEventListener("close",()=>{this.deliveredNotifications=this.deliveredNotifications.filter(()=>!this)},!1),this.deliveredNotifications.push(t),t}onClick(i){const t={actionId:"tap",notification:i};this.notifyListeners("localNotificationActionPerformed",t)}onShow(i){this.notifyListeners("localNotificationReceived",i)}}export{f as LocalNotificationsWeb};
@@ -1 +1 @@
1
- import{bw as a}from"./main-lr_lRuBZ.js";import{ImpactStyle as i,NotificationType as r}from"./index-Bma1pZ6y.js";import"./modulepreload-polyfill-B5Qt9EMX.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class u extends a{constructor(){super(...arguments),this.selectionStarted=!1}async impact(t){const e=this.patternForImpact(t==null?void 0:t.style);this.vibrateWithPattern(e)}async notification(t){const e=this.patternForNotification(t==null?void 0:t.type);this.vibrateWithPattern(e)}async vibrate(t){const e=(t==null?void 0:t.duration)||300;this.vibrateWithPattern([e])}async selectionStart(){this.selectionStarted=!0}async selectionChanged(){this.selectionStarted&&this.vibrateWithPattern([70])}async selectionEnd(){this.selectionStarted=!1}patternForImpact(t=i.Heavy){return t===i.Medium?[43]:t===i.Light?[20]:[61]}patternForNotification(t=r.Success){return t===r.Warning?[30,40,30,50,60]:t===r.Error?[27,45,50]:[35,65,21]}vibrateWithPattern(t){if(navigator.vibrate)navigator.vibrate(t);else throw this.unavailable("Browser does not support the vibrate API")}}export{u as HapticsWeb};
1
+ import{bw as a}from"./main-sZyyVmvr.js";import{ImpactStyle as i,NotificationType as r}from"./index-DENcwRRk.js";import"./modulepreload-polyfill-B5Qt9EMX.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class u extends a{constructor(){super(...arguments),this.selectionStarted=!1}async impact(t){const e=this.patternForImpact(t==null?void 0:t.style);this.vibrateWithPattern(e)}async notification(t){const e=this.patternForNotification(t==null?void 0:t.type);this.vibrateWithPattern(e)}async vibrate(t){const e=(t==null?void 0:t.duration)||300;this.vibrateWithPattern([e])}async selectionStart(){this.selectionStarted=!0}async selectionChanged(){this.selectionStarted&&this.vibrateWithPattern([70])}async selectionEnd(){this.selectionStarted=!1}patternForImpact(t=i.Heavy){return t===i.Medium?[43]:t===i.Light?[20]:[61]}patternForNotification(t=r.Success){return t===r.Warning?[30,40,30,50,60]:t===r.Error?[27,45,50]:[35,65,21]}vibrateWithPattern(t){if(navigator.vibrate)navigator.vibrate(t);else throw this.unavailable("Browser does not support the vibrate API")}}export{u as HapticsWeb};
package/dist/index.html CHANGED
@@ -22,7 +22,7 @@
22
22
  <link rel="icon" type="image/png" sizes="16x16" href="/assets/icons/favicon-16x16.png" />
23
23
  <link rel="apple-touch-icon" sizes="180x180" href="/assets/icons/apple-touch-icon.png" />
24
24
  <title>Tide Commander</title>
25
- <script type="module" crossorigin src="/assets/main-lr_lRuBZ.js"></script>
25
+ <script type="module" crossorigin src="/assets/main-sZyyVmvr.js"></script>
26
26
  <link rel="modulepreload" crossorigin href="/assets/modulepreload-polyfill-B5Qt9EMX.js">
27
27
  <link rel="modulepreload" crossorigin href="/assets/vendor-react--Eh9ivFN.js">
28
28
  <link rel="modulepreload" crossorigin href="/assets/vendor-three-Chj50gSY.js">
@@ -52,5 +52,22 @@ export const slackPlugin = {
52
52
  if (!integrationCtx)
53
53
  throw new Error('Slack not initialized');
54
54
  await setConfigValues(config, integrationCtx.secrets);
55
+ // Auto-connect or disconnect based on new config state
56
+ const updated = loadConfig();
57
+ const botToken = integrationCtx.secrets.get('SLACK_BOT_TOKEN');
58
+ const appToken = integrationCtx.secrets.get('SLACK_APP_TOKEN');
59
+ if (updated.enabled && botToken && appToken) {
60
+ // Tokens + enabled: (re)connect
61
+ try {
62
+ await slackClient.reconnect();
63
+ }
64
+ catch {
65
+ // Error is already captured in config status by reconnect()
66
+ }
67
+ }
68
+ else if (!updated.enabled && slackClient.isConnected()) {
69
+ // Disabled: disconnect
70
+ await slackClient.disconnect();
71
+ }
55
72
  },
56
73
  };
@@ -235,6 +235,18 @@ export function waitForReply(params) {
235
235
  replyWaiters.add(waiter);
236
236
  });
237
237
  }
238
+ // ─── Channel Management ───
239
+ export async function joinChannel(channel) {
240
+ if (!webClient)
241
+ throw new Error('Slack not connected');
242
+ const result = await webClient.conversations.join({ channel });
243
+ const ch = result.channel;
244
+ if (!ch)
245
+ throw new Error(`Failed to join channel ${channel}`);
246
+ // Update cache
247
+ channelNameCache.set(ch.id, ch.name);
248
+ return { id: ch.id, name: ch.name };
249
+ }
238
250
  // ─── Lookup ───
239
251
  export async function listChannels() {
240
252
  if (!webClient)
@@ -332,6 +344,57 @@ export async function findUserByName(displayName) {
332
344
  }
333
345
  return null;
334
346
  }
347
+ // ─── Direct Messages ───
348
+ export async function openDmChannel(userId) {
349
+ if (!webClient)
350
+ throw new Error('Slack not connected');
351
+ const result = await webClient.conversations.open({ users: userId });
352
+ const channelId = result.channel?.id;
353
+ if (!channelId)
354
+ throw new Error(`Failed to open DM channel with user ${userId}`);
355
+ return channelId;
356
+ }
357
+ export async function sendDm(params) {
358
+ const dmChannel = await openDmChannel(params.userId);
359
+ return sendMessage({
360
+ channel: dmChannel,
361
+ text: params.text,
362
+ agentId: params.agentId,
363
+ workflowInstanceId: params.workflowInstanceId,
364
+ });
365
+ }
366
+ export async function searchUsers(query) {
367
+ if (!webClient)
368
+ throw new Error('Slack not connected');
369
+ const result = await webClient.users.list({ limit: 500 });
370
+ const lower = query.toLowerCase();
371
+ const matches = [];
372
+ for (const u of result.members || []) {
373
+ if (u.deleted || u.is_bot)
374
+ continue;
375
+ const profile = u.profile;
376
+ const name = u.name || '';
377
+ const realName = u.real_name || '';
378
+ const displayName = profile?.display_name || '';
379
+ const email = profile?.email || '';
380
+ if (name.toLowerCase().includes(lower) ||
381
+ realName.toLowerCase().includes(lower) ||
382
+ displayName.toLowerCase().includes(lower) ||
383
+ email.toLowerCase().includes(lower)) {
384
+ const user = {
385
+ id: u.id,
386
+ name,
387
+ realName,
388
+ displayName: displayName || name,
389
+ email,
390
+ isBot: false,
391
+ };
392
+ userCache.set(user.id, user);
393
+ matches.push(user);
394
+ }
395
+ }
396
+ return matches;
397
+ }
335
398
  // ─── Event Subscription (for triggers) ───
336
399
  export function onMessage(callback) {
337
400
  messageListeners.add(callback);
@@ -62,7 +62,7 @@ export const slackConfigSchema = [
62
62
  key: 'SLACK_BOT_TOKEN',
63
63
  label: 'Bot Token',
64
64
  type: 'password',
65
- description: 'Slack Bot User OAuth Token (xoxb-...)',
65
+ description: 'Slack Bot User OAuth Token (xoxb-...). Found in the "OAuth & Permissions" section of your Slack app. Required scopes: channels:history, channels:read, chat:write, groups:history, groups:read, im:history, im:read, users:read.',
66
66
  placeholder: 'xoxb-...',
67
67
  required: true,
68
68
  secret: true,
@@ -100,7 +100,39 @@ router.get('/channels', async (_req, res) => {
100
100
  res.status(500).json({ error: `Failed to list channels: ${err instanceof Error ? err.message : err}` });
101
101
  }
102
102
  });
103
- // GET /api/slack/users/:userIdResolve a user
103
+ // POST /api/slack/channels/joinJoin a channel
104
+ router.post('/channels/join', async (req, res) => {
105
+ try {
106
+ const { channel } = req.body;
107
+ if (!channel) {
108
+ res.status(400).json({ error: 'channel is required' });
109
+ return;
110
+ }
111
+ const result = await slackClient.joinChannel(channel);
112
+ res.json({ success: true, channel: result });
113
+ }
114
+ catch (err) {
115
+ log.error(`Slack join channel error: ${err}`);
116
+ res.status(500).json({ error: `Failed to join channel: ${err instanceof Error ? err.message : err}` });
117
+ }
118
+ });
119
+ // GET /api/slack/users/search?q=... — Search users by name or email
120
+ router.get('/users/search', async (req, res) => {
121
+ try {
122
+ const query = req.query.q;
123
+ if (!query) {
124
+ res.status(400).json({ error: 'q query param is required' });
125
+ return;
126
+ }
127
+ const users = await slackClient.searchUsers(query);
128
+ res.json({ users });
129
+ }
130
+ catch (err) {
131
+ log.error(`Slack user search error: ${err}`);
132
+ res.status(500).json({ error: `Failed to search users: ${err instanceof Error ? err.message : err}` });
133
+ }
134
+ });
135
+ // GET /api/slack/users/:userId — Resolve a user by ID
104
136
  router.get('/users/:userId', async (req, res) => {
105
137
  try {
106
138
  const user = await slackClient.resolveUser(req.params.userId);
@@ -111,6 +143,22 @@ router.get('/users/:userId', async (req, res) => {
111
143
  res.status(500).json({ error: `Failed to resolve user: ${err instanceof Error ? err.message : err}` });
112
144
  }
113
145
  });
146
+ // POST /api/slack/dm — Send a direct message to a user
147
+ router.post('/dm', async (req, res) => {
148
+ try {
149
+ const { userId, text, agentId, workflowInstanceId } = req.body;
150
+ if (!userId || !text) {
151
+ res.status(400).json({ error: 'userId and text are required' });
152
+ return;
153
+ }
154
+ const result = await slackClient.sendDm({ userId, text, agentId, workflowInstanceId });
155
+ res.json({ success: true, ts: result.ts, channel: result.channel });
156
+ }
157
+ catch (err) {
158
+ log.error(`Slack DM error: ${err}`);
159
+ res.status(500).json({ error: `Failed to send DM: ${err instanceof Error ? err.message : err}` });
160
+ }
161
+ });
114
162
  // GET /api/slack/status — Get connection status
115
163
  router.get('/status', (_req, res) => {
116
164
  const config = loadConfig();
@@ -23,6 +23,27 @@ To reply in a thread, add \`"threadTs":"THREAD_TIMESTAMP"\`.
23
23
 
24
24
  To track which agent sent the message, add \`"agentId":"YOUR_AGENT_ID"\`.
25
25
 
26
+ ## Send a Direct Message
27
+
28
+ \`\`\`bash
29
+ curl -s -X POST http://localhost:5174/api/slack/dm \\
30
+ -H "Content-Type: application/json" \\
31
+ -d '{"userId":"U0123456789","text":"Your message here"}'
32
+ \`\`\`
33
+
34
+ Required: \`userId\` (Slack user ID), \`text\`.
35
+ Optional: \`agentId\`, \`workflowInstanceId\`.
36
+
37
+ Use the search endpoint below to find a user's ID by name or email.
38
+
39
+ ## Search Users
40
+
41
+ \`\`\`bash
42
+ curl -s "http://localhost:5174/api/slack/users/search?q=john"
43
+ \`\`\`
44
+
45
+ Searches by name, display name, real name, or email. Returns matching users with their IDs.
46
+
26
47
  ## Read Channel Messages
27
48
 
28
49
  \`\`\`bash
@@ -52,6 +73,16 @@ Optional body fields:
52
73
  - \`messagePattern\`: Regex to match reply text
53
74
  - \`timeoutMs\`: Timeout in ms (default 300000 = 5 min)
54
75
 
76
+ ## Join a Channel
77
+
78
+ \`\`\`bash
79
+ curl -s -X POST http://localhost:5174/api/slack/channels/join \\
80
+ -H "Content-Type: application/json" \\
81
+ -d '{"channel":"CHANNEL_ID"}'
82
+ \`\`\`
83
+
84
+ The bot must join a channel before it can read messages or post in it. Use the list endpoint to find channel IDs.
85
+
55
86
  ## List Channels
56
87
 
57
88
  \`\`\`bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tide-commander",
3
- "version": "1.37.2",
3
+ "version": "1.38.0",
4
4
  "description": "Visual multi-agent orchestrator and manager for Claude Code with 3D/2D interface",
5
5
  "repository": {
6
6
  "type": "git",