@dex-ai/coding-agent 0.1.92
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/bin/dex.ts +402 -0
- package/package.json +45 -0
- package/src/__tests__/command-validation.test.ts +205 -0
- package/src/__tests__/history.test.ts +183 -0
- package/src/cli-extension.ts +153 -0
- package/src/commands/extension-loader.ts +399 -0
- package/src/commands/extension.ts +924 -0
- package/src/commands/update.ts +419 -0
- package/src/env.d.ts +5 -0
- package/src/extensions/cli-tui-components/ActivityPanel.vue +24 -0
- package/src/extensions/cli-tui-components/ActivityPanel.vue.compiled.ts +96 -0
- package/src/extensions/cli-tui-components/App.vue +127 -0
- package/src/extensions/cli-tui-components/App.vue.compiled.ts +374 -0
- package/src/extensions/cli-tui-components/ApprovalPrompt.vue +30 -0
- package/src/extensions/cli-tui-components/ApprovalPrompt.vue.compiled.ts +72 -0
- package/src/extensions/cli-tui-components/AskPanel.vue +228 -0
- package/src/extensions/cli-tui-components/AskPanel.vue.compiled.ts +419 -0
- package/src/extensions/cli-tui-components/CommandPalette.vue +19 -0
- package/src/extensions/cli-tui-components/CommandPalette.vue.compiled.ts +65 -0
- package/src/extensions/cli-tui-components/ConfirmModal.vue +29 -0
- package/src/extensions/cli-tui-components/ConfirmModal.vue.compiled.ts +72 -0
- package/src/extensions/cli-tui-components/DiffView.vue +139 -0
- package/src/extensions/cli-tui-components/DiffView.vue.compiled.ts +274 -0
- package/src/extensions/cli-tui-components/FormModal.vue +58 -0
- package/src/extensions/cli-tui-components/FormModal.vue.compiled.ts +156 -0
- package/src/extensions/cli-tui-components/Header.vue +13 -0
- package/src/extensions/cli-tui-components/Header.vue.compiled.ts +42 -0
- package/src/extensions/cli-tui-components/InputArea.vue +202 -0
- package/src/extensions/cli-tui-components/InputArea.vue.compiled.ts +243 -0
- package/src/extensions/cli-tui-components/InteractivePanel.vue +32 -0
- package/src/extensions/cli-tui-components/InteractivePanel.vue.compiled.ts +103 -0
- package/src/extensions/cli-tui-components/ListModal.vue +58 -0
- package/src/extensions/cli-tui-components/ListModal.vue.compiled.ts +130 -0
- package/src/extensions/cli-tui-components/MarkdownContent.ts +54 -0
- package/src/extensions/cli-tui-components/Messages.vue +68 -0
- package/src/extensions/cli-tui-components/Messages.vue.compiled.ts +253 -0
- package/src/extensions/cli-tui-components/Modal.vue +56 -0
- package/src/extensions/cli-tui-components/Modal.vue.compiled.ts +61 -0
- package/src/extensions/cli-tui-components/SettingsPanel.vue +178 -0
- package/src/extensions/cli-tui-components/SettingsPanel.vue.compiled.ts +359 -0
- package/src/extensions/cli-tui-components/Spinner.vue +19 -0
- package/src/extensions/cli-tui-components/Spinner.vue.compiled.ts +42 -0
- package/src/extensions/cli-tui-components/StatusBar.vue +45 -0
- package/src/extensions/cli-tui-components/StatusBar.vue.compiled.ts +106 -0
- package/src/extensions/cli-tui-components/SteeringPreview.vue +11 -0
- package/src/extensions/cli-tui-components/SteeringPreview.vue.compiled.ts +38 -0
- package/src/extensions/cli-tui-components/ThinkingBlock.vue +40 -0
- package/src/extensions/cli-tui-components/ThinkingBlock.vue.compiled.ts +82 -0
- package/src/extensions/cli-tui-components/ToolCall.vue +114 -0
- package/src/extensions/cli-tui-components/ToolCall.vue.compiled.ts +319 -0
- package/src/extensions/cli-tui-components/UserMessage.vue +40 -0
- package/src/extensions/cli-tui-components/UserMessage.vue.compiled.ts +148 -0
- package/src/extensions/cli-tui-components/ask-panel-controller.ts +573 -0
- package/src/extensions/cli-tui-components/settings-panel-controller.ts +958 -0
- package/src/extensions/cli-tui.ts +2349 -0
- package/src/extensions/debug.ts +46 -0
- package/src/extensions/headless.ts +55 -0
- package/src/extensions/modal-system.ts +719 -0
- package/src/host.ts +505 -0
- package/src/index.ts +9 -0
- package/src/input/history.ts +233 -0
- package/src/input/index.ts +6 -0
- package/src/panels/dynamic-panel.ts +5 -0
- package/src/panels/index.ts +43 -0
- package/src/panels/state.ts +73 -0
- package/src/panels/types.ts +79 -0
- package/src/panels/widget.ts +25 -0
- package/src/provider-registry.ts +44 -0
- package/src/stderr-capture.ts +248 -0
- package/src/types.ts +20 -0
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extension loader — loads installed extensions from registry.
|
|
3
|
+
*
|
|
4
|
+
* Reads registry.json from user and project scopes, resolves each extension's
|
|
5
|
+
* entry point from its isolated node_modules/, and imports it.
|
|
6
|
+
*
|
|
7
|
+
* Each extension is expected to export:
|
|
8
|
+
* - default: Extension | Extension[] (native dex extension)
|
|
9
|
+
* - For pi-compat: detected via "pi" field in package.json, loaded via @dex-ai/pi-compat
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
13
|
+
import { join, resolve } from "node:path";
|
|
14
|
+
import { homedir } from "node:os";
|
|
15
|
+
import type { Extension } from "@dex-ai/sdk";
|
|
16
|
+
|
|
17
|
+
/* ------------------------------------------------------------------ */
|
|
18
|
+
/* Constants */
|
|
19
|
+
/* ------------------------------------------------------------------ */
|
|
20
|
+
|
|
21
|
+
const USER_EXTENSIONS_DIR = join(homedir(), ".dex", "extensions");
|
|
22
|
+
const USER_PACKAGES_DIR = join(USER_EXTENSIONS_DIR, "packages");
|
|
23
|
+
const USER_REGISTRY_PATH = join(USER_EXTENSIONS_DIR, "registry.json");
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Extensions that are always injected by CodingAgent.create() (the SDK layer).
|
|
27
|
+
* User-installed copies of these would result in duplicates — skip loading them.
|
|
28
|
+
*/
|
|
29
|
+
const SDK_BUILTIN_PACKAGES = new Set([
|
|
30
|
+
"@dex-ai/tools-extension",
|
|
31
|
+
"@dex-ai/skills-extension",
|
|
32
|
+
"@dex-ai/ask-extension",
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
/* ------------------------------------------------------------------ */
|
|
36
|
+
/* Types */
|
|
37
|
+
/* ------------------------------------------------------------------ */
|
|
38
|
+
|
|
39
|
+
interface RegistryEntry {
|
|
40
|
+
source: "npm" | "file";
|
|
41
|
+
package: string;
|
|
42
|
+
version?: string;
|
|
43
|
+
path?: string;
|
|
44
|
+
type?: "native" | "pi-compat";
|
|
45
|
+
installedAt: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface Registry {
|
|
49
|
+
version: 1;
|
|
50
|
+
extensions: Record<string, RegistryEntry>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* ------------------------------------------------------------------ */
|
|
54
|
+
/* Registry reading */
|
|
55
|
+
/* ------------------------------------------------------------------ */
|
|
56
|
+
|
|
57
|
+
function loadRegistry(registryPath: string): Registry {
|
|
58
|
+
if (!existsSync(registryPath)) {
|
|
59
|
+
return { version: 1, extensions: {} };
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const data = JSON.parse(readFileSync(registryPath, "utf-8"));
|
|
63
|
+
if (data.version === 1 && data.extensions) return data;
|
|
64
|
+
return { version: 1, extensions: {} };
|
|
65
|
+
} catch {
|
|
66
|
+
return { version: 1, extensions: {} };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* ------------------------------------------------------------------ */
|
|
71
|
+
/* Entry point resolution */
|
|
72
|
+
/* ------------------------------------------------------------------ */
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Resolve the entry point for an extension package.
|
|
76
|
+
*
|
|
77
|
+
* Checks in order:
|
|
78
|
+
* 1. package.json "dex.extensions" field
|
|
79
|
+
* 2. package.json "exports"."." field
|
|
80
|
+
* 3. package.json "main" field
|
|
81
|
+
* 4. index.ts / index.js
|
|
82
|
+
*/
|
|
83
|
+
function resolveEntryPoint(packageDir: string): string | null {
|
|
84
|
+
const pkgJsonPath = join(packageDir, "package.json");
|
|
85
|
+
if (!existsSync(pkgJsonPath)) return null;
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
|
|
89
|
+
|
|
90
|
+
// 1. dex.extensions field
|
|
91
|
+
if (pkg.dex?.extensions?.length) {
|
|
92
|
+
const first = pkg.dex.extensions[0];
|
|
93
|
+
const resolved = join(packageDir, first);
|
|
94
|
+
if (existsSync(resolved)) return resolved;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 2. exports."." field
|
|
98
|
+
if (pkg.exports?.["."]?.default) {
|
|
99
|
+
const resolved = join(packageDir, pkg.exports["."].default);
|
|
100
|
+
if (existsSync(resolved)) return resolved;
|
|
101
|
+
}
|
|
102
|
+
if (pkg.exports?.["."]) {
|
|
103
|
+
const exp = pkg.exports["."];
|
|
104
|
+
if (typeof exp === "string") {
|
|
105
|
+
const resolved = join(packageDir, exp);
|
|
106
|
+
if (existsSync(resolved)) return resolved;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 3. main field
|
|
111
|
+
if (pkg.main) {
|
|
112
|
+
const resolved = join(packageDir, pkg.main);
|
|
113
|
+
if (existsSync(resolved)) return resolved;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 4. index.ts / index.js
|
|
117
|
+
const indexTs = join(packageDir, "index.ts");
|
|
118
|
+
if (existsSync(indexTs)) return indexTs;
|
|
119
|
+
const indexJs = join(packageDir, "index.js");
|
|
120
|
+
if (existsSync(indexJs)) return indexJs;
|
|
121
|
+
|
|
122
|
+
// 5. src/index.ts (common convention)
|
|
123
|
+
const srcIndexTs = join(packageDir, "src", "index.ts");
|
|
124
|
+
if (existsSync(srcIndexTs)) return srcIndexTs;
|
|
125
|
+
|
|
126
|
+
return null;
|
|
127
|
+
} catch {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/* ------------------------------------------------------------------ */
|
|
133
|
+
/* Pi-compat loading */
|
|
134
|
+
/* ------------------------------------------------------------------ */
|
|
135
|
+
|
|
136
|
+
async function loadPiCompatExtension(
|
|
137
|
+
extensionDir: string,
|
|
138
|
+
packageName: string,
|
|
139
|
+
): Promise<Extension | null> {
|
|
140
|
+
// Find @dex-ai/pi-compat — check user packages first
|
|
141
|
+
const piCompatDir = join(USER_PACKAGES_DIR, "pi-compat");
|
|
142
|
+
const piCompatPkgDir = join(
|
|
143
|
+
piCompatDir,
|
|
144
|
+
"node_modules",
|
|
145
|
+
"@dex-ai",
|
|
146
|
+
"pi-compat",
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
if (!existsSync(piCompatPkgDir)) {
|
|
150
|
+
console.error(
|
|
151
|
+
`Cannot load pi-compat extension "${packageName}": @dex-ai/pi-compat is not installed.`,
|
|
152
|
+
);
|
|
153
|
+
console.error(
|
|
154
|
+
"Install it with: dex extension install npm:@dex-ai/pi-compat",
|
|
155
|
+
);
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
const piCompatEntry = resolveEntryPoint(piCompatPkgDir);
|
|
161
|
+
if (!piCompatEntry) {
|
|
162
|
+
console.error(
|
|
163
|
+
"Cannot resolve @dex-ai/pi-compat entry point. Try reinstalling it.",
|
|
164
|
+
);
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const piCompat = await import(piCompatEntry);
|
|
169
|
+
const piCompatExtension =
|
|
170
|
+
piCompat.piCompatExtension ?? piCompat.default?.piCompatExtension;
|
|
171
|
+
|
|
172
|
+
if (typeof piCompatExtension !== "function") {
|
|
173
|
+
console.error(
|
|
174
|
+
"@dex-ai/pi-compat does not export piCompatExtension function.",
|
|
175
|
+
);
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// The pi-compat adapter loads the extension from the package's node_modules
|
|
180
|
+
const packageDir = join(extensionDir, "node_modules", packageName);
|
|
181
|
+
const ext = await piCompatExtension({
|
|
182
|
+
paths: [packageDir],
|
|
183
|
+
});
|
|
184
|
+
return ext;
|
|
185
|
+
} catch (err) {
|
|
186
|
+
console.error(
|
|
187
|
+
`Failed to load pi-compat extension "${packageName}":`,
|
|
188
|
+
err instanceof Error ? err.message : err,
|
|
189
|
+
);
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/* ------------------------------------------------------------------ */
|
|
195
|
+
/* Native extension loading */
|
|
196
|
+
/* ------------------------------------------------------------------ */
|
|
197
|
+
|
|
198
|
+
async function loadNativeExtension(
|
|
199
|
+
extensionDir: string,
|
|
200
|
+
packageName: string,
|
|
201
|
+
): Promise<Extension[]> {
|
|
202
|
+
const packageDir = join(extensionDir, "node_modules", packageName);
|
|
203
|
+
|
|
204
|
+
if (!existsSync(packageDir)) {
|
|
205
|
+
console.error(`Extension package not found at: ${packageDir}`);
|
|
206
|
+
return [];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const entryPoint = resolveEntryPoint(packageDir);
|
|
210
|
+
if (!entryPoint) {
|
|
211
|
+
console.error(
|
|
212
|
+
`Cannot resolve entry point for "${packageName}" in ${packageDir}`,
|
|
213
|
+
);
|
|
214
|
+
return [];
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
const mod = await import(entryPoint);
|
|
219
|
+
const ext = mod.default;
|
|
220
|
+
|
|
221
|
+
if (Array.isArray(ext)) {
|
|
222
|
+
return ext.filter(
|
|
223
|
+
(e: unknown) => e && typeof e === "object" && (e as any).name,
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
if (ext && typeof ext === "object" && ext.name) {
|
|
227
|
+
return [ext];
|
|
228
|
+
}
|
|
229
|
+
if (typeof ext === "function") {
|
|
230
|
+
let result = ext();
|
|
231
|
+
// Await if the factory returns a promise (async factories)
|
|
232
|
+
if (
|
|
233
|
+
result &&
|
|
234
|
+
typeof result === "object" &&
|
|
235
|
+
typeof result.then === "function"
|
|
236
|
+
) {
|
|
237
|
+
result = await result;
|
|
238
|
+
}
|
|
239
|
+
if (result && typeof result === "object" && result.name) {
|
|
240
|
+
return [result];
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Try named exports
|
|
245
|
+
const extensions: Extension[] = [];
|
|
246
|
+
for (const [key, value] of Object.entries(mod)) {
|
|
247
|
+
if (key === "default") continue;
|
|
248
|
+
|
|
249
|
+
// Named export is already an Extension object
|
|
250
|
+
if (value && typeof value === "object" && (value as any).name) {
|
|
251
|
+
extensions.push(value as Extension);
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Named export is a factory function (e.g. devtoolsExtension())
|
|
256
|
+
if (typeof value === "function" && key.includes("xtension")) {
|
|
257
|
+
try {
|
|
258
|
+
let result = (value as Function)();
|
|
259
|
+
// Await if the factory returns a promise (async factories)
|
|
260
|
+
if (
|
|
261
|
+
result &&
|
|
262
|
+
typeof result === "object" &&
|
|
263
|
+
typeof result.then === "function"
|
|
264
|
+
) {
|
|
265
|
+
result = await result;
|
|
266
|
+
}
|
|
267
|
+
if (result && typeof result === "object" && (result as any).name) {
|
|
268
|
+
extensions.push(result as Extension);
|
|
269
|
+
}
|
|
270
|
+
} catch {
|
|
271
|
+
// Factory requires args — skip
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (extensions.length > 0) return extensions;
|
|
276
|
+
|
|
277
|
+
// Extension loaded but no Extension objects found.
|
|
278
|
+
// This is normal for extensions that require configuration.
|
|
279
|
+
return [];
|
|
280
|
+
} catch (err) {
|
|
281
|
+
console.error(
|
|
282
|
+
`Failed to load extension "${packageName}":`,
|
|
283
|
+
err instanceof Error ? err.message : err,
|
|
284
|
+
);
|
|
285
|
+
return [];
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/* ------------------------------------------------------------------ */
|
|
290
|
+
/* Public API */
|
|
291
|
+
/* ------------------------------------------------------------------ */
|
|
292
|
+
|
|
293
|
+
export interface LoadedExtensions {
|
|
294
|
+
extensions: Extension[];
|
|
295
|
+
providers: Map<string, Extension>;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Load all installed extensions from user and project registries.
|
|
300
|
+
* Project scope wins on name collision.
|
|
301
|
+
*/
|
|
302
|
+
export async function loadInstalledExtensions(
|
|
303
|
+
cwd: string,
|
|
304
|
+
): Promise<LoadedExtensions> {
|
|
305
|
+
const extensions: Extension[] = [];
|
|
306
|
+
const providers = new Map<string, Extension>();
|
|
307
|
+
|
|
308
|
+
// Load registries
|
|
309
|
+
const userRegistry = loadRegistry(USER_REGISTRY_PATH);
|
|
310
|
+
const projectRegistryPath = join(cwd, ".dex", "extensions", "registry.json");
|
|
311
|
+
|
|
312
|
+
// Skip project scope if it resolves to the same path as user scope
|
|
313
|
+
const isSamePath =
|
|
314
|
+
resolve(projectRegistryPath) === resolve(USER_REGISTRY_PATH);
|
|
315
|
+
const projectRegistry = isSamePath
|
|
316
|
+
? { version: 1 as const, extensions: {} as Record<string, RegistryEntry> }
|
|
317
|
+
: loadRegistry(projectRegistryPath);
|
|
318
|
+
|
|
319
|
+
// Merge: project wins on collision
|
|
320
|
+
const merged: Record<
|
|
321
|
+
string,
|
|
322
|
+
{ entry: RegistryEntry; scope: "user" | "project" }
|
|
323
|
+
> = {};
|
|
324
|
+
for (const [name, entry] of Object.entries(userRegistry.extensions)) {
|
|
325
|
+
merged[name] = { entry, scope: "user" };
|
|
326
|
+
}
|
|
327
|
+
for (const [name, entry] of Object.entries(projectRegistry.extensions)) {
|
|
328
|
+
merged[name] = { entry, scope: "project" };
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Prepend installed extension binaries to PATH
|
|
332
|
+
for (const [name, { scope }] of Object.entries(merged)) {
|
|
333
|
+
const extDir =
|
|
334
|
+
scope === "project"
|
|
335
|
+
? join(cwd, ".dex", "extensions", name)
|
|
336
|
+
: join(USER_PACKAGES_DIR, name);
|
|
337
|
+
const binPath = join(extDir, "node_modules", ".bin");
|
|
338
|
+
if (existsSync(binPath)) {
|
|
339
|
+
process.env.PATH = `${binPath}:${process.env.PATH}`;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Load each extension
|
|
344
|
+
for (const [name, { entry, scope }] of Object.entries(merged)) {
|
|
345
|
+
// Skip extensions that the SDK injects internally (avoids duplicates)
|
|
346
|
+
if (SDK_BUILTIN_PACKAGES.has(entry.package)) {
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const extensionDir =
|
|
351
|
+
scope === "project"
|
|
352
|
+
? join(cwd, ".dex", "extensions", name)
|
|
353
|
+
: join(USER_PACKAGES_DIR, name);
|
|
354
|
+
|
|
355
|
+
if (!existsSync(extensionDir)) {
|
|
356
|
+
console.error(
|
|
357
|
+
`Extension "${name}" directory not found. Run: dex extension install ${entry.source}:${entry.package}`,
|
|
358
|
+
);
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (entry.type === "pi-compat") {
|
|
363
|
+
const ext = await loadPiCompatExtension(extensionDir, entry.package);
|
|
364
|
+
if (ext) {
|
|
365
|
+
// Pi-compat extensions may provide models (providers)
|
|
366
|
+
if ((ext as any).models && (ext as any).models.length > 0) {
|
|
367
|
+
providers.set(name, ext);
|
|
368
|
+
} else {
|
|
369
|
+
extensions.push(ext);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
} else {
|
|
373
|
+
const exts = await loadNativeExtension(extensionDir, entry.package);
|
|
374
|
+
for (const ext of exts) {
|
|
375
|
+
if ((ext as any).models && (ext as any).models.length > 0) {
|
|
376
|
+
providers.set(name, ext);
|
|
377
|
+
} else {
|
|
378
|
+
extensions.push(ext);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return { extensions, providers };
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Get the disabled extensions list from settings.
|
|
389
|
+
*/
|
|
390
|
+
export function getDisabledExtensions(): string[] {
|
|
391
|
+
const settingsPath = join(homedir(), ".dex", "settings.json");
|
|
392
|
+
if (!existsSync(settingsPath)) return [];
|
|
393
|
+
try {
|
|
394
|
+
const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
395
|
+
return settings.extensions?.disabled ?? [];
|
|
396
|
+
} catch {
|
|
397
|
+
return [];
|
|
398
|
+
}
|
|
399
|
+
}
|