@groeponline/pi-wishcraft 0.17.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.
Files changed (106) hide show
  1. package/AGENTS.md +68 -0
  2. package/CHANGELOG.md +724 -0
  3. package/CONTRIBUTING.md +37 -0
  4. package/README.md +648 -0
  5. package/RELEASE.md +117 -0
  6. package/ROADMAP.md +52 -0
  7. package/bash-mode/completion-providers.ts +269 -0
  8. package/bash-mode/completion.ts +416 -0
  9. package/bash-mode/editor-ghost.ts +40 -0
  10. package/bash-mode/editor-input.ts +80 -0
  11. package/bash-mode/editor.ts +437 -0
  12. package/bash-mode/history.ts +263 -0
  13. package/bash-mode/shell-session.ts +286 -0
  14. package/bash-mode/transcript.ts +108 -0
  15. package/bash-mode/types.ts +80 -0
  16. package/index.ts +6 -0
  17. package/package.json +55 -0
  18. package/queue/store.ts +443 -0
  19. package/queue/types.ts +54 -0
  20. package/src/config/custom-items.ts +182 -0
  21. package/src/config/extension-statuses.ts +51 -0
  22. package/src/config/layout.ts +60 -0
  23. package/src/config/parse.ts +127 -0
  24. package/src/config/powerline-config.ts +18 -0
  25. package/src/config/presets.ts +245 -0
  26. package/src/config/primitives.ts +117 -0
  27. package/src/config/segment-ids.ts +114 -0
  28. package/src/config/segment-options.ts +128 -0
  29. package/src/config/settings-patch.ts +26 -0
  30. package/src/config/types.ts +277 -0
  31. package/src/core/frontmatter.ts +40 -0
  32. package/src/editor/autocomplete-chain.ts +41 -0
  33. package/src/extension/activate.ts +28 -0
  34. package/src/extension/bash-mode-actions.ts +104 -0
  35. package/src/extension/commands.ts +268 -0
  36. package/src/extension/constants.ts +46 -0
  37. package/src/extension/custom-editor.ts +406 -0
  38. package/src/extension/git-invalidation.ts +40 -0
  39. package/src/extension/layout.ts +160 -0
  40. package/src/extension/menu-views.ts +393 -0
  41. package/src/extension/powerline-widgets.ts +95 -0
  42. package/src/extension/prompt-history.ts +219 -0
  43. package/src/extension/queue-commands.ts +245 -0
  44. package/src/extension/queue-context.ts +12 -0
  45. package/src/extension/queue-integration.ts +434 -0
  46. package/src/extension/segment-context.ts +212 -0
  47. package/src/extension/session-lifecycle.ts +373 -0
  48. package/src/extension/settings-io.ts +202 -0
  49. package/src/extension/shortcuts-config.ts +357 -0
  50. package/src/extension/shortcuts-router.ts +383 -0
  51. package/src/extension/skills/inline-invocation.ts +174 -0
  52. package/src/extension/skills/ook.md +6 -0
  53. package/src/extension/skills/test.md +6 -0
  54. package/src/extension/stale-context.ts +10 -0
  55. package/src/extension/stash-history.ts +103 -0
  56. package/src/extension/state.ts +159 -0
  57. package/src/extension/status-line-renderers.ts +222 -0
  58. package/src/extension/types.ts +97 -0
  59. package/src/extension/vibe-command.ts +160 -0
  60. package/src/extension/welcome-control.ts +27 -0
  61. package/src/extension/welcome-integration.ts +153 -0
  62. package/src/git/status.ts +332 -0
  63. package/src/paths/agent-dirs.ts +67 -0
  64. package/src/render/timer.ts +46 -0
  65. package/src/segments/core.ts +256 -0
  66. package/src/segments/custom.ts +114 -0
  67. package/src/segments/index.ts +3 -0
  68. package/src/segments/registry.ts +87 -0
  69. package/src/segments/shared.ts +36 -0
  70. package/src/segments/system.ts +235 -0
  71. package/src/segments/usage.ts +178 -0
  72. package/src/shell/cd-command.ts +190 -0
  73. package/src/shortcuts/matching.ts +61 -0
  74. package/src/theme/colors.ts +60 -0
  75. package/src/theme/icons.ts +175 -0
  76. package/src/theme/separators.ts +41 -0
  77. package/src/theme/theme.ts +211 -0
  78. package/src/tools/graph.ts +75 -0
  79. package/src/tools/patch.ts +179 -0
  80. package/src/tools/ripgrep.ts +104 -0
  81. package/src/usage/context.ts +97 -0
  82. package/src/usage/ledger.ts +293 -0
  83. package/src/usage/rates.ts +155 -0
  84. package/src/welcome/auto-dismiss.ts +43 -0
  85. package/src/welcome/banner.ts +68 -0
  86. package/src/welcome/discover.ts +234 -0
  87. package/src/welcome/format.ts +18 -0
  88. package/src/welcome/index.ts +5 -0
  89. package/src/welcome/layout.ts +36 -0
  90. package/src/welcome/overlay.ts +80 -0
  91. package/src/welcome/renderer.ts +157 -0
  92. package/src/welcome/sessions.ts +107 -0
  93. package/src/welcome/types.ts +41 -0
  94. package/src/welcome/widgets/graph-widget.ts +25 -0
  95. package/src/welcome/widgets/index.ts +20 -0
  96. package/src/welcome/widgets/queue-widget.ts +26 -0
  97. package/src/welcome/widgets/sessions-widget.ts +23 -0
  98. package/src/welcome/widgets/shortcuts-widget.ts +17 -0
  99. package/src/welcome/widgets/system-widget.ts +29 -0
  100. package/src/working-vibes/generate.ts +144 -0
  101. package/src/working-vibes/index.ts +24 -0
  102. package/src/working-vibes/manager.ts +198 -0
  103. package/src/working-vibes/provider.ts +163 -0
  104. package/src/working-vibes/storage.ts +357 -0
  105. package/theme.example.json +24 -0
  106. package/tsconfig.json +13 -0
@@ -0,0 +1,202 @@
1
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
2
+ import { join, dirname } from "node:path";
3
+
4
+ import type { StatusLinePreset } from "../config/types.ts";
5
+ import type { PowerlineConfig } from "../config/powerline-config.ts";
6
+ import {
7
+ nextPowerlineSettingWithOptions,
8
+ nextPowerlineSettingWithPreset,
9
+ } from "../config/powerline-config.ts";
10
+ import { getAgentPath } from "../paths/agent-dirs.ts";
11
+
12
+ export function isRecord(value: unknown): value is Record<string, unknown> {
13
+ return typeof value === "object" && value !== null && !Array.isArray(value);
14
+ }
15
+
16
+ export function getSettingsPath(): string {
17
+ return getAgentPath("settings.json");
18
+ }
19
+
20
+ export function getProjectSettingsPath(cwd: string): string {
21
+ return join(cwd, ".pi", "settings.json");
22
+ }
23
+
24
+ export function getGlobalCompactionPolicyPath(): string {
25
+ return getAgentPath("compaction-policy.json");
26
+ }
27
+
28
+ export function getCustomCompactionExtensionPath(): string {
29
+ return getAgentPath("extensions", "pi-custom-compaction");
30
+ }
31
+
32
+ export function mergeSettings(
33
+ base: Record<string, unknown>,
34
+ override: Record<string, unknown>,
35
+ ): Record<string, unknown> {
36
+ const merged: Record<string, unknown> = { ...base };
37
+
38
+ for (const [key, overrideValue] of Object.entries(override)) {
39
+ const baseValue = merged[key];
40
+ merged[key] =
41
+ isRecord(baseValue) && isRecord(overrideValue)
42
+ ? mergeSettings(baseValue, overrideValue)
43
+ : overrideValue;
44
+ }
45
+
46
+ return merged;
47
+ }
48
+
49
+ export function readSettingsFile(
50
+ settingsPath: string,
51
+ ): Record<string, unknown> {
52
+ try {
53
+ if (!existsSync(settingsPath)) {
54
+ return {};
55
+ }
56
+
57
+ const parsed = JSON.parse(readFileSync(settingsPath, "utf-8"));
58
+ if (!isRecord(parsed)) {
59
+ console.debug(
60
+ `[powerline-footer] Ignoring non-object settings at ${settingsPath}`,
61
+ );
62
+ return {};
63
+ }
64
+
65
+ return parsed;
66
+ } catch (error) {
67
+ // Settings are user-edited input. Log and keep the extension running with defaults
68
+ // instead of crashing the UI during startup.
69
+ console.debug(
70
+ `[powerline-footer] Failed to read settings from ${settingsPath}:`,
71
+ error,
72
+ );
73
+ return {};
74
+ }
75
+ }
76
+
77
+ export function readWritableSettingsFile(
78
+ settingsPath: string,
79
+ ): Record<string, unknown> | null {
80
+ if (!existsSync(settingsPath)) {
81
+ return {};
82
+ }
83
+
84
+ try {
85
+ const parsed = JSON.parse(readFileSync(settingsPath, "utf-8"));
86
+ if (!isRecord(parsed)) {
87
+ console.debug(
88
+ `[powerline-footer] Refusing to write settings to non-object file at ${settingsPath}`,
89
+ );
90
+ return null;
91
+ }
92
+
93
+ return parsed;
94
+ } catch (error) {
95
+ // Do not overwrite malformed user settings with partial data. Surface the failure
96
+ // through the command handler so the user can fix the file intentionally.
97
+ console.debug(
98
+ `[powerline-footer] Failed to parse settings at ${settingsPath}:`,
99
+ error,
100
+ );
101
+ return null;
102
+ }
103
+ }
104
+
105
+ export function readCompactionPolicyEnabled(
106
+ configPath: string,
107
+ ): boolean | undefined {
108
+ if (!existsSync(configPath)) return undefined;
109
+ try {
110
+ const parsed = JSON.parse(readFileSync(configPath, "utf-8"));
111
+ if (!isRecord(parsed) || typeof parsed.enabled !== "boolean") return false;
112
+ return parsed.enabled;
113
+ } catch (error) {
114
+ console.debug(
115
+ `[powerline-footer] Failed to read compaction policy from ${configPath}:`,
116
+ error,
117
+ );
118
+ return false;
119
+ }
120
+ }
121
+
122
+ export function detectCustomCompactionEnabled(cwd: string): boolean {
123
+ if (!existsSync(getCustomCompactionExtensionPath())) return false;
124
+
125
+ const projectSetting = readCompactionPolicyEnabled(
126
+ join(cwd, ".pi", "compaction-policy.json"),
127
+ );
128
+ if (projectSetting !== undefined) return projectSetting;
129
+
130
+ return readCompactionPolicyEnabled(getGlobalCompactionPolicyPath()) ?? false;
131
+ }
132
+
133
+ export function readSettings(
134
+ cwd: string = process.cwd(),
135
+ ): Record<string, unknown> {
136
+ return mergeSettings(
137
+ readSettingsFile(getSettingsPath()),
138
+ readSettingsFile(getProjectSettingsPath(cwd)),
139
+ );
140
+ }
141
+
142
+ export function writePowerlineSetting(
143
+ cwd: string,
144
+ update: (existingPowerlineSetting: unknown) => unknown,
145
+ ): boolean {
146
+ const globalSettingsPath = getSettingsPath();
147
+ const projectSettingsPath = getProjectSettingsPath(cwd);
148
+ const globalSettings = readWritableSettingsFile(globalSettingsPath);
149
+ const projectSettings = readWritableSettingsFile(projectSettingsPath);
150
+
151
+ if (globalSettings === null || projectSettings === null) {
152
+ return false;
153
+ }
154
+
155
+ const writeToProject = Object.prototype.hasOwnProperty.call(
156
+ projectSettings,
157
+ "powerline",
158
+ );
159
+ const settingsPath = writeToProject
160
+ ? projectSettingsPath
161
+ : globalSettingsPath;
162
+ const settings = writeToProject ? projectSettings : globalSettings;
163
+
164
+ settings.powerline = update(settings.powerline);
165
+
166
+ try {
167
+ mkdirSync(dirname(settingsPath), { recursive: true });
168
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
169
+ return true;
170
+ } catch (error) {
171
+ console.debug(
172
+ `[powerline-footer] Failed to persist powerline setting to ${settingsPath}:`,
173
+ error,
174
+ );
175
+ return false;
176
+ }
177
+ }
178
+
179
+ export function writePowerlinePresetSetting(
180
+ preset: StatusLinePreset,
181
+ cwd: string = process.cwd(),
182
+ ): boolean {
183
+ return writePowerlineSetting(cwd, (existingPowerlineSetting) =>
184
+ nextPowerlineSettingWithPreset(existingPowerlineSetting, preset),
185
+ );
186
+ }
187
+
188
+ export function writePowerlineOptionSetting(
189
+ cwd: string,
190
+ updates: Partial<
191
+ Pick<PowerlineConfig, "welcome" | "stashSharpSShortcut" | "placement">
192
+ >,
193
+ currentPreset: StatusLinePreset,
194
+ ): boolean {
195
+ return writePowerlineSetting(cwd, (existingPowerlineSetting) =>
196
+ nextPowerlineSettingWithOptions(
197
+ existingPowerlineSetting,
198
+ updates,
199
+ currentPreset,
200
+ ),
201
+ );
202
+ }
@@ -0,0 +1,357 @@
1
+ import { TUI_KEYBINDINGS } from "@earendil-works/pi-tui";
2
+
3
+ import type { BashModeSettings } from "../../bash-mode/types.ts";
4
+ import {
5
+ isSupportedSuperShortcut,
6
+ shortcutConflictKey,
7
+ shortcutUsesSuper,
8
+ } from "../shortcuts/matching.ts";
9
+ import { isRecord } from "./settings-io.ts";
10
+ import {
11
+ DEFAULT_BASH_MODE_SETTINGS,
12
+ DEFAULT_SHORTCUTS,
13
+ SHORTCUT_KEYS,
14
+ } from "./constants.ts";
15
+ import type {
16
+ PowerlineShortcutKey,
17
+ PowerlineShortcuts,
18
+ ShortcutBinding,
19
+ } from "./types.ts";
20
+
21
+ const APP_RESERVED_SHORTCUTS = [
22
+ "escape",
23
+ "ctrl+c",
24
+ "ctrl+d",
25
+ "ctrl+z",
26
+ "shift+tab",
27
+ "ctrl+p",
28
+ "shift+ctrl+p",
29
+ "ctrl+l",
30
+ "ctrl+o",
31
+ "shift+ctrl+o",
32
+ "ctrl+t",
33
+ "ctrl+n",
34
+ "ctrl+g",
35
+ "alt+enter",
36
+ "alt+up",
37
+ "alt+down",
38
+ "ctrl+v",
39
+ "alt+v",
40
+ "shift+l",
41
+ "shift+t",
42
+ "ctrl+s",
43
+ "ctrl+r",
44
+ "ctrl+backspace",
45
+ "ctrl+a",
46
+ "ctrl+x",
47
+ "ctrl+u",
48
+ ] as const;
49
+ const EXTRA_RESERVED_SHORTCUTS = ["alt+s"] as const;
50
+ const SHORTCUT_MODIFIER_ORDER = ["ctrl", "alt", "super", "shift"] as const;
51
+ const SHORTCUT_MODIFIERS = new Set<string>(SHORTCUT_MODIFIER_ORDER);
52
+ const SHORTCUT_NAMED_KEYS = new Set([
53
+ "escape",
54
+ "esc",
55
+ "enter",
56
+ "return",
57
+ "tab",
58
+ "space",
59
+ "backspace",
60
+ "delete",
61
+ "insert",
62
+ "clear",
63
+ "home",
64
+ "end",
65
+ "pageup",
66
+ "pagedown",
67
+ "up",
68
+ "down",
69
+ "left",
70
+ "right",
71
+ ]);
72
+ const SHORTCUT_SYMBOL_KEYS = new Set([
73
+ "`",
74
+ "-",
75
+ "=",
76
+ "[",
77
+ "]",
78
+ "\\",
79
+ ";",
80
+ "'",
81
+ ",",
82
+ ".",
83
+ "/",
84
+ "!",
85
+ "@",
86
+ "#",
87
+ "$",
88
+ "%",
89
+ "^",
90
+ "&",
91
+ "*",
92
+ "(",
93
+ ")",
94
+ "_",
95
+ "|",
96
+ "~",
97
+ "{",
98
+ "}",
99
+ ":",
100
+ "<",
101
+ ">",
102
+ "?",
103
+ ]);
104
+
105
+ export function normalizeShortcut(value: string): string {
106
+ const parts = value.trim().toLowerCase().split("+");
107
+ if (parts.length <= 1) return parts[0] ?? "";
108
+
109
+ const modifierRank = new Map<string, number>(
110
+ SHORTCUT_MODIFIER_ORDER.map((modifier, index) => [modifier, index]),
111
+ );
112
+ const modifiers = parts
113
+ .slice(0, -1)
114
+ .sort((a, b) => (modifierRank.get(a) ?? 99) - (modifierRank.get(b) ?? 99));
115
+ return [...modifiers, parts[parts.length - 1]].join("+");
116
+ }
117
+
118
+ export function reservedShortcuts(): Set<string> {
119
+ const shortcuts = new Set<string>(
120
+ [...EXTRA_RESERVED_SHORTCUTS, ...APP_RESERVED_SHORTCUTS].map(
121
+ normalizeShortcut,
122
+ ),
123
+ );
124
+
125
+ for (const definition of Object.values(TUI_KEYBINDINGS)) {
126
+ const defaultKeys = definition.defaultKeys;
127
+ const keys =
128
+ defaultKeys === undefined
129
+ ? []
130
+ : Array.isArray(defaultKeys)
131
+ ? defaultKeys
132
+ : [defaultKeys];
133
+ for (const key of keys) {
134
+ shortcuts.add(normalizeShortcut(key));
135
+ }
136
+ }
137
+
138
+ return shortcuts;
139
+ }
140
+
141
+ export function isValidShortcutKeyPart(keyPart: string): boolean {
142
+ const lowerKeyPart = keyPart.toLowerCase();
143
+
144
+ if (/^[a-z0-9]$/i.test(keyPart)) return true;
145
+ if (/^f([1-9]|1[0-2])$/i.test(keyPart)) return true;
146
+ if (SHORTCUT_NAMED_KEYS.has(lowerKeyPart)) return true;
147
+
148
+ return SHORTCUT_SYMBOL_KEYS.has(keyPart);
149
+ }
150
+
151
+ export function parseShortcutOverride(value: unknown): string | null {
152
+ if (typeof value !== "string") {
153
+ return null;
154
+ }
155
+
156
+ const trimmed = value.trim();
157
+ if (!trimmed || /\s/.test(trimmed)) {
158
+ return null;
159
+ }
160
+
161
+ const parts = trimmed.split("+");
162
+ if (parts.some((part) => part.length === 0)) {
163
+ return null;
164
+ }
165
+
166
+ const modifierParts = parts.slice(0, -1).map((part) => {
167
+ const modifier = part.toLowerCase();
168
+ return modifier === "cmd" || modifier === "command" ? "super" : modifier;
169
+ });
170
+ if (new Set(modifierParts).size !== modifierParts.length) {
171
+ return null;
172
+ }
173
+
174
+ for (const modifier of modifierParts) {
175
+ if (!SHORTCUT_MODIFIERS.has(modifier)) {
176
+ return null;
177
+ }
178
+ }
179
+
180
+ const keyPart = parts[parts.length - 1];
181
+ if (!isValidShortcutKeyPart(keyPart)) {
182
+ return null;
183
+ }
184
+
185
+ const normalizedKey = SHORTCUT_SYMBOL_KEYS.has(keyPart)
186
+ ? keyPart
187
+ : keyPart.toLowerCase();
188
+ const normalizedShortcut = normalizeShortcut(
189
+ [...modifierParts, normalizedKey].join("+"),
190
+ );
191
+ if (
192
+ shortcutUsesSuper(normalizedShortcut) &&
193
+ !isSupportedSuperShortcut(normalizedShortcut)
194
+ ) {
195
+ return null;
196
+ }
197
+
198
+ return normalizedShortcut;
199
+ }
200
+
201
+ export function shortcutUsageKey(shortcut: string): string {
202
+ return shortcutConflictKey(normalizeShortcut(shortcut));
203
+ }
204
+
205
+ export function parseShortcutSetting(
206
+ value: unknown,
207
+ ): ShortcutBinding | undefined {
208
+ if (value === null || value === undefined) return null;
209
+ if (typeof value === "string" && value.trim() === "") return null;
210
+ return parseShortcutOverride(value) ?? undefined;
211
+ }
212
+
213
+ export function findShortcutReplacement(
214
+ key: PowerlineShortcutKey,
215
+ used: Set<string>,
216
+ ): string | null {
217
+ const preferred = DEFAULT_SHORTCUTS[key];
218
+ if (preferred && !used.has(shortcutUsageKey(preferred))) {
219
+ return preferred;
220
+ }
221
+
222
+ for (const shortcutKey of SHORTCUT_KEYS) {
223
+ const candidate = DEFAULT_SHORTCUTS[shortcutKey];
224
+ if (candidate && !used.has(shortcutUsageKey(candidate))) {
225
+ return candidate;
226
+ }
227
+ }
228
+
229
+ return null;
230
+ }
231
+
232
+ export function bashToggleShortcutReservation(
233
+ settings: Record<string, unknown>,
234
+ ): ShortcutBinding {
235
+ const raw = isRecord(settings.bashMode) ? settings.bashMode : {};
236
+ if (!Object.prototype.hasOwnProperty.call(raw, "toggleShortcut")) {
237
+ return DEFAULT_BASH_MODE_SETTINGS.toggleShortcut;
238
+ }
239
+
240
+ const parsed = parseShortcutSetting(raw.toggleShortcut);
241
+ return parsed === undefined
242
+ ? DEFAULT_BASH_MODE_SETTINGS.toggleShortcut
243
+ : parsed;
244
+ }
245
+
246
+ export function resolveShortcutConfig(
247
+ settings: Record<string, unknown>,
248
+ ): PowerlineShortcuts {
249
+ const resolved: PowerlineShortcuts = { ...DEFAULT_SHORTCUTS };
250
+ const shortcutSettings = settings.powerlineShortcuts;
251
+
252
+ if (isRecord(shortcutSettings)) {
253
+ for (const key of SHORTCUT_KEYS) {
254
+ if (!Object.prototype.hasOwnProperty.call(shortcutSettings, key)) {
255
+ continue;
256
+ }
257
+
258
+ const override = parseShortcutSetting(shortcutSettings[key]);
259
+ if (override !== undefined) {
260
+ resolved[key] = override;
261
+ }
262
+ }
263
+ }
264
+
265
+ const used = new Set(Array.from(reservedShortcuts(), shortcutUsageKey));
266
+ const reservedBashToggle = bashToggleShortcutReservation(settings);
267
+ if (reservedBashToggle) {
268
+ used.add(shortcutUsageKey(reservedBashToggle));
269
+ }
270
+
271
+ for (const key of SHORTCUT_KEYS) {
272
+ const configured = resolved[key];
273
+ if (configured === null) {
274
+ continue;
275
+ }
276
+
277
+ const configuredUsageKey = shortcutUsageKey(configured);
278
+
279
+ if (!used.has(configuredUsageKey)) {
280
+ used.add(configuredUsageKey);
281
+ continue;
282
+ }
283
+
284
+ const replacement = findShortcutReplacement(key, used);
285
+ if (!replacement) {
286
+ console.debug(
287
+ `[powerline-footer] Shortcut conflict for ${key}: "${configured}" is already in use`,
288
+ );
289
+ continue;
290
+ }
291
+
292
+ console.debug(
293
+ `[powerline-footer] Shortcut conflict for ${key}: "${configured}" replaced with "${replacement}"`,
294
+ );
295
+
296
+ resolved[key] = replacement;
297
+ used.add(shortcutUsageKey(replacement));
298
+ }
299
+
300
+ return resolved;
301
+ }
302
+
303
+ export function parseBashModeSettings(
304
+ settings: Record<string, unknown>,
305
+ powerlineShortcuts?: PowerlineShortcuts,
306
+ ): BashModeSettings {
307
+ const raw = isRecord(settings.bashMode) ? settings.bashMode : {};
308
+ const used = new Set(Array.from(reservedShortcuts(), shortcutUsageKey));
309
+ if (powerlineShortcuts) {
310
+ for (const shortcut of Object.values(powerlineShortcuts)) {
311
+ if (shortcut) {
312
+ used.add(shortcutUsageKey(shortcut));
313
+ }
314
+ }
315
+ }
316
+
317
+ const configuredToggleShortcut = Object.prototype.hasOwnProperty.call(
318
+ raw,
319
+ "toggleShortcut",
320
+ )
321
+ ? parseShortcutSetting(raw.toggleShortcut)
322
+ : undefined;
323
+ const fallbackToggleShortcut = used.has(
324
+ shortcutUsageKey(DEFAULT_BASH_MODE_SETTINGS.toggleShortcut),
325
+ )
326
+ ? null
327
+ : DEFAULT_BASH_MODE_SETTINGS.toggleShortcut;
328
+ const toggleShortcut =
329
+ configuredToggleShortcut === null
330
+ ? null
331
+ : configuredToggleShortcut &&
332
+ !used.has(shortcutUsageKey(configuredToggleShortcut))
333
+ ? configuredToggleShortcut
334
+ : fallbackToggleShortcut;
335
+
336
+ if (configuredToggleShortcut && toggleShortcut !== configuredToggleShortcut) {
337
+ console.debug(
338
+ `[powerline-footer] Bash mode shortcut conflict: "${configuredToggleShortcut}" replaced with "${toggleShortcut ?? "disabled"}"`,
339
+ );
340
+ }
341
+ const transcriptMaxLines =
342
+ typeof raw.transcriptMaxLines === "number" &&
343
+ Number.isFinite(raw.transcriptMaxLines)
344
+ ? Math.max(100, Math.floor(raw.transcriptMaxLines))
345
+ : DEFAULT_BASH_MODE_SETTINGS.transcriptMaxLines;
346
+ const transcriptMaxBytes =
347
+ typeof raw.transcriptMaxBytes === "number" &&
348
+ Number.isFinite(raw.transcriptMaxBytes)
349
+ ? Math.max(16 * 1024, Math.floor(raw.transcriptMaxBytes))
350
+ : DEFAULT_BASH_MODE_SETTINGS.transcriptMaxBytes;
351
+
352
+ return {
353
+ toggleShortcut,
354
+ transcriptMaxLines,
355
+ transcriptMaxBytes,
356
+ };
357
+ }