@pi-unipi/image 2.6.1 → 2.6.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/image",
3
- "version": "2.6.1",
3
+ "version": "2.6.2",
4
4
  "description": "Image generation and image recognition tools for the Pi coding agent",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -49,8 +49,12 @@
49
49
  "test": "npx tsx --test tests/**/*.test.ts"
50
50
  },
51
51
  "pi": {
52
- "extensions": [],
53
- "skills": [],
52
+ "extensions": [
53
+ "./src/index.ts"
54
+ ],
55
+ "skills": [
56
+ "./skills"
57
+ ],
54
58
  "prompts": [],
55
59
  "themes": []
56
60
  }
@@ -207,14 +207,3 @@ async function resolveProviderKey(
207
207
  const envName = `${provider.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_API_KEY`;
208
208
  return process.env[envName] || undefined;
209
209
  }
210
-
211
- /** Provider ids pi-ai can currently generate with, after registration. */
212
- export function registeredProviderIds(images: {
213
- getProviders?: () => ReadonlyArray<{ id: string }>;
214
- }): string[] {
215
- try {
216
- return (images.getProviders?.() ?? []).map((p) => p.id);
217
- } catch {
218
- return [];
219
- }
220
- }
package/src/tools.ts CHANGED
@@ -19,7 +19,6 @@ import {
19
19
  listAllImageGenModels,
20
20
  resolveImageGenModel,
21
21
  resolveVisionModel,
22
- splitModelRef,
23
22
  type ChatModelRegistry,
24
23
  type VisionModel,
25
24
  } from "./models.js";
@@ -325,5 +324,3 @@ function currentSessionModel(ctx: ExtensionContext): string {
325
324
  }
326
325
  return "";
327
326
  }
328
-
329
- export { splitModelRef };
@@ -6,9 +6,9 @@
6
6
  */
7
7
 
8
8
  import type { Component } from "@earendil-works/pi-tui";
9
- import { matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
9
+ import { matchesKey } from "@earendil-works/pi-tui";
10
10
  import type { Theme } from "@earendil-works/pi-coding-agent";
11
- import { boxInnerWidth, safeRepeat } from "@pi-unipi/core";
11
+ import { boxInnerWidth, OverlayTheme } from "@pi-unipi/core";
12
12
 
13
13
  export interface SelectableModel {
14
14
  provider: string;
@@ -29,7 +29,7 @@ export class ImageModelSelectorOverlay implements Component {
29
29
  private filterMode = false;
30
30
  private saved = false;
31
31
  private error: string | null = null;
32
- private theme: Theme | null = null;
32
+ private overlay = new OverlayTheme();
33
33
  /** Free-text entry, for models the catalog does not know about. */
34
34
  private customMode = false;
35
35
  private custom = "";
@@ -55,7 +55,7 @@ export class ImageModelSelectorOverlay implements Component {
55
55
  }
56
56
 
57
57
  setTheme(theme: Theme): void {
58
- this.theme = theme;
58
+ this.overlay.setTheme(theme);
59
59
  }
60
60
 
61
61
  invalidate(): void {}
@@ -229,41 +229,6 @@ export class ImageModelSelectorOverlay implements Component {
229
229
  this.onClose?.();
230
230
  }
231
231
 
232
- // ─── Theme helpers ───────────────────────────────────────────────────
233
-
234
- private fg(color: string, text: string): string {
235
- if (this.theme) return this.theme.fg(color as never, text);
236
- const codes: Record<string, string> = {
237
- accent: "\x1b[36m",
238
- success: "\x1b[32m",
239
- warning: "\x1b[33m",
240
- error: "\x1b[31m",
241
- dim: "\x1b[2m",
242
- borderMuted: "\x1b[90m",
243
- };
244
- return `${codes[color] ?? ""}${text}\x1b[0m`;
245
- }
246
-
247
- private bold(text: string): string {
248
- return this.theme ? this.theme.bold(text) : `\x1b[1m${text}\x1b[0m`;
249
- }
250
-
251
- private frameLine(content: string, innerWidth: number): string {
252
- const truncated = truncateToWidth(content, innerWidth, "");
253
- const padding = safeRepeat(" ", innerWidth - visibleWidth(truncated));
254
- return `${this.fg("borderMuted", "│")}${truncated}${padding}${this.fg("borderMuted", "│")}`;
255
- }
256
-
257
- private ruleLine(innerWidth: number): string {
258
- return this.fg("borderMuted", `├${safeRepeat("─", innerWidth)}┤`);
259
- }
260
-
261
- private borderLine(innerWidth: number, edge: "top" | "bottom"): string {
262
- const left = edge === "top" ? "┌" : "└";
263
- const right = edge === "top" ? "┐" : "┘";
264
- return this.fg("borderMuted", `${left}${safeRepeat("─", innerWidth)}${right}`);
265
- }
266
-
267
232
  render(width: number): string[] {
268
233
  const innerWidth = boxInnerWidth(width);
269
234
  const lines: string[] = [];
@@ -277,48 +242,48 @@ export class ImageModelSelectorOverlay implements Component {
277
242
  ? "Model used by image_generate"
278
243
  : "Vision model used by image_recognize";
279
244
 
280
- lines.push(this.borderLine(innerWidth, "top"));
281
- lines.push(this.frameLine(this.fg("accent", this.bold(title)), innerWidth));
282
- lines.push(this.frameLine(this.fg("dim", subtitle), innerWidth));
283
- lines.push(this.ruleLine(innerWidth));
245
+ lines.push(this.overlay.borderLine(innerWidth, "top"));
246
+ lines.push(this.overlay.frameLine(this.overlay.fg("accent", this.overlay.bold(title)), innerWidth));
247
+ lines.push(this.overlay.frameLine(this.overlay.fg("dim", subtitle), innerWidth));
248
+ lines.push(this.overlay.ruleLine(innerWidth));
284
249
 
285
250
  // Filter bar
286
251
  if (this.customMode) {
287
252
  lines.push(
288
- this.frameLine(
289
- ` ${this.fg("accent", "Model:")} ${this.custom}${this.fg("accent", "█")}`,
253
+ this.overlay.frameLine(
254
+ ` ${this.overlay.fg("accent", "Model:")} ${this.custom}${this.overlay.fg("accent", "█")}`,
290
255
  innerWidth,
291
256
  ),
292
257
  );
293
258
  lines.push(
294
- this.frameLine(
295
- ` ${this.fg("dim", "e.g. omniroute/fal/fal-ai/flux-2-pro")}`,
259
+ this.overlay.frameLine(
260
+ ` ${this.overlay.fg("dim", "e.g. omniroute/fal/fal-ai/flux-2-pro")}`,
296
261
  innerWidth,
297
262
  ),
298
263
  );
299
264
  } else if (this.filterMode) {
300
265
  lines.push(
301
- this.frameLine(
302
- ` ${this.fg("accent", "Filter:")} ${this.filter}${this.fg("accent", "█")}`,
266
+ this.overlay.frameLine(
267
+ ` ${this.overlay.fg("accent", "Filter:")} ${this.filter}${this.overlay.fg("accent", "█")}`,
303
268
  innerWidth,
304
269
  ),
305
270
  );
306
271
  } else if (this.filter) {
307
272
  lines.push(
308
- this.frameLine(
309
- ` ${this.fg("dim", "Filter:")} ${this.filter} ${this.fg("dim", "(press / to edit)")}`,
273
+ this.overlay.frameLine(
274
+ ` ${this.overlay.fg("dim", "Filter:")} ${this.filter} ${this.overlay.fg("dim", "(press / to edit)")}`,
310
275
  innerWidth,
311
276
  ),
312
277
  );
313
278
  } else {
314
279
  lines.push(
315
- this.frameLine(
316
- ` ${this.fg("dim", `${this.models.length} models · / filter · c custom`)}`,
280
+ this.overlay.frameLine(
281
+ ` ${this.overlay.fg("dim", `${this.models.length} models · / filter · c custom`)}`,
317
282
  innerWidth,
318
283
  ),
319
284
  );
320
285
  }
321
- lines.push(this.ruleLine(innerWidth));
286
+ lines.push(this.overlay.ruleLine(innerWidth));
322
287
 
323
288
  // Model list
324
289
  const terminalRows = process.stdout.rows ?? 30;
@@ -335,47 +300,47 @@ export class ImageModelSelectorOverlay implements Component {
335
300
  ? "No image models available (needs OpenRouter)"
336
301
  : "No vision-capable models configured"
337
302
  : "No models match the filter";
338
- lines.push(this.frameLine(` ${this.fg("dim", empty)}`, innerWidth));
303
+ lines.push(this.overlay.frameLine(` ${this.overlay.fg("dim", empty)}`, innerWidth));
339
304
  } else {
340
305
  for (let i = start; i < end; i++) {
341
306
  const model = this.filtered[i];
342
307
  const isSelected = i === this.selectedIndex;
343
- const marker = isSelected ? this.fg("accent", "▸") : " ";
308
+ const marker = isSelected ? this.overlay.fg("accent", "▸") : " ";
344
309
  const label = model.name || model.id;
345
- const providerTag = this.fg("dim", `[${model.provider}]`);
310
+ const providerTag = this.overlay.fg("dim", `[${model.provider}]`);
346
311
  const base = isSelected
347
- ? `${providerTag} ${this.bold(label)}`
348
- : `${providerTag} ${this.fg("dim", label)}`;
312
+ ? `${providerTag} ${this.overlay.bold(label)}`
313
+ : `${providerTag} ${this.overlay.fg("dim", label)}`;
349
314
  const display = model.unavailable
350
- ? `${base} ${this.fg("warning", `(${model.unavailable})`)}`
315
+ ? `${base} ${this.overlay.fg("warning", `(${model.unavailable})`)}`
351
316
  : base;
352
- lines.push(this.frameLine(` ${marker} ${display}`, innerWidth));
317
+ lines.push(this.overlay.frameLine(` ${marker} ${display}`, innerWidth));
353
318
  }
354
319
  }
355
320
 
356
321
  if (!this.customMode && this.filtered.length > maxVisible) {
357
322
  const pct = Math.round(((this.selectedIndex + 1) / this.filtered.length) * 100);
358
323
  lines.push(
359
- this.frameLine(
360
- this.fg("dim", ` ${pct}% (${this.selectedIndex + 1}/${this.filtered.length})`),
324
+ this.overlay.frameLine(
325
+ this.overlay.fg("dim", ` ${pct}% (${this.selectedIndex + 1}/${this.filtered.length})`),
361
326
  innerWidth,
362
327
  ),
363
328
  );
364
329
  }
365
330
 
366
331
  if (this.error) {
367
- lines.push(this.ruleLine(innerWidth));
368
- lines.push(this.frameLine(` ${this.fg("error", `⚠ ${this.error}`)}`, innerWidth));
332
+ lines.push(this.overlay.ruleLine(innerWidth));
333
+ lines.push(this.overlay.frameLine(` ${this.overlay.fg("error", `⚠ ${this.error}`)}`, innerWidth));
369
334
  }
370
335
  if (this.saved) {
371
- lines.push(this.ruleLine(innerWidth));
372
- lines.push(this.frameLine(` ${this.fg("success", "✓ Model saved")}`, innerWidth));
336
+ lines.push(this.overlay.ruleLine(innerWidth));
337
+ lines.push(this.overlay.frameLine(` ${this.overlay.fg("success", "✓ Model saved")}`, innerWidth));
373
338
  }
374
339
 
375
- lines.push(this.ruleLine(innerWidth));
340
+ lines.push(this.overlay.ruleLine(innerWidth));
376
341
  lines.push(
377
- this.frameLine(
378
- this.fg(
342
+ this.overlay.frameLine(
343
+ this.overlay.fg(
379
344
  "dim",
380
345
  this.customMode
381
346
  ? "Enter save · Esc back to list"
@@ -384,7 +349,7 @@ export class ImageModelSelectorOverlay implements Component {
384
349
  innerWidth,
385
350
  ),
386
351
  );
387
- lines.push(this.borderLine(innerWidth, "bottom"));
352
+ lines.push(this.overlay.borderLine(innerWidth, "bottom"));
388
353
 
389
354
  return lines;
390
355
  }