pi-openai-codex-compat 0.0.10-alpha.2 → 0.0.10-alpha.3

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
@@ -13,6 +13,11 @@
13
13
 
14
14
  ### Fixed
15
15
 
16
+ - Give the `/codex-settings` search field explicit focus that follows typed
17
+ input into search and Up/Down back to the result list, allowing Space to
18
+ change a filtered setting and applying accent color to the result cursor and
19
+ selected item only while that list has focus, with concise focus and action
20
+ labels.
16
21
  - Preserve regular-file permission modes across sequential `apply_patch`
17
22
  replacements and state-changing moves, including files created by earlier
18
23
  instructions in the same patch.
@@ -41,6 +46,8 @@
41
46
 
42
47
  ### Changed
43
48
 
49
+ - Replace the multi-line startup configuration dump with a concise package and
50
+ settings-command notice, and simplify the fallback settings-save error.
44
51
  - Align deferred tool loading with Pi 0.84.3, preferring message-anchored
45
52
  additional tools on capable Codex models while retaining tool-search and
46
53
  top-level fallbacks.
@@ -1,10 +1,5 @@
1
- import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
- import {
3
- DEFAULT_CONFIG,
4
- loadConfig,
5
- writableConfigPath,
6
- type CodexCompatConfig,
7
- } from "./config.ts";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import { DEFAULT_CONFIG, loadConfig, type CodexCompatConfig } from "./config.ts";
8
3
  import type { ConfigContext } from "./config-context.ts";
9
4
  import { registerCodexProvider } from "./codex-provider.ts";
10
5
  import { installCodexFooter } from "./footer.ts";
@@ -25,30 +20,6 @@ export {
25
20
  type OpenAICodexWebSocketDebugStats,
26
21
  } from "./codex-transport.ts";
27
22
 
28
- const DISPLAY_NAME = "OpenAI Codex Compat";
29
-
30
- function settingsSummary(ctx: ExtensionContext, config: CodexCompatConfig): string {
31
- return [
32
- DISPLAY_NAME,
33
- `fast mode: ${config.fastMode ? "on" : "off"}`,
34
- `Responses Lite: ${config.responsesLite ? "on" : "off"}`,
35
- `reasoning mode: ${config.reasoningMode}`,
36
- `Codex tool background: ${config.toolBackground}`,
37
- `apply_patch: ${config.applyPatch ? "on" : "off"}`,
38
- `apply_patch debug output: ${config.applyPatchDebug ? "on" : "off"}`,
39
- `apply_patch diagnostics capture: ${config.applyPatchDiagnostics ? "on" : "off"}`,
40
- `image_gen.imagegen: ${config.imageGeneration ? "on" : "off"}`,
41
- `image result detail: ${config.imageDetail}`,
42
- `web.run: ${config.webRun ? "on" : "off"}`,
43
- `web search: ${config.webSearch}`,
44
- `text verbosity: ${config.textVerbosity}`,
45
- `reasoning summary: ${config.reasoningSummary}`,
46
- `auto-compact threshold: ${config.autoCompactAtPercent ?? "Pi default"}`,
47
- `save target: ${writableConfigPath(ctx.cwd, ctx.isProjectTrusted())}`,
48
- "settings: /codex-settings (Enter saves & closes; Esc discards; Ctrl+S saves)",
49
- ].join("\n");
50
- }
51
-
52
23
  export default function registerOpenAICodexCompat(pi: ExtensionAPI): void {
53
24
  let activeConfig: CodexCompatConfig | undefined;
54
25
  const resolveConfig = (ctx: ConfigContext): CodexCompatConfig => {
@@ -65,7 +36,7 @@ export default function registerOpenAICodexCompat(pi: ExtensionAPI): void {
65
36
  activeConfig = loadConfig(ctx.cwd, ctx.isProjectTrusted());
66
37
  installCodexFooter(ctx, resolveConfig);
67
38
  if (event.reason !== "reload" && ctx.mode === "tui") {
68
- ctx.ui.notify(settingsSummary(ctx, activeConfig), "info");
39
+ ctx.ui.notify("pi-openai-codex-compat loaded · /codex-settings", "info");
69
40
  }
70
41
  });
71
42
 
@@ -9,6 +9,10 @@ import type { Api, Model } from "@earendil-works/pi-ai";
9
9
  import {
10
10
  type Component,
11
11
  Container,
12
+ decodeKittyPrintable,
13
+ type Focusable,
14
+ fuzzyFilter,
15
+ Input,
12
16
  Key,
13
17
  matchesKey,
14
18
  type SettingItem,
@@ -27,6 +31,7 @@ import {
27
31
  TEXT_VERBOSITY_SCHEMA,
28
32
  WEB_SEARCH_MODE_SCHEMA,
29
33
  configLayer,
34
+ globalConfigPath,
30
35
  loadConfig,
31
36
  parseEnvironmentConfig,
32
37
  saveConfig,
@@ -100,6 +105,16 @@ function thresholdValue(value: number | undefined): string {
100
105
  return value === undefined ? "Pi default" : `${value}%`;
101
106
  }
102
107
 
108
+ function isSearchTextInput(data: string): boolean {
109
+ if (decodeKittyPrintable(data) !== undefined || data.includes("\u001b[200~")) return true;
110
+ if (data.length === 0) return false;
111
+ for (let index = 0; index < data.length; index++) {
112
+ const code = data.charCodeAt(index);
113
+ if (code < 32 || code === 0x7f || (code >= 0x80 && code <= 0x9f)) return false;
114
+ }
115
+ return true;
116
+ }
117
+
103
118
  export function settingItems(
104
119
  config: CodexCompatConfig,
105
120
  environmentConfig: ConfigLayer = {},
@@ -322,8 +337,11 @@ async function showSettings(
322
337
  let revision = 0;
323
338
  let savedRevision = 0;
324
339
  let saveQueue = Promise.resolve();
325
- const filePath = writableConfigPath(ctx.cwd, ctx.isProjectTrusted());
326
340
  const environmentConfig = parseEnvironmentConfig();
341
+ const settingsScope =
342
+ writableConfigPath(ctx.cwd, ctx.isProjectTrusted()) === globalConfigPath()
343
+ ? "global"
344
+ : "project";
327
345
  const changeContext = (): SettingsChangeContext => {
328
346
  return {
329
347
  model: selectedRegistryModel(ctx),
@@ -335,13 +353,13 @@ async function showSettings(
335
353
  let closing = false;
336
354
  const container = new Container();
337
355
  container.addChild(
338
- new Text(theme.fg("accent", theme.bold("OpenAI Codex Compatibility Settings")), 1, 1),
339
- );
340
- container.addChild(
341
- new Text(theme.fg("dim", `Ctrl+S saves without closing to ${filePath}`), 1, 0),
356
+ new Text(theme.fg("accent", theme.bold("Codex Settings (pi-openai-codex-compat)")), 1, 1),
342
357
  );
343
358
  const saveStatus = new Text(
344
- theme.fg("dim", "Changes apply to this session immediately."),
359
+ theme.fg(
360
+ "dim",
361
+ `Changes apply to the session immediately · Ctrl+S updates ${settingsScope} settings`,
362
+ ),
345
363
  1,
346
364
  0,
347
365
  );
@@ -372,10 +390,7 @@ async function showSettings(
372
390
  : theme.fg("warning", "Unsaved session changes."),
373
391
  );
374
392
  } catch (cause) {
375
- const error = errorFromThrown(
376
- cause,
377
- "Saving Codex settings failed with a non-Error value.",
378
- );
393
+ const error = errorFromThrown(cause, "Could not save Codex settings.");
379
394
  if (closeAfterSave) closing = false;
380
395
  const message = error.message;
381
396
  saveStatus.setText(theme.fg("error", message));
@@ -402,42 +417,112 @@ async function showSettings(
402
417
  };
403
418
 
404
419
  const listTheme = getSettingsListTheme();
405
- const list = new SettingsList(
406
- settingItems(config, environmentConfig),
407
- 12,
408
- listTheme,
409
- (id, value) => {
410
- const patch = settingPatch(id, value);
411
- if (!patch) return;
412
-
413
- config = applySettingPatch(config, patch);
414
- revision++;
415
- callbacks.onChange?.(config, changeContext());
416
- saveStatus.setText(theme.fg("warning", "Unsaved session changes."));
417
- tui.requestRender();
420
+ const items = settingItems(config, environmentConfig);
421
+ const searchInput = new Input();
422
+ let rootFocused = false;
423
+ let searchFocused = false;
424
+ let filteredItems = items;
425
+ const resultListTheme = {
426
+ ...listTheme,
427
+ label: (text: string, selected: boolean) =>
428
+ searchFocused && selected ? theme.fg("text", text) : listTheme.label(text, selected),
429
+ value: (text: string, selected: boolean) =>
430
+ searchFocused && selected ? theme.fg("text", text) : listTheme.value(text, selected),
431
+ get cursor(): string {
432
+ return searchFocused ? "→ " : listTheme.cursor;
418
433
  },
419
- discardAndClose,
420
- { enableSearch: true },
421
- );
422
- container.addChild({
434
+ };
435
+ const changeSetting = (id: string, value: string): void => {
436
+ const patch = settingPatch(id, value);
437
+ if (!patch) return;
438
+
439
+ config = applySettingPatch(config, patch);
440
+ revision++;
441
+ callbacks.onChange?.(config, changeContext());
442
+ saveStatus.setText(theme.fg("warning", "Unsaved session changes."));
443
+ tui.requestRender();
444
+ };
445
+ const createList = (): SettingsList =>
446
+ new SettingsList(filteredItems, 12, resultListTheme, changeSetting, discardAndClose);
447
+ let list = createList();
448
+
449
+ const setSearchFocused = (focused: boolean): void => {
450
+ searchFocused = focused;
451
+ searchInput.focused = rootFocused && searchFocused;
452
+ };
453
+
454
+ const updateSearch = (data: string): void => {
455
+ const previousQuery = searchInput.getValue();
456
+ searchInput.handleInput(data);
457
+ const query = searchInput.getValue();
458
+ if (query === previousQuery) return;
459
+ filteredItems = fuzzyFilter(items, query, (item) => item.label);
460
+ list = createList();
461
+ };
462
+
463
+ const settingsSelector: Component = {
423
464
  render: (width: number) => {
424
- const lines = list.render(width);
425
- if (lines.length > 0) {
426
- lines[lines.length - 1] = truncateToWidth(
427
- listTheme.hint(
428
- " Type to search · Space changes · Enter save & close · Esc discard & close",
429
- ),
430
- width,
431
- "",
432
- );
465
+ const query = searchInput.getValue();
466
+ const searchLabel = searchFocused
467
+ ? theme.fg("accent", theme.bold("Search"))
468
+ : theme.fg("dim", "Search");
469
+ const searchLines = searchFocused
470
+ ? searchInput.render(width)
471
+ : [truncateToWidth(listTheme.hint(query.length > 0 ? ` ${query}` : ""), width, "")];
472
+ const listLines =
473
+ filteredItems.length > 0
474
+ ? list.render(width)
475
+ : [truncateToWidth(listTheme.hint(" No matching settings"), width, ""), ""];
476
+ const hint = searchFocused
477
+ ? " Type to filter · ↑↓ results · Tab switch · Enter save · Esc discard"
478
+ : " ↑↓ navigate · Type to filter · Space change · Tab search · Enter save · Esc discard";
479
+ if (listLines.length > 0) {
480
+ listLines[listLines.length - 1] = truncateToWidth(listTheme.hint(hint), width, "");
433
481
  }
434
- return lines;
482
+ return [truncateToWidth(searchLabel, width, ""), ...searchLines, "", ...listLines];
483
+ },
484
+ invalidate: () => {
485
+ searchInput.invalidate();
486
+ list.invalidate();
435
487
  },
436
- invalidate: () => list.invalidate(),
437
- handleInput: (data: string) => list.handleInput(data),
438
- });
488
+ handleInput: (data: string) => {
489
+ if (matchesKey(data, Key.tab) || matchesKey(data, Key.shift("tab"))) {
490
+ setSearchFocused(!searchFocused);
491
+ return;
492
+ }
493
+ if (
494
+ searchFocused &&
495
+ (matchesKey(data, Key.up) ||
496
+ matchesKey(data, Key.down) ||
497
+ keybindings.matches(data, "tui.select.up") ||
498
+ keybindings.matches(data, "tui.select.down"))
499
+ ) {
500
+ setSearchFocused(false);
501
+ list.handleInput(data);
502
+ return;
503
+ }
504
+ if (!searchFocused) {
505
+ if (!matchesKey(data, Key.space) && isSearchTextInput(data)) {
506
+ setSearchFocused(true);
507
+ updateSearch(data);
508
+ return;
509
+ }
510
+ list.handleInput(data);
511
+ return;
512
+ }
513
+ updateSearch(data);
514
+ },
515
+ };
516
+ container.addChild(settingsSelector);
439
517
 
440
- return {
518
+ const component: Component & Focusable = {
519
+ get focused() {
520
+ return rootFocused;
521
+ },
522
+ set focused(focused: boolean) {
523
+ rootFocused = focused;
524
+ searchInput.focused = rootFocused && searchFocused;
525
+ },
441
526
  render: (width: number) => container.render(width),
442
527
  invalidate: () => container.invalidate(),
443
528
  handleInput: (data: string) => {
@@ -454,10 +539,11 @@ async function showSettings(
454
539
  discardAndClose();
455
540
  return;
456
541
  }
457
- list.handleInput(data);
542
+ settingsSelector.handleInput?.(data);
458
543
  tui.requestRender();
459
544
  },
460
545
  };
546
+ return component;
461
547
  });
462
548
 
463
549
  await saveQueue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-openai-codex-compat",
3
- "version": "0.0.10-alpha.2",
3
+ "version": "0.0.10-alpha.3",
4
4
  "description": "OpenAI Codex compatibility for Pi with native compaction, fast mode, and Codex-optimized capabilities",
5
5
  "keywords": [
6
6
  "pi-package"
@@ -41,6 +41,7 @@
41
41
  },
42
42
  "devDependencies": {
43
43
  "@2h2d/oxlint-config": "0.1.0",
44
+ "@2h2d/ts-config": "0.0.1",
44
45
  "@earendil-works/pi-ai": "0.84.3",
45
46
  "@earendil-works/pi-coding-agent": "0.84.3",
46
47
  "@earendil-works/pi-tui": "0.84.3",