pi-better-openai 0.1.3 → 0.1.6

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
@@ -2,40 +2,18 @@
2
2
 
3
3
  A pi extension for OpenAI subscription workflows: fast mode, usage visibility, footer polish, and image generation through `openai-codex` auth.
4
4
 
5
- ## Screenshots
6
-
7
- <!-- Add screenshots here. -->
8
-
9
5
  ## Install
10
6
 
11
- Install from a local checkout:
7
+ Install from GitHub:
12
8
 
13
9
  ```bash
14
- pi install /path/to/pi-better-openai
10
+ pi install github:mattleong/pi-better-openai
15
11
  ```
16
12
 
17
- For a project-local install instead of a global install, add `-l`:
13
+ Or install from npm:
18
14
 
19
15
  ```bash
20
- pi install -l /path/to/pi-better-openai
21
- ```
22
-
23
- Reload pi after installing:
24
-
25
- ```text
26
- /reload
27
- ```
28
-
29
- Sign in to OpenAI Codex subscription auth if you want usage stats or image generation:
30
-
31
- ```text
32
- /login openai-codex
33
- ```
34
-
35
- Start pi with fast mode enabled:
36
-
37
- ```bash
38
- pi --fast
16
+ pi install npm:pi-better-openai
39
17
  ```
40
18
 
41
19
  ## Features
@@ -44,8 +22,16 @@ pi --fast
44
22
  - OpenAI subscription usage display via `/openai-usage` and the footer.
45
23
  - Interactive settings picker via `/openai-settings`.
46
24
  - Footer customization for model, thinking, fast mode, usage, and token/cost context.
47
- - OpenAI image generation/editing through the `openai_image` tool.
25
+ - OpenAI image generation/editing through the `openai_image` tool and `/openai-image` command.
48
26
  - Commands:
49
27
  - `/fast` toggles fast mode.
28
+ - `/openai-image <prompt>` generates an image directly.
50
29
  - `/openai-usage` shows current OpenAI subscription usage.
51
30
  - `/openai-settings` opens settings, diagnostics, and config details.
31
+
32
+ ## Screenshots
33
+
34
+ <!-- Add screenshots here. -->
35
+
36
+ <img width="983" height="851" alt="Screenshot 2026-04-29 at 11 53 23 PM" src="https://github.com/user-attachments/assets/07a2fb87-ef48-4396-8b12-124825c8d360" />
37
+ <img width="1327" height="102" alt="Screenshot 2026-04-29 at 11 34 49 PM" src="https://github.com/user-attachments/assets/22042782-c94e-491d-b5af-095f7f0810f9" />
@@ -1,13 +1,14 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { mkdir, readFile, writeFile } from "node:fs/promises";
3
3
  import { homedir } from "node:os";
4
- import { extname, isAbsolute, join, resolve } from "node:path";
4
+ import { extname, isAbsolute, join, resolve, sep } from "node:path";
5
5
  import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
6
6
  import type { ResolvedConfig } from "./config.ts";
7
7
  import { isRecord } from "./config.ts";
8
8
  import { readCodexAuth } from "./usage.ts";
9
9
 
10
10
  const OPENAI_IMAGE_TOOL = "openai_image";
11
+ const OPENAI_IMAGE_COMMAND = "openai-image";
11
12
  const CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
12
13
  const DEFAULT_TIMEOUT_MS = 180_000;
13
14
 
@@ -22,7 +23,7 @@ export type ImageOutputFormat = typeof IMAGE_OUTPUT_FORMATS[number];
22
23
  const TOOL_PARAMS = {
23
24
  type: "object",
24
25
  properties: {
25
- prompt: { type: "string", description: "Image generation/editing prompt." },
26
+ prompt: { type: "string", description: "Image generation/editing prompt. Pass the user's wording verbatim unless they explicitly ask you to refine or expand it." },
26
27
  action: { type: "string", enum: IMAGE_ACTIONS, description: "Whether to generate a new image, edit/reference provided images, or let the model decide." },
27
28
  images: {
28
29
  type: "array",
@@ -337,6 +338,39 @@ async function requestCodexImage(params: ToolParams, ctx: ExtensionContext, cfg:
337
338
  return { ...parsed, prompt: params.prompt, savedPath, model, action, outputFormat };
338
339
  }
339
340
 
341
+ function displayPath(path: string): string {
342
+ const home = homedir();
343
+ if (!home) return path;
344
+ if (path === home) return "~";
345
+ const homePrefix = home.endsWith(sep) ? home : `${home}${sep}`;
346
+ return path.startsWith(homePrefix) ? `~/${path.slice(homePrefix.length)}` : path;
347
+ }
348
+
349
+ function textFromMessageContent(content: unknown): string | undefined {
350
+ if (typeof content === "string") return content.trim() || undefined;
351
+ if (!Array.isArray(content)) return undefined;
352
+ const text = content
353
+ .filter((part) => isRecord(part) && part.type === "text" && typeof part.text === "string")
354
+ .map((part) => (part as { text: string }).text)
355
+ .join("\n")
356
+ .trim();
357
+ return text || undefined;
358
+ }
359
+
360
+ function latestUserPromptFromEntries(entries: unknown[]): string | undefined {
361
+ for (let i = entries.length - 1; i >= 0; i--) {
362
+ const entry = entries[i];
363
+ if (!isRecord(entry) || entry.type !== "message" || !isRecord(entry.message) || entry.message.role !== "user") continue;
364
+ const text = textFromMessageContent(entry.message.content);
365
+ if (text) return text;
366
+ }
367
+ return undefined;
368
+ }
369
+
370
+ function resolveToolPrompt(params: ToolParams, ctx: ExtensionContext): string {
371
+ return latestUserPromptFromEntries(ctx.sessionManager.getEntries()) ?? params.prompt;
372
+ }
373
+
340
374
  function resultText(result: CodexImageResult): string {
341
375
  const parts = [
342
376
  `Generated image via openai-codex/${result.model}.`,
@@ -344,7 +378,7 @@ function resultText(result: CodexImageResult): string {
344
378
  `Prompt: ${result.prompt}`
345
379
  ];
346
380
  if (result.revisedPrompt) parts.push(`Revised prompt: ${result.revisedPrompt}`);
347
- if (result.savedPath) parts.push(`Saved: ${result.savedPath}`);
381
+ if (result.savedPath) parts.push(`Saved: ${displayPath(result.savedPath)}`);
348
382
  return parts.join("\n");
349
383
  }
350
384
 
@@ -387,6 +421,57 @@ export function registerOpenAIImage(pi: ExtensionAPI, getConfig: (ctx: Extension
387
421
  };
388
422
  }
389
423
 
424
+ void import("@mariozechner/pi-tui").then(({ Box, Container, Image, Text }) => {
425
+ pi.registerMessageRenderer<CodexImageResult>("openai-image", (message, _options, theme) => {
426
+ const result = message.details;
427
+ const text = result && isRecord(result)
428
+ ? resultText(result as CodexImageResult)
429
+ : typeof message.content === "string"
430
+ ? message.content
431
+ : message.content.filter((part) => part.type === "text").map((part) => part.text).join("\n");
432
+ const image = result && isRecord(result) && typeof result.data === "string" && typeof result.mimeType === "string"
433
+ ? { data: result.data, mimeType: result.mimeType, savedPath: typeof result.savedPath === "string" ? result.savedPath : undefined }
434
+ : Array.isArray(message.content)
435
+ ? message.content.find((part) => part.type === "image" && typeof part.data === "string" && typeof part.mimeType === "string")
436
+ : undefined;
437
+
438
+ const container = new Container();
439
+ const box = new Box(1, 1, (line) => theme.bg("customMessageBg", line));
440
+ box.addChild(new Text(`${theme.fg("accent", theme.bold("[openai-image]"))}\n\n${text}`, 0, 0));
441
+ if (image) {
442
+ box.addChild(new Image(image.data, image.mimeType, { fallbackColor: (line) => theme.fg("dim", line) }, {
443
+ maxWidthCells: 80,
444
+ maxHeightCells: 24,
445
+ filename: "savedPath" in image && typeof image.savedPath === "string" ? image.savedPath : undefined
446
+ }));
447
+ }
448
+ container.addChild(box);
449
+ return container;
450
+ });
451
+ }).catch(() => undefined);
452
+
453
+ pi.registerCommand(OPENAI_IMAGE_COMMAND, {
454
+ description: "Generate an image with OpenAI Codex image generation",
455
+ handler: async (args, ctx) => {
456
+ const prompt = args.trim();
457
+ if (!prompt) {
458
+ ctx.ui.notify("Usage: /openai-image <prompt>", "error");
459
+ return;
460
+ }
461
+ ctx.ui.notify("Requesting OpenAI image...", "info");
462
+ const result = await generate({ prompt }, ctx);
463
+ pi.sendMessage({
464
+ customType: "openai-image",
465
+ content: [
466
+ { type: "text", text: resultText(result) },
467
+ { type: "image", data: result.data, mimeType: result.mimeType }
468
+ ],
469
+ display: true,
470
+ details: result
471
+ });
472
+ }
473
+ });
474
+
390
475
  pi.registerTool({
391
476
  name: OPENAI_IMAGE_TOOL,
392
477
  label: "OpenAI image",
@@ -394,14 +479,16 @@ export function registerOpenAIImage(pi: ExtensionAPI, getConfig: (ctx: Extension
394
479
  promptSnippet: "Generate or edit raster images via OpenAI Codex subscription auth.",
395
480
  promptGuidelines: [
396
481
  "Use openai_image when the user asks to generate or edit a raster image, photo, illustration, mockup, texture, sprite, or bitmap asset.",
482
+ "Pass the user's image prompt verbatim. Do not embellish, rewrite, add camera/style details, or add negative prompt terms unless the user explicitly asks you to refine the prompt.",
397
483
  "Use openai_image with images for local reference images or edit targets; save project assets into the workspace when requested."
398
484
  ],
399
485
  parameters: TOOL_PARAMS,
400
486
  async execute(_toolCallId, params: ToolParams, signal, onUpdate, ctx) {
401
487
  const cfg = getConfig(ctx);
402
488
  const model = resolveModel(params, ctx, cfg);
489
+ const requestParams = { ...params, prompt: resolveToolPrompt(params, ctx) };
403
490
  onUpdate?.({ content: [{ type: "text", text: `Requesting OpenAI image via openai-codex/${model}...` }] });
404
- const result = await generate(params, ctx, signal);
491
+ const result = await generate(requestParams, ctx, signal);
405
492
  return {
406
493
  content: [
407
494
  { type: "text", text: resultText(result) },
@@ -419,9 +506,12 @@ export const _imageTest = {
419
506
  CODEX_RESPONSES_URL,
420
507
  DEFAULT_TIMEOUT_MS,
421
508
  OPENAI_IMAGE_TOOL,
509
+ OPENAI_IMAGE_COMMAND,
422
510
  extractAccountIdFromJwt,
423
511
  imageMimeType,
424
512
  dataUrlParts,
425
513
  extractImageFromEvent,
514
+ displayPath,
515
+ latestUserPromptFromEntries,
426
516
  buildRequest
427
517
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-better-openai",
3
- "version": "0.1.3",
3
+ "version": "0.1.6",
4
4
  "description": "Personal pi extension that improves OpenAI with fast mode, usage stats, and footer polish.",
5
5
  "keywords": [
6
6
  "fast",