New:Socket for Asana Is Now Available.Learn more
Get Started

spendict

Package Overview
Dependencies
Maintainers
1
Versions
2
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

spendict - npm Package Compare versions

Comparing version
0.1.0
to
0.2.0
+1
-1
package.json
{
"name": "spendict",
"version": "0.1.0",
"version": "0.2.0",
"description": "Spendict CLI — a performance marketer's verdict (run / fix_first / kill) on your ad creatives, from the terminal or your agent.",

@@ -5,0 +5,0 @@ "type": "module",

import { readFileSync } from "node:fs";
import { createInterface } from "node:readline/promises";
import { Command } from "commander";

@@ -11,3 +12,3 @@ import { callTool } from "./api.js";

.description("A performance marketer's verdict (run / fix_first / kill) on your ad creatives.")
.version("0.1.0");
.version("0.2.0");

@@ -62,2 +63,45 @@ const bold = (s) => `\x1b[1m${s}\x1b[0m`;

async function readStdinAll() {
let data = "";
for await (const chunk of process.stdin) data += chunk;
return data.trim();
}
// Real ad copy is full of characters the shell mangles (! " $ '), so when the
// required flags are missing we prompt for values instead — pasted input never
// passes through shell quoting.
async function promptMissing(opts) {
const rl = createInterface({ input: process.stdin, output: process.stdout });
const ask = async (label, { required = false } = {}) => {
for (;;) {
const a = (await rl.question(` ${label}: `)).trim();
if (a) return a;
if (!required) return undefined;
console.log(dim(" (required)"));
}
};
const askMultiline = async (label) => {
console.log(` ${label} ${dim("— paste it as-is, then press Enter on an empty line:")}`);
const lines = [];
for (;;) {
const line = await rl.question("");
if (!line.trim()) {
if (lines.length) break;
continue;
}
lines.push(line);
}
return lines.join("\n");
};
try {
if (!opts.text) opts.text = await askMultiline("Ad text / body");
if (!opts.platform) opts.platform = await ask("Platform (meta | tiktok | google | linkedin | youtube)", { required: true });
if (!opts.product) opts.product = await ask("Product / offer", { required: true });
if (!opts.headline) opts.headline = await ask("Headline (optional, Enter to skip)");
if (!opts.audience) opts.audience = await ask("Target audience (optional, Enter to skip)");
} finally {
rl.close();
}
}
async function run(tool, requestBody, opts) {

@@ -109,4 +153,4 @@ try {

.command("assess")
.description("Gate an ad creative before spend.")
.option("--text <s>", "primary ad text / body")
.description("Gate an ad creative before spend. Run with no flags for interactive mode (paste copy as-is, no shell quoting).")
.option("--text <s>", "primary ad text / body (or pipe it via stdin)")
.option("--headline <s>", "headline")

@@ -120,6 +164,37 @@ .option("--desc <s>", "description / subtext")

.option("--json", "print raw JSON")
.action((opts) => {
const b =
body(opts) ||
{
.addHelpText(
"after",
`
Ad copy is full of characters your shell may mangle (! $ " '), so the safest
inputs skip shell quoting entirely:
Interactive (paste copy as-is): spendict assess
Pipe the ad text via stdin: pbpaste | spendict assess --platform meta --product 'Your offer'
Full JSON body from a file: spendict assess --file ad.json
`,
)
.action(async (opts) => {
let b = body(opts);
if (!b) {
if (!opts.text && !process.stdin.isTTY) opts.text = await readStdinAll();
if ((!opts.text || !opts.platform || !opts.product) && process.stdin.isTTY) {
console.log(`\n ${bold("Interactive mode")} ${dim("— missing fields; paste values as-is (no shell quoting needed).")}`);
try {
await promptMissing(opts);
} catch (e) {
if (e.code !== "ABORT_ERR") throw e;
console.error("\nCancelled.");
process.exitCode = 1;
return;
}
}
const missing = [!opts.text && "--text", !opts.platform && "--platform", !opts.product && "--product"].filter(Boolean);
if (missing.length) {
console.error(
`\x1b[31mMissing required input: ${missing.join(", ")}. Run \`spendict assess\` with no flags for interactive mode, or pipe the ad text via stdin.\x1b[0m`,
);
process.exitCode = 1;
return;
}
b = {
ad_copy: { primary_text: opts.text, headline: opts.headline, description: opts.desc },

@@ -131,3 +206,4 @@ platform: opts.platform,

};
run("assess", b, opts);
}
await run("assess", b, opts);
});

@@ -134,0 +210,0 @@