daily-focus 1.2.0-dev.9 → 2.0.0-dev.15

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.
Files changed (31) hide show
  1. package/README.md +18 -4
  2. package/SETUP.md +156 -105
  3. package/dist/{audit-DNTFZOHH.js → audit-AQUBVWPA.js} +4 -3
  4. package/dist/{chunk-KZNY45XW.js → chunk-72NUDSOV.js} +126 -119
  5. package/dist/{chunk-GQ3NCWRT.js → chunk-ITUK7XKS.js} +6 -19
  6. package/dist/{chunk-KL6RU4XT.js → chunk-JTMBBUMD.js} +316 -10
  7. package/dist/chunk-QG4XXKBP.js +18 -0
  8. package/dist/chunk-U6EPOXEW.js +570 -0
  9. package/dist/{chunk-P3E2B2SD.js → chunk-WPXIQPYX.js} +1 -1
  10. package/dist/{chunk-6SMUSPZG.js → chunk-YPGKXTBX.js} +2 -2
  11. package/dist/cli.js +19 -12
  12. package/dist/{demo-TIIXODJG.js → demo-KE6WDWMA.js} +159 -37
  13. package/dist/{init-XDMAXU2S.js → init-5KJVHWJU.js} +41 -6
  14. package/dist/{seed-TWT7EDXJ.js → seed-T4VEY6DT.js} +4 -3
  15. package/dist/{server-WT4CQ3VX.js → server-FFRNYEAO.js} +2000 -743
  16. package/dist/{service-ZUHI3VKH.js → service-LQ46YRCZ.js} +7 -6
  17. package/package.json +6 -2
  18. package/prompts/README.md +20 -4
  19. package/prompts/assistant.md +7 -1
  20. package/prompts/morning-brief-personal.md +37 -0
  21. package/prompts/morning-brief-work.md +37 -0
  22. package/prompts/onboarding.md +41 -0
  23. package/public/app.js +806 -287
  24. package/public/fonts/IBMPlexSans-Variable.woff2 +0 -0
  25. package/public/fonts/IBMPlexSerif-Italic.woff2 +0 -0
  26. package/public/fonts/IBMPlexSerif-Medium.woff2 +0 -0
  27. package/public/fonts/IBMPlexSerif-Regular.woff2 +0 -0
  28. package/public/fonts/IBMPlexSerif-SemiBold.woff2 +0 -0
  29. package/public/fonts/OFL.txt +93 -0
  30. package/public/style.css +1686 -481
  31. package/dist/chunk-V2UN3JYL.js +0 -140
@@ -1,3 +1,13 @@
1
+ import {
2
+ canonicalId,
3
+ foldActionLog
4
+ } from "./chunk-ITUK7XKS.js";
5
+ import {
6
+ laterISO,
7
+ localDateKey,
8
+ parseISO
9
+ } from "./chunk-72NUDSOV.js";
10
+
1
11
  // src/codex.ts
2
12
  import { execFile } from "node:child_process";
3
13
  import { promisify } from "node:util";
@@ -99,6 +109,18 @@ async function codexVersionWarning(bin, run = defaultExec) {
99
109
  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
110
  return `Codex ${version} is older than ${CODEX_MINIMUM}, the oldest the dashboard's limits on Codex work with. ${consequence} Update Codex.`;
101
111
  }
112
+ async function codexLatestModel(bin, run = defaultExec) {
113
+ try {
114
+ const catalog = JSON.parse(await run(bin, ["debug", "models"], { timeout: LIST_TIMEOUT_MS }));
115
+ const listed = (Array.isArray(catalog.models) ? catalog.models : []).filter(
116
+ (model) => typeof model === "object" && model !== null && typeof model.slug === "string" && typeof model.priority === "number" && model.visibility === "list"
117
+ );
118
+ listed.sort((a, b) => a.priority - b.priority);
119
+ return listed[0]?.slug ?? null;
120
+ } catch {
121
+ return null;
122
+ }
123
+ }
102
124
 
103
125
  // src/assistant.ts
104
126
  import { spawn } from "node:child_process";
@@ -106,6 +128,8 @@ import { randomUUID } from "node:crypto";
106
128
  import { appendFile, mkdir, readFile } from "node:fs/promises";
107
129
  import { createInterface } from "node:readline";
108
130
  var TURN_TIMEOUT_MS = 10 * 6e4;
131
+ var RESTARTED = "the dashboard restarted while this was running";
132
+ var SHUT_DOWN = "the dashboard shut down";
109
133
  var TURNS_SHOWN = 20;
110
134
  var STEPS_KEPT = 100;
111
135
  var DETAIL_LENGTH = 200;
@@ -191,6 +215,7 @@ function foldAssistantLog(text) {
191
215
  sessionId: null,
192
216
  request: record.request,
193
217
  action: typeof record.action === "string" ? record.action : null,
218
+ auto: typeof record.auto === "string" ? record.auto : null,
194
219
  startedAt: record.at,
195
220
  endedAt: null,
196
221
  status: "running",
@@ -250,7 +275,7 @@ function parseLine(line) {
250
275
  }
251
276
  }
252
277
  var claudeAdapter = {
253
- args({ prompt, sessionId, model, effort, tools, denyEdits }) {
278
+ args({ prompt, sessionId, model, effort, tools, denyEdits, schema }) {
254
279
  const args = [
255
280
  "-p",
256
281
  "--output-format",
@@ -265,6 +290,7 @@ var claudeAdapter = {
265
290
  if (tools.length > 0) args.push("--allowedTools", ...tools);
266
291
  if (model) args.push("--model", model);
267
292
  if (effort) args.push("--effort", effort);
293
+ if (schema) args.push("--json-schema", schema.json);
268
294
  if (sessionId) args.push("--resume", sessionId);
269
295
  args.push("--", prompt);
270
296
  return args;
@@ -273,6 +299,10 @@ var claudeAdapter = {
273
299
  const event = parseLine(line);
274
300
  if (!event) return null;
275
301
  const type = event.type;
302
+ if (type === "system" && event.subtype === "task_notification" && typeof event.tool_use_id === "string") {
303
+ const step = { id: event.tool_use_id, state: event.status === "completed" ? "done" : "failed", final: true };
304
+ return { steps: [step] };
305
+ }
276
306
  if (type === "system" && typeof event.session_id === "string") {
277
307
  return { sessionId: event.session_id };
278
308
  }
@@ -284,7 +314,9 @@ var claudeAdapter = {
284
314
  if (text) parsed.text = text;
285
315
  const steps = content.flatMap((block) => claudeStep(block) ?? []);
286
316
  if (steps.length) parsed.steps = steps;
287
- return text || steps.length ? parsed : null;
317
+ if (!text && !steps.length) return null;
318
+ if (typeof event.parent_tool_use_id === "string") parsed.by = event.parent_tool_use_id;
319
+ return parsed;
288
320
  }
289
321
  if (type === "result") {
290
322
  const ok = event.is_error !== true && event.subtype === "success";
@@ -297,10 +329,11 @@ var claudeAdapter = {
297
329
  }
298
330
  };
299
331
  var codexAdapter = {
300
- args({ prompt, sessionId, model, effort, drafts, codexConfig }) {
332
+ args({ prompt, sessionId, model, effort, drafts, codexConfig, schema }) {
301
333
  const args = ["exec"];
302
334
  if (sessionId) args.push("resume");
303
335
  args.push("--json", "--skip-git-repo-check");
336
+ if (schema) args.push("--output-schema", schema.file);
304
337
  const config = [
305
338
  'sandbox_mode="workspace-write"',
306
339
  "sandbox_workspace_write.network_access=true",
@@ -341,7 +374,9 @@ var ADAPTERS = {
341
374
  claude: claudeAdapter,
342
375
  codex: codexAdapter
343
376
  };
344
- var CLAUDE_QUIET_TOOLS = /* @__PURE__ */ new Set(["ToolSearch", "TodoWrite"]);
377
+ var CLAUDE_QUIET_TOOLS = /* @__PURE__ */ new Set(["ToolSearch", "TodoWrite", "StructuredOutput"]);
378
+ var HANDOFF = "Hand part of it to a subagent";
379
+ var WAIT = "Wait for its subagents";
345
380
  function claudeStep(block) {
346
381
  if (block.type === "tool_result" && typeof block.tool_use_id === "string") {
347
382
  return { id: block.tool_use_id, state: block.is_error === true ? "failed" : "done" };
@@ -366,7 +401,7 @@ function claudeStep(block) {
366
401
  return step("List files", said("pattern"));
367
402
  case "Task":
368
403
  case "Agent":
369
- return step("Hand part of it to a subagent", said("description"));
404
+ return { ...step(HANDOFF, said("description")), handoff: true };
370
405
  }
371
406
  const mcp = /^mcp__(.+?)__(.+)$/.exec(block.name);
372
407
  if (mcp) return step(connectorLabel(mcp[1], mcp[2]), compactArgs(input));
@@ -388,6 +423,7 @@ function codexStep(item, completed) {
388
423
  if (item.type === "mcp_tool_call" && typeof item.server === "string" && typeof item.tool === "string") {
389
424
  return { id: item.id, text: connectorLabel(item.server, item.tool), detail: compactArgs(item.arguments), state };
390
425
  }
426
+ if (item.type === "collab_tool_call" && item.tool === "wait") return { id: item.id, text: WAIT, detail: null, state };
391
427
  if (item.type === "web_search") {
392
428
  const action = item.action ?? {};
393
429
  if (action.type === "open_page") return { id: item.id, text: "Open a page", detail: shorten(action.url ?? null), state };
@@ -475,7 +511,7 @@ var Assistant = class _Assistant {
475
511
  for (const turn of this.turns.values()) {
476
512
  if (turn.status !== "running") continue;
477
513
  turn.status = "aborted";
478
- turn.error = "the dashboard restarted while this was running";
514
+ turn.error = RESTARTED;
479
515
  turn.endedAt = (/* @__PURE__ */ new Date()).toISOString();
480
516
  await this.append({
481
517
  event: "aborted",
@@ -488,6 +524,15 @@ var Assistant = class _Assistant {
488
524
  });
489
525
  }
490
526
  }
527
+ /**
528
+ * Every turn the dashboard started on its own, however old: what has been
529
+ * asked ahead is never asked again, and the panel's window of recent turns
530
+ * is no record of that.
531
+ */
532
+ autoTurns() {
533
+ return [...this.turns.values()].filter((turn) => turn.auto !== null);
534
+ }
535
+ /** The assistant's half of the state. Asking ahead is `autoask.ts`'s to add, since it needs the board. */
491
536
  view() {
492
537
  const items = {};
493
538
  for (const turn of this.turns.values()) {
@@ -520,9 +565,10 @@ ${typed}` : action.request : typed;
520
565
  }
521
566
  /**
522
567
  * Start a turn. Returns once the process is running, not once it has answered;
523
- * the answer arrives through `onChange` and the next state.
568
+ * the answer arrives through `onChange` and the next state. `auto` marks a
569
+ * turn the dashboard started on its own, with the key of what it answers.
524
570
  */
525
- async ask(ask, context) {
571
+ async ask(ask, context, options = {}) {
526
572
  const agent = this.config.assistant.agent;
527
573
  if (!agent) throw new Error("the assistant is off; set DAILY_FOCUS_ASSISTANT to claude or codex");
528
574
  if (this.running.has(ask.itemId)) throw new Error("the assistant is already working on this item");
@@ -539,6 +585,7 @@ ${typed}` : action.request : typed;
539
585
  sessionId: null,
540
586
  request: resolved.request,
541
587
  action: resolved.action,
588
+ auto: options.auto ?? null,
542
589
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
543
590
  endedAt: null,
544
591
  status: "running",
@@ -554,6 +601,7 @@ ${typed}` : action.request : typed;
554
601
  agent,
555
602
  request: turn.request,
556
603
  action: turn.action,
604
+ ...turn.auto ? { auto: turn.auto } : {},
557
605
  at: turn.startedAt
558
606
  });
559
607
  const prompt = previous ? resolved.request : composePrompt(await this.instructions(), context, resolved.request, this.config.profile);
@@ -653,7 +701,7 @@ ${typed}` : action.request : typed;
653
701
  }
654
702
  /** Kill everything, for shutdown. The exit handlers write the aborted lines. */
655
703
  async close() {
656
- for (const itemId of [...this.running.keys()]) await this.stop(itemId, "the dashboard shut down");
704
+ for (const itemId of [...this.running.keys()]) await this.stop(itemId, SHUT_DOWN);
657
705
  }
658
706
  async finish(turn, outcome, aborted = false) {
659
707
  const at = (/* @__PURE__ */ new Date()).toISOString();
@@ -720,10 +768,268 @@ ${typed}` : action.request : typed;
720
768
  }
721
769
  };
722
770
 
771
+ // src/autoask.ts
772
+ var AUTO_ACTIONS = ["assess-review", "explain-ci"];
773
+ function autoKeys(row) {
774
+ if (row.court !== "you" || row.status !== "open") return [];
775
+ const keys = [];
776
+ const heard = row.reasons.filter((reason) => reason.kind === "changes-requested" || reason.kind === "activity").map((reason) => reason.at ?? "");
777
+ if (heard.length > 0) keys.push(`assess-review@${heard.sort().at(-1) || row.updatedAt}`);
778
+ if (row.reasons.some((reason) => reason.kind === "ci-failing")) {
779
+ const commit = row.headSha ?? row.lastActivityByYou ?? row.updatedAt;
780
+ keys.push(`explain-ci@${commit}:${[...row.failingChecks].sort().join(",")}`);
781
+ }
782
+ return keys;
783
+ }
784
+ function actionOf(key) {
785
+ const action = key.slice(0, key.indexOf("@"));
786
+ return AUTO_ACTIONS.includes(action) ? action : null;
787
+ }
788
+ var SETTLE_MS = 1e3;
789
+ var AutoAsk = class {
790
+ deps;
791
+ timer = null;
792
+ /** A look is under way: one at a time, so two can't both start a turn. */
793
+ looking = false;
794
+ /** Something changed while a look was under way, so look again after it. */
795
+ again = false;
796
+ constructor(deps) {
797
+ this.deps = deps;
798
+ }
799
+ get enabled() {
800
+ return this.deps.config.assistant.auto && this.deps.assistant.enabled;
801
+ }
802
+ /** Something changed — the board, or a turn — so look again shortly. Coalesced. */
803
+ poke() {
804
+ if (!this.enabled || this.timer) return;
805
+ this.timer = setTimeout(() => {
806
+ this.timer = null;
807
+ void this.consider().catch((err) => {
808
+ console.error(`[daily-focus] could not ask ahead: ${err.message}`);
809
+ });
810
+ }, SETTLE_MS);
811
+ this.timer.unref();
812
+ }
813
+ /**
814
+ * Start the next question the board calls for, if one may start now. Returns
815
+ * its key, or null when nothing was started. Each finished turn pokes, which
816
+ * is what takes the queue on to the next.
817
+ */
818
+ async consider() {
819
+ if (!this.enabled || !this.deps.canStart()) return null;
820
+ if (this.looking) {
821
+ this.again = true;
822
+ return null;
823
+ }
824
+ this.looking = true;
825
+ try {
826
+ return await this.look();
827
+ } finally {
828
+ this.looking = false;
829
+ if (this.again) {
830
+ this.again = false;
831
+ this.poke();
832
+ }
833
+ }
834
+ }
835
+ async look() {
836
+ const { assistant } = this.deps;
837
+ await assistant.start();
838
+ const asked = assistant.autoTurns();
839
+ if (asked.some((turn) => turn.status === "running")) return null;
840
+ if (this.usedToday(asked) >= this.deps.config.assistant.autoLimit) return null;
841
+ const items = assistant.view().items;
842
+ const cutShort = (turn) => turn.status === "aborted" && (turn.error === RESTARTED || turn.error === SHUT_DOWN);
843
+ const done = new Set(asked.filter((turn) => !cutShort(turn)).map((turn) => `${turn.itemId} ${turn.auto}`));
844
+ for (const row of await this.deps.rows()) {
845
+ if (items[row.id]?.running) continue;
846
+ for (const key of autoKeys(row)) {
847
+ const action = actionOf(key);
848
+ if (!action || done.has(`${row.id} ${key}`)) continue;
849
+ try {
850
+ await assistant.ask({ itemId: row.id, action }, await this.deps.context(row.id), { auto: key });
851
+ return key;
852
+ } catch {
853
+ return null;
854
+ }
855
+ }
856
+ }
857
+ return null;
858
+ }
859
+ /** Asking ahead as the page shows it, for the board's rows as they are now. */
860
+ view(rows) {
861
+ const keyed = {};
862
+ for (const row of rows) {
863
+ const keys = autoKeys(row);
864
+ if (keys.length > 0) keyed[row.id] = keys;
865
+ }
866
+ return {
867
+ enabled: this.enabled,
868
+ limit: this.deps.config.assistant.autoLimit,
869
+ usedToday: this.usedToday(this.deps.assistant.autoTurns()),
870
+ rows: keyed
871
+ };
872
+ }
873
+ close() {
874
+ if (this.timer) clearTimeout(this.timer);
875
+ this.timer = null;
876
+ }
877
+ /** Turns started on its own today, on the server's calendar, however they went. */
878
+ usedToday(asked) {
879
+ const today = localDateKey(this.deps.now?.() ?? /* @__PURE__ */ new Date());
880
+ return asked.filter((turn) => localDateKey(new Date(turn.startedAt)) === today).length;
881
+ }
882
+ };
883
+
884
+ // src/prs.ts
885
+ var NUDGE_AFTER_MS = 24 * 36e5;
886
+ var STALE_DRAFT_AFTER_MS = 14 * 864e5;
887
+ var COURT_ORDER = ["you", "ready", "reviewers", "gate", "blocked", "checks", "draft"];
888
+ function courtOrder(court) {
889
+ return COURT_ORDER.indexOf(court);
890
+ }
891
+ function ms(iso) {
892
+ const parsed = parseISO(iso);
893
+ return parsed ? parsed.getTime() : null;
894
+ }
895
+ function deriveDecision(pr) {
896
+ if (pr.reviewDecision) return pr.reviewDecision;
897
+ if (pr.reviews.some((review) => review.state === "CHANGES_REQUESTED")) return "CHANGES_REQUESTED";
898
+ if (pr.reviews.some((review) => review.state === "APPROVED")) return "APPROVED";
899
+ return null;
900
+ }
901
+ function matchMergeGates(pr, gates) {
902
+ if (gates.length === 0) return [];
903
+ return (pr.pendingChecks ?? []).filter((check) => gates.includes(check.name));
904
+ }
905
+ function newest(pr, state) {
906
+ return pr.reviews.filter((review) => review.state === state).sort((a, b) => a.at < b.at ? 1 : -1)[0];
907
+ }
908
+ function judge(pr, now, nudgedAt, gates = []) {
909
+ const decision = deriveDecision(pr);
910
+ const you = pr.lastActivityByYou ?? null;
911
+ const others = pr.lastActivityByOthers ?? null;
912
+ const mergeState = pr.mergeStateStatus ?? null;
913
+ if (pr.isDraft) {
914
+ const touched = you ?? pr.createdAt;
915
+ const idle = now.getTime() - (ms(touched) ?? now.getTime());
916
+ return { court: "draft", decision, reasons: [], stale: idle >= STALE_DRAFT_AFTER_MS, nudge: false, since: touched };
917
+ }
918
+ const reasons = [];
919
+ if (decision === "CHANGES_REQUESTED") {
920
+ const request = newest(pr, "CHANGES_REQUESTED");
921
+ reasons.push({ kind: "changes-requested", login: request?.login, at: request?.at });
922
+ }
923
+ if (pr.checks === "failure") reasons.push({ kind: "ci-failing" });
924
+ if (pr.mergeable === "CONFLICTING" || mergeState === "DIRTY") reasons.push({ kind: "conflicts" });
925
+ if (mergeState === "BEHIND") reasons.push({ kind: "behind" });
926
+ const theirMove = others !== null && (you === null || others.at > you);
927
+ const approval = newest(pr, "APPROVED");
928
+ const afterApproval = others !== null && approval !== void 0 && others.at > approval.at;
929
+ const approved = decision === "APPROVED" && !afterApproval;
930
+ if (theirMove && others && !approved) {
931
+ reasons.push({ kind: "activity", login: others.login, at: others.at, activity: others.kind });
932
+ }
933
+ if (reasons.length > 0) {
934
+ const since = theirMove && others ? others.at : pr.updatedAt;
935
+ return { court: "you", decision, reasons, stale: false, nudge: false, since };
936
+ }
937
+ const waitingSince = laterISO(pr.readyAt, you) ?? pr.createdAt;
938
+ const settled = (court, reason) => ({
939
+ court,
940
+ decision,
941
+ reasons: [reason],
942
+ stale: false,
943
+ nudge: false,
944
+ since: waitingSince
945
+ });
946
+ if (decision !== "REVIEW_REQUIRED") {
947
+ const gate = matchMergeGates(pr, gates);
948
+ if (gate.length > 0) return settled("gate", { kind: "merge-gate", checks: gate });
949
+ const pending = pr.pendingChecks ?? [];
950
+ const running = { kind: "checks-pending" };
951
+ if (pending.length > 0) running.checks = pending;
952
+ if (mergeState === "BLOCKED" && pending.length === 0) {
953
+ return settled("blocked", { kind: "merge-blocked" });
954
+ }
955
+ if (mergeState === "BLOCKED" || mergeState === "UNSTABLE") return settled("checks", running);
956
+ if (mergeState === "UNKNOWN") return settled("checks", { kind: "mergeability-unknown" });
957
+ if (approved) {
958
+ if (mergeState === "CLEAN" || mergeState === "HAS_HOOKS") {
959
+ return { court: "ready", decision, reasons: [], stale: false, nudge: false, since: approval?.at ?? pr.updatedAt };
960
+ }
961
+ if (mergeState === null) {
962
+ const unfinished = pr.checks === "pending" || pending.length > 0;
963
+ if (!unfinished) {
964
+ return { court: "ready", decision, reasons: [], stale: false, nudge: false, since: approval?.at ?? pr.updatedAt };
965
+ }
966
+ return settled("checks", running);
967
+ }
968
+ }
969
+ }
970
+ const quietSince = laterISO(waitingSince, nudgedAt) ?? waitingSince;
971
+ const waited = now.getTime() - (ms(quietSince) ?? now.getTime());
972
+ const verdict = { court: "reviewers", decision, reasons, stale: false, nudge: waited >= NUDGE_AFTER_MS, since: waitingSince };
973
+ const nudged = ms(nudgedAt);
974
+ if (nudgedAt && nudged !== null && quietSince === nudgedAt && !verdict.nudge) {
975
+ verdict.nudgedAt = nudgedAt;
976
+ verdict.nudgedUntil = new Date(nudged + NUDGE_AFTER_MS).toISOString();
977
+ }
978
+ return verdict;
979
+ }
980
+ function resolveBoard(pulls, actions, now, gates = []) {
981
+ const folded = foldActionLog(actions, now);
982
+ const rows = pulls.map((pr) => {
983
+ const log = folded.get(canonicalId(pr.id));
984
+ const verdict = judge(pr, now, log?.nudgedAt ?? null, gates);
985
+ const row = { ...pr, ...verdict, status: log?.status === "snoozed" ? "snoozed" : "open", notes: log?.notes ?? [] };
986
+ if (row.status === "snoozed" && log?.snoozedUntil) row.snoozedUntil = log.snoozedUntil;
987
+ return row;
988
+ });
989
+ return rows.sort((a, b) => {
990
+ const court = courtOrder(a.court) - courtOrder(b.court);
991
+ if (court !== 0) return court;
992
+ if (a.since !== b.since) return a.since < b.since ? -1 : 1;
993
+ return a.id < b.id ? -1 : 1;
994
+ });
995
+ }
996
+ function countBoard(rows) {
997
+ const counts = {
998
+ you: 0,
999
+ ready: 0,
1000
+ reviewers: 0,
1001
+ gate: 0,
1002
+ blocked: 0,
1003
+ checks: 0,
1004
+ draft: 0,
1005
+ parked: 0
1006
+ };
1007
+ for (const row of rows) {
1008
+ if (row.status === "snoozed") counts.parked++;
1009
+ else counts[row.court]++;
1010
+ }
1011
+ return counts;
1012
+ }
1013
+
723
1014
  export {
1015
+ computerUseOff,
1016
+ refusalFor,
724
1017
  CodexGate,
725
1018
  codexVersionWarning,
1019
+ codexLatestModel,
726
1020
  QUICK_ACTIONS,
727
1021
  ADAPTERS,
728
- Assistant
1022
+ HANDOFF,
1023
+ WAIT,
1024
+ unwrapShell,
1025
+ connectorLabel,
1026
+ compactArgs,
1027
+ shorten,
1028
+ Assistant,
1029
+ autoKeys,
1030
+ actionOf,
1031
+ AutoAsk,
1032
+ judge,
1033
+ resolveBoard,
1034
+ countBoard
729
1035
  };
@@ -0,0 +1,18 @@
1
+ // src/fs.ts
2
+ import { mkdir, rename, writeFile } from "node:fs/promises";
3
+ import { dirname } from "node:path";
4
+ async function writeJsonAtomic(path, value) {
5
+ await mkdir(dirname(path), { recursive: true });
6
+ const temp = tempPathFor(path);
7
+ await writeFile(temp, `${JSON.stringify(value, null, 2)}
8
+ `, "utf8");
9
+ await rename(temp, path);
10
+ }
11
+ function tempPathFor(path) {
12
+ return `${path}.tmp`;
13
+ }
14
+
15
+ export {
16
+ writeJsonAtomic,
17
+ tempPathFor
18
+ };