@marckrenn/pi-sub-bar 1.0.6 → 1.1.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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @marckrenn/pi-sub-bar
2
2
 
3
+ ## 1.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`e9c1c39`](https://github.com/marckrenn/pi-sub/commit/e9c1c394286b302e018c2c824d16978b2b4d3d44) Thanks [@plesiv](https://github.com/plesiv)! - Make keybindings configurable
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [[`7ce2a92`](https://github.com/marckrenn/pi-sub/commit/7ce2a92b15e766fd85a4b7eb85d6fc5c5aa32dca)]:
12
+ - @marckrenn/pi-sub-core@1.1.0
13
+ - @marckrenn/pi-sub-shared@1.1.0
14
+
3
15
  ## 1.0.6
4
16
 
5
17
  ### Patch Changes
package/README.md CHANGED
@@ -94,8 +94,12 @@ The extension loads automatically. Use:
94
94
  - `sub-bar:settings` - Open display + provider UI settings (includes Provider Shown)
95
95
  - `sub-bar:import <share string>` - Preview a shared theme and choose to save/apply
96
96
  - `sub-core:settings` - Configure provider enablement/order + usage/status refresh settings
97
- - `Ctrl+Alt+P` - Cycle through available providers
98
- - `Ctrl+Alt+R` - Toggle reset timer format (relative vs datetime)
97
+ - `Ctrl+Alt+P` - Cycle through available providers (configurable)
98
+ - `Ctrl+Alt+R` - Toggle reset timer format (configurable)
99
+
100
+ **Keybindings:**
101
+
102
+ Shortcuts are configurable via `sub-bar:settings` → Keybindings. Enter any valid key combo (e.g. `ctrl+alt+p`, `ctrl+shift+s`) or `none` to disable a shortcut. Keybinding changes take effect after pi restart.
99
103
 
100
104
  **Caching:**
101
105
  - Handled by sub-core at `~/.pi/agent/cache/sub-core/cache.json`
package/index.ts CHANGED
@@ -10,10 +10,11 @@ import * as fs from "node:fs";
10
10
  import { homedir, tmpdir } from "node:os";
11
11
  import { join } from "node:path";
12
12
  import type { ProviderName, ProviderUsageEntry, SubCoreAllState, SubCoreState, UsageSnapshot } from "./src/types.js";
13
- import { getDefaultSettings, type Settings, type BaseTextColor } from "./src/settings-types.js";
13
+ import { type Settings, type BaseTextColor } from "./src/settings-types.js";
14
14
  import { isBackgroundColor, resolveBaseTextColor, resolveDividerColor } from "./src/settings-types.js";
15
15
  import { buildDividerLine } from "./src/dividers.js";
16
16
  import type { CoreSettings } from "@marckrenn/pi-sub-shared";
17
+ import type { KeyId } from "@mariozechner/pi-tui";
17
18
  import { formatUsageStatus, formatUsageStatusWithWidth } from "./src/formatting.js";
18
19
  import { clearSettingsCache, loadSettings, saveSettings, SETTINGS_PATH } from "./src/settings.js";
19
20
  import { showSettingsUI } from "./src/settings-ui.js";
@@ -124,7 +125,7 @@ function loadScopedModelPatterns(cwd: string): string[] {
124
125
  */
125
126
  export default function createExtension(pi: ExtensionAPI) {
126
127
  let lastContext: ExtensionContext | undefined;
127
- let settings: Settings = getDefaultSettings();
128
+ let settings: Settings = loadSettings();
128
129
  let uiEnabled = true;
129
130
  let currentUsage: UsageSnapshot | undefined;
130
131
  let usageEntries: Partial<Record<ProviderName, UsageSnapshot>> = {};
@@ -399,6 +400,7 @@ export default function createExtension(pi: ExtensionAPI) {
399
400
  displayThemes: loaded.displayThemes,
400
401
  displayUserTheme: loaded.displayUserTheme,
401
402
  pinnedProvider: loaded.pinnedProvider,
403
+ keybindings: loaded.keybindings,
402
404
  };
403
405
  coreSettings = getFallbackCoreSettings(settings);
404
406
  updateFetchFailureTicker();
@@ -547,7 +549,7 @@ export default function createExtension(pi: ExtensionAPI) {
547
549
  }),
548
550
  { placement: settings.display.widgetPlacement ?? "belowEditor" },
549
551
  );
550
-
552
+
551
553
  }
552
554
 
553
555
  function resolveDisplayedUsage(): UsageSnapshot | undefined {
@@ -905,24 +907,30 @@ export default function createExtension(pi: ExtensionAPI) {
905
907
  });
906
908
 
907
909
  // Register shortcut to cycle providers
908
- pi.registerShortcut("ctrl+alt+p", {
909
- description: "Cycle usage provider",
910
- handler: async () => {
911
- emitCoreAction({ type: "cycleProvider" });
912
- },
913
- });
910
+ const cycleProviderKey = settings.keybindings?.cycleProvider || "ctrl+alt+p";
911
+ if (cycleProviderKey !== "none") {
912
+ pi.registerShortcut(cycleProviderKey as KeyId, {
913
+ description: "Cycle usage provider",
914
+ handler: async () => {
915
+ emitCoreAction({ type: "cycleProvider" });
916
+ },
917
+ });
918
+ }
914
919
 
915
920
  // Register shortcut to toggle reset timer format
916
- pi.registerShortcut("ctrl+alt+r", {
917
- description: "Toggle reset timer format",
918
- handler: async () => {
919
- settings.display.resetTimeFormat = settings.display.resetTimeFormat === "datetime" ? "relative" : "datetime";
920
- saveSettings(settings);
921
- if (lastContext && currentUsage) {
922
- renderUsageWidget(lastContext, currentUsage);
923
- }
924
- },
925
- });
921
+ const toggleResetFormatKey = settings.keybindings?.toggleResetFormat || "ctrl+alt+r";
922
+ if (toggleResetFormatKey !== "none") {
923
+ pi.registerShortcut(toggleResetFormatKey as KeyId, {
924
+ description: "Toggle reset timer format",
925
+ handler: async () => {
926
+ settings.display.resetTimeFormat = settings.display.resetTimeFormat === "datetime" ? "relative" : "datetime";
927
+ saveSettings(settings);
928
+ if (lastContext && currentUsage) {
929
+ renderUsageWidget(lastContext, currentUsage);
930
+ }
931
+ },
932
+ });
933
+ }
926
934
 
927
935
  pi.on("session_start", async (_event, ctx) => {
928
936
  lastContext = ctx;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marckrenn/pi-sub-bar",
3
- "version": "1.0.6",
3
+ "version": "1.1.0",
4
4
  "description": "Usage widget extension for pi-coding-agent - shows current provider usage above the editor",
5
5
  "keywords": [
6
6
  "pi-package"
@@ -30,8 +30,8 @@
30
30
  "typescript": "^5.8.0"
31
31
  },
32
32
  "dependencies": {
33
- "@marckrenn/pi-sub-core": "^1.0.6",
34
- "@marckrenn/pi-sub-shared": "^1.0.6"
33
+ "@marckrenn/pi-sub-core": "^1.1.0",
34
+ "@marckrenn/pi-sub-shared": "^1.1.0"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "@mariozechner/pi-coding-agent": "*"
@@ -12,6 +12,8 @@ export type TooltipSelectItem = SelectItem & { tooltip?: string };
12
12
 
13
13
  export function buildMainMenuItems(settings: Settings, pinnedProvider?: ProviderName | null): TooltipSelectItem[] {
14
14
  const pinnedLabel = pinnedProvider ? PROVIDER_DISPLAY_NAMES[pinnedProvider] : "auto (current provider)";
15
+ const kb = settings.keybindings;
16
+ const kbDesc = `cycle: ${kb.cycleProvider}, reset: ${kb.toggleResetFormat}`;
15
17
  return [
16
18
  {
17
19
  value: "display-theme",
@@ -37,6 +39,12 @@ export function buildMainMenuItems(settings: Settings, pinnedProvider?: Provider
37
39
  description: pinnedLabel,
38
40
  tooltip: "Select which provider is shown in the widget.",
39
41
  },
42
+ {
43
+ value: "keybindings",
44
+ label: "Keybindings",
45
+ description: kbDesc,
46
+ tooltip: "Configure keyboard shortcuts. Changes take effect after pi restart.",
47
+ },
40
48
  {
41
49
  value: "open-core-settings",
42
50
  label: "Additional settings",
@@ -60,6 +60,7 @@ type SettingsCategory =
60
60
  | "providers"
61
61
  | "pin-provider"
62
62
  | ProviderCategory
63
+ | "keybindings"
63
64
  | "display"
64
65
  | "display-theme"
65
66
  | "display-theme-save"
@@ -341,6 +342,7 @@ export async function showSettingsUI(
341
342
  main: "sub-bar Settings",
342
343
  providers: "Provider Settings",
343
344
  "pin-provider": "Provider Shown",
345
+ keybindings: "Keybindings",
344
346
  display: "Adv. Display Settings",
345
347
  "display-theme": "Themes",
346
348
  "display-theme-save": "Save Theme",
@@ -453,6 +455,72 @@ export async function showSettingsUI(
453
455
  };
454
456
  activeList = selectList;
455
457
  container.addChild(selectList);
458
+ } else if (currentCategory === "keybindings") {
459
+ const parseKeybinding = (raw: string): string | null => {
460
+ const trimmed = raw.trim().toLowerCase();
461
+ if (!trimmed) {
462
+ ctx.ui.notify("Enter a key combo (e.g. ctrl+alt+p) or 'none' to disable", "warning");
463
+ return null;
464
+ }
465
+ if (trimmed === "none") return "none";
466
+ const parts = trimmed.split("+");
467
+ const modifiers = new Set(["ctrl", "shift", "alt"]);
468
+ const baseKeys = parts.filter((p) => !modifiers.has(p));
469
+ if (baseKeys.length !== 1) {
470
+ ctx.ui.notify("Invalid key combo. Use format like ctrl+alt+p or ctrl+s", "warning");
471
+ return null;
472
+ }
473
+ return trimmed;
474
+ };
475
+
476
+ const kbItems: SettingItem[] = [
477
+ {
478
+ id: "cycleProvider",
479
+ label: "Cycle Provider",
480
+ currentValue: settings.keybindings.cycleProvider,
481
+ description: "Shortcut to cycle through providers. Changes take effect after pi restart.",
482
+ submenu: buildInputSubmenu(
483
+ "Cycle Provider shortcut",
484
+ parseKeybinding,
485
+ undefined,
486
+ "Enter a key combo (e.g. ctrl+alt+p) or 'none' to disable.",
487
+ ),
488
+ },
489
+ {
490
+ id: "toggleResetFormat",
491
+ label: "Toggle Reset Format",
492
+ currentValue: settings.keybindings.toggleResetFormat,
493
+ description: "Shortcut to toggle reset timer format. Changes take effect after pi restart.",
494
+ submenu: buildInputSubmenu(
495
+ "Toggle Reset Format shortcut",
496
+ parseKeybinding,
497
+ undefined,
498
+ "Enter a key combo (e.g. ctrl+alt+r) or 'none' to disable.",
499
+ ),
500
+ },
501
+ ];
502
+
503
+ const handleKbChange = (id: string, value: string) => {
504
+ if (id === "cycleProvider" || id === "toggleResetFormat") {
505
+ settings.keybindings = { ...settings.keybindings, [id]: value };
506
+ saveSettings(settings);
507
+ if (onSettingsChange) void onSettingsChange(settings);
508
+ }
509
+ };
510
+
511
+ const kbList = new SettingsList(
512
+ kbItems,
513
+ Math.min(kbItems.length + 3, 10),
514
+ getSettingsListTheme(),
515
+ handleKbChange,
516
+ () => {
517
+ currentCategory = "main";
518
+ rebuild();
519
+ tui.requestRender();
520
+ },
521
+ );
522
+ activeList = kbList;
523
+ container.addChild(kbList);
456
524
  } else if (currentCategory === "providers") {
457
525
  const items = buildProviderListItems(settings, coreSettings.providers);
458
526
  const selectList = new SelectList(items, Math.min(items.length, 10), {
@@ -1238,6 +1306,7 @@ export async function showSettingsUI(
1238
1306
  // Help text
1239
1307
  const usesSettingsList =
1240
1308
  Boolean(providerCategory) ||
1309
+ currentCategory === "keybindings" ||
1241
1310
  currentCategory === "display-layout" ||
1242
1311
  currentCategory === "display-bar" ||
1243
1312
  currentCategory === "display-provider" ||
@@ -266,6 +266,19 @@ export interface ProviderSettingsMap {
266
266
 
267
267
  export type { BehaviorSettings, CoreSettings } from "@marckrenn/pi-sub-shared";
268
268
 
269
+ /**
270
+ * Keybinding settings.
271
+ * Values are key-combo strings accepted by pi's registerShortcut (e.g. "ctrl+alt+p").
272
+ * Use "none" to disable a shortcut.
273
+ * Changes take effect after pi restart.
274
+ */
275
+ export interface KeybindingSettings {
276
+ /** Shortcut to cycle through providers */
277
+ cycleProvider: string;
278
+ /** Shortcut to toggle reset timer format */
279
+ toggleResetFormat: string;
280
+ }
281
+
269
282
  /**
270
283
  * Display settings
271
284
  */
@@ -378,6 +391,8 @@ export interface Settings extends Omit<CoreSettings, "providers"> {
378
391
  displayUserTheme: DisplaySettings | null;
379
392
  /** Pinned provider override for display */
380
393
  pinnedProvider: ProviderName | null;
394
+ /** Keybinding settings (changes require pi restart) */
395
+ keybindings: KeybindingSettings;
381
396
  }
382
397
 
383
398
  /**
@@ -502,6 +517,11 @@ export function getDefaultSettings(): Settings {
502
517
  displayUserTheme: null,
503
518
  pinnedProvider: null,
504
519
 
520
+ keybindings: {
521
+ cycleProvider: "ctrl+alt+p",
522
+ toggleResetFormat: "ctrl+alt+r",
523
+ },
524
+
505
525
  behavior: {
506
526
  refreshInterval: 60,
507
527
  minRefreshInterval: 10,
package/src/settings.ts CHANGED
@@ -40,6 +40,7 @@ function parseSettings(content: string): Settings {
40
40
  displayThemes: loaded.displayThemes,
41
41
  displayUserTheme: loaded.displayUserTheme,
42
42
  pinnedProvider: loaded.pinnedProvider,
43
+ keybindings: loaded.keybindings,
43
44
  } as Partial<Settings>);
44
45
  }
45
46
 
@@ -108,6 +109,7 @@ export function saveSettings(settings: Settings): boolean {
108
109
  const themesChanged = JSON.stringify(settings.displayThemes) !== JSON.stringify(cachedSettings.displayThemes);
109
110
  const userThemeChanged = JSON.stringify(settings.displayUserTheme) !== JSON.stringify(cachedSettings.displayUserTheme);
110
111
  const pinnedChanged = settings.pinnedProvider !== cachedSettings.pinnedProvider;
112
+ const keybindingsChanged = JSON.stringify(settings.keybindings) !== JSON.stringify(cachedSettings.keybindings);
111
113
 
112
114
  next = {
113
115
  ...diskSettings,
@@ -117,6 +119,7 @@ export function saveSettings(settings: Settings): boolean {
117
119
  displayThemes: themesChanged ? settings.displayThemes : diskSettings.displayThemes,
118
120
  displayUserTheme: userThemeChanged ? settings.displayUserTheme : diskSettings.displayUserTheme,
119
121
  pinnedProvider: pinnedChanged ? settings.pinnedProvider : diskSettings.pinnedProvider,
122
+ keybindings: keybindingsChanged ? settings.keybindings : diskSettings.keybindings,
120
123
  };
121
124
  }
122
125
  }
@@ -127,6 +130,7 @@ export function saveSettings(settings: Settings): boolean {
127
130
  displayThemes: next.displayThemes,
128
131
  displayUserTheme: next.displayUserTheme,
129
132
  pinnedProvider: next.pinnedProvider,
133
+ keybindings: next.keybindings,
130
134
  }, null, 2);
131
135
  storage.writeFile(SETTINGS_PATH, content);
132
136
  cachedSettings = next;
@@ -150,6 +154,7 @@ export function resetSettings(): Settings {
150
154
  displayThemes: defaults.displayThemes,
151
155
  displayUserTheme: defaults.displayUserTheme,
152
156
  pinnedProvider: defaults.pinnedProvider,
157
+ keybindings: defaults.keybindings,
153
158
  version: defaults.version,
154
159
  };
155
160
  saveSettings(next);