@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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +190 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nexusbloom/cli",
3
- "version": "0.2.9",
3
+ "version": "0.2.11",
4
4
  "description": "NexusBloom CLI — run tools and workflows from your terminal",
5
5
  "type": "module",
6
6
  "bin": {
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: { "Content-Type": "application/json" },
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.cyan(manifest.pricing || "free")}`);
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);