@aliou/pi-neuralwatt 0.7.4 → 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.
Files changed (37) hide show
  1. package/README.md +4 -2
  2. package/{src/extensions → extensions}/command-quotas/command.ts +2 -2
  3. package/extensions/command-quotas/components/progress-bar.ts +38 -0
  4. package/{src/extensions → extensions}/command-quotas/components/quota-tabs.ts +4 -4
  5. package/{src/extensions → extensions}/command-quotas/components/quotas-display.ts +1 -1
  6. package/{src/extensions → extensions}/command-quotas/index.ts +3 -3
  7. package/extensions/provider/commands/settings/index.ts +246 -0
  8. package/{src/extensions → extensions}/provider/index.ts +28 -20
  9. package/{src/extensions → extensions}/provider/models/hidden.ts +3 -3
  10. package/{src/extensions → extensions}/provider/models/legacy.ts +2 -0
  11. package/{src/extensions → extensions}/provider/models/public-models.ts +132 -46
  12. package/{src/extensions → extensions}/provider/quota-store.ts +4 -4
  13. package/{src/extensions → extensions}/provider/sse-quotas.ts +1 -1
  14. package/{src/extensions → extensions}/quota-warnings/index.ts +6 -7
  15. package/{src/extensions → extensions}/quota-warnings/notifier.ts +2 -2
  16. package/{src/extensions → extensions}/sub-bar-integration/index.ts +9 -10
  17. package/{src/extensions → extensions}/sub-bar-integration/snapshot.ts +3 -3
  18. package/package.json +20 -14
  19. package/schema.json +50 -6
  20. package/src/config/defaults.ts +17 -0
  21. package/src/config/index.ts +9 -0
  22. package/src/config/loader.ts +74 -0
  23. package/src/config/migration/01-disable-legacy-model-ids-by-default.ts +44 -0
  24. package/src/config/migration/02-flat-to-nested-config.ts +149 -0
  25. package/src/config/migration/index.ts +16 -0
  26. package/src/config/types.ts +77 -0
  27. package/src/{types/quota-events.ts → events.ts} +25 -13
  28. package/src/lib/neuralwatt-api.ts +1 -1
  29. package/src/types/quota-result.ts +12 -0
  30. package/src/utils/quota-bar.ts +0 -38
  31. package/src/config.ts +0 -210
  32. /package/{src/lib/env.ts → extensions/_shared/auth.ts} +0 -0
  33. /package/{src/extensions → extensions}/provider/context-overflow.ts +0 -0
  34. /package/{src/extensions → extensions}/provider/models/cache.ts +0 -0
  35. /package/{src/extensions → extensions}/provider/models/index.ts +0 -0
  36. /package/{src/extensions → extensions}/provider/rate-limit-error.ts +0 -0
  37. /package/{src/extensions → extensions}/provider/stream-simple.ts +0 -0
@@ -0,0 +1,149 @@
1
+ import { constants } from "node:fs";
2
+ import { copyFile } from "node:fs/promises";
3
+ import { basename, dirname, join } from "node:path";
4
+ import type { Migration } from "@aliou/pi-utils-settings";
5
+ import packageJson from "../../../package.json";
6
+ import type { NeuralwattConfig, PreviousNeuralwattConfig } from "../types";
7
+
8
+ type MigrationConfig = PreviousNeuralwattConfig | NeuralwattConfig;
9
+
10
+ type FlatConfigKey = keyof Omit<PreviousNeuralwattConfig, "$schema">;
11
+ type MutableConfigRecord = Record<string, unknown>;
12
+
13
+ const FLAT_CONFIG_KEYS = [
14
+ "quotaCommand",
15
+ "quotaWarnings",
16
+ "subBarIntegration",
17
+ "includeLegacyModelIds",
18
+ "includeHiddenModels",
19
+ ] as const satisfies readonly FlatConfigKey[];
20
+
21
+ const FLAT_CONFIG_MIGRATION_MESSAGE =
22
+ "Config migrated to the nested format. A backup was written next to the original config file.";
23
+
24
+ function booleanValue(
25
+ record: MutableConfigRecord,
26
+ key: FlatConfigKey,
27
+ ): boolean | undefined {
28
+ return typeof record[key] === "boolean"
29
+ ? (record[key] as boolean)
30
+ : undefined;
31
+ }
32
+
33
+ function isObject(value: unknown): value is Record<string, unknown> {
34
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
35
+ }
36
+
37
+ function isPreviousConfig(
38
+ config: MigrationConfig,
39
+ ): config is PreviousNeuralwattConfig {
40
+ const record = config as MutableConfigRecord;
41
+ return FLAT_CONFIG_KEYS.some((key) => typeof record[key] === "boolean");
42
+ }
43
+
44
+ export async function backupConfig(filePath: string): Promise<void> {
45
+ const dir = dirname(filePath);
46
+ const base = basename(filePath, ".json");
47
+ const backupPath = join(
48
+ dir,
49
+ `${base}.v${packageJson.version}-flat-config.json`,
50
+ );
51
+
52
+ try {
53
+ await copyFile(filePath, backupPath, constants.COPYFILE_EXCL);
54
+ } catch (error) {
55
+ if (
56
+ error &&
57
+ typeof error === "object" &&
58
+ "code" in error &&
59
+ error.code === "EEXIST"
60
+ ) {
61
+ return;
62
+ }
63
+ throw error;
64
+ }
65
+ }
66
+
67
+ export const flatToNestedConfigMigration: Migration<MigrationConfig> = {
68
+ name: "flat-to-nested-config",
69
+ shouldRun: isPreviousConfig,
70
+ message: FLAT_CONFIG_MIGRATION_MESSAGE,
71
+ run: async (config, filePath) => {
72
+ await backupConfig(filePath);
73
+
74
+ const previous = config as PreviousNeuralwattConfig;
75
+ const nestedConfig = config as NeuralwattConfig;
76
+ const record = previous as MutableConfigRecord;
77
+ const nested: NeuralwattConfig = {
78
+ provider: {
79
+ ...(isObject(nestedConfig.provider) ? nestedConfig.provider : {}),
80
+ },
81
+ quotaCommand: isObject(nestedConfig.quotaCommand)
82
+ ? { ...nestedConfig.quotaCommand }
83
+ : {},
84
+ quotaWarnings: isObject(nestedConfig.quotaWarnings)
85
+ ? { ...nestedConfig.quotaWarnings }
86
+ : {},
87
+ subBarIntegration: isObject(nestedConfig.subBarIntegration)
88
+ ? { ...nestedConfig.subBarIntegration }
89
+ : {},
90
+ };
91
+
92
+ const includeLegacyModelIds = booleanValue(record, "includeLegacyModelIds");
93
+ if (
94
+ nested.provider?.includeLegacyModelIds === undefined &&
95
+ includeLegacyModelIds !== undefined
96
+ ) {
97
+ nested.provider = {
98
+ ...nested.provider,
99
+ includeLegacyModelIds,
100
+ };
101
+ }
102
+
103
+ const includeHiddenModels = booleanValue(record, "includeHiddenModels");
104
+ if (
105
+ nested.provider?.includeHiddenModels === undefined &&
106
+ includeHiddenModels !== undefined
107
+ ) {
108
+ nested.provider = {
109
+ ...nested.provider,
110
+ includeHiddenModels,
111
+ };
112
+ }
113
+
114
+ const quotaCommand = booleanValue(record, "quotaCommand");
115
+ if (
116
+ nested.quotaCommand?.enabled === undefined &&
117
+ quotaCommand !== undefined
118
+ ) {
119
+ nested.quotaCommand = {
120
+ ...nested.quotaCommand,
121
+ enabled: quotaCommand,
122
+ };
123
+ }
124
+
125
+ const quotaWarnings = booleanValue(record, "quotaWarnings");
126
+ if (
127
+ nested.quotaWarnings?.enabled === undefined &&
128
+ quotaWarnings !== undefined
129
+ ) {
130
+ nested.quotaWarnings = {
131
+ ...nested.quotaWarnings,
132
+ enabled: quotaWarnings,
133
+ };
134
+ }
135
+
136
+ const subBarIntegration = booleanValue(record, "subBarIntegration");
137
+ if (
138
+ nested.subBarIntegration?.enabled === undefined &&
139
+ subBarIntegration !== undefined
140
+ ) {
141
+ nested.subBarIntegration = {
142
+ ...nested.subBarIntegration,
143
+ enabled: subBarIntegration,
144
+ };
145
+ }
146
+
147
+ return nested;
148
+ },
149
+ };
@@ -0,0 +1,16 @@
1
+ import type { Migration } from "@aliou/pi-utils-settings";
2
+ import type { NeuralwattRawConfig } from "../types";
3
+
4
+ export { disableLegacyModelIdsByDefaultMigration } from "./01-disable-legacy-model-ids-by-default";
5
+ export {
6
+ backupConfig,
7
+ flatToNestedConfigMigration,
8
+ } from "./02-flat-to-nested-config";
9
+
10
+ import { disableLegacyModelIdsByDefaultMigration } from "./01-disable-legacy-model-ids-by-default";
11
+ import { flatToNestedConfigMigration } from "./02-flat-to-nested-config";
12
+
13
+ export const migrations: Migration<NeuralwattRawConfig>[] = [
14
+ disableLegacyModelIdsByDefaultMigration,
15
+ flatToNestedConfigMigration,
16
+ ];
@@ -0,0 +1,77 @@
1
+ export interface PreviousNeuralwattConfig {
2
+ /** $schema URL for editor autocomplete. */
3
+ $schema?: string;
4
+
5
+ /** Show the quota command (/neuralwatt:quota). */
6
+ quotaCommand?: boolean;
7
+
8
+ /** Show quota warnings when credits or energy are low. */
9
+ quotaWarnings?: boolean;
10
+
11
+ /** Show usage in the sub-bar / status bar. */
12
+ subBarIntegration?: boolean;
13
+
14
+ /** Include legacy Neuralwatt model IDs in the model picker. */
15
+ includeLegacyModelIds?: boolean;
16
+
17
+ /** Include hidden Neuralwatt models discovered via the authenticated API. */
18
+ includeHiddenModels?: boolean;
19
+ }
20
+
21
+ export interface NeuralwattProviderConfig {
22
+ /** Include legacy Neuralwatt model IDs in the model picker. */
23
+ includeLegacyModelIds?: boolean;
24
+
25
+ /** Include hidden Neuralwatt models discovered via the authenticated API. */
26
+ includeHiddenModels?: boolean;
27
+ }
28
+
29
+ export interface NeuralwattQuotaCommandConfig {
30
+ /** Show the quota command (/neuralwatt:quota). */
31
+ enabled?: boolean;
32
+ }
33
+
34
+ export interface NeuralwattQuotaWarningsConfig {
35
+ /** Show quota warnings when credits or energy are low. */
36
+ enabled?: boolean;
37
+ }
38
+
39
+ export interface NeuralwattSubBarIntegrationConfig {
40
+ /** Show usage in the sub-bar / status bar. */
41
+ enabled?: boolean;
42
+ }
43
+
44
+ export interface NeuralwattConfig {
45
+ /** $schema URL for editor autocomplete. */
46
+ $schema?: string;
47
+
48
+ /** Provider/model behavior. */
49
+ provider?: NeuralwattProviderConfig;
50
+
51
+ /** Quota command feature. */
52
+ quotaCommand?: NeuralwattQuotaCommandConfig;
53
+
54
+ /** Quota warning feature. */
55
+ quotaWarnings?: NeuralwattQuotaWarningsConfig;
56
+
57
+ /** Sub-bar/status-bar integration feature. */
58
+ subBarIntegration?: NeuralwattSubBarIntegrationConfig;
59
+ }
60
+
61
+ export type NeuralwattRawConfig = PreviousNeuralwattConfig | NeuralwattConfig;
62
+
63
+ export interface ResolvedNeuralwattConfig {
64
+ provider: {
65
+ includeLegacyModelIds: boolean;
66
+ includeHiddenModels: boolean;
67
+ };
68
+ quotaCommand: {
69
+ enabled: boolean;
70
+ };
71
+ quotaWarnings: {
72
+ enabled: boolean;
73
+ };
74
+ subBarIntegration: {
75
+ enabled: boolean;
76
+ };
77
+ }
@@ -1,6 +1,19 @@
1
- import type { NeuralwattQuotas } from "./quota-api";
1
+ import type { ResolvedNeuralwattConfig } from "./config";
2
+ import type { NeuralwattQuotas } from "./types/quota-api";
2
3
 
3
- export type QuotaSource = "header" | "api" | "sse";
4
+ export type NeuralwattFeatureId =
5
+ | "quotaCommand"
6
+ | "quotaWarnings"
7
+ | "subBarIntegration";
8
+
9
+ export const NEURALWATT_EXTENSIONS_REQUEST_EVENT =
10
+ "neuralwatt:extensions:request" as const;
11
+
12
+ export const NEURALWATT_EXTENSIONS_REGISTER_EVENT =
13
+ "neuralwatt:extensions:register" as const;
14
+
15
+ export const NEURALWATT_CONFIG_UPDATED_EVENT =
16
+ "neuralwatt:config:updated" as const;
4
17
 
5
18
  export const NEURALWATT_QUOTAS_UPDATED_EVENT =
6
19
  "neuralwatt:quotas:updated" as const;
@@ -8,22 +21,21 @@ export const NEURALWATT_QUOTAS_UPDATED_EVENT =
8
21
  export const NEURALWATT_QUOTAS_REQUEST_EVENT =
9
22
  "neuralwatt:quotas:request" as const;
10
23
 
24
+ export interface NeuralwattExtensionsRegisterPayload {
25
+ feature: NeuralwattFeatureId;
26
+ }
27
+
28
+ export interface NeuralwattConfigUpdatedPayload {
29
+ config: ResolvedNeuralwattConfig;
30
+ }
31
+
32
+ export type QuotaSource = "header" | "api" | "sse";
33
+
11
34
  export interface NeuralwattQuotasUpdatedPayload {
12
35
  quotas: NeuralwattQuotas;
13
36
  source: QuotaSource;
14
37
  }
15
38
 
16
- export type QuotasErrorKind =
17
- | "cancelled"
18
- | "timeout"
19
- | "config"
20
- | "http"
21
- | "network";
22
-
23
- export type QuotasResult =
24
- | { success: true; data: { quotas: NeuralwattQuotas } }
25
- | { success: false; error: { message: string; kind: QuotasErrorKind } };
26
-
27
39
  /** Minimal quota data parsed from response headers */
28
40
  export interface NeuralwattHeaderQuotas {
29
41
  allowanceRemainingUsd: number;
@@ -3,7 +3,7 @@ import type {
3
3
  NeuralwattApiModelsResponse,
4
4
  } from "../types/models-api";
5
5
  import type { NeuralwattQuotas } from "../types/quota-api";
6
- import type { QuotasResult } from "../types/quota-events";
6
+ import type { QuotasResult } from "../types/quota-result";
7
7
 
8
8
  const BASE_URL = "https://api.neuralwatt.com/v1";
9
9
  const FETCH_TIMEOUT_MS = 15_000;
@@ -0,0 +1,12 @@
1
+ import type { NeuralwattQuotas } from "./quota-api";
2
+
3
+ export type QuotasErrorKind =
4
+ | "cancelled"
5
+ | "timeout"
6
+ | "config"
7
+ | "http"
8
+ | "network";
9
+
10
+ export type QuotasResult =
11
+ | { success: true; data: { quotas: NeuralwattQuotas } }
12
+ | { success: false; error: { message: string; kind: QuotasErrorKind } };
@@ -1,5 +1,3 @@
1
- import type { Theme } from "@earendil-works/pi-coding-agent";
2
-
3
1
  export type Severity = "success" | "warning" | "error";
4
2
 
5
3
  export function severityFromPercent(pct: number): Severity {
@@ -8,42 +6,6 @@ export function severityFromPercent(pct: number): Severity {
8
6
  return "error";
9
7
  }
10
8
 
11
- export type BarStyle = "filled-used" | "filled-remaining";
12
-
13
- /**
14
- * Render a progress bar.
15
- *
16
- * filled-used: filled region = used portion (colored by severity of remaining%)
17
- * filled-remaining: filled region = remaining portion (colored by severity of remaining%)
18
- */
19
- export function renderProgressBar(
20
- percent: number,
21
- width: number,
22
- theme: Theme,
23
- severity: Severity,
24
- style: BarStyle = "filled-remaining",
25
- ): string {
26
- const clamped = Math.max(0, Math.min(100, Math.round(percent)));
27
- const filledCount = Math.round((clamped / 100) * width);
28
-
29
- const parts: string[] = [];
30
- for (let idx = 0; idx < width; idx++) {
31
- const isFilled = idx < filledCount;
32
- if (style === "filled-used") {
33
- // filled = used (severity color), empty = remaining (dim)
34
- parts.push(
35
- isFilled ? theme.fg(severity, "\u2593") : theme.fg("success", "\u2591"),
36
- );
37
- } else {
38
- // filled = remaining (severity color), empty = used (dim)
39
- parts.push(
40
- isFilled ? theme.fg(severity, "\u2588") : theme.fg("dim", "\u2591"),
41
- );
42
- }
43
- }
44
- return parts.join("");
45
- }
46
-
47
9
  export function percentCreditsRemaining(quotas: {
48
10
  balance: { credits_remaining_usd: number; total_credits_usd: number };
49
11
  }): number {
package/src/config.ts DELETED
@@ -1,210 +0,0 @@
1
- import {
2
- ConfigLoader,
3
- registerSettingsCommand,
4
- type SettingsSection,
5
- } from "@aliou/pi-utils-settings";
6
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
7
- import type { SettingItem } from "@earendil-works/pi-tui";
8
-
9
- export type NeuralwattFeatureId =
10
- | "quotaCommand"
11
- | "quotaWarnings"
12
- | "subBarIntegration";
13
-
14
- export const NEURALWATT_EXTENSIONS_REQUEST_EVENT =
15
- "neuralwatt:extensions:request" as const;
16
-
17
- export const NEURALWATT_EXTENSIONS_REGISTER_EVENT =
18
- "neuralwatt:extensions:register" as const;
19
-
20
- export interface NeuralwattExtensionsRegisterPayload {
21
- feature: NeuralwattFeatureId;
22
- }
23
-
24
- export interface NeuralwattConfig {
25
- /** Show the quota command (/neuralwatt:quota). */
26
- quotaCommand?: boolean;
27
- /** Show quota warnings when credits or energy are low. */
28
- quotaWarnings?: boolean;
29
- /** Show usage in the sub-bar / status bar. */
30
- subBarIntegration?: boolean;
31
- /** Include legacy Neuralwatt model IDs in the model picker. */
32
- includeLegacyModelIds?: boolean;
33
- /** Include hidden Neuralwatt models discovered via the authenticated API. */
34
- includeHiddenModels?: boolean;
35
- }
36
-
37
- export interface ResolvedNeuralwattConfig {
38
- quotaCommand: boolean;
39
- quotaWarnings: boolean;
40
- subBarIntegration: boolean;
41
- includeLegacyModelIds: boolean;
42
- includeHiddenModels: boolean;
43
- }
44
-
45
- const DEFAULTS: ResolvedNeuralwattConfig = {
46
- quotaCommand: true,
47
- quotaWarnings: true,
48
- subBarIntegration: true,
49
- includeLegacyModelIds: false,
50
- includeHiddenModels: false,
51
- };
52
-
53
- export const configLoader = new ConfigLoader<
54
- NeuralwattConfig,
55
- ResolvedNeuralwattConfig
56
- >("neuralwatt", DEFAULTS, {
57
- migrations: [
58
- {
59
- name: "disable-legacy-model-ids-by-default",
60
- shouldRun: (config) => config.includeLegacyModelIds === undefined,
61
- message:
62
- "[neuralwatt] legacy model IDs (ids including the provider and the quantization) are disabled by default. You can enable them with /neuralwatt:settings.",
63
- run: (config) => ({ ...config, includeLegacyModelIds: false }),
64
- },
65
- ],
66
- });
67
-
68
- export const NEURALWATT_CONFIG_UPDATED_EVENT =
69
- "neuralwatt:config:updated" as const;
70
-
71
- export interface NeuralwattConfigUpdatedPayload {
72
- config: ResolvedNeuralwattConfig;
73
- }
74
-
75
- export function emitConfigUpdated(pi: ExtensionAPI): void {
76
- pi.events.emit(NEURALWATT_CONFIG_UPDATED_EVENT, {
77
- config: configLoader.getConfig(),
78
- });
79
- }
80
-
81
- export interface RegisterNeuralwattSettingsOptions {
82
- getLoadedFeatures: () => Set<NeuralwattFeatureId>;
83
- }
84
-
85
- function featureRow(
86
- id: NeuralwattFeatureId,
87
- label: string,
88
- description: string,
89
- configValue: boolean,
90
- isLoaded: boolean,
91
- ): SettingItem {
92
- if (isLoaded) {
93
- return {
94
- id,
95
- label,
96
- description,
97
- currentValue: configValue ? "enabled" : "disabled",
98
- values: ["enabled", "disabled"],
99
- };
100
- }
101
- return {
102
- id,
103
- label,
104
- description: `${description} (Not loaded by Pi)`,
105
- currentValue: "unavailable",
106
- values: [],
107
- };
108
- }
109
-
110
- export function registerNeuralwattSettings(
111
- pi: ExtensionAPI,
112
- options: RegisterNeuralwattSettingsOptions,
113
- ): void {
114
- const { getLoadedFeatures } = options;
115
-
116
- registerSettingsCommand<NeuralwattConfig, ResolvedNeuralwattConfig>(pi, {
117
- commandName: "neuralwatt:settings",
118
- title: "Neuralwatt Settings",
119
- configStore: configLoader,
120
- buildSections: (tabConfig, resolved): SettingsSection[] => {
121
- const loaded = getLoadedFeatures();
122
- return [
123
- {
124
- label: "Features",
125
- items: [
126
- featureRow(
127
- "quotaCommand",
128
- "Quota command",
129
- "Toggle the /neuralwatt:quota command, showing your API usage at a glance",
130
- tabConfig?.quotaCommand ?? resolved.quotaCommand,
131
- loaded.has("quotaCommand"),
132
- ),
133
- featureRow(
134
- "quotaWarnings",
135
- "Quota warnings",
136
- "Toggle notifications when credits or energy are running low",
137
- tabConfig?.quotaWarnings ?? resolved.quotaWarnings,
138
- loaded.has("quotaWarnings"),
139
- ),
140
- featureRow(
141
- "subBarIntegration",
142
- "Sub-bar integration",
143
- "Toggle integration with the status bar and sub-core",
144
- tabConfig?.subBarIntegration ?? resolved.subBarIntegration,
145
- loaded.has("subBarIntegration"),
146
- ),
147
- ],
148
- },
149
- {
150
- label: "Other settings",
151
- items: [
152
- {
153
- id: "includeLegacyModelIds",
154
- label: "Legacy model IDs",
155
- description:
156
- "Include deprecated Neuralwatt model IDs as aliases in the model picker",
157
- currentValue:
158
- (tabConfig?.includeLegacyModelIds ??
159
- resolved.includeLegacyModelIds)
160
- ? "include"
161
- : "ignore",
162
- values: ["include", "ignore"],
163
- },
164
- {
165
- id: "includeHiddenModels",
166
- label: "Hidden models",
167
- description:
168
- "Include Neuralwatt models that are accessible via API key but not advertised in the public model list",
169
- currentValue:
170
- (tabConfig?.includeHiddenModels ?? resolved.includeHiddenModels)
171
- ? "include"
172
- : "ignore",
173
- values: ["include", "ignore"],
174
- },
175
- ],
176
- },
177
- ];
178
- },
179
- onSettingChange: (id, newValue, config) => {
180
- // Non-feature toggles are handled first so they are not blocked by the
181
- // loaded-features guard (they are managed directly by the provider).
182
- if (id === "includeLegacyModelIds") {
183
- return { ...config, includeLegacyModelIds: newValue === "include" };
184
- }
185
-
186
- if (id === "includeHiddenModels") {
187
- return { ...config, includeHiddenModels: newValue === "include" };
188
- }
189
-
190
- if (!getLoadedFeatures().has(id as NeuralwattFeatureId)) {
191
- return null;
192
- }
193
-
194
- const enabled = newValue === "enabled";
195
- switch (id) {
196
- case "quotaCommand":
197
- return { ...config, quotaCommand: enabled };
198
- case "quotaWarnings":
199
- return { ...config, quotaWarnings: enabled };
200
- case "subBarIntegration":
201
- return { ...config, subBarIntegration: enabled };
202
- default:
203
- return null;
204
- }
205
- },
206
- onSave: async () => {
207
- emitConfigUpdated(pi);
208
- },
209
- });
210
- }
File without changes