< dep.smol.pub / posts

Post to smolpub from cli with AI-generated titles

You'll need a free API key from OpenAI, and some command-line savvy in order to use this.

You can get an API key here:

https://beta.openai.com/account/api-keys

This also assumes you've already set up ./smolpub.sh (look at the smolpub manual if not).

https://smol.pub/manual

Create a directory and put it in your path, maybe `~/.smolpost`

mkdir ~/.smolpost

Then set up a new npm project:

npm init -y

Now update your package JSON to match this:

{
  "name": "smolpub",
  "version": "1.0.0",
  "description": "",
  "main": "blogpost.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "dependencies": {
    "body-parser": "^1.20.1",
    "cors": "^2.8.5",
    "express": "^4.18.2",
    "node-fetch": "^2.6.6",
    "openai": "^3.1.0"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

Go ahead and install dependencies:

npm install

Now create your `blogpost.js` executable:

touch blogpost.js
chmod +x blogpost.js

Now populate it:

#!/usr/bin/env node

const express = require("express");
const bodyParser = require("body-parser");
const { Configuration, OpenAIApi } = require("openai");
const cors = require("cors");
const fetch = require("node-fetch");
const fs = require("fs");
const path = require("path");
const child_process = require("child_process");
const os = require("os");

const configuration = new Configuration({
  apiKey: "<YOUR API KEY>",
});

const app = express();
app.use(bodyParser.json());
app.use(cors());

const openai = new OpenAIApi(configuration);

app.listen(3001, () => {
  console.log("Server started on port 3001");
});

// Assign arguments to variables for better readability
const slug = process.argv[2];
let body = process.argv[3];

// Check if 'body' is actually a path to a file
if (fs.existsSync(body)) {
  // If it is, read the content of the file and use it as the body
  body = fs.readFileSync(body, "utf-8");
}

app.post("/gpt3", async (req, res) => {
  await openai
    .createCompletion({
      model: "text-davinci-003",
      prompt: `Please summarize the following into a short title: ${body}`,
      max_tokens: 100,
      temperature: 0.5,
    })
    .then((response) => {
      res.json({
        text: response.data.choices[0].text,
      });
    })
    .catch((err) => {
      console.log(err);
      res.json({
        error: err,
      });
    });
});

async function getSlug() {
  try {
    console.log("Incoming:", body);
    const res = await fetch("http://localhost:3001/gpt3", {
      method: "POST",
      body: JSON.stringify({ body }),
      headers: { "Content-Type": "application/json" },
    });
    const data = await res.json();

    // Check if two arguments are provided
    if (process.argv.length !== 4) {
      console.error("Usage: blogpost.js <slug> <body>");
      process.exit(1);
    }

    // Determine script directory
    const scriptsDir = path.join(os.homedir(), ".scripts/smolpub");

    // Change to script directory
    process.chdir(scriptsDir);

    // trim any whitespace and linebreaks of 'data'
    try {
      data.text = data.text.trim();

      // remove any quotes from 'data'
      data.text = data.text.replace(/"/g, "");

      // Create a new file with the provided slug and write the body into the file
      fs.writeFileSync(slug, `# ${data.text}\n\n${body}\n`);

      // Publish the post and remove the file
      try {
        child_process.execSync(`smolpub.sh ${slug}`);
        fs.unlinkSync(slug);

        // stop the server
        process.exit(0);
      } catch (error) {
        console.error(`Error: ${error}`);
        process.exit(1);
      }
    } catch (error) {
      console.error("Error:", error);
    }
  } catch (error) {
    console.error("Error: ", error);
  }
}

getSlug();

Go ahead and use it like this:

./blogpost.js gpt-post "This is the body of my blog post."

Or pass a file:

./blogpost.js gpt-post some-file.txt