@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.
Files changed (41) hide show
  1. package/assets/worker-kits/growthub-zernio-social-v1/.env.example +5 -0
  2. package/assets/worker-kits/growthub-zernio-social-v1/QUICKSTART.md +36 -4
  3. package/assets/worker-kits/growthub-zernio-social-v1/bundles/growthub-zernio-social-v1.json +30 -1
  4. package/assets/worker-kits/growthub-zernio-social-v1/docs/growthub-agentic-social-platform-ui-shell.md +134 -0
  5. package/assets/worker-kits/growthub-zernio-social-v1/docs/local-adapters.md +2 -2
  6. package/assets/worker-kits/growthub-zernio-social-v1/growthub-meta/README.md +5 -8
  7. package/assets/worker-kits/growthub-zernio-social-v1/growthub-meta/kit-standard.md +1 -1
  8. package/assets/worker-kits/growthub-zernio-social-v1/kit.json +33 -1
  9. package/assets/worker-kits/growthub-zernio-social-v1/skills.md +1 -1
  10. package/assets/worker-kits/growthub-zernio-social-v1/studio/.env.example +3 -0
  11. package/assets/worker-kits/growthub-zernio-social-v1/studio/dist/assets/index-DTmBMuXr.js +78 -0
  12. package/assets/worker-kits/growthub-zernio-social-v1/studio/dist/assets/index-gHr-nTMF.css +1 -0
  13. package/assets/worker-kits/growthub-zernio-social-v1/studio/dist/index.html +14 -0
  14. package/assets/worker-kits/growthub-zernio-social-v1/studio/index.html +13 -0
  15. package/assets/worker-kits/growthub-zernio-social-v1/studio/package-lock.json +1677 -0
  16. package/assets/worker-kits/growthub-zernio-social-v1/studio/package.json +20 -0
  17. package/assets/worker-kits/growthub-zernio-social-v1/studio/serve.mjs +60 -0
  18. package/assets/worker-kits/growthub-zernio-social-v1/studio/src/App.jsx +130 -0
  19. package/assets/worker-kits/growthub-zernio-social-v1/studio/src/api.js +146 -0
  20. package/assets/worker-kits/growthub-zernio-social-v1/studio/src/app.css +558 -0
  21. package/assets/worker-kits/growthub-zernio-social-v1/studio/src/lib/rules.js +64 -0
  22. package/assets/worker-kits/growthub-zernio-social-v1/studio/src/lib/templates.js +207 -0
  23. package/assets/worker-kits/growthub-zernio-social-v1/studio/src/main.jsx +10 -0
  24. package/assets/worker-kits/growthub-zernio-social-v1/studio/src/views/Accounts.jsx +57 -0
  25. package/assets/worker-kits/growthub-zernio-social-v1/studio/src/views/Agent.jsx +167 -0
  26. package/assets/worker-kits/growthub-zernio-social-v1/studio/src/views/Analytics.jsx +164 -0
  27. package/assets/worker-kits/growthub-zernio-social-v1/studio/src/views/ApiKeys.jsx +143 -0
  28. package/assets/worker-kits/growthub-zernio-social-v1/studio/src/views/Automations.jsx +122 -0
  29. package/assets/worker-kits/growthub-zernio-social-v1/studio/src/views/CommentRules.jsx +592 -0
  30. package/assets/worker-kits/growthub-zernio-social-v1/studio/src/views/Compose.jsx +185 -0
  31. package/assets/worker-kits/growthub-zernio-social-v1/studio/src/views/Dashboard.jsx +87 -0
  32. package/assets/worker-kits/growthub-zernio-social-v1/studio/src/views/Inbox.jsx +144 -0
  33. package/assets/worker-kits/growthub-zernio-social-v1/studio/src/views/Queues.jsx +167 -0
  34. package/assets/worker-kits/growthub-zernio-social-v1/studio/src/views/Scheduled.jsx +85 -0
  35. package/assets/worker-kits/growthub-zernio-social-v1/studio/src/views/Sequences.jsx +160 -0
  36. package/assets/worker-kits/growthub-zernio-social-v1/studio/src/views/Templates.jsx +275 -0
  37. package/assets/worker-kits/growthub-zernio-social-v1/studio/vite.config.js +7 -0
  38. package/assets/worker-kits/growthub-zernio-social-v1/workers/zernio-social-operator/CLAUDE.md +3 -3
  39. package/dist/index.js +8541 -850
  40. package/package.json +1 -1
  41. 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
+ };