total-recall-brain 3.41.2 → 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 (69) 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/cli.mjs +197 -31
  14. package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +4 -4
  15. package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +1 -1
  16. package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +8 -1
  17. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/DESIGN.md +56 -8
  18. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/card.js +1068 -204
  19. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/panel.js +4 -2
  20. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/DESIGN.md +32 -0
  21. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/card.js +436 -274
  22. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +4 -4
  23. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +1 -1
  24. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/DESIGN.md +16 -2
  25. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/card.js +461 -242
  26. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +4 -4
  27. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +1 -1
  28. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +1 -7
  29. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/DESIGN.md +17 -2
  30. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/card.js +426 -300
  31. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +4 -4
  32. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/DESIGN.md +32 -0
  33. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/card.js +350 -283
  34. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +4 -4
  35. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/ui/card.js +31 -19
  36. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +4 -4
  37. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +4 -4
  38. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/DESIGN.md +16 -2
  39. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +380 -298
  40. package/scaffold/.agent/skills/total-recall/plugins/security/cli.mjs +7 -1
  41. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +6 -6
  42. package/scaffold/.agent/skills/total-recall/plugins/security/package.json +1 -1
  43. package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +1 -1
  44. package/scaffold/.agent/skills/total-recall/plugins/security/src/scanner.mjs +17 -3
  45. package/scaffold/.agent/skills/total-recall/plugins/security/ui/DESIGN.md +38 -15
  46. package/scaffold/.agent/skills/total-recall/plugins/security/ui/card.js +60 -34
  47. package/src/cli/command.mjs +23 -18
  48. package/src/cli/daemon.mjs +23 -1
  49. package/src/cli/plugin/copies.mjs +8 -5
  50. package/src/cli/plugin/index.mjs +25 -2
  51. package/src/cli/rebuild.mjs +22 -0
  52. package/src/cli/reset-password.mjs +3 -2
  53. package/src/cli/skill.mjs +8 -2
  54. package/src/cli/status.mjs +25 -7
  55. package/src/core/automatic-brain-updates.mjs +9 -3
  56. package/src/core/brain-updates.mjs +63 -5
  57. package/src/core/global-brain-updates.mjs +21 -1
  58. package/src/core/mesh-brain-updates.mjs +5 -2
  59. package/src/core/meta-harness.mjs +16 -41
  60. package/src/core/plugin-runner.mjs +6 -1
  61. package/src/core/plugin-store.mjs +67 -1
  62. package/src/core/plugin-updates.mjs +1 -1
  63. package/src/core/portable-skills-maintenance.mjs +19 -0
  64. package/src/core/required-plugins.mjs +7 -0
  65. package/src/core/skills-registry.mjs +33 -3
  66. package/src/server/auth.mjs +41 -1
  67. package/src/server/memory-app.mjs +2 -1
  68. package/src/server/routes/auth.mjs +25 -1
  69. package/src/server/routes/plugins.mjs +17 -4
@@ -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);