dsh-opencode-go-usage 1.1.0 → 1.2.1

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/client.js CHANGED
@@ -37,7 +37,8 @@ window.__ModuleLoader__.load({
37
37
  ".ocg-fill{height:100%;border-radius:3px;transition:width .4s ease}",
38
38
  ".ocg-err{font-size:11px;line-height:16px;color:var(--dsw-alias-label-secondary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
39
39
  ".ocg-rail{box-sizing:border-box;width:36px;height:36px;display:flex;align-items:center;justify-content:center;border-radius:8px;font-size:10px;font-weight:700;color:var(--dsw-alias-label-primary);cursor:default}",
40
- ".ocg-rail:hover{background:var(--dsw-alias-interactive-bg-hover)}"
40
+ ".ocg-rail:hover{background:var(--dsw-alias-interactive-bg-hover)}",
41
+ ".ocg-update{display:inline-block;padding:1px 6px;border-radius:8px;font-size:10px;line-height:16px;font-weight:600;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover);text-decoration:none;cursor:pointer;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:110px}"
41
42
  ].join("");
42
43
  document.head.appendChild(tag);
43
44
  }
@@ -45,7 +46,7 @@ window.__ModuleLoader__.load({
45
46
  // ── locale dictionaries ──
46
47
  const NS = "dsh-opencode-go-usage";
47
48
  const zh = {
48
- "window.rolling": "滚动窗口",
49
+ "window.rolling": "滚动窗口 (5h)",
49
50
  "window.weekly": "周窗口",
50
51
  "window.monthly": "月窗口",
51
52
  "window.rolling.hint": "5 小时",
@@ -57,7 +58,9 @@ window.__ModuleLoader__.load({
57
58
  "refresh": "刷新",
58
59
  "loading": "加载中…",
59
60
  "rail.title": "OpenCode GO 月用量 {pct}%",
60
- "rail.plain": "OpenCode GO"
61
+ "rail.plain": "OpenCode GO",
62
+ "update.available": "新版本 v{v}",
63
+ "update.title": "点击查看升级说明"
61
64
  };
62
65
  const en = {
63
66
  "window.rolling": "Rolling (5h)",
@@ -72,7 +75,9 @@ window.__ModuleLoader__.load({
72
75
  "refresh": "Refresh",
73
76
  "loading": "Loading…",
74
77
  "rail.title": "OpenCode GO monthly {pct}%",
75
- "rail.plain": "OpenCode GO"
78
+ "rail.plain": "OpenCode GO",
79
+ "update.available": "v{v} available",
80
+ "update.title": "Click for upgrade instructions"
76
81
  };
77
82
 
78
83
  // ── data helpers ──
@@ -194,6 +199,16 @@ window.__ModuleLoader__.load({
194
199
  className: "ocg-head",
195
200
  children: [
196
201
  react_jsx_runtime.jsx("span", { children: "OpenCode GO" }),
202
+ (data && data.update && data.update.available && data.update.latest
203
+ ? react_jsx_runtime.jsx("a", {
204
+ className: "ocg-update",
205
+ href: "https://www.npmjs.com/package/dsh-opencode-go-usage",
206
+ target: "_blank",
207
+ rel: "noreferrer",
208
+ title: t("update.title"),
209
+ children: t("update.available", { v: data.update.latest })
210
+ })
211
+ : null),
197
212
  react_jsx_runtime.jsx("button", {
198
213
  className: "ocg-refresh",
199
214
  onClick: () => setStamp(stamp + 1),
package/lib/index.js CHANGED
@@ -23,9 +23,12 @@
23
23
  * settings.
24
24
  *
25
25
  * Settings namespace `dsh-opencode-go-usage`:
26
- * - apiKeyEnv: credential ref / env var for the API key (default OPENCODE_GO_API_KEY)
27
- * - baseUrl: GO gateway base (default https://opencode.ai/zen/go)
28
- * - cacheMs: host-side cache TTL (default 30000)
26
+ * - apiKeyEnv: credential ref / env var for the API key (default OPENCODE_GO_API_KEY)
27
+ * - baseUrl: GO gateway base (default https://opencode.ai/zen/go)
28
+ * - cacheMs: host-side cache TTL (default 30000)
29
+ * - updateCheck: check npm for newer versions (default true); result shows
30
+ * in the widget and the command — the plugin never installs
31
+ * itself, upgrading stays an explicit user action.
29
32
  */
30
33
 
31
34
  import { readFileSync } from "node:fs";
@@ -43,12 +46,43 @@ export const namespace = "dsh-opencode-go-usage";
43
46
  const DEFAULT_BASE_URL = "https://opencode.ai/zen/go";
44
47
  const DEFAULT_API_KEY_ENV = "OPENCODE_GO_API_KEY";
45
48
  const DEFAULT_CACHE_MS = 30_000;
49
+ /** npm package name this plugin is published under (update checks). */
50
+ const NPM_PACKAGE = "dsh-opencode-go-usage";
51
+ /** How often the update check may hit the npm registry (ms). */
52
+ const UPDATE_CHECK_INTERVAL_MS = 24 * 3600_000;
53
+
54
+ /** The version this running copy was installed as (read once at load). */
55
+ const CURRENT_VERSION = readPackageVersion();
56
+
57
+ /** Read `version` from the installed package.json next to this file. */
58
+ function readPackageVersion() {
59
+ try {
60
+ const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
61
+ if (typeof pkg?.version === "string") return pkg.version;
62
+ } catch {
63
+ // fall through
64
+ }
65
+ return "0.0.0";
66
+ }
67
+
68
+ /** Compare two `x.y.z` version strings; returns true when `a` is newer than `b`. */
69
+ function isNewer(a, b) {
70
+ const pa = String(a).split("-")[0].split(".").map(Number);
71
+ const pb = String(b).split("-")[0].split(".").map(Number);
72
+ for (let i = 0; i < 3; i++) {
73
+ const na = pa[i] ?? 0;
74
+ const nb = pb[i] ?? 0;
75
+ if (na !== nb) return na > nb;
76
+ }
77
+ return false;
78
+ }
46
79
 
47
80
  /** Settings schema for this plugin's namespace. */
48
81
  export const Config = z.object({
49
82
  apiKeyEnv: z.string().default(DEFAULT_API_KEY_ENV),
50
83
  baseUrl: z.string().default(DEFAULT_BASE_URL),
51
- cacheMs: z.number().default(DEFAULT_CACHE_MS)
84
+ cacheMs: z.number().default(DEFAULT_CACHE_MS),
85
+ updateCheck: z.boolean().default(true)
52
86
  });
53
87
 
54
88
  /**
@@ -150,10 +184,50 @@ export function apply(ctx, rawConfig = {}) {
150
184
  apiKeyEnv: stored.apiKeyEnv,
151
185
  baseUrl: stored.baseUrl,
152
186
  cacheMs: stored.cacheMs,
187
+ updateCheck: stored.updateCheck !== false,
153
188
  locale
154
189
  };
155
190
  };
156
191
 
192
+ // ── update check (npm) ─────────────────────────────────────────────────
193
+ // Checks the registry at most once per UPDATE_CHECK_INTERVAL_MS; failures
194
+ // are silent. The plugin only reports — upgrading stays a user action
195
+ // (`dsh plugin --profile web add dsh-opencode-go-usage@latest`).
196
+ /** @type {{ at: number, latest: string | null } | null} */
197
+ let updateState = null;
198
+ const checkForUpdate = async (config) => {
199
+ const now = Date.now();
200
+ if (updateState !== null && now - updateState.at < UPDATE_CHECK_INTERVAL_MS) return updateState;
201
+ const snapshot = { at: now, latest: null };
202
+ try {
203
+ const res = await fetch(`https://registry.npmjs.org/${NPM_PACKAGE}/latest`, {
204
+ signal: AbortSignal.timeout(10_000)
205
+ });
206
+ if (res.ok) {
207
+ const json = await res.json();
208
+ if (typeof json?.version === "string" && isNewer(json.version, CURRENT_VERSION)) {
209
+ snapshot.latest = json.version;
210
+ }
211
+ }
212
+ } catch {
213
+ // offline / registry hiccup — keep the previous result, refresh later
214
+ snapshot.at = updateState?.at ?? now;
215
+ snapshot.latest = updateState?.latest ?? null;
216
+ }
217
+ updateState = snapshot;
218
+ return snapshot;
219
+ };
220
+ const updateInfoOf = (config) => {
221
+ const st = updateState;
222
+ if (!config.updateCheck || st === null || st.latest === null) {
223
+ return { current: CURRENT_VERSION, available: false, latest: null };
224
+ }
225
+ return { current: CURRENT_VERSION, available: true, latest: st.latest };
226
+ };
227
+
228
+ // Kick off the first check shortly after boot (non-blocking).
229
+ void checkForUpdate(readConfig()).catch(() => {});
230
+
157
231
  // Host-side cache: one in-flight promise + a TTL, so several open tabs or
158
232
  // the command never hammer the gateway.
159
233
  /** @type {{ at: number, promise: Promise<unknown> } | null} */
@@ -180,8 +254,12 @@ export function apply(ctx, rawConfig = {}) {
180
254
  res.setHeader("content-type", "application/json; charset=utf-8");
181
255
  res.setHeader("cache-control", "no-store");
182
256
  try {
257
+ const config = readConfig();
258
+ // Trigger a background refresh when the interval elapsed (never
259
+ // block the response on the registry).
260
+ void checkForUpdate(config).catch(() => {});
183
261
  const data = await quotaOnce();
184
- res.end(JSON.stringify(data));
262
+ res.end(JSON.stringify({ ...data, update: updateInfoOf(config) }));
185
263
  } catch (error) {
186
264
  res.statusCode = 502;
187
265
  res.end(JSON.stringify({ error: error instanceof Error ? error.message : String(error) }));
@@ -235,8 +313,16 @@ export function apply(ctx, rawConfig = {}) {
235
313
  description: "show OpenCode GO plan usage (rolling/weekly/monthly windows)",
236
314
  handler: async () => {
237
315
  try {
316
+ const config = readConfig();
238
317
  const data = await quotaOnce();
239
- return { kind: "success", text: renderUsageText(data, readConfig().locale) };
318
+ let text = renderUsageText(data, config.locale);
319
+ const update = updateInfoOf(config);
320
+ if (update.available && update.latest !== null) {
321
+ text += config.locale === "en"
322
+ ? `\n\nUpdate available: v${update.latest} — run \`dsh plugin --profile web add ${NPM_PACKAGE}@latest\` to upgrade.`
323
+ : `\n\n发现新版本 v${update.latest} — 执行 \`dsh plugin --profile web add ${NPM_PACKAGE}@latest\` 升级。`;
324
+ }
325
+ return { kind: "success", text };
240
326
  } catch (error) {
241
327
  return { kind: "error", text: error instanceof Error ? error.message : String(error) };
242
328
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-opencode-go-usage",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "description": "DSH (DeepSeek Harness) plugin: OpenCode GO plan quota widget in the sidebar, same-origin usage proxy, and a /opencode-go chat command",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",