lovieme 0.0.0-stage → 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/README.md CHANGED
@@ -1,3 +1,101 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
1
+ # 🚀 LovieMe Developer CLI (`lovieme`)
2
+
3
+ The official command-line interface for [LovieMe](https://www.lovie.me) — claim free `*.lovie.me` subdomains, manage global Anycast DNS records, and wire up 1-click cloud hosting presets directly from your terminal or CI/CD pipelines.
4
+
5
+ ---
6
+
7
+ ## ⚡ Zero Installation (Run with `npx`)
8
+
9
+ ```bash
10
+ # Check subdomain availability
11
+ npx lovieme subdomains check my-app
12
+
13
+ # Run live multi-resolver DoH propagation check
14
+ npx lovieme verify my-app
15
+ ```
16
+
17
+ ---
18
+
19
+ ## 📦 Global Installation
20
+
21
+ ```bash
22
+ npm install -g lovieme
23
+ ```
24
+
25
+ ---
26
+
27
+ ## 🔑 Authentication
28
+
29
+ Log in with your LovieMe API key (generate in [Dashboard > API Keys](https://www.lovie.me/dashboard/api-keys)):
30
+
31
+ ```bash
32
+ # Save token locally to ~/.lovieme/config.json
33
+ lovieme login lovie_live_your_token_here
34
+
35
+ # Inspect your account and quotas
36
+ lovieme whoami
37
+ ```
38
+
39
+ Or set the environment variable:
40
+
41
+ ```bash
42
+ export LOVIE_API_KEY="lovie_live_your_token_here"
43
+ ```
44
+
45
+ ---
46
+
47
+ ## 📖 Command Reference
48
+
49
+ ### Subdomains
50
+ ```bash
51
+ # Check availability
52
+ lovieme subdomains check <name>
53
+
54
+ # Claim a subdomain
55
+ lovieme subdomains claim <name>
56
+
57
+ # List your claimed subdomains
58
+ lovieme subdomains list
59
+
60
+ # Delete/release a subdomain
61
+ lovieme subdomains delete <name>
62
+ ```
63
+
64
+ ### Anycast DNS Records
65
+ ```bash
66
+ # List records for a subdomain
67
+ lovieme dns list <subdomain>
68
+
69
+ # Add a CNAME record (points @ to Vercel)
70
+ lovieme dns add <subdomain> --type CNAME --name @ --content cname.vercel-dns.com
71
+
72
+ # Add an A record (points api to your VPS)
73
+ lovieme dns add <subdomain> --type A --name api --content 192.0.2.1
74
+
75
+ # Delete a record by ID
76
+ lovieme dns rm <id>
77
+ ```
78
+
79
+ ### 1-Click Hosting Presets
80
+ ```bash
81
+ # List all verified presets
82
+ lovieme presets list
83
+
84
+ # Apply Vercel preset
85
+ lovieme presets apply <subdomain> vercel
86
+
87
+ # Apply GitHub Pages preset
88
+ lovieme presets apply <subdomain> github-pages --target username.github.io
89
+ ```
90
+
91
+ ### Global DoH Radar Verification
92
+ ```bash
93
+ # Verify propagation worldwide (Google, Cloudflare, Quad9)
94
+ lovieme verify <subdomain> --type CNAME
95
+ ```
96
+
97
+ ---
98
+
99
+ ## 📄 License
100
+ MIT © Sagar Kewat (Sagarithm)
101
+
package/bin/lovieme.js ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { main } from "../dist/index.js";
4
+
5
+ main().catch((err) => {
6
+ console.error("LovieMe CLI Error:", err);
7
+ process.exit(1);
8
+ });
9
+
@@ -0,0 +1,105 @@
1
+ export declare class CliClient {
2
+ private baseUrl;
3
+ private apiKey;
4
+ constructor();
5
+ private request;
6
+ checkSubdomain(name: string): Promise<{
7
+ available: boolean;
8
+ name: string;
9
+ fqdn: string;
10
+ reason?: string;
11
+ }>;
12
+ getProfile(): Promise<{
13
+ profile: {
14
+ id: string;
15
+ email?: string;
16
+ name?: string;
17
+ plan: string;
18
+ quota: {
19
+ subdomains_max: number;
20
+ subdomains_used: number;
21
+ subdomains_remaining: number;
22
+ };
23
+ };
24
+ }>;
25
+ listSubdomains(): Promise<{
26
+ subdomains: Array<{
27
+ id: string;
28
+ name: string;
29
+ fqdn: string;
30
+ created_at: string;
31
+ dns_records_count: number;
32
+ email_forwarder_active: boolean;
33
+ }>;
34
+ }>;
35
+ claimSubdomain(name: string): Promise<{
36
+ success: boolean;
37
+ message: string;
38
+ subdomain: {
39
+ id: string;
40
+ name: string;
41
+ fqdn: string;
42
+ };
43
+ }>;
44
+ deleteSubdomain(name: string): Promise<{
45
+ success: boolean;
46
+ message: string;
47
+ }>;
48
+ listDnsRecords(subdomain: string): Promise<{
49
+ dns_records: Array<{
50
+ id: string;
51
+ type: string;
52
+ name: string;
53
+ content: string;
54
+ ttl: number;
55
+ priority?: number;
56
+ }>;
57
+ }>;
58
+ addDnsRecord(data: {
59
+ subdomain: string;
60
+ type: string;
61
+ name: string;
62
+ content: string;
63
+ ttl?: number;
64
+ priority?: number;
65
+ }): Promise<{
66
+ success: boolean;
67
+ message: string;
68
+ record: {
69
+ id: string;
70
+ };
71
+ }>;
72
+ removeDnsRecord(id: string): Promise<{
73
+ success: boolean;
74
+ message: string;
75
+ }>;
76
+ listPresets(): Promise<{
77
+ presets: Array<{
78
+ id: string;
79
+ name: string;
80
+ description: string;
81
+ records: Array<{
82
+ name: string;
83
+ type: string;
84
+ content: string;
85
+ }>;
86
+ }>;
87
+ }>;
88
+ applyPreset(subdomain: string, preset: string, customTarget?: string): Promise<{
89
+ success: boolean;
90
+ message: string;
91
+ created_records: Array<{
92
+ id: string;
93
+ name: string;
94
+ type: string;
95
+ content: string;
96
+ }>;
97
+ }>;
98
+ verifyDoH(domain: string, type?: string): Promise<{
99
+ resolver: string;
100
+ status: string;
101
+ latency: string;
102
+ targets: string;
103
+ }[]>;
104
+ private ensureAuth;
105
+ }
package/dist/client.js ADDED
@@ -0,0 +1,119 @@
1
+ import { getConfig } from "./config.js";
2
+ export class CliClient {
3
+ baseUrl;
4
+ apiKey;
5
+ constructor() {
6
+ const config = getConfig();
7
+ this.apiKey = config.apiKey || "";
8
+ this.baseUrl = (config.apiUrl || "https://www.lovie.me").replace(/\/+$/, "");
9
+ }
10
+ async request(path, options = {}) {
11
+ const url = `${this.baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
12
+ const headers = {
13
+ "Content-Type": "application/json",
14
+ "User-Agent": "LovieMe-CLI/0.1.0",
15
+ ...(options.headers || {}),
16
+ };
17
+ if (this.apiKey) {
18
+ headers["Authorization"] = `Bearer ${this.apiKey}`;
19
+ }
20
+ const res = await fetch(url, { ...options, headers });
21
+ const isJson = (res.headers.get("content-type") || "").includes("application/json");
22
+ const payload = isJson ? await res.json() : await res.text();
23
+ if (!res.ok) {
24
+ const msg = (typeof payload === "object" && payload !== null && (payload.error || payload.message)) ||
25
+ `HTTP ${res.status}: ${res.statusText}`;
26
+ throw new Error(String(msg));
27
+ }
28
+ return payload;
29
+ }
30
+ async checkSubdomain(name) {
31
+ const clean = name.replace(/\.lovie\.me$/i, "").trim().toLowerCase();
32
+ return this.request(`/api/v1/subdomains/check?name=${encodeURIComponent(clean)}`);
33
+ }
34
+ async getProfile() {
35
+ this.ensureAuth();
36
+ return this.request("/api/v1/me");
37
+ }
38
+ async listSubdomains() {
39
+ this.ensureAuth();
40
+ return this.request("/api/v1/subdomains");
41
+ }
42
+ async claimSubdomain(name) {
43
+ this.ensureAuth();
44
+ const clean = name.replace(/\.lovie\.me$/i, "").trim().toLowerCase();
45
+ return this.request("/api/v1/subdomains", { method: "POST", body: JSON.stringify({ name: clean }) });
46
+ }
47
+ async deleteSubdomain(name) {
48
+ this.ensureAuth();
49
+ const clean = name.replace(/\.lovie\.me$/i, "").trim().toLowerCase();
50
+ return this.request(`/api/v1/subdomains/${encodeURIComponent(clean)}`, {
51
+ method: "DELETE",
52
+ });
53
+ }
54
+ async listDnsRecords(subdomain) {
55
+ this.ensureAuth();
56
+ const clean = subdomain.replace(/\.lovie\.me$/i, "").trim().toLowerCase();
57
+ return this.request(`/api/v1/dns/records?subdomain=${encodeURIComponent(clean)}`);
58
+ }
59
+ async addDnsRecord(data) {
60
+ this.ensureAuth();
61
+ return this.request("/api/v1/dns/records", {
62
+ method: "POST",
63
+ body: JSON.stringify(data),
64
+ });
65
+ }
66
+ async removeDnsRecord(id) {
67
+ this.ensureAuth();
68
+ return this.request(`/api/v1/dns/records/${encodeURIComponent(id)}`, {
69
+ method: "DELETE",
70
+ });
71
+ }
72
+ async listPresets() {
73
+ return this.request("/api/v1/dns/presets");
74
+ }
75
+ async applyPreset(subdomain, preset, customTarget) {
76
+ this.ensureAuth();
77
+ return this.request("/api/v1/dns/presets", {
78
+ method: "POST",
79
+ body: JSON.stringify({ subdomain, preset, customTarget }),
80
+ });
81
+ }
82
+ async verifyDoH(domain, type = "A") {
83
+ const fqdn = domain.endsWith(".lovie.me") ? domain : `${domain}.lovie.me`;
84
+ const resolvers = [
85
+ { name: "Google Public DNS", url: `https://dns.google/resolve?name=${encodeURIComponent(fqdn)}&type=${type}` },
86
+ { name: "Cloudflare 1.1.1.1", url: `https://cloudflare-dns.com/dns-query?name=${encodeURIComponent(fqdn)}&type=${type}` },
87
+ { name: "Quad9 9.9.9.9", url: `https://dns.quad9.net/dns-query?name=${encodeURIComponent(fqdn)}&type=${type}` },
88
+ ];
89
+ const settled = await Promise.allSettled(resolvers.map(async (r) => {
90
+ const start = Date.now();
91
+ const res = await fetch(r.url, { headers: { Accept: "application/dns-json" } });
92
+ const latency = Date.now() - start;
93
+ if (!res.ok)
94
+ throw new Error(`HTTP ${res.status}`);
95
+ const data = (await res.json());
96
+ return {
97
+ resolver: r.name,
98
+ status: data.Answer && data.Answer.length > 0 ? "RESOLVED" : "NOT FOUND",
99
+ latency: `${latency}ms`,
100
+ targets: (data.Answer || []).map((a) => a.data).join(", ") || "-",
101
+ };
102
+ }));
103
+ return settled.map((s, i) => {
104
+ if (s.status === "fulfilled")
105
+ return s.value;
106
+ return {
107
+ resolver: resolvers[i].name,
108
+ status: "ERROR",
109
+ latency: "-",
110
+ targets: "-",
111
+ };
112
+ });
113
+ }
114
+ ensureAuth() {
115
+ if (!this.apiKey) {
116
+ throw new Error("Authentication required. Run `lovieme login <token>` or set LOVIE_API_KEY environment variable.");
117
+ }
118
+ }
119
+ }
@@ -0,0 +1,7 @@
1
+ export interface LovieMeConfig {
2
+ apiKey?: string;
3
+ apiUrl?: string;
4
+ }
5
+ export declare function getConfig(): LovieMeConfig;
6
+ export declare function saveApiKey(key: string, url?: string): void;
7
+ export declare function clearConfig(): void;
package/dist/config.js ADDED
@@ -0,0 +1,40 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import * as os from "node:os";
4
+ const CONFIG_DIR = path.join(os.homedir(), ".lovieme");
5
+ const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
6
+ export function getConfig() {
7
+ const envKey = process.env.LOVIE_API_KEY;
8
+ const envUrl = process.env.LOVIE_API_URL;
9
+ let fileConfig = {};
10
+ if (fs.existsSync(CONFIG_FILE)) {
11
+ try {
12
+ const raw = fs.readFileSync(CONFIG_FILE, "utf-8");
13
+ fileConfig = JSON.parse(raw);
14
+ }
15
+ catch {
16
+ // Ignore corrupted config
17
+ }
18
+ }
19
+ return {
20
+ apiKey: envKey || fileConfig.apiKey || "",
21
+ apiUrl: envUrl || fileConfig.apiUrl || "https://www.lovie.me",
22
+ };
23
+ }
24
+ export function saveApiKey(key, url) {
25
+ if (!fs.existsSync(CONFIG_DIR)) {
26
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
27
+ }
28
+ const existing = getConfig();
29
+ const nextConfig = {
30
+ ...existing,
31
+ apiKey: key.trim(),
32
+ apiUrl: url || existing.apiUrl || "https://www.lovie.me",
33
+ };
34
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(nextConfig, null, 2), "utf-8");
35
+ }
36
+ export function clearConfig() {
37
+ if (fs.existsSync(CONFIG_FILE)) {
38
+ fs.unlinkSync(CONFIG_FILE);
39
+ }
40
+ }
@@ -0,0 +1,4 @@
1
+ import { Command } from "commander";
2
+ import { CliClient } from "./client.js";
3
+ export declare function createProgram(client?: CliClient): Command;
4
+ export declare function main(): Promise<void>;
package/dist/index.js ADDED
@@ -0,0 +1,250 @@
1
+ import { Command } from "commander";
2
+ import { CliClient } from "./client.js";
3
+ import { saveApiKey, clearConfig } from "./config.js";
4
+ import { logSuccess, logInfo, logError, formatTable, colors } from "./ui.js";
5
+ export function createProgram(client) {
6
+ const cli = client || new CliClient();
7
+ const program = new Command();
8
+ program
9
+ .name("lovieme")
10
+ .description("Official CLI for LovieMe — Developer & AI-Agent Custom Subdomains & Anycast DNS Cloud")
11
+ .version("0.1.0");
12
+ // 1. Auth: login / logout / whoami
13
+ program
14
+ .command("login")
15
+ .description("Log in to LovieMe using your personal or AI agent API key (lovie_live_...)")
16
+ .argument("<key>", "API key secret")
17
+ .action((key) => {
18
+ saveApiKey(key);
19
+ logSuccess("Saved API key to ~/.lovieme/config.json");
20
+ });
21
+ program
22
+ .command("logout")
23
+ .description("Remove stored credentials")
24
+ .action(() => {
25
+ clearConfig();
26
+ logSuccess("Logged out and purged ~/.lovieme/config.json");
27
+ });
28
+ program
29
+ .command("whoami")
30
+ .description("Show authenticated profile, plan tier, and subdomain quotas")
31
+ .action(async () => {
32
+ try {
33
+ const { profile } = await cli.getProfile();
34
+ console.log(`\n${colors.bold}LovieMe Account Profile${colors.reset}`);
35
+ console.log(` User: ${profile.name || profile.email || profile.id}`);
36
+ console.log(` Plan: ${colors.green}${profile.plan.toUpperCase()}${colors.reset}`);
37
+ console.log(` Subdomains Used: ${profile.quota.subdomains_used} / ${profile.quota.subdomains_max}`);
38
+ console.log(` Remaining: ${profile.quota.subdomains_remaining}\n`);
39
+ }
40
+ catch (err) {
41
+ logError(err instanceof Error ? err.message : String(err));
42
+ process.exitCode = 1;
43
+ }
44
+ });
45
+ // 2. Subdomains: check / claim / list / rm
46
+ const subdomains = program.command("subdomains").alias("sub").description("Manage custom *.lovie.me subdomains");
47
+ subdomains
48
+ .command("check <name>")
49
+ .description("Check availability of a subdomain prefix")
50
+ .action(async (name) => {
51
+ try {
52
+ const res = await cli.checkSubdomain(name);
53
+ if (res.available) {
54
+ logSuccess(`${colors.bold}${res.fqdn}${colors.reset} is AVAILABLE!`);
55
+ }
56
+ else {
57
+ logError(`${colors.bold}${res.fqdn}${colors.reset} is NOT available: ${res.reason || "already registered"}`);
58
+ }
59
+ }
60
+ catch (err) {
61
+ logError(err instanceof Error ? err.message : String(err));
62
+ process.exitCode = 1;
63
+ }
64
+ });
65
+ subdomains
66
+ .command("claim <name>")
67
+ .description("Claim an available subdomain prefix under your account")
68
+ .action(async (name) => {
69
+ try {
70
+ const res = await cli.claimSubdomain(name);
71
+ logSuccess(`Subdomain claimed: ${colors.bold}${res.subdomain.fqdn}${colors.reset}`);
72
+ }
73
+ catch (err) {
74
+ logError(err instanceof Error ? err.message : String(err));
75
+ process.exitCode = 1;
76
+ }
77
+ });
78
+ subdomains
79
+ .command("list")
80
+ .description("List all subdomains owned by your account")
81
+ .action(async () => {
82
+ try {
83
+ const { subdomains: list } = await cli.listSubdomains();
84
+ if (list.length === 0) {
85
+ logInfo("You have no claimed subdomains yet. Use `lovieme subdomains claim <name>`.");
86
+ return;
87
+ }
88
+ const headers = ["NAME", "FQDN", "DNS RECORDS", "FORWARDER", "CREATED AT"];
89
+ const rows = list.map((s) => [
90
+ s.name,
91
+ s.fqdn,
92
+ String(s.dns_records_count),
93
+ s.email_forwarder_active ? "Active" : "None",
94
+ new Date(s.created_at).toISOString().slice(0, 10),
95
+ ]);
96
+ console.log(`\n${formatTable(headers, rows)}\n`);
97
+ }
98
+ catch (err) {
99
+ logError(err instanceof Error ? err.message : String(err));
100
+ process.exitCode = 1;
101
+ }
102
+ });
103
+ subdomains
104
+ .command("delete <name>")
105
+ .alias("rm")
106
+ .description("Release and permanently delete a subdomain")
107
+ .action(async (name) => {
108
+ try {
109
+ await cli.deleteSubdomain(name);
110
+ logSuccess(`Deleted subdomain: ${name}.lovie.me`);
111
+ }
112
+ catch (err) {
113
+ logError(err instanceof Error ? err.message : String(err));
114
+ process.exitCode = 1;
115
+ }
116
+ });
117
+ // 3. DNS: list / add / rm
118
+ const dns = program.command("dns").description("Manage Gcore Anycast DNS records for your subdomains");
119
+ dns
120
+ .command("list <subdomain>")
121
+ .description("List all active DNS records for a subdomain")
122
+ .action(async (subdomain) => {
123
+ try {
124
+ const { dns_records: records } = await cli.listDnsRecords(subdomain);
125
+ if (records.length === 0) {
126
+ logInfo(`No DNS records found for ${subdomain}.lovie.me.`);
127
+ return;
128
+ }
129
+ const headers = ["ID", "TYPE", "NAME", "CONTENT", "TTL", "PRIORITY"];
130
+ const rows = records.map((r) => [
131
+ r.id.slice(0, 8),
132
+ r.type,
133
+ r.name,
134
+ r.content,
135
+ String(r.ttl),
136
+ r.priority !== undefined && r.priority !== null ? String(r.priority) : "-",
137
+ ]);
138
+ console.log(`\n${formatTable(headers, rows)}\n`);
139
+ }
140
+ catch (err) {
141
+ logError(err instanceof Error ? err.message : String(err));
142
+ process.exitCode = 1;
143
+ }
144
+ });
145
+ dns
146
+ .command("add <subdomain>")
147
+ .description("Create a new DNS record on Anycast nameservers")
148
+ .requiredOption("-t, --type <type>", "Record type (A, AAAA, CNAME, TXT, MX)")
149
+ .requiredOption("-n, --name <name>", "Host label (@, www, api, etc.)")
150
+ .requiredOption("-c, --content <content>", "Target IP or host")
151
+ .option("--ttl <ttl>", "TTL in seconds", "300")
152
+ .option("-p, --priority <priority>", "Priority (required for MX)")
153
+ .action(async (subdomain, opts) => {
154
+ try {
155
+ await cli.addDnsRecord({
156
+ subdomain,
157
+ type: opts.type.toUpperCase(),
158
+ name: opts.name,
159
+ content: opts.content,
160
+ ttl: parseInt(opts.ttl, 10),
161
+ priority: opts.priority ? parseInt(opts.priority, 10) : undefined,
162
+ });
163
+ logSuccess(`Created ${opts.type.toUpperCase()} record on ${opts.name}.${subdomain}.lovie.me`);
164
+ }
165
+ catch (err) {
166
+ logError(err instanceof Error ? err.message : String(err));
167
+ process.exitCode = 1;
168
+ }
169
+ });
170
+ dns
171
+ .command("rm <id>")
172
+ .description("Delete a DNS record by ID")
173
+ .action(async (id) => {
174
+ try {
175
+ await cli.removeDnsRecord(id);
176
+ logSuccess(`Deleted DNS record: ${id}`);
177
+ }
178
+ catch (err) {
179
+ logError(err instanceof Error ? err.message : String(err));
180
+ process.exitCode = 1;
181
+ }
182
+ });
183
+ // 4. Presets
184
+ const presets = program.command("presets").description("1-Click Cloud Hosting Presets (Vercel, Railway, Render, etc.)");
185
+ presets
186
+ .command("list")
187
+ .description("List all verified cloud hosting presets")
188
+ .action(async () => {
189
+ try {
190
+ const { presets: list } = await cli.listPresets();
191
+ const headers = ["ID", "PLATFORM", "DESCRIPTION", "DEFAULT RECORDS"];
192
+ const rows = list.map((p) => [
193
+ p.id,
194
+ p.name,
195
+ p.description,
196
+ p.records.map((r) => `${r.type} ${r.content}`).join("; "),
197
+ ]);
198
+ console.log(`\n${formatTable(headers, rows)}\n`);
199
+ }
200
+ catch (err) {
201
+ logError(err instanceof Error ? err.message : String(err));
202
+ process.exitCode = 1;
203
+ }
204
+ });
205
+ presets
206
+ .command("apply <subdomain> <preset>")
207
+ .description("Apply a cloud hosting preset to a subdomain")
208
+ .option("-t, --target <customTarget>", "Custom target value (e.g. GitHub username or Railway target)")
209
+ .action(async (subdomain, preset, opts) => {
210
+ try {
211
+ const res = await cli.applyPreset(subdomain, preset, opts.target);
212
+ logSuccess(res.message);
213
+ for (const r of res.created_records) {
214
+ console.log(` + ${r.type} ${r.name} -> ${r.content}`);
215
+ }
216
+ }
217
+ catch (err) {
218
+ logError(err instanceof Error ? err.message : String(err));
219
+ process.exitCode = 1;
220
+ }
221
+ });
222
+ // 5. Verify DoH
223
+ program
224
+ .command("verify <domain>")
225
+ .description("Verify live Anycast propagation across Google (8.8.8.8), Cloudflare (1.1.1.1), and Quad9 (9.9.9.9)")
226
+ .option("-t, --type <type>", "Record type to verify (A, CNAME, etc.)", "A")
227
+ .action(async (domain, opts) => {
228
+ try {
229
+ logInfo(`Verifying DoH propagation for ${domain} (${opts.type})...`);
230
+ const results = await cli.verifyDoH(domain, opts.type);
231
+ const headers = ["RESOLVER", "STATUS", "LATENCY", "RESOLVED TARGETS"];
232
+ const rows = results.map((r) => [
233
+ r.resolver,
234
+ r.status === "RESOLVED" ? `${colors.green}RESOLVED${colors.reset}` : `${colors.red}${r.status}${colors.reset}`,
235
+ r.latency,
236
+ r.targets,
237
+ ]);
238
+ console.log(`\n${formatTable(headers, rows)}\n`);
239
+ }
240
+ catch (err) {
241
+ logError(err instanceof Error ? err.message : String(err));
242
+ process.exitCode = 1;
243
+ }
244
+ });
245
+ return program;
246
+ }
247
+ export async function main() {
248
+ const program = createProgram();
249
+ await program.parseAsync(process.argv);
250
+ }
package/dist/ui.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ export declare const colors: {
2
+ reset: string;
3
+ bold: string;
4
+ dim: string;
5
+ green: string;
6
+ yellow: string;
7
+ blue: string;
8
+ magenta: string;
9
+ cyan: string;
10
+ red: string;
11
+ white: string;
12
+ };
13
+ export declare function logSuccess(msg: string): void;
14
+ export declare function logInfo(msg: string): void;
15
+ export declare function logWarn(msg: string): void;
16
+ export declare function logError(msg: string): void;
17
+ export declare function formatTable(headers: string[], rows: string[][]): string;
package/dist/ui.js ADDED
@@ -0,0 +1,38 @@
1
+ export const colors = {
2
+ reset: "\x1b[0m",
3
+ bold: "\x1b[1m",
4
+ dim: "\x1b[2m",
5
+ green: "\x1b[32m",
6
+ yellow: "\x1b[33m",
7
+ blue: "\x1b[34m",
8
+ magenta: "\x1b[35m",
9
+ cyan: "\x1b[36m",
10
+ red: "\x1b[31m",
11
+ white: "\x1b[37m",
12
+ };
13
+ export function logSuccess(msg) {
14
+ console.log(`${colors.green}✔${colors.reset} ${msg}`);
15
+ }
16
+ export function logInfo(msg) {
17
+ console.log(`${colors.cyan}ℹ${colors.reset} ${msg}`);
18
+ }
19
+ export function logWarn(msg) {
20
+ console.log(`${colors.yellow}⚠${colors.reset} ${msg}`);
21
+ }
22
+ export function logError(msg) {
23
+ console.error(`${colors.red}✖${colors.reset} ${msg}`);
24
+ }
25
+ export function formatTable(headers, rows) {
26
+ const colWidths = headers.map((h, i) => {
27
+ const maxRowLen = rows.reduce((max, row) => Math.max(max, (row[i] || "").length), 0);
28
+ return Math.max(h.length, maxRowLen);
29
+ });
30
+ const headerLine = headers.map((h, i) => h.padEnd(colWidths[i])).join(" ");
31
+ const separator = colWidths.map((w) => "─".repeat(w)).join(" ");
32
+ const rowLines = rows.map((row) => row.map((cell, i) => (cell || "").padEnd(colWidths[i])).join(" "));
33
+ return [
34
+ `${colors.bold}${headerLine}${colors.reset}`,
35
+ `${colors.dim}${separator}${colors.reset}`,
36
+ ...rowLines,
37
+ ].join("\n");
38
+ }
package/package.json CHANGED
@@ -1,6 +1,42 @@
1
- {
2
- "name": "lovieme",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
1
+ {
2
+ "name": "lovieme",
3
+ "version": "0.1.0",
4
+ "description": "Official CLI for LovieMe — claim free *.lovie.me subdomains, configure Anycast DNS, and wire up cloud presets in seconds.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "bin": {
9
+ "lovieme": "bin/lovieme.js",
10
+ "lovie": "bin/lovieme.js"
11
+ },
12
+ "files": [
13
+ "dist",
14
+ "bin",
15
+ "README.md"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsc",
19
+ "start": "node dist/index.js",
20
+ "test": "vitest run"
21
+ },
22
+ "keywords": [
23
+ "lovieme",
24
+ "cli",
25
+ "subdomain",
26
+ "dns",
27
+ "developer-tools",
28
+ "anycast",
29
+ "vercel",
30
+ "github-pages"
31
+ ],
32
+ "author": "Sagar Kewat (Sagarithm)",
33
+ "license": "MIT",
34
+ "dependencies": {
35
+ "commander": "^13.1.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^20.17.0",
39
+ "typescript": "^5.8.0",
40
+ "vitest": "^4.1.7"
41
+ }
42
+ }