pi-native-output-styles 0.4.0 → 0.5.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/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.0
4
+
5
+ One command, plus a bundled style and an agent that maintains styles.
6
+
7
+ ### Added
8
+
9
+ - `/output-style <request>` hands the task to an agent that reviews, rewrites, or creates styles. The brief is short Markdown in `extensions/prompts/output-style-leader.md`, and the agent decides for itself whether to fan work out to other agents.
10
+ - Bundled `caveman` style, from [carlosduplar/caveman-output-style-claude-code](https://github.com/carlosduplar/caveman-output-style-claude-code) (MIT, © 2026 Carlos Mello).
11
+
12
+ ### Changed
13
+
14
+ - **`/style` is gone; `/output-style` is the only command.** A single word that names a style, `off`, or `none` is style management; anything else is a request for the agent. `/output-style` with no arguments lists what exists.
15
+ - Style names are matched case-insensitively and resolved to their declared name.
16
+
3
17
  ## 0.4.0
4
18
 
5
19
  Repackaged as `pi-native-output-styles` — a Pi-only output-style switcher built on Pi's native `.pi/` directories.
package/LICENSE CHANGED
@@ -2,6 +2,8 @@ MIT License
2
2
 
3
3
  Copyright (c) 2026 LoneExile
4
4
  Copyright (c) 2026 code-koan contributors
5
+ The bundled `caveman` output style is Copyright (c) 2026 Carlos Mello
6
+ (https://github.com/carlosduplar/caveman-output-style-claude-code), MIT.
5
7
 
6
8
  Permission is hereby granted, free of charge, to any person obtaining a copy
7
9
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  [![npm version](https://img.shields.io/npm/v/pi-native-output-styles.svg)](https://www.npmjs.com/package/pi-native-output-styles)
5
5
  [![license](https://img.shields.io/npm/l/pi-native-output-styles.svg)](./LICENSE)
6
6
 
7
- Named, swappable system-prompt styles for [Pi](https://pi.dev) with a live `/style` switcher. Unlike Claude Code's output styles (which need `/clear` to switch), styles here apply and switch **live, mid-session**.
7
+ Named, swappable system-prompt styles for [Pi](https://pi.dev), plus an agent that can review, rewrite, and create them for you. Unlike Claude Code's output styles (which need `/clear` to switch), styles here apply and switch **live, mid-session**.
8
8
 
9
9
  Styles and saved defaults live in Pi's own directories: `~/.pi/agent/output-styles/` and `<repo>/.pi/output-styles/`.
10
10
 
@@ -19,17 +19,31 @@ pi install git:github.com/code-koan/pi-native-output-styles
19
19
 
20
20
  Then start a **new** session. Extensions do not hot-reload.
21
21
 
22
- ## Use
22
+ ## One command
23
23
 
24
- - `/style` show the active style and list the available ones.
25
- - `/style <name>` — activate a style for this session.
26
- - `/style <name> --save` — also save it as your personal (user) default.
27
- - `/style <name> --project` — save it as the project default (committed with the repo).
28
- - `/style off` — clear the active style for this session, overriding any saved default.
29
- - `/style off --save` / `/style off --project` — also clear the saved default. `none` is an alias for `off`.
30
- - While composing `/style`, a hint line below the input shows the available flags.
24
+ `/output-style` does both jobs. What you type decides which one you get.
31
25
 
32
- The style is applied every turn, and the status line shows `style: eli5`.
26
+ **Switch styles** the first word is a style name, `off`, or nothing:
27
+
28
+ ```text
29
+ /output-style list styles and show the active one
30
+ /output-style caveman use caveman for this session
31
+ /output-style concise --save and make it your default
32
+ /output-style concise --project save it as this project's default
33
+ /output-style off clear it; --save / --project also clears the default
34
+ ```
35
+
36
+ **Ask the agent to work on a style** — anything else:
37
+
38
+ ```text
39
+ /output-style review the reviewer style
40
+ /output-style 重写这个 output style,让它更简洁、更适合编程
41
+ /output-style 创建一个适合代码 Review 的 output style
42
+ ```
43
+
44
+ The rule is one line: *a single word that names a style, `off`, or `none` is management; anything else is a request for the agent.* So `/output-style concise` activates, and `/output-style rewrite concise` asks. To review a style whose name you would otherwise activate, say more than its name.
45
+
46
+ While composing the command, a hint line under the editor shows both forms.
33
47
 
34
48
  ## How a style is applied
35
49
 
@@ -37,9 +51,23 @@ Pi's assembled system prompt is flat prose plus XML blocks — there is no perso
37
51
 
38
52
  It is idempotent: a prompt that already carries the marker is left untouched, so switching styles mid-session never stacks a second block. Nothing else in the prompt is modified.
39
53
 
54
+ ## The style agent
55
+
56
+ `/output-style <request>` hands the task to the agent with a short brief and the live picture: which style is active, both writable directories, and every style that exists with its tier and file path.
57
+
58
+ The agent owns the task end to end. It decides for itself whether the work needs other agents — a review is the usual case, where prompt quality, responsibility boundaries, and conflicts/redundancy are worth splitting across children. It uses whatever delegation tool the session has, and does the analysis itself when there is none. The final write and the summary stay with it.
59
+
60
+ Review findings are specific: conflicting instructions, duplicate or unenforceable rules, vague wording, over-constraining the model, content that belongs to a different concern, AI-slop voice, and rules that are hard to follow while actually working. Rewrites keep what works, delete what does not, and add only what is missing.
61
+
62
+ A review reports; it does not edit. The file is written only when the request asks for a change. In observed runs the agent delegated on its own when a task had several independent angles to check, and worked solo on a small single-file review.
63
+
64
+ The brief lives in [`extensions/prompts/output-style-leader.md`](extensions/prompts/output-style-leader.md) and is plain Markdown — edit it without touching code.
65
+
40
66
  ## Bundled styles
41
67
 
42
- `concise` · `explanatory` · `teacher` · `reviewer` · `diagrams-first` · `ste` · `eli5`
68
+ `caveman` · `concise` · `explanatory` · `teacher` · `reviewer` · `diagrams-first` · `ste` · `eli5`
69
+
70
+ `caveman` is [Carlos Mello's Caveman output style](https://github.com/carlosduplar/caveman-output-style-claude-code) — terse replies, no filler, same technical signal. The same repo also ships a more aggressive `caveman-ultra`; it is not bundled, but you can drop it into your own `output-styles/` directory unchanged.
43
71
 
44
72
  `ste` writes in [ASD-STE100](https://asd-ste100.org) Simplified Technical English, with the v2.0 action-first reply shape for person-addressed replies, tasks, issues, pull request descriptions, and commit messages. Adapted from [Ege Chelebi's ste-writing skill](https://github.com/woosal1337/blog/blob/9240b25eac013467554fd8217f319743aa0282b8/videos/ep01-the-cure-for-ai-slop/asd-ste100/SKILL.md).
45
73
 
@@ -60,12 +88,12 @@ description: Teach as you go
60
88
  Act as a patient teacher. Explain the concept before applying it.
61
89
  ```
62
90
 
63
- The body becomes the `# Personality` block.
91
+ `name` is lowercase kebab-case, `description` is one line and shows up in `/output-style`.
64
92
 
65
93
  Precedence:
66
94
 
67
95
  - **Definitions** (low → high): bundled < user < project.
68
- - **Which style is active**: session `/style` > user default > project default.
96
+ - **Which style is active**: session `/output-style` > user default > project default.
69
97
 
70
98
  ## Config
71
99
 
@@ -91,4 +119,4 @@ bun x tsc --noEmit
91
119
 
92
120
  ## Credits
93
121
 
94
- Started as a fork of [LoneExile/pi-output-styles](https://github.com/LoneExile/pi-output-styles), reworked to use Pi's native `.pi/` directories. MIT licensed; original work © 2026 LoneExile.
122
+ Started as a fork of [LoneExile/pi-output-styles](https://github.com/LoneExile/pi-output-styles). Bundles [caveman](https://github.com/carlosduplar/caveman-output-style-claude-code) by Carlos Mello. MIT licensed; original work © 2026 LoneExile, caveman style © 2026 Carlos Mello.
@@ -11,6 +11,21 @@ export interface Style {
11
11
  name: string;
12
12
  description: string;
13
13
  body: string;
14
+ /** Absolute file path; set by discoverStyles. */
15
+ path?: string;
16
+ }
17
+
18
+ /** Where a style definition was found. Only `project` and `user` are writable. */
19
+ export type StyleTier = "bundled" | "user" | "project";
20
+
21
+ export interface StyleSource {
22
+ dir: string;
23
+ tier: StyleTier;
24
+ }
25
+
26
+ export interface StyleEntry extends Style {
27
+ tier: StyleTier;
28
+ path: string;
14
29
  }
15
30
 
16
31
  type NotifyType = "info" | "warning" | "error";
@@ -25,6 +40,7 @@ interface ExtensionUI {
25
40
  interface ExtensionContext {
26
41
  cwd: string;
27
42
  hasUI: boolean;
43
+ isIdle?(): boolean;
28
44
  ui: ExtensionUI;
29
45
  setInterval?(callback: () => void, ms?: number): unknown;
30
46
  }
@@ -59,6 +75,7 @@ interface ExtensionAPI {
59
75
  handler: (args: string, ctx: ExtensionContext) => void | Promise<void>;
60
76
  },
61
77
  ): void;
78
+ sendUserMessage(content: string, options?: { deliverAs?: "steer" | "followUp" }): void;
62
79
  }
63
80
 
64
81
  export function parseStyle(text: string, fallbackName: string): Style {
@@ -99,7 +116,7 @@ export function discoverStyles(dirsLowToHigh: string[]): Map<string, Style> {
99
116
  }
100
117
  const style = parseStyle(text, entry.slice(0, -3));
101
118
  if (style.body.length === 0) continue;
102
- styles.set(style.name, style);
119
+ styles.set(style.name, { ...style, path: join(dir, entry) });
103
120
  }
104
121
  }
105
122
  return styles;
@@ -207,20 +224,46 @@ export function parseStyleCommandArgs(args: string): StyleCommandArgs {
207
224
  return { name, persist };
208
225
  }
209
226
 
227
+ // One command serves both jobs, so the split has to be guessable from the
228
+ // words alone. Rule: the request is style management only when it is empty,
229
+ // or its single non-flag word is `off`/`none` or a style that exists. Anything
230
+ // else is a task for the agent. `/output-style concise` activates; `/output-style
231
+ // rewrite concise` asks the agent.
232
+ export type StyleCommandRoute = { kind: "manage" } | { kind: "task"; request: string };
233
+
234
+ export function routeStyleCommand(args: string, styleNames: Iterable<string>): StyleCommandRoute {
235
+ const request = args.trim();
236
+ if (request.length === 0) return { kind: "manage" };
237
+ const words = request.split(/\s+/).filter(t => t.length > 0 && !t.startsWith("--"));
238
+ if (words.length === 0) return { kind: "manage" }; // flags only
239
+ if (words.length > 1) return { kind: "task", request };
240
+ const word = words[0].toLowerCase();
241
+ if (OFF_WORDS[word]) return { kind: "manage" };
242
+ const known = new Set([...styleNames].map(n => n.toLowerCase()));
243
+ return known.has(word) ? { kind: "manage" } : { kind: "task", request };
244
+ }
245
+
246
+ // Routing matches names case-insensitively, but the style map is keyed by the
247
+ // exact declared name, so the selected name has to be canonicalised before use.
248
+ export function resolveStyleName(name: string, styleNames: Iterable<string>): string | null {
249
+ const all = [...styleNames];
250
+ return all.find(n => n === name) ?? all.find(n => n.toLowerCase() === name.toLowerCase()) ?? null;
251
+ }
252
+
210
253
  const STATUS_KEY = "output-styles";
211
254
  const HINT_KEY = "output-styles-hint";
212
- // Persistent ghost hint shown below the editor while a `/style` command is
213
- // being composed. Pi only renders inline usage ghost text for builtin
214
- // commands, so this widget carries the same message for extension commands.
255
+ // Persistent ghost hint shown below the editor while `/output-style` is being
256
+ // composed. Pi only renders inline usage ghost text for builtin commands, so
257
+ // this widget carries the same message for extension commands.
215
258
  const STYLE_HINT_LINES = [
216
- "/style <name|off> [--save] [--project]",
217
- "persist: --save (user default, --global alias) · --project (this project)",
259
+ "/output-style <name|off> [--save] [--project]",
260
+ "/output-style <ask the agent to review, rewrite, or create a style>",
218
261
  ];
219
262
 
220
- // Pure matcher for the widget: show the hint while the input starts with a
221
- // `/style` command word (line start, with optional leading whitespace).
263
+ // Pure matcher for the widget: show the hint while the input starts with the
264
+ // `/output-style` command word (line start, optional leading whitespace).
222
265
  export function styleHintFor(text: string): string[] | null {
223
- return /^\s*\/style(?:\s|$)/.test(text) ? STYLE_HINT_LINES : null;
266
+ return /^\s*\/output-style(?:\s|$)/.test(text) ? STYLE_HINT_LINES : null;
224
267
  }
225
268
 
226
269
  // Poller state: one started flag guards re-registration across in-process
@@ -259,9 +302,29 @@ export function startHintPoller(ctx: ExtensionContext): void {
259
302
  type SessionSelection = { type: "inherit" } | { type: "off" } | { type: "style"; name: string };
260
303
  let session: SessionSelection = { type: "inherit" };
261
304
 
262
- function styleDirs(cwd: string): string[] {
305
+ export function styleSources(cwd: string): StyleSource[] {
263
306
  // low → high precedence: bundled < user < project
264
- return [bundledStylesDir(), userStylesDir(), projectStylesDir(cwd)];
307
+ return [
308
+ { dir: bundledStylesDir(), tier: "bundled" },
309
+ { dir: userStylesDir(), tier: "user" },
310
+ { dir: projectStylesDir(cwd), tier: "project" },
311
+ ];
312
+ }
313
+
314
+ // One row per style name: the winning definition plus the file that actually
315
+ // produced it, so a rewrite knows whether it may edit in place.
316
+ export function styleCatalog(cwd: string): StyleEntry[] {
317
+ const byName = new Map<string, StyleEntry>();
318
+ for (const { dir, tier } of styleSources(cwd)) {
319
+ for (const style of discoverStyles([dir]).values()) {
320
+ if (style.path) byName.set(style.name, { ...style, tier, path: style.path });
321
+ }
322
+ }
323
+ return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
324
+ }
325
+
326
+ function styleDirs(cwd: string): string[] {
327
+ return styleSources(cwd).map(s => s.dir);
265
328
  }
266
329
 
267
330
  // Argument completions for `/style <name>`: matches style names by prefix.
@@ -302,6 +365,37 @@ function refreshStatus(ctx: ExtensionContext, style: Style | null): void {
302
365
  ctx.ui.setStatus(STATUS_KEY, style ? `style: ${style.name}` : undefined);
303
366
  }
304
367
 
368
+ // The Leader brief is plain Markdown next to the extension, so its wording can
369
+ // be edited without touching code.
370
+ export function leaderBrief(): string {
371
+ return readFileSync(join(dirname(fileURLToPath(import.meta.url)), "prompts", "output-style-leader.md"), "utf8").trim();
372
+ }
373
+
374
+ // The brief stays fixed and short. What changes per run is the live picture:
375
+ // which style is active, where styles may be written, and what already exists.
376
+ // The user's words are passed through untouched — this is a router, not a form.
377
+ export function buildStyleTask(brief: string, cwd: string, active: Style | null, request: string): string {
378
+ const catalog = styleCatalog(cwd);
379
+ return [
380
+ brief,
381
+ "",
382
+ "## Context",
383
+ "",
384
+ `Active style: ${active?.name ?? "(none)"}`,
385
+ `Project style dir: ${projectStylesDir(cwd)}`,
386
+ `User style dir: ${userStylesDir()}`,
387
+ "",
388
+ "Available styles:",
389
+ ...(catalog.length
390
+ ? catalog.map(s => `- ${s.name} [${s.tier}] ${s.path}${s.description ? ` — ${s.description}` : ""}`)
391
+ : ["(none)"]),
392
+ "",
393
+ "## Request",
394
+ "",
395
+ request,
396
+ ].join("\n");
397
+ }
398
+
305
399
  export default function outputStyles(pi: ExtensionAPI): void {
306
400
  pi.on("session_start", (_event, ctx) => {
307
401
  refreshStatus(ctx, resolveActiveStyle(ctx.cwd));
@@ -332,13 +426,30 @@ export default function outputStyles(pi: ExtensionAPI): void {
332
426
  }
333
427
  });
334
428
 
335
- pi.registerCommand("style", {
429
+ pi.registerCommand("output-style", {
336
430
  description:
337
- "Select an output style (injected as the # Personality block of the system prompt), or clear it. Usage: /style [name|off] [--save] [--project]",
431
+ "Select an output style, or ask the agent to review, rewrite, or create one. Usage: /output-style <name|off|what you want> [--save] [--project]",
338
432
  getArgumentCompletions: argumentPrefix => styleCompletions(argumentPrefix, process.cwd()),
339
433
  handler: (args, ctx) => {
340
- const { name, persist } = parseStyleCommandArgs(args);
341
434
  const styles = discoverStyles(styleDirs(ctx.cwd));
435
+ const route = routeStyleCommand(args, styles.keys());
436
+
437
+ if (route.kind === "task") {
438
+ let task: string;
439
+ try {
440
+ task = buildStyleTask(leaderBrief(), ctx.cwd, resolveActiveStyle(ctx.cwd, styles), route.request);
441
+ } catch (err) {
442
+ ctx.ui.notify(`Could not build the output-style task: ${String(err)}`, "error");
443
+ return;
444
+ }
445
+ // Mid-stream the delivery mode is required; otherwise the message goes
446
+ // out immediately and triggers the turn.
447
+ pi.sendUserMessage(task, ctx.isIdle?.() === false ? { deliverAs: "followUp" } : undefined);
448
+ return;
449
+ }
450
+
451
+ const { name: requested, persist } = parseStyleCommandArgs(args);
452
+ const name = requested === null ? null : (resolveStyleName(requested, styles.keys()) ?? requested);
342
453
  const available = [...styles.keys()].sort().join(", ") || "(none)";
343
454
 
344
455
  const unknownFlags = args
@@ -355,7 +466,11 @@ export default function outputStyles(pi: ExtensionAPI): void {
355
466
  .sort((a, b) => a.name.localeCompare(b.name))
356
467
  .map(s => (s.description ? `${s.name} — ${s.description}` : s.name))
357
468
  .join("\n");
358
- ctx.ui.notify(`Active style: ${current?.name ?? "(none)"}\nAvailable:\n${listing || "(none)"}`, "info");
469
+ ctx.ui.notify(
470
+ `Active style: ${current?.name ?? "(none)"}\nAvailable:\n${listing || "(none)"}\n\n` +
471
+ "/output-style <name|off> [--save] [--project] to switch, or /output-style <request> to have the agent review, rewrite, or create a style.",
472
+ "info",
473
+ );
359
474
  return;
360
475
  }
361
476
  if (OFF_WORDS[name.toLowerCase()]) {
@@ -0,0 +1,71 @@
1
+ # Output style task
2
+
3
+ You own this end to end: read the request, decide what work it needs, do it,
4
+ then verify what you produced. Finish with a short report of what changed and
5
+ where.
6
+
7
+ ## What an output style is
8
+
9
+ A style defines **how** you behave: voice, structure, level of detail,
10
+ interaction style. It is not a home for project architecture, coding rules,
11
+ domain knowledge, tooling conventions, or repo facts. Anything like that is a
12
+ responsibility-boundary bug: drop it, or say where it belongs instead.
13
+
14
+ ## Delegation
15
+
16
+ Decide for yourself whether the task needs other agents. Delegate only when
17
+ independent angles genuinely improve the result. A review is the usual case:
18
+ prompt quality, responsibility boundaries, and conflicts/redundancy are worth
19
+ splitting. Use the `subagent` tool when it is available, and run the children
20
+ in one workflow call. If it is not available, do the analysis yourself.
21
+ Children report findings back to you and never write files. You do the final
22
+ write and the final summary — never hand the file off.
23
+
24
+ ## Reviewing
25
+
26
+ Review means report, not edit. Do not change the file unless the request also
27
+ asks for the change; offer the fix instead.
28
+
29
+ Name each finding specifically: what is wrong, what it costs, and the fix.
30
+ Look for:
31
+
32
+ - instructions that conflict with each other
33
+ - duplicate, dead, or unenforceable rules
34
+ - vague wording that cannot be acted on
35
+ - over-constraining the model
36
+ - content that belongs to another concern
37
+ - AI-slop voice: inflated claims, filler, ceremony
38
+ - rules that are hard to follow while actually working
39
+
40
+ No praise padding, and do not restate the style back at the user.
41
+
42
+ ## Rewriting
43
+
44
+ Keep what works, delete what does not, add only what is missing. Fewer sharp
45
+ rules beat more rules. Do not add text to look thorough, and do not quietly
46
+ widen the scope.
47
+
48
+ ## Creating
49
+
50
+ Write the smallest style that achieves the goal. `name` is lowercase
51
+ kebab-case; `description` is one line and shows up in `/style`.
52
+
53
+ ## File format
54
+
55
+ ---
56
+ name: <kebab-case>
57
+ description: <one line>
58
+ ---
59
+ <style body>
60
+
61
+ ## Where to write
62
+
63
+ Default to the project directory. Write to the user directory only when asked
64
+ for personal or global. Never edit a style under a package install path — those
65
+ are read-only bundled styles; copy one out instead.
66
+
67
+ ## Before you finish
68
+
69
+ - Re-read what you wrote: frontmatter plus body, nothing else.
70
+ - Confirm the file parses and the `name` matches what `/style <name>` expects.
71
+ - State the path you wrote and what changed.
@@ -0,0 +1,11 @@
1
+ ---
2
+ name: caveman
3
+ description: Ultra-compact replies. Less fluff. Same technical signal.
4
+ ---
5
+
6
+ Smart caveman style: terse responses, keep technical substance, drop ALL fluff.
7
+ Lead with answer. Drop articles, filler, pleasantries, hedging, preamble, recap.
8
+ Fragments OK. Pattern: `[thing] [action] [reason]. [next step].`
9
+ Keep technical terms, code, commands, paths, JSON, errors unchanged.
10
+ Bullets/tables only when scan > prose.
11
+ Normal language for safety, irreversible actions, or confusion risk.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-native-output-styles",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Named, swappable system-prompt styles for Pi — live /style switcher with native .pi/ directory support.",
5
5
  "keywords": [
6
6
  "pi-package",