@nevermorelove/ompp 0.2.0 → 0.2.1

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/ompp.js +37 -85
  2. package/package.json +5 -2
package/ompp.js CHANGED
@@ -278,88 +278,43 @@ function createMode(name) {
278
278
  return 0;
279
279
  }
280
280
 
281
- function pickInteractive(modes) {
282
- return new Promise((resolve, reject) => {
283
- const stdin = process.stdin;
284
- let idx = 0;
285
- let settled = false;
286
-
287
- const finish = (value) => {
288
- if (settled) return;
289
- settled = true;
290
- stdin.removeListener("data", onData);
291
- stdin.setRawMode(false);
292
- process.stdout.write("\u001b[?25h"); // show cursor
293
- if (value === null) reject(new Error("mode selection cancelled"));
294
- else resolve(value);
295
- };
296
-
297
- const render = (first) => {
298
- if (!first) process.stdout.write(`\u001b[${modes.length}A`);
299
- const body = modes
300
- .map((m, i) => (i === idx ? "> " + m : " " + m))
301
- .map((l) => "\u001b[2K" + l)
302
- .join("\n");
303
- process.stdout.write("\u001b[?25l" + body + "\n");
304
- };
305
-
306
- function onData(buf) {
307
- // A single read can carry several keys (arrow + Enter glued together,
308
- // a paste). Walk key by key instead of matching the whole chunk.
309
- const s = buf.toString();
310
- let i = 0;
311
- while (i < s.length) {
312
- const ch = s[i];
313
- if (ch === "\u001b") {
314
- const seq = s.slice(i);
315
- if (seq.startsWith("\u001b[A") || seq.startsWith("\u001b[B") ||
316
- seq.startsWith("\u001b[C") || seq.startsWith("\u001b[D")) {
317
- if (seq.startsWith("\u001b[A")) {
318
- idx = (idx - 1 + modes.length) % modes.length;
319
- render(false);
320
- } else if (seq.startsWith("\u001b[B")) {
321
- idx = (idx + 1) % modes.length;
322
- render(false);
323
- } // C/D: left/right, ignore
324
- i += 3;
325
- continue;
326
- }
327
- if (seq.length === 1) {
328
- // Bare escape with nothing after it in this chunk: cancel.
329
- finish(null);
330
- process.exit(0);
331
- }
332
- i++; // unknown or split sequence, skip the escape byte
333
- continue;
334
- }
335
- if (ch === "\r" || ch === "\n") return finish(modes[idx]);
336
- if (ch === "\u0003") {
337
- // Ctrl+C: restore the terminal and bail like a shell would.
338
- finish(null);
339
- process.exit(130);
340
- }
341
- if (ch === "q") {
342
- finish(null);
343
- process.exit(0);
344
- }
345
- if (ch === "j") {
346
- idx = (idx + 1) % modes.length;
347
- render(false);
348
- }
349
- if (ch === "k") {
350
- idx = (idx - 1 + modes.length) % modes.length;
351
- render(false);
352
- }
353
- i++;
354
- }
355
- }
281
+ async function pickMode(modeNames) {
282
+ const { select, isCancel, cancel } = require("@clack/prompts");
283
+ const chosen = await select({
284
+ message: "Pick a mode",
285
+ options: modeNames.map((name) => ({ value: name, label: name })),
286
+ });
287
+ if (isCancel(chosen)) {
288
+ cancel("Cancelled");
289
+ process.exit(130);
290
+ }
291
+ return chosen;
292
+ }
356
293
 
357
- stdin.setRawMode(true);
358
- stdin.resume();
359
- render(true);
294
+ // Non-TTY stdin: clack needs an interactive terminal, so numbered input.
295
+ function pickModePiped(modeNames) {
296
+ return new Promise((resolve, reject) => {
297
+ const readline = require("readline");
298
+ const rl = readline.createInterface({ input: process.stdin });
299
+ process.stdout.write("Select a mode:\n");
300
+ modeNames.forEach((m, i) => process.stdout.write(` ${i + 1}. ${m}\n`));
301
+ process.stdout.write(`Choice [1-${modeNames.length}]: `);
302
+ let done = false;
303
+ rl.once("line", (line) => {
304
+ done = true;
305
+ rl.close();
306
+ const n = parseInt(line.trim(), 10);
307
+ if (n >= 1 && n <= modeNames.length) resolve(modeNames[n - 1]);
308
+ else reject(new Error(`"${line.trim()}" is not a valid choice`));
309
+ });
310
+ rl.once("close", () => {
311
+ if (!done) reject(new Error("no mode selected"));
312
+ });
360
313
  });
361
314
  }
362
315
 
316
+
317
+
363
318
  async function main() {
364
319
  const argv = process.argv.slice(2);
365
320
 
@@ -427,13 +382,10 @@ async function main() {
427
382
  }
428
383
  } else {
429
384
  try {
430
- mode = process.stdin.isTTY
431
- ? await pickInteractive(modeNames).then((n) =>
432
- modes.find((m) => m.name === n),
433
- )
434
- : await pickByNumber(modeNames).then((n) =>
435
- modes.find((m) => m.name === n),
436
- );
385
+ const name = process.stdin.isTTY
386
+ ? await pickMode(modeNames)
387
+ : await pickModePiped(modeNames);
388
+ mode = modes.find((m) => m.name === name);
437
389
  } catch (err) {
438
390
  console.error(`[ompp] ${err.message}`);
439
391
  return 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nevermorelove/ompp",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Mode presets for the omp coding agent: one folder per mode, zero config code.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -24,5 +24,8 @@
24
24
  "cli"
25
25
  ],
26
26
  "author": "ForeverInLaw",
27
- "license": "MIT"
27
+ "license": "MIT",
28
+ "dependencies": {
29
+ "@clack/prompts": "^1.8.1"
30
+ }
28
31
  }