@henryqw/pi-prompt-creator 1.0.6 → 1.1.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.
package/CONTEXT.md CHANGED
@@ -21,4 +21,4 @@ Turn repetition and correction in the current Pi conversation into one editable
21
21
  | Scripts, Skills, and other automation | Their owning package or repository |
22
22
  | This extension | Current-conversation Signal detection, Prompt Candidate generation, and Prompt Review |
23
23
 
24
- The extension stays silent when it finds no Prompt Candidate. Background analysis remains visible while it is running. Every completed candidate waits for explicit user display, so background work never steals editor focus.
24
+ The extension stays silent when it finds no Prompt Candidate. Background analysis remains visible while it is running. Manually requested candidates display when ready; automatic candidates wait for explicit user display, so background work never steals editor focus.
package/README.md CHANGED
@@ -23,22 +23,19 @@ Run `/task-models` before analysis. Assign a model to `fast`, or override `pi-pr
23
23
 
24
24
  Run `/promptor` in the interactive TUI after a repeated request or correction appears:
25
25
 
26
- 1. Choose `Analyze now`. Wait for the `Prompt ready — /promptor` widget.
27
- 2. Run `/promptor` again and choose `Show candidate`.
28
- 3. Ask Main to refine it and return only the complete Final Prompt Draft.
29
- 4. Run `/promptor`, choose `Save latest Main draft`, and enter a lowercase kebab-case name.
26
+ 1. Run `/promptor`. The completed candidate appears automatically.
27
+ 2. Ask Main to refine it and return only the complete Final Prompt Draft.
28
+ 3. Run `/promptor save` to save under the suggested name, or `/promptor save <name>` to rename it.
30
29
 
31
- The prompt is created at `~/.pi/agent/prompts/<name>.md`, then Pi reloads its resources.
30
+ The prompt is created at `~/.pi/agent/prompts/<name>.md`, then Pi reloads its resources. No selection menu or name prompt is required.
32
31
 
33
- The menu adapts to the current state:
34
-
35
- | Item | Action |
32
+ | Command | Action |
36
33
  | --- | --- |
37
- | `Analyze now` or `Analyze again` | Start one visible background analysis. |
38
- | `Automatic On` or `Automatic Off` | Turn automatic analysis on or off. |
39
- | `Show candidate` | Add the candidate to the conversation for review. |
40
- | `Dismiss candidate` | Forget the pending candidate. |
41
- | `Save latest Main draft` | Save Main's newest completed review reply after you show a candidate. |
34
+ | `/promptor` | Start analysis, or show a candidate prepared by automatic analysis. |
35
+ | `/promptor analyze` | Analyze again after a candidate has been reviewed or dismissed. |
36
+ | `/promptor save [name]` | Save Main's newest completed review reply, using the suggested name by default. |
37
+ | `/promptor dismiss` | Forget a candidate prepared by automatic analysis. |
38
+ | `/promptor automatic <on\|off>` | Enable or disable automatic analysis. |
42
39
 
43
40
  ## Flow
44
41
 
@@ -68,11 +65,11 @@ New user input does not stop a running child. Branch navigation discards its old
68
65
 
69
66
  ### Review and save
70
67
 
71
- The widget shows `Prompt ready — /promptor` until you show or dismiss the completed candidate.
68
+ A manually requested analysis displays its completed candidate immediately. Automatic background analysis keeps its candidate behind the `Prompt ready — /promptor` widget until you run `/promptor` or `/promptor dismiss`, so background work never interrupts the conversation.
72
69
 
73
- The extension never injects a candidate automatically. `Show candidate` adds one visible message and marks its contents as untrusted.
70
+ Displayed candidates are visible messages marked as untrusted.
74
71
 
75
- Refine the candidate with Main. Ask Main to return only the complete Final Prompt Draft before saving.
72
+ Refine the candidate with Main. Ask Main to return only the complete Final Prompt Draft before saving it with `/promptor save [name]`.
76
73
 
77
74
  The save item appears only after you show a candidate and Main then completes a valid Markdown review reply.
78
75
 
@@ -32,7 +32,7 @@ const NAME = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
32
32
  const DISALLOWED_MARKDOWN_CONTROLS = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/;
33
33
  const READY_WIDGET = "Prompt ready — /promptor";
34
34
  const FAILURE_WIDGET = "Prompt analysis failed — /promptor";
35
- const REFINEMENT_GUIDANCE = "Refine this candidate conversationally with the user. When the user approves it, Main must emit only the complete Final Prompt Draft. Then save that full reply through /promptor.";
35
+ const REFINEMENT_GUIDANCE = "Refine this candidate conversationally with the user. When the user approves it, Main must emit only the complete Final Prompt Draft. Then run /promptor save to use the suggested name, or /promptor save <name>.";
36
36
 
37
37
  export const DRAFT_TASK = {
38
38
  id: "pi-prompt-creator/draft",
@@ -278,8 +278,6 @@ export default function promptCreatorExtension(pi: ExtensionAPI, options: Prompt
278
278
  let configWarned = false;
279
279
  let inputCount = 0;
280
280
  let branchGeneration = 0;
281
- let closed = false;
282
- let analysisRan = false;
283
281
  let candidate: PromptCandidate | undefined;
284
282
  let candidateNameHint: string | undefined;
285
283
  let reviewBoundary: ReviewBoundary | undefined;
@@ -295,11 +293,10 @@ export default function promptCreatorExtension(pi: ExtensionAPI, options: Prompt
295
293
  if (ctx.mode === "tui") ctx.ui.setWidget(WIDGET_KEY, [FAILURE_WIDGET]);
296
294
  };
297
295
  const isCurrent = (run: ActiveRun) =>
298
- !closed && activeRun === run && branchGeneration === run.branchGeneration && !run.controller.signal.aborted;
296
+ activeRun === run && branchGeneration === run.branchGeneration && !run.controller.signal.aborted;
299
297
  const resetBranch = (ctx: ExtensionContext) => {
300
298
  branchGeneration += 1;
301
299
  inputCount = 0;
302
- analysisRan = false;
303
300
  candidate = undefined;
304
301
  candidateNameHint = undefined;
305
302
  reviewBoundary = undefined;
@@ -311,10 +308,23 @@ export default function promptCreatorExtension(pi: ExtensionAPI, options: Prompt
311
308
  maxTurns: 3,
312
309
  timeout: { idleMs: 2 * 60_000, maxMs: 5 * 60_000 },
313
310
  });
311
+ const showCandidate = (ctx: ExtensionContext) => {
312
+ if (!candidate) return;
313
+ const shown = candidate;
314
+ const boundaryId = ctx.sessionManager.getLeafId();
315
+ candidate = undefined;
316
+ candidateNameHint = shown.name;
317
+ clearWidget(ctx);
318
+ pi.sendMessage({
319
+ customType: CANDIDATE_MESSAGE_TYPE,
320
+ content: candidateMessage(shown),
321
+ display: true,
322
+ }, { triggerTurn: false });
323
+ reviewBoundary = boundaryId ? { entryId: boundaryId } : undefined;
324
+ };
314
325
  const startAnalysis = (ctx: ExtensionContext, manual: boolean) => {
315
326
  if (ctx.mode !== "tui" || activeRun || candidate) return;
316
327
  if (manual) automaticConsumed = true;
317
- analysisRan = true;
318
328
  failure = false;
319
329
  candidateNameHint = undefined;
320
330
  const payload = analysisPayload(pi, ctx);
@@ -353,7 +363,8 @@ export default function promptCreatorExtension(pi: ExtensionAPI, options: Prompt
353
363
  }
354
364
  candidate = drafted;
355
365
  candidateNameHint = drafted.name;
356
- ctx.ui.setWidget(WIDGET_KEY, [READY_WIDGET]);
366
+ if (manual) showCandidate(ctx);
367
+ else ctx.ui.setWidget(WIDGET_KEY, [READY_WIDGET]);
357
368
  }).catch(() => {
358
369
  if (isCurrent(run)) showFailure(ctx);
359
370
  }).finally(() => {
@@ -363,13 +374,13 @@ export default function promptCreatorExtension(pi: ExtensionAPI, options: Prompt
363
374
 
364
375
  const saveLatestDraft = async (
365
376
  draft: string,
377
+ requestedName: string | undefined,
366
378
  expectedReview: ReviewBoundary,
367
379
  expectedBranch: number,
368
380
  ctx: ExtensionCommandContext,
369
381
  ) => {
370
- const requested = await ctx.ui.input("Prompt name", candidateNameHint ?? "my-prompt");
371
- if (requested === undefined || expectedBranch !== branchGeneration || reviewBoundary !== expectedReview) return;
372
- const name = requested.trim();
382
+ if (expectedBranch !== branchGeneration || reviewBoundary !== expectedReview) return;
383
+ const name = requestedName ?? candidateNameHint ?? "";
373
384
  if (!isPromptName(name)) {
374
385
  ctx.ui.notify(`Use lowercase kebab-case starting with a letter, up to ${MAX_NAME_CHARS} characters.`, "warning");
375
386
  return;
@@ -398,7 +409,6 @@ export default function promptCreatorExtension(pi: ExtensionAPI, options: Prompt
398
409
  };
399
410
 
400
411
  pi.on("session_start", (_event, ctx) => {
401
- closed = false;
402
412
  activeRun?.controller.abort(new Error("Prompt Creator session changed."));
403
413
  activeRun = undefined;
404
414
  resetBranch(ctx);
@@ -442,44 +452,59 @@ export default function promptCreatorExtension(pi: ExtensionAPI, options: Prompt
442
452
 
443
453
  pi.on("session_tree", (_event, ctx) => resetBranch(ctx));
444
454
  pi.on("session_shutdown", (_event, ctx) => {
445
- closed = true;
446
- branchGeneration += 1;
447
- candidate = undefined;
448
- candidateNameHint = undefined;
449
- reviewBoundary = undefined;
450
- failure = false;
451
- inputCount = 0;
452
- clearWidget(ctx);
455
+ resetBranch(ctx);
453
456
  activeRun?.controller.abort(new Error("Prompt Creator shut down."));
454
457
  activeRun = undefined;
455
458
  });
456
459
 
457
460
  pi.registerCommand("promptor", {
458
- description: "Analyze this conversation and manage prompt candidates",
459
- handler: async (_args, ctx) => {
461
+ description: "Analyze prompts, show a ready candidate, or save with /promptor save [name]",
462
+ getArgumentCompletions: (prefix) => {
463
+ const commands = ["analyze", "dismiss", "save", "automatic on", "automatic off"];
464
+ const matches = commands.filter((command) => command.startsWith(prefix));
465
+ return matches.length ? matches.map((command) => ({ value: command, label: command })) : null;
466
+ },
467
+ handler: async (args, ctx) => {
460
468
  if (ctx.mode !== "tui") {
461
469
  ctx.ui.notify("/promptor requires the interactive TUI.", "warning");
462
470
  return;
463
471
  }
464
- const menuBranch = branchGeneration;
472
+ const commandBranch = branchGeneration;
465
473
  const review = reviewBoundary;
466
474
  const draft = latestAssistantDraft(ctx, review);
467
- const analyze = analysisRan ? "Analyze again" : "Analyze now";
468
- const toggle = automatic ? "Automatic Off" : "Automatic On";
469
- const choices = [
470
- ...(!activeRun && !candidate ? [analyze] : []),
471
- toggle,
472
- ...(candidate ? ["Show candidate", "Dismiss candidate"] : []),
473
- ...(draft ? ["Save latest Main draft"] : []),
474
- ];
475
- const selected = await ctx.ui.select("Prompt Creator", choices);
476
- if (!selected || menuBranch !== branchGeneration) return;
477
- if (selected === analyze) {
478
- startAnalysis(ctx, true);
475
+ const [action = "", ...values] = args.trim().split(/\s+/);
476
+ if (!action) {
477
+ if (candidate) showCandidate(ctx);
478
+ else if (activeRun) ctx.ui.notify("Prompt analysis is already running.", "info");
479
+ else if (draft) ctx.ui.notify("Run /promptor save to create the reviewed prompt.", "info");
480
+ else startAnalysis(ctx, true);
481
+ return;
482
+ }
483
+ if (action === "analyze" && values.length === 0) {
484
+ if (candidate) ctx.ui.notify("Show or dismiss the pending candidate first.", "warning");
485
+ else if (activeRun) ctx.ui.notify("Prompt analysis is already running.", "info");
486
+ else startAnalysis(ctx, true);
479
487
  return;
480
488
  }
481
- if (selected === toggle) {
482
- const next = !automatic;
489
+ if (action === "dismiss" && values.length === 0) {
490
+ if (!candidate) ctx.ui.notify("No prompt candidate is waiting.", "warning");
491
+ else {
492
+ candidate = undefined;
493
+ candidateNameHint = undefined;
494
+ clearWidget(ctx);
495
+ }
496
+ return;
497
+ }
498
+ if (action === "save" && values.length <= 1) {
499
+ if (!draft || !review) {
500
+ ctx.ui.notify("No reviewed Main draft is ready to save.", "warning");
501
+ return;
502
+ }
503
+ await saveLatestDraft(draft, values[0], review, commandBranch, ctx);
504
+ return;
505
+ }
506
+ if (action === "automatic" && values.length === 1 && (values[0] === "on" || values[0] === "off")) {
507
+ const next = values[0] === "on";
483
508
  try {
484
509
  await configStore.save({ automatic: next, inputThreshold });
485
510
  automatic = next;
@@ -489,29 +514,7 @@ export default function promptCreatorExtension(pi: ExtensionAPI, options: Prompt
489
514
  }
490
515
  return;
491
516
  }
492
- if (selected === "Show candidate" && candidate) {
493
- const shown = candidate;
494
- const boundaryId = ctx.sessionManager.getLeafId();
495
- candidate = undefined;
496
- candidateNameHint = shown.name;
497
- clearWidget(ctx);
498
- pi.sendMessage({
499
- customType: CANDIDATE_MESSAGE_TYPE,
500
- content: candidateMessage(shown),
501
- display: true,
502
- }, { triggerTurn: false });
503
- reviewBoundary = boundaryId ? { entryId: boundaryId } : undefined;
504
- return;
505
- }
506
- if (selected === "Dismiss candidate" && candidate) {
507
- candidate = undefined;
508
- candidateNameHint = undefined;
509
- clearWidget(ctx);
510
- return;
511
- }
512
- if (selected === "Save latest Main draft" && draft && review) {
513
- await saveLatestDraft(draft, review, menuBranch, ctx);
514
- }
517
+ ctx.ui.notify("Use /promptor, /promptor analyze, /promptor dismiss, /promptor save [name], or /promptor automatic <on|off>.", "warning");
515
518
  },
516
519
  });
517
520
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@henryqw/pi-prompt-creator",
3
- "version": "1.0.6",
3
+ "version": "1.1.1",
4
4
  "description": "Turn current Pi conversation signals into user-approved prompt templates.",
5
5
  "keywords": [
6
6
  "pi-package",