@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,419 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dex update` — Update dex and all installed extensions to latest versions.
|
|
3
|
+
*
|
|
4
|
+
* Checks the registry for newer versions of:
|
|
5
|
+
* 1. @dex-ai/coding-agent (dex itself)
|
|
6
|
+
* 2. All user-installed extensions
|
|
7
|
+
*
|
|
8
|
+
* Uses the configured package manager (settings.packageManager).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
existsSync,
|
|
13
|
+
readFileSync,
|
|
14
|
+
writeFileSync,
|
|
15
|
+
mkdirSync,
|
|
16
|
+
readdirSync,
|
|
17
|
+
} from "node:fs";
|
|
18
|
+
import { join } from "node:path";
|
|
19
|
+
import { homedir } from "node:os";
|
|
20
|
+
import { spawnSync } from "node:child_process";
|
|
21
|
+
|
|
22
|
+
/* ------------------------------------------------------------------ */
|
|
23
|
+
/* Helpers */
|
|
24
|
+
/* ------------------------------------------------------------------ */
|
|
25
|
+
|
|
26
|
+
type PackageManagerName = "npm" | "bun" | "pnpm" | "yarn";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Copy .npmrc from ~/.dex/app to a target directory so that npm resolves
|
|
30
|
+
* packages against the configured registry (e.g. local Verdaccio).
|
|
31
|
+
*/
|
|
32
|
+
function ensureNpmrc(targetDir: string): void {
|
|
33
|
+
const appNpmrc = join(homedir(), ".dex", "app", ".npmrc");
|
|
34
|
+
if (!existsSync(appNpmrc)) return;
|
|
35
|
+
try {
|
|
36
|
+
const contents = readFileSync(appNpmrc, "utf-8");
|
|
37
|
+
mkdirSync(targetDir, { recursive: true });
|
|
38
|
+
writeFileSync(join(targetDir, ".npmrc"), contents);
|
|
39
|
+
} catch {
|
|
40
|
+
// Non-fatal
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Query the configured npm registry for the latest version of a package.
|
|
46
|
+
* Returns null if the query fails (network error, package not found, etc.).
|
|
47
|
+
*/
|
|
48
|
+
function getLatestRegistryVersion(pkg: string, cwd: string): string | null {
|
|
49
|
+
const result = spawnSync("npm", ["view", pkg, "version"], {
|
|
50
|
+
cwd,
|
|
51
|
+
stdio: "pipe",
|
|
52
|
+
});
|
|
53
|
+
if (result.status !== 0) return null;
|
|
54
|
+
const version = result.stdout?.toString().trim();
|
|
55
|
+
return version || null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getConfiguredPackageManager(): PackageManagerName {
|
|
59
|
+
const settingsPath = join(homedir(), ".dex", "settings.json");
|
|
60
|
+
if (existsSync(settingsPath)) {
|
|
61
|
+
try {
|
|
62
|
+
const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
63
|
+
const pm = settings.packageManager;
|
|
64
|
+
if (pm === "npm" || pm === "bun" || pm === "pnpm" || pm === "yarn") {
|
|
65
|
+
return pm;
|
|
66
|
+
}
|
|
67
|
+
} catch {
|
|
68
|
+
// Fall through to default
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return "npm";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function getCurrentDexVersion(): string {
|
|
75
|
+
try {
|
|
76
|
+
const pkgPath = join(import.meta.dir, "../../package.json");
|
|
77
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
78
|
+
return pkg.version ?? "unknown";
|
|
79
|
+
} catch {
|
|
80
|
+
return "unknown";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* ------------------------------------------------------------------ */
|
|
85
|
+
/* Update dex itself */
|
|
86
|
+
/* ------------------------------------------------------------------ */
|
|
87
|
+
|
|
88
|
+
function getDexAppDir(): string {
|
|
89
|
+
return process.env.DEX_APP_DIR ?? join(homedir(), ".dex", "app");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function updateDex(pm: PackageManagerName): {
|
|
93
|
+
updated: boolean;
|
|
94
|
+
oldVersion: string;
|
|
95
|
+
newVersion: string;
|
|
96
|
+
} {
|
|
97
|
+
const oldVersion = getCurrentDexVersion();
|
|
98
|
+
console.log(`Checking for dex updates (current: ${oldVersion})...`);
|
|
99
|
+
|
|
100
|
+
const appDir = getDexAppDir();
|
|
101
|
+
|
|
102
|
+
// Update via the app install directory (local registry)
|
|
103
|
+
// Use `npm install @latest` instead of `npm update` — the latter doesn't
|
|
104
|
+
// reliably extract new tarballs from local registries due to cache issues.
|
|
105
|
+
if (existsSync(join(appDir, "package.json"))) {
|
|
106
|
+
// Sync overrides with dependencies BEFORE any install to avoid EOVERRIDE.
|
|
107
|
+
// npm 11 errors when an override range doesn't satisfy the direct dep range.
|
|
108
|
+
try {
|
|
109
|
+
const appPkgPath = join(appDir, "package.json");
|
|
110
|
+
const appPkgRaw = JSON.parse(readFileSync(appPkgPath, "utf-8"));
|
|
111
|
+
const deps = appPkgRaw.dependencies ?? {};
|
|
112
|
+
const ov: Record<string, string> = appPkgRaw.overrides ?? {};
|
|
113
|
+
let changed = false;
|
|
114
|
+
for (const pkg of Object.keys(ov)) {
|
|
115
|
+
if (deps[pkg] && deps[pkg] !== ov[pkg]) {
|
|
116
|
+
ov[pkg] = deps[pkg];
|
|
117
|
+
changed = true;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (changed) {
|
|
121
|
+
appPkgRaw.overrides = ov;
|
|
122
|
+
writeFileSync(appPkgPath, JSON.stringify(appPkgRaw, null, 2) + "\n");
|
|
123
|
+
}
|
|
124
|
+
} catch {
|
|
125
|
+
/* non-fatal */
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Quick check: is the registry version already installed?
|
|
129
|
+
const latestVersion = getLatestRegistryVersion(
|
|
130
|
+
"@dex-ai/coding-agent",
|
|
131
|
+
appDir,
|
|
132
|
+
);
|
|
133
|
+
const coreUpToDate = latestVersion && latestVersion === oldVersion;
|
|
134
|
+
|
|
135
|
+
if (!coreUpToDate) {
|
|
136
|
+
const result = spawnSync(
|
|
137
|
+
"npm",
|
|
138
|
+
["install", "@dex-ai/coding-agent@latest", "--prefer-online"],
|
|
139
|
+
{
|
|
140
|
+
cwd: appDir,
|
|
141
|
+
stdio: "pipe",
|
|
142
|
+
},
|
|
143
|
+
);
|
|
144
|
+
if (result.status !== 0) {
|
|
145
|
+
const stderr = result.stderr?.toString().trim();
|
|
146
|
+
if (stderr) console.error(` ${stderr}`);
|
|
147
|
+
console.error(" Failed to update dex");
|
|
148
|
+
return { updated: false, oldVersion, newVersion: oldVersion };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Force-update all @dex-ai/* packages to latest.
|
|
153
|
+
// npm install respects semver ranges and won't pull newer patch versions
|
|
154
|
+
// if the range is already satisfied. Explicitly installing each dep
|
|
155
|
+
// at @latest ensures we always get the newest published version.
|
|
156
|
+
// This runs even when coding-agent itself is up to date, because
|
|
157
|
+
// sub-dependencies may have been published independently.
|
|
158
|
+
// We scan node_modules/@dex-ai/ directly to catch ALL transitive deps,
|
|
159
|
+
// not just coding-agent's direct dependencies.
|
|
160
|
+
const dexModulesDir = join(appDir, "node_modules", "@dex-ai");
|
|
161
|
+
if (existsSync(dexModulesDir)) {
|
|
162
|
+
try {
|
|
163
|
+
// Read overrides to skip those packages from force-update
|
|
164
|
+
const appPkg = JSON.parse(
|
|
165
|
+
readFileSync(join(appDir, "package.json"), "utf-8"),
|
|
166
|
+
);
|
|
167
|
+
const overrideSet = new Set(Object.keys(appPkg.overrides ?? {}));
|
|
168
|
+
|
|
169
|
+
const dexPkgs = readdirSync(dexModulesDir)
|
|
170
|
+
.filter(
|
|
171
|
+
(d) => d !== "coding-agent" && !overrideSet.has(`@dex-ai/${d}`),
|
|
172
|
+
)
|
|
173
|
+
.map((d) => `@dex-ai/${d}@latest`);
|
|
174
|
+
if (dexPkgs.length > 0) {
|
|
175
|
+
spawnSync("npm", ["install", ...dexPkgs, "--prefer-online"], {
|
|
176
|
+
cwd: appDir,
|
|
177
|
+
stdio: "pipe",
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
} catch {
|
|
181
|
+
/* best effort — transitive dep update is non-critical */
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Read new version from the app dir
|
|
186
|
+
const pkgPath = join(
|
|
187
|
+
appDir,
|
|
188
|
+
"node_modules",
|
|
189
|
+
"@dex-ai",
|
|
190
|
+
"coding-agent",
|
|
191
|
+
"package.json",
|
|
192
|
+
);
|
|
193
|
+
let newVersion = oldVersion;
|
|
194
|
+
if (existsSync(pkgPath)) {
|
|
195
|
+
try {
|
|
196
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
197
|
+
newVersion = pkg.version ?? oldVersion;
|
|
198
|
+
} catch {
|
|
199
|
+
/* keep oldVersion */
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return { updated: oldVersion !== newVersion, oldVersion, newVersion };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Fallback: global install (legacy path)
|
|
206
|
+
let cmd: string;
|
|
207
|
+
let args: string[];
|
|
208
|
+
|
|
209
|
+
switch (pm) {
|
|
210
|
+
case "bun":
|
|
211
|
+
cmd = "bun";
|
|
212
|
+
args = ["install", "-g", "@dex-ai/coding-agent"];
|
|
213
|
+
break;
|
|
214
|
+
case "pnpm":
|
|
215
|
+
cmd = "pnpm";
|
|
216
|
+
args = ["add", "-g", "@dex-ai/coding-agent@latest"];
|
|
217
|
+
break;
|
|
218
|
+
case "yarn":
|
|
219
|
+
cmd = "yarn";
|
|
220
|
+
args = ["global", "add", "@dex-ai/coding-agent@latest"];
|
|
221
|
+
break;
|
|
222
|
+
case "npm":
|
|
223
|
+
default:
|
|
224
|
+
cmd = "npm";
|
|
225
|
+
args = ["install", "-g", "@dex-ai/coding-agent@latest"];
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const result = spawnSync(cmd, args, { stdio: "pipe" });
|
|
230
|
+
if (result.status !== 0) {
|
|
231
|
+
const stderr = result.stderr?.toString().trim();
|
|
232
|
+
if (stderr) console.error(` ${stderr}`);
|
|
233
|
+
console.error(" Failed to update dex");
|
|
234
|
+
return { updated: false, oldVersion, newVersion: oldVersion };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Re-read version after update
|
|
238
|
+
const newVersion = getCurrentDexVersion();
|
|
239
|
+
return { updated: oldVersion !== newVersion, oldVersion, newVersion };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/* ------------------------------------------------------------------ */
|
|
243
|
+
/* Update extensions */
|
|
244
|
+
/* ------------------------------------------------------------------ */
|
|
245
|
+
|
|
246
|
+
interface RegistryEntry {
|
|
247
|
+
source: "npm" | "file";
|
|
248
|
+
package: string;
|
|
249
|
+
version?: string;
|
|
250
|
+
path?: string;
|
|
251
|
+
type?: "native" | "pi-compat";
|
|
252
|
+
installedAt: string;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
interface Registry {
|
|
256
|
+
version: 1;
|
|
257
|
+
extensions: Record<string, RegistryEntry>;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const USER_EXTENSIONS_DIR = join(homedir(), ".dex", "extensions");
|
|
261
|
+
const USER_PACKAGES_DIR = join(USER_EXTENSIONS_DIR, "packages");
|
|
262
|
+
const USER_REGISTRY_PATH = join(USER_EXTENSIONS_DIR, "registry.json");
|
|
263
|
+
|
|
264
|
+
function loadRegistry(path: string): Registry {
|
|
265
|
+
if (!existsSync(path)) return { version: 1, extensions: {} };
|
|
266
|
+
try {
|
|
267
|
+
const data = JSON.parse(readFileSync(path, "utf-8"));
|
|
268
|
+
if (data.version === 1 && data.extensions) return data as Registry;
|
|
269
|
+
return { version: 1, extensions: {} };
|
|
270
|
+
} catch {
|
|
271
|
+
return { version: 1, extensions: {} };
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function saveRegistry(path: string, registry: Registry): void {
|
|
276
|
+
const dir = join(path, "..");
|
|
277
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
278
|
+
writeFileSync(path, JSON.stringify(registry, null, 2) + "\n");
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function getPackageManagerUpdateCmd(
|
|
282
|
+
pm: PackageManagerName,
|
|
283
|
+
cwd: string,
|
|
284
|
+
pkg: string,
|
|
285
|
+
): { command: string; args: string[] } {
|
|
286
|
+
switch (pm) {
|
|
287
|
+
case "bun":
|
|
288
|
+
return { command: "bun", args: ["update", pkg] };
|
|
289
|
+
case "pnpm":
|
|
290
|
+
return { command: "pnpm", args: ["update", pkg] };
|
|
291
|
+
case "yarn":
|
|
292
|
+
return { command: "yarn", args: ["upgrade", pkg] };
|
|
293
|
+
case "npm":
|
|
294
|
+
default:
|
|
295
|
+
// Use `install @latest --prefer-online` instead of `update` —
|
|
296
|
+
// npm update doesn't reliably extract new tarballs from local registries.
|
|
297
|
+
return {
|
|
298
|
+
command: "npm",
|
|
299
|
+
args: ["install", `${pkg}@latest`, "--prefer-online"],
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function readInstalledVersion(
|
|
305
|
+
extensionDir: string,
|
|
306
|
+
packageName: string,
|
|
307
|
+
): string | null {
|
|
308
|
+
// Handle scoped packages
|
|
309
|
+
const pkgJsonPath = join(
|
|
310
|
+
extensionDir,
|
|
311
|
+
"node_modules",
|
|
312
|
+
packageName,
|
|
313
|
+
"package.json",
|
|
314
|
+
);
|
|
315
|
+
if (!existsSync(pkgJsonPath)) return null;
|
|
316
|
+
try {
|
|
317
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
|
|
318
|
+
return pkg.version ?? null;
|
|
319
|
+
} catch {
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function updateExtensions(pm: PackageManagerName): {
|
|
325
|
+
total: number;
|
|
326
|
+
updated: number;
|
|
327
|
+
} {
|
|
328
|
+
const registry = loadRegistry(USER_REGISTRY_PATH);
|
|
329
|
+
const npmEntries = Object.entries(registry.extensions).filter(
|
|
330
|
+
([_, e]) => e.source === "npm",
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
if (npmEntries.length === 0) {
|
|
334
|
+
console.log("No extensions installed.");
|
|
335
|
+
return { total: 0, updated: 0 };
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
console.log(`\nUpdating ${npmEntries.length} extension(s)...`);
|
|
339
|
+
let updatedCount = 0;
|
|
340
|
+
|
|
341
|
+
for (const [name, entry] of npmEntries) {
|
|
342
|
+
const extensionDir = join(USER_PACKAGES_DIR, name);
|
|
343
|
+
if (!existsSync(extensionDir)) {
|
|
344
|
+
console.error(
|
|
345
|
+
` ✗ ${name}: directory missing (reinstall with: dex extension install npm:${entry.package})`,
|
|
346
|
+
);
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const oldVersion = entry.version ?? "unknown";
|
|
351
|
+
ensureNpmrc(extensionDir);
|
|
352
|
+
|
|
353
|
+
// Quick check: is the registry version already installed?
|
|
354
|
+
const latestVersion = getLatestRegistryVersion(entry.package, extensionDir);
|
|
355
|
+
if (latestVersion && latestVersion === oldVersion) {
|
|
356
|
+
console.log(` · ${name}: up to date (${oldVersion})`);
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const cmd = getPackageManagerUpdateCmd(pm, extensionDir, entry.package);
|
|
361
|
+
const result = spawnSync(cmd.command, cmd.args, {
|
|
362
|
+
cwd: extensionDir,
|
|
363
|
+
stdio: "pipe",
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
if (result.status !== 0) {
|
|
367
|
+
console.error(` ✗ ${name}: update failed`);
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const newVersion =
|
|
372
|
+
readInstalledVersion(extensionDir, entry.package) ?? oldVersion;
|
|
373
|
+
|
|
374
|
+
if (oldVersion !== newVersion) {
|
|
375
|
+
console.log(` ✓ ${name}: ${oldVersion} → ${newVersion}`);
|
|
376
|
+
registry.extensions[name] = {
|
|
377
|
+
...entry,
|
|
378
|
+
version: newVersion,
|
|
379
|
+
installedAt: new Date().toISOString(),
|
|
380
|
+
};
|
|
381
|
+
updatedCount++;
|
|
382
|
+
} else {
|
|
383
|
+
console.log(` · ${name}: up to date (${newVersion})`);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
saveRegistry(USER_REGISTRY_PATH, registry);
|
|
388
|
+
return { total: npmEntries.length, updated: updatedCount };
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/* ------------------------------------------------------------------ */
|
|
392
|
+
/* Main */
|
|
393
|
+
/* ------------------------------------------------------------------ */
|
|
394
|
+
|
|
395
|
+
export function runUpdateCommand(): void {
|
|
396
|
+
const pm = getConfiguredPackageManager();
|
|
397
|
+
|
|
398
|
+
// 1. Update dex itself
|
|
399
|
+
const dex = updateDex(pm);
|
|
400
|
+
if (dex.updated) {
|
|
401
|
+
console.log(` ✓ dex: ${dex.oldVersion} → ${dex.newVersion}`);
|
|
402
|
+
} else {
|
|
403
|
+
console.log(` · dex: up to date (${dex.newVersion})`);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// 2. Update all installed extensions
|
|
407
|
+
const ext = updateExtensions(pm);
|
|
408
|
+
|
|
409
|
+
// Summary
|
|
410
|
+
console.log("");
|
|
411
|
+
const totalUpdated = (dex.updated ? 1 : 0) + ext.updated;
|
|
412
|
+
if (totalUpdated > 0) {
|
|
413
|
+
console.log(
|
|
414
|
+
`Updated ${totalUpdated} package(s). Restart dex to use new versions.`,
|
|
415
|
+
);
|
|
416
|
+
} else {
|
|
417
|
+
console.log("Everything is up to date.");
|
|
418
|
+
}
|
|
419
|
+
}
|
package/src/env.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<!-- Extension-contributed content -->
|
|
3
|
+
<WidgetRenderer v-if="content" :node="content" />
|
|
4
|
+
<!-- Default spinner when generating -->
|
|
5
|
+
<line v-else-if="isGenerating">
|
|
6
|
+
<text color="accent" :style="{ opacity: dotOpacity }">●</text>
|
|
7
|
+
<text dim :style="{ paddingLeft: 1 }">crushing tokens...</text>
|
|
8
|
+
</line>
|
|
9
|
+
<!-- Nothing otherwise -->
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<script setup lang="ts">
|
|
13
|
+
import { computed } from "@vue/runtime-core";
|
|
14
|
+
import { WidgetRenderer } from "@dex-ai/vue-tui";
|
|
15
|
+
import type { WidgetNode } from "@dex-ai/vue-tui";
|
|
16
|
+
|
|
17
|
+
const props = defineProps<{
|
|
18
|
+
content: WidgetNode | null;
|
|
19
|
+
isGenerating: boolean;
|
|
20
|
+
spinnerFrame: number;
|
|
21
|
+
}>();
|
|
22
|
+
|
|
23
|
+
const dotOpacity = computed(() => props.spinnerFrame % 2 === 0 ? 1 : 0.4);
|
|
24
|
+
</script>
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ActivityPanel — dynamic content zone above the input area.
|
|
3
|
+
*
|
|
4
|
+
* Renders either:
|
|
5
|
+
* - Extension-contributed content (WidgetNode from a DynamicPanel)
|
|
6
|
+
* - Default spinner ("crushing tokens") when generating with no panel
|
|
7
|
+
* - Nothing when idle and no panel is visible
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import * as _Vue from "@vue/runtime-core";
|
|
11
|
+
const {
|
|
12
|
+
h: _h,
|
|
13
|
+
createElementVNode: _createElementVNode,
|
|
14
|
+
createElementBlock: _createElementBlock,
|
|
15
|
+
createCommentVNode: _createCommentVNode,
|
|
16
|
+
createVNode: _createVNode,
|
|
17
|
+
normalizeStyle: _normalizeStyle,
|
|
18
|
+
openBlock: _openBlock,
|
|
19
|
+
} = _Vue;
|
|
20
|
+
|
|
21
|
+
import { defineComponent as _defineComponent } from "@vue/runtime-core";
|
|
22
|
+
import { computed } from "@vue/runtime-core";
|
|
23
|
+
import { WidgetRenderer } from "@dex-ai/vue-tui";
|
|
24
|
+
|
|
25
|
+
const __sfc_main = /*@__PURE__*/ _defineComponent({
|
|
26
|
+
__name: "ActivityPanel",
|
|
27
|
+
props: {
|
|
28
|
+
content: { type: [Object, null], required: true },
|
|
29
|
+
isGenerating: { type: Boolean, required: true },
|
|
30
|
+
spinnerFrame: { type: Number, required: true },
|
|
31
|
+
},
|
|
32
|
+
setup(__props: any, { expose: __expose }) {
|
|
33
|
+
__expose();
|
|
34
|
+
|
|
35
|
+
const props = __props;
|
|
36
|
+
|
|
37
|
+
const dotOpacity = computed(() => (props.spinnerFrame % 2 === 0 ? 1 : 0.4));
|
|
38
|
+
|
|
39
|
+
const __returned__ = { props, dotOpacity, WidgetRenderer };
|
|
40
|
+
Object.defineProperty(__returned__, "__isScriptSetup", {
|
|
41
|
+
enumerable: false,
|
|
42
|
+
value: true,
|
|
43
|
+
});
|
|
44
|
+
return __returned__;
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const render = (
|
|
49
|
+
_ctx: any,
|
|
50
|
+
_cache: any,
|
|
51
|
+
$props: any,
|
|
52
|
+
$setup: any,
|
|
53
|
+
$data: any,
|
|
54
|
+
$options: any,
|
|
55
|
+
) => {
|
|
56
|
+
// If we have extension-provided content, render it
|
|
57
|
+
if ($props.content) {
|
|
58
|
+
return _createVNode(
|
|
59
|
+
$setup["WidgetRenderer"],
|
|
60
|
+
{ node: $props.content },
|
|
61
|
+
null,
|
|
62
|
+
8,
|
|
63
|
+
["node"],
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Default spinner when generating
|
|
68
|
+
if ($props.isGenerating) {
|
|
69
|
+
return (
|
|
70
|
+
_openBlock(),
|
|
71
|
+
_createElementBlock("line", null, [
|
|
72
|
+
_createElementVNode(
|
|
73
|
+
"text",
|
|
74
|
+
{
|
|
75
|
+
color: "accent",
|
|
76
|
+
style: _normalizeStyle({ opacity: $setup.dotOpacity }),
|
|
77
|
+
},
|
|
78
|
+
"●",
|
|
79
|
+
4 /* STYLE */,
|
|
80
|
+
),
|
|
81
|
+
_createElementVNode(
|
|
82
|
+
"text",
|
|
83
|
+
{ dim: "", style: { paddingLeft: 1 } },
|
|
84
|
+
"crushing tokens...",
|
|
85
|
+
),
|
|
86
|
+
])
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Nothing to show
|
|
91
|
+
return _createCommentVNode("v-if", true);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
__sfc_main.render = render;
|
|
95
|
+
|
|
96
|
+
export default __sfc_main;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<root>
|
|
3
|
+
<Header :title="title" :subtitle="subtitle" />
|
|
4
|
+
<spacer />
|
|
5
|
+
<Messages :messages="messages" :spinnerFrame="spinnerFrame" :expandToolOutput="toolDetailOpen" />
|
|
6
|
+
<ThinkingBlock v-if="streamingReasoning && !hideThinking" :content="streamingReasoning" status="running" :spinnerFrame="spinnerFrame" :expanded="false" />
|
|
7
|
+
<MarkdownContent v-if="streamingText" :content="streamingText" :streaming="true" dot />
|
|
8
|
+
<ActivityPanel :content="activityContent" :isGenerating="isGenerating" :spinnerFrame="spinnerFrame" />
|
|
9
|
+
<ApprovalPrompt v-if="approvalRequest" :request="approvalRequest" />
|
|
10
|
+
<Modal v-if="modalProps" :modal="modalProps" />
|
|
11
|
+
<SteeringPreview v-if="steeringQueue" :text="steeringQueue" />
|
|
12
|
+
|
|
13
|
+
<!-- Zone 1: Interactive Panel (above input) -->
|
|
14
|
+
<InteractivePanel
|
|
15
|
+
v-if="activeInteractivePanel && !approvalRequest && !modalProps"
|
|
16
|
+
:panel="activeInteractivePanel"
|
|
17
|
+
:width="terminalCols"
|
|
18
|
+
:state="agentState"
|
|
19
|
+
/>
|
|
20
|
+
|
|
21
|
+
<SettingsPanel v-if="settingsPanel && settingsPanel.open && !approvalRequest && !modalProps"
|
|
22
|
+
:tabs="settingsPanel.tabs"
|
|
23
|
+
:activeTabIndex="settingsPanel.activeTabIndex"
|
|
24
|
+
:focusedFieldIndex="settingsPanel.focusedFieldIndex"
|
|
25
|
+
:focusZone="settingsPanel.focusZone"
|
|
26
|
+
:editing="settingsPanel.editing"
|
|
27
|
+
:values="settingsPanel.values"
|
|
28
|
+
:error="settingsPanel.error"
|
|
29
|
+
:searchFilter="settingsPanel.searchFilter"
|
|
30
|
+
:scrollOffset="settingsPanel.scrollOffset"
|
|
31
|
+
:visibleRows="settingsPanel.visibleRows"
|
|
32
|
+
/>
|
|
33
|
+
<AskPanel v-else-if="askPanel && askPanel.open && !approvalRequest && !modalProps"
|
|
34
|
+
:tabs="askPanel.tabs"
|
|
35
|
+
:singleTab="askPanel.singleTab"
|
|
36
|
+
:activeTabIndex="askPanel.activeTabIndex"
|
|
37
|
+
:onSubmitTab="askPanel.onSubmitTab"
|
|
38
|
+
:tabStates="askPanel.tabStates"
|
|
39
|
+
:focusedOptionIndex="askPanel.focusedOptionIndex"
|
|
40
|
+
:checkedValues="askPanel.checkedValues"
|
|
41
|
+
:freeTextFocused="askPanel.freeTextFocused"
|
|
42
|
+
:freeTextBuffer="askPanel.freeTextBuffer"
|
|
43
|
+
:actionFocused="askPanel.actionFocused"
|
|
44
|
+
:actionBarIndex="askPanel.actionBarIndex"
|
|
45
|
+
:tabResults="askPanel.tabResults"
|
|
46
|
+
:scrollOffset="askPanel.scrollOffset"
|
|
47
|
+
:visibleOptions="askPanel.visibleOptions"
|
|
48
|
+
/>
|
|
49
|
+
<InputArea v-else-if="!approvalRequest && !modalProps" :lines="lines" :cursorRow="cursorRow" :cursorCol="cursorCol" :hint="isGenerating ? 'add steering details' : undefined" :pasteTokens="pasteTokens" :pasteBuffer="pasteBuffer" />
|
|
50
|
+
<CommandPalette v-if="commandPaletteOpen" :items="filteredCommands" :selectedIndex="commandPaletteIndex" />
|
|
51
|
+
<StatusBar v-if="!commandPaletteOpen && !modalProps" :cwd="cwd" :model="model" :tokens="tokens" :contextWindow="contextWindow" :permissionMode="permissionMode" :thinkingLevel="thinkingLevel" :extensionStatuses="extensionStatuses" />
|
|
52
|
+
</root>
|
|
53
|
+
</template>
|
|
54
|
+
|
|
55
|
+
<script setup lang="ts">
|
|
56
|
+
import Header from "./Header.vue";
|
|
57
|
+
import Messages from "./Messages.vue";
|
|
58
|
+
import ActivityPanel from "./ActivityPanel.vue";
|
|
59
|
+
import InputArea from "./InputArea.vue";
|
|
60
|
+
import SteeringPreview from "./SteeringPreview.vue";
|
|
61
|
+
import StatusBar from "./StatusBar.vue";
|
|
62
|
+
import ApprovalPrompt from "./ApprovalPrompt.vue";
|
|
63
|
+
import CommandPalette from "./CommandPalette.vue";
|
|
64
|
+
import Modal from "./Modal.vue";
|
|
65
|
+
import MarkdownContent from "./MarkdownContent";
|
|
66
|
+
import ThinkingBlock from "./ThinkingBlock.vue";
|
|
67
|
+
import InteractivePanel from "./InteractivePanel.vue";
|
|
68
|
+
import SettingsPanel from "./SettingsPanel.vue";
|
|
69
|
+
import AskPanel from "./AskPanel.vue";
|
|
70
|
+
import type { WidgetNode } from "@dex-ai/vue-tui";
|
|
71
|
+
import type { InteractivePanel as InteractivePanelType } from "../../panels";
|
|
72
|
+
import type { SettingsPanelState } from "./settings-panel-controller";
|
|
73
|
+
import type { AskPanelState } from "./ask-panel-controller";
|
|
74
|
+
|
|
75
|
+
defineProps<{
|
|
76
|
+
title: string;
|
|
77
|
+
subtitle: string;
|
|
78
|
+
messages: any[];
|
|
79
|
+
streamingText: string;
|
|
80
|
+
streamingReasoning: string;
|
|
81
|
+
hideThinking: boolean;
|
|
82
|
+
isGenerating: boolean;
|
|
83
|
+
spinnerFrame: number;
|
|
84
|
+
lines: string[];
|
|
85
|
+
cursorRow: number;
|
|
86
|
+
cursorCol: number;
|
|
87
|
+
pasteTokens: Map<number, { id: number; content: string; lineCount: number }>;
|
|
88
|
+
pasteBuffer: { id: number; index: number; entry: { type: string; content?: string; lineCount?: number; data?: Uint8Array; mediaType?: string } }[];
|
|
89
|
+
cwd: string;
|
|
90
|
+
model: string;
|
|
91
|
+
tokens: number;
|
|
92
|
+
contextWindow: number;
|
|
93
|
+
permissionMode: 'read' | 'auto' | 'yolo';
|
|
94
|
+
thinkingLevel: string | undefined;
|
|
95
|
+
extensionStatuses: Record<string, string>;
|
|
96
|
+
approvalRequest: { toolName: string; reason: string; input: unknown } | null;
|
|
97
|
+
commandPaletteOpen: boolean;
|
|
98
|
+
filteredCommands: Array<{ name: string; description: string }>;
|
|
99
|
+
commandPaletteIndex: number;
|
|
100
|
+
modalProps: {
|
|
101
|
+
type: 'list' | 'form' | 'confirm';
|
|
102
|
+
title: string;
|
|
103
|
+
filteredItems?: Array<{ label: string; hint?: string }>;
|
|
104
|
+
selectedIndex?: number;
|
|
105
|
+
filter?: string;
|
|
106
|
+
allowFilter?: boolean;
|
|
107
|
+
maxVisible?: number;
|
|
108
|
+
fields?: Array<{ key: string; label: string; type: string; value?: string; placeholder?: string; hint?: string; required?: boolean; readonly?: boolean; options?: string[] }>;
|
|
109
|
+
values?: Record<string, string>;
|
|
110
|
+
focusedIndex?: number;
|
|
111
|
+
editing?: boolean;
|
|
112
|
+
editBuffer?: string;
|
|
113
|
+
error?: string;
|
|
114
|
+
body?: string;
|
|
115
|
+
options?: Array<{ key: string; label: string; value: string }>;
|
|
116
|
+
} | null;
|
|
117
|
+
toolDetailOpen: boolean;
|
|
118
|
+
activityContent: WidgetNode | null;
|
|
119
|
+
steeringQueue: string | null;
|
|
120
|
+
terminalRows: number;
|
|
121
|
+
terminalCols: number;
|
|
122
|
+
activeInteractivePanel: InteractivePanelType | null;
|
|
123
|
+
agentState: ReadonlyMap<string, unknown>;
|
|
124
|
+
settingsPanel: SettingsPanelState | null;
|
|
125
|
+
askPanel: AskPanelState | null;
|
|
126
|
+
}>();
|
|
127
|
+
</script>
|