dsh-superagent-console 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -64
- package/lib/client.js +480 -216
- package/lib/index.d.ts +13 -1
- package/lib/index.js +201 -12
- package/package.json +6 -3
package/lib/index.d.ts
CHANGED
|
@@ -28,6 +28,8 @@ interface Prereq {
|
|
|
28
28
|
name: string;
|
|
29
29
|
ok: boolean;
|
|
30
30
|
detail: string;
|
|
31
|
+
/** Suggested remediation command for a failed check; absent when none applies. */
|
|
32
|
+
hint?: string;
|
|
31
33
|
}
|
|
32
34
|
interface EngineHealth {
|
|
33
35
|
exit: number;
|
|
@@ -86,6 +88,16 @@ interface EngineActionResult {
|
|
|
86
88
|
}
|
|
87
89
|
/** Run the lifecycle script's start/restart action and wait for its verdict. */
|
|
88
90
|
declare function engineAction(config: ResolvedConsoleConfig, action: 'start' | 'restart', env?: InstallEnv): Promise<EngineActionResult>;
|
|
91
|
+
/**
|
|
92
|
+
* Collect `link:` dependencies whose target no longer exists. The official
|
|
93
|
+
* CLI sails through the package operation itself (pnpm trusts the lockfile's
|
|
94
|
+
* recorded link resolution without checking the target) but its post-install
|
|
95
|
+
* bundle reconciliation probes `node_modules/<name>/package.json` and exits 1
|
|
96
|
+
* with an opaque "cannot resolve profile bundle" error — the shape seen after
|
|
97
|
+
* a hosting checkout is moved and only the manifest specifiers are rewritten.
|
|
98
|
+
* Refusing up front keeps the failure readable and names the repair.
|
|
99
|
+
*/
|
|
100
|
+
declare function danglingLinks(config: ResolvedConsoleConfig): string[];
|
|
89
101
|
/** Minimal structural view of the settings service the sync reads through. */
|
|
90
102
|
interface SettingsFace {
|
|
91
103
|
describe(): Array<{
|
|
@@ -163,4 +175,4 @@ declare function proxyHandler(options: ProxyOptions): (req: IncomingMessage, res
|
|
|
163
175
|
declare const name: string;
|
|
164
176
|
declare const inject: string[];
|
|
165
177
|
declare function apply(ctx: Context, config?: Partial<ResolvedConsoleConfig>): void;
|
|
166
|
-
export { Config, type ConsoleState, type DetectResult, type Prereq, type ProviderDiff, type ResolvedConsoleConfig, type SettingsFace, type SetupState, type StageId, type SyncMode, type SyncReport, apply, applyProviderSync, detect, diffProviders, engineAction, inject, name, proxyHandler, readEngineProviders, readHostProviders, readSetupState, runInstall };
|
|
178
|
+
export { Config, type ConsoleState, type DetectResult, type Prereq, type ProviderDiff, type ResolvedConsoleConfig, type SettingsFace, type SetupState, type StageId, type SyncMode, type SyncReport, apply, applyProviderSync, danglingLinks, detect, diffProviders, engineAction, inject, name, proxyHandler, readEngineProviders, readHostProviders, readSetupState, runInstall };
|
package/lib/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import os from "node:os";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import z from "@deepseek-ai/schemastery";
|
|
4
4
|
import fs, { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
-
import { execFile } from "node:child_process";
|
|
5
|
+
import { execFile, spawn } from "node:child_process";
|
|
6
6
|
import { parse, stringify } from "yaml";
|
|
7
7
|
import { Readable } from "node:stream";
|
|
8
8
|
// src/config.ts
|
|
@@ -37,13 +37,31 @@ const Config = z.object({
|
|
|
37
37
|
* Every spawn goes through `execFile` with an argv array (never a shell
|
|
38
38
|
* string), an explicit timeout, and an augmented PATH: the desktop app
|
|
39
39
|
* process may run with a minimal GUI PATH, so the node installation bin
|
|
40
|
-
* directory
|
|
40
|
+
* directory, the configured `extraPath` entries, and the app's bundled
|
|
41
|
+
* dsh CLI directory are prepended (in that order; user entries win).
|
|
41
42
|
*/
|
|
42
43
|
const MAX_BUFFER = 16777216;
|
|
43
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* Directory of the dsh CLI shipped inside the desktop app bundle: the
|
|
46
|
+
* Electron binary lives at `<App>.app/Contents/MacOS/<bin>`, so the bundled
|
|
47
|
+
* CLI resolves to `../Resources/runtime/cli/bin`. Null outside an app
|
|
48
|
+
* bundle (plain node runs, tests) or when the bundle carries no CLI.
|
|
49
|
+
* Uncached on purpose: spawns are rare and the probe is one existsSync.
|
|
50
|
+
*/
|
|
51
|
+
function bundledCliDir(execPath = process.execPath) {
|
|
52
|
+
const candidate = path.join(path.dirname(execPath), "..", "Resources", "runtime", "cli", "bin");
|
|
53
|
+
return existsSync(path.join(candidate, "dsh")) ? candidate : null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* PATH prepended for every spawned tool: the running node's bin dir, the
|
|
57
|
+
* configured extras, the app's bundled CLI dir (fallback only, so an
|
|
58
|
+
* explicit user install keeps winning), then the inherited PATH.
|
|
59
|
+
*/
|
|
44
60
|
function spawnPath(extraPath) {
|
|
45
61
|
const parts = [path.dirname(process.execPath)];
|
|
46
62
|
if (extraPath) parts.push(...extraPath.split(":").filter(Boolean));
|
|
63
|
+
const bundled = bundledCliDir();
|
|
64
|
+
if (bundled) parts.push(bundled);
|
|
47
65
|
const inherited = process.env.PATH ?? "";
|
|
48
66
|
return [...parts, inherited].filter(Boolean).join(":");
|
|
49
67
|
}
|
|
@@ -74,6 +92,73 @@ function run(argv, options = {}) {
|
|
|
74
92
|
});
|
|
75
93
|
});
|
|
76
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Spawn like {@link run} but deliver output as it arrives: the plugins API
|
|
97
|
+
* mirrors each chunk into the operation state so the dialog can stream the
|
|
98
|
+
* install log the way the native manager does. Output stays bounded by the
|
|
99
|
+
* consumer (it keeps a tail); this runner only concatenates what it saw.
|
|
100
|
+
*/
|
|
101
|
+
function runStreaming(argv, options = {}, onChunk) {
|
|
102
|
+
const [file, ...args] = argv;
|
|
103
|
+
if (!file) throw new Error("runStreaming() needs a command");
|
|
104
|
+
const { cwd, timeoutMs = 3e4, extraPath } = options;
|
|
105
|
+
const child = spawn(file, args, {
|
|
106
|
+
cwd,
|
|
107
|
+
stdio: [
|
|
108
|
+
"ignore",
|
|
109
|
+
"pipe",
|
|
110
|
+
"pipe"
|
|
111
|
+
],
|
|
112
|
+
env: {
|
|
113
|
+
...process.env,
|
|
114
|
+
PATH: spawnPath(extraPath)
|
|
115
|
+
},
|
|
116
|
+
windowsHide: true
|
|
117
|
+
});
|
|
118
|
+
let stdout = "";
|
|
119
|
+
let stderr = "";
|
|
120
|
+
let timedOut = false;
|
|
121
|
+
const timer = setTimeout(() => {
|
|
122
|
+
timedOut = true;
|
|
123
|
+
child.kill("SIGKILL");
|
|
124
|
+
}, timeoutMs);
|
|
125
|
+
child.stdout?.on("data", (chunk) => {
|
|
126
|
+
stdout += chunk.toString();
|
|
127
|
+
onChunk?.(chunk.toString());
|
|
128
|
+
});
|
|
129
|
+
child.stderr?.on("data", (chunk) => {
|
|
130
|
+
stderr += chunk.toString();
|
|
131
|
+
onChunk?.(chunk.toString());
|
|
132
|
+
});
|
|
133
|
+
return {
|
|
134
|
+
promise: new Promise((resolve) => {
|
|
135
|
+
const startedAt = Date.now();
|
|
136
|
+
child.on("error", (error) => {
|
|
137
|
+
clearTimeout(timer);
|
|
138
|
+
resolve({
|
|
139
|
+
code: -1,
|
|
140
|
+
stdout,
|
|
141
|
+
stderr: stderr + String(error),
|
|
142
|
+
timedOut
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
child.on("close", (code) => {
|
|
146
|
+
clearTimeout(timer);
|
|
147
|
+
const hitTimeout = timedOut && Date.now() - startedAt >= timeoutMs - 1;
|
|
148
|
+
resolve({
|
|
149
|
+
code: code ?? (timedOut || hitTimeout ? -1 : 0),
|
|
150
|
+
stdout,
|
|
151
|
+
stderr,
|
|
152
|
+
timedOut: timedOut || hitTimeout
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
}),
|
|
156
|
+
kill: () => {
|
|
157
|
+
clearTimeout(timer);
|
|
158
|
+
child.kill("SIGKILL");
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
}
|
|
77
162
|
/** Truncate captured output for audit logging. */
|
|
78
163
|
function tail$1(text, max = 2e3) {
|
|
79
164
|
const value = text.trim();
|
|
@@ -220,7 +305,13 @@ async function engineHealth(engineDir, profileName, target, env = {}) {
|
|
|
220
305
|
};
|
|
221
306
|
}
|
|
222
307
|
}
|
|
223
|
-
/**
|
|
308
|
+
/** Locale-neutral remediation commands surfaced next to a failed check (darwin only). */
|
|
309
|
+
function missingHint(name) {
|
|
310
|
+
if (process.platform !== "darwin") return void 0;
|
|
311
|
+
if (name === "tmux") return "brew install tmux";
|
|
312
|
+
if (name === "pnpm") return "npm install -g pnpm";
|
|
313
|
+
}
|
|
314
|
+
/** L3: toolchain probe; never throws, missing tools surface as ok:false entries (with a hint where one applies). */
|
|
224
315
|
async function prereqCheck(env = {}) {
|
|
225
316
|
const checks = [nodeOk()];
|
|
226
317
|
for (const tool of TOOL_CHECKS) {
|
|
@@ -233,7 +324,8 @@ async function prereqCheck(env = {}) {
|
|
|
233
324
|
checks.push({
|
|
234
325
|
name: tool.name,
|
|
235
326
|
ok: false,
|
|
236
|
-
detail: result.code === -1 ? "not found on PATH" : tail(output, 200)
|
|
327
|
+
detail: result.code === -1 ? "not found on PATH" : tail(output, 200),
|
|
328
|
+
hint: missingHint(tool.name)
|
|
237
329
|
});
|
|
238
330
|
continue;
|
|
239
331
|
}
|
|
@@ -242,7 +334,8 @@ async function prereqCheck(env = {}) {
|
|
|
242
334
|
checks.push({
|
|
243
335
|
name: tool.name,
|
|
244
336
|
ok,
|
|
245
|
-
detail: ok ? version : `${version} (needs >= ${tool.min})
|
|
337
|
+
detail: ok ? version : `${version} (needs >= ${tool.min})`,
|
|
338
|
+
hint: ok ? void 0 : missingHint(tool.name)
|
|
246
339
|
});
|
|
247
340
|
}
|
|
248
341
|
return checks;
|
|
@@ -302,6 +395,11 @@ async function detect(config, env = {}) {
|
|
|
302
395
|
* own host profile);
|
|
303
396
|
* - install specs must be non-empty, option-free single tokens; remove
|
|
304
397
|
* names must already be profile dependencies;
|
|
398
|
+
* - before any install/remove, `link:` dependencies are realpath-checked;
|
|
399
|
+
* a dangling target (checkout moved, manifest rewritten without
|
|
400
|
+
* reinstalling) makes the official CLI's post-install bundle
|
|
401
|
+
* reconciliation exit 1 with an opaque resolve error, so the operation
|
|
402
|
+
* refuses up front and names the repair;
|
|
305
403
|
* - the engine core package (`dsh-superagent`) can be neither removed
|
|
306
404
|
* nor toggled — without it the console has no subject;
|
|
307
405
|
* - operations are single-flight in-process: install, remove, and
|
|
@@ -407,6 +505,71 @@ function validateRegistry(raw) {
|
|
|
407
505
|
if (!/^https?:\/\/\S+$/.test(registry) || registry.length > 256) throw new PluginRequestError("invalid registry: an http(s) URL is required");
|
|
408
506
|
return registry;
|
|
409
507
|
}
|
|
508
|
+
/**
|
|
509
|
+
* Collect `link:` dependencies whose target no longer exists. The official
|
|
510
|
+
* CLI sails through the package operation itself (pnpm trusts the lockfile's
|
|
511
|
+
* recorded link resolution without checking the target) but its post-install
|
|
512
|
+
* bundle reconciliation probes `node_modules/<name>/package.json` and exits 1
|
|
513
|
+
* with an opaque "cannot resolve profile bundle" error — the shape seen after
|
|
514
|
+
* a hosting checkout is moved and only the manifest specifiers are rewritten.
|
|
515
|
+
* Refusing up front keeps the failure readable and names the repair.
|
|
516
|
+
*/
|
|
517
|
+
function danglingLinks(config) {
|
|
518
|
+
const manifest = readManifest(config.profileName);
|
|
519
|
+
if (!manifest) return [];
|
|
520
|
+
const dir = profileDir(config.profileName);
|
|
521
|
+
const broken = [];
|
|
522
|
+
for (const [name, spec] of Object.entries(manifest.dependencies ?? {})) {
|
|
523
|
+
if (!spec.startsWith("link:")) continue;
|
|
524
|
+
const target = spec.slice(5);
|
|
525
|
+
const resolved = path.isAbsolute(target) ? target : path.resolve(dir, target);
|
|
526
|
+
if (!existsSync(resolved)) broken.push(`${name} (${spec})`);
|
|
527
|
+
}
|
|
528
|
+
return broken;
|
|
529
|
+
}
|
|
530
|
+
/** Package-name reading of an install spec, when derivable offline. */
|
|
531
|
+
function specPackageName(config, spec) {
|
|
532
|
+
const trimmed = spec.trim();
|
|
533
|
+
if (trimmed.startsWith("link:") || trimmed.startsWith("file:")) {
|
|
534
|
+
const target = trimmed.slice(trimmed.indexOf(":") + 1);
|
|
535
|
+
const resolved = path.isAbsolute(target) ? target : path.resolve(profileDir(config.profileName), target);
|
|
536
|
+
try {
|
|
537
|
+
const manifest = JSON.parse(readFileSync(path.join(resolved, "package.json"), "utf8"));
|
|
538
|
+
return typeof manifest.name === "string" ? manifest.name : null;
|
|
539
|
+
} catch {
|
|
540
|
+
return null;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
if (/^https?:\/\//.test(trimmed) || trimmed.startsWith("git")) return null;
|
|
544
|
+
if (trimmed.startsWith("@")) {
|
|
545
|
+
const at = trimmed.indexOf("@", 1);
|
|
546
|
+
return at === -1 ? trimmed : trimmed.slice(0, at);
|
|
547
|
+
}
|
|
548
|
+
const at = trimmed.indexOf("@");
|
|
549
|
+
return at === -1 ? trimmed : trimmed.slice(0, at);
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Refuse installs before they start when the spec is malformed, already a
|
|
553
|
+
* profile dependency, or the profile carries dangling link: dependencies
|
|
554
|
+
* (the official CLI's post-install reconciliation exits 1 on those). Mirrors
|
|
555
|
+
* the native manager's checking phase and its refusal screen.
|
|
556
|
+
*/
|
|
557
|
+
function checkProblems(config, raw) {
|
|
558
|
+
const spec = typeof raw === "string" ? raw.trim() : "";
|
|
559
|
+
if (!spec || spec.length > 512 || /[\r\n]/.test(spec) || spec.startsWith("-")) return [{
|
|
560
|
+
kind: "invalid",
|
|
561
|
+
detail: "a non-empty package name, git URL, tarball, or absolute path is required"
|
|
562
|
+
}];
|
|
563
|
+
const problems = [];
|
|
564
|
+
const broken = danglingLinks(config);
|
|
565
|
+
if (broken.length > 0) problems.push({
|
|
566
|
+
kind: "links",
|
|
567
|
+
detail: broken.join(", ")
|
|
568
|
+
});
|
|
569
|
+
const name = specPackageName(config, spec);
|
|
570
|
+
if (name && Object.hasOwn(readManifest(config.profileName)?.dependencies ?? {}, name)) problems.push({ kind: "installed" });
|
|
571
|
+
return problems;
|
|
572
|
+
}
|
|
410
573
|
/** Validate a remove/enable target: an existing profile dependency, not the engine core. */
|
|
411
574
|
function validateName(config, raw, allowProtected = false) {
|
|
412
575
|
const name = typeof raw === "string" ? raw.trim() : "";
|
|
@@ -434,12 +597,29 @@ async function runCliOperation(config, env, kind, target, registryUrl) {
|
|
|
434
597
|
target: redact$1(target),
|
|
435
598
|
...registryUrl ? { registry: registryUrl } : {}
|
|
436
599
|
});
|
|
437
|
-
const
|
|
600
|
+
const before = Object.keys(readManifest(config.profileName)?.dependencies ?? {});
|
|
601
|
+
let output = "";
|
|
602
|
+
const result = await runStreaming(argv, {
|
|
438
603
|
timeoutMs: kind === "install" ? 9e5 : 6e5,
|
|
439
604
|
extraPath: env.extraPath
|
|
440
|
-
})
|
|
441
|
-
|
|
605
|
+
}, (chunk) => {
|
|
606
|
+
output = (output + chunk).slice(-4e3);
|
|
607
|
+
last = {
|
|
608
|
+
...last,
|
|
609
|
+
output: redact$1(output.trim())
|
|
610
|
+
};
|
|
611
|
+
}).promise;
|
|
442
612
|
const failed = result.code !== 0;
|
|
613
|
+
let installed;
|
|
614
|
+
if (!failed && kind === "install") {
|
|
615
|
+
const after = readManifest(config.profileName)?.dependencies ?? {};
|
|
616
|
+
installed = Object.entries(after).filter(([name]) => !before.includes(name)).map(([name, spec]) => ({
|
|
617
|
+
name,
|
|
618
|
+
spec,
|
|
619
|
+
version: readInstalledManifest(config.profileName, name)?.version ?? null,
|
|
620
|
+
bundle: readInstalledManifest(config.profileName, name)?.dsh?.bundle !== void 0
|
|
621
|
+
}));
|
|
622
|
+
}
|
|
443
623
|
const state = {
|
|
444
624
|
running: false,
|
|
445
625
|
kind,
|
|
@@ -448,8 +628,10 @@ async function runCliOperation(config, env, kind, target, registryUrl) {
|
|
|
448
628
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
449
629
|
exitCode: result.code,
|
|
450
630
|
timedOut: result.timedOut,
|
|
451
|
-
output,
|
|
452
|
-
error: failed ? result.timedOut ? "command timed out" : `exit ${result.code}` : null
|
|
631
|
+
output: tail$1(redact$1((result.stdout + "\n" + result.stderr).trim()), 4e3),
|
|
632
|
+
error: failed ? result.timedOut ? "command timed out" : `exit ${result.code}` : null,
|
|
633
|
+
installed,
|
|
634
|
+
profileDir: profileDir(config.profileName)
|
|
453
635
|
};
|
|
454
636
|
last = state;
|
|
455
637
|
audit$1(config, {
|
|
@@ -470,6 +652,8 @@ async function runCliOperation(config, env, kind, target, registryUrl) {
|
|
|
470
652
|
function runPluginOperation(config, env, kind, target, registry) {
|
|
471
653
|
if (config.profileName === "desktop") return Promise.reject(new PluginRequestError("refusing to manage plugins of the desktop profile"));
|
|
472
654
|
if (inFlight$1) return Promise.reject(new PluginRequestError("another plugin operation is already running"));
|
|
655
|
+
const broken = danglingLinks(config);
|
|
656
|
+
if (broken.length > 0) return Promise.reject(new PluginRequestError(`dangling link dependencies in the ${config.profileName} profile — the official installer's bundle reconciliation would fail; repair them first:\n ` + broken.join("\n ") + `\nFix: correct the targets in package.json and the pnpm-lock version fields, then run 'dsh plugin --profile ${config.profileName} install'.`));
|
|
473
657
|
const prepared = kind === "install" ? validateSpec(target) : validateName(config, target);
|
|
474
658
|
const registryUrl = kind === "install" ? validateRegistry(registry) : null;
|
|
475
659
|
last = {
|
|
@@ -1504,6 +1688,11 @@ function consoleApiHandler(options) {
|
|
|
1504
1688
|
case "GET /superagent-console/api/plugins/operation":
|
|
1505
1689
|
sendJson(res, 200, pluginOperation());
|
|
1506
1690
|
return;
|
|
1691
|
+
case "POST /superagent-console/api/plugins/check": {
|
|
1692
|
+
const body = await readJson(req);
|
|
1693
|
+
sendJson(res, 200, { problems: checkProblems(config, body.spec) });
|
|
1694
|
+
return;
|
|
1695
|
+
}
|
|
1507
1696
|
case "POST /superagent-console/api/plugins/install": {
|
|
1508
1697
|
const body = await readJson(req);
|
|
1509
1698
|
if (pluginBusy()) {
|
|
@@ -1730,4 +1919,4 @@ function apply(ctx, config) {
|
|
|
1730
1919
|
ctx.logger?.info?.("dsh-superagent-console: provider sync source attached (autoSync %s, mode %s)", resolved.autoSync ? "on" : "off", resolved.syncMode);
|
|
1731
1920
|
});
|
|
1732
1921
|
}
|
|
1733
|
-
export { Config, apply, applyProviderSync, detect, diffProviders, engineAction, inject, name, proxyHandler, readEngineProviders, readHostProviders, readSetupState, runInstall };
|
|
1922
|
+
export { Config, apply, applyProviderSync, danglingLinks, detect, diffProviders, engineAction, inject, name, proxyHandler, readEngineProviders, readHostProviders, readSetupState, runInstall };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-superagent-console",
|
|
3
|
-
"description": "Desktop console for the
|
|
4
|
-
"version": "0.1.
|
|
3
|
+
"description": "Desktop console for the dsh-superagent engine: same-origin proxied panel with engine detection, guided online install, plugin management, and model-provider sync.",
|
|
4
|
+
"version": "0.1.3",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Shuai-S",
|
|
7
7
|
"type": "module",
|
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
"types": "./lib/index.d.ts",
|
|
13
13
|
"default": "./lib/index.js"
|
|
14
14
|
},
|
|
15
|
-
"./client": "./lib/client.js"
|
|
15
|
+
"./client": "./lib/client.js",
|
|
16
|
+
"./locale/*": "./locale/*",
|
|
17
|
+
"./package.json": "./package.json"
|
|
16
18
|
},
|
|
17
19
|
"files": [
|
|
18
20
|
"lib",
|
|
@@ -71,6 +73,7 @@
|
|
|
71
73
|
"@deepseek-ai/dsh-client-locale": "0.2.1-alpha.2",
|
|
72
74
|
"@deepseek-ai/dsh-client-ui-layout": "0.2.1-alpha.2",
|
|
73
75
|
"@deepseek-ai/dsh-client-ui-plugin-manager": "0.2.1-alpha.2",
|
|
76
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.2.1-rc.5",
|
|
74
77
|
"@deepseek-ai/dsh-client-ui-renderer": "0.2.1-alpha.2",
|
|
75
78
|
"@deepseek-ai/dsh-client-ui-settings": "0.2.1-alpha.2",
|
|
76
79
|
"@deepseek-ai/dsh-client-ui-sidebar": "0.2.1-alpha.2",
|