daily-focus 1.2.0-dev.8 → 2.0.0-dev.14
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 +25 -7
- package/SETUP.md +156 -105
- package/dist/{audit-DNTFZOHH.js → audit-AQUBVWPA.js} +4 -3
- package/dist/{chunk-KZNY45XW.js → chunk-72NUDSOV.js} +126 -119
- package/dist/{chunk-GQ3NCWRT.js → chunk-ITUK7XKS.js} +6 -19
- package/dist/{chunk-SX5FPS3A.js → chunk-JTMBBUMD.js} +454 -22
- package/dist/chunk-QG4XXKBP.js +18 -0
- package/dist/chunk-U6EPOXEW.js +570 -0
- package/dist/{chunk-P3E2B2SD.js → chunk-WPXIQPYX.js} +1 -1
- package/dist/{chunk-6SMUSPZG.js → chunk-YPGKXTBX.js} +2 -2
- package/dist/cli.js +19 -12
- package/dist/{demo-6Q22AAC7.js → demo-KE6WDWMA.js} +194 -40
- package/dist/{init-XDMAXU2S.js → init-5KJVHWJU.js} +41 -6
- package/dist/{seed-TWT7EDXJ.js → seed-T4VEY6DT.js} +4 -3
- package/dist/{server-PIYOIUKJ.js → server-FFRNYEAO.js} +2000 -743
- package/dist/{service-ZUHI3VKH.js → service-LQ46YRCZ.js} +7 -6
- package/package.json +2 -2
- package/prompts/README.md +20 -4
- package/prompts/assistant.md +14 -3
- package/prompts/morning-brief-personal.md +37 -0
- package/prompts/morning-brief-work.md +37 -0
- package/prompts/onboarding.md +41 -0
- package/public/app.js +976 -381
- package/public/style.css +821 -100
- package/dist/chunk-V2UN3JYL.js +0 -140
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
import {
|
|
2
|
+
writeJsonAtomic
|
|
3
|
+
} from "./chunk-QG4XXKBP.js";
|
|
4
|
+
import {
|
|
5
|
+
LAUNCH_SETTINGS,
|
|
6
|
+
PROFILES,
|
|
7
|
+
layerSettings,
|
|
8
|
+
loadConfig
|
|
9
|
+
} from "./chunk-72NUDSOV.js";
|
|
10
|
+
|
|
11
|
+
// src/settings.ts
|
|
12
|
+
var SETTING_GROUPS = [
|
|
13
|
+
{ id: "instance", title: "This dashboard" },
|
|
14
|
+
{ id: "agent", title: "Morning agent" },
|
|
15
|
+
{ id: "assistant", title: "Assistant" },
|
|
16
|
+
{ id: "github", title: "Pull requests" },
|
|
17
|
+
{ id: "jira", title: "Jira tickets" },
|
|
18
|
+
{ id: "calendar", title: "Calendar" },
|
|
19
|
+
{ id: "day", title: "Working day" }
|
|
20
|
+
];
|
|
21
|
+
var OFF_OR_CLI = [
|
|
22
|
+
{ value: "off", label: "Off" },
|
|
23
|
+
{ value: "claude", label: "Claude Code" },
|
|
24
|
+
{ value: "codex", label: "Codex" }
|
|
25
|
+
];
|
|
26
|
+
var EFFORTS = ["low", "medium", "high", "xhigh", "max"].map((value) => ({ value, label: value }));
|
|
27
|
+
var always = (text) => () => text;
|
|
28
|
+
var SETTINGS = [
|
|
29
|
+
{
|
|
30
|
+
key: "DAILY_FOCUS_PROFILE",
|
|
31
|
+
group: "instance",
|
|
32
|
+
label: "What this dashboard briefs",
|
|
33
|
+
help: "Work or home. Picks the morning prompt and the defaults below: the personal profile switches off Jira and free time.",
|
|
34
|
+
kind: "choice",
|
|
35
|
+
choices: PROFILES.map((value) => ({ value, label: value === "work" ? "Work" : "Personal life" })),
|
|
36
|
+
fallback: always("Work")
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
key: "DAILY_FOCUS_AGENT",
|
|
40
|
+
group: "agent",
|
|
41
|
+
label: "Write the brief with",
|
|
42
|
+
help: "The coding-agent CLI the dashboard runs each morning to write your brief. It has to be installed and logged in.",
|
|
43
|
+
kind: "choice",
|
|
44
|
+
choices: OFF_OR_CLI,
|
|
45
|
+
fallback: always("Off")
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
key: "DAILY_FOCUS_AGENT_AT",
|
|
49
|
+
group: "agent",
|
|
50
|
+
label: "Run it at",
|
|
51
|
+
help: "Local time, such as 06:30, or off to run it only when you press refresh. If the dashboard was off at that time, it runs when it next starts that day.",
|
|
52
|
+
kind: "time",
|
|
53
|
+
fallback: always("07:00")
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
key: "DAILY_FOCUS_AGENT_DAYS",
|
|
57
|
+
group: "agent",
|
|
58
|
+
label: "On these days",
|
|
59
|
+
help: "Cron-style weekday numbers: 1-5 is Monday to Friday, 0-4 is Sunday to Thursday, 0,6 is the weekend.",
|
|
60
|
+
kind: "text",
|
|
61
|
+
fallback: always("1-5, Monday to Friday")
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
key: "DAILY_FOCUS_AGENT_MODEL",
|
|
65
|
+
group: "agent",
|
|
66
|
+
label: "Model",
|
|
67
|
+
help: "Passed to the CLI as it is, such as opus for Claude Code. Empty uses the CLI's own default.",
|
|
68
|
+
kind: "text",
|
|
69
|
+
fallback: always("The CLI's own")
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
key: "DAILY_FOCUS_AGENT_EFFORT",
|
|
73
|
+
group: "agent",
|
|
74
|
+
label: "Effort",
|
|
75
|
+
help: "A brief is worth a high effort.",
|
|
76
|
+
kind: "choice",
|
|
77
|
+
choices: EFFORTS,
|
|
78
|
+
fallback: always("The CLI's own")
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
key: "DAILY_FOCUS_AGENT_SEARCH_TRASH",
|
|
82
|
+
group: "agent",
|
|
83
|
+
label: "Search deleted mail",
|
|
84
|
+
help: "Lets the agent look in your mail\u2019s Trash as well as the inbox and archive. Off, it leaves out what you deleted.",
|
|
85
|
+
kind: "flag",
|
|
86
|
+
fallback: always("Off")
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
key: "DAILY_FOCUS_AGENT_TOOLS",
|
|
90
|
+
group: "agent",
|
|
91
|
+
label: "Tools Claude Code may use",
|
|
92
|
+
help: "In Claude Code's permission syntax, comma-separated. Add your calendar, chat and document connectors by name, or the brief reports them as unreachable. The default allows every Gmail tool, sending included; name the tools you want instead to narrow it. Ignored by Codex.",
|
|
93
|
+
kind: "list",
|
|
94
|
+
fallback: always("Bash(gh *), Bash(acli *), WebFetch, mcp__claude_ai_Gmail"),
|
|
95
|
+
advanced: true
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
key: "DAILY_FOCUS_AGENT_BIN",
|
|
99
|
+
group: "agent",
|
|
100
|
+
label: "Path to the CLI",
|
|
101
|
+
help: "Only needed when the dashboard can't find the CLI on its own.",
|
|
102
|
+
kind: "text",
|
|
103
|
+
fallback: always("Found on PATH"),
|
|
104
|
+
advanced: true
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
key: "DAILY_FOCUS_STALE_AFTER_HOURS",
|
|
108
|
+
group: "agent",
|
|
109
|
+
label: "Expect a new brief every",
|
|
110
|
+
help: "Hours, counted only on the days above. After that, and 45 minutes more, the page says the agent may not have run.",
|
|
111
|
+
kind: "number",
|
|
112
|
+
fallback: always("24 hours"),
|
|
113
|
+
advanced: true
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
key: "DAILY_FOCUS_ASSISTANT",
|
|
117
|
+
group: "assistant",
|
|
118
|
+
label: "Answer questions with",
|
|
119
|
+
help: "Puts an assistant in every item's panel, told to read and draft and never to send, post or edit. Under Codex, every connector write but a Gmail draft is refused.",
|
|
120
|
+
kind: "choice",
|
|
121
|
+
choices: OFF_OR_CLI,
|
|
122
|
+
fallback: always("Off")
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
key: "DAILY_FOCUS_ASSISTANT_MODEL",
|
|
126
|
+
group: "assistant",
|
|
127
|
+
label: "Model",
|
|
128
|
+
help: "Passed to the CLI as it is. Empty uses the CLI's own default.",
|
|
129
|
+
kind: "text",
|
|
130
|
+
fallback: always("The CLI's own")
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
key: "DAILY_FOCUS_ASSISTANT_EFFORT",
|
|
134
|
+
group: "assistant",
|
|
135
|
+
label: "Effort",
|
|
136
|
+
help: "Passed to the CLI as it is.",
|
|
137
|
+
kind: "choice",
|
|
138
|
+
choices: EFFORTS,
|
|
139
|
+
fallback: always("The CLI's own")
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
key: "DAILY_FOCUS_ASSISTANT_TOOLS",
|
|
143
|
+
group: "assistant",
|
|
144
|
+
label: "Tools Claude Code may use",
|
|
145
|
+
help: "In Claude Code's permission syntax, comma-separated. Editing tools are denied whatever this says. The default allows every Gmail tool, sending included; name the tools you want instead to narrow it. Ignored by Codex.",
|
|
146
|
+
kind: "list",
|
|
147
|
+
fallback: always("Bash(gh *), Bash(acli *), WebFetch, mcp__claude_ai_Gmail"),
|
|
148
|
+
advanced: true
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
key: "DAILY_FOCUS_ASSISTANT_COMPUTER_USE",
|
|
152
|
+
group: "assistant",
|
|
153
|
+
label: "Computer use",
|
|
154
|
+
help: "Codex only. On lets the assistant use Codex's computer-use and browser plugins, which act in your apps and browser with your sign-ins. Off switches them off for its runs. Claude Code uses only the tools in its list.",
|
|
155
|
+
kind: "flag",
|
|
156
|
+
fallback: always("Off")
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
key: "DAILY_FOCUS_ASSISTANT_AUTO",
|
|
160
|
+
group: "assistant",
|
|
161
|
+
label: "Answer ahead on pull requests",
|
|
162
|
+
help: "When a pull request starts waiting on you because of a review or a failing check, asks the assistant about it before you do, so the answer is on the row when you look. Only reads; it never drafts. It spends the CLI\u2019s usage without you pressing anything.",
|
|
163
|
+
kind: "flag",
|
|
164
|
+
fallback: always("Off")
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
key: "DAILY_FOCUS_ASSISTANT_AUTO_LIMIT",
|
|
168
|
+
group: "assistant",
|
|
169
|
+
label: "Answer ahead at most",
|
|
170
|
+
help: "Times a day the assistant may start on its own, counted on the dashboard\u2019s calendar.",
|
|
171
|
+
kind: "number",
|
|
172
|
+
fallback: always("10 a day"),
|
|
173
|
+
advanced: true
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
key: "DAILY_FOCUS_ASSISTANT_BIN",
|
|
177
|
+
group: "assistant",
|
|
178
|
+
label: "Path to the CLI",
|
|
179
|
+
help: "Only needed when the dashboard can't find the CLI on its own.",
|
|
180
|
+
kind: "text",
|
|
181
|
+
fallback: always("Found on PATH"),
|
|
182
|
+
advanced: true
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
key: "DAILY_FOCUS_GITHUB",
|
|
186
|
+
group: "github",
|
|
187
|
+
label: "Pull request board",
|
|
188
|
+
help: "Reads your open pull requests through the GitHub CLI. Sign in with gh auth login. The Jira ticket board uses it too, to catch merges Jira hasn't heard about yet.",
|
|
189
|
+
kind: "flag",
|
|
190
|
+
fallback: always("On")
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
key: "DAILY_FOCUS_GITHUB_ACCOUNTS",
|
|
194
|
+
group: "github",
|
|
195
|
+
label: "Accounts",
|
|
196
|
+
help: "GitHub logins to read as, comma-separated, when gh holds more than one. Empty uses the active one.",
|
|
197
|
+
kind: "list",
|
|
198
|
+
fallback: always("gh's active account")
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
key: "DAILY_FOCUS_GITHUB_SCOPE",
|
|
202
|
+
group: "github",
|
|
203
|
+
label: "Only these organisations or repositories",
|
|
204
|
+
help: "Comma-separated: acme, or acme/webapp for one repository. Empty counts every pull request you have open.",
|
|
205
|
+
kind: "list",
|
|
206
|
+
fallback: always("Everything")
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
key: "DAILY_FOCUS_GITHUB_MERGE_GATE_CHECKS",
|
|
210
|
+
group: "github",
|
|
211
|
+
label: "Merge gate checks",
|
|
212
|
+
help: "Exact names of checks that stand for a repository's whole merge policy, comma-separated. Pending ones get their own bucket.",
|
|
213
|
+
kind: "list",
|
|
214
|
+
fallback: always("None"),
|
|
215
|
+
advanced: true
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
key: "DAILY_FOCUS_GITHUB_POLL_MINUTES",
|
|
219
|
+
group: "github",
|
|
220
|
+
label: "Read every",
|
|
221
|
+
help: "Minutes between reads while a tab is open.",
|
|
222
|
+
kind: "number",
|
|
223
|
+
fallback: always("5 minutes"),
|
|
224
|
+
advanced: true
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
key: "DAILY_FOCUS_GH",
|
|
228
|
+
group: "github",
|
|
229
|
+
label: "Path to gh",
|
|
230
|
+
help: "Only needed when the dashboard can't find the GitHub CLI on its own.",
|
|
231
|
+
kind: "text",
|
|
232
|
+
fallback: always("Found on PATH"),
|
|
233
|
+
advanced: true
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
key: "DAILY_FOCUS_JIRA",
|
|
237
|
+
group: "jira",
|
|
238
|
+
label: "Jira ticket board",
|
|
239
|
+
help: "Flags tickets whose status disagrees with their pull requests, through the Atlassian CLI. Sign in with acli jira auth login.",
|
|
240
|
+
kind: "flag",
|
|
241
|
+
fallback: (profile) => profile === "personal" ? "Off" : "On"
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
key: "DAILY_FOCUS_JIRA_PROJECTS",
|
|
245
|
+
group: "jira",
|
|
246
|
+
label: "Only these projects",
|
|
247
|
+
help: "Project keys, comma-separated. Empty means every project you can see.",
|
|
248
|
+
kind: "list",
|
|
249
|
+
fallback: always("Every project")
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
key: "DAILY_FOCUS_JIRA_HOLD_STATUSES",
|
|
253
|
+
group: "jira",
|
|
254
|
+
label: "Statuses that mean on hold",
|
|
255
|
+
help: "Spelled as your Jira spells them, comma-separated, such as Blocked, On Hold. Tickets in them are not flagged for having no code linked.",
|
|
256
|
+
kind: "list",
|
|
257
|
+
fallback: always("None")
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
key: "DAILY_FOCUS_JIRA_IN_PROGRESS_STATUSES",
|
|
261
|
+
group: "jira",
|
|
262
|
+
label: "Statuses that mean working on it",
|
|
263
|
+
help: "What the Working on view shows, comma-separated. Use In Progress to leave out In Review.",
|
|
264
|
+
kind: "list",
|
|
265
|
+
fallback: always("Everything Jira calls in progress")
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
key: "DAILY_FOCUS_JIRA_SITE",
|
|
269
|
+
group: "jira",
|
|
270
|
+
label: "Atlassian site",
|
|
271
|
+
help: "The host links are built from, such as acme.atlassian.net. Empty uses the one acli signed in to.",
|
|
272
|
+
kind: "text",
|
|
273
|
+
fallback: always("acli's own"),
|
|
274
|
+
advanced: true
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
key: "DAILY_FOCUS_JIRA_POLL_MINUTES",
|
|
278
|
+
group: "jira",
|
|
279
|
+
label: "Read every",
|
|
280
|
+
help: "Minutes between reads while a tab is open.",
|
|
281
|
+
kind: "number",
|
|
282
|
+
fallback: always("15 minutes"),
|
|
283
|
+
advanced: true
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
key: "DAILY_FOCUS_ACLI",
|
|
287
|
+
group: "jira",
|
|
288
|
+
label: "Path to acli",
|
|
289
|
+
help: "Only needed when the dashboard can't find the Atlassian CLI on its own.",
|
|
290
|
+
kind: "text",
|
|
291
|
+
fallback: always("Found on PATH"),
|
|
292
|
+
advanced: true
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
key: "DAILY_FOCUS_CALENDAR",
|
|
296
|
+
group: "calendar",
|
|
297
|
+
label: "Live agenda",
|
|
298
|
+
help: "Reads today's events from Calendar.app on a Mac, so a meeting declined at eleven frees the slot. Off uses the events in the brief.",
|
|
299
|
+
kind: "flag",
|
|
300
|
+
fallback: always("On")
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
key: "DAILY_FOCUS_CALENDARS",
|
|
304
|
+
group: "calendar",
|
|
305
|
+
label: "Calendars",
|
|
306
|
+
help: "Names as Calendar.app shows them, comma-separated. Empty reads none, and the agenda uses the brief.",
|
|
307
|
+
kind: "list",
|
|
308
|
+
fallback: always("None")
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
key: "DAILY_FOCUS_CALENDAR_ADDRESSES",
|
|
312
|
+
group: "calendar",
|
|
313
|
+
label: "Your email addresses",
|
|
314
|
+
help: "Comma-separated. Used only to find your reply on an invitation, so a meeting you declined stops blocking your time.",
|
|
315
|
+
kind: "list",
|
|
316
|
+
fallback: always("None")
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
key: "DAILY_FOCUS_CALENDAR_POLL_MINUTES",
|
|
320
|
+
group: "calendar",
|
|
321
|
+
label: "Read every",
|
|
322
|
+
help: "Minutes between reads while a tab is open.",
|
|
323
|
+
kind: "number",
|
|
324
|
+
fallback: always("5 minutes"),
|
|
325
|
+
advanced: true
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
key: "DAILY_FOCUS_CALENDAR_APP",
|
|
329
|
+
group: "calendar",
|
|
330
|
+
label: "Path to the calendar helper",
|
|
331
|
+
help: "Only needed when the helper is somewhere other than where it was built.",
|
|
332
|
+
kind: "text",
|
|
333
|
+
fallback: always("The built copy"),
|
|
334
|
+
advanced: true
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
key: "DAILY_FOCUS_FREE_WINDOWS",
|
|
338
|
+
group: "day",
|
|
339
|
+
label: "Show free time",
|
|
340
|
+
help: "Find focus windows between meetings in your working hours. Off shows events only.",
|
|
341
|
+
kind: "flag",
|
|
342
|
+
fallback: (profile) => profile === "personal" ? "Off" : "On"
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
key: "DAILY_FOCUS_WORK_START",
|
|
346
|
+
group: "day",
|
|
347
|
+
label: "Working day starts at",
|
|
348
|
+
help: "Hour of the day, 0 to 23, when the brief doesn't say.",
|
|
349
|
+
kind: "number",
|
|
350
|
+
fallback: always("9")
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
key: "DAILY_FOCUS_WORK_END",
|
|
354
|
+
group: "day",
|
|
355
|
+
label: "Working day ends at",
|
|
356
|
+
help: "Hour of the day, 0 to 23, when the brief doesn't say.",
|
|
357
|
+
kind: "number",
|
|
358
|
+
fallback: always("17")
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
key: "DAILY_FOCUS_MIN_FREE_WINDOW",
|
|
362
|
+
group: "day",
|
|
363
|
+
label: "Shortest focus window",
|
|
364
|
+
help: "Minutes a gap has to last to count as time for focused work.",
|
|
365
|
+
kind: "number",
|
|
366
|
+
fallback: always("45 minutes")
|
|
367
|
+
}
|
|
368
|
+
];
|
|
369
|
+
var BY_KEY = new Map(SETTINGS.map((def) => [def.key, def]));
|
|
370
|
+
function present(record, key) {
|
|
371
|
+
const value = record[key];
|
|
372
|
+
return value === void 0 || value.trim() === "" ? null : value.trim();
|
|
373
|
+
}
|
|
374
|
+
function profileOf(sources, settings = sources.settings) {
|
|
375
|
+
const raw = (present(layerSettings(sources, settings), "DAILY_FOCUS_PROFILE") ?? "work").toLowerCase();
|
|
376
|
+
return PROFILES.includes(raw) ? raw : "work";
|
|
377
|
+
}
|
|
378
|
+
function describeSettings(sources) {
|
|
379
|
+
const profile = profileOf(sources);
|
|
380
|
+
const settings = SETTINGS.map((def) => {
|
|
381
|
+
const fixed = present(sources.fixed, def.key);
|
|
382
|
+
const saved = present(sources.settings, def.key);
|
|
383
|
+
const [value, source] = fixed !== null ? [fixed, "fixed"] : saved !== null ? [saved, "settings"] : [null, "default"];
|
|
384
|
+
return {
|
|
385
|
+
key: def.key,
|
|
386
|
+
group: def.group,
|
|
387
|
+
label: def.label,
|
|
388
|
+
help: def.help,
|
|
389
|
+
kind: def.kind,
|
|
390
|
+
choices: def.choices ?? null,
|
|
391
|
+
advanced: def.advanced === true,
|
|
392
|
+
value,
|
|
393
|
+
source,
|
|
394
|
+
fallback: def.fallback(profile)
|
|
395
|
+
};
|
|
396
|
+
});
|
|
397
|
+
return { groups: SETTING_GROUPS, settings, error: sources.settingsError };
|
|
398
|
+
}
|
|
399
|
+
function applySettingsChange(sources, change) {
|
|
400
|
+
const next = { ...sources.settings };
|
|
401
|
+
for (const [key, raw] of Object.entries(change)) {
|
|
402
|
+
if (LAUNCH_SETTINGS.includes(key)) {
|
|
403
|
+
return { error: `${key} says where the store is or how the server listens, so it is given when the dashboard starts, not here` };
|
|
404
|
+
}
|
|
405
|
+
if (!BY_KEY.has(key)) return { error: `${key} is not a setting` };
|
|
406
|
+
if (raw !== null && typeof raw !== "string") return { error: `${key} must be text, or null for the default` };
|
|
407
|
+
if (present(sources.fixed, key) !== null) {
|
|
408
|
+
return { error: `${key} is fixed in this dashboard, so it can't be changed here` };
|
|
409
|
+
}
|
|
410
|
+
const value = raw?.trim() ?? "";
|
|
411
|
+
if (value === "") delete next[key];
|
|
412
|
+
else next[key] = value;
|
|
413
|
+
}
|
|
414
|
+
try {
|
|
415
|
+
loadConfig(layerSettings(sources, next));
|
|
416
|
+
} catch (error) {
|
|
417
|
+
return { error: error instanceof Error ? error.message : String(error) };
|
|
418
|
+
}
|
|
419
|
+
return { settings: next };
|
|
420
|
+
}
|
|
421
|
+
async function saveSettings(path, settings) {
|
|
422
|
+
const sorted = {};
|
|
423
|
+
for (const key of Object.keys(settings).sort()) sorted[key] = settings[key];
|
|
424
|
+
await writeJsonAtomic(path, sorted);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// src/templates.ts
|
|
428
|
+
var FOCUS_TEMPLATE = `---
|
|
429
|
+
objective:
|
|
430
|
+
blocker:
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
<!--
|
|
434
|
+
Fill in the two lines above: the one thing you are actually trying to achieve right
|
|
435
|
+
now, and what is in the way today, if anything. Leaving \`objective\` blank is fine:
|
|
436
|
+
the dashboard shows a quiet reminder instead. Delete this file to turn the feature
|
|
437
|
+
off entirely.
|
|
438
|
+
|
|
439
|
+
The dashboard renders them at the top of the board, and the briefing agent ranks the
|
|
440
|
+
whole day against them \u2014 so an objective phrased as a project ("ship the thing")
|
|
441
|
+
gives it less to work with than one phrased as a next move ("get the staging path
|
|
442
|
+
working again").
|
|
443
|
+
|
|
444
|
+
Prose below the frontmatter is shown under the objective. Comments like this one are
|
|
445
|
+
not. Keep it short and keep it current. This file is the only input describing what
|
|
446
|
+
you are trying to do, as opposed to what other people sent you overnight.
|
|
447
|
+
-->
|
|
448
|
+
|
|
449
|
+
<!-- agent-only -->
|
|
450
|
+
Everything below this marker is read by the agent and never shown on the board. The
|
|
451
|
+
dashboard shows it only in the editor you open to change it.
|
|
452
|
+
|
|
453
|
+
Put context here that should steer the ranking but shouldn't be on screen during a
|
|
454
|
+
screen share: why this objective matters right now, who is watching it, what is
|
|
455
|
+
riding on it. The agent weighs it, and is told never to quote it into a title or
|
|
456
|
+
detail.
|
|
457
|
+
`;
|
|
458
|
+
var WORK_SOURCES_TEMPLATE = `# Sources
|
|
459
|
+
|
|
460
|
+
The personal half of your morning brief. The prompt next to this file
|
|
461
|
+
(\`prompt.md\`) says *what* to gather and how to judge it; this file says *who* and
|
|
462
|
+
*where*. The briefing agent reads it; the dashboard shows it only in its editor.
|
|
463
|
+
|
|
464
|
+
Replace the placeholders below and delete whatever doesn't apply. A section you leave
|
|
465
|
+
out is fine \u2014 the agent falls back to your primary calendar and connected accounts,
|
|
466
|
+
and reports the gap. A section that is *wrong* is worse than one that is missing.
|
|
467
|
+
|
|
468
|
+
## Identity
|
|
469
|
+
|
|
470
|
+
Meeting notes, Doc assignments and tickets attribute work by name rather than by
|
|
471
|
+
account, so the agent needs to know which name is yours before it can tell your action
|
|
472
|
+
items from everyone else's. Leaving this out doesn't fail loudly \u2014 it just quietly
|
|
473
|
+
stops raising anything from those sources.
|
|
474
|
+
|
|
475
|
+
- Name, as meeting notes and Doc assignments write it: \`Your Name\`
|
|
476
|
+
- Work email: \`you@example.com\`
|
|
477
|
+
- GitHub login: \`your-github-login\` \u2014 review requests and authorship are judged
|
|
478
|
+
against it
|
|
479
|
+
|
|
480
|
+
## Calendars
|
|
481
|
+
|
|
482
|
+
Query:
|
|
483
|
+
|
|
484
|
+
- the primary work calendar
|
|
485
|
+
- \`Some Shared Calendar\` \u2014 a second calendar whose events should still block time
|
|
486
|
+
|
|
487
|
+
Never query or include: \`Some Noisy Calendar\`.
|
|
488
|
+
|
|
489
|
+
## Holiday calendars
|
|
490
|
+
|
|
491
|
+
- \`Holidays in <where your colleagues are>\` \u2014 a holiday there means slow replies, and
|
|
492
|
+
it is worth saying so.
|
|
493
|
+
- \`Holidays in <where you are>\` \u2014 call one out today, or within the next 14 days.
|
|
494
|
+
|
|
495
|
+
## Recurring meeting notes
|
|
496
|
+
|
|
497
|
+
Documents to re-read on every run. Name the tab where it matters, since these tend to
|
|
498
|
+
be long and only one part is current:
|
|
499
|
+
|
|
500
|
+
- https://docs.google.com/document/d/<document id>/edit?tab=t.0 \u2014 what meeting it is
|
|
501
|
+
`;
|
|
502
|
+
var PERSONAL_SOURCES_TEMPLATE = `# Sources
|
|
503
|
+
|
|
504
|
+
The private half of your morning brief. The prompt next to this file
|
|
505
|
+
(\`prompt.md\`) says *what* to gather and how to judge it; this file says *who* and
|
|
506
|
+
*where*. The briefing agent reads it; the dashboard shows it only in its editor.
|
|
507
|
+
|
|
508
|
+
Replace the placeholders below and delete whatever doesn't apply. A section you leave
|
|
509
|
+
out is fine \u2014 the agent reports the gap. A section that is *wrong* is worse than one
|
|
510
|
+
that is missing.
|
|
511
|
+
|
|
512
|
+
## Identity
|
|
513
|
+
|
|
514
|
+
- Name: \`Your Name\`
|
|
515
|
+
- Personal email: \`you@personal.example\`
|
|
516
|
+
- GitHub login: \`your-github-login\` \u2014 review requests and authorship are judged
|
|
517
|
+
against it
|
|
518
|
+
|
|
519
|
+
## My weeks
|
|
520
|
+
|
|
521
|
+
What decides how much of a day is yours, so the agent can set the day's bounds: when
|
|
522
|
+
work ends, regular pickups, a rhythm that alternates week to week and how to tell
|
|
523
|
+
which week it is.
|
|
524
|
+
|
|
525
|
+
- Workdays end at 16:00; personal time is from then until 22:00.
|
|
526
|
+
|
|
527
|
+
## Calendars
|
|
528
|
+
|
|
529
|
+
- \`Personal\`
|
|
530
|
+
- \`Family\` \u2014 shared; its events block time too
|
|
531
|
+
|
|
532
|
+
Never query or include: \`Some Noisy Calendar\`.
|
|
533
|
+
|
|
534
|
+
## Holiday calendars
|
|
535
|
+
|
|
536
|
+
- \`Holidays in <where you live>\` \u2014 including school holidays, if there is one
|
|
537
|
+
|
|
538
|
+
## Email
|
|
539
|
+
|
|
540
|
+
Which tool reaches the account, and any senders to always raise or always skip.
|
|
541
|
+
|
|
542
|
+
## e-Boks
|
|
543
|
+
|
|
544
|
+
Which tool or CLI reaches it.
|
|
545
|
+
|
|
546
|
+
## Reminders
|
|
547
|
+
|
|
548
|
+
Which tool reaches Apple Reminders, and which lists to read. Unset means all of them.
|
|
549
|
+
|
|
550
|
+
## Messages
|
|
551
|
+
|
|
552
|
+
Which tool reaches Apple Messages, and any conversations to skip.
|
|
553
|
+
|
|
554
|
+
## GitHub
|
|
555
|
+
|
|
556
|
+
Which repositories or organisations are your own projects. Unset means everything
|
|
557
|
+
the login above has open.
|
|
558
|
+
`;
|
|
559
|
+
function sourcesTemplate(profile) {
|
|
560
|
+
return profile === "personal" ? PERSONAL_SOURCES_TEMPLATE : WORK_SOURCES_TEMPLATE;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
export {
|
|
564
|
+
SETTINGS,
|
|
565
|
+
describeSettings,
|
|
566
|
+
applySettingsChange,
|
|
567
|
+
saveSettings,
|
|
568
|
+
FOCUS_TEMPLATE,
|
|
569
|
+
sourcesTemplate
|
|
570
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -21,7 +21,8 @@ Usage
|
|
|
21
21
|
daily-focus --demo look around with sample data, in a throwaway store;
|
|
22
22
|
add --profile personal to see the personal profile
|
|
23
23
|
daily-focus service [--remove] keep it running in the background (macOS)
|
|
24
|
-
daily-focus init
|
|
24
|
+
daily-focus init [--profile <name>]
|
|
25
|
+
create the store with templates to edit by hand
|
|
25
26
|
daily-focus audit [--against <items.json>]
|
|
26
27
|
check the current brief against the contract
|
|
27
28
|
daily-focus seed [--force] write a sample brief to the store
|
|
@@ -31,13 +32,14 @@ Options
|
|
|
31
32
|
--data <dir> the store (default ~/.daily-focus)
|
|
32
33
|
--port <port> the port to listen on (default 4321)
|
|
33
34
|
--host <host> the address to listen on (default 127.0.0.1)
|
|
34
|
-
--profile <name> work or personal, for a new store
|
|
35
|
+
--profile <name> work or personal, for the demo or a new store's init
|
|
35
36
|
--no-open don't open the browser
|
|
36
37
|
--exit-with-stdin stop when standard input closes, for an app running it as a child
|
|
37
38
|
-v, --version print the version
|
|
38
39
|
-h, --help print this
|
|
39
40
|
|
|
40
|
-
|
|
41
|
+
The first three can also be set as DAILY_FOCUS_DATA, DAILY_FOCUS_PORT and
|
|
42
|
+
DAILY_FOCUS_HOST. Everything else is set up in the dashboard itself.`;
|
|
41
43
|
function openInBrowser(url) {
|
|
42
44
|
const [command, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
|
|
43
45
|
const child = spawn(command, args, { stdio: "ignore", detached: true });
|
|
@@ -49,11 +51,12 @@ function shouldOpen(flag) {
|
|
|
49
51
|
return flag && process.stdout.isTTY === true && !process.env.CI;
|
|
50
52
|
}
|
|
51
53
|
async function demo(open, profile) {
|
|
52
|
-
const { demoEnv, demoIntegrations, demoProfile, writeDemoStore } = await import("./demo-
|
|
54
|
+
const { DEMO_FIXED, demoEnv, demoIntegrations, demoProfile, writeDemoStore } = await import("./demo-KE6WDWMA.js");
|
|
53
55
|
const chosen = demoProfile(profile);
|
|
54
56
|
const store = await mkdtemp(join(tmpdir(), "daily-focus-demo-"));
|
|
55
57
|
process.on("exit", () => rmSync(store, { recursive: true, force: true }));
|
|
56
|
-
|
|
58
|
+
const now = /* @__PURE__ */ new Date();
|
|
59
|
+
await writeDemoStore(store, chosen, now);
|
|
57
60
|
await start(
|
|
58
61
|
open,
|
|
59
62
|
(url) => {
|
|
@@ -61,11 +64,11 @@ async function demo(open, profile) {
|
|
|
61
64
|
The demo is at ${url}, with sample data in a throwaway store (the ${chosen} profile).`);
|
|
62
65
|
console.log(" Nothing is connected and nothing runs. Stop it with Ctrl+C; the store goes with it.\n");
|
|
63
66
|
},
|
|
64
|
-
{ env: demoEnv(process.env, store,
|
|
67
|
+
{ env: demoEnv(process.env, store), fixed: DEMO_FIXED, integrations: demoIntegrations(chosen, now) }
|
|
65
68
|
);
|
|
66
69
|
}
|
|
67
70
|
async function start(open, onStarted, options = {}) {
|
|
68
|
-
const { runServer } = await import("./server-
|
|
71
|
+
const { runServer } = await import("./server-FFRNYEAO.js");
|
|
69
72
|
await runServer({
|
|
70
73
|
...options,
|
|
71
74
|
onStarted: (url) => {
|
|
@@ -130,11 +133,12 @@ async function main(argv) {
|
|
|
130
133
|
if (values.data !== void 0) process.env.DAILY_FOCUS_DATA = values.data;
|
|
131
134
|
if (values.port !== void 0) process.env.DAILY_FOCUS_PORT = values.port;
|
|
132
135
|
if (values.host !== void 0) process.env.DAILY_FOCUS_HOST = values.host;
|
|
133
|
-
if (values.profile !== void 0) process.env.DAILY_FOCUS_PROFILE = values.profile;
|
|
134
136
|
const open = values.open !== false;
|
|
135
137
|
const [command, ...rest] = positionals;
|
|
136
138
|
if (rest.length > 0) throw new Error(`unexpected ${rest[0]}; see daily-focus --help`);
|
|
137
139
|
const only = {
|
|
140
|
+
// The demo's, or init's, which saves it for a new store.
|
|
141
|
+
profile: ["start", "init"],
|
|
138
142
|
demo: ["start"],
|
|
139
143
|
"exit-with-stdin": ["start"],
|
|
140
144
|
open: ["start"],
|
|
@@ -152,6 +156,9 @@ async function main(argv) {
|
|
|
152
156
|
case void 0:
|
|
153
157
|
case "start":
|
|
154
158
|
if (values.demo) return demo(open, values.profile);
|
|
159
|
+
if (values.profile !== void 0) {
|
|
160
|
+
throw new Error("--profile is for --demo and init; a dashboard's profile is chosen on its setup screen, or in its settings");
|
|
161
|
+
}
|
|
155
162
|
if (IN_NPX_CACHE) {
|
|
156
163
|
console.log(" Running from npx. To keep it running in the background, install it:");
|
|
157
164
|
console.log(" npm install -g daily-focus, then daily-focus service.\n");
|
|
@@ -162,20 +169,20 @@ async function main(argv) {
|
|
|
162
169
|
}
|
|
163
170
|
return start(open);
|
|
164
171
|
case "service": {
|
|
165
|
-
const { main: service } = await import("./service-
|
|
172
|
+
const { main: service } = await import("./service-LQ46YRCZ.js");
|
|
166
173
|
process.exitCode = await service(values.remove ? ["--remove"] : []);
|
|
167
174
|
return;
|
|
168
175
|
}
|
|
169
176
|
// These three are scripts that do their work as they load, and read their
|
|
170
177
|
// own flags from the command line.
|
|
171
178
|
case "init":
|
|
172
|
-
await import("./init-
|
|
179
|
+
await import("./init-5KJVHWJU.js");
|
|
173
180
|
return;
|
|
174
181
|
case "audit":
|
|
175
|
-
await import("./audit-
|
|
182
|
+
await import("./audit-AQUBVWPA.js");
|
|
176
183
|
return;
|
|
177
184
|
case "seed":
|
|
178
|
-
await import("./seed-
|
|
185
|
+
await import("./seed-T4VEY6DT.js");
|
|
179
186
|
return;
|
|
180
187
|
case "build-calendar":
|
|
181
188
|
return buildCalendar();
|