dsh-update-status 0.1.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/lib/index.js ADDED
@@ -0,0 +1,511 @@
1
+ import { createRequire } from "node:module";
2
+ import z from "@deepseek-ai/schemastery";
3
+ import { existsSync, readFileSync, realpathSync } from "node:fs";
4
+ import { dirname, join, normalize, sep } from "node:path";
5
+ //#region src/shared/types.ts
6
+ /**
7
+ * JSON-only contract shared by the Host and Web halves.
8
+ *
9
+ * The static package uses the authenticated Connection RPC channel because
10
+ * `harness.handle` / `host.call` are dynamic-Cordis-only closure APIs in DSH
11
+ * 0.1.2-rc.1. The endpoint vocabulary remains deliberately small and private
12
+ * to this plugin channel.
13
+ */
14
+ const PLUGIN_ID = "dsh-update-status";
15
+ const PACKAGE_NAME = "@deepseek-ai/dsh";
16
+ const UPDATE_STATUS_CHANNEL = "/dsh-update-status";
17
+ const RELEASE_CHANNELS = [
18
+ "latest",
19
+ "next",
20
+ "alpha"
21
+ ];
22
+ function isCacheTtlMinutes(value) {
23
+ return typeof value === "number" && Number.isInteger(value) && value >= 30 && value <= 1440;
24
+ }
25
+ const UPDATE_ENDPOINTS = {
26
+ getStatus: "get-status",
27
+ checkUpdate: "check-update"
28
+ };
29
+ function isReleaseChannel(value) {
30
+ return value === "latest" || value === "next" || value === "alpha";
31
+ }
32
+ //#endregion
33
+ //#region src/host/installation.ts
34
+ /**
35
+ * Read-only facts about the DSH process installation. No shell command is run:
36
+ * the probe reads only package manifests and Node resolution anchors already
37
+ * available to the current Host process.
38
+ */
39
+ function manifestAt(path) {
40
+ try {
41
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
42
+ if (parsed.name !== "@deepseek-ai/dsh" || typeof parsed.version !== "string" || parsed.version.trim() === "") return void 0;
43
+ return {
44
+ version: parsed.version.trim(),
45
+ root: dirname(path)
46
+ };
47
+ } catch {
48
+ return;
49
+ }
50
+ }
51
+ function realPath(path) {
52
+ try {
53
+ return realpathSync(path);
54
+ } catch {
55
+ return path;
56
+ }
57
+ }
58
+ /** Ascend from an entry file until the owning DSH package manifest is found. */
59
+ function manifestFromEntry(entryPath) {
60
+ let directory = dirname(realPath(entryPath));
61
+ for (let depth = 0; depth < 10; depth += 1) {
62
+ const manifest = join(directory, "package.json");
63
+ if (existsSync(manifest)) {
64
+ const found = manifestAt(manifest);
65
+ if (found !== void 0) return {
66
+ ...found,
67
+ root: realPath(found.root)
68
+ };
69
+ }
70
+ const parent = dirname(directory);
71
+ if (parent === directory) break;
72
+ directory = parent;
73
+ }
74
+ }
75
+ function manifestFromResolver(resolve) {
76
+ try {
77
+ const found = manifestAt(resolve(PACKAGE_NAME + "/package.json"));
78
+ if (found !== void 0) return {
79
+ ...found,
80
+ root: realPath(found.root)
81
+ };
82
+ } catch {}
83
+ try {
84
+ return manifestFromEntry(resolve(PACKAGE_NAME));
85
+ } catch {
86
+ return;
87
+ }
88
+ }
89
+ function homeResolver(ctx) {
90
+ try {
91
+ const get = ctx.get;
92
+ const dshHomePath = typeof get === "function" ? get.call(ctx, "dshHomePath") : void 0;
93
+ if (typeof dshHomePath !== "function") return void 0;
94
+ const anchor = dshHomePath("profiles", "dsh-update-status-probe.cjs");
95
+ return createRequire(anchor).resolve;
96
+ } catch {
97
+ return;
98
+ }
99
+ }
100
+ function ownResolver(selfUrl) {
101
+ try {
102
+ return createRequire(selfUrl).resolve;
103
+ } catch {
104
+ return;
105
+ }
106
+ }
107
+ /**
108
+ * Classify only when path evidence is strong. A profile-local mirror must not
109
+ * be advertised as an upgradeable global installation, hence the conservative
110
+ * unknown fallback.
111
+ */
112
+ function classifyInstallRoot(packageRoot, nodePrefix = process.execPath) {
113
+ if (packageRoot === void 0) return "unknown";
114
+ const root = normalize(realPath(packageRoot)).split(sep).join("/");
115
+ const prefix = nodePrefix === void 0 ? "" : normalize(dirname(dirname(nodePrefix))).split(sep).join("/");
116
+ if (!root.includes("/node_modules/") && /\/apps\/cli(?:\/|$)/.test(root)) return "source-checkout";
117
+ if (root.includes("/.pnpm/") || /\/pnpm\/global(?:\/|$)/.test(root)) return "pnpm-global";
118
+ if (prefix !== "" && root.startsWith(prefix + "/lib/node_modules/")) return "npm-global";
119
+ if (/\/lib\/node_modules\/@deepseek-ai\/dsh$/.test(root)) return "npm-global";
120
+ return "unknown";
121
+ }
122
+ /** Generate guidance only; this package never invokes the string it returns. */
123
+ function upgradeCommandFor(installKind, packageName = PACKAGE_NAME, channel = "latest") {
124
+ const specifier = `${packageName}@${channel}`;
125
+ switch (installKind) {
126
+ case "npm-global": return `npm install -g ${specifier}`;
127
+ case "pnpm-global": return `pnpm add -g ${specifier}`;
128
+ case "source-checkout": return "在 DSH checkout 中拉取代码、安装依赖并重新构建;插件不会从 GUI 原地替换";
129
+ default: return "请确认 dsh 安装方式后再升级;当前插件不会代为执行";
130
+ }
131
+ }
132
+ /**
133
+ * Resolve the package that launched this process first, then the DSH home
134
+ * mirror and finally this plugin's resolver. All failures degrade safely.
135
+ */
136
+ function detectInstallation(ctx, selfUrl = import.meta.url) {
137
+ const argvEntry = typeof process.argv[1] === "string" ? manifestFromEntry(process.argv[1]) : void 0;
138
+ const fromHome = homeResolver(ctx);
139
+ const home = fromHome === void 0 ? void 0 : manifestFromResolver(fromHome);
140
+ const own = ownResolver(selfUrl);
141
+ const local = own === void 0 ? void 0 : manifestFromResolver(own);
142
+ const found = argvEntry ?? home ?? local;
143
+ const installKind = classifyInstallRoot(found?.root);
144
+ const channel = "latest";
145
+ return {
146
+ currentVersion: found?.version ?? "unknown",
147
+ packageName: PACKAGE_NAME,
148
+ channel,
149
+ installKind,
150
+ ...found?.root === void 0 ? {} : { packageRoot: found.root },
151
+ upgradeCommand: upgradeCommandFor(installKind, PACKAGE_NAME, channel)
152
+ };
153
+ }
154
+ //#endregion
155
+ //#region src/host/rpc.ts
156
+ function failure(code, message) {
157
+ return {
158
+ ok: false,
159
+ error: {
160
+ code,
161
+ message,
162
+ details: {}
163
+ }
164
+ };
165
+ }
166
+ function requestOf(value) {
167
+ if (value === null || value === void 0) return {};
168
+ if (typeof value !== "object" || Array.isArray(value)) return void 0;
169
+ const record = value;
170
+ if (record.force !== void 0 && typeof record.force !== "boolean") return void 0;
171
+ if (record.channel !== void 0 && !isReleaseChannel(record.channel)) return void 0;
172
+ if (record.cacheTtlMinutes !== void 0 && !isCacheTtlMinutes(record.cacheTtlMinutes)) return void 0;
173
+ return {
174
+ ...record.force === void 0 ? {} : { force: record.force },
175
+ ...record.channel === void 0 ? {} : { channel: record.channel },
176
+ ...record.cacheTtlMinutes === void 0 ? {} : { cacheTtlMinutes: record.cacheTtlMinutes }
177
+ };
178
+ }
179
+ /** Endpoint dispatcher, exported to allow exact JSON-shape tests without a Host. */
180
+ function createUpdateStatusRpcHandler(service) {
181
+ return async (endpoint, payload) => {
182
+ try {
183
+ if (endpoint === UPDATE_ENDPOINTS.getStatus) {
184
+ const request = requestOf(payload);
185
+ if (request === void 0) return failure("dsh-update-status/bad-request", "`force` must be boolean, `channel` must be latest, next, or alpha, and `cacheTtlMinutes` must be an integer from 30 to 1440");
186
+ return {
187
+ ok: true,
188
+ value: await service.getStatus(request.channel, request.cacheTtlMinutes)
189
+ };
190
+ }
191
+ if (endpoint === UPDATE_ENDPOINTS.checkUpdate) {
192
+ const request = requestOf(payload);
193
+ if (request === void 0) return failure("dsh-update-status/bad-request", "`force` must be boolean, `channel` must be latest, next, or alpha, and `cacheTtlMinutes` must be an integer from 30 to 1440");
194
+ return {
195
+ ok: true,
196
+ value: await service.check(request.force === true, request.channel, request.cacheTtlMinutes)
197
+ };
198
+ }
199
+ return failure("dsh-update-status/unknown-endpoint", `unknown endpoint: ${endpoint}`);
200
+ } catch (error) {
201
+ return failure("dsh-update-status/internal", (error instanceof Error ? error.message : String(error)).slice(0, 220));
202
+ }
203
+ };
204
+ }
205
+ /**
206
+ * Static packages do not receive dynamic Cordis's `harness.handle` closure.
207
+ * This registration uses DSH's existing authenticated Connection RPC transport
208
+ * and is removed with the plugin fiber.
209
+ */
210
+ function installUpdateStatusRpc(ctx, service) {
211
+ const handler = createUpdateStatusRpcHandler(service);
212
+ ctx.inject(["connection"], (connectionCtx) => {
213
+ const connection = connectionCtx.get("connection");
214
+ const handle = typeof connection?.rpc?.handle === "function" ? connection.rpc.handle.bind(connection.rpc) : void 0;
215
+ if (handle === void 0) return;
216
+ connectionCtx.effect(() => {
217
+ const unregister = handle(UPDATE_STATUS_CHANNEL, handler);
218
+ return () => {
219
+ Promise.resolve(unregister()).catch(() => {});
220
+ };
221
+ }, "dsh-update-status: authenticated RPC channel");
222
+ });
223
+ }
224
+ //#endregion
225
+ //#region src/host/settings.ts
226
+ const SettingsSchema = z.object({
227
+ sidebarEnabled: z.boolean().default(true),
228
+ channel: z.union([
229
+ "latest",
230
+ "next",
231
+ "alpha"
232
+ ]).default("latest").loose(),
233
+ cacheTtlMinutes: z.number().min(30).max(1440).default(360)
234
+ });
235
+ /** Settings are optional composition; absent providers leave the Web default on. */
236
+ function installSettings(ctx) {
237
+ ctx.inject(["settings"], (settingsCtx) => {
238
+ try {
239
+ settingsCtx.settings.register(PLUGIN_ID, SettingsSchema, {
240
+ base: {
241
+ sidebarEnabled: true,
242
+ channel: "latest",
243
+ cacheTtlMinutes: 360
244
+ },
245
+ applies: "live"
246
+ });
247
+ } catch (error) {
248
+ console.error("[dsh-update-status] settings namespace registration failed:", error);
249
+ }
250
+ });
251
+ }
252
+ //#endregion
253
+ //#region src/shared/semver.ts
254
+ const SEMVER = /^(?:v)?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
255
+ const NUMERIC_IDENTIFIER = /^(0|[1-9]\d*)$/;
256
+ function parseSemver(value) {
257
+ const match = SEMVER.exec(value.trim());
258
+ if (match === null) return void 0;
259
+ const major = Number(match[1]);
260
+ const minor = Number(match[2]);
261
+ const patch = Number(match[3]);
262
+ if (!Number.isSafeInteger(major) || !Number.isSafeInteger(minor) || !Number.isSafeInteger(patch)) return void 0;
263
+ return {
264
+ major,
265
+ minor,
266
+ patch,
267
+ prerelease: match[4] === void 0 ? [] : match[4].split(".")
268
+ };
269
+ }
270
+ function compareIdentifier(left, right) {
271
+ const leftNumeric = NUMERIC_IDENTIFIER.test(left);
272
+ const rightNumeric = NUMERIC_IDENTIFIER.test(right);
273
+ if (leftNumeric && rightNumeric) return Number(left) - Number(right);
274
+ if (leftNumeric) return -1;
275
+ if (rightNumeric) return 1;
276
+ return left < right ? -1 : left > right ? 1 : 0;
277
+ }
278
+ /** Returns a negative number when left is older; undefined means unparsable. */
279
+ function compareSemver(leftValue, rightValue) {
280
+ const left = parseSemver(leftValue);
281
+ const right = parseSemver(rightValue);
282
+ if (left === void 0 || right === void 0) return void 0;
283
+ for (const key of [
284
+ "major",
285
+ "minor",
286
+ "patch"
287
+ ]) if (left[key] !== right[key]) return left[key] - right[key];
288
+ const leftStable = left.prerelease.length === 0;
289
+ const rightStable = right.prerelease.length === 0;
290
+ if (leftStable && rightStable) return 0;
291
+ if (leftStable) return 1;
292
+ if (rightStable) return -1;
293
+ const length = Math.max(left.prerelease.length, right.prerelease.length);
294
+ for (let index = 0; index < length; index += 1) {
295
+ const leftPart = left.prerelease[index];
296
+ const rightPart = right.prerelease[index];
297
+ if (leftPart === void 0) return -1;
298
+ if (rightPart === void 0) return 1;
299
+ const comparison = compareIdentifier(leftPart, rightPart);
300
+ if (comparison !== 0) return comparison;
301
+ }
302
+ return 0;
303
+ }
304
+ //#endregion
305
+ //#region src/host/update-status.ts
306
+ const REGISTRY_URL = "https://registry.npmjs.org/@deepseek-ai%2Fdsh";
307
+ const DEFAULT_TTL_MS = 216e5;
308
+ const DEFAULT_TIMEOUT_MS = 15e3;
309
+ function boundedMessage(error) {
310
+ const trimmed = (error instanceof Error ? error.message : String(error)).replace(/\s+/g, " ").trim();
311
+ return trimmed === "" ? "unknown error" : trimmed.slice(0, 220);
312
+ }
313
+ function warningWith(base, addition) {
314
+ if (base === null || base === "") return addition;
315
+ if (addition === null || addition === "") return base;
316
+ return `${base} ${addition}`;
317
+ }
318
+ function dateOrNull(value) {
319
+ if (typeof value !== "string" || value === "") return null;
320
+ const time = Date.parse(value);
321
+ return Number.isFinite(time) ? new Date(time).toISOString() : null;
322
+ }
323
+ function compatibilityOf(version) {
324
+ return version === "0.1.2-rc.1" ? "verified" : "unverified";
325
+ }
326
+ function registryReleaseOf(value) {
327
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("npm registry returned an invalid document");
328
+ const record = value;
329
+ const tags = record["dist-tags"];
330
+ if (tags === null || typeof tags !== "object" || Array.isArray(tags)) throw new Error("npm registry response has no dist-tags");
331
+ const tagRecord = tags;
332
+ const time = record.time;
333
+ const timeRecord = time !== null && typeof time === "object" && !Array.isArray(time) ? time : {};
334
+ const channels = RELEASE_CHANNELS.map((channel) => {
335
+ const raw = tagRecord[channel];
336
+ const version = typeof raw === "string" && raw.trim() !== "" ? raw.trim() : null;
337
+ return {
338
+ channel,
339
+ version,
340
+ publishedAt: version === null ? null : dateOrNull(timeRecord[version]),
341
+ compatibility: compatibilityOf(version)
342
+ };
343
+ });
344
+ if (channels.every((release) => release.version === null)) throw new Error("npm registry response has no supported dist-tags");
345
+ return { channels };
346
+ }
347
+ /** Only the explicitly approved HTTPS npm Registry authority may be contacted. */
348
+ function assertApprovedRegistryUrl(raw) {
349
+ const url = new URL(raw);
350
+ if (url.protocol !== "https:" || url.hostname !== "registry.npmjs.org" || url.username !== "" || url.password !== "") throw new Error("update check rejected a non-whitelisted registry URL");
351
+ return url;
352
+ }
353
+ /** Create the default HTTPS-only registry reader. */
354
+ function createRegistryFetcher(timeoutMs = DEFAULT_TIMEOUT_MS) {
355
+ const boundedTimeout = Math.max(1e3, Math.min(3e4, Math.floor(timeoutMs)));
356
+ return async () => {
357
+ const url = assertApprovedRegistryUrl(REGISTRY_URL);
358
+ const controller = new AbortController();
359
+ const timer = setTimeout(() => {
360
+ controller.abort();
361
+ }, boundedTimeout);
362
+ try {
363
+ const response = await fetch(url, {
364
+ method: "GET",
365
+ redirect: "error",
366
+ signal: controller.signal,
367
+ headers: { accept: "application/json" }
368
+ });
369
+ if (!response.ok) throw new Error(`npm registry returned HTTP ${response.status}`);
370
+ return registryReleaseOf(await response.json());
371
+ } catch (error) {
372
+ if (controller.signal.aborted) throw new Error(`npm registry timed out after ${boundedTimeout}ms`);
373
+ throw error;
374
+ } finally {
375
+ clearTimeout(timer);
376
+ }
377
+ };
378
+ }
379
+ var UpdateStatusService = class {
380
+ installation;
381
+ fetchLatest;
382
+ now;
383
+ ttlMs;
384
+ releaseUrl;
385
+ cache;
386
+ inFlight;
387
+ constructor(options) {
388
+ this.installation = options.installation;
389
+ this.fetchLatest = options.fetchLatest ?? createRegistryFetcher();
390
+ this.now = options.now ?? Date.now;
391
+ this.ttlMs = Math.max(1, Math.floor(options.ttlMs ?? DEFAULT_TTL_MS));
392
+ this.releaseUrl = options.releaseUrl ?? "https://github.com/deepseek-ai/deepseek-harness/releases";
393
+ }
394
+ getStatus(channel = "latest", cacheTtlMinutes) {
395
+ return this.check(false, channel, cacheTtlMinutes);
396
+ }
397
+ /** `force` bypasses TTL but still joins any registry check already in flight. */
398
+ async check(force = false, channel = "latest", cacheTtlMinutes) {
399
+ const ttlMs = isCacheTtlMinutes(cacheTtlMinutes) ? cacheTtlMinutes * 60 * 1e3 : this.ttlMs;
400
+ const cached = this.cache;
401
+ if (!force && cached !== void 0 && this.now() - cached.checkedAtMs < ttlMs) return this.statusFromCache(cached, channel, true, null);
402
+ if (this.inFlight !== void 0) try {
403
+ return this.statusFromCache(await this.inFlight, channel, false, null);
404
+ } catch (error) {
405
+ return this.statusAfterFailure(channel, error);
406
+ }
407
+ const run = this.refreshRelease();
408
+ this.inFlight = run;
409
+ try {
410
+ return this.statusFromCache(await run, channel, false, null);
411
+ } catch (error) {
412
+ return this.statusAfterFailure(channel, error);
413
+ } finally {
414
+ if (this.inFlight === run) this.inFlight = void 0;
415
+ }
416
+ }
417
+ async refreshRelease() {
418
+ const cache = {
419
+ release: await this.fetchLatest(),
420
+ checkedAtMs: this.now()
421
+ };
422
+ this.cache = cache;
423
+ return cache;
424
+ }
425
+ statusAfterFailure(channel, error) {
426
+ const warning = `无法检查 npm registry:${boundedMessage(error)}`;
427
+ return this.cache === void 0 ? this.statusWithoutRemoteRelease(channel, warning) : this.statusFromCache(this.cache, channel, true, warning);
428
+ }
429
+ statusFromCache(cache, channel, cached, initialWarning) {
430
+ const selected = cache.release.channels.find((release) => release.channel === channel) ?? {
431
+ channel,
432
+ version: null,
433
+ publishedAt: null,
434
+ compatibility: "unverified"
435
+ };
436
+ const comparison = selected.version === null ? void 0 : compareSemver(this.installation.currentVersion, selected.version);
437
+ const missingWarning = selected.version === null ? `npm registry 未发布 ${channel} 通道。` : null;
438
+ const comparisonWarning = selected.version !== null && comparison === void 0 ? `无法按 SemVer 比较当前版本 ${this.installation.currentVersion} 与 ${channel} 通道版本 ${selected.version}。` : null;
439
+ const previewWarning = channel !== "latest" && selected.version !== null && selected.compatibility !== "verified" ? `${channel} 是预览通道,版本 ${selected.version} 尚未验证与本插件兼容。` : null;
440
+ return {
441
+ currentVersion: this.installation.currentVersion,
442
+ latestVersion: selected.version,
443
+ hasUpdate: comparison !== void 0 && comparison < 0,
444
+ cached,
445
+ checkedAt: new Date(cache.checkedAtMs).toISOString(),
446
+ warning: warningWith(warningWith(initialWarning, missingWarning), warningWith(comparisonWarning, previewWarning)),
447
+ installKind: this.installation.installKind,
448
+ upgradeCommand: upgradeCommandFor(this.installation.installKind, this.installation.packageName || "@deepseek-ai/dsh", channel),
449
+ releaseUrl: this.releaseUrl,
450
+ changelogUrl: this.releaseUrl,
451
+ publishedAt: selected.publishedAt,
452
+ packageName: this.installation.packageName || "@deepseek-ai/dsh",
453
+ channel,
454
+ channels: cache.release.channels,
455
+ canApplyInPlace: false
456
+ };
457
+ }
458
+ statusWithoutRemoteRelease(channel, warning) {
459
+ return {
460
+ currentVersion: this.installation.currentVersion,
461
+ latestVersion: null,
462
+ hasUpdate: false,
463
+ cached: false,
464
+ checkedAt: null,
465
+ warning,
466
+ installKind: this.installation.installKind,
467
+ upgradeCommand: upgradeCommandFor(this.installation.installKind, this.installation.packageName || "@deepseek-ai/dsh", channel),
468
+ releaseUrl: this.releaseUrl,
469
+ changelogUrl: this.releaseUrl,
470
+ publishedAt: null,
471
+ packageName: this.installation.packageName || "@deepseek-ai/dsh",
472
+ channel,
473
+ channels: RELEASE_CHANNELS.map((item) => ({
474
+ channel: item,
475
+ version: null,
476
+ publishedAt: null,
477
+ compatibility: "unverified"
478
+ })),
479
+ canApplyInPlace: false
480
+ };
481
+ }
482
+ };
483
+ //#endregion
484
+ //#region src/index.ts
485
+ const name = "dsh-update-status";
486
+ /** Connection supplies the authenticated transport; settings remains optional. */
487
+ const inject = ["connection"];
488
+ /** Deployment config only controls metadata-check timing; it never authorizes upgrades. */
489
+ const Config = z.object({
490
+ cacheTtlHours: z.number().step(1).min(1).max(24).default(6),
491
+ timeoutMs: z.number().step(1).min(1e3).max(3e4).default(DEFAULT_TIMEOUT_MS),
492
+ autoCheckOnMount: z.boolean().default(true)
493
+ });
494
+ function boundedNumber(value, fallback, min, max) {
495
+ if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
496
+ return Math.min(max, Math.max(min, Math.floor(value)));
497
+ }
498
+ function apply(ctx, config = {}) {
499
+ const ttlHours = boundedNumber(config.cacheTtlHours, 6, 1, 24);
500
+ const timeoutMs = boundedNumber(config.timeoutMs, DEFAULT_TIMEOUT_MS, 1e3, 3e4);
501
+ const service = new UpdateStatusService({
502
+ installation: detectInstallation(ctx),
503
+ fetchLatest: createRegistryFetcher(timeoutMs),
504
+ ttlMs: ttlHours * 60 * 60 * 1e3
505
+ });
506
+ installSettings(ctx);
507
+ installUpdateStatusRpc(ctx, service);
508
+ if (config.autoCheckOnMount !== false) service.getStatus();
509
+ }
510
+ //#endregion
511
+ export { Config, UpdateStatusService, apply, inject, name };
package/package.json ADDED
@@ -0,0 +1,101 @@
1
+ {
2
+ "name": "dsh-update-status",
3
+ "version": "0.1.0",
4
+ "description": "Read-only DeepSeek Harness Web plugin that shows the running DSH version, discovers latest/next/alpha releases, warns about compatibility, and generates copy-only upgrade commands.",
5
+ "type": "module",
6
+ "main": "lib/index.js",
7
+ "types": "lib/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./lib/index.d.ts",
11
+ "default": "./lib/index.js"
12
+ },
13
+ "./client": "./lib/client.js",
14
+ "./package.json": "./package.json"
15
+ },
16
+ "files": [
17
+ "lib/index.js",
18
+ "lib/index.d.ts",
19
+ "lib/client.js",
20
+ "lib/client.js.map",
21
+ "cordis.patch.yml",
22
+ "README.md",
23
+ "README.zh.md",
24
+ "CHANGELOG.md",
25
+ "LICENSE",
26
+ "assets/update-panel.png",
27
+ "docs/RELEASING.md"
28
+ ],
29
+ "engines": {
30
+ "node": ">=20"
31
+ },
32
+ "scripts": {
33
+ "build": "tsdown",
34
+ "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.tests.json",
35
+ "test": "pnpm run typecheck && vitest run",
36
+ "test:unit": "vitest run",
37
+ "clean": "rm -rf lib coverage",
38
+ "prepack": "pnpm run build",
39
+ "verify": "pnpm run test && pnpm run build && pnpm pack --dry-run"
40
+ },
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/idoall/dsh-update-status.git"
44
+ },
45
+ "bugs": {
46
+ "url": "https://github.com/idoall/dsh-update-status/issues"
47
+ },
48
+ "homepage": "https://github.com/idoall/dsh-update-status#readme",
49
+ "publishConfig": {
50
+ "access": "public"
51
+ },
52
+ "dsh": {
53
+ "bundle": {
54
+ "patch": "./cordis.patch.yml"
55
+ },
56
+ "client": {
57
+ "platform": "web",
58
+ "inject": [
59
+ "@deepseek-ai/dsh-client-connection",
60
+ "@deepseek-ai/dsh-client-ui-layout",
61
+ "@deepseek-ai/dsh-client-ui-sidebar",
62
+ "@deepseek-ai/dsh-client-ui-slots",
63
+ "@deepseek-ai/dsh-client-ui-settings",
64
+ "@deepseek-ai/dsh-client-ui-settings-general"
65
+ ]
66
+ },
67
+ "compatibility": {
68
+ "dshReleases": {
69
+ "0.1.2-rc.1": "compatible"
70
+ }
71
+ }
72
+ },
73
+ "keywords": [
74
+ "deepseek-harness",
75
+ "dsh",
76
+ "cordis",
77
+ "plugin",
78
+ "update",
79
+ "version"
80
+ ],
81
+ "license": "MIT",
82
+ "dependencies": {
83
+ "@deepseek-ai/schemastery": "^3.18.2"
84
+ },
85
+ "peerDependencies": {
86
+ "@deepseek-ai/cordis": "^4.0.2",
87
+ "@deepseek-ai/dsh-settings": "0.1.2-rc.1"
88
+ },
89
+ "devDependencies": {
90
+ "@deepseek-ai/cordis": "^4.0.2",
91
+ "@deepseek-ai/dsh-settings": "0.1.2-rc.1",
92
+ "@deepseek-ai/schemastery": "^3.18.2",
93
+ "@types/node": "^26.2.0",
94
+ "@types/react": "^18.3.31",
95
+ "react": "^18.3.1",
96
+ "tsdown": "^0.22.14",
97
+ "typescript": "^7.0.2",
98
+ "vitest": "^4.1.11"
99
+ },
100
+ "packageManager": "pnpm@10.33.1"
101
+ }