coder-config 0.43.27 → 0.44.1

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.
@@ -19,7 +19,7 @@
19
19
 
20
20
  <!-- PWA Manifest -->
21
21
  <link rel="manifest" href="/manifest.json">
22
- <script type="module" crossorigin src="/assets/index-D8VqNb3_.js"></script>
22
+ <script type="module" crossorigin src="/assets/index-BmstVwHo.js"></script>
23
23
  <link rel="stylesheet" crossorigin href="/assets/index-qZ-hWTZQ.css">
24
24
  </head>
25
25
  <body>
@@ -278,6 +278,74 @@ function getLoopHookStatus() {
278
278
  return status;
279
279
  }
280
280
 
281
+ /**
282
+ * Check if ralph-loop plugin is installed at user scope
283
+ * Returns { installed: boolean, scope: string|null }
284
+ */
285
+ function getRalphLoopPluginStatus() {
286
+ const installedPluginsPath = path.join(os.homedir(), '.claude', 'plugins', 'installed_plugins.json');
287
+
288
+ if (!fs.existsSync(installedPluginsPath)) {
289
+ return { installed: false, scope: null, needsInstall: true };
290
+ }
291
+
292
+ try {
293
+ const data = JSON.parse(fs.readFileSync(installedPluginsPath, 'utf8'));
294
+ const plugins = data.plugins || {};
295
+ const ralphLoop = plugins['ralph-loop@claude-plugins-official'];
296
+
297
+ if (!ralphLoop || ralphLoop.length === 0) {
298
+ return { installed: false, scope: null, needsInstall: true };
299
+ }
300
+
301
+ // Check if any installation is at user scope
302
+ const userScopeInstall = ralphLoop.find(p => p.scope === 'user');
303
+ if (userScopeInstall) {
304
+ return { installed: true, scope: 'user', needsInstall: false };
305
+ }
306
+
307
+ // Plugin is installed but only at project scope
308
+ return {
309
+ installed: true,
310
+ scope: 'project',
311
+ projectPath: ralphLoop[0].projectPath,
312
+ needsInstall: true,
313
+ message: 'Plugin is installed for a specific project only. Install at user scope for global access.'
314
+ };
315
+ } catch (e) {
316
+ return { installed: false, scope: null, needsInstall: true, error: e.message };
317
+ }
318
+ }
319
+
320
+ /**
321
+ * Install ralph-loop plugin at user scope via CLI
322
+ * Uses execFileSync with fixed args (no shell injection risk)
323
+ */
324
+ async function installRalphLoopPlugin() {
325
+ const { execFileSync } = require('child_process');
326
+
327
+ try {
328
+ // Run claude plugin install command with execFileSync (safer than execSync)
329
+ // All arguments are fixed strings - no user input
330
+ execFileSync('claude', ['plugin', 'install', 'ralph-loop@claude-plugins-official', '--scope', 'user'], {
331
+ encoding: 'utf8',
332
+ timeout: 30000, // 30 second timeout
333
+ stdio: ['pipe', 'pipe', 'pipe']
334
+ });
335
+
336
+ return {
337
+ success: true,
338
+ message: 'ralph-loop plugin installed successfully at user scope'
339
+ };
340
+ } catch (e) {
341
+ return {
342
+ success: false,
343
+ error: e.message,
344
+ suggestion: 'Try running manually: claude plugin install ralph-loop@claude-plugins-official --scope user'
345
+ };
346
+ }
347
+ }
348
+
281
349
  /**
282
350
  * Install loop hooks (or verify official plugin is installed)
283
351
  */
@@ -328,4 +396,6 @@ module.exports = {
328
396
  recordIteration,
329
397
  getLoopHookStatus,
330
398
  installLoopHooks,
399
+ getRalphLoopPluginStatus,
400
+ installRalphLoopPlugin,
331
401
  };
package/ui/server.cjs CHANGED
@@ -788,6 +788,17 @@ class ConfigUIServer {
788
788
  if (req.method === 'POST') return this.json(res, routes.loops.installLoopHooks(this.manager));
789
789
  break;
790
790
 
791
+ case '/api/loops/plugin-status':
792
+ if (req.method === 'GET') return this.json(res, routes.loops.getRalphLoopPluginStatus());
793
+ break;
794
+
795
+ case '/api/loops/install-plugin':
796
+ if (req.method === 'POST') {
797
+ const result = await routes.loops.installRalphLoopPlugin();
798
+ return this.json(res, result);
799
+ }
800
+ break;
801
+
791
802
  case '/api/activity':
792
803
  if (req.method === 'GET') return this.json(res, routes.activity.getActivitySummary(this.manager));
793
804
  if (req.method === 'DELETE') return this.json(res, routes.activity.clearActivity(this.manager, body.olderThanDays || 30));