daily-focus 1.1.1-dev.3 → 1.2.0-dev.5

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 CHANGED
@@ -131,11 +131,13 @@ notes live too; the reply streams in there, and you can follow up in the same
131
131
  conversation. The panel stays on the item it was opened for while you move through
132
132
  the list.
133
133
 
134
- It reads and drafts, and that is all. An email reply is created as a Gmail draft on
135
- the thread, so it is waiting there when you open the mail; a reply to a reviewer is
136
- handed back as text for you to post. It never sends, posts, or edits a file, and it
137
- has no checkout: fixing conflicts or a build is work for a real coding session, and
138
- this is for the questions you would otherwise answer by opening six tabs.
134
+ It is told to read and draft, and nothing more. An email reply is created as a Gmail
135
+ draft on the thread, so it is waiting there when you open the mail; a reply to a
136
+ reviewer is handed back as text for you to post. It is told never to send, post, or
137
+ edit a file, and it has no checkout: fixing conflicts or a build is work for a real
138
+ coding session, and this is for the questions you would otherwise answer by opening
139
+ six tabs. How much of that the CLI itself enforces differs between Claude Code and
140
+ Codex; see [What the assistant can do](SETUP.md#what-the-assistant-can-do).
139
141
 
140
142
  ## The morning agent
141
143
 
package/SETUP.md CHANGED
@@ -126,14 +126,23 @@ it finishes, and a chat where you can ask it why it did what it did. See
126
126
  The agent reaches your sources the way the CLI does:
127
127
 
128
128
  - **Codex** runs in its workspace-write sandbox with network access, confined to the
129
- store. Connectors from the Codex app are found through its plugin catalogue. There
130
- is nobody to approve a command outside the sandbox, so the agent reports that
131
- restriction for any `gh` command that needs it, such as switching accounts.
129
+ store. Connectors from the Codex app are found through its plugin catalogue, and
130
+ every write through one is refused: a connector tool counts as a write unless it
131
+ is marked read-only. A command that needs to leave the sandbox, such as `gh`
132
+ switching accounts, is decided by your own Codex approval settings, as it would be
133
+ in your Codex. With nobody set to approve, the agent reports that restriction; with
134
+ Codex's automatic reviewer, the reviewer decides. The dashboard won't start a run
135
+ on a Codex older than 0.144.0, which would ignore the refusal of connector writes;
136
+ update Codex if a run fails saying so.
132
137
  - **Claude Code** may write `items.json` and nothing else in the store, and reach
133
138
  its sources with only what `DAILY_FOCUS_AGENT_TOOLS` allows. The default covers
134
139
  GitHub, Jira, web pages and the claude.ai Gmail connector. Add your calendar, chat
135
140
  and document connectors to the list by name, or the brief will report them as
136
- unreachable.
141
+ unreachable. The default allows every Gmail tool, sending included, and `gh` and
142
+ `acli` can write as well as read; the agent's instructions forbid changing
143
+ anything. To narrow Gmail to reading, name its tools instead of the connector, as
144
+ [What the assistant can do](#what-the-assistant-can-do) shows, without the two
145
+ draft tools.
137
146
 
138
147
  Set `DAILY_FOCUS_AGENT_MODEL` and `DAILY_FOCUS_AGENT_EFFORT` if the CLI's own
139
148
  defaults are not what you want for a brief; a brief is worth a high effort. If the
@@ -277,14 +286,53 @@ the CLI is configured with; `DAILY_FOCUS_ASSISTANT_MODEL` and
277
286
  `DAILY_FOCUS_ASSISTANT_EFFORT` override them. If the server can't find the CLI,
278
287
  set `DAILY_FOCUS_ASSISTANT_BIN` to its path.
279
288
 
280
- The assistant reads and drafts; it never sends, posts or edits anything, and it has
281
- no checkout to work in. That is deliberate. Fixing merge conflicts or a failing build
282
- belongs in a real coding session; this panel is for the questions you'd otherwise
283
- answer by opening six tabs. The Gmail draft is the one thing it creates, through the
284
- Gmail tool the CLI has: with Claude Code that is the claude.ai Gmail connector, which
285
- `DAILY_FOCUS_ASSISTANT_TOOLS` allows by default; with Codex it is the Gmail plugin
286
- from the Codex app, which the CLI finds through its plugin catalogue. Without either,
287
- the assistant hands you the text instead and says so.
289
+ The assistant is told to read and draft, and never to send, post or edit anything,
290
+ and it has no checkout to work in. That is deliberate. Fixing merge conflicts or a
291
+ failing build belongs in a real coding session; this panel is for the questions you'd
292
+ otherwise answer by opening six tabs. The Gmail draft is the one thing it creates,
293
+ and revises when you ask, through the Gmail tool the CLI has: with Claude Code that
294
+ is the claude.ai Gmail connector, which `DAILY_FOCUS_ASSISTANT_TOOLS` allows by
295
+ default; with Codex it is the Gmail app linked to your ChatGPT account, which the CLI
296
+ finds through its plugin catalogue. Without either, the assistant hands you the text
297
+ instead and says so.
298
+
299
+ ### What the assistant can do
300
+
301
+ The assistant runs the CLI you already use, with the access that CLI already has.
302
+ The dashboard narrows some of that, and leaves the rest to the assistant's
303
+ instructions. What the CLI itself enforces:
304
+
305
+ - **Claude Code** may use only the tools in `DAILY_FOCUS_ASSISTANT_TOOLS`, and never
306
+ its file-editing tools, whatever the list says; anything else is refused rather
307
+ than asked about. Rules in your own Claude Code settings that allow a tool still
308
+ apply. The default list is broader than the assistant needs:
309
+ `mcp__claude_ai_Gmail` allows every tool on the Gmail connector, sending and
310
+ deleting included, and `gh` and `acli` can comment, merge and move tickets as well
311
+ as read. Only the instructions keep it from doing those. To hold it to reading
312
+ mail and drafting replies, name the Gmail tools instead of the connector:
313
+
314
+ ```dotenv
315
+ DAILY_FOCUS_ASSISTANT_TOOLS=Bash(gh *),Bash(acli *),WebFetch,mcp__claude_ai_Gmail__search_threads,mcp__claude_ai_Gmail__get_thread,mcp__claude_ai_Gmail__get_message,mcp__claude_ai_Gmail__list_labels,mcp__claude_ai_Gmail__list_drafts,mcp__claude_ai_Gmail__get_draft,mcp__claude_ai_Gmail__create_draft,mcp__claude_ai_Gmail__update_draft
316
+ ```
317
+
318
+ A tool left off the list is refused, so if the connector renames one, the
319
+ assistant reports that it can't reach your mail rather than working around it.
320
+ - **Codex** refuses every write through a connector (Gmail, Calendar, Drive, GitHub
321
+ and the other apps your ChatGPT account links) except creating and revising a
322
+ Gmail draft. A connector tool counts as a write unless it is marked read-only. A
323
+ tool you have told Codex to always allow, or an app given approval settings of its
324
+ own in your Codex config, keeps them. Computer use, the plugins that act in your
325
+ apps and browser with your sign-ins, is off unless you turn on
326
+ `DAILY_FOCUS_ASSISTANT_COMPUTER_USE`; the dashboard checks with Codex that it is
327
+ off before each session, and refuses to start one it can't confirm. Shell
328
+ commands follow your own Codex config: the sandbox keeps writes in the
329
+ assistant's working directory, and a command that needs to leave it is decided
330
+ by your Codex approval settings, as it would be in your own Codex. That keeps
331
+ `gh` working, and means it can write. All of this needs Codex 0.149.0 or
332
+ newer, and the page warns when yours is older. Before 0.144.0 Codex silently
333
+ ignores the refusal of connector writes, so the dashboard won't start the
334
+ assistant or the morning agent on it at all; before 0.149.0 it can't switch
335
+ computer use off, so the assistant won't answer while that setting is off.
288
336
 
289
337
  Its instructions live in `prompts/assistant.md`, linked into the store as
290
338
  `assistant.md` each time the dashboard starts. What it says stays in the chat in the panel: it
@@ -493,13 +541,14 @@ matters when `DAILY_FOCUS_FREE_WINDOWS` is off.
493
541
  | `DAILY_FOCUS_ASSISTANT_BIN` | *the CLI's name* | Path to the assistant's CLI, for when the server's PATH lacks it. `~` is expanded |
494
542
  | `DAILY_FOCUS_ASSISTANT_MODEL` | *the CLI's own* | Model passed to the CLI untouched: an alias like `opus`, or a full id. Unset passes no flag |
495
543
  | `DAILY_FOCUS_ASSISTANT_EFFORT` | *the CLI's own* | Effort passed to the CLI untouched: `low`, `medium`, `high`, `xhigh` or `max`. Unset passes no flag |
496
- | `DAILY_FOCUS_ASSISTANT_TOOLS` | `Bash(gh *),Bash(acli *),WebFetch,mcp__claude_ai_Gmail` | What Claude Code may use without asking, in its permission syntax, comma-separated. Editing tools are denied regardless. Ignored by Codex |
544
+ | `DAILY_FOCUS_ASSISTANT_TOOLS` | `Bash(gh *),Bash(acli *),WebFetch,mcp__claude_ai_Gmail` | What Claude Code may use without asking, in its permission syntax, comma-separated. Editing tools are denied regardless. The default allows every Gmail tool, sending included; see [What the assistant can do](#what-the-assistant-can-do) to narrow it. Ignored by Codex |
545
+ | `DAILY_FOCUS_ASSISTANT_COMPUTER_USE` | `off` | `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. Ignored by Claude Code, which uses only the tools in its list |
497
546
  | `DAILY_FOCUS_AGENT` | `off` | `codex` or `claude` lets the dashboard run the morning agent through that CLI: each scheduled morning, and from the refresh icon beside the brief's age. `off` hides it |
498
547
  | `DAILY_FOCUS_AGENT_AT` | `07:00` | Local time, `HH:MM`, at which the dashboard starts the agent on each scheduled day, or as soon as it is running after that. `off` means it never starts one on its own; the refresh icon still does. Needs `DAILY_FOCUS_AGENT` |
499
548
  | `DAILY_FOCUS_AGENT_BIN` | *the CLI's name* | Path to that CLI, for when the server's PATH lacks it. `~` is expanded |
500
549
  | `DAILY_FOCUS_AGENT_MODEL` | *the CLI's own* | Model passed to the CLI untouched. Unset passes no flag |
501
550
  | `DAILY_FOCUS_AGENT_EFFORT` | *the CLI's own* | Effort passed to the CLI untouched. Unset passes no flag |
502
- | `DAILY_FOCUS_AGENT_TOOLS` | `Bash(gh *),Bash(acli *),WebFetch,mcp__claude_ai_Gmail` | What Claude Code may use to reach your sources, in its permission syntax, comma-separated. Writing `items.json`, and nothing else in the store, is allowed regardless. Ignored by Codex |
551
+ | `DAILY_FOCUS_AGENT_TOOLS` | `Bash(gh *),Bash(acli *),WebFetch,mcp__claude_ai_Gmail` | What Claude Code may use to reach your sources, in its permission syntax, comma-separated. Writing `items.json`, and nothing else in the store, is allowed regardless. The default allows every Gmail tool, sending included. Ignored by Codex |
503
552
 
504
553
  ## If something looks wrong
505
554
 
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  readSystemZone,
3
3
  startingZone
4
- } from "./chunk-AUBJ2IDQ.js";
4
+ } from "./chunk-AIQO2YAI.js";
5
5
  import {
6
6
  linkState
7
7
  } from "./chunk-QKT2FAB5.js";
@@ -12,7 +12,7 @@ import {
12
12
  readArchiveIndex,
13
13
  resolveItems,
14
14
  runContractChecks
15
- } from "./chunk-C6FXWHGT.js";
15
+ } from "./chunk-26JG3D5C.js";
16
16
  import {
17
17
  BRIEF_REFRESH_GRACE_HOURS,
18
18
  describeSchedule,
@@ -22,7 +22,7 @@ import {
22
22
  resolveSchedule,
23
23
  runsOn,
24
24
  workingMsBetween
25
- } from "./chunk-NWYOCDUU.js";
25
+ } from "./chunk-RNE27FQY.js";
26
26
  import {
27
27
  command
28
28
  } from "./chunk-BC2W7NND.js";
@@ -14,7 +14,7 @@ import {
14
14
  startOfLocalDay,
15
15
  workingDaysSince,
16
16
  workingMsBetween
17
- } from "./chunk-NWYOCDUU.js";
17
+ } from "./chunk-RNE27FQY.js";
18
18
 
19
19
  // src/fs.ts
20
20
  import { mkdir, rename, writeFile } from "node:fs/promises";
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  writeJsonAtomic
3
- } from "./chunk-C6FXWHGT.js";
3
+ } from "./chunk-26JG3D5C.js";
4
4
  import {
5
5
  localTimeZone
6
- } from "./chunk-NWYOCDUU.js";
6
+ } from "./chunk-RNE27FQY.js";
7
7
 
8
8
  // src/zone.ts
9
9
  import { readFile, readlink } from "node:fs/promises";
@@ -353,7 +353,8 @@ function envAssistant(env) {
353
353
  binPath: expandHome((env.DAILY_FOCUS_ASSISTANT_BIN ?? "").trim() || (agent ?? "claude")),
354
354
  model: envString("DAILY_FOCUS_ASSISTANT_MODEL", "", env) || null,
355
355
  effort: envString("DAILY_FOCUS_ASSISTANT_EFFORT", "", env) || null,
356
- tools: tools.length > 0 ? tools : DEFAULT_ASSISTANT_TOOLS
356
+ tools: tools.length > 0 ? tools : DEFAULT_ASSISTANT_TOOLS,
357
+ computerUse: envFlag("DAILY_FOCUS_ASSISTANT_COMPUTER_USE", false, env)
357
358
  };
358
359
  }
359
360
  var DEFAULT_ASSISTANT_TOOLS = ["Bash(gh *)", "Bash(acli *)", "WebFetch", "mcp__claude_ai_Gmail"];
@@ -1,3 +1,105 @@
1
+ // src/codex.ts
2
+ import { execFile } from "node:child_process";
3
+ import { promisify } from "node:util";
4
+ var GMAIL_CONNECTOR = "connector_2128aebfecb84f64a069897515042a44";
5
+ var CONNECTOR_POLICY = [
6
+ 'apps._default.default_tools_approval_mode="writes"',
7
+ 'apps._default.approvals_reviewer="user"'
8
+ ];
9
+ var GMAIL_DRAFTS = ["create_draft", "update_draft"].map(
10
+ (tool) => `apps.${GMAIL_CONNECTOR}.tools.${tool}.approval_mode="approve"`
11
+ );
12
+ var COMPUTER_USE_PLUGINS = [
13
+ "unified-computer-use@openai-bundled",
14
+ "computer-use@openai-bundled",
15
+ "browser@openai-bundled",
16
+ "chrome@openai-bundled"
17
+ ];
18
+ var COMPUTER_USE_SERVERS = ["node_repl", "cua_repl", "computer-use"];
19
+ var LIST_TIMEOUT_MS = 3e4;
20
+ var exec = promisify(execFile);
21
+ var defaultExec = async (file, args, { timeout }) => {
22
+ try {
23
+ const { stdout } = await exec(file, args, { timeout, maxBuffer: 16 * 1024 * 1024 });
24
+ return stdout;
25
+ } catch (err) {
26
+ const stderr = String(err.stderr ?? "").trim();
27
+ if (!stderr) throw err;
28
+ throw new Error(stderr.split("\n").slice(0, 2).join(" "));
29
+ }
30
+ };
31
+ function configArgs(overrides) {
32
+ return overrides.flatMap((override) => ["-c", override]);
33
+ }
34
+ async function enabledServers(bin, overrides, run) {
35
+ const output = await run(bin, ["mcp", "list", "--json", ...configArgs(overrides)], { timeout: LIST_TIMEOUT_MS });
36
+ let servers;
37
+ try {
38
+ servers = JSON.parse(output);
39
+ } catch {
40
+ throw new Error("codex mcp list --json printed something other than JSON");
41
+ }
42
+ if (!Array.isArray(servers)) throw new Error("codex mcp list --json printed something other than a list");
43
+ return servers.filter((server) => typeof server?.name === "string").filter((server) => server.enabled !== false).map((server) => server.name);
44
+ }
45
+ async function computerUseOff(bin, run = defaultExec) {
46
+ const overrides = COMPUTER_USE_PLUGINS.map((plugin) => `plugins.${plugin}.enabled=false`);
47
+ const left = (await enabledServers(bin, overrides, run)).filter((name) => COMPUTER_USE_SERVERS.includes(name));
48
+ if (left.length === 0) return overrides;
49
+ overrides.push(...left.map((name) => `mcp_servers.${name}.enabled=false`));
50
+ const still = (await enabledServers(bin, overrides, run)).filter((name) => COMPUTER_USE_SERVERS.includes(name));
51
+ if (still.length > 0) throw new Error(`Codex still starts ${still.join(", ")}`);
52
+ return overrides;
53
+ }
54
+ var CODEX_CONNECTOR_POLICY = "0.144.0";
55
+ var CODEX_MINIMUM = "0.149.0";
56
+ function versionParts(text) {
57
+ const match = /(\d+)\.(\d+)\.(\d+)/.exec(text);
58
+ return match ? match.slice(1, 4).map(Number) : null;
59
+ }
60
+ function olderThan(version, than) {
61
+ const a = versionParts(version);
62
+ const b = versionParts(than);
63
+ if (!a || !b) return false;
64
+ for (let i = 0; i < 3; i++) {
65
+ if (a[i] !== b[i]) return a[i] < b[i];
66
+ }
67
+ return false;
68
+ }
69
+ async function codexVersion(bin, run = defaultExec) {
70
+ try {
71
+ return versionParts(await run(bin, ["--version"], { timeout: LIST_TIMEOUT_MS }))?.join(".") ?? null;
72
+ } catch {
73
+ return null;
74
+ }
75
+ }
76
+ function refusalFor(version) {
77
+ if (!version || !olderThan(version, CODEX_CONNECTOR_POLICY)) return null;
78
+ return `Codex ${version} is older than ${CODEX_CONNECTOR_POLICY}, which ignores the dashboard's refusal of connector writes without saying so, so the dashboard won't run it. Update Codex`;
79
+ }
80
+ var CodexGate = class {
81
+ #bin;
82
+ #run;
83
+ #cleared = false;
84
+ constructor(bin, run = defaultExec) {
85
+ this.#bin = bin;
86
+ this.#run = run;
87
+ }
88
+ async refusal() {
89
+ if (this.#cleared) return null;
90
+ const version = await codexVersion(this.#bin, this.#run);
91
+ const refusal = refusalFor(version);
92
+ if (version && !refusal) this.#cleared = true;
93
+ return refusal;
94
+ }
95
+ };
96
+ async function codexVersionWarning(bin, run = defaultExec) {
97
+ const version = await codexVersion(bin, run);
98
+ if (!version || !olderThan(version, CODEX_MINIMUM)) return null;
99
+ const consequence = olderThan(version, CODEX_CONNECTOR_POLICY) ? `Before ${CODEX_CONNECTOR_POLICY} Codex ignores the dashboard's refusal of connector writes without saying so, so the dashboard won't start the agent or the assistant on it.` : `Before ${CODEX_MINIMUM} Codex can't switch computer use off, so the assistant won't answer while computer use is off in its settings.`;
100
+ return `Codex ${version} is older than ${CODEX_MINIMUM}, the oldest the dashboard's limits on Codex work with. ${consequence} Update Codex.`;
101
+ }
102
+
1
103
  // src/assistant.ts
2
104
  import { spawn } from "node:child_process";
3
105
  import { randomUUID } from "node:crypto";
@@ -166,13 +268,20 @@ var claudeAdapter = {
166
268
  }
167
269
  };
168
270
  var codexAdapter = {
169
- args({ prompt, sessionId, model, effort }) {
271
+ args({ prompt, sessionId, model, effort, drafts, codexConfig }) {
170
272
  const args = ["exec"];
171
273
  if (sessionId) args.push("resume");
172
274
  args.push("--json", "--skip-git-repo-check");
173
- args.push("-c", 'sandbox_mode="workspace-write"', "-c", "sandbox_workspace_write.network_access=true");
174
- if (model) args.push("-c", `model=${JSON.stringify(model)}`);
175
- if (effort) args.push("-c", `model_reasoning_effort=${JSON.stringify(effort)}`);
275
+ const config = [
276
+ 'sandbox_mode="workspace-write"',
277
+ "sandbox_workspace_write.network_access=true",
278
+ ...CONNECTOR_POLICY,
279
+ ...drafts ? GMAIL_DRAFTS : [],
280
+ ...codexConfig
281
+ ];
282
+ if (model) config.push(`model=${JSON.stringify(model)}`);
283
+ if (effort) config.push(`model_reasoning_effort=${JSON.stringify(effort)}`);
284
+ args.push(...configArgs(config));
176
285
  if (sessionId) args.push(sessionId);
177
286
  args.push(prompt);
178
287
  return args;
@@ -219,10 +328,17 @@ var Assistant = class _Assistant {
219
328
  config;
220
329
  hooks;
221
330
  spawnFn;
222
- constructor(config, hooks, spawnFn = spawn) {
331
+ execFn;
332
+ /** Refuses a Codex too old to hold to the connector rules; see `codex.ts`. */
333
+ codexGate;
334
+ /** What switches computer use off, once Codex has said; see `computerUseConfig`. */
335
+ codexOff = null;
336
+ constructor(config, hooks, spawnFn = spawn, execFn) {
223
337
  this.config = config;
224
338
  this.hooks = hooks;
225
339
  this.spawnFn = spawnFn;
340
+ this.execFn = execFn;
341
+ this.codexGate = new CodexGate(config.assistant.binPath, execFn);
226
342
  }
227
343
  get enabled() {
228
344
  return this.config.assistant.agent !== null;
@@ -329,6 +445,19 @@ ${typed}` : action.request : typed;
329
445
  });
330
446
  const prompt = previous ? resolved.request : composePrompt(await this.instructions(), context, resolved.request, this.config.profile);
331
447
  await mkdir(this.config.assistantDir, { recursive: true });
448
+ const refusal = agent === "codex" ? await this.codexGate.refusal() : null;
449
+ if (refusal) {
450
+ await this.finish(turn, { ok: false, error: refusal });
451
+ return turn;
452
+ }
453
+ let codexConfig;
454
+ try {
455
+ codexConfig = await this.computerUseConfig();
456
+ } catch (err) {
457
+ const error = err.code === "ENOENT" ? this.spawnError(err) : `computer use is off in the settings, and Codex could not confirm it: ${err.message}. Update Codex, or turn computer use on for the assistant`;
458
+ await this.finish(turn, { ok: false, error });
459
+ return turn;
460
+ }
332
461
  const adapter = ADAPTERS[agent];
333
462
  const args = adapter.args({
334
463
  prompt,
@@ -336,7 +465,9 @@ ${typed}` : action.request : typed;
336
465
  model: this.config.assistant.model,
337
466
  effort: this.config.assistant.effort,
338
467
  tools: this.config.assistant.tools,
339
- denyEdits: true
468
+ denyEdits: true,
469
+ drafts: true,
470
+ codexConfig
340
471
  });
341
472
  let child;
342
473
  try {
@@ -421,6 +552,20 @@ ${event.text}` : event.text;
421
552
  }
422
553
  this.hooks.onChange();
423
554
  }
555
+ /**
556
+ * What switches computer use off, when the assistant runs Codex and the user
557
+ * hasn't turned it on. Asked of Codex once and kept for the life of the
558
+ * process; settings that change it restart the dashboard anyway. A failure
559
+ * isn't kept, so the next turn asks again.
560
+ */
561
+ computerUseConfig() {
562
+ if (this.config.assistant.agent !== "codex" || this.config.assistant.computerUse) return Promise.resolve([]);
563
+ this.codexOff ??= computerUseOff(this.config.assistant.binPath, this.execFn).catch((err) => {
564
+ this.codexOff = null;
565
+ throw err;
566
+ });
567
+ return this.codexOff;
568
+ }
424
569
  spawnError(err) {
425
570
  const code = err.code;
426
571
  const bin = this.config.assistant.binPath;
@@ -450,6 +595,8 @@ ${event.text}` : event.text;
450
595
  };
451
596
 
452
597
  export {
598
+ CodexGate,
599
+ codexVersionWarning,
453
600
  QUICK_ACTIONS,
454
601
  ADAPTERS,
455
602
  Assistant
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  localDateKey
3
- } from "./chunk-NWYOCDUU.js";
3
+ } from "./chunk-RNE27FQY.js";
4
4
 
5
5
  // src/sample.ts
6
6
  function timeOn(now, hour, minute = 0) {
package/dist/cli.js CHANGED
@@ -49,7 +49,7 @@ 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-AMK3Y4JZ.js");
52
+ const { demoEnv, demoIntegrations, demoProfile, writeDemoStore } = await import("./demo-KVFAVGO7.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 }));
@@ -65,7 +65,7 @@ async function demo(open, profile) {
65
65
  );
66
66
  }
67
67
  async function start(open, onStarted, options = {}) {
68
- const { runServer } = await import("./server-5HCA62YY.js");
68
+ const { runServer } = await import("./server-SOCS3SZJ.js");
69
69
  await runServer({
70
70
  ...options,
71
71
  onStarted: (url) => {
@@ -162,20 +162,20 @@ async function main(argv) {
162
162
  }
163
163
  return start(open);
164
164
  case "service": {
165
- const { main: service } = await import("./service-63U4WBUH.js");
165
+ const { main: service } = await import("./service-NAAAV67B.js");
166
166
  process.exitCode = await service(values.remove ? ["--remove"] : []);
167
167
  return;
168
168
  }
169
169
  // These three are scripts that do their work as they load, and read their
170
170
  // own flags from the command line.
171
171
  case "init":
172
- await import("./init-YGOPPELJ.js");
172
+ await import("./init-57WHZZNG.js");
173
173
  return;
174
174
  case "audit":
175
- await import("./audit-B5GKGH2P.js");
175
+ await import("./audit-ITRXIX6T.js");
176
176
  return;
177
177
  case "seed":
178
- await import("./seed-57RJGU4Y.js");
178
+ await import("./seed-VALN2TCQ.js");
179
179
  return;
180
180
  case "build-calendar":
181
181
  return buildCalendar();
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  QUICK_ACTIONS
3
- } from "./chunk-GFCWPYEI.js";
3
+ } from "./chunk-SX5FPS3A.js";
4
4
  import {
5
5
  dateFromNow,
6
6
  sampleBrief,
7
7
  sampleGeneratedAt
8
- } from "./chunk-YWHADSKR.js";
8
+ } from "./chunk-UY6HQLGE.js";
9
9
  import {
10
10
  PROFILES,
11
11
  localDateKey
12
- } from "./chunk-NWYOCDUU.js";
12
+ } from "./chunk-RNE27FQY.js";
13
13
  import "./chunk-BC2W7NND.js";
14
14
 
15
15
  // src/demo.ts
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-QKT2FAB5.js";
9
9
  import {
10
10
  loadConfig
11
- } from "./chunk-NWYOCDUU.js";
11
+ } from "./chunk-RNE27FQY.js";
12
12
  import {
13
13
  PACKAGED,
14
14
  command
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  sampleBrief
3
- } from "./chunk-YWHADSKR.js";
3
+ } from "./chunk-UY6HQLGE.js";
4
4
  import {
5
5
  Store
6
- } from "./chunk-C6FXWHGT.js";
6
+ } from "./chunk-26JG3D5C.js";
7
7
  import {
8
8
  loadConfig
9
- } from "./chunk-NWYOCDUU.js";
9
+ } from "./chunk-RNE27FQY.js";
10
10
  import {
11
11
  command
12
12
  } from "./chunk-BC2W7NND.js";
@@ -1,7 +1,9 @@
1
1
  import {
2
2
  ADAPTERS,
3
- Assistant
4
- } from "./chunk-GFCWPYEI.js";
3
+ Assistant,
4
+ CodexGate,
5
+ codexVersionWarning
6
+ } from "./chunk-SX5FPS3A.js";
5
7
  import {
6
8
  FOCUS_TEMPLATE,
7
9
  sourcesTemplate
@@ -9,7 +11,7 @@ import {
9
11
  import {
10
12
  TimeZoneKeeper,
11
13
  validZone
12
- } from "./chunk-AUBJ2IDQ.js";
14
+ } from "./chunk-AIQO2YAI.js";
13
15
  import {
14
16
  describeLink,
15
17
  linkStore,
@@ -24,7 +26,7 @@ import {
24
26
  stopSession,
25
27
  tempPathFor,
26
28
  writeJsonAtomic
27
- } from "./chunk-C6FXWHGT.js";
29
+ } from "./chunk-26JG3D5C.js";
28
30
  import {
29
31
  CLI_NAMES,
30
32
  LAUNCH_SETTINGS,
@@ -40,7 +42,7 @@ import {
40
42
  readDotEnv,
41
43
  resolveSchedule,
42
44
  scheduledRunDue
43
- } from "./chunk-NWYOCDUU.js";
45
+ } from "./chunk-RNE27FQY.js";
44
46
  import {
45
47
  PACKAGED,
46
48
  command,
@@ -2506,10 +2508,13 @@ var AgentRunner = class {
2506
2508
  config;
2507
2509
  hooks;
2508
2510
  spawnFn;
2509
- constructor(config, hooks, spawnFn = spawn) {
2511
+ /** Refuses a Codex too old to hold to the connector rules; see `codex.ts`. */
2512
+ codexGate;
2513
+ constructor(config, hooks, spawnFn = spawn, execFn) {
2510
2514
  this.config = config;
2511
2515
  this.hooks = hooks;
2512
2516
  this.spawnFn = spawnFn;
2517
+ this.codexGate = new CodexGate(config.agent.binPath, execFn);
2513
2518
  }
2514
2519
  get enabled() {
2515
2520
  return this.config.agent.cli !== null;
@@ -2632,7 +2637,9 @@ var AgentRunner = class {
2632
2637
  model: this.config.agent.model,
2633
2638
  effort: this.config.agent.effort,
2634
2639
  tools: [...briefWritingTools(), ...this.config.agent.tools],
2635
- denyEdits: false
2640
+ denyEdits: false,
2641
+ drafts: false,
2642
+ codexConfig: []
2636
2643
  });
2637
2644
  await this.spawnInto(run6, null, args, RUN_TIMEOUT_MS, "gave up after an hour");
2638
2645
  return run6;
@@ -2678,7 +2685,9 @@ var AgentRunner = class {
2678
2685
  effort: this.config.agent.effort,
2679
2686
  // The sources, and not the brief: a chat is given nothing to write.
2680
2687
  tools: this.config.agent.tools,
2681
- denyEdits: true
2688
+ denyEdits: true,
2689
+ drafts: false,
2690
+ codexConfig: []
2682
2691
  });
2683
2692
  await this.spawnInto(run6, turn, args, TURN_TIMEOUT_MS, "gave up after ten minutes");
2684
2693
  return turn;
@@ -2692,6 +2701,11 @@ var AgentRunner = class {
2692
2701
  const cli = run6.cli;
2693
2702
  const adapter = ADAPTERS[cli];
2694
2703
  await mkdir(this.config.dataDir, { recursive: true });
2704
+ const refusal2 = cli === "codex" ? await this.codexGate.refusal() : null;
2705
+ if (refusal2) {
2706
+ await this.finish(run6, turn, { ok: false, error: refusal2 });
2707
+ return;
2708
+ }
2695
2709
  let child;
2696
2710
  try {
2697
2711
  child = this.spawnFn(this.config.agent.binPath, args, {
@@ -3015,7 +3029,7 @@ var SETTINGS = [
3015
3029
  key: "DAILY_FOCUS_AGENT_TOOLS",
3016
3030
  group: "agent",
3017
3031
  label: "Tools Claude Code may use",
3018
- 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. Ignored by Codex.",
3032
+ 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.",
3019
3033
  kind: "list",
3020
3034
  fallback: always("Bash(gh *), Bash(acli *), WebFetch, mcp__claude_ai_Gmail"),
3021
3035
  advanced: true
@@ -3042,7 +3056,7 @@ var SETTINGS = [
3042
3056
  key: "DAILY_FOCUS_ASSISTANT",
3043
3057
  group: "assistant",
3044
3058
  label: "Answer questions with",
3045
- help: "Puts an assistant in every item's panel. It reads and drafts, and never sends, posts or edits.",
3059
+ 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.",
3046
3060
  kind: "choice",
3047
3061
  choices: OFF_OR_CLI,
3048
3062
  fallback: always("Off")
@@ -3068,11 +3082,19 @@ var SETTINGS = [
3068
3082
  key: "DAILY_FOCUS_ASSISTANT_TOOLS",
3069
3083
  group: "assistant",
3070
3084
  label: "Tools Claude Code may use",
3071
- help: "In Claude Code's permission syntax, comma-separated. Editing tools are denied whatever this says. Ignored by Codex.",
3085
+ 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.",
3072
3086
  kind: "list",
3073
3087
  fallback: always("Bash(gh *), Bash(acli *), WebFetch, mcp__claude_ai_Gmail"),
3074
3088
  advanced: true
3075
3089
  },
3090
+ {
3091
+ key: "DAILY_FOCUS_ASSISTANT_COMPUTER_USE",
3092
+ group: "assistant",
3093
+ label: "Computer use",
3094
+ 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.",
3095
+ kind: "flag",
3096
+ fallback: always("Off")
3097
+ },
3076
3098
  {
3077
3099
  key: "DAILY_FOCUS_ASSISTANT_BIN",
3078
3100
  group: "assistant",
@@ -3722,6 +3744,15 @@ async function startServer(env, options = {}) {
3722
3744
  restartWhenIdle();
3723
3745
  }
3724
3746
  });
3747
+ let codexWarnings = [];
3748
+ const codexBins = /* @__PURE__ */ new Set();
3749
+ if (config.agent.cli === "codex") codexBins.add(config.agent.binPath);
3750
+ if (config.assistant.agent === "codex") codexBins.add(config.assistant.binPath);
3751
+ void Promise.all([...codexBins].map((bin) => codexVersionWarning(bin))).then((found) => {
3752
+ codexWarnings = [...new Set(found.filter((warning) => warning !== null))];
3753
+ for (const warning of codexWarnings) console.warn(`[daily-focus] ${warning}`);
3754
+ if (codexWarnings.length > 0) void broadcast();
3755
+ });
3725
3756
  function requestRestart() {
3726
3757
  restart = options.onRestart ? "waiting" : "manual";
3727
3758
  restartWhenIdle();
@@ -3749,7 +3780,7 @@ async function startServer(env, options = {}) {
3749
3780
  tickets: tickets.view(actions, now),
3750
3781
  assistant: assistant.view(),
3751
3782
  agentRun: agent.view(now),
3752
- setupWarnings: [...configWarnings, ...linkWarning ? [linkWarning] : []],
3783
+ setupWarnings: [...configWarnings, ...codexWarnings, ...linkWarning ? [linkWarning] : []],
3753
3784
  restart,
3754
3785
  setup,
3755
3786
  assetVersion,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  loadConfig
3
- } from "./chunk-NWYOCDUU.js";
3
+ } from "./chunk-RNE27FQY.js";
4
4
  import {
5
5
  IN_NPX_CACHE,
6
6
  PACKAGED,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "daily-focus",
3
- "version": "1.1.1-dev.3",
3
+ "version": "1.2.0-dev.5",
4
4
  "description": "An interactive dashboard of what you should focus on today at work",
5
5
  "keywords": [
6
6
  "dashboard",
@@ -40,7 +40,7 @@
40
40
  },
41
41
  "daily-focus": {
42
42
  "appInterface": 1,
43
- "appSource": "0f59ad2ffabffbfd1054778996c4ee337199c92c"
43
+ "appSource": "288797ddc9f77355cc5ec176f1f21452d49fd3ae"
44
44
  },
45
45
  "scripts": {
46
46
  "start": "node src/server.ts",
package/public/app.js CHANGED
@@ -3777,7 +3777,7 @@ function HelpDialog(_props) {
3777
3777
  el(
3778
3778
  "p",
3779
3779
  { class: "help__note" },
3780
- `Done, snoozed and dismissed all tell the morning agent to leave an item out of tomorrow's brief. A note is sent along with it as free text. On both boards only snooze and note apply: a pull request stays until it's merged or closed, and a ticket whose status looks wrong is put right in Jira rather than here. Click a card to open its panel, where its notes and the assistant live; the panel stays on the item it was opened for. The assistant is a CLI run on your machine; it reads and drafts, never sends or posts. Click "updated \u2026 ago" in the header to open the morning agent's latest run: its report, earlier runs, and a place to ask it why it did what it did.`
3780
+ `Done, snoozed and dismissed all tell the morning agent to leave an item out of tomorrow's brief. A note is sent along with it as free text. On both boards only snooze and note apply: a pull request stays until it's merged or closed, and a ticket whose status looks wrong is put right in Jira rather than here. Click a card to open its panel, where its notes and the assistant live; the panel stays on the item it was opened for. The assistant is a CLI run on your machine, told to read and draft, never to send or post. Click "updated \u2026 ago" in the header to open the morning agent's latest run: its report, earlier runs, and a place to ask it why it did what it did.`
3781
3781
  ),
3782
3782
  el("form", { method: "dialog" }, el("button", { type: "submit", class: "button" }, "Close"))
3783
3783
  );