@cabane/companion 0.6.19 → 0.6.21

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.
@@ -161,6 +161,26 @@ var companionConfigSchema = z2.object({
161
161
  opencode: z2.object({
162
162
  serverUrl: z2.string().url()
163
163
  }).strict().optional(),
164
+ // CT1082: the Claude Code runtime, when the user connected it on this machine.
165
+ // The change this task exists for: Claude Code used to be the one harness a
166
+ // device exposed with no configuration — a `claude --version` exit-0 was taken as
167
+ // consent — and it is now the third config-driven harness, opted into exactly
168
+ // like codex. `claude` on PATH is still required (you can't run what isn't
169
+ // installed), but presence alone no longer exposes anything: the manifest
170
+ // advertises claude-code only when this block says so AND the binary is there.
171
+ //
172
+ // The block's PRESENCE is also the migration marker (see `migrateConnectedHarnesses`).
173
+ // Absent means the config predates this task — a device whose user was never
174
+ // asked — and the migration grandfathers it on first start. So every config
175
+ // written from here on carries the block explicitly, including a freshly-paired
176
+ // one, which starts at `enabled: false`: a new device is connected to nothing
177
+ // until its user says otherwise.
178
+ //
179
+ // Not to be confused with the per-agent `agents.<key>.claudeCode.autoMemory`
180
+ // above — that's one agent's memory switch, this is the device's connected set.
181
+ claudeCode: z2.object({
182
+ enabled: z2.boolean().optional()
183
+ }).strict().optional(),
164
184
  // CT481: the codex runtime, when the operator runs Codex on this machine. Unlike
165
185
  // opencode (a long-lived `opencode serve` addressed by URL), Codex is a local CLI
166
186
  // the `@openai/codex-sdk` spawns per turn — so the config is just an opt-in flag,
@@ -211,31 +231,38 @@ function loadConfig() {
211
231
  function loadConfigTolerant() {
212
232
  const path = configPath();
213
233
  const empty = {};
214
- if (!existsSync(path)) return { local: empty, note: null };
234
+ const fresh = { local: empty, note: null, hadPriorConfig: false };
235
+ if (!existsSync(path)) return fresh;
215
236
  let raw;
216
237
  try {
217
238
  raw = readFileSync(path, "utf8");
218
239
  } catch {
219
- return { local: empty, note: null };
240
+ return fresh;
220
241
  }
221
- if (raw.trim().length === 0) return { local: empty, note: null };
242
+ if (raw.trim().length === 0) return fresh;
222
243
  let parsed;
223
244
  try {
224
245
  parsed = JSON.parse(raw);
225
246
  } catch {
226
- return { local: empty, note: `${path} was unreadable (invalid JSON) and has been reset.` };
247
+ return {
248
+ local: empty,
249
+ note: `${path} was unreadable (invalid JSON) and has been reset.`,
250
+ hadPriorConfig: true
251
+ };
227
252
  }
228
253
  const strict = companionConfigSchema.safeParse(parsed);
229
254
  if (strict.success) {
230
- const { agents: agents2, dashboardPort, autoOpen, logLevel } = strict.data;
255
+ const { agents: agents2, dashboardPort, autoOpen, logLevel, claudeCode: claudeCode2 } = strict.data;
231
256
  return {
232
257
  local: {
233
258
  ...agents2 !== void 0 ? { agents: agents2 } : {},
234
259
  ...dashboardPort !== void 0 ? { dashboardPort } : {},
235
260
  ...autoOpen !== void 0 ? { autoOpen } : {},
236
- ...logLevel !== void 0 ? { logLevel } : {}
261
+ ...logLevel !== void 0 ? { logLevel } : {},
262
+ ...claudeCode2 !== void 0 ? { claudeCode: claudeCode2 } : {}
237
263
  },
238
- note: null
264
+ note: null,
265
+ hadPriorConfig: true
239
266
  };
240
267
  }
241
268
  const obj = parsed && typeof parsed === "object" ? parsed : {};
@@ -247,9 +274,12 @@ function loadConfigTolerant() {
247
274
  if (obj.logLevel === "warn" || obj.logLevel === "info" || obj.logLevel === "debug") {
248
275
  local.logLevel = obj.logLevel;
249
276
  }
277
+ const claudeCode = companionConfigSchema.shape.claudeCode.safeParse(obj.claudeCode);
278
+ if (claudeCode.success && claudeCode.data !== void 0) local.claudeCode = claudeCode.data;
250
279
  return {
251
280
  local,
252
- note: `the existing ${path} was from an older or incompatible companion; re-pairing rewrote it.`
281
+ note: `the existing ${path} was from an older or incompatible companion; re-pairing rewrote it.`,
282
+ hadPriorConfig: true
253
283
  };
254
284
  }
255
285
  function saveConfig(cfg) {
@@ -375,8 +405,16 @@ function resolvePairBaseUrl(server) {
375
405
  return raw;
376
406
  }
377
407
  function writePairedConfig(paired) {
378
- const { local, note } = loadConfigTolerant();
408
+ const { local, note, hadPriorConfig } = loadConfigTolerant();
379
409
  const config = {
410
+ // CT1082: a device pairs connected to NOTHING — `claudeCode: { enabled: false }`
411
+ // written explicitly, not left absent, because absence is exactly what marks a
412
+ // pre-CT1082 config for the grandfathering migration. Omit it and a brand-new
413
+ // device gets auto-connected on its first start by the behaviour this task
414
+ // removes; stamp it on a RE-pair and an upgrading user's working device gets
415
+ // disconnected instead. So it's written only when there was no config here
416
+ // before. `local` spreads after, so a re-pair keeps the answer already given.
417
+ ...hadPriorConfig ? {} : { claudeCode: { enabled: false } },
380
418
  ...local,
381
419
  baseUrl: paired.baseUrl,
382
420
  deviceToken: paired.deviceToken,