total-recall-brain 3.41.1 → 3.41.3

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.
Files changed (105) hide show
  1. package/bin/total-recall.mjs +9 -3
  2. package/frontend/dist/assets/{index-BG9NkVUy.css → index-BIQmqDKP.css} +1 -1
  3. package/frontend/dist/assets/{index-BtuiZqwP.js → index-DFenQQEQ.js} +74 -79
  4. package/frontend/dist/index.html +2 -2
  5. package/frontend/dist/sw.js +8 -5
  6. package/frontend/src/api/auth.ts +0 -3
  7. package/frontend/src/components/plugins/PluginPanel.tsx +7 -3
  8. package/frontend/src/components/plugins/card/CardEngine.tsx +1 -1
  9. package/frontend/src/pages/LoginPage.tsx +1 -41
  10. package/frontend/src/pages/PluginsPage.tsx +14 -7
  11. package/package.json +2 -2
  12. package/scaffold/.agent/skills/plugin/scripts/record.mjs +6 -3
  13. package/scaffold/.agent/skills/total-recall/plugins/auth/README.md +10 -0
  14. package/scaffold/.agent/skills/total-recall/plugins/auth/cli.mjs +197 -31
  15. package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +1456 -12
  16. package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +4 -4
  17. package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +8 -1
  18. package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +210 -27
  19. package/scaffold/.agent/skills/total-recall/plugins/auth/src/provider-catalog.mjs +10 -1
  20. package/scaffold/.agent/skills/total-recall/plugins/auth/src/rotation-capability.mjs +15 -0
  21. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-env-export.mjs +78 -6
  22. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-merge.mjs +131 -0
  23. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-remote-deploy.mjs +111 -2
  24. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-store.mjs +26 -5
  25. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/DESIGN.md +56 -8
  26. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/card.js +1068 -204
  27. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/panel.js +4 -2
  28. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/DESIGN.md +32 -0
  29. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/card.js +436 -274
  30. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +4 -4
  31. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +1 -1
  32. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/DESIGN.md +16 -2
  33. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/card.js +461 -242
  34. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +4 -4
  35. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +1 -1
  36. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +1 -7
  37. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/DESIGN.md +17 -2
  38. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/card.js +426 -300
  39. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +4 -4
  40. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/DESIGN.md +32 -0
  41. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/card.js +350 -283
  42. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +4 -4
  43. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/ui/card.js +31 -19
  44. package/scaffold/.agent/skills/total-recall/plugins/mesh/README.md +10 -0
  45. package/scaffold/.agent/skills/total-recall/plugins/mesh/{src/core/headscale-server.mjs → capability/control-server.mjs} +5 -2
  46. package/scaffold/.agent/skills/total-recall/plugins/mesh/capability/headscale-api.mjs +192 -0
  47. package/scaffold/.agent/skills/total-recall/plugins/mesh/capability/index.mjs +46 -0
  48. package/scaffold/.agent/skills/total-recall/plugins/mesh/cli.mjs +9 -1
  49. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +23 -22
  50. package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +6 -3
  51. package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +242 -8
  52. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/mesh.mjs +14 -9
  53. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/server.mjs +1 -1
  54. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/credentials.mjs +4 -2
  55. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/headscale-client.mjs +34 -181
  56. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh-auth.mjs +2 -2
  57. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/presence.mjs +5 -5
  58. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/screen-view.mjs +78 -0
  59. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/DESIGN.md +211 -12
  60. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/card.js +323 -388
  61. package/scaffold/.agent/skills/total-recall/plugins/openwiki/README.md +12 -1
  62. package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +7 -4
  63. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +6 -6
  64. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +3 -2
  65. package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +34 -1
  66. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/bootstrap.mjs +99 -0
  67. package/scaffold/.agent/skills/total-recall/plugins/openwiki/templates/openwiki/README.md +10 -0
  68. package/scaffold/.agent/skills/total-recall/plugins/openwiki/templates/openwiki/architecture.md +5 -0
  69. package/scaffold/.agent/skills/total-recall/plugins/openwiki/templates/openwiki/ide.md +10 -0
  70. package/scaffold/.agent/skills/total-recall/plugins/openwiki/templates/openwiki/memory.md +11 -0
  71. package/scaffold/.agent/skills/total-recall/plugins/openwiki/templates/openwiki/secrets.md +7 -0
  72. package/scaffold/.agent/skills/total-recall/plugins/openwiki/templates/openwiki/skills.md +7 -0
  73. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/DESIGN.md +16 -2
  74. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +380 -298
  75. package/scaffold/.agent/skills/total-recall/plugins/security/cli.mjs +7 -1
  76. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +6 -6
  77. package/scaffold/.agent/skills/total-recall/plugins/security/package.json +1 -1
  78. package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +1 -1
  79. package/scaffold/.agent/skills/total-recall/plugins/security/src/scanner.mjs +17 -3
  80. package/scaffold/.agent/skills/total-recall/plugins/security/ui/DESIGN.md +38 -15
  81. package/scaffold/.agent/skills/total-recall/plugins/security/ui/card.js +60 -34
  82. package/scaffold/.agent/skills/total-recall/references/auth.md +2 -0
  83. package/src/cli/command.mjs +23 -18
  84. package/src/cli/daemon.mjs +23 -1
  85. package/src/cli/plugin/copies.mjs +8 -5
  86. package/src/cli/plugin/index.mjs +25 -2
  87. package/src/cli/rebuild.mjs +22 -0
  88. package/src/cli/reset-password.mjs +3 -2
  89. package/src/cli/skill.mjs +8 -2
  90. package/src/cli/status.mjs +25 -7
  91. package/src/core/automatic-brain-updates.mjs +9 -3
  92. package/src/core/brain-updates.mjs +63 -5
  93. package/src/core/global-brain-updates.mjs +21 -1
  94. package/src/core/mesh-brain-updates.mjs +5 -2
  95. package/src/core/meta-harness.mjs +16 -41
  96. package/src/core/plugin-runner.mjs +6 -1
  97. package/src/core/plugin-store.mjs +67 -1
  98. package/src/core/plugin-updates.mjs +1 -1
  99. package/src/core/portable-skills-maintenance.mjs +19 -0
  100. package/src/core/required-plugins.mjs +7 -0
  101. package/src/core/skills-registry.mjs +33 -3
  102. package/src/server/auth.mjs +41 -1
  103. package/src/server/memory-app.mjs +2 -1
  104. package/src/server/routes/auth.mjs +25 -1
  105. package/src/server/routes/plugins.mjs +17 -4
@@ -456,6 +456,11 @@ export function deploySkill(brainDir, skillIdOrPath, opts = {}) {
456
456
  if (layered && fs.existsSync(destDir) && fs.lstatSync(destDir).isSymbolicLink()) {
457
457
  throw new Error(`Refusing layered deploy into symlink: ${destDir}`);
458
458
  }
459
+ if (opts.dryRun) return { success: true, dryRun: true, skillId: resolved.id, destDir,
460
+ sourcePath: resolved.sourcePath, layered, existed, routeCore: Boolean(opts.routeCore), actions: [
461
+ { op: 'replace-managed', from: layered && existed ? path.join(resolved.sourcePath, 'core') : resolved.sourcePath, to: layered && existed ? path.join(destDir, 'core') : destDir },
462
+ ...(opts.routeCore && layered ? [{ op: 'route-core', path: path.join(destDir, 'SKILL.md') }] : []),
463
+ ] };
459
464
  const replacement = layered && existed
460
465
  ? replaceSkillDir(path.join(resolved.sourcePath, 'core'), path.join(destDir, 'core'), {
461
466
  requiredFile: null,
@@ -463,6 +468,14 @@ export function deploySkill(brainDir, skillIdOrPath, opts = {}) {
463
468
  })
464
469
  : replaceSkillDir(resolved.sourcePath, destDir, { managedFiles: priorInstall?.managed_files || [] });
465
470
 
471
+ if (opts.routeCore && layered) {
472
+ const wrapper = path.join(destDir, 'SKILL.md');
473
+ const current = fs.readFileSync(wrapper, 'utf8');
474
+ const marker = '<!-- total-recall:managed-core-route:v1 -->';
475
+ if (!current.includes(marker)) fs.writeFileSync(wrapper, current.replace(/\s*$/, '') +
476
+ `\n\n${marker}\nRead the [maintained portable core](core/SKILL.md) for current protocol contracts; preserve repository-specific instructions above.\n`);
477
+ }
478
+
466
479
  let adaptResult = { adapted: false };
467
480
  if (opts.adapt && (!layered || !existed)) {
468
481
  const openwikiDir =
@@ -536,7 +549,7 @@ function installDetail(entry, inst) {
536
549
  const liveHash = exists ? hashSkillContent(inst.path) : null;
537
550
  const liveCoreHash = layered && exists ? hashSkillLayer(inst.path, 'core') : null;
538
551
  const liveRepoHash = layered && exists ? hashSkillLayer(inst.path, 'repo') : null;
539
- const drift = layered
552
+ const drift = !exists ? true : layered
540
553
  ? liveCoreHash !== (entry?.core_hash || inst.core_hash)
541
554
  : legacyUnderLayered
542
555
  ? false
@@ -548,7 +561,9 @@ function installDetail(entry, inst) {
548
561
  return {
549
562
  ...inst,
550
563
  exists,
564
+ required: !inst.obsolete,
551
565
  live_hash: liveHash,
566
+ verification: !exists ? 'missing' : legacyUnderLayered ? 'not_adopted' : drift ? 'drifted' : 'verified',
552
567
  ...(layered ? {
553
568
  core_hash: liveCoreHash,
554
569
  repo_hash: liveRepoHash,
@@ -576,7 +591,7 @@ export function skillStatus(brainDir, skillId) {
576
591
  } catch (err) {
577
592
  // One unreadable install (a symlinked layer, a moved repo) must not hide
578
593
  // the status of every other install.
579
- return { ...inst, exists: fs.existsSync(inst.path), error: err.message, drift: false };
594
+ return { ...inst, exists: fs.existsSync(inst.path), error: err.message, drift: true, verification: 'error' };
580
595
  }
581
596
  });
582
597
 
@@ -593,7 +608,11 @@ export function skillStatus(brainDir, skillId) {
593
608
  localPath,
594
609
  installs: installDetails,
595
610
  install_count: installDetails.length,
596
- any_drift: installDetails.some((i) => i.drift),
611
+ any_drift: installDetails.some((i) => !i.obsolete && i.drift),
612
+ verified: Boolean(entry && localPath) && installDetails.every((i) => i.obsolete || (i.verification === 'verified' && (!i.contract || i.contract.ok))),
613
+ missing_count: installDetails.filter((i) => !i.obsolete && !i.exists).length,
614
+ obsolete_count: installDetails.filter((i) => i.obsolete).length,
615
+ error_count: installDetails.filter((i) => i.error).length,
597
616
  any_contract_failure: installDetails.some((i) => i.contract && !i.contract.ok),
598
617
  };
599
618
  }
@@ -1269,6 +1288,17 @@ export function syncSkillTwoWay(brainDir, skillId, opts = {}) {
1269
1288
  }
1270
1289
 
1271
1290
  const { entry, locations } = collectSkillLocations(brainDir, skillId);
1291
+ const missingInstalls = loadRegistry(brainDir).installs.filter((inst) =>
1292
+ inst.skill_id === skillId && !inst.obsolete && !fs.existsSync(path.join(inst.path, 'SKILL.md')),
1293
+ );
1294
+ if (missingInstalls.length) {
1295
+ return {
1296
+ skillId, in_sync: false, dryRun,
1297
+ error: 'Recorded skill destinations are missing; reconcile existing repositories and deploy explicitly before synchronization',
1298
+ missing: missingInstalls.map((inst) => ({ path: inst.path, repo: inst.repo, repository_exists: Boolean(inst.repo && fs.existsSync(inst.repo)) })),
1299
+ locations: locations.length, actions: [],
1300
+ };
1301
+ }
1272
1302
  if (!locations.length) {
1273
1303
  return { skillId, skipped: true, reason: 'no live copies found' };
1274
1304
  }
@@ -6,4 +6,44 @@ import { findValidKeyByToken, keyHasAnyScope, recordKeyUsage, recordKeyOutcome }
6
6
  import { logger } from '../core/logger.mjs';
7
7
  const owner = await loadPluginModule('auth', 'server-auth');
8
8
  export const BCRYPT_COST = owner.BCRYPT_COST;
9
- export const { loadSecurityConfig, apiRateLimiter, authRateLimiter, sandboxRateLimiter, ingestRateLimiter, isLoopbackIp, isMeshIp, isLocalRequest, requireHttps, requireAuthOrLocal, corsOptions, requireAuth, requireScope, requireSandboxEnabled, sessionCookieOptions, loginHandler, changePasswordHandler, mintStepUpToken, verifyStepUpToken, verifyDashboardPassword, logoutHandler, signSessionToken, dashboardAuthState } = owner.createServerAuth({ brainDir, authBrainDir: process.env.TR_SECRETS_BRAIN || brainDir, nodeEnv, watchdog, findValidKeyByToken, keyHasAnyScope, recordKeyUsage, recordKeyOutcome, logger });
9
+ const base = owner.createServerAuth({ brainDir, authBrainDir: process.env.TR_SECRETS_BRAIN || brainDir, nodeEnv, watchdog, findValidKeyByToken, keyHasAnyScope, recordKeyUsage, recordKeyOutcome, logger });
10
+ export const { loadSecurityConfig, apiRateLimiter, authRateLimiter, sandboxRateLimiter, ingestRateLimiter, isLoopbackIp, isMeshIp, isLocalRequest, requireHttps, requireAuthOrLocal, corsOptions, requireScope, requireSandboxEnabled, sessionCookieOptions, loginHandler, changePasswordHandler, mintStepUpToken, verifyStepUpToken, verifyDashboardPassword, logoutHandler, signSessionToken, dashboardAuthState } = base;
11
+
12
+ const hostName = (value) => String(value || '').replace(/^https?:\/\//, '').replace(/[/?#].*$/, '').replace(/:\d+$/, '').replace(/^\[(.*)\]$/, '$1');
13
+
14
+ let alwaysRequired = false;
15
+
16
+ /** Headless hosts (the memory-only runtime) never offer the open-local mode. */
17
+ export function enforceLogin() {
18
+ alwaysRequired = true;
19
+ }
20
+
21
+ /**
22
+ * Dashboard login is optional for a local install. It is required when
23
+ * `dashboard.require_login` is true, or, when unset, once a dashboard password
24
+ * exists. When not required, only a genuinely local browser or CLI is let in:
25
+ * loopback socket, loopback Host header and no foreign Origin (which blocks DNS
26
+ * rebinding and cross-site requests). Bearer tokens and every other client
27
+ * (mesh, tunnel, remote) still authenticate normally.
28
+ */
29
+ export function loginRequired() {
30
+ if (alwaysRequired) return true;
31
+ const configured = loadSecurityConfig().dashboard?.require_login;
32
+ return typeof configured === 'boolean' ? configured : dashboardAuthState().configured;
33
+ }
34
+
35
+ function isOpenLocalRequest(req) {
36
+ if (req.headers.authorization) return false;
37
+ if (!isLocalRequest(req)) return false;
38
+ if (!isLoopbackIp(hostName(req.headers.host))) return false;
39
+ const origin = req.headers.origin;
40
+ return !origin || isLoopbackIp(hostName(origin));
41
+ }
42
+
43
+ export function requireAuth(req, res, next) {
44
+ if (!loginRequired() && isOpenLocalRequest(req)) {
45
+ req.auth = { type: 'dashboard_session', local: true };
46
+ return next();
47
+ }
48
+ return base.requireAuth(req, res, next);
49
+ }
@@ -4,7 +4,7 @@ import cors from 'cors';
4
4
  import cookieParser from 'cookie-parser';
5
5
  import fs from 'node:fs';
6
6
  import { fileURLToPath } from 'node:url';
7
- import { requireHttps, corsOptions, apiRateLimiter, requireAuthOrLocal } from './auth.mjs';
7
+ import { requireHttps, corsOptions, apiRateLimiter, requireAuthOrLocal, enforceLogin } from './auth.mjs';
8
8
  import { memoryRouter } from './routes/memory.mjs';
9
9
  import { authRouter } from './routes/auth.mjs';
10
10
  import { keysRouter } from './routes/keys.mjs';
@@ -17,6 +17,7 @@ import { getNodes } from '../core/vault-cache.mjs';
17
17
  const { version } = JSON.parse(fs.readFileSync(fileURLToPath(new URL('../../package.json', import.meta.url)), 'utf8'));
18
18
 
19
19
  export function createMemoryApp() {
20
+ enforceLogin();
20
21
  const app = express();
21
22
  app.disable('x-powered-by');
22
23
  app.set('trust proxy', 'loopback');
@@ -1,7 +1,31 @@
1
1
  /** Required auth plugin adapter for dashboard authentication routes. */
2
+ import express from 'express';
2
3
  import * as auth from '../auth.mjs';
3
4
  import { logger } from '../../core/logger.mjs';
4
5
  import { BRAIN_DIR } from './_shared.mjs';
5
6
  import { loadPluginModule } from '../../core/plugin-runtime.mjs';
6
7
  const { createAuthRouter } = await loadPluginModule('auth', 'auth-router');
7
- export const authRouter = createAuthRouter({ auth, logger, BRAIN_DIR });
8
+ const pluginRouter = createAuthRouter({ auth, logger, BRAIN_DIR });
9
+
10
+ /**
11
+ * Total Recall has no Google sign-in: the plugin serves it for apps that do,
12
+ * so this router refuses those routes and drops Google from provider discovery.
13
+ */
14
+ export const authRouter = express.Router();
15
+ authRouter.use('/auth/google', (_req, res) => res.status(404).json({ error: 'Google sign-in is not available in Total Recall' }));
16
+ authRouter.use('/auth/providers', (req, res, next) => {
17
+ if (req.method !== 'GET') return next();
18
+ const json = res.json.bind(res);
19
+ res.json = (body) => {
20
+ if (body && typeof body === 'object') delete body.google;
21
+ return json(body);
22
+ };
23
+ next();
24
+ });
25
+ authRouter.use('/auth/status', (req, res, next) => {
26
+ if (req.method !== 'GET') return next();
27
+ const json = res.json.bind(res);
28
+ res.json = (body) => json({ ...body, login_required: auth.loginRequired() });
29
+ next();
30
+ });
31
+ authRouter.use(pluginRouter);
@@ -307,15 +307,28 @@ router.patch("/api/plugins/:id/card-face", requireAuth, requireScope("config:wri
307
307
  /**
308
308
  * POST /api/plugins/:id/run body: { subcommand?, args? }
309
309
  * Runs the plugin's CLI handler in a child process. Executing plugin code is a
310
- * write-level action.
310
+ * write-level action unless the installed manifest explicitly declares the
311
+ * exact operation read-only. Alternate execution and credential input remain
312
+ * write-level regardless of that declaration.
311
313
  */
312
- router.post("/api/plugins/:id/run", requireAuth, requireScope("config:write"), async (req, res) => {
314
+ router.post("/api/plugins/:id/run", requireAuth, (req, res, next) => {
315
+ const { subcommand, args = [], stdin, execution } = req.body || {};
316
+ try {
317
+ const plugin = getPluginById(req.params.id, projectRoot());
318
+ const operation = plugin?.valid && plugin.manifest?.cli?.subcommands?.find(item => item.name === subcommand);
319
+ res.locals.executionPlugin = plugin;
320
+ const read = typeof subcommand === 'string' && Array.isArray(args) && args.every(arg => typeof arg === 'string') &&
321
+ operation?.effect === 'read' && stdin === undefined && !execution &&
322
+ !operation.args?.some(arg => arg.secret);
323
+ return requireScope(read ? 'config:read' : 'config:write')(req, res, next);
324
+ } catch (error) { badRequest(res, error.message); }
325
+ }, async (req, res) => {
313
326
  try {
314
327
  const { subcommand = "", args = [], stdin, execution } = req.body || {};
315
328
  if (typeof subcommand !== "string" || !Array.isArray(args) || args.some((a) => typeof a !== "string")) {
316
329
  return badRequest(res, "subcommand must be a string and args an array of strings");
317
330
  }
318
- const plugin = getPluginById(req.params.id, projectRoot());
331
+ const plugin = res.locals.executionPlugin;
319
332
  if (!plugin) return res.status(404).json({ success: false, error: `Plugin ${req.params.id} not found` });
320
333
  if (!plugin.valid) return badRequest(res, `Plugin ${plugin.id} has an invalid manifest: ${plugin.errors.join("; ")}`);
321
334
  if (!plugin.manifest?.cli?.handler) return badRequest(res, `Plugin ${plugin.id} does not declare a CLI handler`);
@@ -330,7 +343,7 @@ router.post("/api/plugins/:id/run", requireAuth, requireScope("config:write"), a
330
343
  return res.json({ success: result.ok, pluginId: plugin.id, ...result });
331
344
  }
332
345
 
333
- const result = await runPluginCommand(plugin, { subcommand, args, ...(stdin !== undefined ? { stdinText: stdin } : {}), cwd: projectRoot(), secretsBrainDir: process.env.TR_SECRETS_BRAIN || globalBrainDir || BRAIN_DIR });
346
+ const result = await runPluginCommand(plugin, { subcommand, args, ...(stdin !== undefined ? { stdinText: stdin } : {}), cwd: projectRoot(), secretsBrainDir: process.env.TR_SECRETS_BRAIN || globalBrainDir || BRAIN_DIR, maxOutputBytes: 4 * 1024 * 1024 });
334
347
  res.json({ success: result.ok, pluginId: plugin.id, ...result });
335
348
  } catch (err) {
336
349
  badRequest(res, err.message);