@moltflow/skills 1.4.0 → 2.0.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.
- package/CHANGELOG.md +168 -0
- package/SKILL.md +653 -33
- package/moltflow/SKILL.md +463 -0
- package/moltflow-a2a/SKILL.md +437 -0
- package/moltflow-admin/SKILL.md +392 -0
- package/moltflow-ai/SKILL.md +473 -0
- package/moltflow-leads/SKILL.md +275 -0
- package/moltflow-outreach/SKILL.md +379 -0
- package/moltflow-reviews/SKILL.md +242 -0
- package/package.json +18 -4
- package/scripts/a2a_client.py +1 -1
- package/scripts/admin.py +2 -4
- package/scripts/ai_config.py +107 -30
- package/scripts/leads.py +111 -0
- package/scripts/outreach.py +228 -0
- package/scripts/quickstart.py +2 -2
- package/scripts/reviews.py +1 -1
- package/scripts/send_message.py +32 -23
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
MoltFlow Outreach - Bulk Send, Scheduled Messages, Custom Groups
|
|
4
|
+
"""
|
|
5
|
+
import os
|
|
6
|
+
import json
|
|
7
|
+
import requests
|
|
8
|
+
|
|
9
|
+
API_KEY = os.environ.get("MOLTFLOW_API_KEY")
|
|
10
|
+
BASE_URL = os.environ.get("MOLTFLOW_API_URL", "https://apiv2.waiflow.app")
|
|
11
|
+
|
|
12
|
+
if not API_KEY:
|
|
13
|
+
print("Error: MOLTFLOW_API_KEY environment variable not set")
|
|
14
|
+
exit(1)
|
|
15
|
+
|
|
16
|
+
headers = {"X-API-Key": API_KEY, "Content-Type": "application/json"}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# ============================================================================
|
|
20
|
+
# Custom Groups
|
|
21
|
+
# ============================================================================
|
|
22
|
+
|
|
23
|
+
def list_custom_groups(limit: int = 50, offset: int = 0):
|
|
24
|
+
"""List all custom contact groups."""
|
|
25
|
+
r = requests.get(
|
|
26
|
+
f"{BASE_URL}/api/v2/custom-groups",
|
|
27
|
+
headers=headers,
|
|
28
|
+
params={"limit": limit, "offset": offset},
|
|
29
|
+
)
|
|
30
|
+
r.raise_for_status()
|
|
31
|
+
return r.json()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def create_custom_group(name: str, members: list = None):
|
|
35
|
+
"""Create a new custom group. members = list of {"phone": str, "name": str?} dicts."""
|
|
36
|
+
data = {"name": name}
|
|
37
|
+
if members:
|
|
38
|
+
data["members"] = members
|
|
39
|
+
r = requests.post(f"{BASE_URL}/api/v2/custom-groups", headers=headers, json=data)
|
|
40
|
+
r.raise_for_status()
|
|
41
|
+
return r.json()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def add_members(group_id: str, contacts: list):
|
|
45
|
+
"""Add members to a custom group. contacts = list of {"phone": str} dicts."""
|
|
46
|
+
r = requests.post(
|
|
47
|
+
f"{BASE_URL}/api/v2/custom-groups/{group_id}/members/add",
|
|
48
|
+
headers=headers,
|
|
49
|
+
json={"contacts": contacts},
|
|
50
|
+
)
|
|
51
|
+
r.raise_for_status()
|
|
52
|
+
return r.json()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def export_group_csv(group_id: str):
|
|
56
|
+
"""Export group members as CSV."""
|
|
57
|
+
r = requests.get(
|
|
58
|
+
f"{BASE_URL}/api/v2/custom-groups/{group_id}/export/csv",
|
|
59
|
+
headers=headers,
|
|
60
|
+
)
|
|
61
|
+
r.raise_for_status()
|
|
62
|
+
return r.text
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def export_group_json(group_id: str):
|
|
66
|
+
"""Export group members as JSON."""
|
|
67
|
+
r = requests.get(
|
|
68
|
+
f"{BASE_URL}/api/v2/custom-groups/{group_id}/export/json",
|
|
69
|
+
headers=headers,
|
|
70
|
+
)
|
|
71
|
+
r.raise_for_status()
|
|
72
|
+
return r.json()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# ============================================================================
|
|
76
|
+
# Bulk Send
|
|
77
|
+
# ============================================================================
|
|
78
|
+
|
|
79
|
+
def create_bulk_job(session_id: str, custom_group_id: str, message_content: str):
|
|
80
|
+
"""Create a bulk send job targeting a custom group."""
|
|
81
|
+
r = requests.post(
|
|
82
|
+
f"{BASE_URL}/api/v2/bulk-send",
|
|
83
|
+
headers=headers,
|
|
84
|
+
json={
|
|
85
|
+
"session_id": session_id,
|
|
86
|
+
"custom_group_id": custom_group_id,
|
|
87
|
+
"message_content": message_content,
|
|
88
|
+
},
|
|
89
|
+
)
|
|
90
|
+
r.raise_for_status()
|
|
91
|
+
return r.json()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def list_bulk_jobs(limit: int = 50):
|
|
95
|
+
"""List all bulk send jobs."""
|
|
96
|
+
r = requests.get(
|
|
97
|
+
f"{BASE_URL}/api/v2/bulk-send",
|
|
98
|
+
headers=headers,
|
|
99
|
+
params={"limit": limit},
|
|
100
|
+
)
|
|
101
|
+
r.raise_for_status()
|
|
102
|
+
return r.json()
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def get_bulk_job(job_id: str):
|
|
106
|
+
"""Get bulk job details and progress."""
|
|
107
|
+
r = requests.get(f"{BASE_URL}/api/v2/bulk-send/{job_id}", headers=headers)
|
|
108
|
+
r.raise_for_status()
|
|
109
|
+
return r.json()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def pause_bulk_job(job_id: str):
|
|
113
|
+
"""Pause a running bulk send job."""
|
|
114
|
+
r = requests.post(f"{BASE_URL}/api/v2/bulk-send/{job_id}/pause", headers=headers)
|
|
115
|
+
r.raise_for_status()
|
|
116
|
+
return r.json()
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def resume_bulk_job(job_id: str):
|
|
120
|
+
"""Resume a paused bulk send job."""
|
|
121
|
+
r = requests.post(f"{BASE_URL}/api/v2/bulk-send/{job_id}/resume", headers=headers)
|
|
122
|
+
r.raise_for_status()
|
|
123
|
+
return r.json()
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def cancel_bulk_job(job_id: str):
|
|
127
|
+
"""Cancel a bulk send job."""
|
|
128
|
+
r = requests.post(f"{BASE_URL}/api/v2/bulk-send/{job_id}/cancel", headers=headers)
|
|
129
|
+
r.raise_for_status()
|
|
130
|
+
return r.json()
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# ============================================================================
|
|
134
|
+
# Scheduled Messages
|
|
135
|
+
# ============================================================================
|
|
136
|
+
|
|
137
|
+
def create_scheduled_message(
|
|
138
|
+
session_id: str,
|
|
139
|
+
custom_group_id: str,
|
|
140
|
+
message_content: str,
|
|
141
|
+
name: str = "Scheduled Message",
|
|
142
|
+
schedule_type: str = "one_time",
|
|
143
|
+
cron_expression: str = None,
|
|
144
|
+
timezone: str = "UTC",
|
|
145
|
+
scheduled_time: str = None,
|
|
146
|
+
):
|
|
147
|
+
"""Create a scheduled message.
|
|
148
|
+
|
|
149
|
+
schedule_type: one_time, daily, weekly, monthly, cron
|
|
150
|
+
scheduled_time: ISO 8601 datetime (required for 'one_time')
|
|
151
|
+
cron_expression: cron string (required for recurring types)
|
|
152
|
+
"""
|
|
153
|
+
data = {
|
|
154
|
+
"session_id": session_id,
|
|
155
|
+
"custom_group_id": custom_group_id,
|
|
156
|
+
"message_content": message_content,
|
|
157
|
+
"name": name,
|
|
158
|
+
"schedule_type": schedule_type,
|
|
159
|
+
"timezone": timezone,
|
|
160
|
+
}
|
|
161
|
+
if scheduled_time:
|
|
162
|
+
data["scheduled_time"] = scheduled_time
|
|
163
|
+
if cron_expression:
|
|
164
|
+
data["cron_expression"] = cron_expression
|
|
165
|
+
r = requests.post(f"{BASE_URL}/api/v2/scheduled-messages", headers=headers, json=data)
|
|
166
|
+
r.raise_for_status()
|
|
167
|
+
return r.json()
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def list_scheduled_messages(limit: int = 50):
|
|
171
|
+
"""List all scheduled messages."""
|
|
172
|
+
r = requests.get(
|
|
173
|
+
f"{BASE_URL}/api/v2/scheduled-messages",
|
|
174
|
+
headers=headers,
|
|
175
|
+
params={"limit": limit},
|
|
176
|
+
)
|
|
177
|
+
r.raise_for_status()
|
|
178
|
+
return r.json()
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def pause_scheduled(message_id: str):
|
|
182
|
+
"""Pause a scheduled message."""
|
|
183
|
+
r = requests.post(
|
|
184
|
+
f"{BASE_URL}/api/v2/scheduled-messages/{message_id}/pause",
|
|
185
|
+
headers=headers,
|
|
186
|
+
)
|
|
187
|
+
r.raise_for_status()
|
|
188
|
+
return r.json()
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def resume_scheduled(message_id: str):
|
|
192
|
+
"""Resume a paused scheduled message."""
|
|
193
|
+
r = requests.post(
|
|
194
|
+
f"{BASE_URL}/api/v2/scheduled-messages/{message_id}/resume",
|
|
195
|
+
headers=headers,
|
|
196
|
+
)
|
|
197
|
+
r.raise_for_status()
|
|
198
|
+
return r.json()
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def cancel_scheduled(message_id: str):
|
|
202
|
+
"""Cancel a scheduled message."""
|
|
203
|
+
r = requests.post(
|
|
204
|
+
f"{BASE_URL}/api/v2/scheduled-messages/{message_id}/cancel",
|
|
205
|
+
headers=headers,
|
|
206
|
+
)
|
|
207
|
+
r.raise_for_status()
|
|
208
|
+
return r.json()
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
if __name__ == "__main__":
|
|
212
|
+
print("MoltFlow Outreach")
|
|
213
|
+
print("=" * 40)
|
|
214
|
+
|
|
215
|
+
# Custom groups
|
|
216
|
+
groups = list_custom_groups()
|
|
217
|
+
group_list = groups.get("groups", groups) if isinstance(groups, dict) else groups
|
|
218
|
+
print(f"\nCustom Groups: {len(group_list) if isinstance(group_list, list) else 0}")
|
|
219
|
+
|
|
220
|
+
# Bulk jobs
|
|
221
|
+
jobs = list_bulk_jobs()
|
|
222
|
+
job_list = jobs.get("jobs", jobs) if isinstance(jobs, dict) else jobs
|
|
223
|
+
print(f"Bulk Jobs: {len(job_list) if isinstance(job_list, list) else 0}")
|
|
224
|
+
|
|
225
|
+
# Scheduled messages
|
|
226
|
+
scheduled = list_scheduled_messages()
|
|
227
|
+
sched_list = scheduled.get("messages", scheduled) if isinstance(scheduled, dict) else scheduled
|
|
228
|
+
print(f"Scheduled Messages: {len(sched_list) if isinstance(sched_list, list) else 0}")
|
package/scripts/quickstart.py
CHANGED
|
@@ -6,11 +6,11 @@ import os
|
|
|
6
6
|
import requests
|
|
7
7
|
|
|
8
8
|
API_KEY = os.environ.get("MOLTFLOW_API_KEY")
|
|
9
|
-
BASE_URL = os.environ.get("MOLTFLOW_API_URL", "https://
|
|
9
|
+
BASE_URL = os.environ.get("MOLTFLOW_API_URL", "https://apiv2.waiflow.app")
|
|
10
10
|
|
|
11
11
|
if not API_KEY:
|
|
12
12
|
print("Error: MOLTFLOW_API_KEY environment variable not set")
|
|
13
|
-
print("Get your API key at https://
|
|
13
|
+
print("Get your API key at https://waiflow.app")
|
|
14
14
|
exit(1)
|
|
15
15
|
|
|
16
16
|
headers = {"X-API-Key": API_KEY, "Content-Type": "application/json"}
|
package/scripts/reviews.py
CHANGED
|
@@ -6,7 +6,7 @@ import os
|
|
|
6
6
|
import requests
|
|
7
7
|
|
|
8
8
|
API_KEY = os.environ.get("MOLTFLOW_API_KEY")
|
|
9
|
-
BASE_URL = os.environ.get("MOLTFLOW_API_URL", "https://
|
|
9
|
+
BASE_URL = os.environ.get("MOLTFLOW_API_URL", "https://apiv2.waiflow.app")
|
|
10
10
|
|
|
11
11
|
if not API_KEY:
|
|
12
12
|
print("Error: MOLTFLOW_API_KEY environment variable not set")
|
package/scripts/send_message.py
CHANGED
|
@@ -1,38 +1,47 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
3
|
Send WhatsApp Message Helper
|
|
4
|
-
|
|
4
|
+
Simple wrapper for the most common task — sending a text message via the REST API.
|
|
5
5
|
"""
|
|
6
6
|
import argparse
|
|
7
|
+
import os
|
|
7
8
|
import sys
|
|
8
9
|
import json
|
|
9
|
-
|
|
10
|
+
import requests
|
|
11
|
+
|
|
12
|
+
BASE_URL = os.environ.get("MOLTFLOW_API_URL", "https://apiv2.waiflow.app")
|
|
13
|
+
API_KEY = os.environ.get("MOLTFLOW_API_KEY")
|
|
14
|
+
|
|
10
15
|
|
|
11
16
|
def main():
|
|
12
|
-
parser = argparse.ArgumentParser(description="Send WhatsApp message via
|
|
13
|
-
parser.add_argument("--
|
|
14
|
-
parser.add_argument("--
|
|
15
|
-
parser.add_argument("--to", required=True, help="Target phone number (e.g. 1234567890)")
|
|
17
|
+
parser = argparse.ArgumentParser(description="Send a WhatsApp message via MoltFlow API.")
|
|
18
|
+
parser.add_argument("--session", required=True, help="Session UUID")
|
|
19
|
+
parser.add_argument("--to", required=True, help="Target chat ID (e.g. 1234567890@c.us)")
|
|
16
20
|
parser.add_argument("--text", required=True, help="Message text content")
|
|
17
|
-
|
|
21
|
+
parser.add_argument("--key", default=API_KEY, help="API key (default: MOLTFLOW_API_KEY env)")
|
|
22
|
+
|
|
18
23
|
args = parser.parse_args()
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
# Construct standard A2A message payload
|
|
23
|
-
params = {
|
|
24
|
-
"phone": args.to,
|
|
25
|
-
"text": args.text
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
response = client.send_rpc("message/send", params)
|
|
29
|
-
|
|
30
|
-
# Output result
|
|
31
|
-
print(json.dumps(response, indent=2))
|
|
32
|
-
|
|
33
|
-
# Exit code based on success
|
|
34
|
-
if "error" in response:
|
|
24
|
+
|
|
25
|
+
if not args.key:
|
|
26
|
+
print("Error: provide --key or set MOLTFLOW_API_KEY env var")
|
|
35
27
|
sys.exit(1)
|
|
36
28
|
|
|
29
|
+
response = requests.post(
|
|
30
|
+
f"{BASE_URL}/api/v2/messages/send",
|
|
31
|
+
headers={"X-API-Key": args.key, "Content-Type": "application/json"},
|
|
32
|
+
json={
|
|
33
|
+
"session_id": args.session,
|
|
34
|
+
"chat_id": args.to,
|
|
35
|
+
"message": args.text,
|
|
36
|
+
},
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
result = response.json()
|
|
40
|
+
print(json.dumps(result, indent=2))
|
|
41
|
+
|
|
42
|
+
if not response.ok:
|
|
43
|
+
sys.exit(1)
|
|
44
|
+
|
|
45
|
+
|
|
37
46
|
if __name__ == "__main__":
|
|
38
47
|
main()
|