@nexusbloom/cli 0.2.9 → 0.2.11
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.
- package/package.json +1 -1
- package/src/index.js +190 -3
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -422,10 +422,13 @@ async function executeLocal(slug, input, jsonMode) {
|
|
|
422
422
|
result = coreLogicFn(input);
|
|
423
423
|
}
|
|
424
424
|
|
|
425
|
-
// Fire-and-forget: track the run for total runs counting
|
|
425
|
+
// Fire-and-forget: track the run for total runs counting and api_usage
|
|
426
|
+
const apiKey = process.env.NEXUSBLOOM_API_KEY || configGet("apiKey") || configGet("key");
|
|
427
|
+
const trackHeaders = { "Content-Type": "application/json" };
|
|
428
|
+
if (apiKey) trackHeaders["Authorization"] = `Bearer ${apiKey}`;
|
|
426
429
|
fetch(`${API_BASE}/api/track-run`, {
|
|
427
430
|
method: "POST",
|
|
428
|
-
headers:
|
|
431
|
+
headers: trackHeaders,
|
|
429
432
|
body: JSON.stringify({ slug, source: "cli" }),
|
|
430
433
|
}).catch(() => {});
|
|
431
434
|
|
|
@@ -498,7 +501,7 @@ program
|
|
|
498
501
|
console.log(` ${chalk.dim(manifest.short_description || "")}`);
|
|
499
502
|
console.log(` Version: ${chalk.cyan(manifest.version || "1.0.0")}`);
|
|
500
503
|
console.log(` Runtime: ${chalk.cyan(manifest.runtime || "browser")}`);
|
|
501
|
-
console.log(` Pricing: ${chalk.
|
|
504
|
+
console.log(` Pricing: ${chalk.green("free")}`);
|
|
502
505
|
if (manifest.input_schema?.properties) {
|
|
503
506
|
console.log(chalk.bold(`\n Input Parameters:`));
|
|
504
507
|
const props = manifest.input_schema.properties;
|
|
@@ -1108,4 +1111,188 @@ async function renderQRCode(data, slug) {
|
|
|
1108
1111
|
}
|
|
1109
1112
|
}
|
|
1110
1113
|
|
|
1114
|
+
// ─── `nxb create` — scaffold a new tool locally ──────────────────────────
|
|
1115
|
+
const VALID_RUNTIMES = ["browser", "node", "both"];
|
|
1116
|
+
const VALID_CATEGORIES = [
|
|
1117
|
+
"text", "image", "data", "network", "security", "development",
|
|
1118
|
+
"education", "business", "utility", "math", "game", "Other",
|
|
1119
|
+
];
|
|
1120
|
+
|
|
1121
|
+
program
|
|
1122
|
+
.command("create")
|
|
1123
|
+
.description("Scaffold a new tool locally")
|
|
1124
|
+
.option("--slug <slug>", "Tool slug (lowercase, hyphenated)")
|
|
1125
|
+
.option("--name <name>", "Display name")
|
|
1126
|
+
.option("--desc <desc>", "Short description")
|
|
1127
|
+
.option("--category <category>", `Category (${VALID_CATEGORIES.join(", ")})`)
|
|
1128
|
+
.option("--runtime <runtime>", `Runtime (${VALID_RUNTIMES.join(", ")})`)
|
|
1129
|
+
.option("--dir <dir>", "Target directory (default: ./src/tools/<slug>)")
|
|
1130
|
+
.action(async (options) => {
|
|
1131
|
+
const rl = readline.createInterface({
|
|
1132
|
+
input: process.stdin,
|
|
1133
|
+
output: process.stdout,
|
|
1134
|
+
});
|
|
1135
|
+
const ask = (q) => new Promise((r) => rl.question(q, r));
|
|
1136
|
+
|
|
1137
|
+
// Gather inputs
|
|
1138
|
+
let slug = options.slug;
|
|
1139
|
+
while (!slug || !/^[a-z0-9]+(-[a-z0-9]+)*$/.test(slug)) {
|
|
1140
|
+
slug = (await ask(chalk.cyan("Tool slug (e.g. my-tool): "))).trim().toLowerCase();
|
|
1141
|
+
if (!slug) { console.log(chalk.red("Slug is required.")); continue; }
|
|
1142
|
+
if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(slug)) {
|
|
1143
|
+
console.log(chalk.red("Slug must be lowercase, hyphenated (e.g. my-tool)."));
|
|
1144
|
+
slug = null;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
let name = options.name;
|
|
1149
|
+
while (!name || name.length < 2) {
|
|
1150
|
+
name = (await ask(chalk.cyan("Display name: "))).trim();
|
|
1151
|
+
if (!name) console.log(chalk.red("Name is required."));
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
let desc = options.desc;
|
|
1155
|
+
while (!desc) {
|
|
1156
|
+
desc = (await ask(chalk.cyan("Short description: "))).trim();
|
|
1157
|
+
if (!desc) console.log(chalk.red("Description is required."));
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
let category = options.category;
|
|
1161
|
+
while (!category || !VALID_CATEGORIES.includes(category)) {
|
|
1162
|
+
const catList = VALID_CATEGORIES.join(", ");
|
|
1163
|
+
category = (await ask(chalk.cyan("Category (" + catList + "): "))).trim().toLowerCase();
|
|
1164
|
+
if (category === "other") category = "Other";
|
|
1165
|
+
if (!VALID_CATEGORIES.includes(category)) {
|
|
1166
|
+
console.log(chalk.red("Invalid category. Choose from: " + catList));
|
|
1167
|
+
category = null;
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
let runtime = options.runtime;
|
|
1172
|
+
while (!runtime || !VALID_RUNTIMES.includes(runtime)) {
|
|
1173
|
+
const rtList = VALID_RUNTIMES.join(", ");
|
|
1174
|
+
runtime = (await ask(chalk.cyan("Runtime (" + rtList + "): "))).trim().toLowerCase();
|
|
1175
|
+
if (!VALID_RUNTIMES.includes(runtime)) {
|
|
1176
|
+
console.log(chalk.red("Invalid runtime. Choose from: " + rtList));
|
|
1177
|
+
runtime = null;
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
rl.close();
|
|
1182
|
+
|
|
1183
|
+
// Generate v2.js source
|
|
1184
|
+
const pascalName = slug
|
|
1185
|
+
.split("-")
|
|
1186
|
+
.map((p) => p.charAt(0).toUpperCase() + p.slice(1))
|
|
1187
|
+
.join("");
|
|
1188
|
+
|
|
1189
|
+
const v2Source = `/**
|
|
1190
|
+
* ${name} — ${desc}
|
|
1191
|
+
* Auto-generated by nxb create
|
|
1192
|
+
*/
|
|
1193
|
+
|
|
1194
|
+
export const MANIFEST = {
|
|
1195
|
+
slug: "${slug}",
|
|
1196
|
+
name: "${name}",
|
|
1197
|
+
short_description: "${desc}",
|
|
1198
|
+
icon: "🛠️",
|
|
1199
|
+
category: "${category}",
|
|
1200
|
+
tags: [],
|
|
1201
|
+
runtime: "${runtime}",
|
|
1202
|
+
pricing: "free",
|
|
1203
|
+
version: "1.0.0",
|
|
1204
|
+
timeout_ms: 5000,
|
|
1205
|
+
input_schema: {
|
|
1206
|
+
type: "object",
|
|
1207
|
+
properties: {
|
|
1208
|
+
input: {
|
|
1209
|
+
type: "string",
|
|
1210
|
+
description: "Input text to process",
|
|
1211
|
+
},
|
|
1212
|
+
},
|
|
1213
|
+
required: ["input"],
|
|
1214
|
+
},
|
|
1215
|
+
output_schema: {
|
|
1216
|
+
type: "object",
|
|
1217
|
+
properties: {
|
|
1218
|
+
result: {
|
|
1219
|
+
type: "string",
|
|
1220
|
+
description: "Processed output",
|
|
1221
|
+
},
|
|
1222
|
+
},
|
|
1223
|
+
},
|
|
1224
|
+
};
|
|
1225
|
+
|
|
1226
|
+
export async function coreLogic(input) {
|
|
1227
|
+
// Replace with your tool's logic
|
|
1228
|
+
return { result: \`Processed: \${input.input}\` };
|
|
1229
|
+
}
|
|
1230
|
+
`;
|
|
1231
|
+
|
|
1232
|
+
const indexSource = `import React from "react";
|
|
1233
|
+
import { MANIFEST } from "./v2";
|
|
1234
|
+
|
|
1235
|
+
export default function ${pascalName}Tool({ onResult }) {
|
|
1236
|
+
const [input, setInput] = React.useState("");
|
|
1237
|
+
const [result, setResult] = React.useState(null);
|
|
1238
|
+
|
|
1239
|
+
const handleRun = async () => {
|
|
1240
|
+
try {
|
|
1241
|
+
const { coreLogic } = await import("./v2");
|
|
1242
|
+
const output = await coreLogic({ input });
|
|
1243
|
+
setResult(output);
|
|
1244
|
+
if (onResult) onResult(output);
|
|
1245
|
+
} catch (err) {
|
|
1246
|
+
setResult({ error: err.message });
|
|
1247
|
+
}
|
|
1248
|
+
};
|
|
1249
|
+
|
|
1250
|
+
return (
|
|
1251
|
+
<div className="nexusbloom-tool">
|
|
1252
|
+
<h2 className="text-xl font-bold mb-4">{MANIFEST.name}</h2>
|
|
1253
|
+
<p className="text-gray-600 dark:text-gray-400 mb-4">{MANIFEST.short_description}</p>
|
|
1254
|
+
<textarea className="w-full p-2 border rounded" rows={4} value={input}
|
|
1255
|
+
onChange={(e) => setInput(e.target.value)} placeholder="Enter input..." />
|
|
1256
|
+
<button className="mt-2 px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
|
|
1257
|
+
onClick={handleRun}>Run</button>
|
|
1258
|
+
{result && <pre className="mt-4 p-3 bg-gray-100 rounded overflow-auto">{JSON.stringify(result, null, 2)}</pre>}
|
|
1259
|
+
</div>
|
|
1260
|
+
);
|
|
1261
|
+
}
|
|
1262
|
+
`;
|
|
1263
|
+
|
|
1264
|
+
// Determine target directory
|
|
1265
|
+
const targetDir = options.dir || path.join(process.cwd(), "src/tools", slug);
|
|
1266
|
+
const v2Path = path.join(targetDir, "v2.js");
|
|
1267
|
+
const indexPath = path.join(targetDir, "index.js");
|
|
1268
|
+
|
|
1269
|
+
// Check for existing files
|
|
1270
|
+
if (fs.existsSync(v2Path) || fs.existsSync(indexPath)) {
|
|
1271
|
+
const overwrite = (await new Promise((r) => {
|
|
1272
|
+
const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1273
|
+
rl2.question(chalk.yellow(`Tool "${slug}" already exists. Overwrite? (y/N): `), (a) => {
|
|
1274
|
+
rl2.close();
|
|
1275
|
+
r(a.trim().toLowerCase() === "y");
|
|
1276
|
+
});
|
|
1277
|
+
}));
|
|
1278
|
+
if (!overwrite) {
|
|
1279
|
+
console.log(chalk.dim("Aborted."));
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
// Write files
|
|
1285
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
1286
|
+
fs.writeFileSync(v2Path, v2Source, "utf-8");
|
|
1287
|
+
fs.writeFileSync(indexPath, indexSource, "utf-8");
|
|
1288
|
+
|
|
1289
|
+
console.log(chalk.green(`\n ✓ Tool "${name}" created successfully!\n`));
|
|
1290
|
+
console.log(chalk.dim(` ${v2Path}`));
|
|
1291
|
+
console.log(chalk.dim(` ${indexPath}\n`));
|
|
1292
|
+
console.log(chalk.dim(" Next steps:"));
|
|
1293
|
+
console.log(chalk.dim(" 1. Edit v2.js to implement your tool logic"));
|
|
1294
|
+
console.log(chalk.dim(" 2. Run nxb run --local <slug> to test locally"));
|
|
1295
|
+
console.log(chalk.dim(" 3. Publish from the VS Code extension or API\n"));
|
|
1296
|
+
});
|
|
1297
|
+
|
|
1111
1298
|
program.parse(process.argv);
|