@growthub/cli 0.3.58 → 0.3.60
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/assets/worker-kits/growthub-zernio-social-v1/.env.example +5 -0
- package/assets/worker-kits/growthub-zernio-social-v1/QUICKSTART.md +36 -4
- package/assets/worker-kits/growthub-zernio-social-v1/bundles/growthub-zernio-social-v1.json +30 -1
- package/assets/worker-kits/growthub-zernio-social-v1/docs/growthub-agentic-social-platform-ui-shell.md +134 -0
- package/assets/worker-kits/growthub-zernio-social-v1/docs/local-adapters.md +2 -2
- package/assets/worker-kits/growthub-zernio-social-v1/growthub-meta/README.md +5 -8
- package/assets/worker-kits/growthub-zernio-social-v1/growthub-meta/kit-standard.md +1 -1
- package/assets/worker-kits/growthub-zernio-social-v1/kit.json +33 -1
- package/assets/worker-kits/growthub-zernio-social-v1/skills.md +1 -1
- package/assets/worker-kits/growthub-zernio-social-v1/studio/.env.example +3 -0
- package/assets/worker-kits/growthub-zernio-social-v1/studio/dist/assets/index-DTmBMuXr.js +78 -0
- package/assets/worker-kits/growthub-zernio-social-v1/studio/dist/assets/index-gHr-nTMF.css +1 -0
- package/assets/worker-kits/growthub-zernio-social-v1/studio/dist/index.html +14 -0
- package/assets/worker-kits/growthub-zernio-social-v1/studio/index.html +13 -0
- package/assets/worker-kits/growthub-zernio-social-v1/studio/package-lock.json +1677 -0
- package/assets/worker-kits/growthub-zernio-social-v1/studio/package.json +20 -0
- package/assets/worker-kits/growthub-zernio-social-v1/studio/serve.mjs +60 -0
- package/assets/worker-kits/growthub-zernio-social-v1/studio/src/App.jsx +130 -0
- package/assets/worker-kits/growthub-zernio-social-v1/studio/src/api.js +146 -0
- package/assets/worker-kits/growthub-zernio-social-v1/studio/src/app.css +558 -0
- package/assets/worker-kits/growthub-zernio-social-v1/studio/src/lib/rules.js +64 -0
- package/assets/worker-kits/growthub-zernio-social-v1/studio/src/lib/templates.js +207 -0
- package/assets/worker-kits/growthub-zernio-social-v1/studio/src/main.jsx +10 -0
- package/assets/worker-kits/growthub-zernio-social-v1/studio/src/views/Accounts.jsx +57 -0
- package/assets/worker-kits/growthub-zernio-social-v1/studio/src/views/Agent.jsx +167 -0
- package/assets/worker-kits/growthub-zernio-social-v1/studio/src/views/Analytics.jsx +164 -0
- package/assets/worker-kits/growthub-zernio-social-v1/studio/src/views/ApiKeys.jsx +143 -0
- package/assets/worker-kits/growthub-zernio-social-v1/studio/src/views/Automations.jsx +122 -0
- package/assets/worker-kits/growthub-zernio-social-v1/studio/src/views/CommentRules.jsx +592 -0
- package/assets/worker-kits/growthub-zernio-social-v1/studio/src/views/Compose.jsx +185 -0
- package/assets/worker-kits/growthub-zernio-social-v1/studio/src/views/Dashboard.jsx +87 -0
- package/assets/worker-kits/growthub-zernio-social-v1/studio/src/views/Inbox.jsx +144 -0
- package/assets/worker-kits/growthub-zernio-social-v1/studio/src/views/Queues.jsx +167 -0
- package/assets/worker-kits/growthub-zernio-social-v1/studio/src/views/Scheduled.jsx +85 -0
- package/assets/worker-kits/growthub-zernio-social-v1/studio/src/views/Sequences.jsx +160 -0
- package/assets/worker-kits/growthub-zernio-social-v1/studio/src/views/Templates.jsx +275 -0
- package/assets/worker-kits/growthub-zernio-social-v1/studio/vite.config.js +7 -0
- package/assets/worker-kits/growthub-zernio-social-v1/workers/zernio-social-operator/CLAUDE.md +3 -3
- package/dist/index.js +8541 -850
- package/package.json +1 -1
- package/assets/worker-kits/growthub-zernio-social-v1/docs/postiz-ui-shell-integration.md +0 -166
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "growthub-agentic-social-media-platform",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "vite build",
|
|
9
|
+
"preview": "vite preview",
|
|
10
|
+
"serve": "node serve.mjs"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"react": "^18.3.1",
|
|
14
|
+
"react-dom": "^18.3.1"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@vitejs/plugin-react": "^4.3.1",
|
|
18
|
+
"vite": "^5.4.2"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import http from "node:http";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
const studioDir = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const distDir = path.join(studioDir, "dist");
|
|
8
|
+
const host = process.env.HOST || "127.0.0.1";
|
|
9
|
+
const port = Number.parseInt(process.env.PORT || "4173", 10);
|
|
10
|
+
|
|
11
|
+
const MIME_TYPES = new Map([
|
|
12
|
+
[".html", "text/html; charset=utf-8"],
|
|
13
|
+
[".js", "application/javascript; charset=utf-8"],
|
|
14
|
+
[".css", "text/css; charset=utf-8"],
|
|
15
|
+
[".json", "application/json; charset=utf-8"],
|
|
16
|
+
[".svg", "image/svg+xml"],
|
|
17
|
+
[".png", "image/png"],
|
|
18
|
+
[".jpg", "image/jpeg"],
|
|
19
|
+
[".jpeg", "image/jpeg"],
|
|
20
|
+
[".ico", "image/x-icon"],
|
|
21
|
+
[".txt", "text/plain; charset=utf-8"],
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
function safeResolve(requestPath) {
|
|
25
|
+
const relativePath = requestPath === "/" ? "index.html" : requestPath.replace(/^\/+/, "");
|
|
26
|
+
const resolvedPath = path.resolve(distDir, relativePath);
|
|
27
|
+
if (!resolvedPath.startsWith(distDir + path.sep) && resolvedPath !== path.join(distDir, "index.html")) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
return resolvedPath;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function sendFile(res, filePath) {
|
|
34
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
35
|
+
const contentType = MIME_TYPES.get(ext) || "application/octet-stream";
|
|
36
|
+
res.writeHead(200, { "Content-Type": contentType });
|
|
37
|
+
fs.createReadStream(filePath).pipe(res);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const server = http.createServer((req, res) => {
|
|
41
|
+
const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
|
|
42
|
+
const filePath = safeResolve(url.pathname);
|
|
43
|
+
|
|
44
|
+
if (!filePath) {
|
|
45
|
+
res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8" });
|
|
46
|
+
res.end("Forbidden");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
|
|
51
|
+
sendFile(res, filePath);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
sendFile(res, path.join(distDir, "index.html"));
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
server.listen(port, host, () => {
|
|
59
|
+
process.stdout.write(`Zernio studio bundle running at http://${host}:${port}\n`);
|
|
60
|
+
});
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { useState, useEffect, createContext, useContext } from 'react';
|
|
2
|
+
import { api, PROFILE_ID } from './api.js';
|
|
3
|
+
import Dashboard from './views/Dashboard.jsx';
|
|
4
|
+
import Accounts from './views/Accounts.jsx';
|
|
5
|
+
import Compose from './views/Compose.jsx';
|
|
6
|
+
import Scheduled from './views/Scheduled.jsx';
|
|
7
|
+
import Queues from './views/Queues.jsx';
|
|
8
|
+
import Analytics from './views/Analytics.jsx';
|
|
9
|
+
import Agent from './views/Agent.jsx';
|
|
10
|
+
import ApiKeys from './views/ApiKeys.jsx';
|
|
11
|
+
import Automations from './views/Automations.jsx';
|
|
12
|
+
import Templates from './views/Templates.jsx';
|
|
13
|
+
import CommentRules from './views/CommentRules.jsx';
|
|
14
|
+
import Sequences from './views/Sequences.jsx';
|
|
15
|
+
|
|
16
|
+
export const AppCtx = createContext({});
|
|
17
|
+
export const useApp = () => useContext(AppCtx);
|
|
18
|
+
|
|
19
|
+
const NAV = [
|
|
20
|
+
{ section: 'Publishing' },
|
|
21
|
+
{ id: 'dashboard', label: 'Dashboard', icon: '🏠' },
|
|
22
|
+
{ id: 'accounts', label: 'Accounts', icon: '🔗' },
|
|
23
|
+
{ id: 'compose', label: 'Compose', icon: '✏️' },
|
|
24
|
+
{ id: 'scheduled', label: 'Scheduled', icon: '📅' },
|
|
25
|
+
{ id: 'queues', label: 'Queues', icon: '🔄' },
|
|
26
|
+
{ id: 'analytics', label: 'Analytics', icon: '📊' },
|
|
27
|
+
{ section: 'Comment Automation' },
|
|
28
|
+
{ id: 'templates', label: 'Templates', icon: '📝' },
|
|
29
|
+
{ id: 'commentrules', label: 'Comment Rules', icon: '💬' },
|
|
30
|
+
{ id: 'sequences', label: 'Sequences', icon: '🔀' },
|
|
31
|
+
{ id: 'automations', label: 'Automations', icon: '⚡' },
|
|
32
|
+
{ section: 'Agent' },
|
|
33
|
+
{ id: 'agent', label: 'Agent / Swarm', icon: '🤖' },
|
|
34
|
+
{ id: 'apikeys', label: 'API Keys', icon: '🔑' },
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const VIEWS = {
|
|
38
|
+
dashboard: Dashboard,
|
|
39
|
+
accounts: Accounts,
|
|
40
|
+
compose: Compose,
|
|
41
|
+
scheduled: Scheduled,
|
|
42
|
+
queues: Queues,
|
|
43
|
+
analytics: Analytics,
|
|
44
|
+
agent: Agent,
|
|
45
|
+
automations: Automations,
|
|
46
|
+
apikeys: ApiKeys,
|
|
47
|
+
templates: Templates,
|
|
48
|
+
commentrules: CommentRules,
|
|
49
|
+
sequences: Sequences,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export default function App() {
|
|
53
|
+
const [view, setView] = useState('dashboard');
|
|
54
|
+
const [profile, setProfile] = useState(null);
|
|
55
|
+
const [accounts, setAccounts] = useState([]);
|
|
56
|
+
const [toast, setToast] = useState(null);
|
|
57
|
+
const [toastOk, setToastOk] = useState(true);
|
|
58
|
+
|
|
59
|
+
const showToast = (msg, ok = true) => {
|
|
60
|
+
setToast(msg);
|
|
61
|
+
setToastOk(ok);
|
|
62
|
+
setTimeout(() => setToast(null), 3500);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const reload = () => {
|
|
66
|
+
if (!PROFILE_ID) return;
|
|
67
|
+
Promise.all([api.getProfile(PROFILE_ID), api.getAccounts(PROFILE_ID)])
|
|
68
|
+
.then(([prof, accs]) => {
|
|
69
|
+
setProfile(prof);
|
|
70
|
+
setAccounts(accs.accounts || []);
|
|
71
|
+
})
|
|
72
|
+
.catch(e => showToast(e.message, false));
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
useEffect(reload, []);
|
|
76
|
+
|
|
77
|
+
const Current = VIEWS[view] || Dashboard;
|
|
78
|
+
const navLabel = NAV.find(n => n.id === view)?.label || '';
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<AppCtx.Provider value={{ profile, accounts, PROFILE_ID, showToast, reload }}>
|
|
82
|
+
<div className="layout">
|
|
83
|
+
<nav className="sidebar">
|
|
84
|
+
<div className="logo">⚡ Growthub</div>
|
|
85
|
+
|
|
86
|
+
{NAV.map((item, i) =>
|
|
87
|
+
item.section
|
|
88
|
+
? <div key={i} className="nav-section">{item.section}</div>
|
|
89
|
+
: (
|
|
90
|
+
<button
|
|
91
|
+
key={item.id}
|
|
92
|
+
className={`nav-item ${view === item.id ? 'active' : ''}`}
|
|
93
|
+
onClick={() => setView(item.id)}
|
|
94
|
+
>
|
|
95
|
+
<span className="nav-icon">{item.icon}</span>
|
|
96
|
+
{item.label}
|
|
97
|
+
</button>
|
|
98
|
+
)
|
|
99
|
+
)}
|
|
100
|
+
|
|
101
|
+
<div className="sidebar-bottom">
|
|
102
|
+
<div className="account-badge">
|
|
103
|
+
<span className="dot" />
|
|
104
|
+
<span>{profile?.name || (PROFILE_ID ? 'Loading…' : 'No profile set')}</span>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
</nav>
|
|
108
|
+
|
|
109
|
+
<div className="main">
|
|
110
|
+
<div className="topbar">
|
|
111
|
+
<span className="topbar-title">{navLabel}</span>
|
|
112
|
+
<div className="row">
|
|
113
|
+
<button className="btn btn-ghost btn-sm" onClick={reload}>↻ Refresh</button>
|
|
114
|
+
<button className="btn btn-primary btn-sm" onClick={() => setView('compose')}>+ New Post</button>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
<div className="content">
|
|
118
|
+
<Current onNavigate={setView} />
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
{toast && (
|
|
124
|
+
<div className={`toast ${toastOk ? 'toast-ok' : 'toast-err'}`}>
|
|
125
|
+
{toast}
|
|
126
|
+
</div>
|
|
127
|
+
)}
|
|
128
|
+
</AppCtx.Provider>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
const BASE = import.meta.env.VITE_ZERNIO_API_URL || 'https://zernio.com/api/v1';
|
|
2
|
+
const KEY = import.meta.env.VITE_ZERNIO_API_KEY || '';
|
|
3
|
+
export const PROFILE_ID = import.meta.env.VITE_ZERNIO_PROFILE_ID || '';
|
|
4
|
+
|
|
5
|
+
function authHeaders(extra = {}) {
|
|
6
|
+
return { 'Authorization': `Bearer ${KEY}`, 'Content-Type': 'application/json', ...extra };
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async function request(method, path, body, idempotencyKey) {
|
|
10
|
+
const h = authHeaders(idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : {});
|
|
11
|
+
const opts = { method, headers: h };
|
|
12
|
+
if (body !== undefined) opts.body = JSON.stringify(body);
|
|
13
|
+
|
|
14
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
15
|
+
const r = await fetch(`${BASE}${path}`, opts);
|
|
16
|
+
if (r.status === 429) {
|
|
17
|
+
const wait = (parseInt(r.headers.get('Retry-After') || '5', 10) * 1000) + 500;
|
|
18
|
+
await new Promise(res => setTimeout(res, wait));
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (!r.ok) {
|
|
22
|
+
const err = await r.json().catch(() => ({}));
|
|
23
|
+
const code = err?.error?.code || '';
|
|
24
|
+
const msg = err?.error?.message || `HTTP ${r.status}`;
|
|
25
|
+
const e = new Error(msg);
|
|
26
|
+
e.code = code;
|
|
27
|
+
e.status = r.status;
|
|
28
|
+
throw e;
|
|
29
|
+
}
|
|
30
|
+
return r.json();
|
|
31
|
+
}
|
|
32
|
+
throw new Error('Rate limited: max retries reached');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const get = (path) => request('GET', path);
|
|
36
|
+
const post = (path, body, ikey) => request('POST', path, body, ikey);
|
|
37
|
+
const put = (path, body) => request('PUT', path, body);
|
|
38
|
+
const del = (path) => request('DELETE', path);
|
|
39
|
+
|
|
40
|
+
export const api = {
|
|
41
|
+
// ── Profiles ────────────────────────────────────────────────────────────────
|
|
42
|
+
getProfiles: () => get('/profiles'),
|
|
43
|
+
getProfile: (id) => get(`/profiles/${id}`),
|
|
44
|
+
|
|
45
|
+
// ── Accounts ────────────────────────────────────────────────────────────────
|
|
46
|
+
getAccounts: (profileId) => get(`/accounts?profileId=${profileId}`),
|
|
47
|
+
|
|
48
|
+
// ── Posts ───────────────────────────────────────────────────────────────────
|
|
49
|
+
getPosts: (profileId, status = 'scheduled') => get(`/posts?profileId=${profileId}&status=${status}`),
|
|
50
|
+
getAllPosts: (profileId) => get(`/posts?profileId=${profileId}`),
|
|
51
|
+
getPost: (id) => get(`/posts/${id}`),
|
|
52
|
+
// Normalises the response: API returns { post: {...} } on create
|
|
53
|
+
createPost: async (body, ikey) => {
|
|
54
|
+
const d = await post('/posts', body, ikey);
|
|
55
|
+
return d.post || d;
|
|
56
|
+
},
|
|
57
|
+
deletePost: (id) => del(`/posts/${id}`),
|
|
58
|
+
|
|
59
|
+
// ── Queues ──────────────────────────────────────────────────────────────────
|
|
60
|
+
getQueues: (profileId) => get(`/queues?profileId=${profileId}`),
|
|
61
|
+
createQueue: (body) => post('/queues', body),
|
|
62
|
+
updateQueue: (id, body) => put(`/queues/${id}`, body),
|
|
63
|
+
deleteQueue: (id) => del(`/queues/${id}`),
|
|
64
|
+
|
|
65
|
+
// ── Media ───────────────────────────────────────────────────────────────────
|
|
66
|
+
uploadMedia: async (file) => {
|
|
67
|
+
const fd = new FormData();
|
|
68
|
+
fd.append('file', file);
|
|
69
|
+
const r = await fetch(`${BASE}/media`, {
|
|
70
|
+
method: 'POST',
|
|
71
|
+
headers: { 'Authorization': `Bearer ${KEY}` },
|
|
72
|
+
body: fd,
|
|
73
|
+
});
|
|
74
|
+
if (!r.ok) {
|
|
75
|
+
const e = await r.json().catch(() => ({}));
|
|
76
|
+
throw new Error(e?.error?.message || `HTTP ${r.status}`);
|
|
77
|
+
}
|
|
78
|
+
return r.json();
|
|
79
|
+
},
|
|
80
|
+
getMedia: (id) => get(`/media/${id}`),
|
|
81
|
+
|
|
82
|
+
// ── Inbox ───────────────────────────────────────────────────────────────────
|
|
83
|
+
getInbox: (profileId) => get(`/inbox?profileId=${profileId}`),
|
|
84
|
+
getConversation: (id) => get(`/inbox/${id}`),
|
|
85
|
+
replyConversation: (id, body) => post(`/inbox/${id}/reply`, body),
|
|
86
|
+
|
|
87
|
+
// ── Analytics ───────────────────────────────────────────────────────────────
|
|
88
|
+
getPostAnalytics: (profileId, from, to) => get(`/analytics/posts?profileId=${profileId}&from=${from}&to=${to}`),
|
|
89
|
+
getAccountAnalytics: (profileId, from, to) => get(`/analytics/accounts?profileId=${profileId}&from=${from}&to=${to}`),
|
|
90
|
+
|
|
91
|
+
// ── API Keys ─────────────────────────────────────────────────────────────────
|
|
92
|
+
getApiKeys: () => get('/api-keys'),
|
|
93
|
+
createApiKey: (body) => post('/api-keys', body),
|
|
94
|
+
deleteApiKey: (id) => del(`/api-keys/${id}`),
|
|
95
|
+
|
|
96
|
+
// ── Platforms ────────────────────────────────────────────────────────────────
|
|
97
|
+
getPlatforms: () => get('/platforms'),
|
|
98
|
+
|
|
99
|
+
// ── Connect ──────────────────────────────────────────────────────────────────
|
|
100
|
+
connectPlatform: (platform) => get(`/connect/${platform}`),
|
|
101
|
+
|
|
102
|
+
// ── Contacts ─────────────────────────────────────────────────────────────────
|
|
103
|
+
getContacts: () => get('/contacts'),
|
|
104
|
+
createContact:(body) => post('/contacts', body),
|
|
105
|
+
|
|
106
|
+
// ── Broadcasts ───────────────────────────────────────────────────────────────
|
|
107
|
+
getBroadcasts: () => get('/broadcasts'),
|
|
108
|
+
createBroadcast: (body) => post('/broadcasts', body),
|
|
109
|
+
getBroadcast: (id) => get(`/broadcasts/${id}`),
|
|
110
|
+
|
|
111
|
+
// ── Sequences ────────────────────────────────────────────────────────────────
|
|
112
|
+
getSequences: () => get('/sequences'),
|
|
113
|
+
getSequence: (id) => get(`/sequences/${id}`),
|
|
114
|
+
activateSequence: (id) => post(`/sequences/${id}/activate`, {}),
|
|
115
|
+
pauseSequence: (id) => post(`/sequences/${id}/pause`, {}),
|
|
116
|
+
|
|
117
|
+
// ── Comment-to-DM Automations (Instagram + Facebook only) ───────────────────
|
|
118
|
+
// Real endpoint: POST /api/v1/comment-automations
|
|
119
|
+
// Required: name, profileId, accountId, platformPostId, dmMessage
|
|
120
|
+
// Optional: keywords (comma-separated string), commentReply, isActive
|
|
121
|
+
getCommentAutomations: (profileId) => get(`/comment-automations${profileId ? '?profileId=' + profileId : ''}`),
|
|
122
|
+
getCommentAutomation: (id) => get(`/comment-automations/${id}`),
|
|
123
|
+
createCommentAutomation: (body) => post('/comment-automations', body),
|
|
124
|
+
updateCommentAutomation: (id, body) => put(`/comment-automations/${id}`, body),
|
|
125
|
+
deleteCommentAutomation: (id) => del(`/comment-automations/${id}`),
|
|
126
|
+
getCommentAutomationLogs: (id) => get(`/comment-automations/${id}/logs`),
|
|
127
|
+
|
|
128
|
+
// ── Platform OAuth connect ────────────────────────────────────────────────
|
|
129
|
+
getConnectUrl: (platform, profileId) => get(`/connect/${platform}?profileId=${profileId || PROFILE_ID}`),
|
|
130
|
+
|
|
131
|
+
// ── Comments on posts ─────────────────────────────────────────────────────
|
|
132
|
+
getPostComments: (postId, accountId) => get(`/posts/${postId}/comments?accountId=${accountId}`),
|
|
133
|
+
replyToComment: (postId, body) => post(`/posts/${postId}/comments`, body),
|
|
134
|
+
deleteComment: (postId, commentId, accountId) => del(`/posts/${postId}/comments/${commentId}?accountId=${accountId}`),
|
|
135
|
+
hideComment: (postId, commentId, body) => post(`/posts/${postId}/comments/${commentId}/hide`, body),
|
|
136
|
+
|
|
137
|
+
// ── Legacy stubs (kept for backward compat in Agent view) ────────────────
|
|
138
|
+
getAutomations: () => get('/comment-automations'),
|
|
139
|
+
runAutomation: (id) => post(`/comment-automations/${id}/run`, {}),
|
|
140
|
+
|
|
141
|
+
// ── Webhooks ─────────────────────────────────────────────────────────────────
|
|
142
|
+
getWebhooks: () => get('/webhooks'),
|
|
143
|
+
createWebhook: (body) => post('/webhooks', body),
|
|
144
|
+
updateWebhook: (id, body) => put(`/webhooks/${id}`, body),
|
|
145
|
+
getWebhookLogs: (id) => get(`/webhooks/${id}/logs`),
|
|
146
|
+
};
|