@nevermorelove/ompp 0.3.1 → 0.4.0
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/README.md +4 -0
- package/ompp.js +131 -37
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,6 +32,10 @@ ompp list print mode names
|
|
|
32
32
|
ompp pentest --dry-run show the omp command line instead of running it
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
+
In the picker, `r` renames the highlighted mode and `d` deletes it (after a
|
|
36
|
+
confirmation). The picker reopens after either, so a mispress costs nothing:
|
|
37
|
+
cancel the rename or keep the mode, and you are back in the list.
|
|
38
|
+
|
|
35
39
|
Before launch it prints one line to stderr, `[ompp] mode: pentest`, so you always know where you are.
|
|
36
40
|
|
|
37
41
|
Modes are read from two places, and same-named modes from the first win:
|
package/ompp.js
CHANGED
|
@@ -64,16 +64,14 @@ function isNewerVersion(current, latest) {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
function printUpdateBanner(current, latest) {
|
|
67
|
-
const line = `Update available! ${current}
|
|
67
|
+
const line = `Update available! ${current} -> ${latest}`;
|
|
68
68
|
const hint = `Run "npm i -g @nevermorelove/ompp" to update`;
|
|
69
|
-
const
|
|
70
|
-
const pad = (s) => "
|
|
71
|
-
console.error("┌" + "─".repeat(
|
|
72
|
-
console.error("│" + " ".repeat(width) + "│");
|
|
69
|
+
const inner = Math.max(line.length, hint.length) + 2;
|
|
70
|
+
const pad = (s) => " " + s + " ".repeat(inner - 1 - s.length);
|
|
71
|
+
console.error("┌" + "─".repeat(inner) + "┐");
|
|
73
72
|
console.error("│" + pad(line) + "│");
|
|
74
73
|
console.error("│" + pad(hint) + "│");
|
|
75
|
-
console.error("
|
|
76
|
-
console.error("└" + "─".repeat(width) + "┘");
|
|
74
|
+
console.error("└" + "─".repeat(inner) + "┘");
|
|
77
75
|
}
|
|
78
76
|
|
|
79
77
|
async function checkForUpdate() {
|
|
@@ -228,6 +226,7 @@ function usage() {
|
|
|
228
226
|
|
|
229
227
|
Usage:
|
|
230
228
|
ompp pick a mode interactively, then launch omp
|
|
229
|
+
(r: rename, d: delete on the highlighted mode)
|
|
231
230
|
ompp <mode> [args...] launch omp in a mode; everything after <mode>
|
|
232
231
|
is passed to omp unchanged and wins over the mode
|
|
233
232
|
ompp create <name> create a new mode with placeholder files
|
|
@@ -345,44 +344,135 @@ function createMode(name) {
|
|
|
345
344
|
return 0;
|
|
346
345
|
}
|
|
347
346
|
|
|
347
|
+
function renameMode(mode, newName) {
|
|
348
|
+
if (!newName || !isValidModeName(newName)) {
|
|
349
|
+
console.error(`[ompp] "${newName}" is not a valid mode name.`);
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
if (RESERVED_NAMES.includes(newName)) {
|
|
353
|
+
console.error(`[ompp] "${newName}" is reserved for ompp commands.`);
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
const from = modeDirOf(mode.name, mode.source);
|
|
357
|
+
const to = modeDirOf(newName, mode.source);
|
|
358
|
+
if (fs.existsSync(to)) {
|
|
359
|
+
console.error(`[ompp] mode "${newName}" already exists.`);
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
fs.renameSync(from, to);
|
|
363
|
+
console.error(`[ompp] renamed "${mode.name}" to "${newName}"`);
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function deleteMode(mode) {
|
|
368
|
+
const dir = modeDirOf(mode.name, mode.source);
|
|
369
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
370
|
+
console.error(`[ompp] deleted mode "${mode.name}"`);
|
|
371
|
+
return true;
|
|
372
|
+
}
|
|
373
|
+
|
|
348
374
|
const CREATE_OPTION = "__create__";
|
|
349
375
|
|
|
350
376
|
function isValidModeName(name) {
|
|
351
377
|
return /^[a-z0-9][a-z0-9-]*$/.test(name);
|
|
352
378
|
}
|
|
353
379
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
380
|
+
// The interactive picker. Receives mode objects ({name, source}), returns
|
|
381
|
+
// the picked mode object, or {create: <name>} for the create flow.
|
|
382
|
+
// r renames and d deletes the highlighted mode; both re-render the picker.
|
|
383
|
+
async function pickMode(modes) {
|
|
384
|
+
const { select, text, confirm, isCancel, cancel } = require("@clack/prompts");
|
|
385
|
+
for (;;) {
|
|
386
|
+
const options = [
|
|
387
|
+
...modes.map((m) => ({
|
|
388
|
+
value: m,
|
|
389
|
+
label: m.name,
|
|
390
|
+
hint: "r: rename, d: delete",
|
|
391
|
+
})),
|
|
392
|
+
{ value: CREATE_OPTION, label: "Create a new mode +" },
|
|
393
|
+
];
|
|
394
|
+
let action = null; // "rename" | "delete" | null
|
|
395
|
+
// Registered before the prompt so it sees the keypress first and can
|
|
396
|
+
// rewrite r/d into "return" for the prompt's own keypress listener.
|
|
397
|
+
const onKeypress = (ch, key) => {
|
|
398
|
+
if (!key || !key.name) return;
|
|
399
|
+
if (key.name === "r" && !key.ctrl && !key.meta) {
|
|
400
|
+
action = "rename";
|
|
401
|
+
key.name = "return";
|
|
402
|
+
} else if (key.name === "d" && !key.ctrl && !key.meta) {
|
|
403
|
+
action = "delete";
|
|
404
|
+
key.name = "return";
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
process.stdin.on("keypress", onKeypress);
|
|
408
|
+
const chosen = await select({
|
|
409
|
+
message: "Pick a mode",
|
|
410
|
+
options,
|
|
378
411
|
});
|
|
379
|
-
|
|
412
|
+
process.stdin.removeListener("keypress", onKeypress);
|
|
413
|
+
|
|
414
|
+
if (isCancel(chosen)) {
|
|
380
415
|
cancel("Cancelled");
|
|
381
416
|
process.exit(130);
|
|
382
417
|
}
|
|
383
|
-
|
|
418
|
+
|
|
419
|
+
// r/d on the highlighted row: run the action, then reopen the picker.
|
|
420
|
+
if (action && chosen !== CREATE_OPTION) {
|
|
421
|
+
if (action === "rename") {
|
|
422
|
+
const newName = await text({
|
|
423
|
+
message: `Rename "${chosen.name}" to`,
|
|
424
|
+
validate: (v) => {
|
|
425
|
+
if (!v || !isValidModeName(v)) {
|
|
426
|
+
return "Lowercase letters, digits, and dashes only";
|
|
427
|
+
}
|
|
428
|
+
if (RESERVED_NAMES.includes(v)) return `"${v}" is reserved`;
|
|
429
|
+
if (modes.some((m) => m.name === v)) return `"${v}" already exists`;
|
|
430
|
+
return undefined;
|
|
431
|
+
},
|
|
432
|
+
});
|
|
433
|
+
if (!isCancel(newName) && renameMode(chosen, newName)) {
|
|
434
|
+
chosen.name = newName;
|
|
435
|
+
} else {
|
|
436
|
+
cancel("Rename cancelled");
|
|
437
|
+
}
|
|
438
|
+
} else {
|
|
439
|
+
const sure = await confirm({
|
|
440
|
+
message: `Delete "${chosen.name}"? This removes its folder.`,
|
|
441
|
+
active: "Delete",
|
|
442
|
+
inactive: "Keep",
|
|
443
|
+
});
|
|
444
|
+
if (!isCancel(sure) && sure) {
|
|
445
|
+
deleteMode(chosen);
|
|
446
|
+
// Splice in place: main() keeps a reference to this array.
|
|
447
|
+
modes.splice(
|
|
448
|
+
modes.findIndex((m) => m.name === chosen.name),
|
|
449
|
+
1,
|
|
450
|
+
);
|
|
451
|
+
} else {
|
|
452
|
+
cancel("Delete cancelled");
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
continue; // reopen the picker with the updated list
|
|
456
|
+
}
|
|
457
|
+
if (chosen === CREATE_OPTION) {
|
|
458
|
+
const name = await text({
|
|
459
|
+
message: "New mode name",
|
|
460
|
+
validate: (v) => {
|
|
461
|
+
if (!v || !isValidModeName(v)) {
|
|
462
|
+
return "Lowercase letters, digits, and dashes only";
|
|
463
|
+
}
|
|
464
|
+
if (RESERVED_NAMES.includes(v)) return `"${v}" is reserved`;
|
|
465
|
+
return undefined;
|
|
466
|
+
},
|
|
467
|
+
});
|
|
468
|
+
if (isCancel(name)) {
|
|
469
|
+
cancel("Cancelled");
|
|
470
|
+
process.exit(130);
|
|
471
|
+
}
|
|
472
|
+
return { create: name };
|
|
473
|
+
}
|
|
474
|
+
return chosen;
|
|
384
475
|
}
|
|
385
|
-
return chosen;
|
|
386
476
|
}
|
|
387
477
|
|
|
388
478
|
// Non-TTY stdin: clack needs an interactive terminal, so numbered input.
|
|
@@ -523,16 +613,20 @@ async function main() {
|
|
|
523
613
|
|
|
524
614
|
if (!mode) {
|
|
525
615
|
try {
|
|
526
|
-
|
|
527
|
-
? await pickMode(
|
|
616
|
+
let picked = process.stdin.isTTY
|
|
617
|
+
? await pickMode(modes)
|
|
528
618
|
: await pickModePiped(modeNames);
|
|
619
|
+
if (typeof picked === "string") {
|
|
620
|
+
// The piped picker returns a name; normalize to the mode object.
|
|
621
|
+
picked = modes.find((m) => m.name === picked);
|
|
622
|
+
}
|
|
529
623
|
if (picked && typeof picked === "object" && picked.create) {
|
|
530
624
|
// Scaffold and stop: the mode is all placeholders, launching it
|
|
531
625
|
// now would run an unconfigured mode. The user fills it in and
|
|
532
626
|
// launches when ready.
|
|
533
627
|
return createMode(picked.create);
|
|
534
628
|
} else {
|
|
535
|
-
mode =
|
|
629
|
+
mode = picked;
|
|
536
630
|
}
|
|
537
631
|
} catch (err) {
|
|
538
632
|
console.error(`[ompp] ${err.message}`);
|