agent-messenger 2.37.2 → 2.38.1

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 (95) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +6 -0
  3. package/dist/package.json +1 -1
  4. package/dist/src/platforms/discord/cli.d.ts.map +1 -1
  5. package/dist/src/platforms/discord/cli.js +3 -1
  6. package/dist/src/platforms/discord/cli.js.map +1 -1
  7. package/dist/src/platforms/discord/client.d.ts +17 -1
  8. package/dist/src/platforms/discord/client.d.ts.map +1 -1
  9. package/dist/src/platforms/discord/client.js +46 -0
  10. package/dist/src/platforms/discord/client.js.map +1 -1
  11. package/dist/src/platforms/discord/commands/emoji.d.ts +13 -0
  12. package/dist/src/platforms/discord/commands/emoji.d.ts.map +1 -0
  13. package/dist/src/platforms/discord/commands/emoji.js +93 -0
  14. package/dist/src/platforms/discord/commands/emoji.js.map +1 -0
  15. package/dist/src/platforms/discord/commands/index.d.ts +2 -0
  16. package/dist/src/platforms/discord/commands/index.d.ts.map +1 -1
  17. package/dist/src/platforms/discord/commands/index.js +2 -0
  18. package/dist/src/platforms/discord/commands/index.js.map +1 -1
  19. package/dist/src/platforms/discord/commands/server.d.ts.map +1 -1
  20. package/dist/src/platforms/discord/commands/server.js +16 -1
  21. package/dist/src/platforms/discord/commands/server.js.map +1 -1
  22. package/dist/src/platforms/discord/commands/sticker.d.ts +15 -0
  23. package/dist/src/platforms/discord/commands/sticker.d.ts.map +1 -0
  24. package/dist/src/platforms/discord/commands/sticker.js +97 -0
  25. package/dist/src/platforms/discord/commands/sticker.js.map +1 -0
  26. package/dist/src/platforms/discord/expression-format.d.ts +41 -0
  27. package/dist/src/platforms/discord/expression-format.d.ts.map +1 -0
  28. package/dist/src/platforms/discord/expression-format.js +107 -0
  29. package/dist/src/platforms/discord/expression-format.js.map +1 -0
  30. package/dist/src/platforms/discord/expression-names.d.ts +9 -0
  31. package/dist/src/platforms/discord/expression-names.d.ts.map +1 -0
  32. package/dist/src/platforms/discord/expression-names.js +33 -0
  33. package/dist/src/platforms/discord/expression-names.js.map +1 -0
  34. package/dist/src/platforms/discord/index.d.ts +1 -1
  35. package/dist/src/platforms/discord/index.d.ts.map +1 -1
  36. package/dist/src/platforms/discord/index.js.map +1 -1
  37. package/dist/src/platforms/discord/types.d.ts +29 -0
  38. package/dist/src/platforms/discord/types.d.ts.map +1 -1
  39. package/dist/src/platforms/discord/types.js.map +1 -1
  40. package/dist/src/platforms/slack/cli.d.ts.map +1 -1
  41. package/dist/src/platforms/slack/cli.js +5 -0
  42. package/dist/src/platforms/slack/cli.js.map +1 -1
  43. package/dist/src/platforms/slack/commands/auth.d.ts.map +1 -1
  44. package/dist/src/platforms/slack/commands/auth.js +9 -3
  45. package/dist/src/platforms/slack/commands/auth.js.map +1 -1
  46. package/dist/src/platforms/slack/credential-manager.d.ts +2 -0
  47. package/dist/src/platforms/slack/credential-manager.d.ts.map +1 -1
  48. package/dist/src/platforms/slack/credential-manager.js +17 -3
  49. package/dist/src/platforms/slack/credential-manager.js.map +1 -1
  50. package/docs/content/docs/cli/discord.mdx +73 -0
  51. package/docs/content/docs/cli/slack.mdx +8 -1
  52. package/docs/content/docs/sdk/discord.mdx +50 -0
  53. package/package.json +1 -1
  54. package/skills/agent-channeltalk/SKILL.md +1 -1
  55. package/skills/agent-channeltalkbot/SKILL.md +1 -1
  56. package/skills/agent-discord/SKILL.md +69 -1
  57. package/skills/agent-discord/references/common-patterns.md +77 -0
  58. package/skills/agent-discordbot/SKILL.md +1 -1
  59. package/skills/agent-imessage/SKILL.md +1 -1
  60. package/skills/agent-instagram/SKILL.md +1 -1
  61. package/skills/agent-kakaotalk/SKILL.md +1 -1
  62. package/skills/agent-line/SKILL.md +1 -1
  63. package/skills/agent-slack/SKILL.md +9 -2
  64. package/skills/agent-slack/references/authentication.md +12 -0
  65. package/skills/agent-slack/references/common-patterns.md +18 -0
  66. package/skills/agent-slackbot/SKILL.md +1 -1
  67. package/skills/agent-teams/SKILL.md +1 -1
  68. package/skills/agent-telegram/SKILL.md +1 -1
  69. package/skills/agent-telegrambot/SKILL.md +1 -1
  70. package/skills/agent-webex/SKILL.md +1 -1
  71. package/skills/agent-webexbot/SKILL.md +1 -1
  72. package/skills/agent-wechatbot/SKILL.md +1 -1
  73. package/skills/agent-whatsapp/SKILL.md +1 -1
  74. package/skills/agent-whatsappbot/SKILL.md +1 -1
  75. package/src/platforms/discord/cli.ts +4 -0
  76. package/src/platforms/discord/client.test.ts +133 -0
  77. package/src/platforms/discord/client.ts +72 -0
  78. package/src/platforms/discord/commands/emoji.test.ts +122 -0
  79. package/src/platforms/discord/commands/emoji.ts +124 -0
  80. package/src/platforms/discord/commands/index.ts +2 -0
  81. package/src/platforms/discord/commands/server.test.ts +45 -1
  82. package/src/platforms/discord/commands/server.ts +21 -1
  83. package/src/platforms/discord/commands/sticker.test.ts +118 -0
  84. package/src/platforms/discord/commands/sticker.ts +135 -0
  85. package/src/platforms/discord/expression-format.test.ts +107 -0
  86. package/src/platforms/discord/expression-format.ts +117 -0
  87. package/src/platforms/discord/expression-names.test.ts +41 -0
  88. package/src/platforms/discord/expression-names.ts +36 -0
  89. package/src/platforms/discord/index.ts +2 -0
  90. package/src/platforms/discord/types.ts +31 -0
  91. package/src/platforms/slack/cli.test.ts +111 -1
  92. package/src/platforms/slack/cli.ts +5 -0
  93. package/src/platforms/slack/commands/auth.ts +14 -3
  94. package/src/platforms/slack/credential-manager.ts +21 -3
  95. package/src/platforms/slack/credential-manager.workspace-select.test.ts +70 -0
@@ -1,4 +1,7 @@
1
- import { describe, expect, it } from 'bun:test'
1
+ import { afterEach, beforeEach, describe, expect, it } from 'bun:test'
2
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
3
+ import { tmpdir } from 'node:os'
4
+ import { join } from 'node:path'
2
5
 
3
6
  import { spawn } from 'bun'
4
7
 
@@ -94,4 +97,111 @@ describe('CLI Framework', () => {
94
97
  expect(output.trim()).toBe(pkg.version)
95
98
  })
96
99
  })
100
+
101
+ describe('global --workspace flag with two stored workspaces', () => {
102
+ let sandbox: string
103
+ let preloadPath: string
104
+
105
+ const credentialsPath = () => join(sandbox, 'slack-credentials.json')
106
+ const readConfig = () => JSON.parse(readFileSync(credentialsPath(), 'utf-8'))
107
+ const workspace = (id: string) => ({
108
+ workspace_id: id,
109
+ workspace_name: `Workspace ${id}`,
110
+ token: `xoxc-${id}`,
111
+ cookie: `xoxd-${id}`,
112
+ })
113
+
114
+ async function runCli(...args: string[]) {
115
+ const env: Record<string, string | undefined> = {
116
+ ...process.env,
117
+ AGENT_MESSENGER_CONFIG_DIR: sandbox,
118
+ HOME: sandbox,
119
+ }
120
+ for (const key of Object.keys(env)) {
121
+ if (key.startsWith('E2E_SLACK_')) delete env[key]
122
+ }
123
+ const proc = spawn(['bun', '--preload', preloadPath, './src/platforms/slack/cli.ts', ...args], {
124
+ cwd: process.cwd(),
125
+ env,
126
+ stdio: ['ignore', 'pipe', 'pipe'],
127
+ })
128
+ const [stdout, stderr, exitCode] = await Promise.all([
129
+ new Response(proc.stdout).text(),
130
+ new Response(proc.stderr).text(),
131
+ proc.exited,
132
+ ])
133
+ return { stdout, stderr, exitCode }
134
+ }
135
+
136
+ beforeEach(() => {
137
+ sandbox = mkdtempSync(join(tmpdir(), 'agent-slack-workspace-flag-'))
138
+ writeFileSync(
139
+ credentialsPath(),
140
+ JSON.stringify({ current_workspace: 'T1', workspaces: { T1: workspace('T1'), T2: workspace('T2') } }),
141
+ )
142
+ // Answer auth.test offline from the token, so the real CLI never reaches Slack.
143
+ preloadPath = join(sandbox, 'fake-slack-api.ts')
144
+ writeFileSync(
145
+ preloadPath,
146
+ `import { WebClient } from ${JSON.stringify(import.meta.resolve('@slack/web-api'))}
147
+ WebClient.prototype.apiCall = async function (method) {
148
+ if (method !== 'auth.test') throw new Error('unexpected Slack API call: ' + method)
149
+ const teamId = String(this.token).replace('xoxc-', '')
150
+ return { ok: true, team_id: teamId, team: 'Workspace ' + teamId, user_id: 'U1', user: 'tester' }
151
+ }
152
+ `,
153
+ )
154
+ })
155
+
156
+ afterEach(() => {
157
+ rmSync(sandbox, { recursive: true, force: true })
158
+ })
159
+
160
+ it('targets the selected workspace for an ordinary command', async () => {
161
+ const result = await runCli('--workspace', 'T2', 'workspace', 'current')
162
+
163
+ expect(result.exitCode).toBe(0)
164
+ expect(JSON.parse(result.stdout).workspace_id).toBe('T2')
165
+ expect(readConfig().current_workspace).toBe('T1')
166
+ })
167
+
168
+ it('auth status reports the selected workspace', async () => {
169
+ const result = await runCli('--workspace', 'T2', 'auth', 'status')
170
+
171
+ expect(result.exitCode).toBe(0)
172
+ expect(JSON.parse(result.stdout)).toMatchObject({ workspace_id: 'T2', valid: true })
173
+ expect(readConfig().current_workspace).toBe('T1')
174
+ })
175
+
176
+ it('auth logout removes the selected workspace and keeps the current one', async () => {
177
+ const result = await runCli('--workspace', 'T2', 'auth', 'logout')
178
+
179
+ expect(result.exitCode).toBe(0)
180
+ expect(JSON.parse(result.stdout)).toEqual({ removed: 'T2', success: true })
181
+ const config = readConfig()
182
+ expect(config.current_workspace).toBe('T1')
183
+ expect(config.workspaces).toEqual({ T1: workspace('T1') })
184
+ })
185
+
186
+ it('auth status fails for a selected workspace that is not stored', async () => {
187
+ const result = await runCli('--workspace', 'T3', 'auth', 'status')
188
+
189
+ expect(result.exitCode).toBe(1)
190
+ expect(JSON.parse(result.stdout).error).toContain('T3')
191
+ expect(readConfig().current_workspace).toBe('T1')
192
+ })
193
+
194
+ it('without the flag, auth status and logout still use the current workspace', async () => {
195
+ const status = await runCli('auth', 'status')
196
+ expect(status.exitCode).toBe(0)
197
+ expect(JSON.parse(status.stdout).workspace_id).toBe('T1')
198
+
199
+ const logout = await runCli('auth', 'logout')
200
+ expect(logout.exitCode).toBe(0)
201
+ expect(JSON.parse(logout.stdout)).toEqual({ removed: 'T1', success: true })
202
+ const config = readConfig()
203
+ expect(config.current_workspace).toBeNull()
204
+ expect(config.workspaces).toEqual({ T2: workspace('T2') })
205
+ })
206
+ })
97
207
  })
@@ -25,6 +25,7 @@ import {
25
25
  whoamiCommand,
26
26
  workspaceCommand,
27
27
  } from './commands/index'
28
+ import { setSelectedWorkspaceId } from './credential-manager'
28
29
  import { ensureSlackAuth } from './ensure-auth'
29
30
 
30
31
  function isAuthCommand(command: CommandType): boolean {
@@ -47,6 +48,10 @@ program
47
48
  .option('--workspace <id>', 'Use specific workspace')
48
49
 
49
50
  program.hook('preAction', async (_thisCommand, actionCommand) => {
51
+ const workspaceId = program.opts<{ workspace?: string }>().workspace
52
+ if (workspaceId) {
53
+ setSelectedWorkspaceId(workspaceId)
54
+ }
50
55
  if (isAuthCommand(actionCommand)) return
51
56
  await ensureSlackAuth()
52
57
  })
@@ -7,7 +7,7 @@ import { formatOutput } from '@/shared/utils/output'
7
7
  import { debug } from '@/shared/utils/stderr'
8
8
 
9
9
  import { SlackClient, SlackError } from '../client'
10
- import { CredentialManager } from '../credential-manager'
10
+ import { CredentialManager, getSelectedWorkspaceId } from '../credential-manager'
11
11
  import { refreshCookie, refreshKnownWorkspaceDomains, tryWebTokenRefresh } from '../ensure-auth'
12
12
  import type { RefreshResult } from '../ensure-auth'
13
13
  import { loginWithQr } from '../qr-http-login'
@@ -192,7 +192,7 @@ async function logoutAction(workspace: string | undefined, options: { pretty?: b
192
192
  const credManager = new CredentialManager()
193
193
  const config = await credManager.load()
194
194
 
195
- let targetWorkspace = workspace
195
+ let targetWorkspace = workspace ?? getSelectedWorkspaceId() ?? undefined
196
196
 
197
197
  if (!targetWorkspace) {
198
198
  if (!config.current_workspace) {
@@ -229,7 +229,18 @@ async function statusAction(options: { pretty?: boolean }): Promise<void> {
229
229
  const ws = await credManager.getWorkspace()
230
230
 
231
231
  if (!ws) {
232
- console.log(formatOutput({ error: 'No current workspace set. Run "auth extract" first.' }, options.pretty))
232
+ const selectedWorkspace = getSelectedWorkspaceId()
233
+ console.log(
234
+ formatOutput(
235
+ selectedWorkspace
236
+ ? {
237
+ error: `Workspace not found: ${selectedWorkspace}`,
238
+ hint: 'Run "workspace list" to see available workspaces.',
239
+ }
240
+ : { error: 'No current workspace set. Run "auth extract" first.' },
241
+ options.pretty,
242
+ ),
243
+ )
233
244
  process.exit(1)
234
245
  }
235
246
 
@@ -5,6 +5,20 @@ import { join } from 'node:path'
5
5
  import { getConfigDir } from '../../shared/utils/config-dir'
6
6
  import type { Config, WorkspaceCredentials } from './types'
7
7
 
8
+ // Process-wide workspace selection set from the CLI `--workspace <id>` global flag (see
9
+ // the preAction hook in cli.ts). When present it makes commands target that workspace for
10
+ // this single CLI invocation WITHOUT mutating the persisted current_workspace, so the
11
+ // default (no-flag) path and any long-lived watcher keep resolving to current_workspace.
12
+ let selectedWorkspaceId: string | null = null
13
+
14
+ export function setSelectedWorkspaceId(id: string | null): void {
15
+ selectedWorkspaceId = id
16
+ }
17
+
18
+ export function getSelectedWorkspaceId(): string | null {
19
+ return selectedWorkspaceId
20
+ }
21
+
8
22
  export class SlackCredentialManager {
9
23
  private configDir: string
10
24
  private credentialsPath: string
@@ -34,6 +48,10 @@ export class SlackCredentialManager {
34
48
  }
35
49
 
36
50
  async getWorkspace(id?: string): Promise<WorkspaceCredentials | null> {
51
+ // An explicit id wins; otherwise fall back to the process-wide `--workspace` selection.
52
+ // When neither is set, behaviour is unchanged (resolves to current_workspace below).
53
+ const resolvedId = id ?? selectedWorkspaceId ?? undefined
54
+
37
55
  // Check env vars first (take precedence over file-based credentials)
38
56
  // Only use env credentials if no specific id requested, or id matches env workspace
39
57
  const envToken = process.env.E2E_SLACK_TOKEN
@@ -42,7 +60,7 @@ export class SlackCredentialManager {
42
60
  const envWorkspaceName = process.env.E2E_SLACK_WORKSPACE_NAME
43
61
 
44
62
  if (envToken && envCookie && envWorkspaceId && envWorkspaceName) {
45
- if (!id || id === envWorkspaceId) {
63
+ if (!resolvedId || resolvedId === envWorkspaceId) {
46
64
  return {
47
65
  token: envToken,
48
66
  cookie: envCookie,
@@ -54,8 +72,8 @@ export class SlackCredentialManager {
54
72
 
55
73
  const config = await this.load()
56
74
 
57
- if (id) {
58
- return config.workspaces[id] ?? null
75
+ if (resolvedId) {
76
+ return config.workspaces[resolvedId] ?? null
59
77
  }
60
78
 
61
79
  if (!config.current_workspace) {
@@ -0,0 +1,70 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from 'bun:test'
2
+ import { rmSync } from 'node:fs'
3
+ import { join } from 'node:path'
4
+
5
+ import { CredentialManager, setSelectedWorkspaceId } from '@/platforms/slack/credential-manager'
6
+ import type { WorkspaceCredentials } from '@/platforms/slack/types'
7
+
8
+ const testConfigDir = join(import.meta.dir, '.test-config-ws-select')
9
+
10
+ const current: WorkspaceCredentials = {
11
+ workspace_id: 'ws-current',
12
+ workspace_name: 'Current',
13
+ token: 'xoxc-current',
14
+ cookie: 'xoxd-current',
15
+ }
16
+ const other: WorkspaceCredentials = {
17
+ workspace_id: 'ws-other',
18
+ workspace_name: 'Other',
19
+ token: 'xoxc-other',
20
+ cookie: 'xoxd-other',
21
+ }
22
+
23
+ describe('CredentialManager global --workspace selection', () => {
24
+ let manager: CredentialManager
25
+
26
+ beforeEach(async () => {
27
+ rmSync(testConfigDir, { recursive: true, force: true })
28
+ manager = new CredentialManager(testConfigDir)
29
+ setSelectedWorkspaceId(null)
30
+ await manager.setWorkspace(current)
31
+ await manager.setWorkspace(other)
32
+ await manager.setCurrentWorkspace('ws-current')
33
+ })
34
+
35
+ afterEach(() => {
36
+ setSelectedWorkspaceId(null)
37
+ rmSync(testConfigDir, { recursive: true, force: true })
38
+ })
39
+
40
+ it('getWorkspace() returns the selected workspace over current when no id is passed', async () => {
41
+ setSelectedWorkspaceId('ws-other')
42
+
43
+ const retrieved = await manager.getWorkspace()
44
+
45
+ expect(retrieved).toEqual(other)
46
+ })
47
+
48
+ it('selecting a workspace does not mutate current_workspace', async () => {
49
+ setSelectedWorkspaceId('ws-other')
50
+
51
+ await manager.getWorkspace()
52
+
53
+ const config = await manager.load()
54
+ expect(config.current_workspace).toBe('ws-current')
55
+ })
56
+
57
+ it('falls back to current when nothing is selected (no regression)', async () => {
58
+ const retrieved = await manager.getWorkspace()
59
+
60
+ expect(retrieved).toEqual(current)
61
+ })
62
+
63
+ it('an explicit id argument takes precedence over the selection', async () => {
64
+ setSelectedWorkspaceId('ws-current')
65
+
66
+ const retrieved = await manager.getWorkspace('ws-other')
67
+
68
+ expect(retrieved).toEqual(other)
69
+ })
70
+ })