@dennisrongo/dsh-weather 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/README.md ADDED
@@ -0,0 +1,32 @@
1
+ # dsh-weather
2
+
3
+ Weather bar for the [DeepSeek Harness](https://github.com/deepseek-ai/dsh) web UI — current conditions, a short hourly outlook, humidity and wind, pinned to the bottom-center of the page.
4
+
5
+ - **Data:** [Open-Meteo](https://open-meteo.com) (free, no API key) via the browser.
6
+ - **Location:** `localStorage["dsh-weather:location"] = "Your City"` if set, else coarse IP geolocation (ipapi.co), else New York.
7
+ - **Mount point:** additive `shell.overlay` slot — pure-consumer client plugin, empty host half.
8
+ - **Units:** Fahrenheit by default — click the temperature to toggle °F/°C. The choice is remembered in `localStorage["dsh-weather:unit"]`.
9
+ - **Refresh:** every 15 min, plus a manual ⟳ button.
10
+
11
+ ## Build
12
+
13
+ ```sh
14
+ pnpm install
15
+ pnpm build
16
+ pnpm test
17
+ ```
18
+
19
+ ## Use in a dsh profile
20
+
21
+ ```json
22
+ { "dependencies": { "@dennisrongo/dsh-weather": "file:../dsh-weather" } }
23
+ ```
24
+
25
+ ```yaml
26
+ # cordis.patch.yml
27
+ - insert:
28
+ - id: dsh-weather
29
+ name: '@dennisrongo/dsh-weather'
30
+ ```
31
+
32
+ Restart the profile; the bar appears at the bottom of the web UI.
@@ -0,0 +1,7 @@
1
+ # dsh bundle patch: inserts this plugin into a profile's layer stack, so
2
+ # `dsh plugin add` mounts it with no hand-editing of the profile's own
3
+ # cordis.patch.yml. Both id: and name: are required — a bare id: is an
4
+ # id-targeted override of an existing row and silently no-ops.
5
+ - insert:
6
+ - id: dsh-weather
7
+ name: '@dennisrongo/dsh-weather'
package/lib/client.js ADDED
@@ -0,0 +1,405 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@dennisrongo/dsh-weather",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ "use strict";
7
+ var __create = Object.create;
8
+ var __defProp = Object.defineProperty;
9
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
10
+ var __getOwnPropNames = Object.getOwnPropertyNames;
11
+ var __getProtoOf = Object.getPrototypeOf;
12
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
13
+ var __export = (target, all) => {
14
+ for (var name in all)
15
+ __defProp(target, name, { get: all[name], enumerable: true });
16
+ };
17
+ var __copyProps = (to, from, except, desc) => {
18
+ if (from && typeof from === "object" || typeof from === "function") {
19
+ for (let key of __getOwnPropNames(from))
20
+ if (!__hasOwnProp.call(to, key) && key !== except)
21
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
22
+ }
23
+ return to;
24
+ };
25
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
26
+ // If the importer is in node compatibility mode or this is not an ESM
27
+ // file that has been converted to a CommonJS file using a Babel-
28
+ // compatible transform (i.e. "__esModule" has not been set), then set
29
+ // "default" to the CommonJS "module.exports" for node compatibility.
30
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
31
+ mod
32
+ ));
33
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
34
+
35
+ // src/client.tsx
36
+ var client_exports = {};
37
+ __export(client_exports, {
38
+ apply: () => apply,
39
+ describeCode: () => describeCode,
40
+ fmtHour: () => fmtHour,
41
+ fmtTemp: () => fmtTemp,
42
+ inject: () => inject,
43
+ toF: () => toF
44
+ });
45
+ module.exports = __toCommonJS(client_exports);
46
+ var import_react = __toESM(require("react"), 1);
47
+ var import_jsx_runtime = require("react/jsx-runtime");
48
+ var inject = ["slots"];
49
+ function describeCode(code, isDay = true) {
50
+ const d = isDay;
51
+ if (code === 0) return { icon: d ? "\u2600\uFE0F" : "\u{1F319}", label: "Clear" };
52
+ if (code === 1) return { icon: d ? "\u{1F324}\uFE0F" : "\u{1F319}", label: "Mostly clear" };
53
+ if (code === 2) return { icon: "\u26C5", label: "Partly cloudy" };
54
+ if (code === 3) return { icon: "\u2601\uFE0F", label: "Overcast" };
55
+ if (code === 45 || code === 48) return { icon: "\u{1F32B}\uFE0F", label: "Fog" };
56
+ if (code >= 51 && code <= 55) return { icon: "\u{1F326}\uFE0F", label: "Drizzle" };
57
+ if (code >= 56 && code <= 57) return { icon: "\u{1F327}\uFE0F", label: "Freezing drizzle" };
58
+ if (code >= 61 && code <= 65) return { icon: "\u{1F327}\uFE0F", label: "Rain" };
59
+ if (code >= 66 && code <= 67) return { icon: "\u{1F327}\uFE0F", label: "Freezing rain" };
60
+ if (code >= 71 && code <= 77) return { icon: "\u{1F328}\uFE0F", label: "Snow" };
61
+ if (code >= 80 && code <= 82) return { icon: "\u{1F326}\uFE0F", label: "Showers" };
62
+ if (code >= 85 && code <= 86) return { icon: "\u{1F328}\uFE0F", label: "Snow showers" };
63
+ if (code === 95) return { icon: "\u26C8\uFE0F", label: "Thunderstorm" };
64
+ if (code >= 96 && code <= 99) return { icon: "\u26C8\uFE0F", label: "Thunderstorm + hail" };
65
+ return { icon: "\u{1F321}\uFE0F", label: `Code ${code}` };
66
+ }
67
+ function fmtHour(iso) {
68
+ const h = Number(iso.slice(11, 13));
69
+ if (Number.isNaN(h)) return iso;
70
+ const suffix = h >= 12 ? "pm" : "am";
71
+ const hr = h % 12 === 0 ? 12 : h % 12;
72
+ return `${hr}${suffix}`;
73
+ }
74
+ function toF(celsius) {
75
+ return celsius * 9 / 5 + 32;
76
+ }
77
+ function fmtTemp(celsius, unit, withUnit = true) {
78
+ const value = Math.round(unit === "F" ? toF(celsius) : celsius);
79
+ return withUnit ? `${value}\xB0${unit}` : `${value}\xB0`;
80
+ }
81
+ var REFRESH_MS = 15 * 60 * 1e3;
82
+ var LOCATION_KEY = "dsh-weather:location";
83
+ var UNIT_KEY = "dsh-weather:unit";
84
+ function loadUnit() {
85
+ try {
86
+ return window.localStorage.getItem(UNIT_KEY) === "C" ? "C" : "F";
87
+ } catch {
88
+ return "F";
89
+ }
90
+ }
91
+ function saveUnit(unit) {
92
+ try {
93
+ window.localStorage.setItem(UNIT_KEY, unit);
94
+ } catch {
95
+ }
96
+ }
97
+ var DEFAULT_LOCATION = { latitude: 40.7128, longitude: -74.006, label: "New York" };
98
+ var GEO_TIMEOUT_MS = 4e3;
99
+ var GEO_PROVIDERS = [
100
+ {
101
+ url: "https://get.geojs.io/v1/ip/geo.json",
102
+ parse: (d) => {
103
+ const latitude = Number(d?.latitude);
104
+ const longitude = Number(d?.longitude);
105
+ if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) return null;
106
+ const region = d?.region ?? d?.country_code ?? "";
107
+ return { latitude, longitude, label: d?.city ? `${d.city}, ${region}`.replace(/, $/, "") : "Current location" };
108
+ }
109
+ },
110
+ {
111
+ url: "https://freeipapi.com/api/json",
112
+ parse: (d) => {
113
+ const latitude = Number(d?.latitude);
114
+ const longitude = Number(d?.longitude);
115
+ if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) return null;
116
+ const region = d?.regionName ?? d?.countryCode ?? "";
117
+ return { latitude, longitude, label: d?.cityName ? `${d.cityName}, ${region}`.replace(/, $/, "") : "Current location" };
118
+ }
119
+ }
120
+ ];
121
+ async function fetchJson(url, timeoutMs = GEO_TIMEOUT_MS) {
122
+ const ctrl = new AbortController();
123
+ const timer = window.setTimeout(() => ctrl.abort(), timeoutMs);
124
+ try {
125
+ const res = await fetch(url, { signal: ctrl.signal });
126
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
127
+ return await res.json();
128
+ } finally {
129
+ window.clearTimeout(timer);
130
+ }
131
+ }
132
+ async function resolveLocation() {
133
+ const override = window.localStorage.getItem(LOCATION_KEY);
134
+ if (override) {
135
+ const q = encodeURIComponent(override);
136
+ const data = await fetchJson(
137
+ `https://geocoding-api.open-meteo.com/v1/search?name=${q}&count=1&language=en&format=json`
138
+ );
139
+ const hit = data?.results?.[0];
140
+ if (hit) {
141
+ return {
142
+ latitude: hit.latitude,
143
+ longitude: hit.longitude,
144
+ label: hit.admin1 ? `${hit.name}, ${hit.admin1}` : String(hit.name)
145
+ };
146
+ }
147
+ throw new Error(`unknown place "${override}"`);
148
+ }
149
+ for (const provider of GEO_PROVIDERS) {
150
+ try {
151
+ const hit = provider.parse(await fetchJson(provider.url));
152
+ if (hit) return hit;
153
+ } catch {
154
+ }
155
+ }
156
+ return DEFAULT_LOCATION;
157
+ }
158
+ async function fetchWeather() {
159
+ const geo = await resolveLocation();
160
+ const url = `https://api.open-meteo.com/v1/forecast?latitude=${geo.latitude}&longitude=${geo.longitude}&current=temperature_2m,apparent_temperature,relative_humidity_2m,is_day,weather_code,wind_speed_10m&hourly=temperature_2m,weather_code&forecast_days=2&wind_speed_unit=kmh&timezone=auto`;
161
+ const res = await fetch(url);
162
+ if (!res.ok) throw new Error(`open-meteo HTTP ${res.status}`);
163
+ const data = await res.json();
164
+ const c = data.current;
165
+ const times = data.hourly?.time ?? [];
166
+ const temps = data.hourly?.temperature_2m ?? [];
167
+ const codes = data.hourly?.weather_code ?? [];
168
+ const nowMs = Date.now();
169
+ const next = [];
170
+ for (let i = 0; i < times.length && next.length < 6; i++) {
171
+ const at = Date.parse(times[i]);
172
+ if (Number.isNaN(at) || at < nowMs - 30 * 60 * 1e3) continue;
173
+ if (at === next[next.length - 1]?.at) continue;
174
+ next.push({ at, temperatureC: temps[i], weatherCode: codes[i] });
175
+ }
176
+ return {
177
+ status: "ready",
178
+ where: geo.label,
179
+ now: {
180
+ temperatureC: c.temperature_2m,
181
+ apparentC: c.apparent_temperature,
182
+ weatherCode: c.weather_code,
183
+ isDay: c.is_day === 1,
184
+ windKph: c.wind_speed_10m,
185
+ humidity: c.relative_humidity_2m
186
+ },
187
+ next: next.filter((_, i) => i % 2 === 0).slice(0, 4),
188
+ fetchedAt: nowMs
189
+ };
190
+ }
191
+ var BAR_STYLES = `
192
+ .dshwx {
193
+ position: fixed;
194
+ left: 50%;
195
+ transform: translateX(-50%);
196
+ top: 8px;
197
+ z-index: 2147482900;
198
+ pointer-events: auto;
199
+ display: flex;
200
+ align-items: center;
201
+ gap: 10px;
202
+ max-width: calc(100vw - 32px);
203
+ padding: 5px 14px;
204
+ border-radius: 999px;
205
+ border: 1px solid var(--dsw-alias-border-l2, rgba(255,255,255,0.12));
206
+ background: var(--dsw-specific-sidebar-fill, #1b1b1c);
207
+ color: var(--dsw-alias-label-secondary, #cfd3d6);
208
+ font: 400 12px/1.4 var(--dsw-font-family, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif);
209
+ font-variant-numeric: tabular-nums;
210
+ box-shadow: var(--dsw-shadow-lv3, 0 0 1px rgba(0,0,0,0.2), 0 8px 24px rgba(0,0,0,0.12));
211
+ cursor: default;
212
+ user-select: none;
213
+ white-space: nowrap;
214
+ }
215
+ body[data-ds-dark-theme] .dshwx { box-shadow: 0 0 0 1px rgba(0,0,0,0.5), 0 8px 24px rgba(0,0,0,0.5); }
216
+ .dshwx[hidden] { display: none; }
217
+ .dshwx-icon { font-size: 15px; }
218
+ .dshwx-temp {
219
+ color: var(--dsw-alias-label-primary, #f9fafb);
220
+ font-weight: 600; font-size: 13px;
221
+ font-family: inherit; font-variant-numeric: tabular-nums;
222
+ border: 0; background: transparent; padding: 1px 4px; margin: 0 -2px;
223
+ border-radius: 6px; cursor: pointer; line-height: inherit;
224
+ }
225
+ .dshwx-temp:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,0.08)); }
226
+ .dshwx-temp:focus-visible { outline: 2px solid var(--dsw-alias-label-caption, #81858c); outline-offset: 1px; }
227
+ .dshwx-label { color: var(--dsw-alias-label-secondary, #cfd3d6); }
228
+ .dshwx-where {
229
+ color: var(--dsw-alias-label-tertiary, #adb2b8);
230
+ max-width: 160px; overflow: hidden; text-overflow: ellipsis;
231
+ }
232
+ .dshwx-sep { width: 1px; height: 14px; background: var(--dsw-alias-border-l2, rgba(255,255,255,0.12)); flex: none; }
233
+ .dshwx-meta { color: var(--dsw-alias-label-caption, #81858c); font-size: 11px; }
234
+ .dshwx-hours { display: flex; gap: 8px; align-items: center; }
235
+ .dshwx-hour { display: flex; align-items: center; gap: 3px; color: var(--dsw-alias-label-caption, #81858c); font-size: 11px; }
236
+ .dshwx-hour b { color: var(--dsw-alias-label-secondary, #cfd3d6); font-weight: 500; }
237
+ .dshwx-refresh {
238
+ border: 0; background: transparent; cursor: pointer;
239
+ color: var(--dsw-alias-label-caption, #81858c);
240
+ font-size: 12px; padding: 2px; border-radius: 50%;
241
+ display: grid; place-items: center;
242
+ }
243
+ .dshwx-refresh:hover { color: var(--dsw-alias-label-primary, #f9fafb); background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,0.08)); }
244
+ .dshwx-refresh.busy { animation: dshwx-spin 0.9s linear infinite; }
245
+ @keyframes dshwx-spin { to { transform: rotate(360deg); } }
246
+ .dshwx-error { color: var(--dsw-alias-state-error-primary, #ef4444); font-size: 11.5px; }
247
+ /* --- Responsive tiers ---------------------------------------------------
248
+ The bar is a single nowrap pill, so narrow screens shed detail rather than
249
+ wrap. Each tier also drops the separator that preceded the hidden group,
250
+ otherwise stray dividers float with nothing between them. */
251
+
252
+ /* Tablet: drop the hourly outlook and the humidity/wind readout. */
253
+ @media (max-width: 720px) {
254
+ .dshwx-hours, .dshwx-meta { display: none; }
255
+ .dshwx-sep-hours, .dshwx-sep-meta { display: none; }
256
+ }
257
+
258
+ /* Phone: tighten spacing, shrink the place name, and give the controls
259
+ touch-sized hit areas without changing the pill's visual weight. */
260
+ @media (max-width: 520px) {
261
+ .dshwx {
262
+ gap: 7px;
263
+ padding: 4px 10px;
264
+ max-width: calc(100vw - 16px);
265
+ font-size: 11.5px;
266
+ }
267
+ .dshwx-where { max-width: 92px; }
268
+ .dshwx-icon { font-size: 14px; }
269
+ .dshwx-temp { font-size: 12.5px; padding: 5px 7px; margin: -4px -3px; }
270
+ .dshwx-refresh { padding: 7px; margin: -5px; }
271
+ }
272
+
273
+ /* Very narrow: the place name is the least load-bearing text \u2014 the icon,
274
+ temperature and condition carry the meaning. */
275
+ @media (max-width: 380px) {
276
+ .dshwx-where, .dshwx-sep-where { display: none; }
277
+ .dshwx { gap: 6px; }
278
+ }
279
+
280
+ /* Coarse pointers (touch) get the larger hit areas at any width. */
281
+ @media (pointer: coarse) {
282
+ .dshwx-temp { padding: 6px 8px; margin: -4px -4px; }
283
+ .dshwx-refresh { padding: 8px; margin: -6px; }
284
+ }
285
+
286
+ @media (prefers-reduced-motion: reduce) {
287
+ .dshwx-refresh.busy { animation: none; }
288
+ }
289
+ `;
290
+ var stylesInjected = false;
291
+ function injectStyles() {
292
+ if (stylesInjected) return;
293
+ stylesInjected = true;
294
+ const tag = document.createElement("style");
295
+ tag.dataset.plugin = "@dennisrongo/dsh-weather";
296
+ tag.textContent = BAR_STYLES;
297
+ document.head.appendChild(tag);
298
+ }
299
+ function WeatherBar() {
300
+ const [state, setState] = import_react.default.useState({ status: "loading" });
301
+ const [busy, setBusy] = import_react.default.useState(false);
302
+ const [unit, setUnit] = import_react.default.useState(loadUnit);
303
+ const toggleUnit = () => {
304
+ setUnit((prev) => {
305
+ const next = prev === "C" ? "F" : "C";
306
+ saveUnit(next);
307
+ return next;
308
+ });
309
+ };
310
+ import_react.default.useEffect(() => injectStyles(), []);
311
+ import_react.default.useEffect(() => {
312
+ let alive = true;
313
+ const load = async () => {
314
+ setBusy(true);
315
+ try {
316
+ const next = await fetchWeather();
317
+ if (alive) setState(next);
318
+ } catch (e) {
319
+ if (alive) setState({ status: "error", error: String(e?.message ?? e) });
320
+ } finally {
321
+ if (alive) setBusy(false);
322
+ }
323
+ };
324
+ void load();
325
+ const timer = window.setInterval(load, REFRESH_MS);
326
+ return () => {
327
+ alive = false;
328
+ window.clearInterval(timer);
329
+ };
330
+ }, []);
331
+ const reload = () => {
332
+ setBusy(true);
333
+ fetchWeather().then(setState).catch((e) => setState({ status: "error", error: String(e?.message ?? e) })).finally(() => setBusy(false));
334
+ };
335
+ if (state.status === "loading") {
336
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dshwx", "aria-live": "polite", children: [
337
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dshwx-icon", children: "\u{1F321}\uFE0F" }),
338
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dshwx-label", children: "Loading weather\u2026" })
339
+ ] });
340
+ }
341
+ if (state.status === "error" || !state.now) {
342
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dshwx", "aria-live": "polite", children: [
343
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dshwx-icon", children: "\u26A0\uFE0F" }),
344
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dshwx-error", title: state.error, children: "Weather unavailable" }),
345
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: `dshwx-refresh${busy ? " busy" : ""}`, title: "Retry", onClick: reload, children: "\u27F3" })
346
+ ] });
347
+ }
348
+ const { icon, label } = describeCode(state.now.weatherCode, state.now.isDay);
349
+ const other = unit === "C" ? "F" : "C";
350
+ const title = `${label} in ${state.where ?? ""} \u2014 feels like ${fmtTemp(state.now.apparentC, unit)}, humidity ${state.now.humidity}%, wind ${Math.round(state.now.windKph)} km/h \xB7 click the temperature for \xB0${other}`;
351
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dshwx", title, "aria-live": "polite", children: [
352
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dshwx-icon", children: icon }),
353
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
354
+ "button",
355
+ {
356
+ type: "button",
357
+ className: "dshwx-temp",
358
+ onClick: toggleUnit,
359
+ title: `Switch to \xB0${other}`,
360
+ "aria-label": `Temperature ${fmtTemp(state.now.temperatureC, unit)}. Switch to degrees ${other === "F" ? "Fahrenheit" : "Celsius"}.`,
361
+ children: fmtTemp(state.now.temperatureC, unit)
362
+ }
363
+ ),
364
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dshwx-label", children: label }),
365
+ state.where ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
366
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dshwx-sep dshwx-sep-where" }),
367
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dshwx-where", children: state.where })
368
+ ] }) : null,
369
+ state.next && state.next.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
370
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dshwx-sep dshwx-sep-hours" }),
371
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dshwx-hours", children: state.next.map((h) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dshwx-hour", children: [
372
+ new Date(h.at).getHours() % 12 || 12,
373
+ new Date(h.at).getHours() >= 12 ? "pm" : "am",
374
+ " ",
375
+ describeCode(h.weatherCode).icon,
376
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("b", { children: fmtTemp(h.temperatureC, unit, false) })
377
+ ] }, h.at)) })
378
+ ] }) : null,
379
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dshwx-sep dshwx-sep-meta" }),
380
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dshwx-meta", children: [
381
+ "\u{1F4A7}",
382
+ state.now.humidity,
383
+ "% \u{1F32C}\uFE0F",
384
+ Math.round(state.now.windKph),
385
+ "km/h"
386
+ ] }),
387
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: `dshwx-refresh${busy ? " busy" : ""}`, title: "Refresh weather", onClick: reload, children: "\u27F3" })
388
+ ] });
389
+ }
390
+ function apply(ctx) {
391
+ ctx.effect(
392
+ () => ctx.slots.inject(
393
+ "shell.overlay",
394
+ () => ctx.slots.register(
395
+ { name: "shell.overlay", id: "dsh-weather" },
396
+ () => import_react.default.createElement(WeatherBar)
397
+ )
398
+ ),
399
+ "dsh-weather: shell.overlay registration"
400
+ );
401
+ }
402
+
403
+ return module.exports;
404
+ }
405
+ });
package/lib/index.js ADDED
@@ -0,0 +1,6 @@
1
+ // src/index.ts
2
+ function apply() {
3
+ }
4
+ export {
5
+ apply
6
+ };
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@dennisrongo/dsh-weather",
3
+ "version": "0.1.0",
4
+ "description": "Weather bar for DeepSeek Harness (dsh) — current conditions at the bottom of the web UI via Open-Meteo, rendered into shell.overlay",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Dennis Rongo",
8
+ "main": "lib/index.js",
9
+ "types": "lib/types/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./lib/types/index.d.ts",
13
+ "default": "./lib/index.js"
14
+ },
15
+ "./client": {
16
+ "default": "./lib/client.js"
17
+ },
18
+ "./package.json": "./package.json"
19
+ },
20
+ "files": [
21
+ "lib",
22
+ "!lib/client.body.cjs",
23
+ "!lib/*.test.mjs",
24
+ "README.md",
25
+ "cordis.patch.yml"
26
+ ],
27
+ "scripts": {
28
+ "build": "node build/build.mjs",
29
+ "postbuild": "node build/postbuild.mjs",
30
+ "deploy": "node build/postbuild.mjs",
31
+ "typecheck": "tsc --noEmit",
32
+ "test": "node test/smoke.mjs"
33
+ },
34
+ "dsh": {
35
+ "bundle": {
36
+ "patch": "./cordis.patch.yml"
37
+ },
38
+ "client": {
39
+ "platform": "web",
40
+ "inject": [
41
+ "@deepseek-ai/dsh-client-runtime"
42
+ ],
43
+ "immediately": true
44
+ }
45
+ },
46
+ "devDependencies": {
47
+ "@types/react": "^18.3.0",
48
+ "esbuild": "^0.24.0",
49
+ "react": "^18.3.0",
50
+ "react-dom": "^18.3.0",
51
+ "typescript": "^5.6.0"
52
+ },
53
+ "repository": {
54
+ "type": "git",
55
+ "url": "git+https://github.com/dennisrongo/dsh-plugins.git",
56
+ "directory": "plugins/dsh-weather"
57
+ },
58
+ "homepage": "https://github.com/dennisrongo/dsh-plugins/tree/main/plugins/dsh-weather#readme",
59
+ "bugs": {
60
+ "url": "https://github.com/dennisrongo/dsh-plugins/issues"
61
+ }
62
+ }