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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +6 -0
- package/dist/package.json +1 -1
- package/dist/src/platforms/discord/cli.d.ts.map +1 -1
- package/dist/src/platforms/discord/cli.js +3 -1
- package/dist/src/platforms/discord/cli.js.map +1 -1
- package/dist/src/platforms/discord/client.d.ts +17 -1
- package/dist/src/platforms/discord/client.d.ts.map +1 -1
- package/dist/src/platforms/discord/client.js +46 -0
- package/dist/src/platforms/discord/client.js.map +1 -1
- package/dist/src/platforms/discord/commands/emoji.d.ts +13 -0
- package/dist/src/platforms/discord/commands/emoji.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/emoji.js +93 -0
- package/dist/src/platforms/discord/commands/emoji.js.map +1 -0
- package/dist/src/platforms/discord/commands/index.d.ts +2 -0
- package/dist/src/platforms/discord/commands/index.d.ts.map +1 -1
- package/dist/src/platforms/discord/commands/index.js +2 -0
- package/dist/src/platforms/discord/commands/index.js.map +1 -1
- package/dist/src/platforms/discord/commands/server.d.ts.map +1 -1
- package/dist/src/platforms/discord/commands/server.js +16 -1
- package/dist/src/platforms/discord/commands/server.js.map +1 -1
- package/dist/src/platforms/discord/commands/sticker.d.ts +15 -0
- package/dist/src/platforms/discord/commands/sticker.d.ts.map +1 -0
- package/dist/src/platforms/discord/commands/sticker.js +97 -0
- package/dist/src/platforms/discord/commands/sticker.js.map +1 -0
- package/dist/src/platforms/discord/expression-format.d.ts +41 -0
- package/dist/src/platforms/discord/expression-format.d.ts.map +1 -0
- package/dist/src/platforms/discord/expression-format.js +107 -0
- package/dist/src/platforms/discord/expression-format.js.map +1 -0
- package/dist/src/platforms/discord/expression-names.d.ts +9 -0
- package/dist/src/platforms/discord/expression-names.d.ts.map +1 -0
- package/dist/src/platforms/discord/expression-names.js +33 -0
- package/dist/src/platforms/discord/expression-names.js.map +1 -0
- package/dist/src/platforms/discord/index.d.ts +1 -1
- package/dist/src/platforms/discord/index.d.ts.map +1 -1
- package/dist/src/platforms/discord/index.js.map +1 -1
- package/dist/src/platforms/discord/types.d.ts +29 -0
- package/dist/src/platforms/discord/types.d.ts.map +1 -1
- package/dist/src/platforms/discord/types.js.map +1 -1
- package/dist/src/platforms/slack/cli.d.ts.map +1 -1
- package/dist/src/platforms/slack/cli.js +5 -0
- package/dist/src/platforms/slack/cli.js.map +1 -1
- package/dist/src/platforms/slack/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/slack/commands/auth.js +9 -3
- package/dist/src/platforms/slack/commands/auth.js.map +1 -1
- package/dist/src/platforms/slack/credential-manager.d.ts +2 -0
- package/dist/src/platforms/slack/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/slack/credential-manager.js +17 -3
- package/dist/src/platforms/slack/credential-manager.js.map +1 -1
- package/docs/content/docs/cli/discord.mdx +73 -0
- package/docs/content/docs/cli/slack.mdx +8 -1
- package/docs/content/docs/sdk/discord.mdx +50 -0
- package/package.json +1 -1
- package/skills/agent-channeltalk/SKILL.md +1 -1
- package/skills/agent-channeltalkbot/SKILL.md +1 -1
- package/skills/agent-discord/SKILL.md +69 -1
- package/skills/agent-discord/references/common-patterns.md +77 -0
- package/skills/agent-discordbot/SKILL.md +1 -1
- package/skills/agent-imessage/SKILL.md +1 -1
- package/skills/agent-instagram/SKILL.md +1 -1
- package/skills/agent-kakaotalk/SKILL.md +1 -1
- package/skills/agent-line/SKILL.md +1 -1
- package/skills/agent-slack/SKILL.md +9 -2
- package/skills/agent-slack/references/authentication.md +12 -0
- package/skills/agent-slack/references/common-patterns.md +18 -0
- package/skills/agent-slackbot/SKILL.md +1 -1
- package/skills/agent-teams/SKILL.md +1 -1
- package/skills/agent-telegram/SKILL.md +1 -1
- package/skills/agent-telegrambot/SKILL.md +1 -1
- package/skills/agent-webex/SKILL.md +1 -1
- package/skills/agent-webexbot/SKILL.md +1 -1
- package/skills/agent-wechatbot/SKILL.md +1 -1
- package/skills/agent-whatsapp/SKILL.md +1 -1
- package/skills/agent-whatsappbot/SKILL.md +1 -1
- package/src/platforms/discord/cli.ts +4 -0
- package/src/platforms/discord/client.test.ts +133 -0
- package/src/platforms/discord/client.ts +72 -0
- package/src/platforms/discord/commands/emoji.test.ts +122 -0
- package/src/platforms/discord/commands/emoji.ts +124 -0
- package/src/platforms/discord/commands/index.ts +2 -0
- package/src/platforms/discord/commands/server.test.ts +45 -1
- package/src/platforms/discord/commands/server.ts +21 -1
- package/src/platforms/discord/commands/sticker.test.ts +118 -0
- package/src/platforms/discord/commands/sticker.ts +135 -0
- package/src/platforms/discord/expression-format.test.ts +107 -0
- package/src/platforms/discord/expression-format.ts +117 -0
- package/src/platforms/discord/expression-names.test.ts +41 -0
- package/src/platforms/discord/expression-names.ts +36 -0
- package/src/platforms/discord/index.ts +2 -0
- package/src/platforms/discord/types.ts +31 -0
- package/src/platforms/slack/cli.test.ts +111 -1
- package/src/platforms/slack/cli.ts +5 -0
- package/src/platforms/slack/commands/auth.ts +14 -3
- package/src/platforms/slack/credential-manager.ts +21 -3
- 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
|
-
|
|
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 (!
|
|
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 (
|
|
58
|
-
return config.workspaces[
|
|
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
|
+
})
|