social-light 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,274 @@
1
+ import fs from "fs-extra";
2
+ import chalk from "chalk";
3
+ import ora from "ora";
4
+ import inquirer from "inquirer";
5
+ import path from "path";
6
+ import os from "os";
7
+
8
+ import { getConfig, configExists } from "../utils/config.mjs";
9
+ import {
10
+ createPost as dbCreatePost,
11
+ logAction,
12
+ initializeDb,
13
+ } from "../utils/db.mjs";
14
+ import {
15
+ generateTitle,
16
+ suggestPublishDate,
17
+ enhanceContent,
18
+ } from "../utils/ai.mjs";
19
+
20
+ /**
21
+ * Create a new social media post
22
+ * @param {Object} argv - Command arguments
23
+ * @example
24
+ * await createPost({ file: 'my-post.txt' });
25
+ */
26
+ export const createPost = async (argv) => {
27
+ const config = getConfig();
28
+ let content = "";
29
+
30
+ try {
31
+ // Check if initialized and initialize if needed
32
+ if (!configExists()) {
33
+ console.log(chalk.yellow("Social Light is not initialized yet."));
34
+ console.log("Initializing with default settings...");
35
+
36
+ // Create config directory
37
+ const configDir = path.join(os.homedir(), ".social-light");
38
+ fs.ensureDirSync(configDir);
39
+
40
+ // Initialize database
41
+ const dbInitialized = initializeDb();
42
+ if (!dbInitialized) {
43
+ throw new Error("Failed to initialize database");
44
+ }
45
+ }
46
+ // File-based creation
47
+ if (argv.file) {
48
+ const spinner = ora(`Reading file ${argv.file}...`).start();
49
+
50
+ try {
51
+ content = await fs.readFile(argv.file, "utf8");
52
+ spinner.succeed(`File ${argv.file} loaded successfully!`);
53
+ } catch (error) {
54
+ spinner.fail(`Failed to read file: ${error.message}`);
55
+ process.exit(1);
56
+ }
57
+ }
58
+ // Interactive creation
59
+ else {
60
+ // Function to handle input with 3 empty lines to end
61
+ const getContentInput = async (prompt) => {
62
+ console.log(`${prompt} (Press Enter 3 times in a row when done)`);
63
+ let lines = [];
64
+ let emptyLineCount = 0;
65
+
66
+ // Set up recursive prompt for input
67
+ const promptLine = async () => {
68
+ const { input } = await inquirer.prompt([
69
+ {
70
+ type: "input",
71
+ name: "input",
72
+ message: ">",
73
+ },
74
+ ]);
75
+
76
+ // Check for empty line
77
+ if (input.trim() === "") {
78
+ emptyLineCount++;
79
+
80
+ // If we have 3 consecutive empty lines, we're done
81
+ if (emptyLineCount >= 3) {
82
+ // Remove the last empty lines (if any) from the result
83
+ while (
84
+ lines.length > 0 &&
85
+ lines[lines.length - 1].trim() === ""
86
+ ) {
87
+ lines.pop();
88
+ }
89
+ console.log(chalk.green("\n✓ Content input complete"));
90
+ return lines.join("\n");
91
+ }
92
+ } else {
93
+ // Reset empty line counter when non-empty input is received
94
+ emptyLineCount = 0;
95
+ }
96
+
97
+ lines.push(input);
98
+ return promptLine();
99
+ };
100
+
101
+ return promptLine();
102
+ };
103
+
104
+ // Get post content
105
+ content = await getContentInput("Enter your post content:");
106
+ }
107
+
108
+ // Generate title with AI or prompt for manual entry
109
+ let spinner = ora("Generating title suggestion...").start();
110
+ let title = "";
111
+
112
+ if (config.aiEnabled) {
113
+ title = await generateTitle(content);
114
+ spinner.succeed("Title suggestion generated");
115
+ } else {
116
+ spinner.info("AI is disabled, skipping title generation");
117
+ }
118
+
119
+ // Allow manual title override
120
+ const { titleInput } = await inquirer.prompt([
121
+ {
122
+ type: "input",
123
+ name: "titleInput",
124
+ message: "Enter post title (or press Enter to use suggestion):",
125
+ default: title,
126
+ },
127
+ ]);
128
+
129
+ title = titleInput;
130
+
131
+ // Generate publish date with AI or prompt for manual entry
132
+ spinner = ora("Suggesting publish date...").start();
133
+ let publishDate = "";
134
+
135
+ if (config.aiEnabled) {
136
+ publishDate = await suggestPublishDate();
137
+ spinner.succeed(`Suggested publish date: ${publishDate}`);
138
+ } else {
139
+ spinner.info("AI is disabled, skipping date suggestion");
140
+ }
141
+
142
+ // Allow manual date override
143
+ const { dateInput } = await inquirer.prompt([
144
+ {
145
+ type: "input",
146
+ name: "dateInput",
147
+ message:
148
+ "Enter publish date (YYYY-MM-DD) or press Enter to use suggestion:",
149
+ default: publishDate,
150
+ validate: (input) => {
151
+ if (!input) return true;
152
+ return /^\d{4}-\d{2}-\d{2}$/.test(input)
153
+ ? true
154
+ : "Please use YYYY-MM-DD format";
155
+ },
156
+ },
157
+ ]);
158
+
159
+ publishDate = dateInput;
160
+
161
+ // Select platforms
162
+ const { selectedPlatforms } = await inquirer.prompt([
163
+ {
164
+ type: "checkbox",
165
+ name: "selectedPlatforms",
166
+ message: "Select platforms to publish to:",
167
+ choices: [
168
+ {
169
+ name: "Bluesky",
170
+ value: "Bluesky",
171
+ checked: config.defaultPlatforms.includes("Bluesky"),
172
+ },
173
+ ],
174
+ },
175
+ ]);
176
+
177
+ const platforms = selectedPlatforms.join(",");
178
+
179
+ // Option to enhance content for the primary platform
180
+ if (config.aiEnabled && selectedPlatforms.length > 0) {
181
+ const primaryPlatform = selectedPlatforms[0];
182
+
183
+ const { enhance } = await inquirer.prompt([
184
+ {
185
+ type: "confirm",
186
+ name: "enhance",
187
+ message: `Would you like AI to enhance your content for ${primaryPlatform}?`,
188
+ default: false,
189
+ },
190
+ ]);
191
+
192
+ if (enhance) {
193
+ spinner = ora(`Enhancing content for ${primaryPlatform}...`).start();
194
+ const enhancedContent = await enhanceContent(content, primaryPlatform);
195
+
196
+ if (enhancedContent !== content) {
197
+ spinner.succeed("Content enhanced");
198
+
199
+ // Show the original and enhanced versions
200
+ console.log("\n", chalk.cyan("Original:"), chalk.gray(content));
201
+ console.log(
202
+ "\n",
203
+ chalk.cyan("Enhanced:"),
204
+ chalk.white(enhancedContent)
205
+ );
206
+
207
+ const { useEnhanced } = await inquirer.prompt([
208
+ {
209
+ type: "confirm",
210
+ name: "useEnhanced",
211
+ message: "Use the enhanced version?",
212
+ default: true,
213
+ },
214
+ ]);
215
+
216
+ if (useEnhanced) {
217
+ content = enhancedContent;
218
+ }
219
+ } else {
220
+ spinner.info("No significant enhancements suggested");
221
+ }
222
+ }
223
+ }
224
+
225
+ // Create post in database
226
+ spinner = ora("Saving post...").start();
227
+
228
+ const postId = dbCreatePost({
229
+ title,
230
+ content,
231
+ platforms,
232
+ publish_date: publishDate,
233
+ });
234
+
235
+ // Log the action
236
+ logAction("post_created", {
237
+ postId,
238
+ title,
239
+ platforms: selectedPlatforms,
240
+ publishDate,
241
+ });
242
+
243
+ spinner.succeed(`Post created successfully with ID: ${postId}`);
244
+
245
+ // Summary
246
+ console.log("\n", chalk.cyan("Post Summary:"));
247
+ console.log(` ${chalk.gray("•")} ${chalk.bold("Title:")} ${title}`);
248
+ console.log(
249
+ ` ${chalk.gray("•")} ${chalk.bold("Platforms:")} ${platforms || "None"}`
250
+ );
251
+ console.log(
252
+ ` ${chalk.gray("•")} ${chalk.bold("Publish Date:")} ${
253
+ publishDate || "Not scheduled"
254
+ }`
255
+ );
256
+ console.log(
257
+ ` ${chalk.gray("•")} ${chalk.bold("Content:")} ${content.substring(
258
+ 0,
259
+ 50
260
+ )}${content.length > 50 ? "..." : ""}`
261
+ );
262
+
263
+ console.log("\n", chalk.green("✓"), "Post created successfully!");
264
+ console.log(
265
+ chalk.gray(" Run"),
266
+ chalk.cyan("social-light unpublished"),
267
+ chalk.gray("to see your scheduled posts")
268
+ );
269
+ } catch (error) {
270
+ console.error(chalk.red("Error creating post:"), error.message);
271
+ console.error(error);
272
+ process.exit(1);
273
+ }
274
+ };
@@ -0,0 +1,198 @@
1
+ import chalk from "chalk";
2
+ import inquirer from "inquirer";
3
+ import { getPosts, getPostById, updatePost, logAction } from "../utils/db.mjs";
4
+ import { getConfig } from "../utils/config.mjs";
5
+
6
+ /**
7
+ * Edit a draft post by index
8
+ * @param {Object} argv - Command arguments
9
+ */
10
+ export const editPost = async (argv) => {
11
+ try {
12
+ // Get unpublished posts
13
+ const posts = getPosts({ published: false });
14
+
15
+ if (posts.length === 0) {
16
+ console.log(chalk.yellow("No unpublished posts found to edit."));
17
+ console.log(
18
+ chalk.gray("Run"),
19
+ chalk.cyan("social-light create"),
20
+ chalk.gray("to create a new post.")
21
+ );
22
+ return;
23
+ }
24
+
25
+ // Determine which post to edit
26
+ let postIndex = argv.index;
27
+
28
+ // If no index provided, prompt user to select a post
29
+ if (postIndex === undefined) {
30
+ const { selectedIndex } = await inquirer.prompt([
31
+ {
32
+ type: "list",
33
+ name: "selectedIndex",
34
+ message: "Select a post to edit:",
35
+ choices: posts.map((post, index) => ({
36
+ name: `[${index + 1}] ${
37
+ post.title || "No title"
38
+ } - ${post.content.substring(0, 40)}...`,
39
+ value: index + 1,
40
+ })),
41
+ },
42
+ ]);
43
+
44
+ postIndex = selectedIndex;
45
+ }
46
+
47
+ // Validate post index
48
+ if (postIndex < 1 || postIndex > posts.length) {
49
+ console.error(
50
+ chalk.red(`Invalid post index. Must be between 1 and ${posts.length}.`)
51
+ );
52
+ process.exit(1);
53
+ }
54
+
55
+ // Get post by ID
56
+ const post = getPostById(posts[postIndex - 1].id);
57
+
58
+ if (!post) {
59
+ console.error(chalk.red("Post not found."));
60
+ process.exit(1);
61
+ }
62
+
63
+ // Function to handle multiline input
64
+ const getMultilineInput = async (prompt, defaultText) => {
65
+ console.log(`${prompt} (Type 'EOF' on a new line when done)`);
66
+ console.log(`Current content: ${defaultText}`);
67
+ console.log("Enter new content:");
68
+
69
+ let lines = [];
70
+
71
+ // Set up recursive prompt for multiline input
72
+ const promptLine = async () => {
73
+ const { input } = await inquirer.prompt([
74
+ {
75
+ type: "input",
76
+ name: "input",
77
+ message: ">",
78
+ },
79
+ ]);
80
+
81
+ if (input === "EOF") {
82
+ return lines.length > 0 ? lines.join("\n") : defaultText;
83
+ }
84
+
85
+ lines.push(input);
86
+ return promptLine();
87
+ };
88
+
89
+ return promptLine();
90
+ };
91
+
92
+ // Ask if user wants to edit with multiline input
93
+ const { useMultiline } = await inquirer.prompt([
94
+ {
95
+ type: "confirm",
96
+ name: "useMultiline",
97
+ message: "Would you like to edit content with multiline input?",
98
+ default: true,
99
+ },
100
+ ]);
101
+
102
+ // Get title
103
+ const { title } = await inquirer.prompt([
104
+ {
105
+ type: "input",
106
+ name: "title",
107
+ message: "Edit title:",
108
+ default: post.title || "",
109
+ },
110
+ ]);
111
+
112
+ // Get content based on user preference
113
+ let content;
114
+ if (useMultiline) {
115
+ content = await getMultilineInput("Edit content:", post.content);
116
+ } else {
117
+ const result = await inquirer.prompt([
118
+ {
119
+ type: "input",
120
+ name: "content",
121
+ message: "Edit content:",
122
+ default: post.content,
123
+ },
124
+ ]);
125
+ content = result.content;
126
+ }
127
+
128
+ // Get publish date and platforms
129
+ const { publishDate, platforms } = await inquirer.prompt([
130
+ {
131
+ type: "input",
132
+ name: "publishDate",
133
+ message: "Edit publish date (YYYY-MM-DD):",
134
+ default: post.publish_date || "",
135
+ validate: (input) => {
136
+ if (!input) return true;
137
+ return /^\d{4}-\d{2}-\d{2}$/.test(input)
138
+ ? true
139
+ : "Please use YYYY-MM-DD format";
140
+ },
141
+ },
142
+ {
143
+ type: "checkbox",
144
+ name: "platforms",
145
+ message: "Select platforms to publish to:",
146
+ choices: [
147
+ // { name: 'Twitter', value: 'Twitter', checked: post.platforms && post.platforms.includes('Twitter') },
148
+ {
149
+ name: "Bluesky",
150
+ value: "Bluesky",
151
+ checked: post.platforms && post.platforms.includes("Bluesky"),
152
+ },
153
+ // { name: 'TikTok', value: 'TikTok', checked: post.platforms && post.platforms.includes('TikTok') },
154
+ // { name: 'Instagram', value: 'Instagram', checked: post.platforms && post.platforms.includes('Instagram') },
155
+ // { name: 'LinkedIn', value: 'LinkedIn', checked: post.platforms && post.platforms.includes('LinkedIn') }
156
+ ],
157
+ },
158
+ ]);
159
+
160
+ const config = getConfig();
161
+
162
+ // Update post in database
163
+ const updatedPost = {
164
+ title,
165
+ content,
166
+ platforms: platforms.join(","),
167
+ publish_date: publishDate,
168
+ };
169
+
170
+ const success = updatePost(post.id, updatedPost);
171
+
172
+ if (success) {
173
+ // Log the action
174
+ logAction("post_edited", {
175
+ postId: post.id,
176
+ title: title,
177
+ });
178
+
179
+ console.log(chalk.green("\n✓ Post updated successfully!"));
180
+ console.log(
181
+ chalk.gray("Run"),
182
+ chalk.cyan("social-light unpublished"),
183
+ chalk.gray("to see your updated post.")
184
+ );
185
+ console.log(
186
+ chalk.gray("Run"),
187
+ chalk.cyan("social-light publish"),
188
+ chalk.gray("to publish eligible posts.")
189
+ );
190
+ } else {
191
+ console.error(chalk.red("Failed to update post."));
192
+ }
193
+ } catch (error) {
194
+ console.error(chalk.red("Error editing post:"), error.message);
195
+ console.error(error);
196
+ process.exit(1);
197
+ }
198
+ };