@xiaoyuyu6420/dsh-backup 0.7.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/README.zh.md +1 -1
- package/lib/client.js +67 -32
- package/lib/index.js +258 -15
- package/package.json +4 -2
package/lib/index.js
CHANGED
|
@@ -42,6 +42,8 @@ import { hostname } from 'node:os';
|
|
|
42
42
|
import { dirname, join } from 'node:path';
|
|
43
43
|
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
44
44
|
import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
45
|
+
import { settingsNamespace, SettingsConflictError } from '@deepseek-ai/dsh-settings';
|
|
46
|
+
import z from '@deepseek-ai/schemastery';
|
|
45
47
|
|
|
46
48
|
export const name = 'dsh-backup';
|
|
47
49
|
export const inject = ['subprocess', 'commands', 'timer', 'tools'];
|
|
@@ -67,6 +69,97 @@ const SENSITIVE_DEFAULTS = ['.credentials.yaml', '.env', 'qq-bridge/config.json'
|
|
|
67
69
|
/** 备份目录下保存明文敏感文件的子目录(随备份刷新为最新一份的副本)。 */
|
|
68
70
|
const VAULT_DIR = 'vault';
|
|
69
71
|
|
|
72
|
+
/** Settings 命名空间:经 `ctx.settings` 提供(持久化到 $DSH_HOME/settings.yaml)。 */
|
|
73
|
+
const NS = settingsNamespace('dsh-backup');
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 设置默认值。SCHEMA 的 .default() 与 resolveBase 的初始值都引用这里,
|
|
77
|
+
* 避免同一字面量散落多处导致迁移逻辑漏改。
|
|
78
|
+
*/
|
|
79
|
+
const DEFAULTS = Object.freeze({
|
|
80
|
+
destination: '~/Desktop/dsh-backups',
|
|
81
|
+
exclude: [],
|
|
82
|
+
redact: [...SENSITIVE_DEFAULTS],
|
|
83
|
+
githubRepo: '',
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 解析 settings 命名空间的 schema,同时定义默认值。
|
|
88
|
+
* Base 层:pluginConfig(cordis.patch.yml,插件级或 profile 级)。
|
|
89
|
+
* User 层:Settings 面板运行时编辑,持久化到 settings.yaml。
|
|
90
|
+
*/
|
|
91
|
+
const SCHEMA = z.object({
|
|
92
|
+
destination: z.string().default(DEFAULTS.destination),
|
|
93
|
+
keep: z.number().min(0).default(0),
|
|
94
|
+
exclude: z.array(z.string()).default(DEFAULTS.exclude),
|
|
95
|
+
redact: z.union([z.array(z.string()), 'off']).default(DEFAULTS.redact),
|
|
96
|
+
githubRepo: z.string().default(DEFAULTS.githubRepo),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const SETTINGS_FIELDS = ['destination', 'keep', 'exclude', 'redact', 'githubRepo'];
|
|
100
|
+
|
|
101
|
+
/** 校验并归一化单个字段到 `out`;非法值忽略。 */
|
|
102
|
+
function normalizeField(out, field, value) {
|
|
103
|
+
if (field === 'destination') {
|
|
104
|
+
if (typeof value === 'string' && value.trim()) out.destination = value.trim();
|
|
105
|
+
} else if (field === 'keep') {
|
|
106
|
+
const n = Number(value);
|
|
107
|
+
if (Number.isFinite(n) && n >= 0) out.keep = Math.floor(n);
|
|
108
|
+
} else if (field === 'exclude') {
|
|
109
|
+
if (Array.isArray(value)) out.exclude = value.filter((v) => typeof v === 'string');
|
|
110
|
+
} else if (field === 'redact') {
|
|
111
|
+
// 'none' 为旧配置兼容:旧版允许 'none' 关闭脱敏,新版 SCHEMA 只接受数组或 'off'
|
|
112
|
+
if (value === 'off' || value === false || value === 'none') out.redact = 'off';
|
|
113
|
+
else if (Array.isArray(value)) out.redact = value.filter((v) => typeof v === 'string');
|
|
114
|
+
} else if (field === 'githubRepo') {
|
|
115
|
+
if (typeof value === 'string') out.githubRepo = value;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 将 loader 提供的 config(cordis.patch.yml)合并到 DEFAULTS 上,逐字段归一化。
|
|
121
|
+
* SCHEMA 的 .default() 与这里共用 DEFAULTS,避免迁移逻辑漏改。
|
|
122
|
+
*/
|
|
123
|
+
function resolveBase(config) {
|
|
124
|
+
const out = { ...DEFAULTS, redact: [...DEFAULTS.redact] };
|
|
125
|
+
if (config && typeof config === 'object') {
|
|
126
|
+
for (const field of SETTINGS_FIELDS) normalizeField(out, field, config[field]);
|
|
127
|
+
}
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** 校验 partial 中提供的字段;非法字段名返回给调用方,用于 400 响应。 */
|
|
132
|
+
function validatePartial(partial) {
|
|
133
|
+
const invalid = [];
|
|
134
|
+
for (const field of SETTINGS_FIELDS) {
|
|
135
|
+
if (partial[field] === undefined) continue;
|
|
136
|
+
const probe = {};
|
|
137
|
+
normalizeField(probe, field, partial[field]);
|
|
138
|
+
if (probe[field] === undefined) invalid.push(field);
|
|
139
|
+
}
|
|
140
|
+
return invalid;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** 判断是否为 settings seam 的乐观并发冲突错误。 */
|
|
144
|
+
function isSettingsConflict(err) {
|
|
145
|
+
return err instanceof SettingsConflictError || (err && err.code === 'SETTINGS_CONFLICT');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** 从 settings 服务中取出本命名空间的描述符(脱敏视图)。 */
|
|
149
|
+
function describeOwn(settings, ns) {
|
|
150
|
+
const list = settings.describe({ redactSecrets: true });
|
|
151
|
+
return list.find((descriptor) => descriptor.ns === ns);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** 将命名空间描述符整理为客户端使用的线格式。 */
|
|
155
|
+
function describeResponse(descriptor) {
|
|
156
|
+
return {
|
|
157
|
+
...descriptor.value,
|
|
158
|
+
revision: descriptor.revision,
|
|
159
|
+
hasOverrides: !!(descriptor.user && Object.keys(descriptor.user).length > 0),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
70
163
|
/**
|
|
71
164
|
* `backupPanel` Remote 命名空间的调用描述符(src-json codec)。手工经
|
|
72
165
|
* `ctx.typert.register()` 注册——运行时 registry 接受 src-json,免去 zod
|
|
@@ -167,6 +260,68 @@ class BackupPanelService extends TypertRemoteService {
|
|
|
167
260
|
}
|
|
168
261
|
|
|
169
262
|
export function apply(ctx, pluginConfig) {
|
|
263
|
+
// ---------- Settings seam ----------
|
|
264
|
+
// settings 服务可选:无该服务的 profile(非 Web)回退到 pluginConfig base 层。
|
|
265
|
+
let settingsService;
|
|
266
|
+
ctx.inject(['settings'], (sctx) => {
|
|
267
|
+
settingsService = sctx.settings;
|
|
268
|
+
settingsService.register(NS, SCHEMA, { base: resolveBase(pluginConfig) });
|
|
269
|
+
// detach 清理:服务中途 dispose 时置空,resolvedConfig() 自动降级到 pluginConfig
|
|
270
|
+
return () => { settingsService = undefined; };
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// 旧配置兼容警告:redact: 'none' 在新版映射为 'off'
|
|
274
|
+
if (pluginConfig && pluginConfig.redact === 'none') {
|
|
275
|
+
console.warn("[dsh-backup] config.redact: 'none' 已废弃,请改用 'off'(行为相同,'none' 将在未来版本移除)");
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* 解析后的生效配置:schema 默认值 ← base(cordis.patch.yml)←
|
|
280
|
+
* 用户层(settings.yaml)。settings 服务不可用时回退到 pluginConfig。
|
|
281
|
+
*/
|
|
282
|
+
function resolvedConfig() {
|
|
283
|
+
if (settingsService) {
|
|
284
|
+
const descriptor = describeOwn(settingsService, NS);
|
|
285
|
+
if (descriptor && descriptor.value) return descriptor.value;
|
|
286
|
+
}
|
|
287
|
+
return resolveBase(pluginConfig);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** 读取并解析 JSON 请求体(上限 1 MiB)。 */
|
|
291
|
+
function readJsonBody(req) {
|
|
292
|
+
return new Promise((resolve, reject) => {
|
|
293
|
+
const chunks = [];
|
|
294
|
+
let size = 0;
|
|
295
|
+
req.on('data', (chunk) => {
|
|
296
|
+
size += chunk.length;
|
|
297
|
+
if (size > 1024 * 1024) {
|
|
298
|
+
reject(new Error('request body too large'));
|
|
299
|
+
req.once('error', () => {});
|
|
300
|
+
req.destroy();
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
chunks.push(chunk);
|
|
304
|
+
});
|
|
305
|
+
req.on('end', () => {
|
|
306
|
+
const text = Buffer.concat(chunks).toString('utf8');
|
|
307
|
+
if (text.trim() === '') return resolve({});
|
|
308
|
+
try {
|
|
309
|
+
resolve(JSON.parse(text));
|
|
310
|
+
} catch {
|
|
311
|
+
reject(new Error('invalid JSON body'));
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
req.on('error', reject);
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/** 写入 JSON 响应。 */
|
|
319
|
+
function sendJson(res, status, data) {
|
|
320
|
+
const body = JSON.stringify(data);
|
|
321
|
+
res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8', 'Cache-Control': 'no-store' });
|
|
322
|
+
res.end(body);
|
|
323
|
+
}
|
|
324
|
+
|
|
170
325
|
// ---------- 工具函数 ----------
|
|
171
326
|
/**
|
|
172
327
|
* 读取 subprocess 收集的输出流。dsh-subprocess 的收集契约:输出超过
|
|
@@ -228,28 +383,32 @@ export function apply(ctx, pluginConfig) {
|
|
|
228
383
|
const home = homeRaw ? toFwd(homeRaw.replace(/\/+$/, '')) : undefined;
|
|
229
384
|
const dshHome = env?.get('DSH_HOME')?.value || (home ? `${home}/.dsh` : undefined);
|
|
230
385
|
if (!home || !dshHome) throw new Error('无法解析 HOME/USERPROFILE 或 DSH_HOME(launchEnvironment 缺失)');
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
386
|
+
const cfg = resolvedConfig();
|
|
387
|
+
const raw = typeof cfg.destination === 'string' && cfg.destination.trim()
|
|
388
|
+
? cfg.destination.trim()
|
|
389
|
+
: DEFAULTS.destination;
|
|
234
390
|
const root = raw.startsWith('~') ? `${home}${raw.slice(1)}` : toFwd(raw);
|
|
235
391
|
return { home, dshHome: toFwd(dshHome), root };
|
|
236
392
|
}
|
|
237
393
|
|
|
238
394
|
function defaultKeep() {
|
|
239
|
-
const
|
|
395
|
+
const cfg = resolvedConfig();
|
|
396
|
+
const k = Number(cfg.keep);
|
|
240
397
|
return Number.isFinite(k) && k > 0 ? Math.floor(k) : 7;
|
|
241
398
|
}
|
|
242
399
|
|
|
243
400
|
function extraExcludes() {
|
|
244
|
-
const
|
|
401
|
+
const cfg = resolvedConfig();
|
|
402
|
+
const list = Array.isArray(cfg.exclude) ? cfg.exclude : [];
|
|
245
403
|
return list.filter((p) => typeof p === 'string' && p.length > 0).map((p) => `--exclude=${p}`);
|
|
246
404
|
}
|
|
247
405
|
|
|
248
406
|
/** 生效的脱敏清单:默认集 + config.redact 追加(false/'off' 整体关闭)。 */
|
|
249
407
|
function sensitivePaths() {
|
|
250
|
-
const cfg =
|
|
251
|
-
|
|
252
|
-
|
|
408
|
+
const cfg = resolvedConfig();
|
|
409
|
+
const redactCfg = cfg.redact;
|
|
410
|
+
if (redactCfg === false || redactCfg === 'off' || redactCfg === 'none') return [];
|
|
411
|
+
const extra = Array.isArray(redactCfg) ? redactCfg.filter((p) => typeof p === 'string' && p.trim().length) : [];
|
|
253
412
|
return [...new Set([...SENSITIVE_DEFAULTS, ...extra.map((p) => p.trim())])];
|
|
254
413
|
}
|
|
255
414
|
|
|
@@ -510,11 +669,12 @@ export function apply(ctx, pluginConfig) {
|
|
|
510
669
|
|
|
511
670
|
// ---------- GitHub 同步 ----------
|
|
512
671
|
function githubConfig() {
|
|
513
|
-
// 运行时设置(面板/命令,存 auto.json)优先,cordis.yml 的 githubRepo
|
|
672
|
+
// 运行时设置(面板/命令,存 auto.json)优先,settings.yaml → cordis.yml 的 githubRepo 是初始默认。
|
|
673
|
+
const cfg = resolvedConfig();
|
|
514
674
|
const raw = githubState && typeof githubState.repo === 'string' && githubState.repo.trim()
|
|
515
675
|
? githubState.repo.trim()
|
|
516
|
-
: (typeof
|
|
517
|
-
?
|
|
676
|
+
: (typeof cfg.githubRepo === 'string' && cfg.githubRepo.trim()
|
|
677
|
+
? cfg.githubRepo.trim()
|
|
518
678
|
: '');
|
|
519
679
|
if (!raw) return null;
|
|
520
680
|
const env = ctx.get('launchEnvironment');
|
|
@@ -1042,7 +1202,8 @@ export function apply(ctx, pluginConfig) {
|
|
|
1042
1202
|
|
|
1043
1203
|
/** 自动备份的保留份数:config.keep 支配(未配置时 <24h 3 份、否则 7 份)。 */
|
|
1044
1204
|
function autoKeep() {
|
|
1045
|
-
const
|
|
1205
|
+
const cfg = resolvedConfig();
|
|
1206
|
+
const k = Number(cfg.keep);
|
|
1046
1207
|
return Number.isFinite(k) && k > 0 ? Math.floor(k) : (autoHours >= 24 ? 7 : 3);
|
|
1047
1208
|
}
|
|
1048
1209
|
|
|
@@ -1399,10 +1560,11 @@ export function apply(ctx, pluginConfig) {
|
|
|
1399
1560
|
githubStatus: async () => {
|
|
1400
1561
|
const cfg = githubConfig();
|
|
1401
1562
|
const { root } = paths();
|
|
1563
|
+
const resolved = resolvedConfig();
|
|
1402
1564
|
return {
|
|
1403
|
-
// repoRaw 是用户原始输入(运行时值优先,否则 cordis.yml 默认),供面板编辑框回填
|
|
1565
|
+
// repoRaw 是用户原始输入(运行时值优先,否则 settings.yaml → cordis.yml 默认),供面板编辑框回填
|
|
1404
1566
|
// 两路都经 stripUserinfo:避免 cordis.yml config.githubRepo 内嵌 token 时经面板泄露
|
|
1405
|
-
repoRaw: githubState.repo ?? (typeof
|
|
1567
|
+
repoRaw: githubState.repo ?? (typeof resolved.githubRepo === 'string' ? stripUserinfo(resolved.githubRepo) : null),
|
|
1406
1568
|
repo: cfg ? cfg.repo : null,
|
|
1407
1569
|
tokenSet: Boolean(cfg?.token),
|
|
1408
1570
|
syncDir: `${root}/${SYNC_DIR}`,
|
|
@@ -1454,13 +1616,33 @@ export function apply(ctx, pluginConfig) {
|
|
|
1454
1616
|
if (!raw) {
|
|
1455
1617
|
githubState = { ...githubState, repo: null, lastError: null };
|
|
1456
1618
|
await saveAutoState();
|
|
1457
|
-
|
|
1619
|
+
// 清除用户层的 githubRepo,让 base 层(cordis.patch.yml)透传回来
|
|
1620
|
+
if (settingsService) {
|
|
1621
|
+
try {
|
|
1622
|
+
// 读取当前用户层,移除 githubRepo 字段后 replace 回去
|
|
1623
|
+
const desc = describeOwn(settingsService, NS);
|
|
1624
|
+
const userLayer = desc?.user ? { ...desc.user } : {};
|
|
1625
|
+
delete userLayer.githubRepo;
|
|
1626
|
+
await settingsService.replace(NS, userLayer);
|
|
1627
|
+
} catch (err) {
|
|
1628
|
+
return { ok: false, summary: String(err && err.message ? err.message : err) };
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
return { ok: true, repo: null, summary: 'GitHub 同步仓库已清除用户层覆盖(回退到 cordis.yml 配置,若有)。' };
|
|
1458
1632
|
}
|
|
1459
1633
|
const invalid = validateRepo(raw);
|
|
1460
1634
|
if (invalid) return { ok: false, summary: invalid };
|
|
1461
1635
|
const clean = stripUserinfo(raw);
|
|
1462
1636
|
githubState = { ...githubState, repo: clean, lastError: null };
|
|
1463
1637
|
await saveAutoState();
|
|
1638
|
+
// 同步写入 settings.yaml
|
|
1639
|
+
if (settingsService) {
|
|
1640
|
+
try {
|
|
1641
|
+
await settingsService.update(NS, { githubRepo: clean });
|
|
1642
|
+
} catch (err) {
|
|
1643
|
+
return { ok: false, summary: String(err && err.message ? err.message : err) };
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1464
1646
|
return { ok: true, repo: clean, summary: `GitHub 同步仓库已设为: ${clean}` };
|
|
1465
1647
|
},
|
|
1466
1648
|
};
|
|
@@ -1486,6 +1668,67 @@ export function apply(ctx, pluginConfig) {
|
|
|
1486
1668
|
}), 'dsh-backup: download route');
|
|
1487
1669
|
});
|
|
1488
1670
|
|
|
1671
|
+
// Settings 薄代理:经 ctx.settings 官方 seam 的 GET/POST 接口。
|
|
1672
|
+
// 用户编辑持久化到 $DSH_HOME/settings.yaml;base 层(cordis.patch.yml)
|
|
1673
|
+
// 本插件从不写入。
|
|
1674
|
+
ctx.inject(['webServer'], (scope) => {
|
|
1675
|
+
scope.effect(() => scope.webServer.register({
|
|
1676
|
+
kind: 'exact',
|
|
1677
|
+
path: '/dsh-backup/settings',
|
|
1678
|
+
handler: async (req, res) => {
|
|
1679
|
+
try {
|
|
1680
|
+
// 与 /backup-download 同一 loopback 校验:settings 路由更敏感,绝不对非本机暴露
|
|
1681
|
+
const host = String(req.headers?.host || '');
|
|
1682
|
+
if (!/^(127\.0\.0\.1|localhost|\[::1\])(:\d+)?$/.test(host)) {
|
|
1683
|
+
sendJson(res, 403, { error: 'forbidden' });
|
|
1684
|
+
return;
|
|
1685
|
+
}
|
|
1686
|
+
if (!settingsService) {
|
|
1687
|
+
sendJson(res, 503, { error: 'settings-unavailable' });
|
|
1688
|
+
return;
|
|
1689
|
+
}
|
|
1690
|
+
if (req.method === 'GET' || req.method === 'HEAD') {
|
|
1691
|
+
sendJson(res, 200, describeResponse(describeOwn(settingsService, NS)));
|
|
1692
|
+
return;
|
|
1693
|
+
}
|
|
1694
|
+
if (req.method !== 'POST') {
|
|
1695
|
+
sendJson(res, 405, { error: 'method-not-allowed' });
|
|
1696
|
+
return;
|
|
1697
|
+
}
|
|
1698
|
+
const body = await readJsonBody(req);
|
|
1699
|
+
const expectedRevision = typeof body.revision === 'number' ? body.revision : undefined;
|
|
1700
|
+
try {
|
|
1701
|
+
if (body.reset === true) {
|
|
1702
|
+
// 重置:清除用户层,值回退到 base 层
|
|
1703
|
+
await settingsService.replace(NS, {}, expectedRevision);
|
|
1704
|
+
} else {
|
|
1705
|
+
const invalid = validatePartial(body);
|
|
1706
|
+
if (invalid.length > 0) {
|
|
1707
|
+
sendJson(res, 400, { error: 'invalid-field', fields: invalid });
|
|
1708
|
+
return;
|
|
1709
|
+
}
|
|
1710
|
+
// 保存:仅合并提供的字段到用户层
|
|
1711
|
+
const patch = {};
|
|
1712
|
+
for (const field of SETTINGS_FIELDS) {
|
|
1713
|
+
if (body[field] !== undefined) normalizeField(patch, field, body[field]);
|
|
1714
|
+
}
|
|
1715
|
+
await settingsService.update(NS, patch, expectedRevision);
|
|
1716
|
+
}
|
|
1717
|
+
} catch (err) {
|
|
1718
|
+
if (isSettingsConflict(err)) {
|
|
1719
|
+
sendJson(res, 409, { error: 'settings-conflict', revision: err.actual });
|
|
1720
|
+
return;
|
|
1721
|
+
}
|
|
1722
|
+
throw err;
|
|
1723
|
+
}
|
|
1724
|
+
sendJson(res, 200, describeResponse(describeOwn(settingsService, NS)));
|
|
1725
|
+
} catch (err) {
|
|
1726
|
+
sendJson(res, 400, { error: String(err && err.message ? err.message : err) });
|
|
1727
|
+
}
|
|
1728
|
+
},
|
|
1729
|
+
}), 'dsh-backup: settings route');
|
|
1730
|
+
});
|
|
1731
|
+
|
|
1489
1732
|
// 启动时恢复持久化的定时备份计划(不阻塞插件装配);错过则 delay=0 立即补跑。
|
|
1490
1733
|
void (async () => {
|
|
1491
1734
|
const h = await loadAutoState();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xiaoyuyu6420/dsh-backup",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Backup, restore, download and GitHub-sync DeepSeek Harness user data (~/.dsh): /backup, scheduled auto-backup that survives restarts, sha256 checksums, integrity verify, rotation, credential redaction with a local vault (plaintext never leaves the machine), cross-machine restore preflight with github pull, and a visual Settings panel. Cross-platform (macOS/Linux/Windows). 一键备份与恢复 DSH 数据:定时自动备份、完整性校验、凭据默认脱敏(明文只存本机 vault)、跨机恢复预检与 github pull 拉取,附 Settings 可视面板。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -46,9 +46,11 @@
|
|
|
46
46
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
47
47
|
"@deepseek-ai/dsh-commands": "^0.1.1-rc.2",
|
|
48
48
|
"@deepseek-ai/dsh-fs": "^0.1.1-rc.2",
|
|
49
|
+
"@deepseek-ai/dsh-settings": "^0.1.1-rc.2",
|
|
49
50
|
"@deepseek-ai/dsh-subprocess": "^0.1.1-rc.2",
|
|
50
51
|
"@deepseek-ai/dsh-tools": "^0.1.1-rc.2",
|
|
51
|
-
"@deepseek-ai/dsh-typert-protocol": "^0.1.1-rc.2"
|
|
52
|
+
"@deepseek-ai/dsh-typert-protocol": "^0.1.1-rc.2",
|
|
53
|
+
"@deepseek-ai/schemastery": "^3.18.1"
|
|
52
54
|
},
|
|
53
55
|
"devDependencies": {
|
|
54
56
|
"esbuild": "^0.25.0",
|