@oh-my-pi/pi-coding-agent 15.6.0 → 15.7.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.
Files changed (140) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/types/capability/rule-buckets.d.ts +30 -0
  3. package/dist/types/capability/rule.d.ts +7 -0
  4. package/dist/types/cli/completion-gen.d.ts +80 -0
  5. package/dist/types/commands/complete.d.ts +6 -0
  6. package/dist/types/commands/completions.d.ts +13 -0
  7. package/dist/types/commands/setup.d.ts +10 -1
  8. package/dist/types/config/settings-schema.d.ts +170 -10
  9. package/dist/types/discovery/builtin-defaults.d.ts +1 -0
  10. package/dist/types/discovery/builtin-rules/index.d.ts +7 -0
  11. package/dist/types/discovery/index.d.ts +1 -0
  12. package/dist/types/edit/hashline/block-resolver.d.ts +9 -0
  13. package/dist/types/edit/hashline/index.d.ts +1 -0
  14. package/dist/types/eval/py/kernel.d.ts +3 -0
  15. package/dist/types/eval/py/runtime.d.ts +11 -1
  16. package/dist/types/export/html/template.generated.d.ts +1 -1
  17. package/dist/types/main.d.ts +1 -0
  18. package/dist/types/modes/components/index.d.ts +1 -0
  19. package/dist/types/modes/components/segment-track.d.ts +22 -0
  20. package/dist/types/modes/components/welcome.d.ts +21 -0
  21. package/dist/types/modes/interactive-mode.d.ts +3 -2
  22. package/dist/types/modes/setup-wizard/index.d.ts +16 -0
  23. package/dist/types/modes/setup-wizard/scenes/glyph.d.ts +2 -0
  24. package/dist/types/modes/setup-wizard/scenes/outro.d.ts +2 -0
  25. package/dist/types/modes/setup-wizard/scenes/providers.d.ts +2 -0
  26. package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +19 -0
  27. package/dist/types/modes/setup-wizard/scenes/splash.d.ts +11 -0
  28. package/dist/types/modes/setup-wizard/scenes/theme.d.ts +2 -0
  29. package/dist/types/modes/setup-wizard/scenes/types.d.ts +43 -0
  30. package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +19 -0
  31. package/dist/types/modes/setup-wizard/wizard-overlay.d.ts +14 -0
  32. package/dist/types/modes/theme/shimmer.d.ts +2 -0
  33. package/dist/types/modes/theme/theme.d.ts +11 -0
  34. package/dist/types/modes/types.d.ts +5 -1
  35. package/dist/types/tiny/device.d.ts +78 -0
  36. package/dist/types/tiny/dtype.d.ts +85 -0
  37. package/dist/types/tiny/models.d.ts +6 -6
  38. package/dist/types/tiny/text.d.ts +15 -0
  39. package/dist/types/tiny/title-client.d.ts +8 -0
  40. package/dist/types/tools/bash.d.ts +0 -1
  41. package/dist/types/tools/eval.d.ts +1 -1
  42. package/dist/types/tools/index.d.ts +0 -1
  43. package/dist/types/tui/code-cell.d.ts +2 -0
  44. package/dist/types/tui/output-block.d.ts +17 -0
  45. package/package.json +9 -9
  46. package/src/capability/rule-buckets.ts +64 -0
  47. package/src/capability/rule.ts +8 -0
  48. package/src/cli/completion-gen.ts +550 -0
  49. package/src/cli/setup-cli.ts +5 -3
  50. package/src/cli-commands.ts +2 -0
  51. package/src/cli.ts +1 -7
  52. package/src/commands/complete.ts +66 -0
  53. package/src/commands/completions.ts +60 -0
  54. package/src/commands/setup.ts +29 -4
  55. package/src/config/settings-schema.ts +70 -11
  56. package/src/discovery/builtin-defaults.ts +39 -0
  57. package/src/discovery/builtin-rules/index.ts +48 -0
  58. package/src/discovery/builtin-rules/rs-box-leak.md +48 -0
  59. package/src/discovery/builtin-rules/rs-future-prelude.md +23 -0
  60. package/src/discovery/builtin-rules/rs-lazylock.md +51 -0
  61. package/src/discovery/builtin-rules/rs-match-ergonomics.md +67 -0
  62. package/src/discovery/builtin-rules/rs-parking-lot.md +44 -0
  63. package/src/discovery/builtin-rules/rs-result-type.md +19 -0
  64. package/src/discovery/builtin-rules/ts-bare-catch.md +38 -0
  65. package/src/discovery/builtin-rules/ts-import-type.md +42 -0
  66. package/src/discovery/builtin-rules/ts-no-any.md +56 -0
  67. package/src/discovery/builtin-rules/ts-no-dynamic-import.md +39 -0
  68. package/src/discovery/builtin-rules/ts-no-return-type.md +45 -0
  69. package/src/discovery/builtin-rules/ts-no-tiny-functions.md +50 -0
  70. package/src/discovery/builtin-rules/ts-promise-with-resolvers.md +65 -0
  71. package/src/discovery/builtin-rules/ts-set-map.md +28 -0
  72. package/src/discovery/index.ts +1 -0
  73. package/src/edit/hashline/block-resolver.ts +14 -0
  74. package/src/edit/hashline/diff.ts +4 -1
  75. package/src/edit/hashline/execute.ts +2 -1
  76. package/src/edit/hashline/index.ts +1 -0
  77. package/src/eval/py/kernel.ts +37 -15
  78. package/src/eval/py/runtime.ts +57 -28
  79. package/src/export/html/template.generated.ts +1 -1
  80. package/src/export/html/template.js +0 -12
  81. package/src/export/ttsr.ts +2 -0
  82. package/src/internal-urls/docs-index.generated.ts +7 -8
  83. package/src/main.ts +18 -1
  84. package/src/modes/components/hook-selector.ts +15 -17
  85. package/src/modes/components/index.ts +1 -0
  86. package/src/modes/components/segment-track.ts +52 -0
  87. package/src/modes/components/tips.txt +2 -1
  88. package/src/modes/components/tool-execution.ts +5 -1
  89. package/src/modes/components/welcome.ts +47 -42
  90. package/src/modes/controllers/input-controller.ts +12 -21
  91. package/src/modes/interactive-mode.ts +17 -5
  92. package/src/modes/setup-wizard/index.ts +88 -0
  93. package/src/modes/setup-wizard/scenes/glyph.ts +96 -0
  94. package/src/modes/setup-wizard/scenes/outro.ts +35 -0
  95. package/src/modes/setup-wizard/scenes/providers.ts +69 -0
  96. package/src/modes/setup-wizard/scenes/sign-in.ts +193 -0
  97. package/src/modes/setup-wizard/scenes/splash.ts +201 -0
  98. package/src/modes/setup-wizard/scenes/theme.ts +299 -0
  99. package/src/modes/setup-wizard/scenes/types.ts +48 -0
  100. package/src/modes/setup-wizard/scenes/web-search.ts +128 -0
  101. package/src/modes/setup-wizard/wizard-overlay.ts +275 -0
  102. package/src/modes/theme/shimmer.ts +5 -0
  103. package/src/modes/theme/theme.ts +44 -20
  104. package/src/modes/types.ts +6 -1
  105. package/src/prompts/system/orchestrate-notice.md +1 -1
  106. package/src/prompts/tools/read.md +4 -0
  107. package/src/sdk.ts +5 -15
  108. package/src/slash-commands/builtin-registry.ts +8 -0
  109. package/src/tiny/device.ts +117 -0
  110. package/src/tiny/dtype.ts +101 -0
  111. package/src/tiny/models.ts +7 -6
  112. package/src/tiny/text.ts +36 -1
  113. package/src/tiny/title-client.ts +58 -3
  114. package/src/tiny/worker.ts +93 -29
  115. package/src/tools/bash.ts +16 -13
  116. package/src/tools/eval.ts +9 -4
  117. package/src/tools/index.ts +0 -11
  118. package/src/tools/read.ts +1 -0
  119. package/src/tools/renderers.ts +0 -2
  120. package/src/tui/code-cell.ts +6 -1
  121. package/src/tui/output-block.ts +199 -38
  122. package/dist/types/tools/recipe/index.d.ts +0 -46
  123. package/dist/types/tools/recipe/render.d.ts +0 -36
  124. package/dist/types/tools/recipe/runner.d.ts +0 -60
  125. package/dist/types/tools/recipe/runners/cargo.d.ts +0 -16
  126. package/dist/types/tools/recipe/runners/index.d.ts +0 -2
  127. package/dist/types/tools/recipe/runners/just.d.ts +0 -2
  128. package/dist/types/tools/recipe/runners/make.d.ts +0 -2
  129. package/dist/types/tools/recipe/runners/pkg.d.ts +0 -2
  130. package/dist/types/tools/recipe/runners/task.d.ts +0 -2
  131. package/src/prompts/tools/recipe.md +0 -16
  132. package/src/tools/recipe/index.ts +0 -81
  133. package/src/tools/recipe/render.ts +0 -19
  134. package/src/tools/recipe/runner.ts +0 -219
  135. package/src/tools/recipe/runners/cargo.ts +0 -131
  136. package/src/tools/recipe/runners/index.ts +0 -8
  137. package/src/tools/recipe/runners/just.ts +0 -73
  138. package/src/tools/recipe/runners/make.ts +0 -101
  139. package/src/tools/recipe/runners/pkg.ts +0 -167
  140. package/src/tools/recipe/runners/task.ts +0 -72
@@ -15,8 +15,81 @@ export interface OutputBlockOptions {
15
15
  sections?: Array<{ label?: string; lines: string[] }>;
16
16
  width: number;
17
17
  applyBg?: boolean;
18
+ /** Animate the border with a sweeping dark segment (pending/running state). */
19
+ animate?: boolean;
18
20
  }
19
21
 
22
+ const BORDER_SHIMMER_TICK_MS = 50;
23
+ /** Duration of one full clockwise lap around the border, in ms. Fixed so a box
24
+ * growing (new output line) or resizing only nudges the segment proportionally
25
+ * instead of teleporting it. */
26
+ const BORDER_LAP_MS = 4000;
27
+ /** Length, in border cells, of the moving segment. */
28
+ const BORDER_SEGMENT_LEN = 8;
29
+
30
+ /**
31
+ * Monotonic frame counter for animated borders. Quantized coarse enough to
32
+ * coalesce multiple render passes inside one frame, fine enough to advance on
33
+ * every spinner interval so cached blocks re-render while the segment travels.
34
+ */
35
+ export function borderShimmerTick(): number {
36
+ return Math.floor(Date.now() / BORDER_SHIMMER_TICK_MS);
37
+ }
38
+
39
+ /** Ease-in-out so the segment decelerates into and accelerates out of corners. */
40
+ function easeInOutQuad(t: number): number {
41
+ return t < 0.5 ? 2 * t * t : 1 - (-2 * t + 2) ** 2 / 2;
42
+ }
43
+
44
+ /**
45
+ * Perimeter index of the moving segment's head for a box of inner width `W` and
46
+ * height `H` at time `now`. The lap is split across the four edges in proportion
47
+ * to their length (so the average speed is uniform) and each edge is eased, for a
48
+ * deliberate, non-linear glide that slows at every corner. Position is derived
49
+ * from the wall clock against a fixed lap duration, so a perimeter change (new
50
+ * row / resize) shifts the head by at most a cell or two — no reset.
51
+ */
52
+ export function borderSegmentHead(W: number, H: number, now: number): number {
53
+ const P = 2 * W + 2 * H - 4;
54
+ if (P <= 0) return 0;
55
+ // Edge cell counts, clockwise from top-left: top, right, bottom, left.
56
+ const edgeLens = [W, H - 1, W - 1, H - 2];
57
+ const t = (((now % BORDER_LAP_MS) + BORDER_LAP_MS) % BORDER_LAP_MS) / BORDER_LAP_MS;
58
+ let acc = 0;
59
+ let start = 0;
60
+ for (let i = 0; i < 4; i++) {
61
+ const len = edgeLens[i]!;
62
+ const frac = len / P;
63
+ if (len > 0 && t < acc + frac) {
64
+ const lf = (t - acc) / frac;
65
+ return (start + Math.floor(easeInOutQuad(lf) * len)) % P;
66
+ }
67
+ acc += frac;
68
+ start += len;
69
+ }
70
+ return P - 1;
71
+ }
72
+
73
+ /**
74
+ * Scale a truecolor foreground escape toward black by `factor`. Returns
75
+ * undefined for 256-color escapes (no RGB to scale) so callers fall back to a
76
+ * dimmer theme color.
77
+ */
78
+ function darkenFgAnsi(ansi: string, factor: number): string | undefined {
79
+ const m = /38;2;(\d+);(\d+);(\d+)/.exec(ansi);
80
+ if (!m) return undefined;
81
+ const r = Math.round(Number(m[1]) * factor);
82
+ const g = Math.round(Number(m[2]) * factor);
83
+ const b = Math.round(Number(m[3]) * factor);
84
+ return `\x1b[38;2;${r};${g};${b}m`;
85
+ }
86
+
87
+ type BlockRow =
88
+ | { kind: "bar"; leftChar: string; rightChar: string; label?: string; meta?: string }
89
+ | { kind: "bottom"; leftChar: string; rightChar: string }
90
+ | { kind: "content"; inner: string }
91
+ | { kind: "sixel"; raw: string };
92
+
20
93
  export function renderOutputBlock(options: OutputBlockOptions, theme: Theme): string[] {
21
94
  const { header, headerMeta, state, sections = [], width, applyBg = true } = options;
22
95
  const h = theme.boxSharp.horizontal;
@@ -46,62 +119,148 @@ export function renderOutputBlock(options: OutputBlockOptions, theme: Theme): st
46
119
  };
47
120
  })();
48
121
 
49
- const buildBarLine = (leftChar: string, rightChar: string, label?: string, meta?: string): string => {
50
- const left = border(`${leftChar}${cap}`);
51
- const right = border(rightChar);
52
- if (lineWidth <= 0) return left + right;
53
- const labelText = [label, meta].filter(Boolean).join(theme.sep.dot);
54
- const rawLabel = labelText ? ` ${labelText} ` : " ";
55
- const leftWidth = visibleWidth(left);
56
- const rightWidth = visibleWidth(right);
57
- const maxLabelWidth = Math.max(0, lineWidth - leftWidth - rightWidth);
58
- const trimmedLabel = truncateToWidth(rawLabel, maxLabelWidth);
59
- const labelWidth = visibleWidth(trimmedLabel);
60
- const fillCount = Math.max(0, lineWidth - leftWidth - labelWidth - rightWidth);
61
- return `${left}${trimmedLabel}${border(h.repeat(fillCount))}${right}`;
62
- };
63
-
64
- const contentPrefix = border(`${v} `);
65
- const contentSuffix = border(v);
66
- const contentWidth = Math.max(0, lineWidth - visibleWidth(contentPrefix) - visibleWidth(contentSuffix));
67
- const lines: string[] = [];
122
+ const contentWidth = Math.max(0, lineWidth - visibleWidth(`${v} `) - visibleWidth(v));
68
123
 
69
- lines.push(
70
- padToWidth(buildBarLine(theme.boxSharp.topLeft, theme.boxSharp.topRight, header, headerMeta), lineWidth, bgFn),
71
- );
124
+ // ── Layout pass: collect row descriptors so the border perimeter length is
125
+ // known before the moving segment is positioned. ──
126
+ const rows: BlockRow[] = [];
127
+ rows.push({
128
+ kind: "bar",
129
+ leftChar: theme.boxSharp.topLeft,
130
+ rightChar: theme.boxSharp.topRight,
131
+ label: header,
132
+ meta: headerMeta,
133
+ });
72
134
 
73
- const hasSections = sections.length > 0;
74
- const normalizedSections = hasSections ? sections : [{ lines: [] }];
75
-
76
- for (let i = 0; i < normalizedSections.length; i++) {
77
- const section = normalizedSections[i];
135
+ const normalizedSections = sections.length > 0 ? sections : [{ lines: [] as string[] }];
136
+ for (const section of normalizedSections) {
78
137
  if (section.label) {
79
- lines.push(
80
- padToWidth(buildBarLine(theme.boxSharp.teeRight, theme.boxSharp.teeLeft, section.label), lineWidth, bgFn),
81
- );
138
+ rows.push({
139
+ kind: "bar",
140
+ leftChar: theme.boxSharp.teeRight,
141
+ rightChar: theme.boxSharp.teeLeft,
142
+ label: section.label,
143
+ });
82
144
  }
83
145
  const allLines = section.lines.flatMap(l => l.split("\n"));
84
146
  const sixelLineMask = TERMINAL.imageProtocol === ImageProtocol.Sixel ? getSixelLineMask(allLines) : undefined;
85
147
  for (let lineIndex = 0; lineIndex < allLines.length; lineIndex++) {
86
148
  const line = allLines[lineIndex]!;
87
149
  if (sixelLineMask?.[lineIndex]) {
88
- lines.push(line);
150
+ rows.push({ kind: "sixel", raw: line });
89
151
  continue;
90
152
  }
91
153
  const wrappedLines = wrapTextWithAnsi(line.trimEnd(), contentWidth);
92
154
  for (const wrappedLine of wrappedLines) {
93
155
  const innerPadding = padding(Math.max(0, contentWidth - visibleWidth(wrappedLine)));
94
- const fullLine = `${contentPrefix}${wrappedLine}${innerPadding}${contentSuffix}`;
95
- lines.push(padToWidth(fullLine, lineWidth, bgFn));
156
+ rows.push({ kind: "content", inner: `${wrappedLine}${innerPadding}` });
96
157
  }
97
158
  }
98
159
  }
99
160
 
100
- const bottomLeft = border(`${theme.boxSharp.bottomLeft}${cap}`);
101
- const bottomRight = border(theme.boxSharp.bottomRight);
102
- const bottomFillCount = Math.max(0, lineWidth - visibleWidth(bottomLeft) - visibleWidth(bottomRight));
103
- const bottomLine = `${bottomLeft}${border(h.repeat(bottomFillCount))}${bottomRight}`;
104
- lines.push(padToWidth(bottomLine, lineWidth, bgFn));
161
+ rows.push({ kind: "bottom", leftChar: theme.boxSharp.bottomLeft, rightChar: theme.boxSharp.bottomRight });
162
+
163
+ const H = rows.length;
164
+ const W = lineWidth;
165
+ const animate = (options.animate ?? false) && (state === "running" || state === "pending") && W >= 2 && H >= 2;
166
+
167
+ // ── Segment geometry: one dark run travels the outer edge clockwise,
168
+ // top → right → bottom → left → top. ──
169
+ const P = animate ? 2 * W + 2 * H - 4 : 0;
170
+ const segLen = Math.min(BORDER_SEGMENT_LEN, P);
171
+ const head = animate ? borderSegmentHead(W, H, Date.now()) : 0;
172
+ const segAnsi = animate ? (darkenFgAnsi(theme.getFgAnsi(borderColor), 0.4) ?? theme.getFgAnsi("borderMuted")) : "";
173
+ const seg = (text: string) => `${segAnsi}${text}\x1b[39m`;
174
+
175
+ // Perimeter index of border cell (row r, col c), clockwise from top-left.
176
+ const perimIndex = (r: number, c: number): number => {
177
+ if (r === 0) return c;
178
+ if (c === W - 1) return W - 1 + r;
179
+ if (r === H - 1) return W - 1 + (H - 1) + (W - 1 - c);
180
+ return W - 1 + (H - 1) + (W - 1) + (H - 1 - r);
181
+ };
182
+ const isLit = (idx: number): boolean => (((idx - head) % P) + P) % P < segLen;
183
+ // Color a run of border glyphs starting at (row r, col startCol), grouping
184
+ // consecutive same-state cells so each run emits a single escape pair.
185
+ const colorEdge = (glyphs: string, r: number, startCol: number): string => {
186
+ let out = "";
187
+ let runLit: boolean | null = null;
188
+ let buf = "";
189
+ for (let i = 0; i < glyphs.length; i++) {
190
+ const lit = isLit(perimIndex(r, startCol + i));
191
+ if (lit !== runLit) {
192
+ if (runLit !== null) out += (runLit ? seg : border)(buf);
193
+ buf = "";
194
+ runLit = lit;
195
+ }
196
+ buf += glyphs[i];
197
+ }
198
+ if (runLit !== null) out += (runLit ? seg : border)(buf);
199
+ return out;
200
+ };
201
+
202
+ const renderBar = (
203
+ row: { leftChar: string; rightChar: string; label?: string; meta?: string },
204
+ r: number,
205
+ ): string => {
206
+ const leftGlyphs = `${row.leftChar}${cap}`;
207
+ const rightGlyph = row.rightChar;
208
+ if (lineWidth <= 0) return border(leftGlyphs) + border(rightGlyph);
209
+ const labelText = [row.label, row.meta].filter(Boolean).join(theme.sep.dot);
210
+ const rawLabel = labelText ? ` ${labelText} ` : " ";
211
+ const leftWidth = visibleWidth(leftGlyphs);
212
+ const rightWidth = visibleWidth(rightGlyph);
213
+ const maxLabelWidth = Math.max(0, lineWidth - leftWidth - rightWidth);
214
+ const trimmedLabel = truncateToWidth(rawLabel, maxLabelWidth);
215
+ const labelWidth = visibleWidth(trimmedLabel);
216
+ const fillCount = Math.max(0, lineWidth - leftWidth - labelWidth - rightWidth);
217
+ const fillGlyphs = h.repeat(fillCount);
218
+ if (!animate) {
219
+ return `${border(leftGlyphs)}${trimmedLabel}${border(fillGlyphs)}${border(rightGlyph)}`;
220
+ }
221
+ if (r === 0 || r === H - 1) {
222
+ // Top/bottom edge: the whole horizontal run lies on the perimeter.
223
+ const leftStr = colorEdge(leftGlyphs, r, 0);
224
+ const fillStr = colorEdge(fillGlyphs, r, leftWidth + labelWidth);
225
+ const rightStr = colorEdge(rightGlyph, r, lineWidth - rightWidth);
226
+ return `${leftStr}${trimmedLabel}${fillStr}${rightStr}`;
227
+ }
228
+ // Interior separator: only the first/last cell sit on the outer edge.
229
+ return `${colorEdge(row.leftChar, r, 0)}${border(cap)}${trimmedLabel}${border(fillGlyphs)}${colorEdge(rightGlyph, r, lineWidth - rightWidth)}`;
230
+ };
231
+
232
+ const renderBottom = (row: { leftChar: string; rightChar: string }, r: number): string => {
233
+ const leftGlyphs = `${row.leftChar}${cap}`;
234
+ const rightGlyph = row.rightChar;
235
+ const fillCount = Math.max(0, lineWidth - visibleWidth(leftGlyphs) - visibleWidth(rightGlyph));
236
+ const fillGlyphs = h.repeat(fillCount);
237
+ if (!animate) return `${border(leftGlyphs)}${border(fillGlyphs)}${border(rightGlyph)}`;
238
+ const leftStr = colorEdge(leftGlyphs, r, 0);
239
+ const fillStr = colorEdge(fillGlyphs, r, visibleWidth(leftGlyphs));
240
+ const rightStr = colorEdge(rightGlyph, r, lineWidth - visibleWidth(rightGlyph));
241
+ return `${leftStr}${fillStr}${rightStr}`;
242
+ };
243
+
244
+ const renderContent = (inner: string, r: number): string => {
245
+ if (!animate) return `${border(`${v} `)}${inner}${border(v)}`;
246
+ return `${colorEdge(v, r, 0)} ${inner}${colorEdge(v, r, lineWidth - 1)}`;
247
+ };
248
+
249
+ const lines: string[] = [];
250
+ for (let r = 0; r < H; r++) {
251
+ const row = rows[r]!;
252
+ if (row.kind === "sixel") {
253
+ lines.push(row.raw);
254
+ continue;
255
+ }
256
+ const line =
257
+ row.kind === "bar"
258
+ ? renderBar(row, r)
259
+ : row.kind === "bottom"
260
+ ? renderBottom(row, r)
261
+ : renderContent(row.inner, r);
262
+ lines.push(padToWidth(line, lineWidth, bgFn));
263
+ }
105
264
 
106
265
  return lines;
107
266
  }
@@ -137,6 +296,8 @@ export class CachedOutputBlock {
137
296
  h.optional(options.headerMeta);
138
297
  h.optional(options.state);
139
298
  h.bool(options.applyBg ?? true);
299
+ h.bool(options.animate ?? false);
300
+ if (options.animate) h.u32(borderShimmerTick());
140
301
  if (options.sections) {
141
302
  for (const s of options.sections) {
142
303
  h.optional(s.label);
@@ -1,46 +0,0 @@
1
- import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
2
- import type { Component } from "@oh-my-pi/pi-tui";
3
- import * as z from "zod/v4";
4
- import type { RenderResultOptions } from "../../extensibility/custom-tools/types";
5
- import type { Theme } from "../../modes/theme/theme";
6
- import type { ToolSession } from "..";
7
- import { type BashRenderContext, type BashToolDetails } from "../bash";
8
- import { type RecipeRenderArgs } from "./render";
9
- import { type DetectedRunner } from "./runner";
10
- declare const recipeSchema: z.ZodObject<{
11
- op: z.ZodString;
12
- }, z.core.$strict>;
13
- type RecipeParams = z.infer<typeof recipeSchema>;
14
- type RecipeRenderResult = {
15
- content: Array<{
16
- type: string;
17
- text?: string;
18
- }>;
19
- details?: BashToolDetails;
20
- isError?: boolean;
21
- };
22
- export declare class RecipeTool implements AgentTool<typeof recipeSchema, BashToolDetails, Theme> {
23
- #private;
24
- readonly name = "recipe";
25
- readonly label = "Run";
26
- readonly approval: "exec";
27
- readonly description: string;
28
- readonly parameters: z.ZodObject<{
29
- op: z.ZodString;
30
- }, z.core.$strict>;
31
- readonly strict = true;
32
- readonly concurrency = "exclusive";
33
- readonly loadMode = "discoverable";
34
- readonly summary = "Execute a saved bash recipe (multi-step shell command preset)";
35
- readonly mergeCallAndResult = true;
36
- readonly inline = true;
37
- readonly renderCall: (args: RecipeRenderArgs, options: RenderResultOptions, uiTheme: Theme) => Component;
38
- readonly renderResult: (result: RecipeRenderResult, options: RenderResultOptions & {
39
- renderContext?: BashRenderContext;
40
- }, uiTheme: Theme, args?: RecipeRenderArgs) => Component;
41
- constructor(session: ToolSession, runners: DetectedRunner[]);
42
- static createIf(session: ToolSession): Promise<RecipeTool | null>;
43
- execute(toolCallId: string, { op }: RecipeParams, signal?: AbortSignal, onUpdate?: AgentToolUpdateCallback<BashToolDetails>, ctx?: AgentToolContext): Promise<AgentToolResult<BashToolDetails>>;
44
- }
45
- export * from "./runner";
46
- export { tasksFromCargoMetadata } from "./runners/cargo";
@@ -1,36 +0,0 @@
1
- import type { DetectedRunner } from "./runner";
2
- export interface RecipeRenderArgs {
3
- op?: string;
4
- __partialJson?: string;
5
- [key: string]: unknown;
6
- }
7
- export declare function createRecipeToolRenderer(runners: DetectedRunner[]): {
8
- renderCall(args: RecipeRenderArgs, options: import("../..").RenderResultOptions, uiTheme: import("../..").Theme): import("@oh-my-pi/pi-tui").Component;
9
- renderResult(result: {
10
- content: {
11
- type: string;
12
- text?: string;
13
- }[];
14
- details?: import("..").BashToolDetails;
15
- isError?: boolean;
16
- }, options: import("../..").RenderResultOptions & {
17
- renderContext?: import("..").BashRenderContext;
18
- }, uiTheme: import("../..").Theme, args?: RecipeRenderArgs | undefined): import("@oh-my-pi/pi-tui").Component;
19
- mergeCallAndResult: boolean;
20
- inline: boolean;
21
- };
22
- export declare const recipeToolRenderer: {
23
- renderCall(args: RecipeRenderArgs, options: import("../..").RenderResultOptions, uiTheme: import("../..").Theme): import("@oh-my-pi/pi-tui").Component;
24
- renderResult(result: {
25
- content: {
26
- type: string;
27
- text?: string;
28
- }[];
29
- details?: import("..").BashToolDetails;
30
- isError?: boolean;
31
- }, options: import("../..").RenderResultOptions & {
32
- renderContext?: import("..").BashRenderContext;
33
- }, uiTheme: import("../..").Theme, args?: RecipeRenderArgs | undefined): import("@oh-my-pi/pi-tui").Component;
34
- mergeCallAndResult: boolean;
35
- inline: boolean;
36
- };
@@ -1,60 +0,0 @@
1
- export interface RunnerTask {
2
- name: string;
3
- doc?: string;
4
- /** Parameter names only; used for the `name foo bar` signature line in the description. */
5
- parameters: string[];
6
- /** Override for this specific task, e.g. `cargo run --package crate --bin`. */
7
- commandPrefix?: string;
8
- /** Token passed to the runner command; defaults to `name`. Used when display names are namespaced. */
9
- commandName?: string;
10
- /** Working directory for the task, relative to the session cwd; absent means the runner's root cwd. */
11
- cwd?: string;
12
- }
13
- export interface DetectedRunner {
14
- id: string;
15
- label: string;
16
- /** Resolved shell prefix, e.g. "just" or "bun run" or "make". */
17
- commandPrefix: string;
18
- tasks: RunnerTask[];
19
- }
20
- export interface TaskRunner {
21
- id: string;
22
- label: string;
23
- /**
24
- * Probe `cwd` for the manifest, the binary, and the task list.
25
- * Returns null when this runner does not apply.
26
- */
27
- detect(cwd: string): Promise<DetectedRunner | null>;
28
- }
29
- interface PromptTaskModel {
30
- name: string;
31
- paramSig?: string;
32
- command?: string;
33
- doc?: string;
34
- cwd?: string;
35
- }
36
- interface PromptRunnerModel {
37
- id: string;
38
- label: string;
39
- commandPrefix: string;
40
- tasks: PromptTaskModel[];
41
- hiddenTaskCount?: number;
42
- }
43
- export interface RecipePromptModel {
44
- [key: string]: unknown;
45
- hasMultipleRunners: boolean;
46
- ambiguityExampleRunner?: string;
47
- ambiguityExampleTask?: string;
48
- runners: PromptRunnerModel[];
49
- }
50
- export interface ResolvedTask {
51
- command: string;
52
- cwd?: string;
53
- }
54
- export declare function resolveCommand(op: string, runners: DetectedRunner[]): ResolvedTask;
55
- export declare function resolveTaskFromOp(op: string | undefined, runners: DetectedRunner[]): ResolvedTask | undefined;
56
- export declare function commandFromOp(op: string | undefined, runners: DetectedRunner[]): string | undefined;
57
- export declare function cwdFromOp(op: string | undefined, runners: DetectedRunner[]): string | undefined;
58
- export declare function titleFromOp(op: string | undefined, runners: DetectedRunner[]): string;
59
- export declare function buildPromptModel(runners: DetectedRunner[]): RecipePromptModel;
60
- export {};
@@ -1,16 +0,0 @@
1
- import type { RunnerTask, TaskRunner } from "../runner";
2
- export interface CargoMetadataTarget {
3
- kind?: string[];
4
- name?: string;
5
- }
6
- export interface CargoMetadataPackage {
7
- id?: string;
8
- name?: string;
9
- targets?: CargoMetadataTarget[];
10
- }
11
- export interface CargoMetadata {
12
- packages?: CargoMetadataPackage[];
13
- workspace_members?: string[];
14
- }
15
- export declare function tasksFromCargoMetadata(metadata: CargoMetadata): RunnerTask[];
16
- export declare const cargoRunner: TaskRunner;
@@ -1,2 +0,0 @@
1
- import type { TaskRunner } from "../runner";
2
- export declare const RUNNERS: TaskRunner[];
@@ -1,2 +0,0 @@
1
- import type { TaskRunner } from "../runner";
2
- export declare const justRunner: TaskRunner;
@@ -1,2 +0,0 @@
1
- import type { TaskRunner } from "../runner";
2
- export declare const makeRunner: TaskRunner;
@@ -1,2 +0,0 @@
1
- import type { TaskRunner } from "../runner";
2
- export declare const pkgRunner: TaskRunner;
@@ -1,2 +0,0 @@
1
- import type { TaskRunner } from "../runner";
2
- export declare const taskRunner: TaskRunner;
@@ -1,16 +0,0 @@
1
- Run a recipe / script / target from the project's task runners.
2
-
3
- <instruction>
4
- - `op` is a single string: task name plus any args, e.g. `{op: "test"}` or `{op: "build --release"}`.
5
- - In monorepos, package and Cargo target tasks are namespaced with `/`, e.g. `{op: "pkg-a/test"}` or `{op: "crate/bin/server"}`.
6
- {{#if hasMultipleRunners}}- When the same task name exists in more than one runner, prefix with the runner id, e.g. `{op: "{{ambiguityExampleRunner}}:{{ambiguityExampleTask}}"}`. The available runner ids are: {{#each runners}}`{{id}}`{{#unless @last}}, {{/unless}}{{/each}}.
7
- {{/if}}- Runs in the session's cwd. Output and exit code are returned in the same shape as `bash`.
8
- </instruction>
9
-
10
- {{#each runners}}
11
- <runner id="{{id}}" label="{{label}}" command="{{commandPrefix}}">
12
- {{#each tasks}}
13
- - `{{name}}{{#if paramSig}} {{paramSig}}{{/if}}`{{#if doc}} — {{doc}}{{/if}}{{#if command}} (`{{command}}`{{#if cwd}} in `{{cwd}}`{{/if}}){{/if}}
14
- {{/each}}
15
- </runner>
16
- {{/each}}
@@ -1,81 +0,0 @@
1
- import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
2
- import type { Component } from "@oh-my-pi/pi-tui";
3
- import { prompt } from "@oh-my-pi/pi-utils";
4
- import * as z from "zod/v4";
5
- import type { RenderResultOptions } from "../../extensibility/custom-tools/types";
6
- import type { Theme } from "../../modes/theme/theme";
7
- import recipeDescription from "../../prompts/tools/recipe.md" with { type: "text" };
8
- import type { ToolSession } from "..";
9
- import { type BashRenderContext, BashTool, type BashToolDetails } from "../bash";
10
- import { createRecipeToolRenderer, type RecipeRenderArgs } from "./render";
11
- import { buildPromptModel, type DetectedRunner, resolveCommand } from "./runner";
12
- import { RUNNERS } from "./runners";
13
-
14
- const recipeSchema = z
15
- .object({
16
- op: z.string().describe('task name and args, e.g. "test" or "build --release"'),
17
- })
18
- .strict();
19
- type RecipeParams = z.infer<typeof recipeSchema>;
20
-
21
- type RecipeRenderResult = {
22
- content: Array<{ type: string; text?: string }>;
23
- details?: BashToolDetails;
24
- isError?: boolean;
25
- };
26
-
27
- export class RecipeTool implements AgentTool<typeof recipeSchema, BashToolDetails, Theme> {
28
- readonly name = "recipe";
29
- readonly label = "Run";
30
- readonly approval = "exec" as const;
31
- readonly description: string;
32
- readonly parameters = recipeSchema;
33
- readonly strict = true;
34
- readonly concurrency = "exclusive";
35
- readonly loadMode = "discoverable";
36
- readonly summary = "Execute a saved bash recipe (multi-step shell command preset)";
37
- readonly mergeCallAndResult = true;
38
- readonly inline = true;
39
- readonly renderCall: (args: RecipeRenderArgs, options: RenderResultOptions, uiTheme: Theme) => Component;
40
- readonly renderResult: (
41
- result: RecipeRenderResult,
42
- options: RenderResultOptions & { renderContext?: BashRenderContext },
43
- uiTheme: Theme,
44
- args?: RecipeRenderArgs,
45
- ) => Component;
46
-
47
- readonly #bash: BashTool;
48
- readonly #runners: DetectedRunner[];
49
-
50
- constructor(session: ToolSession, runners: DetectedRunner[]) {
51
- this.#runners = runners;
52
- this.#bash = new BashTool(session);
53
- this.description = prompt.render(recipeDescription, buildPromptModel(runners));
54
- const renderer = createRecipeToolRenderer(runners);
55
- this.renderCall = renderer.renderCall;
56
- this.renderResult = renderer.renderResult;
57
- }
58
-
59
- static async createIf(session: ToolSession): Promise<RecipeTool | null> {
60
- if (!session.settings.get("recipe.enabled")) return null;
61
- const detected = (await Promise.all(RUNNERS.map(runner => runner.detect(session.cwd)))).filter(
62
- (runner): runner is DetectedRunner => runner !== null && runner.tasks.length > 0,
63
- );
64
- if (detected.length === 0) return null;
65
- return new RecipeTool(session, detected);
66
- }
67
-
68
- async execute(
69
- toolCallId: string,
70
- { op }: RecipeParams,
71
- signal?: AbortSignal,
72
- onUpdate?: AgentToolUpdateCallback<BashToolDetails>,
73
- ctx?: AgentToolContext,
74
- ): Promise<AgentToolResult<BashToolDetails>> {
75
- const { command, cwd } = resolveCommand(op, this.#runners);
76
- return await this.#bash.execute(toolCallId, { command, cwd }, signal, onUpdate, ctx);
77
- }
78
- }
79
-
80
- export * from "./runner";
81
- export { tasksFromCargoMetadata } from "./runners/cargo";
@@ -1,19 +0,0 @@
1
- import { createShellRenderer } from "../bash";
2
- import type { DetectedRunner } from "./runner";
3
- import { commandFromOp, cwdFromOp, titleFromOp } from "./runner";
4
-
5
- export interface RecipeRenderArgs {
6
- op?: string;
7
- __partialJson?: string;
8
- [key: string]: unknown;
9
- }
10
-
11
- export function createRecipeToolRenderer(runners: DetectedRunner[]) {
12
- return createShellRenderer<RecipeRenderArgs>({
13
- resolveTitle: args => titleFromOp(args?.op, runners),
14
- resolveCommand: args => commandFromOp(args?.op, runners),
15
- resolveCwd: args => cwdFromOp(args?.op, runners),
16
- });
17
- }
18
-
19
- export const recipeToolRenderer = createRecipeToolRenderer([]);