coder-config 0.50.6-beta → 0.50.7-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/config-loader.js +49 -0
- package/lib/cli.js +13 -0
- package/lib/constants.js +1 -1
- package/lib/heartbeat.js +58 -1
- package/package.json +1 -1
package/config-loader.js
CHANGED
|
@@ -31,6 +31,7 @@ const { getProjectsRegistryPath, loadProjectsRegistry, saveProjectsRegistry, pro
|
|
|
31
31
|
const { getWorkstreamsPath, loadWorkstreams, saveWorkstreams, workstreamList, workstreamCreate, workstreamUpdate, workstreamDelete, workstreamUse, workstreamActive, workstreamAddProject, workstreamRemoveProject, workstreamInject, workstreamDetect, workstreamGet, getActiveWorkstream, countWorkstreamsForProject, workstreamInstallHook, workstreamInstallHookGemini, workstreamInstallHookCodex, workstreamDeactivate, workstreamCheckPath, getSettingsPath, loadSettings, saveSettings, workstreamAddTrigger, workstreamRemoveTrigger, workstreamSetAutoActivate, setGlobalAutoActivate, shouldAutoActivate, workstreamCheckFolder, workstreamInstallCdHook, workstreamUninstallCdHook, workstreamCdHookStatus, discoverSubProjects, generateRulesFromRepos, generateRulesWithClaude, generateRulesWithAI, getAvailableAITools, findAIBinary, AI_TOOLS, workstreamSetSandbox } = require('./lib/workstreams');
|
|
32
32
|
const { getActivityPath, getDefaultActivity, loadActivity, saveActivity, detectProjectRoot, activityLog, activitySummary, generateWorkstreamName, activitySuggestWorkstreams, activityClear } = require('./lib/activity');
|
|
33
33
|
const { getLoopsPath, loadLoops, saveLoops, loadLoopState, saveLoopState, loadHistory, saveHistory, loopList, loopCreate, loopGet, loopUpdate, loopDelete, loopStart, loopPause, loopResume, loopCancel, loopApprove, loopComplete, loopFail, loopStatus, loopHistory, loopConfig, getActiveLoop, recordIteration, saveClarifications, savePlan, loadClarifications, loadPlan, loopInject, archiveLoop } = require('./lib/loops');
|
|
34
|
+
const { heartbeat: runHeartbeat, formatReport, getExitCode, saveLastHeartbeat, shouldNotify, buildMacosNotification, loadHeartbeatConfig, getDefaultHeartbeatConfig } = require('./lib/heartbeat');
|
|
34
35
|
const { getSessionStatus, showSessionStatus, flushContext, clearContext, installHooks: sessionInstallHooks, getFlushedContext, installFlushCommand, installAll: sessionInstallAll, SESSION_DIR, FLUSHED_CONTEXT_FILE } = require('./lib/sessions');
|
|
35
36
|
const { runCli } = require('./lib/cli');
|
|
36
37
|
const { shellStatus, shellInstall, shellUninstall, printShellStatus } = require('./lib/shell');
|
|
@@ -253,6 +254,54 @@ class ClaudeConfigManager {
|
|
|
253
254
|
loopInject(silent) { return loopInject(this.installDir, silent); }
|
|
254
255
|
archiveLoop(loopId) { return archiveLoop(this.installDir, loopId); }
|
|
255
256
|
|
|
257
|
+
loopHeartbeat(options = {}) {
|
|
258
|
+
const report = runHeartbeat(this.installDir);
|
|
259
|
+
|
|
260
|
+
if (options.json) {
|
|
261
|
+
console.log(JSON.stringify(report, null, 2));
|
|
262
|
+
} else if (options.quiet) {
|
|
263
|
+
if (getExitCode(report) === 1) {
|
|
264
|
+
console.log(formatReport(report));
|
|
265
|
+
}
|
|
266
|
+
} else {
|
|
267
|
+
console.log(formatReport(report));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (options.notify) {
|
|
271
|
+
const config = loadHeartbeatConfig(this.installDir);
|
|
272
|
+
const newAlerts = report.alerts.filter(a =>
|
|
273
|
+
(a.severity === 'critical' || a.severity === 'warning') &&
|
|
274
|
+
shouldNotify(this.installDir, a, config.cooldownMinutes || 15)
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
if (newAlerts.length > 0) {
|
|
278
|
+
if (config.notifications?.macos?.enabled !== false) {
|
|
279
|
+
const cmd = buildMacosNotification(report);
|
|
280
|
+
try {
|
|
281
|
+
require('child_process').execSync(cmd);
|
|
282
|
+
} catch (e) {
|
|
283
|
+
// osascript may fail in non-GUI contexts
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (config.notifications?.slack?.enabled && config.notifications?.slack?.webhookUrl) {
|
|
288
|
+
const https = require('https');
|
|
289
|
+
const url = new URL(config.notifications.slack.webhookUrl);
|
|
290
|
+
const payload = JSON.stringify({ text: `Ralph Heartbeat: ${report.summary}` });
|
|
291
|
+
const req = https.request({ hostname: url.hostname, path: url.pathname, method: 'POST',
|
|
292
|
+
headers: { 'Content-Type': 'application/json' }
|
|
293
|
+
});
|
|
294
|
+
req.write(payload);
|
|
295
|
+
req.end();
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
saveLastHeartbeat(this.installDir, report);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return report;
|
|
303
|
+
}
|
|
304
|
+
|
|
256
305
|
// Activity
|
|
257
306
|
getActivityPath() { return getActivityPath(this.installDir); }
|
|
258
307
|
loadActivity() { return loadActivity(this.installDir); }
|
package/lib/cli.js
CHANGED
|
@@ -272,6 +272,16 @@ function runCli(manager) {
|
|
|
272
272
|
} else {
|
|
273
273
|
manager.loopConfig();
|
|
274
274
|
}
|
|
275
|
+
} else if (args[1] === 'heartbeat') {
|
|
276
|
+
const options = {
|
|
277
|
+
notify: args.includes('--notify'),
|
|
278
|
+
quiet: args.includes('--quiet'),
|
|
279
|
+
json: args.includes('--json')
|
|
280
|
+
};
|
|
281
|
+
const report = manager.loopHeartbeat(options);
|
|
282
|
+
if (options.quiet) {
|
|
283
|
+
process.exitCode = report.alerts.some(a => a.severity === 'critical' || a.severity === 'warning') ? 1 : 0;
|
|
284
|
+
}
|
|
275
285
|
} else if (args[1] === 'inject') {
|
|
276
286
|
const silent = args.includes('--silent') || args.includes('-s');
|
|
277
287
|
manager.loopInject(silent);
|
|
@@ -456,6 +466,9 @@ ${chalk.dim('Configuration manager for AI coding tools (Claude Code, Gemini CLI,
|
|
|
456
466
|
cmd('loop cancel <id>', 'Cancel loop'),
|
|
457
467
|
cmd('loop status [id]', 'Show loop status'),
|
|
458
468
|
cmd('loop config', 'Show/set loop config'),
|
|
469
|
+
cmd('loop heartbeat', 'Check loop health'),
|
|
470
|
+
cmd('loop heartbeat --notify', 'Check + send notifications'),
|
|
471
|
+
cmd('loop heartbeat --quiet', 'Silent unless alerts'),
|
|
459
472
|
]));
|
|
460
473
|
console.log();
|
|
461
474
|
}
|
package/lib/constants.js
CHANGED
package/lib/heartbeat.js
CHANGED
|
@@ -277,6 +277,62 @@ function getExitCode(report) {
|
|
|
277
277
|
return hasActionable ? 1 : 0;
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
+
/**
|
|
281
|
+
* Format a heartbeat report for terminal display
|
|
282
|
+
* @param {object} report - heartbeat report object
|
|
283
|
+
* @returns {string} formatted string for console output
|
|
284
|
+
*/
|
|
285
|
+
function formatReport(report) {
|
|
286
|
+
const chalk = require('chalk');
|
|
287
|
+
const lines = [];
|
|
288
|
+
|
|
289
|
+
lines.push(`♥ Loop Heartbeat — ${report.timestamp}`);
|
|
290
|
+
lines.push('');
|
|
291
|
+
|
|
292
|
+
const alerts = report.alerts || [];
|
|
293
|
+
const critical = alerts.filter(a => a.severity === 'critical');
|
|
294
|
+
const warning = alerts.filter(a => a.severity === 'warning');
|
|
295
|
+
const info = alerts.filter(a => a.severity === 'info');
|
|
296
|
+
const healthy = report.healthy || [];
|
|
297
|
+
|
|
298
|
+
if (critical.length > 0) {
|
|
299
|
+
lines.push(chalk.red('🔴 CRITICAL'));
|
|
300
|
+
for (const a of critical) {
|
|
301
|
+
lines.push(` ${chalk.red('✗')} ${a.name} — ${a.message}`);
|
|
302
|
+
}
|
|
303
|
+
lines.push('');
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (warning.length > 0) {
|
|
307
|
+
lines.push(chalk.yellow('🟡 WARNING'));
|
|
308
|
+
for (const a of warning) {
|
|
309
|
+
lines.push(` ${chalk.yellow('⚠')} ${a.name} — ${a.message}`);
|
|
310
|
+
}
|
|
311
|
+
lines.push('');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (info.length > 0) {
|
|
315
|
+
lines.push(chalk.blue('🔵 INFO'));
|
|
316
|
+
for (const a of info) {
|
|
317
|
+
lines.push(` ${chalk.blue('ℹ')} ${a.name} — ${a.message}`);
|
|
318
|
+
}
|
|
319
|
+
lines.push('');
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (healthy.length > 0) {
|
|
323
|
+
lines.push(chalk.green('🟢 HEALTHY'));
|
|
324
|
+
for (const h of healthy) {
|
|
325
|
+
const phase = h.phase ? ` [${h.phase}]` : '';
|
|
326
|
+
lines.push(` ${chalk.green('●')} ${h.name}${phase} ${h.iteration}`);
|
|
327
|
+
}
|
|
328
|
+
lines.push('');
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
lines.push(`Summary: ${report.summary}`);
|
|
332
|
+
|
|
333
|
+
return lines.join('\n');
|
|
334
|
+
}
|
|
335
|
+
|
|
280
336
|
module.exports = {
|
|
281
337
|
heartbeat,
|
|
282
338
|
getDefaultHeartbeatConfig,
|
|
@@ -287,5 +343,6 @@ module.exports = {
|
|
|
287
343
|
loadLastHeartbeat,
|
|
288
344
|
shouldNotify,
|
|
289
345
|
buildMacosNotification,
|
|
290
|
-
getExitCode
|
|
346
|
+
getExitCode,
|
|
347
|
+
formatReport
|
|
291
348
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coder-config",
|
|
3
|
-
"version": "0.50.
|
|
3
|
+
"version": "0.50.7-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",
|