@nexusbloom/cli 0.2.10 → 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 +184 -0
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1111,4 +1111,188 @@ async function renderQRCode(data, slug) {
|
|
|
1111
1111
|
}
|
|
1112
1112
|
}
|
|
1113
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
|
+
|
|
1114
1298
|
program.parse(process.argv);
|