daily-focus 1.1.1-dev.2 → 1.2.0-dev.10

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.
@@ -14,7 +14,15 @@ import {
14
14
  startOfLocalDay,
15
15
  workingDaysSince,
16
16
  workingMsBetween
17
- } from "./chunk-OPASVYLR.js";
17
+ } from "./chunk-J7ZYVUG6.js";
18
+
19
+ // src/ids.ts
20
+ function canonicalId(id) {
21
+ return id.trim().toLowerCase();
22
+ }
23
+ function fingerprintId(id) {
24
+ return id.toLowerCase().replace(/[^a-z0-9]/g, "");
25
+ }
18
26
 
19
27
  // src/fs.ts
20
28
  import { mkdir, rename, writeFile } from "node:fs/promises";
@@ -30,109 +38,6 @@ function tempPathFor(path) {
30
38
  return `${path}.tmp`;
31
39
  }
32
40
 
33
- // src/ids.ts
34
- function canonicalId(id) {
35
- return id.trim().toLowerCase();
36
- }
37
- function fingerprintId(id) {
38
- return id.toLowerCase().replace(/[^a-z0-9]/g, "");
39
- }
40
-
41
- // src/archive.ts
42
- import { readFile, readdir } from "node:fs/promises";
43
- import { resolve } from "node:path";
44
- var ARCHIVE_FILE = /^items-(\d{4}-\d{2}-\d{2})\.json$/;
45
- var MAX_ARCHIVE_FILES = 130;
46
- var RECENT_WINDOW_DAYS = 14;
47
- function archivePath(config, dateKey) {
48
- return resolve(config.archiveDir, `items-${dateKey}.json`);
49
- }
50
- function briefDateKey(brief) {
51
- if (brief.date && /^\d{4}-\d{2}-\d{2}$/.test(brief.date)) return brief.date;
52
- const generated = parseISO(brief.generatedAt);
53
- return localDateKey(generated ?? /* @__PURE__ */ new Date());
54
- }
55
- async function archiveBrief(config, brief) {
56
- const dateKey = briefDateKey(brief);
57
- const target = archivePath(config, dateKey);
58
- try {
59
- const existing = JSON.parse(await readFile(target, "utf8"));
60
- const existingAt = parseISO(existing.generatedAt)?.getTime() ?? 0;
61
- const incomingAt = parseISO(brief.generatedAt)?.getTime() ?? 0;
62
- if (existingAt >= incomingAt) return false;
63
- } catch {
64
- }
65
- await writeJsonAtomic(target, brief);
66
- return true;
67
- }
68
- async function listArchivedDates(config) {
69
- let names;
70
- try {
71
- names = await readdir(config.archiveDir);
72
- } catch {
73
- return [];
74
- }
75
- return names.map((name) => ARCHIVE_FILE.exec(name)?.[1]).filter((date) => date !== void 0).sort();
76
- }
77
- async function readArchiveIndex(config) {
78
- const index = /* @__PURE__ */ new Map();
79
- const dated = (await listArchivedDates(config)).reverse().slice(0, MAX_ARCHIVE_FILES);
80
- for (const date of dated) {
81
- let brief;
82
- try {
83
- brief = JSON.parse(await readFile(archivePath(config, date), "utf8"));
84
- } catch {
85
- continue;
86
- }
87
- for (const item of brief.items ?? []) {
88
- if (!item?.id) continue;
89
- const existing = index.get(item.id);
90
- if (!existing) {
91
- index.set(item.id, {
92
- title: item.title ?? item.id,
93
- advancesObjective: item.advancesObjective === true,
94
- firstSeen: item.firstSeen
95
- });
96
- continue;
97
- }
98
- if (item.firstSeen && (!existing.firstSeen || item.firstSeen < existing.firstSeen)) {
99
- existing.firstSeen = item.firstSeen;
100
- }
101
- }
102
- }
103
- return index;
104
- }
105
- function computeObjectiveProgress(index, actions, now, counts = isWorkingDay) {
106
- const lastAction = /* @__PURE__ */ new Map();
107
- for (const action of actions) {
108
- if (action.action === "note") continue;
109
- lastAction.set(canonicalId(action.id), action);
110
- }
111
- const byCanonical = /* @__PURE__ */ new Map();
112
- for (const [id, entry] of index) byCanonical.set(canonicalId(id), entry);
113
- let lastAt = null;
114
- let lastTitle = null;
115
- let recentCount = 0;
116
- for (const [id, action] of lastAction) {
117
- if (action.action !== "done") continue;
118
- if (!byCanonical.get(id)?.advancesObjective) continue;
119
- const at = parseISO(action.at);
120
- if (!at) continue;
121
- if (workingDaysSince(at, now, counts) <= RECENT_WINDOW_DAYS) recentCount++;
122
- if (!lastAt || at.getTime() > lastAt.getTime()) {
123
- lastAt = at;
124
- lastTitle = byCanonical.get(id)?.title ?? null;
125
- }
126
- }
127
- return {
128
- workingDaysSince: lastAt ? workingDaysSince(lastAt, now, counts) : null,
129
- lastAt: lastAt ? lastAt.toISOString() : null,
130
- lastTitle,
131
- recentCount,
132
- windowDays: RECENT_WINDOW_DAYS
133
- };
134
- }
135
-
136
41
  // src/validate.ts
137
42
  var SOURCES = /* @__PURE__ */ new Set([
138
43
  "github",
@@ -300,6 +205,10 @@ function parseBrief(text) {
300
205
  if (raw.dayEnd !== void 0 && !dayEnd) warnings.push('items.json has an unreadable "dayEnd"; using the default');
301
206
  return { brief, error: null, warnings };
302
207
  }
208
+ var ACTION_TYPES = ["done", "snooze", "dismiss", "reopen", "note", "nudge"];
209
+ function setsStatus(action) {
210
+ return action.action !== "note" && action.action !== "nudge";
211
+ }
303
212
  function parseActionLine(line) {
304
213
  const trimmed = line.trim();
305
214
  if (trimmed === "") return null;
@@ -313,7 +222,7 @@ function parseActionLine(line) {
313
222
  const id = optionalString(raw.id);
314
223
  const action = optionalString(raw.action);
315
224
  if (!id || !action) return null;
316
- if (!["done", "snooze", "dismiss", "reopen", "note"].includes(action)) return null;
225
+ if (!ACTION_TYPES.includes(action)) return null;
317
226
  const result = {
318
227
  id,
319
228
  action,
@@ -326,6 +235,101 @@ function parseActionLine(line) {
326
235
  return result;
327
236
  }
328
237
 
238
+ // src/archive.ts
239
+ import { readFile, readdir } from "node:fs/promises";
240
+ import { resolve } from "node:path";
241
+ var ARCHIVE_FILE = /^items-(\d{4}-\d{2}-\d{2})\.json$/;
242
+ var MAX_ARCHIVE_FILES = 130;
243
+ var RECENT_WINDOW_DAYS = 14;
244
+ function archivePath(config, dateKey) {
245
+ return resolve(config.archiveDir, `items-${dateKey}.json`);
246
+ }
247
+ function briefDateKey(brief) {
248
+ if (brief.date && /^\d{4}-\d{2}-\d{2}$/.test(brief.date)) return brief.date;
249
+ const generated = parseISO(brief.generatedAt);
250
+ return localDateKey(generated ?? /* @__PURE__ */ new Date());
251
+ }
252
+ async function archiveBrief(config, brief) {
253
+ const dateKey = briefDateKey(brief);
254
+ const target = archivePath(config, dateKey);
255
+ try {
256
+ const existing = JSON.parse(await readFile(target, "utf8"));
257
+ const existingAt = parseISO(existing.generatedAt)?.getTime() ?? 0;
258
+ const incomingAt = parseISO(brief.generatedAt)?.getTime() ?? 0;
259
+ if (existingAt >= incomingAt) return false;
260
+ } catch {
261
+ }
262
+ await writeJsonAtomic(target, brief);
263
+ return true;
264
+ }
265
+ async function listArchivedDates(config) {
266
+ let names;
267
+ try {
268
+ names = await readdir(config.archiveDir);
269
+ } catch {
270
+ return [];
271
+ }
272
+ return names.map((name) => ARCHIVE_FILE.exec(name)?.[1]).filter((date) => date !== void 0).sort();
273
+ }
274
+ async function readArchiveIndex(config) {
275
+ const index = /* @__PURE__ */ new Map();
276
+ const dated = (await listArchivedDates(config)).reverse().slice(0, MAX_ARCHIVE_FILES);
277
+ for (const date of dated) {
278
+ let brief;
279
+ try {
280
+ brief = JSON.parse(await readFile(archivePath(config, date), "utf8"));
281
+ } catch {
282
+ continue;
283
+ }
284
+ for (const item of brief.items ?? []) {
285
+ if (!item?.id) continue;
286
+ const existing = index.get(item.id);
287
+ if (!existing) {
288
+ index.set(item.id, {
289
+ title: item.title ?? item.id,
290
+ advancesObjective: item.advancesObjective === true,
291
+ firstSeen: item.firstSeen
292
+ });
293
+ continue;
294
+ }
295
+ if (item.firstSeen && (!existing.firstSeen || item.firstSeen < existing.firstSeen)) {
296
+ existing.firstSeen = item.firstSeen;
297
+ }
298
+ }
299
+ }
300
+ return index;
301
+ }
302
+ function computeObjectiveProgress(index, actions, now, counts = isWorkingDay) {
303
+ const lastAction = /* @__PURE__ */ new Map();
304
+ for (const action of actions) {
305
+ if (!setsStatus(action)) continue;
306
+ lastAction.set(canonicalId(action.id), action);
307
+ }
308
+ const byCanonical = /* @__PURE__ */ new Map();
309
+ for (const [id, entry] of index) byCanonical.set(canonicalId(id), entry);
310
+ let lastAt = null;
311
+ let lastTitle = null;
312
+ let recentCount = 0;
313
+ for (const [id, action] of lastAction) {
314
+ if (action.action !== "done") continue;
315
+ if (!byCanonical.get(id)?.advancesObjective) continue;
316
+ const at = parseISO(action.at);
317
+ if (!at) continue;
318
+ if (workingDaysSince(at, now, counts) <= RECENT_WINDOW_DAYS) recentCount++;
319
+ if (!lastAt || at.getTime() > lastAt.getTime()) {
320
+ lastAt = at;
321
+ lastTitle = byCanonical.get(id)?.title ?? null;
322
+ }
323
+ }
324
+ return {
325
+ workingDaysSince: lastAt ? workingDaysSince(lastAt, now, counts) : null,
326
+ lastAt: lastAt ? lastAt.toISOString() : null,
327
+ lastTitle,
328
+ recentCount,
329
+ windowDays: RECENT_WINDOW_DAYS
330
+ };
331
+ }
332
+
329
333
  // src/checks.ts
330
334
  var GENEROUS_FLAG_THRESHOLD = 5;
331
335
  function runContractChecks({ brief, items, focus, history }) {
@@ -386,172 +390,9 @@ function findLeakedPhrase(brief, focus) {
386
390
  return null;
387
391
  }
388
392
 
389
- // src/sessions.ts
390
- import { appendFile, mkdir as mkdir2, readFile as readFile2, unlink } from "node:fs/promises";
391
- var MAX_SESSION_MINUTES = 120;
392
- var MIN_LOGGABLE_MINUTES = 1;
393
- async function readActive(config) {
394
- try {
395
- return JSON.parse(await readFile2(config.sessionFile, "utf8"));
396
- } catch {
397
- return null;
398
- }
399
- }
400
- async function writeActive(config, session) {
401
- await writeJsonAtomic(config.sessionFile, session);
402
- }
403
- async function clearActive(config) {
404
- try {
405
- await unlink(config.sessionFile);
406
- } catch {
407
- }
408
- }
409
- async function logSession(config, session, endedAt, endedBy) {
410
- const startedAt = parseISO(session.startedAt);
411
- if (!startedAt) return null;
412
- const actualMinutes = Math.round((endedAt.getTime() - startedAt.getTime()) / 6e4);
413
- if (actualMinutes < MIN_LOGGABLE_MINUTES) return null;
414
- const entry = {
415
- id: session.id,
416
- title: session.title,
417
- startedAt: session.startedAt,
418
- endedAt: endedAt.toISOString(),
419
- plannedMinutes: session.minutes,
420
- actualMinutes,
421
- reachedTarget: actualMinutes >= session.minutes,
422
- advancesObjective: session.advancesObjective,
423
- endedBy
424
- };
425
- await mkdir2(config.dataDir, { recursive: true });
426
- await appendFile(config.sessionsLogFile, `${JSON.stringify(entry)}
427
- `, "utf8");
428
- return entry;
429
- }
430
- async function startSession(config, opts) {
431
- const now = opts.now ?? /* @__PURE__ */ new Date();
432
- await stopSession(config, now);
433
- await writeActive(config, {
434
- id: opts.id,
435
- title: opts.title,
436
- startedAt: now.toISOString(),
437
- minutes: opts.minutes,
438
- advancesObjective: opts.advancesObjective,
439
- // The click that got here is itself the first evidence of a person.
440
- lastActiveAt: now.toISOString()
441
- });
442
- }
443
- async function stopSession(config, now = /* @__PURE__ */ new Date(), endedBy = "user") {
444
- const active = await readActive(config);
445
- if (!active) return null;
446
- await logSession(config, active, now, endedBy);
447
- await clearActive(config);
448
- return active;
449
- }
450
- async function reconcileSession(config, probe, now = /* @__PURE__ */ new Date()) {
451
- if (config.awayAfterMinutes <= 0) return null;
452
- const active = await readActive(config);
453
- if (!active) return null;
454
- const startedAt = parseISO(active.startedAt);
455
- if (!startedAt) {
456
- await clearActive(config);
457
- return null;
458
- }
459
- const awayAfterMs = config.awayAfterMinutes * 6e4;
460
- const previous = parseISO(active.lastActiveAt) ?? startedAt;
461
- if (now.getTime() - previous.getTime() > awayAfterMs) {
462
- const logged = await logSession(config, active, previous, "away");
463
- await clearActive(config);
464
- return logged;
465
- }
466
- const idleSeconds = await probe();
467
- const lastInput = idleSeconds === null ? now : new Date(now.getTime() - Math.max(0, idleSeconds) * 1e3);
468
- const confirmed = lastInput.getTime() > previous.getTime() ? lastInput : previous;
469
- if (confirmed.toISOString() !== active.lastActiveAt) {
470
- await writeActive(config, { ...active, lastActiveAt: confirmed.toISOString() });
471
- }
472
- return null;
473
- }
474
- async function closeIfPastBackstop(config, active, now) {
475
- if (!active) return null;
476
- const startedAt = parseISO(active.startedAt);
477
- const elapsed = startedAt ? (now.getTime() - startedAt.getTime()) / 6e4 : Infinity;
478
- if (elapsed <= MAX_SESSION_MINUTES) return active;
479
- const ceiling = new Date((startedAt?.getTime() ?? now.getTime()) + MAX_SESSION_MINUTES * 6e4);
480
- await stopSession(config, ceiling, "limit");
481
- return null;
482
- }
483
- function lastUnattendedClose(todayLog) {
484
- const last = todayLog.at(-1);
485
- if (!last || last.endedBy === void 0 || last.endedBy === "user") return null;
486
- return {
487
- id: last.id,
488
- title: last.title,
489
- endedAt: last.endedAt,
490
- actualMinutes: last.actualMinutes,
491
- reason: last.endedBy
492
- };
493
- }
494
- async function readSessionState(config, agenda, now = /* @__PURE__ */ new Date()) {
495
- const active = await closeIfPastBackstop(config, await readActive(config), now);
496
- const todayLog = await readTodaysSessions(config, now);
497
- const completedToday = todayLog.length;
498
- const minutesToday = todayLog.reduce((sum, entry) => sum + entry.actualMinutes, 0);
499
- const unattendedClose = active ? null : lastUnattendedClose(todayLog);
500
- if (!active) {
501
- return { active: null, completedToday, minutesToday, unattendedClose };
502
- }
503
- const startedAt = parseISO(active.startedAt);
504
- const endsAt = new Date(startedAt.getTime() + active.minutes * 6e4);
505
- const remainingSeconds = Math.round((endsAt.getTime() - now.getTime()) / 1e3);
506
- const nextEvent = agenda.events.map((event) => parseISO(event.start)).filter((start) => start !== null && start.getTime() > now.getTime()).sort((a, b) => a.getTime() - b.getTime())[0];
507
- return {
508
- active: {
509
- id: active.id,
510
- title: active.title,
511
- startedAt: active.startedAt,
512
- advancesObjective: active.advancesObjective,
513
- lastActiveAt: active.lastActiveAt,
514
- endsAt: endsAt.toISOString(),
515
- minutes: active.minutes,
516
- remainingSeconds,
517
- overrun: remainingSeconds < 0,
518
- collidesWithNextEvent: nextEvent ? nextEvent.getTime() < endsAt.getTime() : false,
519
- nextEventAt: nextEvent ? nextEvent.toISOString() : null
520
- },
521
- completedToday,
522
- minutesToday,
523
- unattendedClose
524
- };
525
- }
526
- async function readSessions(config) {
527
- let text;
528
- try {
529
- text = await readFile2(config.sessionsLogFile, "utf8");
530
- } catch {
531
- return [];
532
- }
533
- const sessions = [];
534
- for (const line of text.split("\n")) {
535
- if (line.trim() === "") continue;
536
- try {
537
- const entry = JSON.parse(line);
538
- if (entry?.id && entry.startedAt) sessions.push(entry);
539
- } catch {
540
- }
541
- }
542
- return sessions;
543
- }
544
- async function readTodaysSessions(config, now) {
545
- const all = await readSessions(config);
546
- return all.filter((entry) => {
547
- const started = parseISO(entry.startedAt);
548
- return started !== null && started.getFullYear() === now.getFullYear() && started.getMonth() === now.getMonth() && started.getDate() === now.getDate();
549
- });
550
- }
551
-
552
393
  // src/store.ts
553
394
  import { createHash } from "node:crypto";
554
- import { appendFile as appendFile2, mkdir as mkdir3, readFile as readFile3, stat } from "node:fs/promises";
395
+ import { appendFile, mkdir as mkdir2, readFile as readFile2, stat } from "node:fs/promises";
555
396
  import { setTimeout as delay } from "node:timers/promises";
556
397
 
557
398
  // src/agenda.ts
@@ -708,7 +549,7 @@ var Store = class {
708
549
  this.config = config;
709
550
  }
710
551
  async ensureDataDir() {
711
- await mkdir3(this.config.dataDir, { recursive: true });
552
+ await mkdir2(this.config.dataDir, { recursive: true });
712
553
  }
713
554
  /**
714
555
  * Which store this is, as an opaque fingerprint: its path, and the directory's
@@ -736,7 +577,7 @@ var Store = class {
736
577
  for (let attempt = 0; attempt < READ_RETRIES; attempt++) {
737
578
  let text;
738
579
  try {
739
- text = await readFile3(this.config.itemsFile, "utf8");
580
+ text = await readFile2(this.config.itemsFile, "utf8");
740
581
  } catch (err) {
741
582
  if (err.code === "ENOENT") {
742
583
  return {
@@ -757,7 +598,7 @@ var Store = class {
757
598
  async readActions() {
758
599
  let text;
759
600
  try {
760
- text = await readFile3(this.config.actionsFile, "utf8");
601
+ text = await readFile2(this.config.actionsFile, "utf8");
761
602
  } catch (err) {
762
603
  if (err.code === "ENOENT") return [];
763
604
  throw err;
@@ -789,7 +630,7 @@ var Store = class {
789
630
  /** Read the standing objective. Returns null when focus.md hasn't been written. */
790
631
  async readFocus() {
791
632
  try {
792
- return parseFocus(await readFile3(this.config.focusFile, "utf8"));
633
+ return parseFocus(await readFile2(this.config.focusFile, "utf8"));
793
634
  } catch (err) {
794
635
  if (err.code === "ENOENT") return null;
795
636
  console.warn(`[daily-focus] could not read focus.md: ${err.message}`);
@@ -802,7 +643,7 @@ var Store = class {
802
643
  `;
803
644
  const write = this.#writeQueue.then(async () => {
804
645
  await this.ensureDataDir();
805
- await appendFile2(this.config.actionsFile, line, "utf8");
646
+ await appendFile(this.config.actionsFile, line, "utf8");
806
647
  });
807
648
  this.#writeQueue = write.catch(() => {
808
649
  });
@@ -870,12 +711,10 @@ var Store = class {
870
711
  }
871
712
  objectiveProgress = computeObjectiveProgress(withToday, actions, now, isRunDay);
872
713
  }
873
- const session = await readSessionState(this.config, agenda, now);
874
714
  return {
875
715
  // toPublicFocus, not focus — the agent-only section must never be serialised.
876
716
  focus: focus ? toPublicFocus(focus) : null,
877
717
  objectiveProgress,
878
- session,
879
718
  schedule: {
880
719
  days: [...schedule.days],
881
720
  source: schedule.source,
@@ -931,6 +770,9 @@ function foldActionLog(actions, now) {
931
770
  case "note":
932
771
  if (action.text) entry.notes.push({ text: action.text, at: action.at });
933
772
  break;
773
+ case "nudge":
774
+ entry.nudgedAt = action.at;
775
+ break;
934
776
  case "done":
935
777
  entry.status = "done";
936
778
  delete entry.snoozedUntil;
@@ -982,16 +824,15 @@ function resolveItems(items, actions, now) {
982
824
  }
983
825
 
984
826
  export {
985
- writeJsonAtomic,
986
- tempPathFor,
987
827
  canonicalId,
988
828
  fingerprintId,
989
- readArchiveIndex,
829
+ writeJsonAtomic,
830
+ tempPathFor,
990
831
  parseBrief,
832
+ ACTION_TYPES,
833
+ setsStatus,
834
+ readArchiveIndex,
991
835
  runContractChecks,
992
- startSession,
993
- stopSession,
994
- reconcileSession,
995
836
  Store,
996
837
  foldActionLog,
997
838
  resolveItems
@@ -0,0 +1,107 @@
1
+ import {
2
+ writeJsonAtomic
3
+ } from "./chunk-RZNQJ6UG.js";
4
+ import {
5
+ localTimeZone
6
+ } from "./chunk-J7ZYVUG6.js";
7
+
8
+ // src/zone.ts
9
+ import { readFile, readlink } from "node:fs/promises";
10
+ function validZone(raw) {
11
+ if (typeof raw !== "string" || !/^[A-Za-z][A-Za-z0-9_+\-/]*$/.test(raw)) return null;
12
+ try {
13
+ new Intl.DateTimeFormat("en", { timeZone: raw });
14
+ return raw;
15
+ } catch {
16
+ return null;
17
+ }
18
+ }
19
+ async function readSystemZone(path = "/etc/localtime") {
20
+ try {
21
+ const target = await readlink(path);
22
+ const name = /zoneinfo\/(?:posix\/|right\/)?(.+)$/.exec(target)?.[1];
23
+ return validZone(name);
24
+ } catch {
25
+ return null;
26
+ }
27
+ }
28
+ async function startingZone(file, system) {
29
+ const saved = await loadZoneFile(file);
30
+ if (!saved) return { timeZone: localTimeZone(), moved: false };
31
+ if (system && saved.systemTimeZone && system !== saved.systemTimeZone) return { timeZone: system, moved: true };
32
+ return { timeZone: saved.timeZone, moved: false };
33
+ }
34
+ async function loadZoneFile(file) {
35
+ let parsed;
36
+ try {
37
+ parsed = JSON.parse(await readFile(file, "utf8"));
38
+ } catch {
39
+ return null;
40
+ }
41
+ const record = parsed ?? {};
42
+ const timeZone = validZone(record.timeZone);
43
+ if (!timeZone) return null;
44
+ return { timeZone, systemTimeZone: validZone(record.systemTimeZone) };
45
+ }
46
+ var TimeZoneKeeper = class {
47
+ #file;
48
+ #readSystem;
49
+ #current = localTimeZone();
50
+ /** The Mac's zone as last read, so `checkSystem` acts on a change rather than a difference. */
51
+ #system = null;
52
+ constructor(file, readSystem = readSystemZone) {
53
+ this.#file = file;
54
+ this.#readSystem = readSystem;
55
+ }
56
+ get current() {
57
+ return this.#current;
58
+ }
59
+ /** Decide the zone at startup; see `startingZone`. */
60
+ async start() {
61
+ this.#system = await this.#readSystem();
62
+ const { timeZone, moved } = await startingZone(this.#file, this.#system);
63
+ if (moved) await this.#adopt(timeZone);
64
+ else this.#apply(timeZone);
65
+ }
66
+ /** A browser says it is in `zone`. True if that changed the dashboard's. */
67
+ async report(zone) {
68
+ if (zone === this.#current) return false;
69
+ await this.#adopt(zone);
70
+ return true;
71
+ }
72
+ /** Follow the Mac if its zone has changed since last read. True if that changed the dashboard's. */
73
+ async checkSystem() {
74
+ const system = await this.#readSystem();
75
+ if (!system || system === this.#system) return false;
76
+ this.#system = system;
77
+ if (system === this.#current) {
78
+ await this.#save();
79
+ return false;
80
+ }
81
+ await this.#adopt(system);
82
+ return true;
83
+ }
84
+ async #adopt(zone) {
85
+ this.#apply(zone);
86
+ await this.#save();
87
+ }
88
+ #apply(zone) {
89
+ this.#current = zone;
90
+ process.env.TZ = zone;
91
+ }
92
+ async #save() {
93
+ const file = { timeZone: this.#current, systemTimeZone: this.#system };
94
+ try {
95
+ await writeJsonAtomic(this.#file, file);
96
+ } catch (err) {
97
+ console.warn(`[daily-focus] could not save the time zone: ${err.message}`);
98
+ }
99
+ }
100
+ };
101
+
102
+ export {
103
+ validZone,
104
+ readSystemZone,
105
+ startingZone,
106
+ TimeZoneKeeper
107
+ };
package/dist/cli.js CHANGED
@@ -49,11 +49,12 @@ function shouldOpen(flag) {
49
49
  return flag && process.stdout.isTTY === true && !process.env.CI;
50
50
  }
51
51
  async function demo(open, profile) {
52
- const { demoEnv, demoIntegrations, demoProfile, writeDemoStore } = await import("./demo-OQLT3QWP.js");
52
+ const { demoEnv, demoIntegrations, demoProfile, writeDemoStore } = await import("./demo-BYLLMKSQ.js");
53
53
  const chosen = demoProfile(profile);
54
54
  const store = await mkdtemp(join(tmpdir(), "daily-focus-demo-"));
55
55
  process.on("exit", () => rmSync(store, { recursive: true, force: true }));
56
- await writeDemoStore(store, chosen);
56
+ const now = /* @__PURE__ */ new Date();
57
+ await writeDemoStore(store, chosen, now);
57
58
  await start(
58
59
  open,
59
60
  (url) => {
@@ -61,11 +62,11 @@ async function demo(open, profile) {
61
62
  The demo is at ${url}, with sample data in a throwaway store (the ${chosen} profile).`);
62
63
  console.log(" Nothing is connected and nothing runs. Stop it with Ctrl+C; the store goes with it.\n");
63
64
  },
64
- { env: demoEnv(process.env, store, chosen), integrations: demoIntegrations(chosen) }
65
+ { env: demoEnv(process.env, store, chosen), integrations: demoIntegrations(chosen, now) }
65
66
  );
66
67
  }
67
68
  async function start(open, onStarted, options = {}) {
68
- const { runServer } = await import("./server-5O3LQVEY.js");
69
+ const { runServer } = await import("./server-WQYUOKB3.js");
69
70
  await runServer({
70
71
  ...options,
71
72
  onStarted: (url) => {
@@ -162,20 +163,20 @@ async function main(argv) {
162
163
  }
163
164
  return start(open);
164
165
  case "service": {
165
- const { main: service } = await import("./service-6ZFOHPWN.js");
166
+ const { main: service } = await import("./service-PUJ3YDH5.js");
166
167
  process.exitCode = await service(values.remove ? ["--remove"] : []);
167
168
  return;
168
169
  }
169
170
  // These three are scripts that do their work as they load, and read their
170
171
  // own flags from the command line.
171
172
  case "init":
172
- await import("./init-DEVCAQZZ.js");
173
+ await import("./init-EMZDG2I7.js");
173
174
  return;
174
175
  case "audit":
175
- await import("./audit-W3O6CLOF.js");
176
+ await import("./audit-7ZCLKF46.js");
176
177
  return;
177
178
  case "seed":
178
- await import("./seed-SMKNFYTC.js");
179
+ await import("./seed-JU4LQ33D.js");
179
180
  return;
180
181
  case "build-calendar":
181
182
  return buildCalendar();