@promarketingstore/cli 0.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.
- package/dist/index.js +311 -0
- package/package.json +28 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { createRequire } from "module";
|
|
5
|
+
import { Command as Command5 } from "commander";
|
|
6
|
+
|
|
7
|
+
// src/commands/login.ts
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from "fs";
|
|
9
|
+
import { createServer } from "http";
|
|
10
|
+
import { homedir } from "os";
|
|
11
|
+
import { join } from "path";
|
|
12
|
+
import { exec } from "child_process";
|
|
13
|
+
import { Command } from "commander";
|
|
14
|
+
|
|
15
|
+
// src/output.ts
|
|
16
|
+
import chalk from "chalk";
|
|
17
|
+
function writeLine(msg) {
|
|
18
|
+
console.log(msg);
|
|
19
|
+
}
|
|
20
|
+
function writeError(msg) {
|
|
21
|
+
console.error(chalk.red("Error:"), msg);
|
|
22
|
+
}
|
|
23
|
+
function writeSuccess(msg) {
|
|
24
|
+
console.log(chalk.green("\u2713"), msg);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// src/commands/login.ts
|
|
28
|
+
var CONFIG_DIR = join(homedir(), ".config", "promarketingstore");
|
|
29
|
+
var TOKEN_FILE = join(CONFIG_DIR, "session.json");
|
|
30
|
+
var API_BASE = "https://promarketingstore-backend.serge-the-dev.workers.dev";
|
|
31
|
+
var GOOGLE_CLIENT_ID = "645025980105-q413u7cf7leigdkvv9apgbgh32kfbib2.apps.googleusercontent.com";
|
|
32
|
+
function loadSession() {
|
|
33
|
+
try {
|
|
34
|
+
if (!existsSync(TOKEN_FILE)) return null;
|
|
35
|
+
const data = JSON.parse(readFileSync(TOKEN_FILE, "utf-8"));
|
|
36
|
+
if (data.expiresAt < Date.now()) {
|
|
37
|
+
writeLine("Session expired. Run: pms login");
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
return data;
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function saveSession(session) {
|
|
46
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
47
|
+
writeFileSync(TOKEN_FILE, JSON.stringify(session, null, 2), { mode: 384 });
|
|
48
|
+
}
|
|
49
|
+
function requireSession() {
|
|
50
|
+
const session = loadSession();
|
|
51
|
+
if (!session) {
|
|
52
|
+
writeError("Not logged in. Run: pms login");
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
return session;
|
|
56
|
+
}
|
|
57
|
+
var loginCommand = new Command("login").description("Sign in with Google (opens browser)").action(async () => {
|
|
58
|
+
writeLine("Opening browser for Google sign-in...");
|
|
59
|
+
const port = await findFreePort();
|
|
60
|
+
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
61
|
+
const tokenPromise = new Promise((resolve, reject) => {
|
|
62
|
+
const timeout = setTimeout(() => {
|
|
63
|
+
server.close();
|
|
64
|
+
reject(new Error("Login timed out (60s)"));
|
|
65
|
+
}, 6e4);
|
|
66
|
+
const server = createServer(async (req, res) => {
|
|
67
|
+
const url = new URL(req.url || "/", `http://127.0.0.1:${port}`);
|
|
68
|
+
if (url.pathname !== "/callback") {
|
|
69
|
+
res.writeHead(404);
|
|
70
|
+
res.end();
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const code = url.searchParams.get("code");
|
|
74
|
+
if (!code) {
|
|
75
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
76
|
+
res.end("<h1>Sign-in failed</h1><p>No authorization code received.</p>");
|
|
77
|
+
clearTimeout(timeout);
|
|
78
|
+
server.close();
|
|
79
|
+
reject(new Error("No code in callback"));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
const exchangeRes = await fetch(`${API_BASE}/v1/auth/google`, {
|
|
84
|
+
method: "POST",
|
|
85
|
+
headers: { "Content-Type": "application/json" },
|
|
86
|
+
body: JSON.stringify({ code, redirect_uri: redirectUri })
|
|
87
|
+
});
|
|
88
|
+
const data = await exchangeRes.json();
|
|
89
|
+
if (!data.token) throw new Error(data.error || "Exchange failed");
|
|
90
|
+
saveSession({
|
|
91
|
+
token: data.token,
|
|
92
|
+
user: data.user,
|
|
93
|
+
expiresAt: Date.now() + 29 * 24 * 60 * 60 * 1e3
|
|
94
|
+
});
|
|
95
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
96
|
+
res.end(`<h1>Signed in as ${data.user?.email}</h1><p>You can close this tab.</p><script>window.close()</script>`);
|
|
97
|
+
clearTimeout(timeout);
|
|
98
|
+
server.close();
|
|
99
|
+
resolve();
|
|
100
|
+
} catch (err) {
|
|
101
|
+
res.writeHead(500, { "Content-Type": "text/html" });
|
|
102
|
+
res.end(`<h1>Sign-in failed</h1><p>${err instanceof Error ? err.message : String(err)}</p>`);
|
|
103
|
+
clearTimeout(timeout);
|
|
104
|
+
server.close();
|
|
105
|
+
reject(err);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
server.listen(port, "127.0.0.1");
|
|
109
|
+
});
|
|
110
|
+
const params = new URLSearchParams({
|
|
111
|
+
client_id: GOOGLE_CLIENT_ID,
|
|
112
|
+
redirect_uri: redirectUri,
|
|
113
|
+
response_type: "code",
|
|
114
|
+
scope: "openid email profile",
|
|
115
|
+
access_type: "offline",
|
|
116
|
+
prompt: "consent"
|
|
117
|
+
});
|
|
118
|
+
const oauthUrl = `https://accounts.google.com/o/oauth2/v2/auth?${params}`;
|
|
119
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
120
|
+
exec(`${cmd} "${oauthUrl}"`);
|
|
121
|
+
try {
|
|
122
|
+
await tokenPromise;
|
|
123
|
+
const session = loadSession();
|
|
124
|
+
writeLine(`Signed in as ${session?.user.email}. Token stored at ${TOKEN_FILE}`);
|
|
125
|
+
} catch (err) {
|
|
126
|
+
writeError(err instanceof Error ? err.message : String(err));
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
var logoutCommand = new Command("logout").description("Sign out and remove stored session").action(() => {
|
|
131
|
+
try {
|
|
132
|
+
if (existsSync(TOKEN_FILE)) unlinkSync(TOKEN_FILE);
|
|
133
|
+
writeLine("Signed out.");
|
|
134
|
+
} catch {
|
|
135
|
+
writeLine("Already signed out.");
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
var whoamiCommand = new Command("whoami").description("Show current signed-in user").action(() => {
|
|
139
|
+
const session = loadSession();
|
|
140
|
+
if (session) {
|
|
141
|
+
writeLine(`${session.user.email} (${session.user.id})`);
|
|
142
|
+
} else {
|
|
143
|
+
writeLine("Not logged in. Run: pms login");
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
async function findFreePort() {
|
|
147
|
+
return new Promise((resolve) => {
|
|
148
|
+
const server = createServer();
|
|
149
|
+
server.listen(0, "127.0.0.1", () => {
|
|
150
|
+
const addr = server.address();
|
|
151
|
+
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
152
|
+
server.close(() => resolve(port));
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// src/commands/status.ts
|
|
158
|
+
import { Command as Command2 } from "commander";
|
|
159
|
+
|
|
160
|
+
// src/api.ts
|
|
161
|
+
var API_BASE2 = "https://promarketingstore-backend.serge-the-dev.workers.dev";
|
|
162
|
+
async function api(path, opts = {}) {
|
|
163
|
+
const session = loadSession();
|
|
164
|
+
const headers = {
|
|
165
|
+
...opts.headers
|
|
166
|
+
};
|
|
167
|
+
if (session) headers["Authorization"] = `Bearer ${session.token}`;
|
|
168
|
+
if (opts.body && typeof opts.body === "string") {
|
|
169
|
+
headers["Content-Type"] = "application/json";
|
|
170
|
+
}
|
|
171
|
+
const res = await fetch(`${API_BASE2}/v1${path}`, { ...opts, headers });
|
|
172
|
+
const data = await res.json();
|
|
173
|
+
if (!res.ok) {
|
|
174
|
+
throw new Error(data.error || `API error ${res.status}`);
|
|
175
|
+
}
|
|
176
|
+
return data;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/commands/status.ts
|
|
180
|
+
var statusCommand = new Command2("status").description("Show connection status \u2014 backend, auth, social accounts").action(async () => {
|
|
181
|
+
try {
|
|
182
|
+
const health = await fetch(`${API_BASE2}/`).then((r) => r.json());
|
|
183
|
+
if (health.ok) writeSuccess(`Backend: connected (${API_BASE2})`);
|
|
184
|
+
else writeError("Backend: unhealthy");
|
|
185
|
+
} catch {
|
|
186
|
+
writeError(`Backend: offline (${API_BASE2})`);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const session = loadSession();
|
|
190
|
+
if (!session) {
|
|
191
|
+
writeLine("Auth: not logged in (run: pms login)");
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
writeSuccess(`Auth: ${session.user.email}`);
|
|
195
|
+
try {
|
|
196
|
+
const { providers } = await api("/social/providers");
|
|
197
|
+
writeLine("\nSocial Providers:");
|
|
198
|
+
for (const p of providers) {
|
|
199
|
+
const icon = p.status === "oauth-ready" || p.status === "account-discovery-ready" ? "\u2713" : "\u25CB";
|
|
200
|
+
writeLine(` ${icon} ${p.name} (${p.status})`);
|
|
201
|
+
}
|
|
202
|
+
} catch {
|
|
203
|
+
}
|
|
204
|
+
try {
|
|
205
|
+
const { accounts } = await api("/social/accounts");
|
|
206
|
+
if (accounts.length) {
|
|
207
|
+
writeLine("\nConnected Accounts:");
|
|
208
|
+
for (const a of accounts) writeLine(` \u2713 ${a.provider}: ${a.display_name}`);
|
|
209
|
+
} else {
|
|
210
|
+
writeLine("\nNo social accounts connected. Use the console to connect.");
|
|
211
|
+
}
|
|
212
|
+
} catch {
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// src/commands/campaigns.ts
|
|
217
|
+
import { Command as Command3 } from "commander";
|
|
218
|
+
var campaignsCommand = new Command3("campaigns").description("List and manage campaigns").action(async () => {
|
|
219
|
+
requireSession();
|
|
220
|
+
try {
|
|
221
|
+
const { campaigns } = await api("/campaigns");
|
|
222
|
+
if (!campaigns.length) {
|
|
223
|
+
writeLine("No campaigns. Create one: pms campaign create --name 'My Campaign'");
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
writeLine(`${campaigns.length} campaign(s):
|
|
227
|
+
`);
|
|
228
|
+
for (const c of campaigns) {
|
|
229
|
+
writeLine(` ${c.status === "active" ? "\u25CF" : "\u25CB"} ${c.name} (${c.id})`);
|
|
230
|
+
writeLine(` Goal: ${c.goal || "\u2014"} | Status: ${c.status} | Created: ${c.created_at?.slice(0, 10)}`);
|
|
231
|
+
}
|
|
232
|
+
} catch (err) {
|
|
233
|
+
writeError(err instanceof Error ? err.message : String(err));
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
var campaignCreateCommand = new Command3("create").description("Create a new campaign").requiredOption("--name <name>", "Campaign name").option("--goal <goal>", "Campaign goal").option("--channels <channels>", "Comma-separated channels (x,facebook,instagram,tiktok)").action(async (opts) => {
|
|
237
|
+
requireSession();
|
|
238
|
+
try {
|
|
239
|
+
const data = await api("/campaigns", {
|
|
240
|
+
method: "POST",
|
|
241
|
+
body: JSON.stringify({
|
|
242
|
+
name: opts.name,
|
|
243
|
+
goal: opts.goal,
|
|
244
|
+
channels: opts.channels?.split(",").map((s) => s.trim())
|
|
245
|
+
})
|
|
246
|
+
});
|
|
247
|
+
writeSuccess(`Campaign created: ${data.campaign.id}`);
|
|
248
|
+
} catch (err) {
|
|
249
|
+
writeError(err instanceof Error ? err.message : String(err));
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
campaignsCommand.addCommand(campaignCreateCommand);
|
|
253
|
+
|
|
254
|
+
// src/commands/post.ts
|
|
255
|
+
import { Command as Command4 } from "commander";
|
|
256
|
+
var postCommand = new Command4("post").description("Create and publish a post to social platforms").requiredOption("--caption <text>", "Post caption/text").option("--media <path>", "Path to media file (image or video)").option("--platforms <list>", "Comma-separated platforms (x,facebook,instagram,tiktok)", "x,facebook,instagram,tiktok").option("--draft", "Create as draft (don't publish immediately)").action(async (opts) => {
|
|
257
|
+
requireSession();
|
|
258
|
+
const platforms = opts.platforms.split(",").map((s) => s.trim());
|
|
259
|
+
try {
|
|
260
|
+
const body = {
|
|
261
|
+
caption: opts.caption,
|
|
262
|
+
platforms,
|
|
263
|
+
status: opts.draft ? "draft" : "queued"
|
|
264
|
+
};
|
|
265
|
+
const data = await api("/posts", {
|
|
266
|
+
method: "POST",
|
|
267
|
+
body: JSON.stringify(body)
|
|
268
|
+
});
|
|
269
|
+
if (opts.draft) {
|
|
270
|
+
writeSuccess(`Draft created: ${data.post.id}`);
|
|
271
|
+
writeLine(" Publish with: pms post publish --id " + data.post.id);
|
|
272
|
+
} else {
|
|
273
|
+
writeSuccess(`Post queued for publishing: ${data.post.id}`);
|
|
274
|
+
writeLine(` Platforms: ${platforms.join(", ")}`);
|
|
275
|
+
}
|
|
276
|
+
} catch (err) {
|
|
277
|
+
writeError(err instanceof Error ? err.message : String(err));
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
var postPublishCommand = new Command4("publish").description("Publish a draft post").requiredOption("--id <postId>", "Post ID to publish").action(async (opts) => {
|
|
281
|
+
requireSession();
|
|
282
|
+
try {
|
|
283
|
+
const data = await api(`/posts/${opts.id}/publish`, { method: "POST", body: "{}" });
|
|
284
|
+
writeSuccess(`Published: ${data.results?.length || 0} platform(s)`);
|
|
285
|
+
for (const r of data.results || []) {
|
|
286
|
+
const icon = r.success ? "\u2713" : "\u2717";
|
|
287
|
+
writeLine(` ${icon} ${r.platform}: ${r.link || r.error || "done"}`);
|
|
288
|
+
}
|
|
289
|
+
} catch (err) {
|
|
290
|
+
writeError(err instanceof Error ? err.message : String(err));
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
postCommand.addCommand(postPublishCommand);
|
|
294
|
+
|
|
295
|
+
// src/index.ts
|
|
296
|
+
var require2 = createRequire(import.meta.url);
|
|
297
|
+
var { version } = require2("../package.json");
|
|
298
|
+
var program = new Command5();
|
|
299
|
+
program.name("pms").description("ProMarketingStore CLI \u2014 manage campaigns and publish to social platforms").version(version);
|
|
300
|
+
program.addCommand(loginCommand);
|
|
301
|
+
program.addCommand(logoutCommand);
|
|
302
|
+
program.addCommand(whoamiCommand);
|
|
303
|
+
program.addCommand(statusCommand);
|
|
304
|
+
program.addCommand(campaignsCommand);
|
|
305
|
+
program.addCommand(postCommand);
|
|
306
|
+
try {
|
|
307
|
+
await program.parseAsync();
|
|
308
|
+
} catch (error) {
|
|
309
|
+
writeError(error instanceof Error ? error.message : String(error));
|
|
310
|
+
process.exit(1);
|
|
311
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@promarketingstore/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for ProMarketingStore — manage campaigns and publish to social platforms",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"pms": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsup src/index.ts --format esm",
|
|
12
|
+
"dev": "tsx src/index.ts",
|
|
13
|
+
"typecheck": "tsc --noEmit"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"chalk": "^5.6.2",
|
|
17
|
+
"commander": "^13.0.0"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/node": "^22.0.0",
|
|
21
|
+
"tsup": "^8.4.0",
|
|
22
|
+
"tsx": "^4.21.0",
|
|
23
|
+
"typescript": "^5.7.0"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist"
|
|
27
|
+
]
|
|
28
|
+
}
|