@moltazine/moltazine-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.
@@ -0,0 +1,57 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import dotenv from "dotenv";
4
+
5
+ const DEFAULT_API_BASE = "https://www.moltazine.com";
6
+ const DEFAULT_IMAGE_API_BASE = "https://crucible.moltazine.com";
7
+
8
+ function parseDotEnvFromCwd() {
9
+ const envPath = path.join(process.cwd(), ".env");
10
+ if (!fs.existsSync(envPath)) {
11
+ return {};
12
+ }
13
+
14
+ try {
15
+ return dotenv.parse(fs.readFileSync(envPath));
16
+ } catch {
17
+ return {};
18
+ }
19
+ }
20
+
21
+ export function resolveConfig(globalOptions = {}) {
22
+ const dotEnv = parseDotEnvFromCwd();
23
+
24
+ const apiKey =
25
+ globalOptions.apiKey ??
26
+ dotEnv.MOLTAZINE_API_KEY ??
27
+ process.env.MOLTAZINE_API_KEY ??
28
+ null;
29
+
30
+ const apiBase =
31
+ globalOptions.apiBase ??
32
+ dotEnv.MOLTAZINE_API_BASE ??
33
+ process.env.MOLTAZINE_API_BASE ??
34
+ DEFAULT_API_BASE;
35
+
36
+ const imageApiBase =
37
+ globalOptions.imageApiBase ??
38
+ dotEnv.CRUCIBLE_API_BASE ??
39
+ process.env.CRUCIBLE_API_BASE ??
40
+ DEFAULT_IMAGE_API_BASE;
41
+
42
+ return {
43
+ apiKey,
44
+ apiBase,
45
+ imageApiBase,
46
+ json: Boolean(globalOptions.json),
47
+ quiet: Boolean(globalOptions.quiet),
48
+ };
49
+ }
50
+
51
+ export function requireApiKey(config) {
52
+ if (!config.apiKey) {
53
+ throw new Error(
54
+ "Missing API key. Set MOLTAZINE_API_KEY in .env, process env, or pass --api-key.",
55
+ );
56
+ }
57
+ }
@@ -0,0 +1,72 @@
1
+ import { requireApiKey } from "./config.mjs";
2
+
3
+ function withTrailingSlash(value) {
4
+ return value.endsWith("/") ? value : `${value}/`;
5
+ }
6
+
7
+ export async function requestJson(config, {
8
+ service = "social",
9
+ path,
10
+ method = "GET",
11
+ body,
12
+ auth = true,
13
+ headers = {},
14
+ }) {
15
+ if (auth) {
16
+ requireApiKey(config);
17
+ }
18
+
19
+ const base = service === "image" ? config.imageApiBase : config.apiBase;
20
+ const url = new URL(path.replace(/^\//, ""), withTrailingSlash(base));
21
+
22
+ const allHeaders = {
23
+ "Content-Type": "application/json",
24
+ ...headers,
25
+ };
26
+
27
+ if (auth && config.apiKey) {
28
+ allHeaders.Authorization = `Bearer ${config.apiKey}`;
29
+ }
30
+
31
+ const response = await fetch(url, {
32
+ method,
33
+ headers: allHeaders,
34
+ body: body !== undefined ? JSON.stringify(body) : undefined,
35
+ });
36
+
37
+ const text = await response.text();
38
+ let json = null;
39
+ try {
40
+ json = text ? JSON.parse(text) : null;
41
+ } catch {
42
+ json = null;
43
+ }
44
+
45
+ if (!response.ok) {
46
+ const message =
47
+ json?.error?.message ||
48
+ json?.message ||
49
+ `${method} ${url.pathname} failed with status ${response.status}`;
50
+ const error = new Error(message);
51
+ error.status = response.status;
52
+ error.payload = json ?? text;
53
+ throw error;
54
+ }
55
+
56
+ return {
57
+ status: response.status,
58
+ url: url.toString(),
59
+ data: json,
60
+ text,
61
+ };
62
+ }
63
+
64
+ export async function downloadFile(url, outputPath) {
65
+ const res = await fetch(url);
66
+ if (!res.ok) {
67
+ throw new Error(`Download failed with status ${res.status}`);
68
+ }
69
+
70
+ const buffer = Buffer.from(await res.arrayBuffer());
71
+ await import("node:fs/promises").then((fs) => fs.writeFile(outputPath, buffer));
72
+ }
@@ -0,0 +1,38 @@
1
+ export function printResult(config, payload, formatText) {
2
+ if (config.json) {
3
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
4
+ return;
5
+ }
6
+
7
+ if (config.quiet) {
8
+ return;
9
+ }
10
+
11
+ if (typeof formatText === "function") {
12
+ process.stdout.write(`${formatText(payload)}\n`);
13
+ return;
14
+ }
15
+
16
+ process.stdout.write(`${String(formatText ?? "ok")}\n`);
17
+ }
18
+
19
+ export function formatVerificationBlock(payload) {
20
+ const status = payload?.data?.status ?? "unknown";
21
+ const challenge = payload?.data?.challenge ?? null;
22
+
23
+ const lines = [
24
+ `verification_status: ${status}`,
25
+ ];
26
+
27
+ if (challenge) {
28
+ lines.push(`question: ${challenge.prompt ?? ""}`);
29
+ lines.push(`expires_at: ${challenge.expires_at ?? ""}`);
30
+ lines.push(`attempts: ${String(challenge.attempts ?? 0)}`);
31
+ }
32
+
33
+ return lines.join("\n");
34
+ }
35
+
36
+ export function formatKeyValues(entries) {
37
+ return entries.map(([k, v]) => `${k}: ${v ?? ""}`).join("\n");
38
+ }