@postact-js/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.
Files changed (3) hide show
  1. package/README.md +20 -0
  2. package/dist/index.js +932 -0
  3. package/package.json +28 -0
package/README.md ADDED
@@ -0,0 +1,20 @@
1
+ # @postact-js/cli
2
+
3
+ The [postact](https://postact.app) command line interface.
4
+
5
+ ```sh
6
+ npm i -g @postact-js/cli
7
+ postact login # device-code login, opens the browser
8
+ postact projects ls
9
+ postact use acme # pick a default project
10
+ postact actions ls
11
+ postact help
12
+ ```
13
+
14
+ Flags: `-p/--project <slug>`, `--json`, `-d/--data <json|@file|->`, `--api <url>`, `--yes`.
15
+
16
+ Config (session token + default project) lives in `%APPDATA%\postact\config.json` on Windows,
17
+ `$XDG_CONFIG_HOME/postact/config.json` elsewhere, written `0600`. `POSTACT_API_URL` overrides the
18
+ API base URL.
19
+
20
+ Firing actions from application code? Use [`@postact-js/sdk`](https://www.npmjs.com/package/@postact-js/sdk).
package/dist/index.js ADDED
@@ -0,0 +1,932 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
4
+
5
+ // src/index.ts
6
+ import { parseArgs } from "node:util";
7
+
8
+ // src/auth.ts
9
+ import { spawn } from "node:child_process";
10
+
11
+ // src/config.ts
12
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
13
+ import { homedir } from "node:os";
14
+ import { join } from "node:path";
15
+ var dir = process.env.APPDATA ? join(process.env.APPDATA, "postact") : join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "postact");
16
+ var file = join(dir, "config.json");
17
+ function load() {
18
+ try {
19
+ return existsSync(file) ? JSON.parse(readFileSync(file, "utf8")) : {};
20
+ } catch {
21
+ return {};
22
+ }
23
+ }
24
+ function save(c) {
25
+ mkdirSync(dir, { recursive: true });
26
+ writeFileSync(file, `${JSON.stringify(c, null, 2)}
27
+ `, { mode: 384 });
28
+ }
29
+ function apiUrl(override) {
30
+ return override || process.env.POSTACT_API_URL || load().apiUrl || "http://localhost:3001";
31
+ }
32
+
33
+ // src/auth.ts
34
+ var CLIENT_ID = "postact-cli";
35
+ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
36
+ function openBrowser(url) {
37
+ try {
38
+ if (process.platform === "win32") {
39
+ spawn("cmd", ["/c", "start", "", url], { stdio: "ignore", detached: true }).unref();
40
+ } else {
41
+ const cmd = process.platform === "darwin" ? "open" : "xdg-open";
42
+ spawn(cmd, [url], { stdio: "ignore", detached: true }).unref();
43
+ }
44
+ } catch {}
45
+ }
46
+ async function post(base, path, body, token) {
47
+ const res = await fetch(`${base}${path}`, {
48
+ method: "POST",
49
+ headers: {
50
+ "content-type": "application/json",
51
+ ...token ? { authorization: `Bearer ${token}` } : {}
52
+ },
53
+ body: JSON.stringify(body)
54
+ });
55
+ return await res.json().catch(() => ({}));
56
+ }
57
+ async function getSession(base, token) {
58
+ const res = await fetch(`${base}/api/auth/get-session`, {
59
+ headers: { authorization: `Bearer ${token}` }
60
+ });
61
+ return await res.json().catch(() => null);
62
+ }
63
+ async function login(apiOverride) {
64
+ const base = apiUrl(apiOverride);
65
+ const code = await post(base, "/api/auth/device/code", {
66
+ client_id: CLIENT_ID,
67
+ scope: ""
68
+ });
69
+ if (!code?.device_code)
70
+ throw new Error(code?.error_description || "could not start device login");
71
+ const url = code.verification_uri_complete ?? code.verification_uri ?? `${base}/device`;
72
+ console.log(`
73
+ Open: ${url}`);
74
+ console.log(` Code: ${code.user_code}
75
+ `);
76
+ openBrowser(url);
77
+ console.log("Waiting for approval…");
78
+ let interval = (code.interval ?? 5) * 1000;
79
+ for (;; ) {
80
+ await sleep(interval);
81
+ const tok = await post(base, "/api/auth/device/token", {
82
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
83
+ device_code: code.device_code,
84
+ client_id: CLIENT_ID
85
+ });
86
+ if (tok?.access_token) {
87
+ save({ ...load(), apiUrl: base, token: tok.access_token });
88
+ const s = await getSession(base, tok.access_token);
89
+ console.log(`✓ Logged in as ${s?.user?.email ?? "?"}`);
90
+ return;
91
+ }
92
+ if (tok?.error === "authorization_pending")
93
+ continue;
94
+ if (tok?.error === "slow_down") {
95
+ interval += 5000;
96
+ continue;
97
+ }
98
+ throw new Error(tok?.error_description || tok?.error || "device authorization failed");
99
+ }
100
+ }
101
+ async function logout() {
102
+ const cfg = load();
103
+ if (cfg.token)
104
+ await post(apiUrl(), "/api/auth/sign-out", {}, cfg.token).catch(() => {});
105
+ save({ ...cfg, token: undefined, defaultProjectId: undefined });
106
+ console.log("Logged out.");
107
+ }
108
+ async function whoami() {
109
+ const cfg = load();
110
+ if (!cfg.token) {
111
+ console.log("Not logged in — run `postact login`.");
112
+ return;
113
+ }
114
+ const s = await getSession(apiUrl(), cfg.token);
115
+ if (!s?.user) {
116
+ console.log("Session expired — run `postact login`.");
117
+ return;
118
+ }
119
+ console.log(`${s.user.email}${s.user.name ? ` (${s.user.name})` : ""}`);
120
+ }
121
+
122
+ // src/commands.ts
123
+ import { readFileSync as readFileSync2 } from "node:fs";
124
+
125
+ // ../../node_modules/.bun/@elysiajs+eden@1.4.9+b64ed85232ac6ff0/node_modules/@elysiajs/eden/dist/chunk-I5KHAGLL.mjs
126
+ var d = class extends Error {
127
+ constructor(e, s) {
128
+ super(s + "");
129
+ this.status = e;
130
+ this.value = s;
131
+ }
132
+ };
133
+ var i = /(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))/;
134
+ var o = /(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat)\s(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s\d{2}\s\d{4}\s\d{2}:\d{2}:\d{2}\sGMT(?:\+|-)\d{4}\s\([^)]+\)/;
135
+ var u = /^(?:(?:(?:(?:0?[1-9]|[12][0-9]|3[01])[/\s-](?:0?[1-9]|1[0-2])[/\s-](?:19|20)\d{2})|(?:(?:19|20)\d{2}[/\s-](?:0?[1-9]|1[0-2])[/\s-](?:0?[1-9]|[12][0-9]|3[01]))))(?:\s(?:1[012]|0?[1-9]):[0-5][0-9](?::[0-5][0-9])?(?:\s[AP]M)?)?$/;
136
+ var c = (t) => t.trim().length !== 0 && !Number.isNaN(Number(t));
137
+ var a = (t, r) => {
138
+ if (typeof t != "string" || r?.parseDate === false)
139
+ return null;
140
+ let e = t.replace(/"/g, "");
141
+ if (i.test(e) || o.test(e) || u.test(e)) {
142
+ let s = new Date(e);
143
+ if (!Number.isNaN(s.getTime()))
144
+ return s;
145
+ }
146
+ return null;
147
+ };
148
+ var p = (t) => {
149
+ let r = t.charCodeAt(0), e = t.charCodeAt(t.length - 1);
150
+ return r === 123 && e === 125 || r === 91 && e === 93;
151
+ };
152
+ var f = (t, r) => JSON.parse(t, (e, s) => {
153
+ let n = a(s, r);
154
+ return n || s;
155
+ });
156
+ var g = (t, r) => {
157
+ if (!t)
158
+ return t;
159
+ if (c(t))
160
+ return +t;
161
+ if (t === "true")
162
+ return true;
163
+ if (t === "false")
164
+ return false;
165
+ if (r?.parseDate !== false) {
166
+ let e = a(t, r);
167
+ if (e)
168
+ return e;
169
+ }
170
+ if (p(t))
171
+ try {
172
+ return f(t, r);
173
+ } catch {}
174
+ return t;
175
+ };
176
+ var S = (t, r) => {
177
+ let e = t.data.toString();
178
+ return e === "null" ? null : g(e, r);
179
+ };
180
+
181
+ // ../../node_modules/.bun/@elysiajs+eden@1.4.9+b64ed85232ac6ff0/node_modules/@elysiajs/eden/dist/chunk-TTKI5TQ7.mjs
182
+ var C = class {
183
+ constructor(t) {
184
+ this.url = t;
185
+ this.ws = new WebSocket(t);
186
+ }
187
+ ws;
188
+ send(t) {
189
+ return Array.isArray(t) ? (t.forEach((n) => this.send(n)), this) : (this.ws.send(typeof t == "object" ? JSON.stringify(t) : t.toString()), this);
190
+ }
191
+ on(t, n, r) {
192
+ return this.addEventListener(t, n, r);
193
+ }
194
+ off(t, n, r) {
195
+ return this.ws.removeEventListener(t, n, r), this;
196
+ }
197
+ subscribe(t, n) {
198
+ return this.addEventListener("message", t, n);
199
+ }
200
+ addEventListener(t, n, r) {
201
+ return this.ws.addEventListener(t, (o2) => {
202
+ if (t === "message") {
203
+ let i2 = S(o2);
204
+ n({ ...o2, data: i2 });
205
+ } else
206
+ n(o2);
207
+ }, r), this;
208
+ }
209
+ removeEventListener(t, n, r) {
210
+ return this.off(t, n, r), this;
211
+ }
212
+ close() {
213
+ return this.ws.close(), this;
214
+ }
215
+ };
216
+ var X = ["get", "post", "put", "delete", "patch", "options", "head", "connect", "subscribe"];
217
+ var P = (e, t) => typeof t == "function" ? t(e) : t === true;
218
+ var _ = ["localhost", "127.0.0.1", "0.0.0.0"];
219
+ var q = typeof FileList > "u";
220
+ var H = (e) => q ? e instanceof Blob : e instanceof FileList || e instanceof File;
221
+ var Y = (e) => {
222
+ if (!e)
223
+ return false;
224
+ for (let t in e)
225
+ if (H(e[t]) || Array.isArray(e[t]) && e[t].find(H))
226
+ return true;
227
+ return false;
228
+ };
229
+ var K = (e) => q ? e : new Promise((t) => {
230
+ let n = new FileReader;
231
+ n.onload = () => {
232
+ let r = new File([n.result], e.name, { lastModified: e.lastModified, type: e.type });
233
+ t(r);
234
+ }, n.readAsArrayBuffer(e);
235
+ });
236
+ var A = async (e, t, n = {}, r = {}) => {
237
+ if (Array.isArray(e)) {
238
+ for (let o2 of e)
239
+ if (!Array.isArray(o2))
240
+ r = await A(o2, t, n, r);
241
+ else {
242
+ let i2 = o2[0];
243
+ if (typeof i2 == "string")
244
+ r[i2.toLowerCase()] = o2[1];
245
+ else
246
+ for (let [s, u2] of i2)
247
+ r[s.toLowerCase()] = u2;
248
+ }
249
+ return r;
250
+ }
251
+ if (!e)
252
+ return r;
253
+ switch (typeof e) {
254
+ case "function":
255
+ if (e instanceof Headers)
256
+ return A(e, t, n, r);
257
+ let o2 = await e(t, n);
258
+ return o2 ? A(o2, t, n, r) : r;
259
+ case "object":
260
+ if (e instanceof Headers)
261
+ return e.forEach((i2, s) => {
262
+ r[s.toLowerCase()] = i2;
263
+ }), r;
264
+ for (let [i2, s] of Object.entries(e))
265
+ r[i2.toLowerCase()] = s;
266
+ return r;
267
+ default:
268
+ return r;
269
+ }
270
+ };
271
+ function V(e, t) {
272
+ let n = e.split(`
273
+ `), r = {};
274
+ for (let o2 of n) {
275
+ if (!o2 || o2.startsWith(":"))
276
+ continue;
277
+ let i2 = o2.indexOf(":");
278
+ if (i2 > 0) {
279
+ let s = o2.slice(0, i2).trim(), u2 = o2.slice(i2 + 1).replace(/^ /, "");
280
+ r[s] = u2 && g(u2, t);
281
+ }
282
+ }
283
+ return Object.keys(r).length > 0 ? r : null;
284
+ }
285
+ function* B(e, t) {
286
+ let n;
287
+ for (;(n = e.value.indexOf(`
288
+
289
+ `)) !== -1; ) {
290
+ let r = e.value.slice(0, n);
291
+ if (e.value = e.value.slice(n + 2), r.trim()) {
292
+ let o2 = V(r, t);
293
+ o2 && (yield o2);
294
+ }
295
+ }
296
+ }
297
+ async function* U(e, t) {
298
+ let n = e.body;
299
+ if (!n)
300
+ return;
301
+ let r = n.getReader(), o2 = new TextDecoder("utf-8");
302
+ if (e.headers.get("Content-Type")?.startsWith("text/event-stream")) {
303
+ let i2 = { value: "" };
304
+ try {
305
+ for (;; ) {
306
+ let { done: u2, value: x } = await r.read();
307
+ if (u2)
308
+ break;
309
+ let m = typeof x == "string" ? x : o2.decode(x, { stream: true });
310
+ i2.value += m, yield* B(i2, t);
311
+ }
312
+ let s = o2.decode();
313
+ if (s && (i2.value += s), yield* B(i2, t), i2.value.trim()) {
314
+ let u2 = V(i2.value, t);
315
+ u2 && (yield u2);
316
+ }
317
+ } finally {
318
+ r.releaseLock();
319
+ }
320
+ } else
321
+ try {
322
+ for (;; ) {
323
+ let { done: i2, value: s } = await r.read();
324
+ if (i2)
325
+ break;
326
+ yield g(typeof s == "string" ? s : o2.decode(s, { stream: true }), { parseDate: t?.parseDate });
327
+ }
328
+ } finally {
329
+ r.releaseLock();
330
+ }
331
+ }
332
+ var L = (e, t, n = [], r) => new Proxy(() => {}, { get(o2, i2) {
333
+ if (i2 === "~path")
334
+ return "/" + n.join("/");
335
+ if (!(n.length === 0 && (i2 === "then" || i2 === "catch" || i2 === "finally")))
336
+ return L(e, t, [...n, i2], r);
337
+ }, apply(o2, i2, [s, u2]) {
338
+ if (!s || u2 || typeof s == "object" && Object.keys(s).length !== 1 || X.includes(n.at(-1))) {
339
+ let x = [...n], m = x.pop(), b = "/" + x.join("/"), { fetcher: G = fetch, headers: R, onRequest: g2, onResponse: D, fetch: $ } = t, E = m === "get" || m === "head" || m === "subscribe", M = E ? s?.query : u2?.query, F = "";
340
+ if (M) {
341
+ let a2 = (k, d2) => {
342
+ d2 != null && (d2 instanceof Date && (d2 = d2.toISOString()), F += (F ? "&" : "?") + `${encodeURIComponent(k)}=${encodeURIComponent(typeof d2 == "object" ? JSON.stringify(d2) : d2 + "")}`);
343
+ };
344
+ for (let [k, d2] of Object.entries(M)) {
345
+ if (Array.isArray(d2)) {
346
+ for (let T of d2)
347
+ a2(k, T);
348
+ continue;
349
+ }
350
+ a2(k, d2);
351
+ }
352
+ }
353
+ if (m === "subscribe") {
354
+ let a2 = e.replace(/^([^]+):\/\//, e.startsWith("https://") ? "wss://" : e.startsWith("http://") || _.find((k) => e.includes(k)) ? "ws://" : "wss://") + b + F;
355
+ return new C(a2);
356
+ }
357
+ return (async () => {
358
+ R = await A(R, b, u2);
359
+ let a2 = { method: m?.toUpperCase(), body: s, ...$, headers: R };
360
+ a2.headers = { ...R, ...await A(E ? s?.headers : u2?.headers, b, a2) };
361
+ let k = E && typeof s == "object" ? s.fetch : u2?.fetch, T = (E && typeof s == "object" ? s.throwHttpError : u2?.throwHttpError) ?? t.throwHttpError;
362
+ if (a2 = { ...a2, ...k }, E && delete a2.body, g2) {
363
+ Array.isArray(g2) || (g2 = [g2]);
364
+ for (let y of g2) {
365
+ let c2 = await y(b, a2);
366
+ typeof c2 == "object" && (a2 = { ...a2, ...c2, headers: { ...a2.headers, ...await A(c2.headers, b, a2) } });
367
+ }
368
+ }
369
+ if (E && delete a2.body, Y(s)) {
370
+ let y = new FormData, c2 = (f2) => {
371
+ if (typeof f2 == "string" || H(f2))
372
+ return false;
373
+ if (typeof f2 == "object") {
374
+ if (f2 !== null)
375
+ return true;
376
+ if (f2 instanceof Date)
377
+ return false;
378
+ }
379
+ return false;
380
+ }, w = async (f2) => f2 instanceof File ? await K(f2) : c2(f2) ? JSON.stringify(f2) : f2;
381
+ for (let [f2, p2] of Object.entries(a2.body)) {
382
+ if (Array.isArray(p2)) {
383
+ if (p2.some((S2) => typeof S2 == "object" && S2 !== null && !H(S2)))
384
+ y.append(f2, JSON.stringify(p2));
385
+ else
386
+ for (let S2 = 0;S2 < p2.length; S2++) {
387
+ let z = p2[S2], Q = await w(z);
388
+ y.append(f2, Q);
389
+ }
390
+ continue;
391
+ }
392
+ if (q) {
393
+ if (Array.isArray(p2))
394
+ for (let O of p2)
395
+ y.append(f2, await w(O));
396
+ else
397
+ y.append(f2, await w(p2));
398
+ continue;
399
+ }
400
+ if (p2 instanceof File) {
401
+ y.append(f2, await K(p2));
402
+ continue;
403
+ }
404
+ if (p2 instanceof FileList) {
405
+ for (let O = 0;O < p2.length; O++)
406
+ y.append(f2, await K(p2[O]));
407
+ continue;
408
+ }
409
+ y.append(f2, await w(p2));
410
+ }
411
+ a2.body = y;
412
+ } else
413
+ typeof s == "object" ? (a2.headers["content-type"] = "application/json", a2.body = JSON.stringify(s)) : s != null && (a2.headers["content-type"] = "text/plain");
414
+ if (E && delete a2.body, g2) {
415
+ Array.isArray(g2) || (g2 = [g2]);
416
+ for (let y of g2) {
417
+ let c2 = await y(b, a2);
418
+ typeof c2 == "object" && (a2 = { ...a2, ...c2, headers: { ...a2.headers, ...await A(c2.headers, b, a2) } });
419
+ }
420
+ }
421
+ u2?.headers?.["content-type"] && (a2.headers["content-type"] = u2?.headers["content-type"]);
422
+ let I = e + b + F, l;
423
+ try {
424
+ l = await (r?.handle(new Request(I, a2)) ?? G(I, a2));
425
+ } catch (y) {
426
+ let c2 = new d(503, y);
427
+ if (P(c2, T))
428
+ throw c2;
429
+ return { data: null, error: c2, response: undefined, status: 503, headers: undefined };
430
+ }
431
+ let h = null, v = null;
432
+ if (D) {
433
+ Array.isArray(D) || (D = [D]);
434
+ for (let y of D)
435
+ try {
436
+ let c2 = await y(l.clone());
437
+ if (c2 != null) {
438
+ h = c2;
439
+ break;
440
+ }
441
+ } catch (c2) {
442
+ c2 instanceof d ? v = c2 : v = new d(422, c2);
443
+ break;
444
+ }
445
+ }
446
+ if (h !== null)
447
+ return { data: h, error: v, response: l, status: l.status, headers: l.headers };
448
+ switch (l.headers.get("Content-Type")?.split(";")[0]) {
449
+ case "text/event-stream":
450
+ h = U(l, { parseDate: t.parseDate });
451
+ break;
452
+ case "application/json":
453
+ h = JSON.parse(await l.text(), (c2, w) => {
454
+ if (typeof w != "string")
455
+ return w;
456
+ let f2 = a(w, { parseDate: t.parseDate });
457
+ return f2 || w;
458
+ });
459
+ break;
460
+ case "application/octet-stream":
461
+ h = await l.arrayBuffer();
462
+ break;
463
+ case "multipart/form-data":
464
+ let y = await l.formData();
465
+ h = {}, y.forEach((c2, w) => {
466
+ h[w] = c2;
467
+ });
468
+ break;
469
+ default:
470
+ l.headers.get("content-type")?.startsWith("text/") && l.headers.get("transfer-encoding") === "chunked" && !l.headers.has("content-length") ? h = U(l, { parseDate: t.parseDate }) : h = await l.text().then((c2) => g(c2, { parseDate: t.parseDate }));
471
+ }
472
+ if (l.status >= 300 || l.status < 200) {
473
+ if (v = new d(l.status, h), P(v, T))
474
+ throw v;
475
+ h = null;
476
+ }
477
+ return { data: h, error: v, response: l, status: l.status, headers: l.headers };
478
+ })();
479
+ }
480
+ return typeof s == "object" ? L(e, t, [...n, Object.values(s)[0]], r) : L(e, t, n);
481
+ } });
482
+ var se = (e, t = {}) => typeof e == "string" ? (t.keepDomain || (e.includes("://") || (e = (_.find((n) => e.includes(n)) ? "http://" : "https://") + e), e.endsWith("/") && (e = e.slice(0, -1))), L(e, t)) : (typeof window < "u" && console.warn("Elysia instance server found on client side, this is not recommended for security reason. Use generic type instead."), L("http://e.ly", t, [], e));
483
+
484
+ // src/client.ts
485
+ function client(apiOverride) {
486
+ const cfg = load();
487
+ if (!cfg.token)
488
+ throw new Error("Not logged in — run `postact login`.");
489
+ return se(apiUrl(apiOverride), {
490
+ headers: { authorization: `Bearer ${cfg.token}` }
491
+ });
492
+ }
493
+ function unwrap(res) {
494
+ if (res.error) {
495
+ const value = res.error.value;
496
+ const body = typeof value === "object" && value !== null ? value : {};
497
+ const msg = typeof body.error === "string" ? body.error : typeof body.message === "string" ? body.message : `request failed (${res.status})`;
498
+ throw Object.assign(new Error(msg), { status: res.status });
499
+ }
500
+ return res.data;
501
+ }
502
+ var UUID = /^[0-9a-f-]{36}$/i;
503
+ async function resolveProjectId(api, want) {
504
+ const target = want || load().defaultProjectId;
505
+ if (!target)
506
+ throw new Error("No project selected — run `postact use <slug>` or pass --project <slug>.");
507
+ if (UUID.test(target))
508
+ return target;
509
+ const { projects } = unwrap(await api.app.projects.get());
510
+ const p2 = projects.find((x) => x.slug === target || x.id === target);
511
+ if (!p2)
512
+ throw new Error(`No project matching "${target}".`);
513
+ return p2.id;
514
+ }
515
+ function setDefaultProject(id) {
516
+ save({ ...load(), defaultProjectId: id });
517
+ }
518
+ var cell = (v) => v == null ? "" : typeof v === "object" ? JSON.stringify(v) : String(v);
519
+ function output(data, json) {
520
+ if (json || !Array.isArray(data)) {
521
+ console.log(JSON.stringify(data ?? null, null, 2));
522
+ return;
523
+ }
524
+ const rows = data;
525
+ if (rows.length === 0) {
526
+ console.log("(none)");
527
+ return;
528
+ }
529
+ const cols = [...new Set(rows.flatMap((r) => Object.keys(r)))];
530
+ const widths = cols.map((c2) => Math.max(c2.length, ...rows.map((r) => cell(r[c2]).length)));
531
+ const line = (cells) => cells.map((c2, i2) => cell(c2).padEnd(widths[i2])).join(" ");
532
+ console.log(line(cols));
533
+ for (const r of rows)
534
+ console.log(line(cols.map((c2) => r[c2])));
535
+ }
536
+
537
+ // src/commands.ts
538
+ var str = (v) => typeof v === "string" ? v : undefined;
539
+ var json = (ctx) => ctx.flags.json === true;
540
+ async function data(ctx) {
541
+ const d2 = str(ctx.flags.data);
542
+ if (!d2)
543
+ return {};
544
+ const raw = d2 === "-" ? await Bun.stdin.text() : d2.startsWith("@") ? readFileSync2(d2.slice(1), "utf8") : d2;
545
+ const parsed = JSON.parse(raw);
546
+ if (typeof parsed !== "object" || parsed === null)
547
+ throw new Error("--data must be a JSON object");
548
+ return parsed;
549
+ }
550
+ async function body(ctx, extra) {
551
+ const base = await data(ctx);
552
+ for (const [k, v] of Object.entries(extra))
553
+ if (v !== undefined)
554
+ base[k] = v;
555
+ return base;
556
+ }
557
+ async function withProject(ctx) {
558
+ const api = client(str(ctx.flags.api));
559
+ const pid = await resolveProjectId(api, str(ctx.flags.project));
560
+ return { api, pid };
561
+ }
562
+ var need = (ctx, i2, name) => {
563
+ const v = ctx.args[i2];
564
+ if (!v)
565
+ throw new Error(`missing <${name}>`);
566
+ return v;
567
+ };
568
+ var commands = {
569
+ projects: {
570
+ async ls(ctx) {
571
+ const api = client(str(ctx.flags.api));
572
+ output(unwrap(await api.app.projects.get()).projects, json(ctx));
573
+ },
574
+ async get(ctx) {
575
+ const { api, pid } = await withProject(ctx);
576
+ output(unwrap(await api.app.p({ projectId: pid }).get()), json(ctx));
577
+ },
578
+ async create(ctx) {
579
+ const api = client(str(ctx.flags.api));
580
+ const name = str(ctx.flags.name) ?? need(ctx, 0, "name");
581
+ const res = unwrap(await api.app.projects.post({ name }));
582
+ console.log(`Created project ${res.projectId}`);
583
+ console.log(`Secret key (shown once): ${res.sk}`);
584
+ },
585
+ async use(ctx) {
586
+ const api = client(str(ctx.flags.api));
587
+ const pid = await resolveProjectId(api, need(ctx, 0, "slug|id"));
588
+ setDefaultProject(pid);
589
+ console.log(`Default project set to ${pid}`);
590
+ }
591
+ },
592
+ actions: {
593
+ async ls(ctx) {
594
+ const { api, pid } = await withProject(ctx);
595
+ const { actions } = unwrap(await api.app.p({ projectId: pid }).actions.get());
596
+ output(actions.map((a2) => ({
597
+ id: a2.id,
598
+ name: a2.name,
599
+ slug: a2.slug,
600
+ trigger: a2.trigger,
601
+ events: a2.eventCount
602
+ })), json(ctx));
603
+ },
604
+ async get(ctx) {
605
+ const { api, pid } = await withProject(ctx);
606
+ const id = need(ctx, 0, "actionId");
607
+ output(unwrap(await api.app.p({ projectId: pid }).actions({ actionId: id }).get()).action, json(ctx));
608
+ },
609
+ async create(ctx) {
610
+ const { api, pid } = await withProject(ctx);
611
+ const b = await body(ctx, {
612
+ name: str(ctx.flags.name),
613
+ slug: str(ctx.flags.slug),
614
+ trigger: str(ctx.flags.trigger)
615
+ });
616
+ output(unwrap(await api.app.p({ projectId: pid }).actions.post(b)).action, json(ctx));
617
+ },
618
+ async update(ctx) {
619
+ const { api, pid } = await withProject(ctx);
620
+ const id = need(ctx, 0, "actionId");
621
+ const b = await body(ctx, {
622
+ name: str(ctx.flags.name),
623
+ slug: str(ctx.flags.slug),
624
+ trigger: str(ctx.flags.trigger)
625
+ });
626
+ output(unwrap(await api.app.p({ projectId: pid }).actions({ actionId: id }).patch(b)).action, json(ctx));
627
+ },
628
+ async rm(ctx) {
629
+ const { api, pid } = await withProject(ctx);
630
+ const id = need(ctx, 0, "actionId");
631
+ output(unwrap(await api.app.p({ projectId: pid }).actions({ actionId: id }).delete()), json(ctx));
632
+ },
633
+ async run(ctx) {
634
+ const { api, pid } = await withProject(ctx);
635
+ const id = need(ctx, 0, "actionId");
636
+ output(unwrap(await api.app.p({ projectId: pid }).actions({ actionId: id }).run.post({})), json(ctx));
637
+ }
638
+ },
639
+ people: {
640
+ async ls(ctx) {
641
+ const { api, pid } = await withProject(ctx);
642
+ const { people } = unwrap(await api.app.p({ projectId: pid }).filters({ filterId: "people" }).get());
643
+ output(people, json(ctx));
644
+ },
645
+ async get(ctx) {
646
+ const { api, pid } = await withProject(ctx);
647
+ const id = need(ctx, 0, "personId");
648
+ output(unwrap(await api.app.p({ projectId: pid }).people({ personId: id }).get()), json(ctx));
649
+ },
650
+ async create(ctx) {
651
+ const { api, pid } = await withProject(ctx);
652
+ const email = str(ctx.flags.email) ?? need(ctx, 0, "email");
653
+ const fields = await data(ctx);
654
+ output(unwrap(await api.app.p({ projectId: pid }).people.post({ email, fields })).person, json(ctx));
655
+ },
656
+ async update(ctx) {
657
+ const { api, pid } = await withProject(ctx);
658
+ const id = need(ctx, 0, "personId");
659
+ const fields = await data(ctx);
660
+ const b = { email: str(ctx.flags.email) ?? "", fields, originalKeys: Object.keys(fields) };
661
+ output(unwrap(await api.app.p({ projectId: pid }).people({ personId: id }).patch(b)).person, json(ctx));
662
+ },
663
+ async rm(ctx) {
664
+ const { api, pid } = await withProject(ctx);
665
+ const id = need(ctx, 0, "personId");
666
+ output(unwrap(await api.app.p({ projectId: pid }).people({ personId: id }).delete()).person, json(ctx));
667
+ }
668
+ },
669
+ events: {
670
+ async ls(ctx) {
671
+ const { api, pid } = await withProject(ctx);
672
+ const filterId = str(ctx.flags.filter) ?? ctx.args[0] ?? "queue";
673
+ const res = unwrap(await api.app.p({ projectId: pid }).filters({ filterId }).get());
674
+ output("events" in res ? res.events : res, json(ctx));
675
+ },
676
+ async reply(ctx) {
677
+ const { api, pid } = await withProject(ctx);
678
+ const id = need(ctx, 0, "eventId");
679
+ const b = {
680
+ templateId: str(ctx.flags.template) ?? null,
681
+ body: str(ctx.flags.body) ?? "",
682
+ close: ctx.flags.close === true
683
+ };
684
+ output(unwrap(await api.app.p({ projectId: pid }).events({ eventId: id }).reply.post(b)), json(ctx));
685
+ },
686
+ async status(ctx) {
687
+ const { api, pid } = await withProject(ctx);
688
+ const id = need(ctx, 0, "eventId");
689
+ const status = str(ctx.flags.status) ?? need(ctx, 1, "open|closed");
690
+ output(unwrap(await api.app.p({ projectId: pid }).events({ eventId: id }).status.post({ status })), json(ctx));
691
+ }
692
+ },
693
+ filters: {
694
+ async ls(ctx) {
695
+ const { api, pid } = await withProject(ctx);
696
+ output(unwrap(await api.app.p({ projectId: pid }).get()).nav.savedFilters, json(ctx));
697
+ },
698
+ async view(ctx) {
699
+ const { api, pid } = await withProject(ctx);
700
+ const id = need(ctx, 0, "filterId");
701
+ output(unwrap(await api.app.p({ projectId: pid }).filters({ filterId: id }).get()), json(ctx));
702
+ },
703
+ async create(ctx) {
704
+ const { api, pid } = await withProject(ctx);
705
+ const b = await body(ctx, { name: str(ctx.flags.name), scope: str(ctx.flags.scope) });
706
+ output(unwrap(await api.app.p({ projectId: pid }).filters.post(b)).filter, json(ctx));
707
+ },
708
+ async rm(ctx) {
709
+ const { api, pid } = await withProject(ctx);
710
+ const id = need(ctx, 0, "filterId");
711
+ output(unwrap(await api.app.p({ projectId: pid }).filters({ filterId: id }).delete()), json(ctx));
712
+ }
713
+ },
714
+ templates: {
715
+ async ls(ctx) {
716
+ const { api, pid } = await withProject(ctx);
717
+ output(unwrap(await api.app.p({ projectId: pid }).emails.templates.get()).templates, json(ctx));
718
+ },
719
+ async get(ctx) {
720
+ const { api, pid } = await withProject(ctx);
721
+ const id = need(ctx, 0, "templateId");
722
+ output(unwrap(await api.app.p({ projectId: pid }).emails.templates({ id }).get()).template, json(ctx));
723
+ },
724
+ async create(ctx) {
725
+ const { api, pid } = await withProject(ctx);
726
+ const b = await body(ctx, {
727
+ name: str(ctx.flags.name),
728
+ subject: str(ctx.flags.subject),
729
+ bodyHtml: str(ctx.flags.body)
730
+ });
731
+ output(unwrap(await api.app.p({ projectId: pid }).emails.templates.post(b)).template, json(ctx));
732
+ },
733
+ async update(ctx) {
734
+ const { api, pid } = await withProject(ctx);
735
+ const id = need(ctx, 0, "templateId");
736
+ const b = await body(ctx, {
737
+ name: str(ctx.flags.name),
738
+ subject: str(ctx.flags.subject),
739
+ bodyHtml: str(ctx.flags.body)
740
+ });
741
+ output(unwrap(await api.app.p({ projectId: pid }).emails.templates({ id }).patch(b)), json(ctx));
742
+ },
743
+ async rm(ctx) {
744
+ const { api, pid } = await withProject(ctx);
745
+ const id = need(ctx, 0, "templateId");
746
+ output(unwrap(await api.app.p({ projectId: pid }).emails.templates({ id }).delete()), json(ctx));
747
+ }
748
+ },
749
+ emails: {
750
+ async ls(ctx) {
751
+ const { api, pid } = await withProject(ctx);
752
+ output(unwrap(await api.app.p({ projectId: pid }).emails.get()).emails, json(ctx));
753
+ }
754
+ },
755
+ keys: {
756
+ async show(ctx) {
757
+ const { api, pid } = await withProject(ctx);
758
+ output(unwrap(await api.app.p({ projectId: pid }).settings.keys.get()), json(ctx));
759
+ },
760
+ async rotate(ctx) {
761
+ const { api, pid } = await withProject(ctx);
762
+ const res = unwrap(await api.app.p({ projectId: pid }).settings.keys.rotate.post({}));
763
+ console.log(`New secret key (shown once): ${res.sk}`);
764
+ }
765
+ },
766
+ vault: {
767
+ async ls(ctx) {
768
+ const { api, pid } = await withProject(ctx);
769
+ output(unwrap(await api.app.p({ projectId: pid }).settings.vault.get()).secrets, json(ctx));
770
+ },
771
+ async set(ctx) {
772
+ const { api, pid } = await withProject(ctx);
773
+ const name = str(ctx.flags.name) ?? need(ctx, 0, "NAME");
774
+ const value = str(ctx.flags.value) ?? need(ctx, 1, "value");
775
+ output(unwrap(await api.app.p({ projectId: pid }).settings.vault.post({ name, value })), json(ctx));
776
+ },
777
+ async rm(ctx) {
778
+ const { api, pid } = await withProject(ctx);
779
+ const id = need(ctx, 0, "secretId");
780
+ output(unwrap(await api.app.p({ projectId: pid }).settings.vault({ secretId: id }).delete()), json(ctx));
781
+ }
782
+ },
783
+ domains: {
784
+ async ls(ctx) {
785
+ const { api, pid } = await withProject(ctx);
786
+ output(unwrap(await api.app.p({ projectId: pid }).settings.domains.get()).domains, json(ctx));
787
+ },
788
+ async records(ctx) {
789
+ const { api, pid } = await withProject(ctx);
790
+ const id = need(ctx, 0, "domainId");
791
+ output(unwrap(await api.app.p({ projectId: pid }).settings.domains({ id }).records.get()).records, json(ctx));
792
+ },
793
+ async add(ctx) {
794
+ const { api, pid } = await withProject(ctx);
795
+ const domain = str(ctx.flags.domain) ?? need(ctx, 0, "domain");
796
+ output(unwrap(await api.app.p({ projectId: pid }).settings.domains.post({ domain })), json(ctx));
797
+ },
798
+ async verify(ctx) {
799
+ const { api, pid } = await withProject(ctx);
800
+ const id = need(ctx, 0, "domainId");
801
+ output(unwrap(await api.app.p({ projectId: pid }).settings.domains({ id }).verify.post({})), json(ctx));
802
+ },
803
+ async rm(ctx) {
804
+ const { api, pid } = await withProject(ctx);
805
+ const id = need(ctx, 0, "domainId");
806
+ output(unwrap(await api.app.p({ projectId: pid }).settings.domains({ id }).delete()), json(ctx));
807
+ }
808
+ },
809
+ collaborators: {
810
+ async ls(ctx) {
811
+ const { api, pid } = await withProject(ctx);
812
+ output(unwrap(await api.app.p({ projectId: pid }).settings.collaborators.get()), json(ctx));
813
+ },
814
+ async invite(ctx) {
815
+ const { api, pid } = await withProject(ctx);
816
+ const email = str(ctx.flags.email) ?? need(ctx, 0, "email");
817
+ output(unwrap(await api.app.p({ projectId: pid }).settings.collaborators.post({ email })), json(ctx));
818
+ },
819
+ async resend(ctx) {
820
+ const { api, pid } = await withProject(ctx);
821
+ const inviteId = need(ctx, 0, "inviteId");
822
+ output(unwrap(await api.app.p({ projectId: pid }).settings.collaborators.invites({ inviteId }).resend.post({})), json(ctx));
823
+ },
824
+ async "rm-invite"(ctx) {
825
+ const { api, pid } = await withProject(ctx);
826
+ const inviteId = need(ctx, 0, "inviteId");
827
+ output(unwrap(await api.app.p({ projectId: pid }).settings.collaborators.invites({ inviteId }).delete()), json(ctx));
828
+ },
829
+ async "rm-member"(ctx) {
830
+ const { api, pid } = await withProject(ctx);
831
+ const memberId = need(ctx, 0, "memberId");
832
+ output(unwrap(await api.app.p({ projectId: pid }).settings.collaborators.members({ memberId }).delete()), json(ctx));
833
+ }
834
+ },
835
+ branding: {
836
+ async show(ctx) {
837
+ const { api, pid } = await withProject(ctx);
838
+ output(unwrap(await api.app.p({ projectId: pid }).settings.branding.get()), json(ctx));
839
+ },
840
+ async set(ctx) {
841
+ const { api, pid } = await withProject(ctx);
842
+ const b = await body(ctx, {});
843
+ output(unwrap(await api.app.p({ projectId: pid }).settings.branding.patch(b)), json(ctx));
844
+ }
845
+ },
846
+ project: {
847
+ async rename(ctx) {
848
+ const { api, pid } = await withProject(ctx);
849
+ const b = await body(ctx, { name: str(ctx.flags.name), slug: str(ctx.flags.slug) });
850
+ output(unwrap(await api.app.p({ projectId: pid }).settings.project.patch(b)), json(ctx));
851
+ },
852
+ async rm(ctx) {
853
+ const { api, pid } = await withProject(ctx);
854
+ if (ctx.flags.yes !== true)
855
+ throw new Error("Refusing to delete a project without --yes");
856
+ output(unwrap(await api.app.p({ projectId: pid }).settings.project.delete()), json(ctx));
857
+ }
858
+ }
859
+ };
860
+
861
+ // src/index.ts
862
+ var options = {
863
+ help: { type: "boolean", short: "h" },
864
+ json: { type: "boolean" },
865
+ yes: { type: "boolean" },
866
+ close: { type: "boolean" },
867
+ project: { type: "string", short: "p" },
868
+ api: { type: "string" },
869
+ data: { type: "string", short: "d" },
870
+ name: { type: "string" },
871
+ slug: { type: "string" },
872
+ email: { type: "string" },
873
+ trigger: { type: "string" },
874
+ status: { type: "string" },
875
+ template: { type: "string" },
876
+ body: { type: "string" },
877
+ subject: { type: "string" },
878
+ scope: { type: "string" },
879
+ value: { type: "string" },
880
+ domain: { type: "string" },
881
+ filter: { type: "string" }
882
+ };
883
+ function parseArgv(args) {
884
+ const { values, positionals } = parseArgs({ args, allowPositionals: true, options });
885
+ const [group, verb, ...rest] = positionals;
886
+ return { group, verb, rest, flags: values };
887
+ }
888
+ function help() {
889
+ console.log(`postact — CLI for the postact dashboard API
890
+
891
+ Usage: postact <command> [verb] [args] [--flags]
892
+
893
+ Auth:
894
+ login device-code login (opens the browser)
895
+ logout revoke + clear the local token
896
+ whoami show the signed-in user
897
+
898
+ Commands:`);
899
+ for (const [g2, verbs] of Object.entries(commands))
900
+ console.log(` ${g2.padEnd(14)} ${Object.keys(verbs).join(", ")}`);
901
+ console.log(`
902
+ Flags: -p/--project <slug|id>, --json, -d/--data <json|@file|->, --api <url>, --yes,
903
+ plus field shortcuts (--name, --email, --slug, --trigger, --status, --body, …)`);
904
+ }
905
+ async function main() {
906
+ const { group, verb, rest, flags } = parseArgv(process.argv.slice(2));
907
+ if (!group || group === "help" || flags.help === true)
908
+ return help();
909
+ if (group === "login")
910
+ return login(typeof flags.api === "string" ? flags.api : undefined);
911
+ if (group === "logout")
912
+ return logout();
913
+ if (group === "whoami")
914
+ return whoami();
915
+ const grp = commands[group];
916
+ if (!grp)
917
+ throw new Error(`unknown command "${group}" — run \`postact help\``);
918
+ const handler = verb ? grp[verb] : undefined;
919
+ if (!handler)
920
+ throw new Error(`unknown verb "${verb ?? ""}" for ${group}
921
+ verbs: ${Object.keys(grp).join(", ")}`);
922
+ await handler({ args: rest, flags });
923
+ }
924
+ if (__require.main == __require.module) {
925
+ main().catch((e) => {
926
+ console.error(`Error: ${e instanceof Error ? e.message : String(e)}`);
927
+ process.exit(1);
928
+ });
929
+ }
930
+ export {
931
+ parseArgv
932
+ };
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@postact-js/cli",
3
+ "version": "0.1.0",
4
+ "description": "Command line interface for postact.",
5
+ "type": "module",
6
+ "bin": {
7
+ "postact": "./dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "homepage": "https://postact.app",
13
+ "publishConfig": {
14
+ "access": "public"
15
+ },
16
+ "scripts": {
17
+ "start": "bun run src/index.ts",
18
+ "build": "bun build src/index.ts --target=node --outdir dist --banner \"#!/usr/bin/env node\"",
19
+ "prepublishOnly": "bun run build",
20
+ "typecheck": "tsc --noEmit"
21
+ },
22
+ "devDependencies": {
23
+ "@elysiajs/eden": "^1.4.9",
24
+ "@types/bun": "^1.3.14",
25
+ "@userdeck/backend": "workspace:*",
26
+ "typescript": "^5"
27
+ }
28
+ }