# mk-resource-creating-a-nodejs-command-line-tool ## getting the command to work - create a repository on github - initialize with a readme - add a gitignore (nodejs template) - clone that repository and change directories to get into it - `npm init` and make your selections - `code .` - add anything you want to ignore to ``.gitignore` ``` .env* .DS_Store _hidden _tests _secret _output ``` - in the root of the project, create a file called `cli.js` - inside, add the node shebang and a simple log message ``` #!/usr/bin/env node console.log("launching my command line tool") ``` - you COULD run `chmod 755 ./cli.js` and then run the script with just `./cli.js` (or the whole path to the script from wherever you are in terminal)--but it's more elegant to define the command in your package.json file, so... - in your package.json file, define this command by adding ``` "bin": {"mycommand": "cli.js"}, ``` - in terminal (once you've done the previous steps, not before) type `npm link` while in the root of your project (just make sure you gave the command a name that isn't already a shell command or tool that we use--like don't use `mkdir` or `ls` or `ffmpeg` etc) - then you should be able to run the script with `mycommand` (or whatever you typed as the property of the object you defined in the "bin" lines of your package.json) ## getting arguments into the command - while in the root of your project, let's add some packages that people frequently use in command line tools ``` npm i yargs figlet clear dotenv ``` - now let's use these in the command ``` const figlet = require('figlet') const clear = require('clear') const yargs = require('yargs').argv require("dotenv").config({ path: __dirname + `/.env` }) clear() console.log(figlet.textSync("my command")) console.log("launching my command with yargs:") console.log(JSON.stringify(yargs, null, 4)) ``` - hopefully it still works - now let's add some environment variables to a file we'll call `.env` (it should be in the same directory as the `cli.js` file for the fourth line above to work) - put any API keys in this file (and make sure it's grayed out in vscode, meaning that it won't be staged for commiting to github) ``` OPENAI_API_KEY=XXXXXXXXXXXXXXSXXXXXXXXX ``` - that api key should now be available as `process.env.OPENAI_API_KEY` throughout your app. - ## create your own utilities etc - create a `/src` folder - inside there create other folders for your code. It's conventional to have one called `/utils` for bits of code you use all across the project (and even in other projects) and the ## direction we're going ``` #!/usr/bin/env node var figlet = require('figlet'); var clear = require('clear'); const llog = require('./src/utils/ll-logs') const { whisper, vision, tts } = require('./src/projects/openai') require("dotenv").config({ path: __dirname + `/.env.cli` }); var yargs = require('yargs').argv; clear() llog.cyan(figlet.textSync("my command.")) llog.gray(`launching with yargs`, yargs) if (yargs.whisper) { whisper({audioFile: yargs.whisper}) } else if (yargs.vision) { vision({imageFile: yargs.vision}) } else if (yargs.tts) { llog.red({textFile: yargs.tts}) tts({textFile: yargs.tts}) } else { console.log(`sorry, you didn't enter a recognized command.`) } ``` if you want llog ``` const ansiColors = { black: `\u001b[30m`, red: `\u001b[38;5;196m`, green: `\u001b[32m`, yellow: `\u001b[38;5;11m`, blue: `\u001b[34m`, magenta: `\u001b[35m`, cyan: `\u001b[36m`, white: `\u001b[37m`, reset: `\u001b[0m`, gray: `\u001b[38;5;245m`, darkgray: `\u001b[38;5;239m`, } function myTypeOfLog(things, color) { things.forEach(thing => { if (typeof thing == "string") { console.log(`${ansiColors[color]}${thing}${ansiColors.reset}`) } else { console.log(`${ansiColors[color]}${JSON.stringify(thing, null, 4)}${ansiColors.reset}`) } }) } module.exports.blue = (...things) => { myTypeOfLog(things, "blue" ) } module.exports.cyan = (...things) => { myTypeOfLog(things, "cyan" ) } module.exports.yellow = (...things) => { myTypeOfLog(things, "yellow" ) } module.exports.magenta = (...things) => {myTypeOfLog(things, "magenta" ) } module.exports.green = (...things) => {myTypeOfLog(things, "green" ) } module.exports.red = (...things) => {myTypeOfLog(things, "red" ) } module.exports.white = (...things) => {myTypeOfLog(things, "white" ) } module.exports.gray = (...things) => {myTypeOfLog(things, "gray" ) } module.exports.grey = (...things) => {myTypeOfLog(things, "gray" ) } module.exports.darkgray = (...things) => {myTypeOfLog(things, "darkgray" ) } module.exports.divider = `#########################################################\n#########################################################` ``` then in `./src/projects/openai` ``` const fs = require('fs').promises; const path = require('path'); const { OpenAI } = require('openai'); module.exports.tts = async ({ textFile }) => { const openai = new OpenAI({apiKey: process.env.OPENAI_API_KEY}); // Read the text content from the textFile const textContent = await fs.readFile(textFile, 'utf8'); // Extract the directory and the file name without extension from textFile const dir = path.dirname(textFile); const baseName = path.basename(textFile, path.extname(textFile)); // Construct the output path with the same structure but with .m4a extension const outputPath = path.join(dir, `${baseName}.mp3`); const mp3 = await openai.audio.speech.create({ model: "tts-1", voice: "alloy", input: textContent, // Use the read text content as input // response_format: "aac" }); console.log(outputPath); const buffer = Buffer.from(await mp3.arrayBuffer()); await fs.writeFile(outputPath, buffer); } // module.exports.whisper = still to do // module.exports.vision = still to do ```