memes-business 0.0.1

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,236 @@
1
+ import { Command } from "commander";
2
+ import chalk from "chalk";
3
+ import ora from "ora";
4
+ import { writeFile, stat } from "node:fs/promises";
5
+ import { join } from "node:path";
6
+ import { confirm } from "@inquirer/prompts";
7
+ import { apiRequest } from "../lib/api.js";
8
+ import { getApiKey, getApiUrl } from "../lib/config.js";
9
+ /**
10
+ * Check if a path is a directory
11
+ */
12
+ async function isDirectory(path) {
13
+ try {
14
+ const stats = await stat(path);
15
+ return stats.isDirectory();
16
+ }
17
+ catch {
18
+ return false;
19
+ }
20
+ }
21
+ /**
22
+ * Download an image from URL
23
+ */
24
+ async function downloadImage(imageUrl, savePath, memeId) {
25
+ const baseUrl = await getApiUrl();
26
+ const apiKey = await getApiKey();
27
+ const fullUrl = imageUrl.startsWith("http")
28
+ ? imageUrl
29
+ : `${baseUrl}${imageUrl}`;
30
+ const response = await fetch(fullUrl, {
31
+ headers: apiKey ? { "x-api-key": apiKey } : {},
32
+ });
33
+ if (!response.ok) {
34
+ throw new Error(`Failed to download: ${response.statusText}`);
35
+ }
36
+ // Resolve the save path (handle directory case)
37
+ let resolvedPath = savePath;
38
+ if (await isDirectory(savePath)) {
39
+ const filename = memeId ? `${memeId}.png` : `meme-${Date.now()}.png`;
40
+ resolvedPath = join(savePath, filename);
41
+ }
42
+ const buffer = await response.arrayBuffer();
43
+ await writeFile(resolvedPath, Buffer.from(buffer));
44
+ return resolvedPath;
45
+ }
46
+ export function createMemesCommand() {
47
+ const memes = new Command("library")
48
+ .description("Manage your saved memes");
49
+ // List subcommand
50
+ memes
51
+ .command("list")
52
+ .description("List your saved memes")
53
+ .option("-p, --page <number>", "page number", "1")
54
+ .option("-l, --limit <number>", "results per page", "10")
55
+ .option("--json", "output as JSON for scripting")
56
+ .action(async (options) => {
57
+ const page = parseInt(options.page, 10);
58
+ const limit = parseInt(options.limit, 10);
59
+ if (isNaN(page) || page < 1) {
60
+ console.error(chalk.red("Error: --page must be a positive number"));
61
+ process.exit(1);
62
+ }
63
+ if (isNaN(limit) || limit < 1) {
64
+ console.error(chalk.red("Error: --limit must be a positive number"));
65
+ process.exit(1);
66
+ }
67
+ const spinner = options.json ? null : ora("Fetching memes...").start();
68
+ const result = await apiRequest(`/api/memes?page=${page}&perPage=${limit}`);
69
+ if (result.error) {
70
+ if (spinner)
71
+ spinner.fail(chalk.red(result.error.message));
72
+ if (options.json)
73
+ console.log(JSON.stringify({ error: result.error }, null, 2));
74
+ process.exit(1);
75
+ }
76
+ const response = result.data;
77
+ if (!response?.data) {
78
+ if (spinner)
79
+ spinner.fail(chalk.red("Invalid response from server"));
80
+ if (options.json)
81
+ console.log(JSON.stringify({ error: { message: "Invalid response" } }, null, 2));
82
+ process.exit(1);
83
+ }
84
+ if (spinner)
85
+ spinner.stop();
86
+ if (options.json) {
87
+ console.log(JSON.stringify(response, null, 2));
88
+ return;
89
+ }
90
+ const memesList = response.data;
91
+ const { pagination } = response.meta;
92
+ if (memesList.length === 0) {
93
+ console.log(chalk.yellow("No memes found."));
94
+ return;
95
+ }
96
+ console.log(chalk.bold(`Your Memes (Page ${pagination.currentPage}/${pagination.totalPages})`));
97
+ console.log(chalk.dim(`Showing ${memesList.length} of ${pagination.totalItems} total`));
98
+ console.log();
99
+ memesList.forEach((meme, index) => {
100
+ const num = chalk.dim(`${((pagination.currentPage - 1) * pagination.perPage + index + 1).toString().padStart(2)}.`);
101
+ const title = chalk.bold(meme.title);
102
+ const vis = meme.isPublic ? chalk.green("public") : chalk.dim("private");
103
+ const date = new Date(meme.createdAt).toLocaleDateString();
104
+ console.log(`${num} ${title} ${vis}`);
105
+ console.log(chalk.dim(` ID: ${meme.id}`));
106
+ if (meme.templateName)
107
+ console.log(chalk.dim(` Template: ${meme.templateName}`));
108
+ console.log(chalk.dim(` Created: ${date}`));
109
+ console.log();
110
+ });
111
+ if (pagination.hasNext) {
112
+ console.log(chalk.dim(`Next page: memes library list --page ${pagination.currentPage + 1}`));
113
+ }
114
+ });
115
+ // Show subcommand
116
+ memes
117
+ .command("show")
118
+ .description("Show details of a specific meme")
119
+ .argument("<id>", "meme ID")
120
+ .option("--json", "output as JSON for scripting")
121
+ .action(async (id, options) => {
122
+ const spinner = options.json ? null : ora("Fetching meme details...").start();
123
+ const result = await apiRequest(`/api/memes/${id}`);
124
+ if (result.error) {
125
+ if (spinner)
126
+ spinner.fail(chalk.red(result.error.message));
127
+ if (options.json)
128
+ console.log(JSON.stringify({ error: result.error }, null, 2));
129
+ process.exit(1);
130
+ }
131
+ const meme = result.data;
132
+ if (!meme) {
133
+ if (spinner)
134
+ spinner.fail(chalk.red("Meme not found"));
135
+ if (options.json)
136
+ console.log(JSON.stringify({ error: { message: "Meme not found" } }, null, 2));
137
+ process.exit(1);
138
+ }
139
+ if (spinner)
140
+ spinner.stop();
141
+ if (options.json) {
142
+ console.log(JSON.stringify(meme, null, 2));
143
+ return;
144
+ }
145
+ console.log(chalk.bold(meme.title));
146
+ console.log();
147
+ console.log(` ${chalk.dim("ID:")} ${meme.id}`);
148
+ if (meme.description)
149
+ console.log(` ${chalk.dim("Desc:")} ${meme.description}`);
150
+ console.log(` ${chalk.dim("Image:")} ${meme.imageUrl}`);
151
+ if (meme.templateName)
152
+ console.log(` ${chalk.dim("Template:")} ${meme.templateName}`);
153
+ console.log(` ${chalk.dim("Visibility:")} ${meme.isPublic ? chalk.green("public") : chalk.dim("private")}`);
154
+ if (meme.width && meme.height)
155
+ console.log(` ${chalk.dim("Size:")} ${meme.width}x${meme.height}`);
156
+ if (meme.fileSize)
157
+ console.log(` ${chalk.dim("File:")} ${(meme.fileSize / 1024).toFixed(1)} KB`);
158
+ console.log(` ${chalk.dim("Created:")} ${new Date(meme.createdAt).toLocaleString()}`);
159
+ console.log(` ${chalk.dim("Updated:")} ${new Date(meme.updatedAt).toLocaleString()}`);
160
+ });
161
+ // Download subcommand
162
+ memes
163
+ .command("download")
164
+ .description("Download a meme image to disk")
165
+ .argument("<id>", "meme ID")
166
+ .argument("[path]", "output file path", "./meme.png")
167
+ .action(async (id, savePath) => {
168
+ const spinner = ora("Fetching meme info...").start();
169
+ const result = await apiRequest(`/api/memes/${id}`);
170
+ if (result.error) {
171
+ spinner.fail(chalk.red(result.error.message));
172
+ process.exit(1);
173
+ }
174
+ const meme = result.data;
175
+ if (!meme) {
176
+ spinner.fail(chalk.red("Meme not found"));
177
+ process.exit(1);
178
+ }
179
+ spinner.text = "Downloading image...";
180
+ try {
181
+ const resolvedPath = await downloadImage(meme.imageUrl, savePath, meme.id);
182
+ spinner.succeed(chalk.green(`Saved to ${resolvedPath}`));
183
+ console.log(chalk.dim(` Title: ${meme.title}`));
184
+ }
185
+ catch (err) {
186
+ const msg = err instanceof Error ? err.message : "Unknown error";
187
+ spinner.fail(chalk.red(`Failed to download: ${msg}`));
188
+ process.exit(1);
189
+ }
190
+ });
191
+ // Delete subcommand
192
+ memes
193
+ .command("delete")
194
+ .description("Delete a meme")
195
+ .argument("<id>", "meme ID")
196
+ .option("-y, --yes", "skip confirmation prompt")
197
+ .action(async (id, options) => {
198
+ // First fetch the meme to show what we're deleting
199
+ const infoSpinner = ora("Fetching meme info...").start();
200
+ const infoResult = await apiRequest(`/api/memes/${id}`);
201
+ if (infoResult.error) {
202
+ infoSpinner.fail(chalk.red(infoResult.error.message));
203
+ process.exit(1);
204
+ }
205
+ const meme = infoResult.data;
206
+ if (!meme) {
207
+ infoSpinner.fail(chalk.red("Meme not found"));
208
+ process.exit(1);
209
+ }
210
+ infoSpinner.stop();
211
+ console.log(chalk.yellow(`About to delete: ${chalk.bold(meme.title)}`));
212
+ console.log(chalk.dim(` ID: ${meme.id}`));
213
+ console.log();
214
+ // Confirm deletion unless --yes flag provided
215
+ if (!options.yes) {
216
+ const confirmed = await confirm({
217
+ message: "Are you sure you want to delete this meme?",
218
+ default: false,
219
+ });
220
+ if (!confirmed) {
221
+ console.log(chalk.dim("Cancelled."));
222
+ return;
223
+ }
224
+ }
225
+ const deleteSpinner = ora("Deleting meme...").start();
226
+ const deleteResult = await apiRequest(`/api/memes/${id}`, {
227
+ method: "DELETE",
228
+ });
229
+ if (deleteResult.error) {
230
+ deleteSpinner.fail(chalk.red(deleteResult.error.message));
231
+ process.exit(1);
232
+ }
233
+ deleteSpinner.succeed(chalk.green("Meme deleted successfully!"));
234
+ });
235
+ return memes;
236
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function createSearchCommand(): Command;
@@ -0,0 +1,68 @@
1
+ import { Command } from "commander";
2
+ import chalk from "chalk";
3
+ import ora from "ora";
4
+ import { apiRequest } from "../lib/api.js";
5
+ export function createSearchCommand() {
6
+ const search = new Command("search")
7
+ .description("Search for meme templates")
8
+ .argument("<query>", "search query")
9
+ .option("-l, --limit <number>", "maximum number of results", "10")
10
+ .option("--json", "output as JSON for scripting")
11
+ .action(async (query, options) => {
12
+ const limit = parseInt(options.limit, 10);
13
+ if (isNaN(limit) || limit < 1) {
14
+ console.error(chalk.red("Error: --limit must be a positive number"));
15
+ process.exit(1);
16
+ }
17
+ const spinner = options.json ? null : ora("Searching templates...").start();
18
+ const result = await apiRequest(`/api/templates/search?q=${encodeURIComponent(query)}&limit=${limit}`);
19
+ if (result.error) {
20
+ if (spinner)
21
+ spinner.fail(chalk.red(result.error.message));
22
+ if (options.json) {
23
+ console.log(JSON.stringify({ error: result.error }, null, 2));
24
+ }
25
+ process.exit(1);
26
+ }
27
+ const response = result.data;
28
+ if (!response || !response.data) {
29
+ if (spinner)
30
+ spinner.fail(chalk.red("Invalid response from server"));
31
+ if (options.json) {
32
+ console.log(JSON.stringify({ error: { message: "Invalid response" } }, null, 2));
33
+ }
34
+ process.exit(1);
35
+ }
36
+ if (spinner)
37
+ spinner.stop();
38
+ // JSON output for scripting
39
+ if (options.json) {
40
+ console.log(JSON.stringify(response, null, 2));
41
+ return;
42
+ }
43
+ // Human-readable output
44
+ const templates = response.data;
45
+ if (templates.length === 0) {
46
+ console.log(chalk.yellow(`No templates found for "${query}"`));
47
+ return;
48
+ }
49
+ console.log(chalk.bold(`Found ${response.meta.totalResults} template${response.meta.totalResults !== 1 ? "s" : ""} for "${query}"`));
50
+ console.log();
51
+ templates.forEach((template, index) => {
52
+ const num = chalk.dim(`${(index + 1).toString().padStart(2)}.`);
53
+ const name = chalk.bold(template.name);
54
+ const score = chalk.cyan(`${(template.similarity * 100).toFixed(1)}%`);
55
+ const boxes = chalk.dim(`(${template.numTextBoxes} text box${template.numTextBoxes !== 1 ? "es" : ""})`);
56
+ console.log(`${num} ${name} ${boxes} ${score}`);
57
+ if (template.description) {
58
+ const desc = template.description.length > 80
59
+ ? template.description.slice(0, 77) + "..."
60
+ : template.description;
61
+ console.log(chalk.dim(` ${desc}`));
62
+ }
63
+ console.log(chalk.dim(` ID: ${template.id}`));
64
+ console.log();
65
+ });
66
+ });
67
+ return search;
68
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare function createStatusCommand(): Command;
3
+ export declare function createUpgradeCommand(): Command;
@@ -0,0 +1,89 @@
1
+ import { Command } from "commander";
2
+ import chalk from "chalk";
3
+ import ora from "ora";
4
+ import open from "open";
5
+ import { apiRequest } from "../lib/api.js";
6
+ import { getApiKey, getApiUrl } from "../lib/config.js";
7
+ export function createStatusCommand() {
8
+ const status = new Command("status")
9
+ .description("Show subscription status and usage")
10
+ .option("--json", "Output as JSON")
11
+ .action(async (options) => {
12
+ const apiKey = await getApiKey();
13
+ if (!apiKey) {
14
+ if (options.json) {
15
+ console.log(JSON.stringify({ error: "Not logged in" }));
16
+ }
17
+ else {
18
+ console.log(chalk.yellow("Not logged in."));
19
+ console.log(chalk.dim("Run 'memes login --api-key <key>' to authenticate."));
20
+ }
21
+ process.exit(1);
22
+ }
23
+ const spinner = options.json ? null : ora("Fetching usage status...").start();
24
+ const result = await apiRequest("/api/generations/remaining");
25
+ if (result.error) {
26
+ if (spinner)
27
+ spinner.fail(chalk.red(result.error.message));
28
+ if (options.json) {
29
+ console.log(JSON.stringify({ error: result.error }));
30
+ }
31
+ process.exit(1);
32
+ }
33
+ if (spinner)
34
+ spinner.stop();
35
+ const data = result.data?.data;
36
+ if (!data) {
37
+ if (spinner)
38
+ spinner.fail(chalk.red("Invalid response from server"));
39
+ if (options.json) {
40
+ console.log(JSON.stringify({ error: { message: "Invalid response" } }));
41
+ }
42
+ process.exit(1);
43
+ }
44
+ if (options.json) {
45
+ console.log(JSON.stringify(data, null, 2));
46
+ return;
47
+ }
48
+ const remaining = data.generationLimit - data.currentGenerations;
49
+ const planType = data.generationLimit > 10 ? "Pro" : "Free";
50
+ console.log(chalk.bold("Subscription Status"));
51
+ console.log();
52
+ console.log(` ${chalk.dim("Plan:")} ${planType === "Pro" ? chalk.green(planType) : chalk.yellow(planType)}`);
53
+ console.log(` ${chalk.dim("Generations:")} ${data.currentGenerations} / ${data.generationLimit} used`);
54
+ console.log(` ${chalk.dim("Remaining:")} ${remaining > 0 ? chalk.green(remaining) : chalk.red(remaining)}`);
55
+ console.log();
56
+ if (remaining <= 0) {
57
+ console.log(chalk.yellow("You've used all your generations for this period."));
58
+ console.log(chalk.dim("Run 'memes upgrade' to get more generations."));
59
+ }
60
+ else if (remaining <= 5) {
61
+ console.log(chalk.yellow(`Only ${remaining} generation(s) remaining.`));
62
+ }
63
+ });
64
+ return status;
65
+ }
66
+ export function createUpgradeCommand() {
67
+ const upgrade = new Command("upgrade")
68
+ .description("Open billing page to upgrade or manage subscription")
69
+ .action(async () => {
70
+ const apiKey = await getApiKey();
71
+ if (!apiKey) {
72
+ console.log(chalk.yellow("Not logged in."));
73
+ console.log(chalk.dim("Run 'memes login --api-key <key>' to authenticate."));
74
+ process.exit(1);
75
+ }
76
+ const baseUrl = await getApiUrl();
77
+ const accountUrl = `${baseUrl}/account`;
78
+ console.log(chalk.dim(`Opening ${accountUrl}...`));
79
+ try {
80
+ await open(accountUrl);
81
+ console.log(chalk.green("Opened billing page in your browser."));
82
+ }
83
+ catch {
84
+ console.log(chalk.yellow("Could not open browser automatically."));
85
+ console.log(chalk.dim(`Visit: ${accountUrl}`));
86
+ }
87
+ });
88
+ return upgrade;
89
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { createLoginCommand, createLogoutCommand, createWhoamiCommand, } from "./commands/auth.js";
4
+ import { createFavoriteCommand, createFavoritesCommand } from "./commands/favorites.js";
5
+ import { createGenerateCommand } from "./commands/generate.js";
6
+ import { createSearchCommand } from "./commands/search.js";
7
+ import { createMemesCommand } from "./commands/memes.js";
8
+ import { createStatusCommand, createUpgradeCommand } from "./commands/status.js";
9
+ const program = new Command();
10
+ program
11
+ .name("memes")
12
+ .description("CLI for memes.business")
13
+ .version("0.0.1");
14
+ // Auth commands
15
+ program.addCommand(createLoginCommand());
16
+ program.addCommand(createLogoutCommand());
17
+ program.addCommand(createWhoamiCommand());
18
+ // Search command
19
+ program.addCommand(createSearchCommand());
20
+ // Generate command
21
+ program.addCommand(createGenerateCommand());
22
+ // Favorites commands
23
+ program.addCommand(createFavoritesCommand());
24
+ program.addCommand(createFavoriteCommand());
25
+ // Memes library command
26
+ program.addCommand(createMemesCommand());
27
+ // Status commands
28
+ program.addCommand(createStatusCommand());
29
+ program.addCommand(createUpgradeCommand());
30
+ program.parse();
@@ -0,0 +1,45 @@
1
+ export interface ApiError {
2
+ error: {
3
+ code: string;
4
+ message: string;
5
+ details?: Record<string, unknown>;
6
+ };
7
+ }
8
+ export interface ApiResponse<T> {
9
+ data?: T;
10
+ error?: ApiError["error"];
11
+ }
12
+ export interface User {
13
+ id: string;
14
+ name: string;
15
+ email: string;
16
+ emailVerified: boolean;
17
+ image?: string | null;
18
+ role?: string | null;
19
+ createdAt: string;
20
+ updatedAt: string;
21
+ }
22
+ export interface Session {
23
+ id: string;
24
+ userId: string;
25
+ token: string;
26
+ expiresAt: string;
27
+ createdAt: string;
28
+ updatedAt: string;
29
+ }
30
+ export interface SessionResponse {
31
+ user: User;
32
+ session: Session;
33
+ }
34
+ /**
35
+ * Make an authenticated API request
36
+ */
37
+ export declare function apiRequest<T>(path: string, options?: RequestInit): Promise<ApiResponse<T>>;
38
+ /**
39
+ * Get the current user session
40
+ */
41
+ export declare function getCurrentSession(): Promise<ApiResponse<SessionResponse>>;
42
+ /**
43
+ * Verify if an API key is valid by attempting to get session
44
+ */
45
+ export declare function verifyApiKey(apiKey: string): Promise<ApiResponse<SessionResponse>>;
@@ -0,0 +1,127 @@
1
+ import { getApiKey, getApiUrl } from "./config.js";
2
+ /**
3
+ * Create headers for API requests with authentication
4
+ */
5
+ async function createHeaders() {
6
+ const headers = new Headers({
7
+ "Content-Type": "application/json",
8
+ });
9
+ const apiKey = await getApiKey();
10
+ if (apiKey) {
11
+ // better-auth API key plugin expects the key in x-api-key header
12
+ headers.set("x-api-key", apiKey);
13
+ }
14
+ return headers;
15
+ }
16
+ /**
17
+ * Make an authenticated API request
18
+ */
19
+ export async function apiRequest(path, options = {}) {
20
+ const baseUrl = await getApiUrl();
21
+ const url = `${baseUrl}${path}`;
22
+ const headers = await createHeaders();
23
+ // Merge provided headers with auth headers
24
+ if (options.headers) {
25
+ const providedHeaders = new Headers(options.headers);
26
+ providedHeaders.forEach((value, key) => {
27
+ headers.set(key, value);
28
+ });
29
+ }
30
+ try {
31
+ const response = await fetch(url, {
32
+ ...options,
33
+ headers,
34
+ });
35
+ const contentType = response.headers.get("content-type");
36
+ if (!contentType?.includes("application/json")) {
37
+ if (!response.ok) {
38
+ return {
39
+ error: {
40
+ code: "HTTP_ERROR",
41
+ message: `HTTP ${response.status}: ${response.statusText}`,
42
+ },
43
+ };
44
+ }
45
+ return { data: undefined };
46
+ }
47
+ const data = (await response.json());
48
+ if (!response.ok) {
49
+ if (data.error) {
50
+ return { error: data.error };
51
+ }
52
+ return {
53
+ error: {
54
+ code: "HTTP_ERROR",
55
+ message: `HTTP ${response.status}: ${response.statusText}`,
56
+ },
57
+ };
58
+ }
59
+ return { data };
60
+ }
61
+ catch (err) {
62
+ const message = err instanceof Error ? err.message : "Unknown error";
63
+ return {
64
+ error: {
65
+ code: "NETWORK_ERROR",
66
+ message: `Failed to connect: ${message}`,
67
+ },
68
+ };
69
+ }
70
+ }
71
+ /**
72
+ * Get the current user session
73
+ */
74
+ export async function getCurrentSession() {
75
+ return apiRequest("/api/auth/get-session");
76
+ }
77
+ /**
78
+ * Verify if an API key is valid by attempting to get session
79
+ */
80
+ export async function verifyApiKey(apiKey) {
81
+ const baseUrl = await getApiUrl();
82
+ const url = `${baseUrl}/api/auth/get-session`;
83
+ try {
84
+ const response = await fetch(url, {
85
+ headers: {
86
+ "Content-Type": "application/json",
87
+ "x-api-key": apiKey,
88
+ },
89
+ });
90
+ const contentType = response.headers.get("content-type");
91
+ if (!contentType?.includes("application/json")) {
92
+ if (!response.ok) {
93
+ return {
94
+ error: {
95
+ code: "HTTP_ERROR",
96
+ message: `HTTP ${response.status}: ${response.statusText}`,
97
+ },
98
+ };
99
+ }
100
+ return {
101
+ error: {
102
+ code: "INVALID_API_KEY",
103
+ message: "Invalid API key or API key not found",
104
+ },
105
+ };
106
+ }
107
+ const data = (await response.json());
108
+ if (!response.ok || !data?.user) {
109
+ return {
110
+ error: {
111
+ code: "INVALID_API_KEY",
112
+ message: "Invalid API key or API key not found",
113
+ },
114
+ };
115
+ }
116
+ return { data: data };
117
+ }
118
+ catch (err) {
119
+ const message = err instanceof Error ? err.message : "Unknown error";
120
+ return {
121
+ error: {
122
+ code: "NETWORK_ERROR",
123
+ message: `Failed to connect: ${message}`,
124
+ },
125
+ };
126
+ }
127
+ }
@@ -0,0 +1,36 @@
1
+ export interface Config {
2
+ apiKey?: string;
3
+ apiUrl?: string;
4
+ }
5
+ declare const CONFIG_DIR: string;
6
+ declare const CONFIG_FILE: string;
7
+ /**
8
+ * Load configuration from disk
9
+ */
10
+ export declare function loadConfig(): Promise<Config>;
11
+ /**
12
+ * Save configuration to disk
13
+ * Sets restrictive permissions (0600) since config contains API keys
14
+ */
15
+ export declare function saveConfig(config: Config): Promise<void>;
16
+ /**
17
+ * Get the API key from config
18
+ */
19
+ export declare function getApiKey(): Promise<string | undefined>;
20
+ /**
21
+ * Set the API key in config
22
+ */
23
+ export declare function setApiKey(apiKey: string): Promise<void>;
24
+ /**
25
+ * Clear the API key from config
26
+ */
27
+ export declare function clearApiKey(): Promise<void>;
28
+ /**
29
+ * Get the API URL from config
30
+ */
31
+ export declare function getApiUrl(): Promise<string>;
32
+ /**
33
+ * Check if user is authenticated
34
+ */
35
+ export declare function isAuthenticated(): Promise<boolean>;
36
+ export { CONFIG_DIR, CONFIG_FILE };