@juanibiapina/pi-extension-settings 0.7.0 → 0.8.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/package.json CHANGED
@@ -1,20 +1,14 @@
1
1
  {
2
2
  "name": "@juanibiapina/pi-extension-settings",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Pi extension for centralized settings management across extensions",
5
5
  "type": "module",
6
- "main": "dist/index.js",
7
- "types": "./dist/index.d.ts",
8
6
  "scripts": {
9
- "clean": "rm -rf dist",
10
- "build": "tsc -p tsconfig.build.json",
11
- "dev": "tsc -p tsconfig.build.json --watch",
12
7
  "check": "biome check --write --error-on-warnings . && tsc --noEmit",
13
- "test": "node --test --import tsx test/*.test.ts",
14
- "prepublishOnly": "npm run clean && npm run build && npm run check"
8
+ "test": "node --test --import tsx test/*.test.ts"
15
9
  },
16
10
  "files": [
17
- "dist/**/*",
11
+ "src/**/*",
18
12
  "README.md"
19
13
  ],
20
14
  "keywords": [
@@ -26,7 +20,7 @@
26
20
  ],
27
21
  "pi": {
28
22
  "extensions": [
29
- "./dist/index.js"
23
+ "./src/index.ts"
30
24
  ]
31
25
  },
32
26
  "author": "Juan Ibiapina",
@@ -38,10 +32,6 @@
38
32
  "engines": {
39
33
  "node": ">=20.0.0"
40
34
  },
41
- "peerDependencies": {
42
- "@earendil-works/pi-coding-agent": "*",
43
- "@earendil-works/pi-tui": "*"
44
- },
45
35
  "devDependencies": {
46
36
  "@biomejs/biome": "2.4.15",
47
37
  "@earendil-works/pi-coding-agent": "^0.75.3",
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Ordered multi-select component.
3
+ *
4
+ * Displays a list of options that can be toggled on/off and reordered.
5
+ * Selected items appear at the top with their position number.
6
+ * Value is stored as a comma-separated string of selected IDs in order.
7
+ */
8
+
9
+ import { type Component, getKeybindings, matchesKey, truncateToWidth } from "@earendil-works/pi-tui";
10
+ import type { OrderedListOption } from "../settings/types.js";
11
+ import type { SettingsListTheme } from "./settings-list.js";
12
+
13
+ interface DisplayItem {
14
+ option: OrderedListOption;
15
+ selected: boolean;
16
+ /** 1-based position within selected items, or 0 if not selected */
17
+ position: number;
18
+ }
19
+
20
+ export class OrderedMultiSelect implements Component {
21
+ private options: OrderedListOption[];
22
+ private selected: string[];
23
+ private cursorIndex = 0;
24
+ private theme: SettingsListTheme;
25
+ private done: (selectedValue?: string) => void;
26
+
27
+ constructor(
28
+ options: OrderedListOption[],
29
+ currentValue: string,
30
+ theme: SettingsListTheme,
31
+ done: (selectedValue?: string) => void,
32
+ ) {
33
+ this.options = options;
34
+ this.selected = currentValue
35
+ .split(",")
36
+ .map((s) => s.trim())
37
+ .filter(Boolean);
38
+ this.theme = theme;
39
+ this.done = done;
40
+ }
41
+
42
+ invalidate(): void {}
43
+
44
+ render(width: number): string[] {
45
+ const lines: string[] = [];
46
+ const items = this.buildDisplayItems();
47
+
48
+ for (let i = 0; i < items.length; i++) {
49
+ const item = items[i];
50
+ const isCursor = i === this.cursorIndex;
51
+ const prefix = isCursor ? this.theme.cursor : " ";
52
+
53
+ let marker: string;
54
+ if (item.selected) {
55
+ marker = this.theme.value(`✓ ${String(item.position).padStart(2)}`, isCursor);
56
+ } else {
57
+ marker = this.theme.label(" ", isCursor);
58
+ }
59
+
60
+ const label = this.theme.label(item.option.label, isCursor);
61
+ const line = `${prefix}${marker} ${label}`;
62
+ lines.push(truncateToWidth(line, width, "…"));
63
+ }
64
+
65
+ // Scroll indicator
66
+ if (items.length > 0) {
67
+ const scrollText = ` (${this.cursorIndex + 1}/${items.length})`;
68
+ lines.push(this.theme.hint(scrollText));
69
+ }
70
+
71
+ // Hint line
72
+ lines.push("");
73
+ lines.push(this.theme.hint(" Space toggle · Shift+↑/↓ reorder · Enter confirm · Esc cancel"));
74
+
75
+ return lines;
76
+ }
77
+
78
+ handleInput(data: string): void {
79
+ const kb = getKeybindings();
80
+ const items = this.buildDisplayItems();
81
+ if (items.length === 0) {
82
+ if (kb.matches(data, "tui.select.cancel")) {
83
+ this.done(undefined);
84
+ }
85
+ return;
86
+ }
87
+
88
+ if (kb.matches(data, "tui.select.up") || matchesKey(data, "up")) {
89
+ this.cursorIndex = this.cursorIndex === 0 ? items.length - 1 : this.cursorIndex - 1;
90
+ } else if (kb.matches(data, "tui.select.down") || matchesKey(data, "down")) {
91
+ this.cursorIndex = this.cursorIndex === items.length - 1 ? 0 : this.cursorIndex + 1;
92
+ } else if (data === " " || matchesKey(data, "space")) {
93
+ this.toggleCurrent(items);
94
+ } else if (matchesKey(data, "shift+up")) {
95
+ this.moveUp(items);
96
+ } else if (matchesKey(data, "shift+down")) {
97
+ this.moveDown(items);
98
+ } else if (kb.matches(data, "tui.select.confirm")) {
99
+ this.done(this.selected.join(","));
100
+ } else if (kb.matches(data, "tui.select.cancel")) {
101
+ this.done(undefined);
102
+ }
103
+ }
104
+
105
+ private buildDisplayItems(): DisplayItem[] {
106
+ const items: DisplayItem[] = [];
107
+
108
+ // Selected items first, in order
109
+ for (let i = 0; i < this.selected.length; i++) {
110
+ const id = this.selected[i];
111
+ const option = this.options.find((o) => o.id === id);
112
+ if (option) {
113
+ items.push({ option, selected: true, position: i + 1 });
114
+ }
115
+ }
116
+
117
+ // Unselected items, in original options order
118
+ for (const option of this.options) {
119
+ if (!this.selected.includes(option.id)) {
120
+ items.push({ option, selected: false, position: 0 });
121
+ }
122
+ }
123
+
124
+ return items;
125
+ }
126
+
127
+ private toggleCurrent(items: DisplayItem[]): void {
128
+ const item = items[this.cursorIndex];
129
+ if (!item) return;
130
+
131
+ const id = item.option.id;
132
+ if (item.selected) {
133
+ // Remove from selected
134
+ this.selected = this.selected.filter((s) => s !== id);
135
+ } else {
136
+ // Add to end of selected
137
+ this.selected.push(id);
138
+ }
139
+ }
140
+
141
+ private moveUp(items: DisplayItem[]): void {
142
+ const item = items[this.cursorIndex];
143
+ if (!item?.selected) return;
144
+
145
+ const idx = this.selected.indexOf(item.option.id);
146
+ if (idx <= 0) return;
147
+
148
+ // Swap with previous in selected array
149
+ [this.selected[idx - 1], this.selected[idx]] = [this.selected[idx], this.selected[idx - 1]];
150
+ // Move cursor up to follow the item
151
+ this.cursorIndex--;
152
+ }
153
+
154
+ private moveDown(items: DisplayItem[]): void {
155
+ const item = items[this.cursorIndex];
156
+ if (!item?.selected) return;
157
+
158
+ const idx = this.selected.indexOf(item.option.id);
159
+ if (idx < 0 || idx >= this.selected.length - 1) return;
160
+
161
+ // Swap with next in selected array
162
+ [this.selected[idx], this.selected[idx + 1]] = [this.selected[idx + 1], this.selected[idx]];
163
+ // Move cursor down to follow the item
164
+ this.cursorIndex++;
165
+ }
166
+ }
@@ -0,0 +1,328 @@
1
+ /**
2
+ * Settings list component with support for cycling values and string input.
3
+ * Based on @earendil-works/pi-tui SettingsList, extended with string editing.
4
+ */
5
+
6
+ import {
7
+ type Component,
8
+ fuzzyFilter,
9
+ getKeybindings,
10
+ Input,
11
+ matchesKey,
12
+ truncateToWidth,
13
+ visibleWidth,
14
+ wrapTextWithAnsi,
15
+ } from "@earendil-works/pi-tui";
16
+
17
+ export interface SettingItem {
18
+ /** Unique identifier for this setting */
19
+ id: string;
20
+ /** Display label (left side) */
21
+ label: string;
22
+ /** Optional description shown when selected */
23
+ description?: string;
24
+ /** Current value to display (right side) */
25
+ currentValue: string;
26
+ /**
27
+ * If provided, Enter/Space cycles through these values.
28
+ * If undefined/empty, the setting is treated as a string input.
29
+ */
30
+ values?: string[];
31
+ /** If provided, Enter opens this submenu. Receives current value and done callback. */
32
+ submenu?: (currentValue: string, done: (selectedValue?: string) => void) => Component;
33
+ /** If false, item is selectable but cannot be edited or changed. */
34
+ editable?: boolean;
35
+ }
36
+
37
+ export interface SettingsListTheme {
38
+ label: (text: string, selected: boolean) => string;
39
+ value: (text: string, selected: boolean) => string;
40
+ description: (text: string) => string;
41
+ cursor: string;
42
+ hint: (text: string) => string;
43
+ }
44
+
45
+ export interface SettingsListOptions {
46
+ enableSearch?: boolean;
47
+ }
48
+
49
+ export class SettingsList implements Component {
50
+ private items: SettingItem[];
51
+ private filteredItems: SettingItem[];
52
+ private theme: SettingsListTheme;
53
+ private selectedIndex = 0;
54
+ private maxVisible: number;
55
+ private onChange: (id: string, newValue: string) => void;
56
+ private onCancel: () => void;
57
+ private searchInput?: Input;
58
+ private searchEnabled: boolean;
59
+
60
+ // Submenu state
61
+ private submenuComponent: Component | null = null;
62
+ private submenuItemIndex: number | null = null;
63
+
64
+ // String editing state
65
+ private editingInput: Input | null = null;
66
+ private editingItemIndex: number | null = null;
67
+
68
+ constructor(
69
+ items: SettingItem[],
70
+ maxVisible: number,
71
+ theme: SettingsListTheme,
72
+ onChange: (id: string, newValue: string) => void,
73
+ onCancel: () => void,
74
+ options: SettingsListOptions = {},
75
+ ) {
76
+ this.items = items;
77
+ this.filteredItems = items;
78
+ this.maxVisible = maxVisible;
79
+ this.theme = theme;
80
+ this.onChange = onChange;
81
+ this.onCancel = onCancel;
82
+ this.searchEnabled = options.enableSearch ?? false;
83
+ if (this.searchEnabled) {
84
+ this.searchInput = new Input();
85
+ }
86
+ }
87
+
88
+ /** Update an item's currentValue */
89
+ updateValue(id: string, newValue: string): void {
90
+ const item = this.items.find((i) => i.id === id);
91
+ if (item) {
92
+ item.currentValue = newValue;
93
+ }
94
+ }
95
+
96
+ invalidate(): void {
97
+ this.submenuComponent?.invalidate?.();
98
+ }
99
+
100
+ render(width: number): string[] {
101
+ // If submenu is active, render it instead
102
+ if (this.submenuComponent) {
103
+ return this.submenuComponent.render(width);
104
+ }
105
+
106
+ return this.renderMainList(width);
107
+ }
108
+
109
+ private renderMainList(width: number): string[] {
110
+ const lines: string[] = [];
111
+
112
+ if (this.searchEnabled && this.searchInput && !this.editingInput) {
113
+ lines.push(...this.searchInput.render(width));
114
+ lines.push("");
115
+ }
116
+
117
+ if (this.items.length === 0) {
118
+ lines.push(this.theme.hint(" No settings available"));
119
+ if (this.searchEnabled) {
120
+ this.addHintLine(lines);
121
+ }
122
+ return lines;
123
+ }
124
+
125
+ const displayItems = this.searchEnabled ? this.filteredItems : this.items;
126
+ if (displayItems.length === 0) {
127
+ lines.push(this.theme.hint(" No matching settings"));
128
+ this.addHintLine(lines);
129
+ return lines;
130
+ }
131
+
132
+ // Calculate visible range with scrolling
133
+ const startIndex = Math.max(
134
+ 0,
135
+ Math.min(this.selectedIndex - Math.floor(this.maxVisible / 2), displayItems.length - this.maxVisible),
136
+ );
137
+ const endIndex = Math.min(startIndex + this.maxVisible, displayItems.length);
138
+
139
+ // Calculate max label width for alignment
140
+ const maxLabelWidth = Math.min(30, Math.max(...this.items.map((item) => visibleWidth(item.label))));
141
+
142
+ // Render visible items
143
+ for (let i = startIndex; i < endIndex; i++) {
144
+ const item = displayItems[i];
145
+ if (!item) continue;
146
+
147
+ const isSelected = i === this.selectedIndex;
148
+ const isEditing = this.editingInput && this.editingItemIndex === i;
149
+ const prefix = isSelected ? this.theme.cursor : " ";
150
+ const prefixWidth = visibleWidth(prefix);
151
+
152
+ // Pad label to align values
153
+ const labelPadded = item.label + " ".repeat(Math.max(0, maxLabelWidth - visibleWidth(item.label)));
154
+ const labelText = this.theme.label(labelPadded, isSelected);
155
+
156
+ // Calculate space for value
157
+ const separator = " ";
158
+ const usedWidth = prefixWidth + maxLabelWidth + visibleWidth(separator);
159
+ const valueMaxWidth = width - usedWidth - 2;
160
+
161
+ if (isEditing && this.editingInput) {
162
+ // Render inline input for string editing
163
+ const inputLines = this.editingInput.render(valueMaxWidth);
164
+ const inputLine = inputLines[0] ?? "";
165
+ lines.push(prefix + labelText + separator + inputLine);
166
+ } else {
167
+ const valueText = this.theme.value(truncateToWidth(item.currentValue, valueMaxWidth, ""), isSelected);
168
+ lines.push(prefix + labelText + separator + valueText);
169
+ }
170
+ }
171
+
172
+ // Add scroll indicator if needed
173
+ if (startIndex > 0 || endIndex < displayItems.length) {
174
+ const scrollText = ` (${this.selectedIndex + 1}/${displayItems.length})`;
175
+ lines.push(this.theme.hint(truncateToWidth(scrollText, width - 2, "")));
176
+ }
177
+
178
+ // Add description for selected item
179
+ const selectedItem = displayItems[this.selectedIndex];
180
+ if (selectedItem?.description && !this.editingInput) {
181
+ lines.push("");
182
+ const wrappedDesc = wrapTextWithAnsi(selectedItem.description, width - 4);
183
+ for (const line of wrappedDesc) {
184
+ lines.push(this.theme.description(` ${line}`));
185
+ }
186
+ }
187
+
188
+ // Add hint
189
+ this.addHintLine(lines);
190
+
191
+ return lines;
192
+ }
193
+
194
+ handleInput(data: string): void {
195
+ // If editing a string value, handle input for the editor
196
+ if (this.editingInput) {
197
+ this.handleEditingInput(data);
198
+ return;
199
+ }
200
+
201
+ // If submenu is active, delegate all input to it
202
+ if (this.submenuComponent) {
203
+ this.submenuComponent.handleInput?.(data);
204
+ return;
205
+ }
206
+
207
+ // Main list input handling
208
+ const kb = getKeybindings();
209
+ const displayItems = this.searchEnabled ? this.filteredItems : this.items;
210
+
211
+ if (kb.matches(data, "tui.select.up")) {
212
+ if (displayItems.length === 0) return;
213
+ this.selectedIndex = this.selectedIndex === 0 ? displayItems.length - 1 : this.selectedIndex - 1;
214
+ } else if (kb.matches(data, "tui.select.down")) {
215
+ if (displayItems.length === 0) return;
216
+ this.selectedIndex = this.selectedIndex === displayItems.length - 1 ? 0 : this.selectedIndex + 1;
217
+ } else if (kb.matches(data, "tui.select.confirm") || data === " ") {
218
+ this.activateItem();
219
+ } else if (kb.matches(data, "tui.select.cancel")) {
220
+ this.onCancel();
221
+ } else if (this.searchEnabled && this.searchInput) {
222
+ const sanitized = data.replace(/ /g, "");
223
+ if (!sanitized) {
224
+ return;
225
+ }
226
+ this.searchInput.handleInput(sanitized);
227
+ this.applyFilter(this.searchInput.getValue());
228
+ }
229
+ }
230
+
231
+ private handleEditingInput(data: string): void {
232
+ if (!this.editingInput) return;
233
+
234
+ // Enter: confirm edit
235
+ if (matchesKey(data, "enter")) {
236
+ this.confirmEdit();
237
+ return;
238
+ }
239
+
240
+ // Escape: cancel edit
241
+ if (matchesKey(data, "escape")) {
242
+ this.cancelEdit();
243
+ return;
244
+ }
245
+
246
+ // Pass other input to the editor
247
+ this.editingInput.handleInput(data);
248
+ }
249
+
250
+ private confirmEdit(): void {
251
+ if (!this.editingInput || this.editingItemIndex === null) return;
252
+
253
+ const displayItems = this.searchEnabled ? this.filteredItems : this.items;
254
+ const item = displayItems[this.editingItemIndex];
255
+ if (item) {
256
+ const newValue = this.editingInput.getValue();
257
+ item.currentValue = newValue;
258
+ this.onChange(item.id, newValue);
259
+ }
260
+
261
+ this.editingInput = null;
262
+ this.editingItemIndex = null;
263
+ }
264
+
265
+ private cancelEdit(): void {
266
+ this.editingInput = null;
267
+ this.editingItemIndex = null;
268
+ }
269
+
270
+ private activateItem(): void {
271
+ const displayItems = this.searchEnabled ? this.filteredItems : this.items;
272
+ const item = displayItems[this.selectedIndex];
273
+ if (!item) return;
274
+
275
+ if (item.editable === false) {
276
+ return;
277
+ }
278
+
279
+ if (item.submenu) {
280
+ // Open submenu, passing current value so it can pre-select correctly
281
+ this.submenuItemIndex = this.selectedIndex;
282
+ this.submenuComponent = item.submenu(item.currentValue, (selectedValue?: string) => {
283
+ if (selectedValue !== undefined) {
284
+ item.currentValue = selectedValue;
285
+ this.onChange(item.id, selectedValue);
286
+ }
287
+ this.closeSubmenu();
288
+ });
289
+ } else if (item.values && item.values.length > 0) {
290
+ // Cycle through values
291
+ const currentIndex = item.values.indexOf(item.currentValue);
292
+ const nextIndex = (currentIndex + 1) % item.values.length;
293
+ const newValue = item.values[nextIndex];
294
+ item.currentValue = newValue;
295
+ this.onChange(item.id, newValue);
296
+ } else {
297
+ // String input - start editing
298
+ this.editingItemIndex = this.selectedIndex;
299
+ this.editingInput = new Input();
300
+ this.editingInput.setValue(item.currentValue);
301
+ }
302
+ }
303
+
304
+ private closeSubmenu(): void {
305
+ this.submenuComponent = null;
306
+ // Restore selection to the item that opened the submenu
307
+ if (this.submenuItemIndex !== null) {
308
+ this.selectedIndex = this.submenuItemIndex;
309
+ this.submenuItemIndex = null;
310
+ }
311
+ }
312
+
313
+ private applyFilter(query: string): void {
314
+ this.filteredItems = fuzzyFilter(this.items, query, (item) => item.label);
315
+ this.selectedIndex = 0;
316
+ }
317
+
318
+ private addHintLine(lines: string[]): void {
319
+ lines.push("");
320
+ if (this.editingInput) {
321
+ lines.push(this.theme.hint(" Enter to confirm · Esc to cancel"));
322
+ } else if (this.searchEnabled) {
323
+ lines.push(this.theme.hint(" Type to search · Enter/Space to change · Esc to cancel"));
324
+ } else {
325
+ lines.push(this.theme.hint(" Enter/Space to change · Esc to cancel"));
326
+ }
327
+ }
328
+ }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Pi extension that provides /extension-settings command.
3
+ */
4
+
5
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
6
+ import { getSettingsListTheme } from "@earendil-works/pi-coding-agent";
7
+ import { Container, Text } from "@earendil-works/pi-tui";
8
+ import { OrderedMultiSelect } from "./components/ordered-multi-select.js";
9
+ import { type SettingItem, SettingsList } from "./components/settings-list.js";
10
+ import { getSetting, setSetting } from "./settings/storage.js";
11
+ import type { SettingDefinition } from "./settings/types.js";
12
+
13
+ interface RegistrationPayload {
14
+ name: string;
15
+ settings: SettingDefinition[];
16
+ }
17
+
18
+ export default function piLibExtension(pi: ExtensionAPI) {
19
+ // Local registry - stores settings registered via events
20
+ const registry = new Map<string, SettingDefinition[]>();
21
+
22
+ // Listen for registration events from other extensions
23
+ pi.events.on("pi-extension-settings:register", (data) => {
24
+ const { name, settings } = data as RegistrationPayload;
25
+ registry.set(name, settings);
26
+ });
27
+
28
+ pi.registerCommand("extension-settings", {
29
+ description: "Configure settings for all extensions",
30
+ handler: async (_args, ctx) => {
31
+ if (registry.size === 0) {
32
+ ctx.ui.notify(
33
+ "No extensions have registered settings. Ensure pi-extension-settings is listed before consumer extensions in your packages array in ~/.pi/settings.json.",
34
+ "info",
35
+ );
36
+ return;
37
+ }
38
+
39
+ // Sort extensions by name
40
+ const sortedExtensions = Array.from(registry.entries()).sort(([a], [b]) => a.localeCompare(b));
41
+
42
+ await ctx.ui.custom((tui, theme, _kb, done) => {
43
+ const container = new Container();
44
+
45
+ // Title
46
+ container.addChild(new Text(theme.fg("accent", theme.bold("Extension Settings")), 1, 1));
47
+
48
+ // Build items grouped by extension
49
+ const items: SettingItem[] = [];
50
+
51
+ for (const [extName, settings] of sortedExtensions) {
52
+ // Add extension header as a non-interactive item
53
+ items.push({
54
+ id: `__header__${extName}`,
55
+ label: theme.bold(extName),
56
+ currentValue: "",
57
+ values: undefined, // No cycling - acts as header
58
+ editable: false,
59
+ });
60
+
61
+ // Add each setting
62
+ for (const setting of settings) {
63
+ const currentValue = getSetting(extName, setting.id, setting.defaultValue) ?? setting.defaultValue;
64
+
65
+ if (setting.options && setting.options.length > 0) {
66
+ // Ordered multi-select: opens a submenu
67
+ items.push({
68
+ id: `${extName}::${setting.id}`,
69
+ label: ` ${setting.label}`,
70
+ description: setting.description,
71
+ currentValue,
72
+ submenu: (val, submenuDone) => {
73
+ return new OrderedMultiSelect(setting.options!, val, getSettingsListTheme(), submenuDone);
74
+ },
75
+ });
76
+ } else {
77
+ items.push({
78
+ id: `${extName}::${setting.id}`,
79
+ label: ` ${setting.label}`,
80
+ description: setting.description,
81
+ currentValue,
82
+ values: setting.values,
83
+ });
84
+ }
85
+ }
86
+ }
87
+
88
+ const settingsList = new SettingsList(
89
+ items,
90
+ Math.min(items.length + 2, 20),
91
+ getSettingsListTheme(),
92
+ (id, newValue) => {
93
+ // Skip headers
94
+ if (id.startsWith("__header__")) return;
95
+
96
+ // Parse extension::settingId
97
+ const [extensionName, settingId] = id.split("::");
98
+ if (extensionName && settingId) {
99
+ setSetting(extensionName, settingId, newValue);
100
+ }
101
+ },
102
+ () => {
103
+ done(undefined);
104
+ },
105
+ { enableSearch: true },
106
+ );
107
+
108
+ container.addChild(settingsList);
109
+
110
+ return {
111
+ render(width: number) {
112
+ return container.render(width);
113
+ },
114
+ invalidate() {
115
+ container.invalidate();
116
+ },
117
+ handleInput(data: string) {
118
+ settingsList.handleInput?.(data);
119
+ tui.requestRender();
120
+ },
121
+ };
122
+ });
123
+ },
124
+ });
125
+ }
@@ -33,7 +33,11 @@
33
33
  * setSetting("my-extension", "timeout", "60");
34
34
  * ```
35
35
  */
36
+
37
+ // Extension entry point
36
38
  export { default } from "./extension.js";
39
+
40
+ // Stateless helpers for reading/writing settings
37
41
  export { getSetting, setSetting } from "./settings/storage.js";
42
+ // Types for documentation/type-safety when emitting events
38
43
  export type { OrderedListOption, SettingDefinition } from "./settings/types.js";
39
- //# sourceMappingURL=index.d.ts.map