@mcuste/pi-diagram 0.0.0 → 0.2.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 CHANGED
@@ -16,8 +16,7 @@ If any of those names are new to you:
16
16
 
17
17
  > **Status: images, text, and files work.** In a terminal that supports inline images the diagram
18
18
  > itself is shown; everywhere else it is box drawing or plain ASCII. Files land outside the
19
- > repository unless a destination is named. The design being followed is
20
- > [docs/terminal_diagram_tool_proposal.md](docs/terminal_diagram_tool_proposal.md).
19
+ > repository unless a destination is named.
21
20
 
22
21
  ## Why
23
22
 
@@ -124,6 +123,19 @@ language.
124
123
  | `formats` | Files to produce: `source`, `svg`, `png`, `txt`. Written outside the repository |
125
124
  | `save` | Also copy them into the repository. `dir` is required |
126
125
 
126
+ ## When the model draws
127
+
128
+ A described tool gets called when the user asks for a diagram, not when a diagram is the clearer
129
+ answer. So the extension adds a short block to the host system prompt: draw when structure, flow,
130
+ or relationships are easier to see than to read, call the tool before explaining, keep the prose
131
+ to what the picture does not show, and never hand-write ASCII art or a Mermaid block. The block
132
+ also maps a question to a diagram, so components go to `architecture`, message order to a sequence
133
+ diagram, tables to `data`, a hierarchy to `tree`, and so on.
134
+
135
+ The result is fewer walls of text: an answer about how parts connect arrives as a picture with a
136
+ few lines around it. The block is appended to whatever the host built, is added once, and is left
137
+ out when the `diagram` tool is not active.
138
+
127
139
  ## What a profile changes
128
140
 
129
141
  The model says what a diagram is for, and the tool decides how it looks:
@@ -161,6 +173,12 @@ to forward the protocol: tmux needs `allow-passthrough`, and herdr needs
161
173
  The image never enters the model's context. It is written to a private temporary directory and
162
174
  read back when the row is displayed.
163
175
 
176
+ The row is bounded to 80 by 30 character cells, so a dense diagram is drawn small. Where the
177
+ terminal supports OSC 8 hyperlinks, the title above the diagram links to that file: click it and
178
+ the diagram opens in the image viewer of the machine, which zooms and pans. A diagram with no
179
+ title shows the linked file name under the image instead. Ghostty, Kitty, WezTerm, and iTerm2 all
180
+ open such a link, some of them on a modified click.
181
+
164
182
  D2 exports PNG by driving a headless browser it downloads on first use, which this tool will not
165
183
  do during a call. Instead the SVG it already produces is rasterized locally by
166
184
  [resvg](https://github.com/yisibl/resvg-js), which needs no browser and no network. Labels are
@@ -231,7 +249,6 @@ and a model given those knobs spends tokens on styling and produces a different
231
249
  ## Documentation
232
250
 
233
251
  - [Safety model](docs/safety.md)
234
- - [Design proposal](docs/terminal_diagram_tool_proposal.md)
235
252
  - [Development and release](docs/development.md)
236
253
  - [Changelog](CHANGELOG.md)
237
254
 
@@ -1 +1 @@
1
- {"version":3,"file":"display.d.ts","sourceRoot":"","sources":["../src/display.ts"],"names":[],"mappings":"AAgBA,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;CACzC;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACzC;AAED,UAAU,YAAY;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAiBD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAS9D;AAED,4EAA4E;AAC5E,wBAAgB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAY5C;AAED,kGAAkG;AAClG,wBAAgB,eAAe,IAAI,OAAO,GAAG,SAAS,CAErD;AAED,4FAA4F;AAC5F,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AAWD,MAAM,WAAW,eAAe;IAC9B,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,qEAAqE;IACrE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,uFAAuF;AACvF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,YAAY,GAAG,SAAS,CASvF;AAED,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,cAAc,GACtB,SAAS,CAuCX;AAUD,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,8EAA8E;IAC9E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,mFAAmF;IACnF,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC"}
1
+ {"version":3,"file":"display.d.ts","sourceRoot":"","sources":["../src/display.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;CACzC;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACzC;AAED,UAAU,YAAY;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAqBD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAS9D;AAED,4EAA4E;AAC5E,wBAAgB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAY5C;AAED,kGAAkG;AAClG,wBAAgB,eAAe,IAAI,OAAO,GAAG,SAAS,CAErD;AAED,4FAA4F;AAC5F,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AAWD,MAAM,WAAW,eAAe;IAC9B,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,qEAAqE;IACrE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,uFAAuF;AACvF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,YAAY,GAAG,SAAS,CASvF;AAED,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,cAAc,GACtB,SAAS,CA4CX;AAoBD,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,8EAA8E;IAC9E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,mFAAmF;IACnF,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC"}
package/dist/display.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { createRequire } from "node:module";
3
+ import { basename, isAbsolute } from "node:path";
3
4
  import { pathToFileURL } from "node:url";
4
5
  /**
5
6
  * Turns a diagram result into terminal components. Both the image and the text are drawn here
@@ -71,10 +72,11 @@ export function renderDiagramResult(view, theme, context) {
71
72
  const line = (text) => {
72
73
  container.addChild(new module.Text(theme.fg("toolOutput", text), 0, 0));
73
74
  };
75
+ const image = drawable(module, view.image, context) ? view.image : undefined;
76
+ const url = image === undefined ? undefined : openable(module, image);
74
77
  if (view.title !== undefined) {
75
- line(view.title);
78
+ line(url === undefined ? view.title : module.hyperlink(view.title, url));
76
79
  }
77
- const image = drawable(module, view.image, context) ? view.image : undefined;
78
80
  if (image === undefined) {
79
81
  line(view.text);
80
82
  }
@@ -85,6 +87,10 @@ export function renderDiagramResult(view, theme, context) {
85
87
  maxHeightCells: MAX_HEIGHT_CELLS,
86
88
  filename: image.path,
87
89
  }, { widthPx: image.widthPx, heightPx: image.heightPx }));
90
+ if (view.title === undefined && url !== undefined) {
91
+ const name = module.hyperlink(basename(image.path), url);
92
+ container.addChild(new module.Text(theme.fg("muted", name), 0, 0));
93
+ }
88
94
  }
89
95
  catch {
90
96
  // The picture is gone from the temp store, so show the text instead.
@@ -97,12 +103,21 @@ export function renderDiagramResult(view, theme, context) {
97
103
  }
98
104
  return container;
99
105
  }
106
+ /**
107
+ * The picture as a link, so a click opens it where it can be zoomed. The row is bounded to keep
108
+ * the transcript readable, so the file is the only way to see a dense diagram in full.
109
+ */
110
+ function openable(module, image) {
111
+ return module.getCapabilities().hyperlinks && isAbsolute(image.path)
112
+ ? pathToFileURL(image.path).href
113
+ : undefined;
114
+ }
100
115
  function drawable(module, image, context) {
101
116
  return image !== undefined && context.showImages && module.getCapabilities().images !== null;
102
117
  }
103
118
  /** Reads the image once per result row: the host calls the renderer again on every redraw. */
104
119
  function read(image, context) {
105
- const cached = context.state["diagramImage"];
120
+ const cached = context.state.diagramImage;
106
121
  if (typeof cached === "object" && cached !== null) {
107
122
  const { path, encoded } = cached;
108
123
  if (path === image.path && typeof encoded === "string") {
@@ -114,7 +129,7 @@ function read(image, context) {
114
129
  throw new Error(`The image at ${image.path} is ${bytes.length} bytes.`);
115
130
  }
116
131
  const encoded = bytes.toString("base64");
117
- context.state["diagramImage"] = { path: image.path, encoded };
132
+ context.state.diagramImage = { path: image.path, encoded };
118
133
  return encoded;
119
134
  }
120
135
  //# sourceMappingURL=display.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"display.js","sourceRoot":"","sources":["../src/display.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"display.js","sourceRoot":"","sources":["../src/display.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The prompt block that makes the model draw by itself. The tool description alone gets the tool
3
+ * called when the user asks for a diagram, not when a diagram is the clearer answer.
4
+ */
5
+ export declare const DIAGRAM_GUIDANCE: string;
6
+ /** Pi hands over one prompt string; Oh My Pi hands over ordered blocks. */
7
+ type HostSystemPrompt = string | readonly string[];
8
+ interface AgentStartEvent {
9
+ readonly systemPrompt?: HostSystemPrompt;
10
+ /** Only Pi reports what it built the prompt from. */
11
+ readonly systemPromptOptions?: {
12
+ readonly selectedTools?: readonly string[];
13
+ };
14
+ }
15
+ interface AgentStartResult {
16
+ readonly systemPrompt: HostSystemPrompt;
17
+ }
18
+ export interface GuidanceExtensionApi {
19
+ on?(event: "before_agent_start", handler: (event: AgentStartEvent) => AgentStartResult | undefined): void;
20
+ }
21
+ export declare function registerDiagramGuidance(pi: GuidanceExtensionApi): void;
22
+ /**
23
+ * The hook replaces the prompt, so returning the guidance alone would drop everything the host
24
+ * built. A prompt that cannot be read is left as it is.
25
+ */
26
+ export declare function withGuidance(event: AgentStartEvent): AgentStartResult | undefined;
27
+ export {};
28
+ //# sourceMappingURL=guidance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guidance.d.ts","sourceRoot":"","sources":["../src/guidance.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,eAAO,MAAM,gBAAgB,QAqBjB,CAAC;AAEb,2EAA2E;AAC3E,KAAK,gBAAgB,GAAG,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;AAEnD,UAAU,eAAe;IACvB,QAAQ,CAAC,YAAY,CAAC,EAAE,gBAAgB,CAAC;IACzC,qDAAqD;IACrD,QAAQ,CAAC,mBAAmB,CAAC,EAAE;QAAE,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,CAAC;CAC/E;AAED,UAAU,gBAAgB;IACxB,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC;CACzC;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,CAAC,CACD,KAAK,EAAE,oBAAoB,EAC3B,OAAO,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,gBAAgB,GAAG,SAAS,GAChE,IAAI,CAAC;CACT;AAED,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,oBAAoB,GAAG,IAAI,CAEtE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,eAAe,GAAG,gBAAgB,GAAG,SAAS,CAcjF"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The prompt block that makes the model draw by itself. The tool description alone gets the tool
3
+ * called when the user asks for a diagram, not when a diagram is the clearer answer.
4
+ */
5
+ export const DIAGRAM_GUIDANCE = [
6
+ "Diagrams:",
7
+ "- Call the diagram tool when structure, flow, or relationships are easier to see than to read.",
8
+ "- Draw instead of writing a wall of text: three or more connected parts means a diagram.",
9
+ "- Call the tool before you explain, then write only what the picture does not show.",
10
+ "- Do not repeat the diagram in prose. The user already sees it.",
11
+ "- Never hand-write ASCII art or a Mermaid block. Call the tool.",
12
+ "- Do not draw one fact, a short list, a list of commands, or code that reads better as code.",
13
+ "- Match the diagram to the question:",
14
+ " - components and their connections: profile architecture",
15
+ " - the order of messages in time: a sequence diagram, shape: sequence_diagram",
16
+ " - tables, columns, and keys: profile data, shape: sql_table",
17
+ " - a hierarchy, such as a call tree or a file layout: profile tree",
18
+ " - an import, module, or package graph: profile dependency",
19
+ " - C4 levels, when the reader expects that convention: profile c4",
20
+ " - a diagram to keep in the repository: profile docs, with save",
21
+ " - a data flow, a state change, or a request path in an answer: profile explain, the default",
22
+ "- Keep one idea per diagram, about 5 to 15 nodes. Split a bigger picture into more calls.",
23
+ "- Label every edge with what moves or what happens.",
24
+ "- Fix the source and call again when the tool reports an error in it.",
25
+ "- Pass save only when the user asks to keep the diagram.",
26
+ ].join("\n");
27
+ export function registerDiagramGuidance(pi) {
28
+ pi.on?.("before_agent_start", withGuidance);
29
+ }
30
+ /**
31
+ * The hook replaces the prompt, so returning the guidance alone would drop everything the host
32
+ * built. A prompt that cannot be read is left as it is.
33
+ */
34
+ export function withGuidance(event) {
35
+ const prompt = event.systemPrompt;
36
+ if (!toolActive(event)) {
37
+ return undefined;
38
+ }
39
+ if (typeof prompt === "string") {
40
+ return prompt === "" || prompt.includes(DIAGRAM_GUIDANCE)
41
+ ? undefined
42
+ : { systemPrompt: `${prompt}\n\n${DIAGRAM_GUIDANCE}` };
43
+ }
44
+ if (!Array.isArray(prompt) || prompt.length === 0 || prompt.includes(DIAGRAM_GUIDANCE)) {
45
+ return undefined;
46
+ }
47
+ return { systemPrompt: [...prompt, DIAGRAM_GUIDANCE] };
48
+ }
49
+ /** Telling the model to draw with a tool the user turned off would waste a turn. */
50
+ function toolActive(event) {
51
+ const tools = event.systemPromptOptions?.selectedTools;
52
+ return tools === undefined || tools.includes("diagram");
53
+ }
54
+ //# sourceMappingURL=guidance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guidance.js","sourceRoot":"","sources":["../src/guidance.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,WAAW;IACX,gGAAgG;IAChG,0FAA0F;IAC1F,qFAAqF;IACrF,iEAAiE;IACjE,iEAAiE;IACjE,8FAA8F;IAC9F,sCAAsC;IACtC,4DAA4D;IAC5D,gFAAgF;IAChF,+DAA+D;IAC/D,qEAAqE;IACrE,6DAA6D;IAC7D,oEAAoE;IACpE,kEAAkE;IAClE,+FAA+F;IAC/F,2FAA2F;IAC3F,qDAAqD;IACrD,uEAAuE;IACvE,0DAA0D;CAC3D,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAsBb,MAAM,UAAU,uBAAuB,CAAC,EAAwB;IAC9D,EAAE,CAAC,EAAE,EAAE,CAAC,oBAAoB,EAAE,YAAY,CAAC,CAAC;AAC9C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,KAAsB;IACjD,MAAM,MAAM,GAAG,KAAK,CAAC,YAAY,CAAC;IAClC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,OAAO,MAAM,KAAK,EAAE,IAAI,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAC;YACvD,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,EAAE,YAAY,EAAE,GAAG,MAAM,OAAO,gBAAgB,EAAE,EAAE,CAAC;IAC3D,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC;QACvF,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,EAAE,YAAY,EAAE,CAAC,GAAG,MAAM,EAAE,gBAAgB,CAAC,EAAE,CAAC;AACzD,CAAC;AAED,oFAAoF;AACpF,SAAS,UAAU,CAAC,KAAsB;IACxC,MAAM,KAAK,GAAG,KAAK,CAAC,mBAAmB,EAAE,aAAa,CAAC;IACvD,OAAO,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;AAC1D,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { type GuidanceExtensionApi } from "./guidance.js";
1
2
  import { type DiagramExtensionApi } from "./tools.js";
2
- export default function piDiagram(pi: DiagramExtensionApi): void;
3
+ export default function piDiagram(pi: DiagramExtensionApi & GuidanceExtensionApi): void;
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,mBAAmB,EAAwB,MAAM,YAAY,CAAC;AAE5E,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,EAAE,EAAE,mBAAmB,GAAG,IAAI,CAE/D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,oBAAoB,EAA2B,MAAM,eAAe,CAAC;AACnF,OAAO,EAAE,KAAK,mBAAmB,EAAwB,MAAM,YAAY,CAAC;AAE5E,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,EAAE,EAAE,mBAAmB,GAAG,oBAAoB,GAAG,IAAI,CAGtF"}
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
+ import { registerDiagramGuidance } from "./guidance.js";
1
2
  import { registerDiagramTools } from "./tools.js";
2
3
  export default function piDiagram(pi) {
3
4
  registerDiagramTools(pi);
5
+ registerDiagramGuidance(pi);
4
6
  }
5
7
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAE5E,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,EAAuB;IACvD,oBAAoB,CAAC,EAAE,CAAC,CAAC;AAC3B,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6B,uBAAuB,EAAE,MAAM,eAAe,CAAC;AACnF,OAAO,EAA4B,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAE5E,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,EAA8C;IAC9E,oBAAoB,CAAC,EAAE,CAAC,CAAC;IACzB,uBAAuB,CAAC,EAAE,CAAC,CAAC;AAC9B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcuste/pi-diagram",
3
- "version": "0.0.0",
3
+ "version": "0.2.0",
4
4
  "description": "Terminal-native diagram rendering as one safe, model-callable tool for the Pi and Oh My Pi coding agents",
5
5
  "keywords": [
6
6
  "pi-package",
package/src/display.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { createRequire } from "node:module";
3
+ import { basename, isAbsolute } from "node:path";
3
4
  import { pathToFileURL } from "node:url";
4
5
 
5
6
  /**
@@ -36,7 +37,11 @@ interface DisplayImage {
36
37
  }
37
38
 
38
39
  interface TuiModule {
39
- readonly getCapabilities: () => { readonly images: "kitty" | "iterm2" | null };
40
+ readonly getCapabilities: () => {
41
+ readonly images: "kitty" | "iterm2" | null;
42
+ readonly hyperlinks: boolean;
43
+ };
44
+ readonly hyperlink: (text: string, url: string) => string;
40
45
  readonly Text: new (text?: string, paddingX?: number, paddingY?: number) => Component;
41
46
  readonly Container: new () => Component & { addChild(child: Component): void };
42
47
  readonly Image: new (
@@ -128,11 +133,12 @@ export function renderDiagramResult(
128
133
  const line = (text: string): void => {
129
134
  container.addChild(new module.Text(theme.fg("toolOutput", text), 0, 0));
130
135
  };
136
+ const image = drawable(module, view.image, context) ? view.image : undefined;
137
+ const url = image === undefined ? undefined : openable(module, image);
131
138
  if (view.title !== undefined) {
132
- line(view.title);
139
+ line(url === undefined ? view.title : module.hyperlink(view.title, url));
133
140
  }
134
141
 
135
- const image = drawable(module, view.image, context) ? view.image : undefined;
136
142
  if (image === undefined) {
137
143
  line(view.text);
138
144
  } else {
@@ -150,6 +156,10 @@ export function renderDiagramResult(
150
156
  { widthPx: image.widthPx, heightPx: image.heightPx },
151
157
  ),
152
158
  );
159
+ if (view.title === undefined && url !== undefined) {
160
+ const name = module.hyperlink(basename(image.path), url);
161
+ container.addChild(new module.Text(theme.fg("muted", name), 0, 0));
162
+ }
153
163
  } catch {
154
164
  // The picture is gone from the temp store, so show the text instead.
155
165
  line(view.text);
@@ -163,6 +173,16 @@ export function renderDiagramResult(
163
173
  return container;
164
174
  }
165
175
 
176
+ /**
177
+ * The picture as a link, so a click opens it where it can be zoomed. The row is bounded to keep
178
+ * the transcript readable, so the file is the only way to see a dense diagram in full.
179
+ */
180
+ function openable(module: TuiModule, image: DisplayImage): string | undefined {
181
+ return module.getCapabilities().hyperlinks && isAbsolute(image.path)
182
+ ? pathToFileURL(image.path).href
183
+ : undefined;
184
+ }
185
+
166
186
  function drawable(
167
187
  module: TuiModule,
168
188
  image: DisplayImage | undefined,
@@ -183,7 +203,7 @@ export interface DiagramView {
183
203
 
184
204
  /** Reads the image once per result row: the host calls the renderer again on every redraw. */
185
205
  function read(image: DisplayImage, context: DisplayContext): string {
186
- const cached = context.state["diagramImage"];
206
+ const cached = context.state.diagramImage;
187
207
  if (typeof cached === "object" && cached !== null) {
188
208
  const { path, encoded } = cached as { path?: unknown; encoded?: unknown };
189
209
  if (path === image.path && typeof encoded === "string") {
@@ -196,6 +216,6 @@ function read(image: DisplayImage, context: DisplayContext): string {
196
216
  throw new Error(`The image at ${image.path} is ${bytes.length} bytes.`);
197
217
  }
198
218
  const encoded = bytes.toString("base64");
199
- context.state["diagramImage"] = { path: image.path, encoded };
219
+ context.state.diagramImage = { path: image.path, encoded };
200
220
  return encoded;
201
221
  }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * The prompt block that makes the model draw by itself. The tool description alone gets the tool
3
+ * called when the user asks for a diagram, not when a diagram is the clearer answer.
4
+ */
5
+
6
+ export const DIAGRAM_GUIDANCE = [
7
+ "Diagrams:",
8
+ "- Call the diagram tool when structure, flow, or relationships are easier to see than to read.",
9
+ "- Draw instead of writing a wall of text: three or more connected parts means a diagram.",
10
+ "- Call the tool before you explain, then write only what the picture does not show.",
11
+ "- Do not repeat the diagram in prose. The user already sees it.",
12
+ "- Never hand-write ASCII art or a Mermaid block. Call the tool.",
13
+ "- Do not draw one fact, a short list, a list of commands, or code that reads better as code.",
14
+ "- Match the diagram to the question:",
15
+ " - components and their connections: profile architecture",
16
+ " - the order of messages in time: a sequence diagram, shape: sequence_diagram",
17
+ " - tables, columns, and keys: profile data, shape: sql_table",
18
+ " - a hierarchy, such as a call tree or a file layout: profile tree",
19
+ " - an import, module, or package graph: profile dependency",
20
+ " - C4 levels, when the reader expects that convention: profile c4",
21
+ " - a diagram to keep in the repository: profile docs, with save",
22
+ " - a data flow, a state change, or a request path in an answer: profile explain, the default",
23
+ "- Keep one idea per diagram, about 5 to 15 nodes. Split a bigger picture into more calls.",
24
+ "- Label every edge with what moves or what happens.",
25
+ "- Fix the source and call again when the tool reports an error in it.",
26
+ "- Pass save only when the user asks to keep the diagram.",
27
+ ].join("\n");
28
+
29
+ /** Pi hands over one prompt string; Oh My Pi hands over ordered blocks. */
30
+ type HostSystemPrompt = string | readonly string[];
31
+
32
+ interface AgentStartEvent {
33
+ readonly systemPrompt?: HostSystemPrompt;
34
+ /** Only Pi reports what it built the prompt from. */
35
+ readonly systemPromptOptions?: { readonly selectedTools?: readonly string[] };
36
+ }
37
+
38
+ interface AgentStartResult {
39
+ readonly systemPrompt: HostSystemPrompt;
40
+ }
41
+
42
+ export interface GuidanceExtensionApi {
43
+ on?(
44
+ event: "before_agent_start",
45
+ handler: (event: AgentStartEvent) => AgentStartResult | undefined,
46
+ ): void;
47
+ }
48
+
49
+ export function registerDiagramGuidance(pi: GuidanceExtensionApi): void {
50
+ pi.on?.("before_agent_start", withGuidance);
51
+ }
52
+
53
+ /**
54
+ * The hook replaces the prompt, so returning the guidance alone would drop everything the host
55
+ * built. A prompt that cannot be read is left as it is.
56
+ */
57
+ export function withGuidance(event: AgentStartEvent): AgentStartResult | undefined {
58
+ const prompt = event.systemPrompt;
59
+ if (!toolActive(event)) {
60
+ return undefined;
61
+ }
62
+ if (typeof prompt === "string") {
63
+ return prompt === "" || prompt.includes(DIAGRAM_GUIDANCE)
64
+ ? undefined
65
+ : { systemPrompt: `${prompt}\n\n${DIAGRAM_GUIDANCE}` };
66
+ }
67
+ if (!Array.isArray(prompt) || prompt.length === 0 || prompt.includes(DIAGRAM_GUIDANCE)) {
68
+ return undefined;
69
+ }
70
+ return { systemPrompt: [...prompt, DIAGRAM_GUIDANCE] };
71
+ }
72
+
73
+ /** Telling the model to draw with a tool the user turned off would waste a turn. */
74
+ function toolActive(event: AgentStartEvent): boolean {
75
+ const tools = event.systemPromptOptions?.selectedTools;
76
+ return tools === undefined || tools.includes("diagram");
77
+ }
package/src/index.ts CHANGED
@@ -1,5 +1,7 @@
1
+ import { type GuidanceExtensionApi, registerDiagramGuidance } from "./guidance.js";
1
2
  import { type DiagramExtensionApi, registerDiagramTools } from "./tools.js";
2
3
 
3
- export default function piDiagram(pi: DiagramExtensionApi): void {
4
+ export default function piDiagram(pi: DiagramExtensionApi & GuidanceExtensionApi): void {
4
5
  registerDiagramTools(pi);
6
+ registerDiagramGuidance(pi);
5
7
  }