dsh-workbuddy-xdpool 1.4.1 → 1.5.2
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/CHANGELOG.md +105 -0
- package/lib/bin.js +523 -25
- package/lib/client.js +89 -30
- package/lib/index.d.ts +15 -2
- package/lib/index.js +568 -42
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -255,39 +255,44 @@ window.__ModuleLoader__.load({
|
|
|
255
255
|
.dsm-workbuddy-xdpool-auto{margin-top:12px;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2,#36373b);border-radius:10px;background:var(--dsw-alias-bg-module-platform,#202126)}
|
|
256
256
|
.dsm-workbuddy-xdpool-auto-head{display:flex;align-items:center;justify-content:space-between;gap:8px}
|
|
257
257
|
.dsm-workbuddy-xdpool-auto-title{color:var(--dsw-alias-label-primary,#e8e8ea);font-size:12px;font-weight:600}
|
|
258
|
-
.dsm-workbuddy-xdpool-auto-switch{padding:3px 12px;border:1px solid var(--dsw-alias-border-l2,#36373b);border-radius:999px;background:transparent;color:var(--dsw-alias-label-
|
|
258
|
+
.dsm-workbuddy-xdpool-auto-switch{padding:3px 12px;border:1px solid var(--dsw-alias-border-l2,#36373b);border-radius:999px;background:transparent;color:var(--dsw-alias-label-secondary,#c6c9d0);font-size:12px;cursor:pointer;transition:color .16s,border-color .16s,background .16s}
|
|
259
259
|
.dsm-workbuddy-xdpool-auto-switch:hover:not(:disabled){border-color:var(--dsw-alias-label-dimmed,#777);color:var(--dsw-alias-label-primary,#e8e8ea)}
|
|
260
260
|
.dsm-workbuddy-xdpool-auto-switch:disabled{opacity:.5;cursor:not-allowed}
|
|
261
261
|
.dsm-workbuddy-xdpool-auto-switch-on{border-color:#28c8b4;color:#28c8b4;background:rgba(40,200,180,.12)}
|
|
262
|
-
.dsm-workbuddy-xdpool-auto-hint{margin:6px 0 0;color:var(--dsw-alias-label-
|
|
262
|
+
.dsm-workbuddy-xdpool-auto-hint{margin:6px 0 0;color:var(--dsw-alias-label-secondary,#c6c9d0);font-size:12px;line-height:1.6}
|
|
263
263
|
.dsm-workbuddy-xdpool-auto-total{display:flex;align-items:baseline;justify-content:space-between;gap:8px;margin-top:8px;padding:6px 8px;border-radius:8px;background:rgba(40,200,180,.08);font-size:11px}
|
|
264
|
-
.dsm-workbuddy-xdpool-auto-total-label{color:var(--dsw-alias-label-
|
|
264
|
+
.dsm-workbuddy-xdpool-auto-total-label{color:var(--dsw-alias-label-secondary,#c6c9d0);font-size:12px}
|
|
265
265
|
.dsm-workbuddy-xdpool-auto-total{display:flex;flex-direction:column;gap:4px;margin-top:8px;padding:6px 8px;border-radius:8px;background:rgba(40,200,180,.08);font-size:11px}
|
|
266
266
|
.dsm-workbuddy-xdpool-auto-total-list{display:flex;flex-direction:column;gap:2px}
|
|
267
267
|
.dsm-workbuddy-xdpool-auto-total-row{color:#28c8b4;font-variant-numeric:tabular-nums}
|
|
268
268
|
.dsm-workbuddy-xdpool-earned{display:flex;flex-direction:column;gap:4px;margin-top:8px;padding-top:8px;border-top:1px dashed var(--dsw-alias-border-l2,#36373b);font-size:11px}
|
|
269
|
-
.dsm-workbuddy-xdpool-earned-label{color:var(--dsw-alias-label-
|
|
269
|
+
.dsm-workbuddy-xdpool-earned-label{color:var(--dsw-alias-label-secondary,#c6c9d0);font-size:12px}
|
|
270
270
|
.dsm-workbuddy-xdpool-earned-list{display:flex;flex-direction:column;gap:2px}
|
|
271
271
|
.dsm-workbuddy-xdpool-earned-row{color:#28c8b4;font-variant-numeric:tabular-nums}
|
|
272
272
|
.dsm-workbuddy-xdpool-earned-value{color:#28c8b4;font-weight:600;font-variant-numeric:tabular-nums}
|
|
273
273
|
.dsm-workbuddy-xdpool-auto-jobs{margin-top:8px;display:flex;flex-direction:column;gap:4px}
|
|
274
|
-
.dsm-workbuddy-xdpool-auto-job{display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-size:
|
|
275
|
-
.dsm-workbuddy-xdpool-auto-job-name{flex:0 0 72px;color:var(--dsw-alias-label-primary,#e8e8ea)}
|
|
276
|
-
.dsm-workbuddy-xdpool-auto-job-when{flex:1 1 auto;font-variant-numeric:tabular-nums}
|
|
277
|
-
.dsm-workbuddy-xdpool-auto-job-last{flex:0 0 auto;text-align:right;font-variant-numeric:tabular-nums}
|
|
278
|
-
|
|
279
|
-
titles, which needs the whole row rather than a cramped right-hand slot. */
|
|
280
|
-
.dsm-workbuddy-xdpool-auto-job-detail{flex:1 1 100%;color:var(--dsw-alias-label-secondary,#b8bcc4);line-height:1.5;word-break:break-word}
|
|
281
|
-
.dsm-workbuddy-xdpool-auto-job-note{flex:0 0 auto;color:var(--dsw-alias-label-secondary,#b8bcc4)}
|
|
282
|
-
.dsm-workbuddy-xdpool-auto-run{flex:0 0 auto;padding:2px 8px;font-size:10px;line-height:16px}
|
|
274
|
+
.dsm-workbuddy-xdpool-auto-job{display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-size:12px;color:var(--dsw-alias-label-secondary,#c6c9d0)}
|
|
275
|
+
.dsm-workbuddy-xdpool-auto-job-name{flex:0 0 auto;min-width:72px;color:var(--dsw-alias-label-primary,#e8e8ea);font-weight:500}
|
|
276
|
+
.dsm-workbuddy-xdpool-auto-job-when{flex:1 1 auto;color:var(--dsw-alias-label-secondary,#c6c9d0);font-variant-numeric:tabular-nums}
|
|
277
|
+
.dsm-workbuddy-xdpool-auto-job-last{flex:0 0 auto;text-align:right;color:var(--dsw-alias-label-secondary,#c6c9d0);font-variant-numeric:tabular-nums;white-space:nowrap}
|
|
278
|
+
.dsm-workbuddy-xdpool-auto-job-note{flex:0 0 auto;color:var(--dsw-alias-label-secondary,#c6c9d0)}
|
|
283
279
|
/* Reserved credits: one inline number field per account. */
|
|
284
280
|
.dsm-workbuddy-xdpool-reserve{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-top:8px;padding-top:8px;border-top:1px dashed var(--dsw-alias-border-l2,#36373b);font-size:11px;color:var(--dsw-alias-label-dimmed,#8a97b5)}
|
|
285
281
|
.dsm-workbuddy-xdpool-reserve-label{flex:0 0 auto}
|
|
286
|
-
.dsm-workbuddy-xdpool-reserve-input{width:
|
|
282
|
+
.dsm-workbuddy-xdpool-reserve-input{width:84px;padding:3px 8px;border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:6px;background:transparent;color:var(--dsw-alias-label-primary,#e8e8ea);font:inherit;font-variant-numeric:tabular-nums;transition:border-color .16s,background .16s}
|
|
283
|
+
.dsm-workbuddy-xdpool-reserve-input:focus{outline:none;border-color:var(--dsw-alias-state-success-primary,#22a06b);background:color-mix(in oklab, var(--dsw-alias-state-success-primary,#22a06b) 7%, transparent)}
|
|
287
284
|
.dsm-workbuddy-xdpool-reserve-input:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:1px}
|
|
288
285
|
.dsm-workbuddy-xdpool-reserve-input:disabled{opacity:.6}
|
|
289
286
|
.dsm-workbuddy-xdpool-reserve-unit{flex:0 0 auto}
|
|
290
287
|
.dsm-workbuddy-xdpool-reserve-badge{flex:0 0 auto;padding:1px 7px;border-radius:999px;background:rgba(232,90,90,.14);color:#e85a5a;font-size:10px}
|
|
288
|
+
/* Explicit Save: the field is a draft, so the button (not a blur) is what
|
|
289
|
+
commits it — and the outcome is reported right here, next to the control. */
|
|
290
|
+
.dsm-workbuddy-xdpool-reserve-save{flex:0 0 auto;appearance:none;font:inherit;font-size:11px;font-weight:600;line-height:16px;padding:3px 12px;border-radius:6px;border:1px solid color-mix(in oklab, var(--dsw-alias-state-success-primary,#22a06b) 55%, transparent);background:color-mix(in oklab, var(--dsw-alias-state-success-primary,#22a06b) 14%, transparent);color:var(--dsw-alias-state-success-primary,#22a06b);cursor:pointer;transition:opacity .16s,background .16s,border-color .16s,color .16s}
|
|
291
|
+
.dsm-workbuddy-xdpool-reserve-save:hover:not(:disabled){background:color-mix(in oklab, var(--dsw-alias-state-success-primary,#22a06b) 24%, transparent);border-color:var(--dsw-alias-state-success-primary,#22a06b)}
|
|
292
|
+
.dsm-workbuddy-xdpool-reserve-save:disabled{cursor:default;border-color:var(--dsw-alias-border-l2,#3a3d45);background:transparent;color:var(--dsw-alias-label-tertiary,#8a90a0);opacity:.75}
|
|
293
|
+
.dsm-workbuddy-xdpool-reserve-note{flex:0 0 auto;font-size:11px;line-height:16px}
|
|
294
|
+
.dsm-workbuddy-xdpool-reserve-note-ok{color:var(--dsw-alias-state-success-primary,#22a06b)}
|
|
295
|
+
.dsm-workbuddy-xdpool-reserve-note-bad{color:var(--dsw-alias-state-error-primary,#ef4444);font-weight:600}
|
|
291
296
|
`.trim();
|
|
292
297
|
//#endregion
|
|
293
298
|
//#region src/client/PoolCard.tsx
|
|
@@ -843,6 +848,17 @@ window.__ModuleLoader__.load({
|
|
|
843
848
|
* also refreshes the account list afterwards: the reserve badge appears
|
|
844
849
|
* as soon as the reading crosses the floor.
|
|
845
850
|
*/
|
|
851
|
+
/**
|
|
852
|
+
* Save one account reserved-credit floor.
|
|
853
|
+
*
|
|
854
|
+
* A reserve only protects credits if the pool knows the balance, so this also
|
|
855
|
+
* refreshes the account list afterwards: the reserve badge appears as soon as
|
|
856
|
+
* the reading crosses the floor.
|
|
857
|
+
*
|
|
858
|
+
* Returns whether the host CONFIRMED the write. The card keys its inline
|
|
859
|
+
* "saved / not saved" note off this, so a failure is shown where the user is
|
|
860
|
+
* looking instead of only in the card-level notice line.
|
|
861
|
+
*/
|
|
846
862
|
const saveCreditReserve = async (accountId, reserve) => {
|
|
847
863
|
setReserveBusy(accountId);
|
|
848
864
|
setFlash(void 0);
|
|
@@ -863,8 +879,10 @@ window.__ModuleLoader__.load({
|
|
|
863
879
|
if (!response.ok) throw new Error(body.error ?? `HTTP ${response.status}`);
|
|
864
880
|
await refresh(activeRegion);
|
|
865
881
|
if (mounted.current) setFlash(reserve > 0 ? t?.("row.reserveSaved", { credits: reserve }) ?? `Keeping ${reserve} credits` : t?.("row.reserveCleared") ?? "Reserve cleared");
|
|
882
|
+
return true;
|
|
866
883
|
} catch (cause) {
|
|
867
884
|
if (mounted.current) setError(cause instanceof Error ? cause.message : String(cause));
|
|
885
|
+
return false;
|
|
868
886
|
} finally {
|
|
869
887
|
if (mounted.current) setReserveBusy(void 0);
|
|
870
888
|
}
|
|
@@ -1234,9 +1252,7 @@ window.__ModuleLoader__.load({
|
|
|
1234
1252
|
onClaimCheckin: (accountId) => {
|
|
1235
1253
|
claimCheckin(accountId);
|
|
1236
1254
|
},
|
|
1237
|
-
onSaveCreditReserve: (accountId, reserve) =>
|
|
1238
|
-
saveCreditReserve(accountId, reserve);
|
|
1239
|
-
},
|
|
1255
|
+
onSaveCreditReserve: (accountId, reserve) => saveCreditReserve(accountId, reserve),
|
|
1240
1256
|
onToggleDisabled: (accountId, disabled) => {
|
|
1241
1257
|
toggleAccountDisabled(accountId, disabled);
|
|
1242
1258
|
},
|
|
@@ -1432,19 +1448,41 @@ window.__ModuleLoader__.load({
|
|
|
1432
1448
|
* each save is a settings write plus a status refresh, and a per-character
|
|
1433
1449
|
* save would hammer both.
|
|
1434
1450
|
*/
|
|
1451
|
+
/**
|
|
1452
|
+
* The reserved-credit floor for one account.
|
|
1453
|
+
*
|
|
1454
|
+
* Saving is an EXPLICIT action, not a blur side effect. The old version
|
|
1455
|
+
* committed `onBlur`, which meant a value could be written without the user
|
|
1456
|
+
* asking for it — and when the write silently failed, the only trace was a
|
|
1457
|
+
* notice line at the top of the card that is easy to miss. That is how
|
|
1458
|
+
* "I typed a number, reopened, and it says 0 again" happened with no visible
|
|
1459
|
+
* error to explain it.
|
|
1460
|
+
*
|
|
1461
|
+
* Now: the field is a draft, Save is enabled only when the draft differs from
|
|
1462
|
+
* what the host last reported, and the outcome (saving / saved / failed) is
|
|
1463
|
+
* shown inline next to the button. Enter also saves, so keyboard flow is not
|
|
1464
|
+
* lost.
|
|
1465
|
+
*/
|
|
1435
1466
|
function CreditReserveRow({ account, t, busy, onSave }) {
|
|
1436
|
-
const
|
|
1467
|
+
const saved = account.creditReserve ?? 0;
|
|
1468
|
+
const [draft, setDraft] = (0, react.useState)(String(saved));
|
|
1469
|
+
const [settled, setSettled] = (0, react.useState)(saved);
|
|
1470
|
+
const [note, setNote] = (0, react.useState)(void 0);
|
|
1437
1471
|
(0, react.useEffect)(() => {
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1472
|
+
setSettled(saved);
|
|
1473
|
+
setDraft((current) => current === String(saved) ? current : String(saved));
|
|
1474
|
+
}, [saved]);
|
|
1475
|
+
const parsed = Number.parseInt(draft, 10);
|
|
1476
|
+
const next = Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : 0;
|
|
1477
|
+
const dirty = next !== settled;
|
|
1478
|
+
const commit = async () => {
|
|
1479
|
+
if (busy || !dirty) return;
|
|
1480
|
+
setNote(void 0);
|
|
1481
|
+
if (await onSave(account.id, next)) {
|
|
1482
|
+
setSettled(next);
|
|
1444
1483
|
setDraft(String(next));
|
|
1445
|
-
|
|
1446
|
-
}
|
|
1447
|
-
onSave(account.id, next);
|
|
1484
|
+
setNote("saved");
|
|
1485
|
+
} else setNote("failed");
|
|
1448
1486
|
};
|
|
1449
1487
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1450
1488
|
className: "dsm-workbuddy-xdpool-reserve",
|
|
@@ -1463,16 +1501,33 @@ window.__ModuleLoader__.load({
|
|
|
1463
1501
|
"aria-label": t?.("row.reserveTitle") ?? "Keep at least",
|
|
1464
1502
|
onChange: (event) => {
|
|
1465
1503
|
setDraft(event.target.value);
|
|
1504
|
+
setNote(void 0);
|
|
1466
1505
|
},
|
|
1467
|
-
onBlur: commit,
|
|
1468
1506
|
onKeyDown: (event) => {
|
|
1469
|
-
if (event.key === "Enter")
|
|
1507
|
+
if (event.key === "Enter") commit();
|
|
1470
1508
|
}
|
|
1471
1509
|
}),
|
|
1472
1510
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1473
1511
|
className: "dsm-workbuddy-xdpool-reserve-unit",
|
|
1474
|
-
children:
|
|
1512
|
+
children: t?.("row.reserveUnit") ?? "credits"
|
|
1513
|
+
}),
|
|
1514
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1515
|
+
type: "button",
|
|
1516
|
+
className: "dsm-workbuddy-xdpool-reserve-save",
|
|
1517
|
+
disabled: busy || !dirty,
|
|
1518
|
+
onClick: () => {
|
|
1519
|
+
commit();
|
|
1520
|
+
},
|
|
1521
|
+
children: busy ? t?.("row.reserveSaving") ?? "Saving…" : t?.("row.reserveSave") ?? "Save"
|
|
1475
1522
|
}),
|
|
1523
|
+
note === "saved" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1524
|
+
className: "dsm-workbuddy-xdpool-reserve-note dsm-workbuddy-xdpool-reserve-note-ok",
|
|
1525
|
+
children: next > 0 ? t?.("row.reserveSaved", { credits: next }) ?? `Keeping ${next} credits` : t?.("row.reserveCleared") ?? "Reserve cleared"
|
|
1526
|
+
}) : null,
|
|
1527
|
+
note === "failed" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1528
|
+
className: "dsm-workbuddy-xdpool-reserve-note dsm-workbuddy-xdpool-reserve-note-bad",
|
|
1529
|
+
children: t?.("row.reserveFailed") ?? "Not saved — try again"
|
|
1530
|
+
}) : null,
|
|
1476
1531
|
account.reserved === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1477
1532
|
className: "dsm-workbuddy-xdpool-reserve-badge",
|
|
1478
1533
|
children: t?.("row.reserveHolding") ?? "Reserved: skipped"
|
|
@@ -1797,9 +1852,11 @@ window.__ModuleLoader__.load({
|
|
|
1797
1852
|
"row.reserveTitle": "Keep at least",
|
|
1798
1853
|
"row.reserveUnit": "credits",
|
|
1799
1854
|
"row.reserveSaving": "Saving…",
|
|
1855
|
+
"row.reserveSave": "Save",
|
|
1800
1856
|
"row.reserveHolding": "Reserved: skipped",
|
|
1801
1857
|
"row.reserveSaved": "Keeping {credits} credits",
|
|
1802
1858
|
"row.reserveCleared": "Reserve cleared",
|
|
1859
|
+
"row.reserveFailed": "Not saved — try again",
|
|
1803
1860
|
"row.autoToday": "Today",
|
|
1804
1861
|
"row.autoEarned": "Automation today",
|
|
1805
1862
|
"row.autoEnergy": "energy",
|
|
@@ -1926,9 +1983,11 @@ window.__ModuleLoader__.load({
|
|
|
1926
1983
|
"row.reserveTitle": "保留积分",
|
|
1927
1984
|
"row.reserveUnit": "积分",
|
|
1928
1985
|
"row.reserveSaving": "保存中…",
|
|
1986
|
+
"row.reserveSave": "保存",
|
|
1929
1987
|
"row.reserveHolding": "已保留·暂停使用",
|
|
1930
1988
|
"row.reserveSaved": "已保留 {credits} 积分",
|
|
1931
1989
|
"row.reserveCleared": "已取消保留",
|
|
1990
|
+
"row.reserveFailed": "保存失败,请重试",
|
|
1932
1991
|
"row.autoToday": "今日自动化",
|
|
1933
1992
|
"row.autoEarned": "今日自动化获得",
|
|
1934
1993
|
"row.autoEnergy": "能量",
|
package/lib/index.d.ts
CHANGED
|
@@ -764,8 +764,12 @@ export declare function defaultDesktopAuthDirs(platform?: NodeJS.Platform, home?
|
|
|
764
764
|
* Parse a WorkBuddy auth document. Accepts the nested desktop shape
|
|
765
765
|
* `{"auth":{...},"account":{...}}` and the flat panel shape; returns undefined
|
|
766
766
|
* when there is no usable access token.
|
|
767
|
+
*
|
|
768
|
+
* `decrypt` opens the desktop app's `$wbEncrypted` field wrapper (5.6.0+, both
|
|
769
|
+
* platforms). Absent means "plain-string builds only", which is what every
|
|
770
|
+
* caller without an at-rest key should pass.
|
|
767
771
|
*/
|
|
768
|
-
export declare function parseWorkBuddyAuth(text: string, sourcePath: string): WorkBuddyCredential | undefined;
|
|
772
|
+
export declare function parseWorkBuddyAuth(text: string, sourcePath: string, decrypt?: (field: unknown) => string): WorkBuddyCredential | undefined;
|
|
769
773
|
export declare function workbuddyAccountId(credential: Pick<WorkBuddyCredential, 'uin' | 'uid' | 'nickname'>): string;
|
|
770
774
|
/** Every directory the pool should scan, in probe order. */
|
|
771
775
|
export declare function candidateAuthDirs(env?: NodeJS.ProcessEnv): string[];
|
|
@@ -1332,7 +1336,7 @@ type AutomationJobKind = 'checkin' | 'tasks' | 'report' | 'streak' | 'travel';
|
|
|
1332
1336
|
export declare const AUTOMATION_JOB_KINDS: readonly AutomationJobKind[];
|
|
1333
1337
|
/** Reject anything that is not a job kind, so a route cannot name an unknown job. */
|
|
1334
1338
|
export declare function isAutomationJobKind(value: unknown): value is AutomationJobKind;
|
|
1335
|
-
export declare function dayKey(date: Date): string;
|
|
1339
|
+
export declare function dayKey(date: Date, timeZone?: string): string;
|
|
1336
1340
|
/**
|
|
1337
1341
|
* Whether `now`'s local hour is one of `hours`.
|
|
1338
1342
|
*
|
|
@@ -1467,6 +1471,15 @@ export declare class WorkBuddyScheduler {
|
|
|
1467
1471
|
* rate-limits per account, so overlapping passes would only trip that limit.
|
|
1468
1472
|
* A job that throws is recorded and the loop continues.
|
|
1469
1473
|
*/
|
|
1474
|
+
/**
|
|
1475
|
+
* Whether `kind` is due at `now`: its earliest configured hour has passed in
|
|
1476
|
+
* the scheduling timezone, and no hour of today has been consumed yet.
|
|
1477
|
+
*
|
|
1478
|
+
* Hours are consumed per SLOT (one entry per configured hour), so a job with
|
|
1479
|
+
* two hours still runs twice a day — but a job whose hour passed while DSH was
|
|
1480
|
+
* closed runs immediately on the next tick instead of waiting for tomorrow.
|
|
1481
|
+
*/
|
|
1482
|
+
private isDue;
|
|
1470
1483
|
private tick;
|
|
1471
1484
|
/** Run one job against every eligible account and record the outcome. */
|
|
1472
1485
|
/**
|