agent-messenger 2.0.0 → 2.1.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 (119) hide show
  1. package/.claude-plugin/marketplace.json +14 -1
  2. package/.claude-plugin/plugin.json +4 -2
  3. package/README.md +33 -29
  4. package/dist/package.json +10 -2
  5. package/dist/src/cli.d.ts.map +1 -1
  6. package/dist/src/cli.js +3 -0
  7. package/dist/src/cli.js.map +1 -1
  8. package/dist/src/platforms/webex/app-config.d.ts +7 -0
  9. package/dist/src/platforms/webex/app-config.d.ts.map +1 -0
  10. package/dist/src/platforms/webex/app-config.js +20 -0
  11. package/dist/src/platforms/webex/app-config.js.map +1 -0
  12. package/dist/src/platforms/webex/cli.d.ts +5 -0
  13. package/dist/src/platforms/webex/cli.d.ts.map +1 -0
  14. package/dist/src/platforms/webex/cli.js +32 -0
  15. package/dist/src/platforms/webex/cli.js.map +1 -0
  16. package/dist/src/platforms/webex/client.d.ts +45 -0
  17. package/dist/src/platforms/webex/client.d.ts.map +1 -0
  18. package/dist/src/platforms/webex/client.js +175 -0
  19. package/dist/src/platforms/webex/client.js.map +1 -0
  20. package/dist/src/platforms/webex/commands/auth.d.ts +15 -0
  21. package/dist/src/platforms/webex/commands/auth.d.ts.map +1 -0
  22. package/dist/src/platforms/webex/commands/auth.js +124 -0
  23. package/dist/src/platforms/webex/commands/auth.js.map +1 -0
  24. package/dist/src/platforms/webex/commands/index.d.ts +6 -0
  25. package/dist/src/platforms/webex/commands/index.d.ts.map +1 -0
  26. package/dist/src/platforms/webex/commands/index.js +6 -0
  27. package/dist/src/platforms/webex/commands/index.js.map +1 -0
  28. package/dist/src/platforms/webex/commands/member.d.ts +7 -0
  29. package/dist/src/platforms/webex/commands/member.d.ts.map +1 -0
  30. package/dist/src/platforms/webex/commands/member.js +34 -0
  31. package/dist/src/platforms/webex/commands/member.js.map +1 -0
  32. package/dist/src/platforms/webex/commands/message.d.ts +26 -0
  33. package/dist/src/platforms/webex/commands/message.d.ts.map +1 -0
  34. package/dist/src/platforms/webex/commands/message.js +153 -0
  35. package/dist/src/platforms/webex/commands/message.js.map +1 -0
  36. package/dist/src/platforms/webex/commands/snapshot.d.ts +9 -0
  37. package/dist/src/platforms/webex/commands/snapshot.d.ts.map +1 -0
  38. package/dist/src/platforms/webex/commands/snapshot.js +72 -0
  39. package/dist/src/platforms/webex/commands/snapshot.js.map +1 -0
  40. package/dist/src/platforms/webex/commands/space.d.ts +11 -0
  41. package/dist/src/platforms/webex/commands/space.d.ts.map +1 -0
  42. package/dist/src/platforms/webex/commands/space.js +59 -0
  43. package/dist/src/platforms/webex/commands/space.js.map +1 -0
  44. package/dist/src/platforms/webex/credential-manager.d.ts +23 -0
  45. package/dist/src/platforms/webex/credential-manager.d.ts.map +1 -0
  46. package/dist/src/platforms/webex/credential-manager.js +148 -0
  47. package/dist/src/platforms/webex/credential-manager.js.map +1 -0
  48. package/dist/src/platforms/webex/ensure-auth.d.ts +2 -0
  49. package/dist/src/platforms/webex/ensure-auth.d.ts.map +1 -0
  50. package/dist/src/platforms/webex/ensure-auth.js +20 -0
  51. package/dist/src/platforms/webex/ensure-auth.js.map +1 -0
  52. package/dist/src/platforms/webex/index.d.ts +6 -0
  53. package/dist/src/platforms/webex/index.d.ts.map +1 -0
  54. package/dist/src/platforms/webex/index.js +5 -0
  55. package/dist/src/platforms/webex/index.js.map +1 -0
  56. package/dist/src/platforms/webex/types.d.ts +124 -0
  57. package/dist/src/platforms/webex/types.d.ts.map +1 -0
  58. package/dist/src/platforms/webex/types.js +63 -0
  59. package/dist/src/platforms/webex/types.js.map +1 -0
  60. package/dist/src/tui/adapters/webex-adapter.d.ts +14 -0
  61. package/dist/src/tui/adapters/webex-adapter.d.ts.map +1 -0
  62. package/dist/src/tui/adapters/webex-adapter.js +79 -0
  63. package/dist/src/tui/adapters/webex-adapter.js.map +1 -0
  64. package/dist/src/tui/app.d.ts.map +1 -1
  65. package/dist/src/tui/app.js +2 -0
  66. package/dist/src/tui/app.js.map +1 -1
  67. package/docs/content/docs/cli/meta.json +1 -0
  68. package/docs/content/docs/cli/webex.mdx +291 -0
  69. package/docs/content/docs/sdk/meta.json +1 -1
  70. package/docs/content/docs/sdk/webex.mdx +260 -0
  71. package/docs/content/docs/tui.mdx +4 -3
  72. package/docs/src/app/page.tsx +2 -2
  73. package/package.json +10 -2
  74. package/skills/agent-channeltalk/SKILL.md +1 -1
  75. package/skills/agent-channeltalkbot/SKILL.md +1 -1
  76. package/skills/agent-discord/SKILL.md +1 -1
  77. package/skills/agent-discordbot/SKILL.md +1 -1
  78. package/skills/agent-instagram/SKILL.md +1 -1
  79. package/skills/agent-kakaotalk/SKILL.md +1 -1
  80. package/skills/agent-line/SKILL.md +1 -1
  81. package/skills/agent-slack/SKILL.md +1 -1
  82. package/skills/agent-slackbot/SKILL.md +1 -1
  83. package/skills/agent-teams/SKILL.md +1 -1
  84. package/skills/agent-telegram/SKILL.md +1 -1
  85. package/skills/agent-webex/SKILL.md +386 -0
  86. package/skills/agent-webex/references/authentication.md +318 -0
  87. package/skills/agent-webex/references/common-patterns.md +723 -0
  88. package/skills/agent-webex/templates/monitor-space.sh +165 -0
  89. package/skills/agent-webex/templates/post-message.sh +170 -0
  90. package/skills/agent-whatsapp/SKILL.md +1 -1
  91. package/skills/agent-whatsappbot/SKILL.md +1 -1
  92. package/src/cli.ts +4 -0
  93. package/src/platforms/webex/app-config.test.ts +98 -0
  94. package/src/platforms/webex/app-config.ts +31 -0
  95. package/src/platforms/webex/cli.test.ts +58 -0
  96. package/src/platforms/webex/cli.ts +39 -0
  97. package/src/platforms/webex/client.test.ts +429 -0
  98. package/src/platforms/webex/client.ts +247 -0
  99. package/src/platforms/webex/commands/auth.test.ts +222 -0
  100. package/src/platforms/webex/commands/auth.ts +180 -0
  101. package/src/platforms/webex/commands/index.ts +5 -0
  102. package/src/platforms/webex/commands/member.test.ts +103 -0
  103. package/src/platforms/webex/commands/member.ts +45 -0
  104. package/src/platforms/webex/commands/message.test.ts +231 -0
  105. package/src/platforms/webex/commands/message.ts +204 -0
  106. package/src/platforms/webex/commands/snapshot.test.ts +96 -0
  107. package/src/platforms/webex/commands/snapshot.ts +91 -0
  108. package/src/platforms/webex/commands/space.test.ts +206 -0
  109. package/src/platforms/webex/commands/space.ts +74 -0
  110. package/src/platforms/webex/credential-manager.test.ts +314 -0
  111. package/src/platforms/webex/credential-manager.ts +197 -0
  112. package/src/platforms/webex/ensure-auth.test.ts +85 -0
  113. package/src/platforms/webex/ensure-auth.ts +19 -0
  114. package/src/platforms/webex/index.test.ts +25 -0
  115. package/src/platforms/webex/index.ts +17 -0
  116. package/src/platforms/webex/types.test.ts +307 -0
  117. package/src/platforms/webex/types.ts +127 -0
  118. package/src/tui/adapters/webex-adapter.ts +103 -0
  119. package/src/tui/app.ts +2 -0
@@ -0,0 +1,165 @@
1
+ #!/bin/bash
2
+ #
3
+ # monitor-space.sh - Monitor a Webex space for new messages
4
+ #
5
+ # Usage:
6
+ # ./monitor-space.sh <space-id> [interval]
7
+ #
8
+ # Arguments:
9
+ # space-id - Space ID to monitor (use 'space list' to find IDs)
10
+ # interval - Polling interval in seconds (default: 10)
11
+ #
12
+ # Example:
13
+ # ./monitor-space.sh "Y2lzY29zcGFyazovL..."
14
+ # ./monitor-space.sh "Y2lzY29zcGFyazovL..." 5
15
+
16
+ set -euo pipefail
17
+
18
+ if [ $# -lt 1 ]; then
19
+ echo "Usage: $0 <space-id> [interval]"
20
+ echo ""
21
+ echo "Examples:"
22
+ echo " $0 'Y2lzY29zcGFyazovL...' # Monitor space, poll every 10s"
23
+ echo " $0 'Y2lzY29zcGFyazovL...' 5 # Monitor space, poll every 5s"
24
+ echo ""
25
+ echo "To find space IDs, run: agent-webex space list"
26
+ exit 1
27
+ fi
28
+
29
+ SPACE_ID="$1"
30
+ INTERVAL="${2:-10}"
31
+
32
+ RED='\033[0;31m'
33
+ GREEN='\033[0;32m'
34
+ YELLOW='\033[1;33m'
35
+ BLUE='\033[0;34m'
36
+ NC='\033[0m'
37
+
38
+ LAST_ID=""
39
+ FIRST_RUN=true
40
+
41
+ format_time() {
42
+ local ts=$1
43
+ if command -v gdate &> /dev/null; then
44
+ gdate -d "$ts" "+%Y-%m-%d %H:%M:%S" 2>/dev/null || echo "$ts"
45
+ else
46
+ date -d "$ts" "+%Y-%m-%d %H:%M:%S" 2>/dev/null || echo "$ts"
47
+ fi
48
+ }
49
+
50
+ truncate_text() {
51
+ local text=$1
52
+ local max_length=100
53
+
54
+ if [ ${#text} -gt $max_length ]; then
55
+ echo "${text:0:$max_length}..."
56
+ else
57
+ echo "$text"
58
+ fi
59
+ }
60
+
61
+ check_messages() {
62
+ MESSAGES=$(agent-webex message list "$SPACE_ID" --limit 1 2>&1)
63
+
64
+ if echo "$MESSAGES" | jq -e '.error' > /dev/null 2>&1; then
65
+ ERROR_MSG=$(echo "$MESSAGES" | jq -r '.error // "Unknown error"')
66
+ echo -e "${RED}Error: $ERROR_MSG${NC}"
67
+
68
+ if echo "$ERROR_MSG" | grep -Eqi "401|unauthorized"; then
69
+ echo -e "${RED}Token expired or invalid. Get a new token from https://developer.webex.com${NC}"
70
+ exit 1
71
+ fi
72
+
73
+ return 1
74
+ fi
75
+
76
+ LATEST_ID=$(echo "$MESSAGES" | jq -r '.[0].id // ""')
77
+
78
+ if [ -z "$LATEST_ID" ]; then
79
+ if [ "$FIRST_RUN" = true ]; then
80
+ echo -e "${YELLOW}No messages in space yet${NC}"
81
+ fi
82
+ FIRST_RUN=false
83
+ return 0
84
+ fi
85
+
86
+ if [ "$LATEST_ID" != "$LAST_ID" ]; then
87
+ if [ "$FIRST_RUN" = false ] && [ -n "$LAST_ID" ]; then
88
+ TEXT=$(echo "$MESSAGES" | jq -r '.[0].text // ""')
89
+ AUTHOR=$(echo "$MESSAGES" | jq -r '.[0].personEmail // "Unknown"')
90
+ TIMESTAMP=$(echo "$MESSAGES" | jq -r '.[0].created // ""')
91
+
92
+ TIME=$(format_time "$TIMESTAMP")
93
+
94
+ echo ""
95
+ echo -e "${GREEN}----------------------------------------------${NC}"
96
+ echo -e "${BLUE}New message in space${NC}"
97
+ echo -e "${GREEN}----------------------------------------------${NC}"
98
+ echo -e "Time: $TIME"
99
+ echo -e "From: $AUTHOR"
100
+ echo -e "Message: $(truncate_text "$TEXT")"
101
+ echo -e "${GREEN}----------------------------------------------${NC}"
102
+
103
+ # Uncomment to auto-respond to keywords:
104
+ # if echo "$TEXT" | grep -qi "status"; then
105
+ # agent-webex message send "$SPACE_ID" "All systems operational."
106
+ # fi
107
+ fi
108
+
109
+ LAST_ID="$LATEST_ID"
110
+ fi
111
+
112
+ FIRST_RUN=false
113
+ return 0
114
+ }
115
+
116
+ if ! command -v agent-webex &> /dev/null; then
117
+ echo -e "${RED}Error: agent-webex not found${NC}"
118
+ echo ""
119
+ echo "Install it with:"
120
+ echo " npm install -g agent-messenger"
121
+ exit 1
122
+ fi
123
+
124
+ echo "Checking authentication..."
125
+ AUTH_STATUS=$(agent-webex auth status 2>&1)
126
+
127
+ if echo "$AUTH_STATUS" | jq -e '.error' > /dev/null 2>&1; then
128
+ echo -e "${RED}Not authenticated!${NC}"
129
+ echo ""
130
+ echo "Run this to authenticate:"
131
+ echo " agent-webex auth login --token <token>"
132
+ echo ""
133
+ echo "Get a token at: https://developer.webex.com/docs/getting-started"
134
+ exit 1
135
+ fi
136
+
137
+ USER_NAME=$(echo "$AUTH_STATUS" | jq -r '.displayName // "Unknown"')
138
+ echo -e "${GREEN}Authenticated as: $USER_NAME${NC}"
139
+ echo ""
140
+
141
+ echo "Verifying space..."
142
+ SPACE_INFO=$(agent-webex space info "$SPACE_ID" 2>&1)
143
+
144
+ if echo "$SPACE_INFO" | jq -e '.error' > /dev/null 2>&1; then
145
+ echo -e "${RED}Space '$SPACE_ID' not found${NC}"
146
+ echo ""
147
+ echo "List available spaces with:"
148
+ echo " agent-webex space list"
149
+ exit 1
150
+ fi
151
+
152
+ SPACE_TITLE=$(echo "$SPACE_INFO" | jq -r '.title // "Unknown"')
153
+ echo -e "${GREEN}Monitoring: $SPACE_TITLE ($SPACE_ID)${NC}"
154
+ echo ""
155
+
156
+ echo -e "${YELLOW}Monitoring for new messages (polling every ${INTERVAL}s)...${NC}"
157
+ echo -e "${YELLOW}Press Ctrl+C to stop${NC}"
158
+ echo ""
159
+
160
+ trap 'echo -e "\n${YELLOW}Monitoring stopped${NC}"; exit 0' INT
161
+
162
+ while true; do
163
+ check_messages
164
+ sleep "$INTERVAL"
165
+ done
@@ -0,0 +1,170 @@
1
+ #!/bin/bash
2
+ #
3
+ # post-message.sh - Send a message to a Webex space with error handling
4
+ #
5
+ # Usage:
6
+ # ./post-message.sh <space-id> <message>
7
+ # ./post-message.sh --space-title <title> <message>
8
+ #
9
+ # Example:
10
+ # ./post-message.sh "Y2lzY29zcGFyazovL..." "Hello from script!"
11
+ # ./post-message.sh --space-title Engineering "Deployment completed"
12
+
13
+ set -euo pipefail
14
+
15
+ # Colors for output
16
+ RED='\033[0;31m'
17
+ GREEN='\033[0;32m'
18
+ YELLOW='\033[1;33m'
19
+ NC='\033[0m' # No Color
20
+
21
+ # Parse arguments
22
+ SPACE_ID=""
23
+ SPACE_TITLE=""
24
+ MESSAGE=""
25
+
26
+ if [ "$1" = "--space-title" ]; then
27
+ if [ $# -lt 3 ]; then
28
+ echo "Usage: $0 --space-title <title> <message>"
29
+ echo ""
30
+ echo "Example:"
31
+ echo " $0 --space-title Engineering 'Build completed'"
32
+ exit 1
33
+ fi
34
+ SPACE_TITLE="$2"
35
+ MESSAGE="$3"
36
+ elif [ $# -lt 2 ]; then
37
+ echo "Usage: $0 <space-id> <message>"
38
+ echo " $0 --space-title <title> <message>"
39
+ echo ""
40
+ echo "Examples:"
41
+ echo " $0 'Y2lzY29zcGFyazovL...' 'Hello world!'"
42
+ echo " $0 --space-title Engineering 'Build completed'"
43
+ exit 1
44
+ else
45
+ SPACE_ID="$1"
46
+ MESSAGE="$2"
47
+ fi
48
+
49
+ # Function to send message with retry logic
50
+ send_message() {
51
+ local space_id=$1
52
+ local message=$2
53
+ local max_attempts=3
54
+ local attempt=1
55
+
56
+ while [ $attempt -le $max_attempts ]; do
57
+ echo -e "${YELLOW}Attempt $attempt/$max_attempts...${NC}"
58
+
59
+ # Send message and capture result
60
+ RESULT=$(agent-webex message send "$space_id" "$message" 2>&1)
61
+
62
+ # Check if successful (has an 'id' field)
63
+ if echo "$RESULT" | jq -e '.id' > /dev/null 2>&1; then
64
+ echo -e "${GREEN}Message sent successfully!${NC}"
65
+
66
+ MSG_ID=$(echo "$RESULT" | jq -r '.id')
67
+
68
+ echo ""
69
+ echo "Message details:"
70
+ echo " Space: $space_id"
71
+ echo " Message ID: $MSG_ID"
72
+
73
+ return 0
74
+ fi
75
+
76
+ # Extract error information
77
+ if echo "$RESULT" | jq -e '.error' > /dev/null 2>&1; then
78
+ ERROR_MSG=$(echo "$RESULT" | jq -r '.error // "Unknown error"')
79
+ echo -e "${RED}Failed: $ERROR_MSG${NC}"
80
+
81
+ # Don't retry on auth errors
82
+ if echo "$ERROR_MSG" | grep -Eqi "401|unauthorized|not authenticated"; then
83
+ echo ""
84
+ echo "Not authenticated. Run:"
85
+ echo " agent-webex auth login --token <token>"
86
+ return 1
87
+ fi
88
+
89
+ # Don't retry on not-found errors
90
+ if echo "$ERROR_MSG" | grep -qi "not found"; then
91
+ echo ""
92
+ echo "Space '$space_id' not found. Check space ID."
93
+ echo "List spaces with: agent-webex space list"
94
+ return 1
95
+ fi
96
+ else
97
+ echo -e "${RED}Unexpected error: $RESULT${NC}"
98
+ fi
99
+
100
+ # Exponential backoff before retry
101
+ if [ $attempt -lt $max_attempts ]; then
102
+ SLEEP_TIME=$((attempt * 2))
103
+ echo "Retrying in ${SLEEP_TIME}s..."
104
+ sleep $SLEEP_TIME
105
+ fi
106
+
107
+ attempt=$((attempt + 1))
108
+ done
109
+
110
+ echo -e "${RED}Failed after $max_attempts attempts${NC}"
111
+ return 1
112
+ }
113
+
114
+ # Check if agent-webex is installed
115
+ if ! command -v agent-webex &> /dev/null; then
116
+ echo -e "${RED}Error: agent-webex not found${NC}"
117
+ echo ""
118
+ echo "Install it with:"
119
+ echo " npm install -g agent-messenger"
120
+ exit 1
121
+ fi
122
+
123
+ # Check authentication
124
+ echo "Checking authentication..."
125
+ AUTH_STATUS=$(agent-webex auth status 2>&1)
126
+
127
+ if echo "$AUTH_STATUS" | jq -e '.error' > /dev/null 2>&1; then
128
+ echo -e "${RED}Not authenticated!${NC}"
129
+ echo ""
130
+ echo "Run this to authenticate:"
131
+ echo " agent-webex auth login --token <token>"
132
+ echo ""
133
+ echo "Get a token at: https://developer.webex.com/docs/getting-started"
134
+ exit 1
135
+ fi
136
+
137
+ USER_NAME=$(echo "$AUTH_STATUS" | jq -r '.displayName // "Unknown"')
138
+ echo -e "${GREEN}Authenticated as: $USER_NAME${NC}"
139
+
140
+ # If space title provided, look up space ID
141
+ if [ -n "$SPACE_TITLE" ]; then
142
+ echo "Looking up space '$SPACE_TITLE'..."
143
+ SPACES=$(agent-webex space list 2>&1)
144
+
145
+ if echo "$SPACES" | jq -e '.error' > /dev/null 2>&1; then
146
+ echo -e "${RED}Failed to list spaces${NC}"
147
+ exit 1
148
+ fi
149
+
150
+ SPACE_ID=$(echo "$SPACES" | jq -r --arg title "$SPACE_TITLE" '.[] | select(.title==$title) | .id')
151
+
152
+ if [ -z "$SPACE_ID" ]; then
153
+ echo -e "${RED}Space '$SPACE_TITLE' not found${NC}"
154
+ echo ""
155
+ echo "Available spaces:"
156
+ echo "$SPACES" | jq -r '.[] | " \(.title) (\(.id))"'
157
+ exit 1
158
+ fi
159
+
160
+ echo -e "${GREEN}Found space: $SPACE_ID${NC}"
161
+ fi
162
+
163
+ echo ""
164
+
165
+ # Send the message
166
+ echo "Sending message to space $SPACE_ID..."
167
+ echo "Message: $MESSAGE"
168
+ echo ""
169
+
170
+ send_message "$SPACE_ID" "$MESSAGE"
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-whatsapp
3
3
  description: Interact with WhatsApp - send messages, read chats, manage conversations
4
- version: 2.0.0
4
+ version: 2.1.0
5
5
  allowed-tools: Bash(agent-whatsapp:*)
6
6
  metadata:
7
7
  openclaw:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-whatsappbot
3
3
  description: Interact with WhatsApp using Cloud API credentials - send messages, manage templates
4
- version: 2.0.0
4
+ version: 2.1.0
5
5
  allowed-tools: Bash(agent-whatsappbot:*)
6
6
  metadata:
7
7
  openclaw:
package/src/cli.ts CHANGED
@@ -68,6 +68,10 @@ program.command('channeltalkbot', 'Interact with Channel Talk using API credenti
68
68
  executableFile: join(__dirname, 'platforms', 'channeltalkbot', `cli${ext}`),
69
69
  })
70
70
 
71
+ program.command('webex', 'Interact with Cisco Webex', {
72
+ executableFile: join(__dirname, 'platforms', 'webex', `cli${ext}`),
73
+ })
74
+
71
75
  program.command('tui', 'Launch unified messenger TUI', {
72
76
  executableFile: join(__dirname, 'tui', `cli${ext}`),
73
77
  })
@@ -0,0 +1,98 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
2
+ import { getWebexAppCredentials } from './app-config'
3
+
4
+ const ENV_KEYS = [
5
+ 'AGENT_WEBEX_CLIENT_ID',
6
+ 'AGENT_WEBEX_CLIENT_SECRET',
7
+ 'AGENT_MESSENGER_WEBEX_CLIENT_ID',
8
+ 'AGENT_MESSENGER_WEBEX_CLIENT_SECRET',
9
+ ] as const
10
+
11
+ describe('webex app config', () => {
12
+ let savedEnv: Record<string, string | undefined> = {}
13
+
14
+ beforeEach(() => {
15
+ savedEnv = {}
16
+ for (const key of ENV_KEYS) {
17
+ savedEnv[key] = process.env[key]
18
+ delete process.env[key]
19
+ }
20
+ })
21
+
22
+ afterEach(() => {
23
+ for (const key of ENV_KEYS) {
24
+ if (savedEnv[key] === undefined) {
25
+ delete process.env[key]
26
+ } else {
27
+ process.env[key] = savedEnv[key]
28
+ }
29
+ }
30
+ })
31
+
32
+ test('returns env credentials when primary env vars are set', () => {
33
+ process.env.AGENT_WEBEX_CLIENT_ID = 'my-client-id'
34
+ process.env.AGENT_WEBEX_CLIENT_SECRET = 'my-client-secret'
35
+
36
+ expect(getWebexAppCredentials()).toEqual({
37
+ clientId: 'my-client-id',
38
+ clientSecret: 'my-client-secret',
39
+ source: 'env',
40
+ })
41
+ })
42
+
43
+ test('returns env credentials when legacy env vars are set', () => {
44
+ process.env.AGENT_MESSENGER_WEBEX_CLIENT_ID = 'legacy-client-id'
45
+ process.env.AGENT_MESSENGER_WEBEX_CLIENT_SECRET = 'legacy-client-secret'
46
+
47
+ expect(getWebexAppCredentials()).toEqual({
48
+ clientId: 'legacy-client-id',
49
+ clientSecret: 'legacy-client-secret',
50
+ source: 'env',
51
+ })
52
+ })
53
+
54
+ test('returns builtin credentials when nothing is configured', () => {
55
+ const result = getWebexAppCredentials()
56
+ expect(result.source).toBe('builtin')
57
+ expect(result.clientId).toBeTruthy()
58
+ expect(result.clientSecret).toBeTruthy()
59
+ })
60
+
61
+ test('returns builtin when only clientId is set', () => {
62
+ process.env.AGENT_WEBEX_CLIENT_ID = 'my-client-id'
63
+
64
+ const result = getWebexAppCredentials()
65
+ expect(result.source).toBe('builtin')
66
+ })
67
+
68
+ test('returns builtin when only clientSecret is set', () => {
69
+ process.env.AGENT_WEBEX_CLIENT_SECRET = 'my-client-secret'
70
+
71
+ const result = getWebexAppCredentials()
72
+ expect(result.source).toBe('builtin')
73
+ })
74
+
75
+ test('trims whitespace from clientId and clientSecret', () => {
76
+ process.env.AGENT_WEBEX_CLIENT_ID = ' my-client-id '
77
+ process.env.AGENT_WEBEX_CLIENT_SECRET = ' my-client-secret '
78
+
79
+ expect(getWebexAppCredentials()).toEqual({
80
+ clientId: 'my-client-id',
81
+ clientSecret: 'my-client-secret',
82
+ source: 'env',
83
+ })
84
+ })
85
+
86
+ test('primary env takes precedence over legacy env', () => {
87
+ process.env.AGENT_WEBEX_CLIENT_ID = 'primary-id'
88
+ process.env.AGENT_WEBEX_CLIENT_SECRET = 'primary-secret'
89
+ process.env.AGENT_MESSENGER_WEBEX_CLIENT_ID = 'legacy-id'
90
+ process.env.AGENT_MESSENGER_WEBEX_CLIENT_SECRET = 'legacy-secret'
91
+
92
+ expect(getWebexAppCredentials()).toEqual({
93
+ clientId: 'primary-id',
94
+ clientSecret: 'primary-secret',
95
+ source: 'env',
96
+ })
97
+ })
98
+ })
@@ -0,0 +1,31 @@
1
+ const BUILTIN_CLIENT_ID = 'C720341c19d8dfb4cab8a5db78be4bc6d5c3983fbe84df94be34c8aa69a695583'
2
+ const BUILTIN_CLIENT_SECRET = 'e90806657443a7f16093c0846690aeeea96cd2b3ed9b79cf544297c526b4f9af'
3
+
4
+ export interface WebexAppCredentials {
5
+ clientId: string
6
+ clientSecret: string
7
+ source: 'env' | 'builtin'
8
+ }
9
+
10
+ function parseTrimmed(value: string | undefined): string | undefined {
11
+ const normalized = value?.trim()
12
+ return normalized ? normalized : undefined
13
+ }
14
+
15
+ export function getWebexAppCredentials(): WebexAppCredentials {
16
+ const envClientId = parseTrimmed(process.env.AGENT_WEBEX_CLIENT_ID)
17
+ const envClientSecret = parseTrimmed(process.env.AGENT_WEBEX_CLIENT_SECRET)
18
+
19
+ if (envClientId && envClientSecret) {
20
+ return { clientId: envClientId, clientSecret: envClientSecret, source: 'env' }
21
+ }
22
+
23
+ const legacyClientId = parseTrimmed(process.env.AGENT_MESSENGER_WEBEX_CLIENT_ID)
24
+ const legacyClientSecret = parseTrimmed(process.env.AGENT_MESSENGER_WEBEX_CLIENT_SECRET)
25
+
26
+ if (legacyClientId && legacyClientSecret) {
27
+ return { clientId: legacyClientId, clientSecret: legacyClientSecret, source: 'env' }
28
+ }
29
+
30
+ return { clientId: BUILTIN_CLIENT_ID, clientSecret: BUILTIN_CLIENT_SECRET, source: 'builtin' }
31
+ }
@@ -0,0 +1,58 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+
3
+ import { spawn } from 'bun'
4
+
5
+ import pkg from '../../../package.json' with { type: 'json' }
6
+
7
+ describe('Webex CLI program structure', () => {
8
+ test('--help shows all commands', async () => {
9
+ const proc = spawn(['bun', 'run', './src/platforms/webex/cli.ts', '--help'], {
10
+ cwd: process.cwd(),
11
+ stdio: ['pipe', 'pipe', 'pipe'],
12
+ })
13
+
14
+ const output = await new Response(proc.stdout).text()
15
+
16
+ expect(output).toContain('auth')
17
+ expect(output).toContain('member')
18
+ expect(output).toContain('message')
19
+ expect(output).toContain('snapshot')
20
+ expect(output).toContain('space')
21
+ })
22
+
23
+ test('--version shows package version', async () => {
24
+ const proc = spawn(['bun', 'run', './src/platforms/webex/cli.ts', '--version'], {
25
+ cwd: process.cwd(),
26
+ stdio: ['pipe', 'pipe', 'pipe'],
27
+ })
28
+
29
+ const output = await new Response(proc.stdout).text()
30
+ expect(output.trim()).toBe(pkg.version)
31
+ })
32
+
33
+ test('auth login --help shows Device Grant options', async () => {
34
+ const proc = spawn(['bun', 'run', './src/platforms/webex/cli.ts', 'auth', 'login', '--help'], {
35
+ cwd: process.cwd(),
36
+ stdio: ['pipe', 'pipe', 'pipe'],
37
+ })
38
+
39
+ const output = await new Response(proc.stdout).text()
40
+
41
+ expect(output).toContain('--token')
42
+ expect(output).toContain('--client-id')
43
+ expect(output).toContain('--client-secret')
44
+ expect(output).toContain('--pretty')
45
+ })
46
+
47
+ test('message dm --help shows email argument', async () => {
48
+ const proc = spawn(['bun', 'run', './src/platforms/webex/cli.ts', 'message', 'dm', '--help'], {
49
+ cwd: process.cwd(),
50
+ stdio: ['pipe', 'pipe', 'pipe'],
51
+ })
52
+
53
+ const output = await new Response(proc.stdout).text()
54
+
55
+ expect(output).toContain('email')
56
+ expect(output).toContain('--markdown')
57
+ })
58
+ })
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import type { Command as CommandType } from 'commander'
4
+ import { Command } from 'commander'
5
+
6
+ import pkg from '../../../package.json' with { type: 'json' }
7
+ import { authCommand, memberCommand, messageCommand, snapshotCommand, spaceCommand } from './commands'
8
+ import { ensureWebexAuth } from './ensure-auth'
9
+
10
+ function isAuthCommand(command: CommandType): boolean {
11
+ let cmd: CommandType | null = command
12
+ while (cmd) {
13
+ if (cmd.name() === 'auth') return true
14
+ cmd = cmd.parent
15
+ }
16
+ return false
17
+ }
18
+
19
+ const program = new Command()
20
+
21
+ program
22
+ .name('agent-webex')
23
+ .description('CLI tool for Cisco Webex communication')
24
+ .version(pkg.version)
25
+
26
+ program.hook('preAction', async (_thisCommand, actionCommand) => {
27
+ if (isAuthCommand(actionCommand)) return
28
+ await ensureWebexAuth()
29
+ })
30
+
31
+ program.addCommand(authCommand)
32
+ program.addCommand(memberCommand)
33
+ program.addCommand(messageCommand)
34
+ program.addCommand(snapshotCommand)
35
+ program.addCommand(spaceCommand)
36
+
37
+ program.parse(process.argv)
38
+
39
+ export default program