ccdeck 3.22.6 → 3.22.7

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.
@@ -40,7 +40,7 @@
40
40
  document.documentElement.setAttribute("data-theme", stored === "light" ? "light" : "dark");
41
41
  })();
42
42
  </script>
43
- <script type="module" crossorigin src="/assets/index-C9dWWFG6.js"></script>
43
+ <script type="module" crossorigin src="/assets/index-AeLjqiYS.js"></script>
44
44
  <link rel="stylesheet" crossorigin href="/assets/index-D0ZefIva.css">
45
45
  </head>
46
46
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccdeck",
3
- "version": "3.22.6",
3
+ "version": "3.22.7",
4
4
  "description": "Live deck of Claude Code and Codex agents — watch tool calls, token spend and every Claude Code subagent on one calm canvas. Run it with npx ccdeck.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -31,7 +31,10 @@ export const REACTIONS = ["notify", "close-tab", "quit-browser"];
31
31
 
32
32
  export const DEFAULTS = {
33
33
  v: 1,
34
- enabled: false,
34
+ // ON unless somebody switches it off (since 3.22.7; off before). A saved
35
+ // `false` still wins — normalise() takes any real boolean — so an upgrade
36
+ // starts watching only on a deck that never saved this field.
37
+ enabled: true,
35
38
  reaction: "notify",
36
39
  quietMinutes: 15,
37
40
  gapMinutes: 15,
@@ -124,7 +127,7 @@ export function normalise(raw) {
124
127
  typeof v === "number" && Number.isFinite(v) && v >= lo && v <= hi ? v : fallback;
125
128
  return {
126
129
  v: 1,
127
- enabled: it.enabled === true,
130
+ enabled: typeof it.enabled === "boolean" ? it.enabled : DEFAULTS.enabled,
128
131
  reaction: REACTIONS.includes(it.reaction) ? it.reaction : DEFAULTS.reaction,
129
132
  quietMinutes: num(it.quietMinutes, DEFAULTS.quietMinutes, 1, 24 * 60),
130
133
  gapMinutes: num(it.gapMinutes, DEFAULTS.gapMinutes, 1, 24 * 60),
@@ -49,37 +49,46 @@ export const prefsPath = (home = deckDataDir()) => join(prefsDir(home), "prefs.j
49
49
 
50
50
  /**
51
51
  * Every preference the deck keeps, with the answer it gives when there is no
52
- * file — which is the answer for every existing install, so it has to be the
53
- * behaviour those installs already have.
52
+ * file. A saved choice always wins over these — normalise() takes any real
53
+ * boolean — so changing one here reaches only decks that never saved that
54
+ * field, and nobody's saved setting is flipped by an upgrade.
54
55
  *
55
- * `notifications` defaults ON because that is what 3.7.0 shipped and what the
56
- * release notes describe; a switch that quietly turned an existing feature off
57
- * on upgrade would be a worse surprise than the noise it is meant to stop.
56
+ * `notifications` defaults OFF since 3.22.7. The deck's own sounds are how it
57
+ * gets attention by default; a desktop notification is something a person
58
+ * turns on. It defaulted on from 3.7.0 until then, and a deck that saved that
59
+ * `true` keeps it.
58
60
  */
59
61
  export const DEFAULTS = Object.freeze({
60
- notifications: true,
62
+ notifications: false,
61
63
  // Whether the deck may update itself: restart into code already on disk once
62
64
  // it is idle, and — while nobody is looking — install a newer release and
63
65
  // restart into that (auto-update.mjs). ON, because this is the banner's
64
66
  // `auto when idle`, which defaulted on as a localStorage key; it moved here so
65
67
  // the server can read it with no page open.
66
68
  autoUpdate: true,
67
- // LAN sync, off until somebody turns it on. `passphrase` is the only secret
68
- // this file has ever held, which is why the write below now names a mode.
69
+ // LAN sync, ON unless somebody turns it off (since 3.22.7; off before).
70
+ // `passphrase` is the only secret this file has ever held, which is why the
71
+ // write below names a mode. AGENTS_DECK_NO_LAN=1 keeps a deck off the network
72
+ // whatever this file says — see lanEnabled.
69
73
  lan: Object.freeze({
70
- enabled: false, name: "", secret: "", shared: [], manual: [], trusted: [], port: 0,
71
- // WHO PAIRS WITH WHOM, WITHOUT ANYBODY PRESSING ANYTHING. Both on, so two
72
- // decks on one network find each other and pair themselves — which is what
73
- // a person with three of their own machines wants and had to do by hand
74
- // six times.
74
+ enabled: true, name: "", secret: "", shared: [], manual: [], trusted: [], port: 0,
75
+ // WHO PAIRS WITH WHOM, WITHOUT ANYBODY PRESSING ANYTHING. Asking is on, so
76
+ // two decks on one network find each other and send each other a request —
77
+ // which is what a person with three of their own machines wants and had to
78
+ // do by hand six times.
75
79
  //
76
- // Read this next to the two switches that gate it. `enabled` above is off,
77
- // so nothing here happens until somebody deliberately puts this deck on the
78
- // network; `shared` is empty, so a deck that pairs is offered nothing until
79
- // somebody ticks a login. These say what happens AFTER both of those, and
80
- // the dialog that turns the feature on prints them.
80
+ // Saying yes is OFF, and that is the half that had to change when `enabled`
81
+ // did. Auto-accept is the accept button pressed in advance, and it was a
82
+ // fair trade while every deck on the network belonged to somebody who had
83
+ // switched this on deliberately. With the feature on for everybody, every
84
+ // ccdeck in an office would pair with every other one in silence — and a
85
+ // login ticked later goes to everything paired. So a pairing takes one
86
+ // press of accept on the machine that was asked.
87
+ //
88
+ // `shared` is empty either way, so a deck that pairs is offered nothing
89
+ // until somebody ticks a login: the gate that did not change.
81
90
  autoAsk: true,
82
- autoAccept: true,
91
+ autoAccept: false,
83
92
  // What somebody HERE calls another deck, keyed by its fingerprint. The name
84
93
  // a deck gives itself is its owner's to choose; this is the other half.
85
94
  aliases: Object.freeze({}),
@@ -140,7 +149,7 @@ function normaliseLan(raw) {
140
149
  const src = raw && typeof raw === "object" ? raw : {};
141
150
  const strings = v => (Array.isArray(v) ? v.filter(x => typeof x === "string") : []);
142
151
  return {
143
- enabled: typeof src.enabled === "boolean" ? src.enabled : false,
152
+ enabled: typeof src.enabled === "boolean" ? src.enabled : DEFAULTS.lan.enabled,
144
153
  name: typeof src.name === "string" ? src.name : "",
145
154
  // THIS DECK'S PRIVATE KEY, and the only secret this file has ever held —
146
155
  // which is why the write below names a mode rather than taking the umask's.
@@ -175,12 +184,14 @@ function normaliseLan(raw) {
175
184
  // anybody pressing `ask` — the outbound half, which gives nothing away: the
176
185
  // machine on the other end still answers it, by hand or by the switch below.
177
186
  autoAsk: typeof src.autoAsk === "boolean" ? src.autoAsk : true,
178
- // AND SAY YES. Every deck that finishes a handshake and is not already
179
- // trusted is pinned without anybody being asked — the accept button pressed
180
- // in advance, and it hands whoever asks a copy of every login this deck
181
- // shares. Absent means the default above; only a real boolean overrides it,
182
- // because a truthy string from a hand-edited file is not an answer.
183
- autoAccept: typeof src.autoAccept === "boolean" ? src.autoAccept : true,
187
+ // AND SAY YES, once somebody has turned this on. Every deck that finishes a
188
+ // handshake and is not already trusted is then pinned without anybody being
189
+ // asked — the accept button pressed in advance, and it hands whoever asks a
190
+ // copy of every login this deck shares. Absent means the default above,
191
+ // which is off since the feature itself started on; only a real boolean
192
+ // overrides it, because a truthy string from a hand-edited file is not an
193
+ // answer.
194
+ autoAccept: typeof src.autoAccept === "boolean" ? src.autoAccept : DEFAULTS.lan.autoAccept,
184
195
  };
185
196
  }
186
197
 
@@ -286,6 +297,20 @@ export function notificationsOn(prefs, env = process.env) {
286
297
  return normalise(prefs).notifications;
287
298
  }
288
299
 
300
+ /**
301
+ * Should the LAN engine run? The file's answer, unless the machine said no.
302
+ *
303
+ * AGENTS_DECK_NO_LAN=1 wins for the reason AGENTS_DECK_NO_NOTIFY does above:
304
+ * whoever launched the deck is making a claim about the machine, and a page
305
+ * posting to /api/prefs is not entitled to overrule it. It earns its keep twice
306
+ * over now that the feature is on by default — it is also how this repo's own
307
+ * suite keeps the decks it boots off the network it is being run on.
308
+ */
309
+ export function lanEnabled(prefs, env = process.env) {
310
+ if (env.AGENTS_DECK_NO_LAN === "1") return false;
311
+ return normalise(prefs).lan.enabled;
312
+ }
313
+
289
314
  /**
290
315
  * Did the MACHINE veto this, as opposed to the person?
291
316
  *
@@ -32,7 +32,7 @@ import { createActivity } from "./activity.mjs";
32
32
  import { createOutputWatch } from "./output-watch.mjs";
33
33
  import { AWAY_BOOT_GRACE_MS, AWAY_RECHECK_MS, AWAY_TICK_MS, awayGate, awayUpdateStep } from "./auto-update.mjs";
34
34
  import { createPresence } from "./presence.mjs";
35
- import { DEFAULTS as PREF_DEFAULTS, cleanAlias, isAliasKey, notificationsOn, notificationsVetoed, publicPrefs, readPrefs, writePrefs } from "./deck-prefs.mjs";
35
+ import { DEFAULTS as PREF_DEFAULTS, cleanAlias, isAliasKey, lanEnabled, notificationsOn, notificationsVetoed, publicPrefs, readPrefs, writePrefs } from "./deck-prefs.mjs";
36
36
  import { createEngine, defaultName } from "./lan-engine.mjs";
37
37
  import { aboutThisDeck } from "./lan-about.mjs";
38
38
  import { PROBE_PS, localAliases, reachability, readProbe } from "./lan-reach.mjs";
@@ -3451,7 +3451,10 @@ async function applyLanPrefs() {
3451
3451
  const lan = _prefs?.lan ?? {};
3452
3452
  try {
3453
3453
  await lanEngine.apply({
3454
- enabled: !!lan.enabled,
3454
+ // The file's answer unless the machine said no: AGENTS_DECK_NO_LAN=1 is
3455
+ // how a launch script — or this repo's own test suite — keeps a deck off
3456
+ // the network now that the default is on.
3457
+ enabled: lanEnabled(_prefs),
3455
3458
  name: lan.name || defaultName(),
3456
3459
  secret: lan.secret || "",
3457
3460
  shared: Array.isArray(lan.shared) ? lan.shared : [],