coder-config 0.50.7-beta → 0.50.8-beta

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/lib/cli.js CHANGED
@@ -263,10 +263,16 @@ function runCli(manager) {
263
263
  manager.loopHistory();
264
264
  } else if (args[1] === 'config') {
265
265
  const updates = {};
266
- const maxIterIdx = args.indexOf('--max-iterations');
267
- if (maxIterIdx !== -1) updates.maxIterations = args[maxIterIdx + 1];
268
- if (args.includes('--auto-approve-plan')) updates.autoApprovePlan = true;
269
- if (args.includes('--no-auto-approve-plan')) updates.autoApprovePlan = false;
266
+ // Dot-notation key=value: coder-config loop config heartbeat.staleThresholdMinutes 30
267
+ if (args[2] && args[3] && !args[2].startsWith('--')) {
268
+ updates[args[2]] = args[3];
269
+ } else {
270
+ // Flag-based (backwards compatible)
271
+ const maxIterIdx = args.indexOf('--max-iterations');
272
+ if (maxIterIdx !== -1) updates.maxIterations = args[maxIterIdx + 1];
273
+ if (args.includes('--auto-approve-plan')) updates.autoApprovePlan = true;
274
+ if (args.includes('--no-auto-approve-plan')) updates.autoApprovePlan = false;
275
+ }
270
276
  if (Object.keys(updates).length > 0) {
271
277
  manager.loopConfig(updates);
272
278
  } else {
package/lib/constants.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * Constants and tool path configurations
3
3
  */
4
4
 
5
- const VERSION = '0.50.7-beta';
5
+ const VERSION = '0.50.8-beta';
6
6
 
7
7
  // Tool-specific path configurations
8
8
  const TOOL_PATHS = {
package/lib/loops.js CHANGED
@@ -4,6 +4,7 @@
4
4
 
5
5
  const fs = require('fs');
6
6
  const path = require('path');
7
+ const { getDefaultHeartbeatConfig } = require('./heartbeat');
7
8
 
8
9
  /**
9
10
  * Get loops directory path
@@ -621,6 +622,32 @@ function archiveLoop(installDir, loopId) {
621
622
  }
622
623
  }
623
624
 
625
+ /**
626
+ * Set a nested value on an object using dot-notation path.
627
+ * Creates intermediate objects as needed.
628
+ */
629
+ function setNestedValue(obj, dotPath, value) {
630
+ const parts = dotPath.split('.');
631
+ let current = obj;
632
+ for (let i = 0; i < parts.length - 1; i++) {
633
+ if (current[parts[i]] === undefined || typeof current[parts[i]] !== 'object') {
634
+ current[parts[i]] = {};
635
+ }
636
+ current = current[parts[i]];
637
+ }
638
+ current[parts[parts.length - 1]] = value;
639
+ }
640
+
641
+ /**
642
+ * Auto-convert string values to appropriate types.
643
+ */
644
+ function coerceValue(value) {
645
+ if (value === 'true') return true;
646
+ if (value === 'false') return false;
647
+ if (typeof value === 'string' && /^\d+$/.test(value)) return parseInt(value, 10);
648
+ return value;
649
+ }
650
+
624
651
  /**
625
652
  * Get/set loop configuration
626
653
  */
@@ -634,11 +661,46 @@ function loopConfig(installDir, updates = null) {
634
661
  console.log(` Auto-approve Plan: ${data.config.autoApprovePlan}`);
635
662
  console.log(` Max Clarify Iterations: ${data.config.maxClarifyIterations}`);
636
663
  console.log(` Completion Promise: ${data.config.completionPromise || 'DONE'}`);
664
+ if (data.config.heartbeat) {
665
+ const hb = data.config.heartbeat;
666
+ const slack = hb.notifications && hb.notifications.slack;
667
+ const macos = hb.notifications && hb.notifications.macos;
668
+ console.log(' Heartbeat:');
669
+ console.log(` Stale Threshold: ${hb.staleThresholdMinutes !== undefined ? hb.staleThresholdMinutes + 'm' : '30m'}`);
670
+ console.log(` Iteration Limit: ${hb.iterationLimitPercent !== undefined ? hb.iterationLimitPercent + '%' : '80%'}`);
671
+ console.log(` Cooldown: ${hb.cooldownMinutes !== undefined ? hb.cooldownMinutes + 'm' : '15m'}`);
672
+ console.log(` macOS Notifications: ${macos && macos.enabled !== undefined ? (macos.enabled ? 'on' : 'off') : 'on'}`);
673
+ if (slack) {
674
+ const slackStatus = slack.enabled ? `on (${slack.channel || '#dev-loops'})` : 'off';
675
+ console.log(` Slack: ${slackStatus}`);
676
+ } else {
677
+ console.log(' Slack: off');
678
+ }
679
+ }
637
680
  console.log('');
638
681
  return data.config;
639
682
  }
640
683
 
641
- // Apply updates
684
+ // Check for dot-notation heartbeat keys
685
+ const dotKeys = Object.keys(updates).filter(k => k.includes('.'));
686
+ if (dotKeys.length > 0) {
687
+ // Initialize heartbeat config with defaults if not present
688
+ if (!data.config.heartbeat) {
689
+ data.config.heartbeat = getDefaultHeartbeatConfig();
690
+ }
691
+ for (const key of dotKeys) {
692
+ if (key.startsWith('heartbeat.')) {
693
+ const subPath = key.slice('heartbeat.'.length);
694
+ setNestedValue(data.config.heartbeat, subPath, coerceValue(updates[key]));
695
+ } else {
696
+ setNestedValue(data.config, key, coerceValue(updates[key]));
697
+ }
698
+ }
699
+ // Remove dot-notation keys so they don't fall through to flat handling
700
+ dotKeys.forEach(k => delete updates[k]);
701
+ }
702
+
703
+ // Apply flat updates
642
704
  if (updates.maxIterations !== undefined) {
643
705
  data.config.maxIterations = parseInt(updates.maxIterations, 10);
644
706
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coder-config",
3
- "version": "0.50.7-beta",
3
+ "version": "0.50.8-beta",
4
4
  "description": "Configuration manager for AI coding tools - Claude Code, Gemini CLI, Codex CLI, Antigravity. Manage MCPs, rules, permissions, memory, and workstreams.",
5
5
  "author": "regression.io",
6
6
  "main": "config-loader.js",
@@ -598,7 +598,14 @@ function setupLoopHooks(projectPath) {
598
598
  return installLoopHooks(null, projectPath);
599
599
  }
600
600
 
601
+ function getHeartbeat(manager) {
602
+ if (!manager) return { error: 'Manager not available' };
603
+ const { heartbeat: runHeartbeat } = require('../../lib/heartbeat');
604
+ return runHeartbeat(manager.installDir);
605
+ }
606
+
601
607
  module.exports = {
608
+ getHeartbeat,
602
609
  getLoops,
603
610
  getActiveLoop,
604
611
  getLoop,
package/ui/server.cjs CHANGED
@@ -886,6 +886,10 @@ class ConfigUIServer {
886
886
  }
887
887
  break;
888
888
 
889
+ case '/api/loops/heartbeat':
890
+ if (req.method === 'GET') return this.json(res, routes.loops.getHeartbeat(this.manager));
891
+ break;
892
+
889
893
  case '/api/loops/tune-prompt':
890
894
  if (req.method === 'POST') {
891
895
  const result = await routes.loops.tunePrompt(