pi-skill-playbook 0.1.3 → 0.1.4

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 CHANGED
@@ -4,12 +4,12 @@ Pi Skill Playbook is a Pi extension that guides Agent Skill usage flows with vis
4
4
 
5
5
  MVP scope:
6
6
 
7
- - `/playbook list`
8
- - `/playbook start <playbook-id> [--run <name>]`
9
- - `/playbook resume <run-id>`
10
- - `/playbook status [run-id]`
11
- - `/playbook done`
12
- - `/playbook choose <outcome>`
7
+ - `/playbook:list`
8
+ - `/playbook:start <playbook-id> [--run <name>]`
9
+ - `/playbook:resume <run-id>`
10
+ - `/playbook:status [run-id]`
11
+ - `/playbook:done`
12
+ - `/playbook:choose <outcome>`
13
13
  - strict YAML validation
14
14
  - active run widget below the editor
15
15
  - marker-based auto advance for single-outcome steps
@@ -17,8 +17,8 @@ MVP scope:
17
17
 
18
18
  Deferred after scaffold:
19
19
 
20
- - `/playbook import-web`
21
- - `/playbook record`
20
+ - `/playbook:import-web`
21
+ - `/playbook:record`
22
22
 
23
23
  ## Install from npm
24
24
 
@@ -74,11 +74,11 @@ Add personal run state to the target repo's `.gitignore`:
74
74
  ## Use
75
75
 
76
76
  ```text
77
- /playbook list
78
- /playbook start feature-development --run my-feature
79
- /playbook done
80
- /playbook choose pass
81
- /playbook status
77
+ /playbook:list
78
+ /playbook:start feature-development --run my-feature
79
+ /playbook:done
80
+ /playbook:choose pass
81
+ /playbook:status
82
82
  ```
83
83
 
84
84
  The widget displays the current step, exact skill command, completion criteria, and outcome labels.
@@ -93,12 +93,14 @@ When a user explicitly runs the current step skill with `/skill:<name>`, Pi Skil
93
93
  PLAYBOOK_OUTCOME: ready-for-prd
94
94
  ```
95
95
 
96
- If the current step has exactly one outcome, a valid marker advances the run automatically. If the step has multiple outcomes, the marker is shown as a suggestion and the user must confirm with `/playbook choose <outcome>`.
96
+ If the current step has exactly one outcome, a valid marker advances the run automatically. If the step has multiple outcomes, the marker is shown as a suggestion and the user must confirm with `/playbook:choose <outcome>`.
97
+
98
+ Legacy space-separated forms such as `/playbook start` remain available for one release.
97
99
 
98
100
  Optional playbook setting:
99
101
 
100
102
  ```yaml
101
103
  autoAdvance: auto # default: marker can advance single-outcome steps
102
- autoAdvance: suggest # marker only suggests /playbook done or /playbook choose
104
+ autoAdvance: suggest # marker only suggests /playbook:done or /playbook:choose
103
105
  autoAdvance: off # no prompt injection or completion detection
104
106
  ```
@@ -9,6 +9,30 @@ import type { LoadedPlaybook, PlaybookRunState } from "../src/types.js";
9
9
 
10
10
  const WIDGET_ID = "pi-skill-playbook";
11
11
 
12
+ const COMMANDS = [
13
+ ["list", "list available playbooks"],
14
+ ["start", "start a playbook run"],
15
+ ["resume", "resume an active playbook run"],
16
+ ["status", "show playbook run status"],
17
+ ["done", "complete the current step"],
18
+ ["choose", "choose a step outcome"],
19
+ ["cancel", "cancel an active playbook run"],
20
+ ] as const;
21
+
22
+ const COLON_COMMAND_ALIASES = COMMANDS.map(([command, description]) => ({
23
+ name: `playbook:${command}`,
24
+ command,
25
+ description,
26
+ }));
27
+
28
+ const COLON_COMPLETION_COMMANDS = new Set(["start", "resume", "status", "cancel", "choose"]);
29
+
30
+ type CommandContext = {
31
+ cwd: string;
32
+ hasUI: boolean;
33
+ ui?: UiLike;
34
+ };
35
+
12
36
  export default function piSkillPlaybook(pi: ExtensionAPI) {
13
37
  let completionCwd = process.cwd();
14
38
  let pendingSkillInvocation: string | undefined;
@@ -65,44 +89,70 @@ export default function piSkillPlaybook(pi: ExtensionAPI) {
65
89
  handler: async (args, ctx) => {
66
90
  const parsed = parseArgs(args);
67
91
  const command = parsed.shift() ?? "status";
68
-
69
92
  try {
70
- switch (command) {
71
- case "list":
72
- await listPlaybooks(pi, ctx.cwd, ctx.hasUI ? ctx.ui : undefined);
73
- return;
74
- case "start":
75
- await startPlaybook(pi, ctx.cwd, parsed, ctx.hasUI ? ctx.ui : undefined);
76
- return;
77
- case "resume":
78
- await resumeRun(ctx.cwd, parsed, ctx.hasUI ? ctx.ui : undefined);
79
- return;
80
- case "status":
81
- await showStatus(ctx.cwd, parsed[0], ctx.hasUI ? ctx.ui : undefined);
82
- return;
83
- case "done":
84
- await completeCurrentStep(ctx.cwd, ctx.hasUI ? ctx.ui : undefined);
85
- return;
86
- case "choose":
87
- await chooseOutcome(ctx.cwd, parsed[0], ctx.hasUI ? ctx.ui : undefined);
88
- return;
89
- case "cancel":
90
- case "stop":
91
- case "abort":
92
- await cancelRun(ctx.cwd, parsed[0], ctx.hasUI ? ctx.ui : undefined);
93
- return;
94
- case "import-web":
95
- case "record":
96
- notify(ctx.hasUI ? ctx.ui : undefined, `/${command} is deferred after the Core 6 MVP scaffold.` , "warning");
97
- return;
98
- default:
99
- notify(ctx.hasUI ? ctx.ui : undefined, usage(), "error");
100
- }
93
+ await handlePlaybookCommand(pi, command, parsed, ctx);
101
94
  } catch (error) {
102
95
  notify(ctx.hasUI ? ctx.ui : undefined, error instanceof Error ? error.message : String(error), "error");
103
96
  }
104
97
  },
105
98
  });
99
+
100
+ for (const alias of COLON_COMMAND_ALIASES) {
101
+ pi.registerCommand(alias.name, {
102
+ description: `Playbook: ${alias.description}. Alias for /playbook ${alias.command}.`,
103
+ ...(COLON_COMPLETION_COMMANDS.has(alias.command)
104
+ ? { getArgumentCompletions: (prefix) => getPlaybookColonArgumentCompletions(completionCwd, alias.command, prefix) }
105
+ : {}),
106
+ handler: async (args, ctx) => {
107
+ try {
108
+ await handlePlaybookCommand(pi, alias.command, parseArgs(args), ctx);
109
+ } catch (error) {
110
+ notify(ctx.hasUI ? ctx.ui : undefined, error instanceof Error ? error.message : String(error), "error");
111
+ }
112
+ },
113
+ });
114
+ }
115
+ }
116
+
117
+ async function handlePlaybookCommand(
118
+ pi: ExtensionAPI,
119
+ command: string,
120
+ args: string[],
121
+ ctx: CommandContext,
122
+ ): Promise<void> {
123
+ const ui = ctx.hasUI ? ctx.ui : undefined;
124
+
125
+ switch (command) {
126
+ case "list":
127
+ await listPlaybooks(pi, ctx.cwd, ui);
128
+ return;
129
+ case "start":
130
+ await startPlaybook(pi, ctx.cwd, args, ui);
131
+ return;
132
+ case "resume":
133
+ await resumeRun(ctx.cwd, args, ui);
134
+ return;
135
+ case "status":
136
+ await showStatus(ctx.cwd, args[0], ui);
137
+ return;
138
+ case "done":
139
+ await completeCurrentStep(ctx.cwd, ui);
140
+ return;
141
+ case "choose":
142
+ await chooseOutcome(ctx.cwd, args[0], ui);
143
+ return;
144
+ case "cancel":
145
+ case "stop":
146
+ case "abort":
147
+ await cancelRun(ctx.cwd, args[0], ui);
148
+ return;
149
+ case "import-web":
150
+ case "record":
151
+ notify(ui, `/playbook:${command} is deferred after the Core 6 MVP scaffold.`, "warning");
152
+ return;
153
+ default:
154
+ notify(ui, usage(), "error");
155
+ }
106
156
  }
107
157
 
108
158
  async function listPlaybooks(pi: ExtensionAPI, cwd: string, ui: UiLike | undefined): Promise<void> {
@@ -125,7 +175,7 @@ async function listPlaybooks(pi: ExtensionAPI, cwd: string, ui: UiLike | undefin
125
175
 
126
176
  async function startPlaybook(pi: ExtensionAPI, cwd: string, args: string[], ui: UiLike | undefined): Promise<void> {
127
177
  const playbookId = args[0];
128
- if (!playbookId) throw new Error("Usage: /playbook start <playbook-id> [--run <name>]");
178
+ if (!playbookId) throw new Error("Usage: /playbook:start <playbook-id> [--run <name>]");
129
179
 
130
180
  const playbook = await findPlaybook(cwd, playbookId);
131
181
  if (!playbook) throw new Error(`Playbook '${playbookId}' not found in .pi/playbooks/.`);
@@ -157,7 +207,7 @@ async function startPlaybook(pi: ExtensionAPI, cwd: string, args: string[], ui:
157
207
 
158
208
  async function resumeRun(cwd: string, args: string[], ui: UiLike | undefined): Promise<void> {
159
209
  const runId = args[0];
160
- if (!runId) throw new Error("Usage: /playbook resume <run-id>");
210
+ if (!runId) throw new Error("Usage: /playbook:resume <run-id>");
161
211
  const run = await loadRun(cwd, runId);
162
212
  if (!run) throw new Error(`Run '${runId}' not found.`);
163
213
  if (run.status !== "active") throw new Error(`Run '${runId}' is ${run.status}.`);
@@ -170,7 +220,7 @@ async function resumeRun(cwd: string, args: string[], ui: UiLike | undefined): P
170
220
 
171
221
  async function cancelRun(cwd: string, explicitRunId: string | undefined, ui: UiLike | undefined): Promise<void> {
172
222
  const runId = explicitRunId ?? (await loadActiveRunId(cwd));
173
- if (!runId) throw new Error("Usage: /playbook cancel [run-id]");
223
+ if (!runId) throw new Error("Usage: /playbook:cancel [run-id]");
174
224
  const run = await loadRun(cwd, runId);
175
225
  if (!run) throw new Error(`Run '${runId}' not found.`);
176
226
 
@@ -231,7 +281,7 @@ async function completeCurrentStep(cwd: string, ui: UiLike | undefined): Promise
231
281
  }
232
282
 
233
283
  async function chooseOutcome(cwd: string, outcome: string | undefined, ui: UiLike | undefined): Promise<void> {
234
- if (!outcome) throw new Error("Usage: /playbook choose <outcome>");
284
+ if (!outcome) throw new Error("Usage: /playbook:choose <outcome>");
235
285
  const { run, playbook } = await loadActive(cwd);
236
286
  await advanceRun(cwd, playbook, run, outcome, ui);
237
287
  }
@@ -333,13 +383,14 @@ function notify(ui: UiLike | undefined, message: string, level: "info" | "warnin
333
383
  function usage(): string {
334
384
  return [
335
385
  "Usage:",
336
- "/playbook list",
337
- "/playbook start <playbook-id> [--run <name>]",
338
- "/playbook resume <run-id>",
339
- "/playbook status [run-id]",
340
- "/playbook done",
341
- "/playbook choose <outcome>",
342
- "/playbook cancel [run-id]",
386
+ "/playbook:list",
387
+ "/playbook:start <playbook-id> [--run <name>]",
388
+ "/playbook:resume <run-id>",
389
+ "/playbook:status [run-id]",
390
+ "/playbook:done",
391
+ "/playbook:choose <outcome>",
392
+ "/playbook:cancel [run-id]",
393
+ "Legacy space-separated /playbook <subcommand> forms remain available for compatibility.",
343
394
  ].join("\n");
344
395
  }
345
396
 
@@ -353,6 +404,26 @@ export async function getPlaybookArgumentCompletions(cwd: string, prefix: string
353
404
  }
354
405
  }
355
406
 
407
+ export async function getPlaybookColonArgumentCompletions(
408
+ cwd: string,
409
+ command: string,
410
+ prefix: string,
411
+ ): Promise<CompletionItem[] | null> {
412
+ try {
413
+ const normalizedPrefix = prefix.trimStart();
414
+ const legacyPrefix = normalizedPrefix ? `${command} ${normalizedPrefix}` : command;
415
+ const items = await getPlaybookArgumentCompletionsUnsafe(cwd, legacyPrefix);
416
+ if (!items) return null;
417
+ const legacyToken = `${command} `;
418
+ return items.map((item) => ({
419
+ ...item,
420
+ value: item.value.startsWith(legacyToken) ? item.value.slice(legacyToken.length) : item.value,
421
+ }));
422
+ } catch {
423
+ return null;
424
+ }
425
+ }
426
+
356
427
  async function getPlaybookArgumentCompletionsUnsafe(cwd: string, prefix: string): Promise<CompletionItem[] | null> {
357
428
  const parsed = parseCompletionPrefix(prefix);
358
429
  const command = parsed.command;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-skill-playbook",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "description": "Pi extension for passive, human-mediated Agent Skill playbooks.",
6
6
  "keywords": ["pi-package", "pi-extension", "agent-skills", "playbook"],
@@ -91,7 +91,7 @@ export function planCompletion(
91
91
  kind: "suggest",
92
92
  outcome: resolved.outcome,
93
93
  to: resolved.to,
94
- message: `Completion marked for step '${run.currentStep}'. Confirm outcome: /playbook choose ${resolved.outcome}`,
94
+ message: `Completion marked for step '${run.currentStep}'. Confirm outcome: /playbook:choose ${resolved.outcome}`,
95
95
  };
96
96
  }
97
97
 
@@ -101,8 +101,8 @@ export function planCompletion(
101
101
  outcome: resolved.outcome,
102
102
  to: resolved.to,
103
103
  message: resolved.outcome === "complete"
104
- ? `Completion marked for final step '${run.currentStep}'. Run /playbook done to complete.`
105
- : `Completion marked for step '${run.currentStep}'. Run /playbook done to advance to '${resolved.to}'.`,
104
+ ? `Completion marked for final step '${run.currentStep}'. Run /playbook:done to complete.`
105
+ : `Completion marked for step '${run.currentStep}'. Run /playbook:done to advance to '${resolved.to}'.`,
106
106
  };
107
107
  }
108
108
 
@@ -129,7 +129,7 @@ export function lastAssistantText(messages: unknown[]): string {
129
129
 
130
130
  function suggestPlan(step: PlaybookStep, stepId: string): CompletionPlan {
131
131
  if (step.transitions.length === 0) {
132
- return { kind: "suggest", outcome: "complete", to: "complete", message: `Completion suspected for final step '${stepId}'. Run /playbook done to complete.` };
132
+ return { kind: "suggest", outcome: "complete", to: "complete", message: `Completion suspected for final step '${stepId}'. Run /playbook:done to complete.` };
133
133
  }
134
134
  if (step.transitions.length === 1) {
135
135
  const transition = step.transitions[0]!;
@@ -137,12 +137,12 @@ function suggestPlan(step: PlaybookStep, stepId: string): CompletionPlan {
137
137
  kind: "suggest",
138
138
  outcome: transition.outcome,
139
139
  to: transition.to,
140
- message: `Completion suspected for step '${stepId}'. Run /playbook done to advance to '${transition.to}'.`,
140
+ message: `Completion suspected for step '${stepId}'. Run /playbook:done to advance to '${transition.to}'.`,
141
141
  };
142
142
  }
143
143
  return {
144
144
  kind: "suggest",
145
- message: `Completion suspected for step '${stepId}'. Choose outcome: ${step.transitions.map((transition) => `/playbook choose ${transition.outcome}`).join(" | ")}`,
145
+ message: `Completion suspected for step '${stepId}'. Choose outcome: ${step.transitions.map((transition) => `/playbook:choose ${transition.outcome}`).join(" | ")}`,
146
146
  };
147
147
  }
148
148