@zigai/pi-mode 0.2.0 → 0.3.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
@@ -18,12 +18,14 @@ pi install npm:@zigai/pi-mode
18
18
  - Adds `Ctrl+Shift+M` to select a mode.
19
19
  - Adds `Ctrl+Space` to cycle modes.
20
20
  - Can show the current mode in the prompt editor border when enabled.
21
- - Colors the prompt editor border from the active mode or thinking level.
21
+ - Colors the prompt editor border from the active mode, with an opt-in setting for thinking-derived border colors.
22
22
 
23
23
  Modes can store a provider, model, thinking level, and optional color. Project-local modes live in `.pi/modes.json` when present; otherwise global modes live in `~/.pi/agent/modes.json`.
24
24
 
25
25
  By default, Pi Mode does not print the mode name in the editor border. To opt in, toggle `Show mode name` from `/mode` → `Configure modes…`, or set `"modeShowName": true` in global `~/.pi/agent/settings.json` or trusted project `.pi/settings.json`. The `/mode` toggle writes to global settings.
26
26
 
27
+ By default, Pi Mode uses the normal editor border color when the active mode does not define an explicit `color`. To opt in to thinking-derived border colors, toggle `Thinking border colors` from `/mode` → `Configure modes…`, or set `"modeUseThinkingBorderColors": true` in global `~/.pi/agent/settings.json` or trusted project `.pi/settings.json`. Explicit per-mode `color` values still apply.
28
+
27
29
  ## License
28
30
 
29
31
  MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zigai/pi-mode",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Pi package that adds prompt modes for model and thinking-level switching.",
5
5
  "keywords": [
6
6
  "mode",
package/src/constants.ts CHANGED
@@ -7,6 +7,8 @@ export const MODE_UI_CONFIGURE = "Configure modes…";
7
7
  export const MODE_UI_ADD = "Add mode…";
8
8
  export const MODE_UI_SHOW_NAME_ON = "Show mode name: on";
9
9
  export const MODE_UI_SHOW_NAME_OFF = "Show mode name: off";
10
+ export const MODE_UI_THINKING_COLORS_ON = "Thinking border colors: on";
11
+ export const MODE_UI_THINKING_COLORS_OFF = "Thinking border colors: off";
10
12
  export const MODE_UI_BACK = "Back";
11
13
 
12
14
  export const ALL_THINKING_LEVELS: ThinkingLevel[] = [
package/src/editor.ts CHANGED
@@ -63,6 +63,7 @@ function enhanceEditor(
63
63
  requestRender: () => void,
64
64
  ): EditorLike {
65
65
  const originalRender = editor.render.bind(editor);
66
+ const defaultBorderColor = editor.borderColor;
66
67
  editor.render = (width: number) => modeLabelLine(originalRender(width), width, editor);
67
68
 
68
69
  const borderColor = (text: string) => {
@@ -70,7 +71,7 @@ function enhanceEditor(
70
71
  if (isBashMode) {
71
72
  return ctx.ui.theme.getBashModeBorderColor()(text);
72
73
  }
73
- return getModeBorderColor(ctx, pi, getCurrentMode())(text);
74
+ return getModeBorderColor(ctx, pi, getCurrentMode(), defaultBorderColor)(text);
74
75
  };
75
76
 
76
77
  Object.defineProperty(editor, "borderColor", {
package/src/index.ts CHANGED
@@ -33,8 +33,11 @@ export default function (pi: ExtensionAPI) {
33
33
 
34
34
  pi.on("session_start", async (_event, ctx) => {
35
35
  setSettingsContext(ctx);
36
- await handleSessionActivated(pi, ctx);
36
+ // Install the editor wrapper before loading mode files so startup renders use
37
+ // the configured border color immediately instead of briefly showing Pi's
38
+ // thinking-level border color.
37
39
  applyModeEditor(pi, ctx);
40
+ await handleSessionActivated(pi, ctx);
38
41
  });
39
42
 
40
43
  pi.on("model_select", async (event, ctx) => {
package/src/mode-state.ts CHANGED
@@ -18,6 +18,8 @@ import {
18
18
  MODE_UI_CONFIGURE,
19
19
  MODE_UI_SHOW_NAME_OFF,
20
20
  MODE_UI_SHOW_NAME_ON,
21
+ MODE_UI_THINKING_COLORS_OFF,
22
+ MODE_UI_THINKING_COLORS_ON,
21
23
  THINKING_UNSET_LABEL,
22
24
  } from "./constants.ts";
23
25
  import {
@@ -28,7 +30,12 @@ import {
28
30
  getProjectModesPath,
29
31
  withFileLock,
30
32
  } from "./storage.ts";
31
- import { setShowModeName, shouldShowModeName } from "./settings.ts";
33
+ import {
34
+ setShowModeName,
35
+ setUseThinkingBorderColors,
36
+ shouldShowModeName,
37
+ shouldUseThinkingBorderColors,
38
+ } from "./settings.ts";
32
39
  import type { ModeRuntime, ModesFile, ModesPatch, ModeSpec, ModeSpecPatch } from "./types.ts";
33
40
 
34
41
  type ScopedModelItem = {
@@ -403,6 +410,7 @@ export function getModeBorderColor(
403
410
  ctx: ExtensionContext,
404
411
  pi: ExtensionAPI,
405
412
  mode: string,
413
+ fallbackBorderColor?: (text: string) => string,
406
414
  ): (text: string) => string {
407
415
  const theme = ctx.ui.theme;
408
416
  const spec = runtime.data.modes[mode];
@@ -413,10 +421,15 @@ export function getModeBorderColor(
413
421
  theme.getFgAnsi(color);
414
422
  return (text: string) => theme.fg(color, text);
415
423
  } catch {
416
- // fall back to thinking-derived colors
424
+ // fall through to the configured fallback border color
417
425
  }
418
426
  }
419
427
 
428
+ if (!shouldUseThinkingBorderColors()) {
429
+ if (fallbackBorderColor !== undefined) return fallbackBorderColor;
430
+ return (text: string) => theme.fg("borderMuted", text);
431
+ }
432
+
420
433
  return theme.getThinkingBorderColor(pi.getThinkingLevel());
421
434
  }
422
435
 
@@ -854,10 +867,15 @@ async function configureModesUI(pi: ExtensionAPI, ctx: ExtensionContext): Promis
854
867
  if (shouldShowModeName()) {
855
868
  showModeNameChoice = MODE_UI_SHOW_NAME_ON;
856
869
  }
870
+ let thinkingColorsChoice = MODE_UI_THINKING_COLORS_OFF;
871
+ if (shouldUseThinkingBorderColors()) {
872
+ thinkingColorsChoice = MODE_UI_THINKING_COLORS_ON;
873
+ }
857
874
  const choice = await ctx.ui.select("Configure modes", [
858
875
  ...names,
859
876
  MODE_UI_ADD,
860
877
  showModeNameChoice,
878
+ thinkingColorsChoice,
861
879
  MODE_UI_BACK,
862
880
  ]);
863
881
  if (choice === undefined || choice.length === 0 || choice === MODE_UI_BACK) return;
@@ -887,6 +905,26 @@ async function configureModesUI(pi: ExtensionAPI, ctx: ExtensionContext): Promis
887
905
  continue;
888
906
  }
889
907
 
908
+ if (choice === MODE_UI_THINKING_COLORS_ON || choice === MODE_UI_THINKING_COLORS_OFF) {
909
+ const next = !shouldUseThinkingBorderColors();
910
+ try {
911
+ setUseThinkingBorderColors(next);
912
+ } catch (error: unknown) {
913
+ ctx.ui.notify(
914
+ `Thinking border colors were not saved: ${errorMessage(error)}`,
915
+ "error",
916
+ );
917
+ continue;
918
+ }
919
+ requestEditorRender?.();
920
+ let displayState = "disabled";
921
+ if (next) {
922
+ displayState = "enabled";
923
+ }
924
+ ctx.ui.notify(`Thinking border colors ${displayState}`, "info");
925
+ continue;
926
+ }
927
+
890
928
  await editModeUI(pi, ctx, choice);
891
929
  }
892
930
  }
@@ -1019,6 +1057,10 @@ export async function handleSessionActivated(
1019
1057
  runtime.currentMode = CUSTOM_MODE_NAME;
1020
1058
  customOverlay = getCurrentSelectionSpec(pi);
1021
1059
  }
1060
+
1061
+ if (ctx.hasUI) {
1062
+ requestEditorRender?.();
1063
+ }
1022
1064
  }
1023
1065
 
1024
1066
  export async function handleModelSelect(
package/src/settings.ts CHANGED
@@ -19,11 +19,12 @@ import { Type, type TSchema } from "typebox";
19
19
  import { Value } from "typebox/value";
20
20
 
21
21
  export const SHOW_MODE_NAME_SETTINGS_KEY = "modeShowName";
22
+ export const USE_THINKING_BORDER_COLORS_SETTINGS_KEY = "modeUseThinkingBorderColors";
22
23
 
23
24
  const SETTINGS_LOCK_TIMEOUT_MS = 5_000;
24
25
  const STALE_SETTINGS_LOCK_MS = 30_000;
25
26
  const SettingsObjectSchema = Type.Object({});
26
- const ShowModeNameSchema = Type.Boolean();
27
+ const BooleanSettingSchema = Type.Boolean();
27
28
 
28
29
  type SettingsReadContext = {
29
30
  cwd: string;
@@ -31,8 +32,13 @@ type SettingsReadContext = {
31
32
  };
32
33
 
33
34
  let settingsReadContext: SettingsReadContext | undefined;
34
- let cachedShowModeName: boolean | undefined;
35
- let cachedSettingsMtimeKey: string | undefined;
35
+ let cachedSettings:
36
+ | {
37
+ mtimeKey: string;
38
+ showModeName: boolean;
39
+ useThinkingBorderColors: boolean;
40
+ }
41
+ | undefined;
36
42
 
37
43
  type ProjectTrustContext = ExtensionContext & {
38
44
  isProjectTrusted?: () => boolean;
@@ -52,8 +58,7 @@ export function setSettingsContext(ctx: ExtensionContext): void {
52
58
  settingsReadContext.projectTrusted !== next.projectTrusted
53
59
  ) {
54
60
  settingsReadContext = next;
55
- cachedShowModeName = undefined;
56
- cachedSettingsMtimeKey = undefined;
61
+ cachedSettings = undefined;
57
62
  }
58
63
  }
59
64
 
@@ -243,29 +248,62 @@ function updateSettingsObject(update: (settings: Record<string, unknown>) => voi
243
248
  });
244
249
  }
245
250
 
246
- function applyShowModeNameSetting(settings: Record<string, unknown>, fallback: boolean): boolean {
247
- return (
248
- parseOptionalBoolean(ShowModeNameSchema, settings[SHOW_MODE_NAME_SETTINGS_KEY]) ?? fallback
249
- );
251
+ function applyBooleanSetting(
252
+ settings: Record<string, unknown>,
253
+ key: string,
254
+ fallback: boolean,
255
+ ): boolean {
256
+ const parsed = parseOptionalBoolean(BooleanSettingSchema, settings[key]);
257
+ if (parsed === undefined) return fallback;
258
+ return parsed;
250
259
  }
251
260
 
252
- export function shouldShowModeName(): boolean {
261
+ function readModeSettings(): {
262
+ showModeName: boolean;
263
+ useThinkingBorderColors: boolean;
264
+ } {
253
265
  const mtimeKey = getSettingsMtimeKey();
254
- if (cachedShowModeName !== undefined && cachedSettingsMtimeKey === mtimeKey) {
255
- return cachedShowModeName;
266
+ if (cachedSettings?.mtimeKey === mtimeKey) {
267
+ return cachedSettings;
256
268
  }
257
269
 
258
270
  const context = settingsReadContext ?? { cwd: process.cwd(), projectTrusted: false };
259
271
  const manager = SettingsManager.create(context.cwd, getAgentDir(), {
260
272
  projectTrusted: context.projectTrusted,
261
273
  });
262
- let show = false;
263
- show = applyShowModeNameSetting(manager.getGlobalSettings() as Record<string, unknown>, show);
264
- show = applyShowModeNameSetting(manager.getProjectSettings() as Record<string, unknown>, show);
274
+ let showModeName = false;
275
+ let useThinkingBorderColors = false;
276
+
277
+ const globalSettings = manager.getGlobalSettings() as Record<string, unknown>;
278
+ showModeName = applyBooleanSetting(globalSettings, SHOW_MODE_NAME_SETTINGS_KEY, showModeName);
279
+ useThinkingBorderColors = applyBooleanSetting(
280
+ globalSettings,
281
+ USE_THINKING_BORDER_COLORS_SETTINGS_KEY,
282
+ useThinkingBorderColors,
283
+ );
284
+
285
+ const projectSettings = manager.getProjectSettings() as Record<string, unknown>;
286
+ showModeName = applyBooleanSetting(projectSettings, SHOW_MODE_NAME_SETTINGS_KEY, showModeName);
287
+ useThinkingBorderColors = applyBooleanSetting(
288
+ projectSettings,
289
+ USE_THINKING_BORDER_COLORS_SETTINGS_KEY,
290
+ useThinkingBorderColors,
291
+ );
292
+
293
+ cachedSettings = {
294
+ mtimeKey,
295
+ showModeName,
296
+ useThinkingBorderColors,
297
+ };
298
+ return cachedSettings;
299
+ }
265
300
 
266
- cachedSettingsMtimeKey = mtimeKey;
267
- cachedShowModeName = show;
268
- return cachedShowModeName;
301
+ export function shouldShowModeName(): boolean {
302
+ return readModeSettings().showModeName;
303
+ }
304
+
305
+ export function shouldUseThinkingBorderColors(): boolean {
306
+ return readModeSettings().useThinkingBorderColors;
269
307
  }
270
308
 
271
309
  export function setShowModeName(show: boolean): void {
@@ -273,6 +311,13 @@ export function setShowModeName(show: boolean): void {
273
311
  settings[SHOW_MODE_NAME_SETTINGS_KEY] = show;
274
312
  });
275
313
 
276
- cachedSettingsMtimeKey = getSettingsMtimeKey();
277
- cachedShowModeName = show;
314
+ cachedSettings = undefined;
315
+ }
316
+
317
+ export function setUseThinkingBorderColors(useThinkingBorderColors: boolean): void {
318
+ updateSettingsObject((settings) => {
319
+ settings[USE_THINKING_BORDER_COLORS_SETTINGS_KEY] = useThinkingBorderColors;
320
+ });
321
+
322
+ cachedSettings = undefined;
278
323
  }
package/src/types.ts CHANGED
@@ -9,7 +9,8 @@ export type ModeSpec = {
9
9
  thinkingLevel?: ThinkingLevel;
10
10
  /**
11
11
  * Optional theme color token to use for the editor border.
12
- * If unset, the border color is derived from the current thinking level.
12
+ * If unset, the default editor border is used unless thinking-derived
13
+ * border colors are enabled in settings.
13
14
  */
14
15
  color?: ThemeColor;
15
16
  };