total-recall-brain 3.33.1 → 3.34.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.
Files changed (69) hide show
  1. package/bin/total-recall.mjs +5 -1
  2. package/docs/reference/cli-reference.md +11 -0
  3. package/package.json +2 -2
  4. package/plugins/creative-search/README.md +9 -2
  5. package/plugins/creative-search/cli.mjs +17 -23
  6. package/plugins/creative-search/config.mjs +46 -20
  7. package/plugins/creative-search/generator.mjs +9 -40
  8. package/plugins/creative-search/plugin.json +4 -2
  9. package/plugins/creative-search/ui/csearch-main.js +16 -13
  10. package/plugins/skill-manager/README.md +30 -0
  11. package/plugins/skill-manager/cli.mjs +104 -0
  12. package/plugins/skill-manager/plugin.json +24 -0
  13. package/scaffold/.agent/skills/decision/SKILL.md +20 -0
  14. package/scaffold/.agent/skills/decision/references/integration.md +25 -0
  15. package/scaffold/.agent/skills/decision/references/use-cases.md +20 -0
  16. package/scaffold/.agent/skills/meta-harness/SKILL.md +7 -105
  17. package/scaffold/.agent/skills/meta-harness/references/optimized/index.md +20 -0
  18. package/scaffold/.agent/skills/meta-harness/references/optimized/manifest.json +79 -0
  19. package/scaffold/.agent/skills/meta-harness/references/optimized/original.md +113 -0
  20. package/scaffold/.agent/skills/meta-harness/references/optimized/section-01.md +5 -0
  21. package/scaffold/.agent/skills/meta-harness/references/optimized/section-02.md +10 -0
  22. package/scaffold/.agent/skills/meta-harness/references/optimized/section-03.md +8 -0
  23. package/scaffold/.agent/skills/meta-harness/references/optimized/section-04.md +2 -0
  24. package/scaffold/.agent/skills/meta-harness/references/optimized/section-05.md +6 -0
  25. package/scaffold/.agent/skills/meta-harness/references/optimized/section-06.md +14 -0
  26. package/scaffold/.agent/skills/meta-harness/references/optimized/section-07.md +8 -0
  27. package/scaffold/.agent/skills/meta-harness/references/optimized/section-08.md +4 -0
  28. package/scaffold/.agent/skills/meta-harness/references/optimized/section-09.md +9 -0
  29. package/scaffold/.agent/skills/meta-harness/references/optimized/section-10.md +6 -0
  30. package/scaffold/.agent/skills/meta-harness/references/optimized/section-11.md +5 -0
  31. package/scaffold/.agent/skills/meta-harness/references/optimized/section-12.md +7 -0
  32. package/scaffold/.agent/skills/meta-harness/references/optimized/section-13.md +4 -0
  33. package/scaffold/.agent/skills/meta-harness/references/optimized/section-14.md +7 -0
  34. package/scaffold/.agent/skills/meta-harness/references/optimized/section-15.md +9 -0
  35. package/scaffold/.agent/skills/meta-harness/references/optimized/section-16.md +5 -0
  36. package/scaffold/.agent/skills/plugins/SKILL.md +7 -50
  37. package/scaffold/.agent/skills/plugins/references/optimized/index.md +9 -0
  38. package/scaffold/.agent/skills/plugins/references/optimized/manifest.json +35 -0
  39. package/scaffold/.agent/skills/plugins/references/optimized/original.md +58 -0
  40. package/scaffold/.agent/skills/plugins/references/optimized/section-01.md +5 -0
  41. package/scaffold/.agent/skills/plugins/references/optimized/section-02.md +25 -0
  42. package/scaffold/.agent/skills/plugins/references/optimized/section-03.md +12 -0
  43. package/scaffold/.agent/skills/plugins/references/optimized/section-04.md +7 -0
  44. package/scaffold/.agent/skills/plugins/references/optimized/section-05.md +5 -0
  45. package/scaffold/.agent/skills/project-management/SKILL.md +18 -0
  46. package/scaffold/.agent/skills/project-management/references/audit-template.md +91 -0
  47. package/scaffold/.agent/skills/project-management/references/project-documents.md +23 -0
  48. package/scaffold/.agent/skills/project-management/scripts/check-audit.mjs +49 -0
  49. package/scaffold/.agent/skills/start/SKILL.md +8 -65
  50. package/scaffold/.agent/skills/start/references/optimized/index.md +10 -0
  51. package/scaffold/.agent/skills/start/references/optimized/manifest.json +39 -0
  52. package/scaffold/.agent/skills/start/references/optimized/original.md +75 -0
  53. package/scaffold/.agent/skills/start/references/optimized/section-01.md +5 -0
  54. package/scaffold/.agent/skills/start/references/optimized/section-02.md +15 -0
  55. package/scaffold/.agent/skills/start/references/optimized/section-03.md +21 -0
  56. package/scaffold/.agent/skills/start/references/optimized/section-04.md +8 -0
  57. package/scaffold/.agent/skills/start/references/optimized/section-05.md +13 -0
  58. package/scaffold/.agent/skills/start/references/optimized/section-06.md +8 -0
  59. package/scaffold/.agent/skills/total-recall/SKILL.md +1 -1
  60. package/src/cli/connect.mjs +7 -14
  61. package/src/cli/skill-optimize.mjs +50 -0
  62. package/src/cli/skill-projection.mjs +22 -3
  63. package/src/cli/skill.mjs +7 -0
  64. package/src/core/plugin-context.mjs +3 -1
  65. package/src/core/plugin-loader.mjs +3 -0
  66. package/src/core/plugin-tasks.mjs +14 -3
  67. package/src/core/schema.mjs +2 -0
  68. package/src/core/skill-optimizer.mjs +190 -0
  69. package/templates/skill-routers.json +119 -0
@@ -266,7 +266,7 @@ async function main() {
266
266
  }
267
267
 
268
268
  // Check installed plugins for custom CLI commands declared in manifest
269
- const { discoverPlugins } = await import('../src/core/plugin-loader.mjs');
269
+ const { discoverPlugins, PACKAGE_ROOT } = await import('../src/core/plugin-loader.mjs');
270
270
  const plugins = discoverPlugins(process.cwd());
271
271
  for (const p of plugins) {
272
272
  if (p.valid && p.manifest.cli?.command === command) {
@@ -274,6 +274,10 @@ async function main() {
274
274
  if (handlerRel) {
275
275
  const absHandler = path.resolve(p.dir, handlerRel);
276
276
  if (fs.existsSync(absHandler)) {
277
+ // Match the host context supplied to dashboard/daemon handlers.
278
+ process.env.TR_PACKAGE_ROOT = PACKAGE_ROOT;
279
+ process.env.TR_PLUGIN_ID = p.id;
280
+ process.env.TR_PLUGIN_DIR = p.dir;
277
281
  const handler = await import(absHandler);
278
282
  if (handler.run) {
279
283
  await handler.run(process.argv);
@@ -441,6 +441,17 @@ Search, install, security audit, and remove packages from the skills.sh registry
441
441
  - `scan <skill-name>`: Trigger a static security vulnerability audit.
442
442
  - `list` (or `ls`): Enumerate all active parent skills and sub-skills.
443
443
  - `remove <name>` (or `rm`): Safely delete a skill and re-compile rules.
444
+ - `optimize [paths...]`: Audit skill ownership and entrypoint size. Explicit
445
+ `--apply` performs verified lossless extraction; original instructions and
446
+ references are retained. Ambiguous ownership requires review.
447
+
448
+ ### `skill-manager` (installed plugin)
449
+
450
+ `configure <file>` selects a mesh node and authorized roots from JSON on that
451
+ node. `status` reports configuration and the last SSSS report; `audit` inspects
452
+ without applying; `optimize` follows the configured `autoApply` preference.
453
+ The daemon runs daily at 03:00 local time and processes changed packages only.
454
+ See the bundled skill-manager README for global and repository configuration.
444
455
 
445
456
  ---
446
457
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "total-recall-brain",
3
- "version": "3.33.1",
3
+ "version": "3.34.1",
4
4
  "description": "Personal AI system — database-free, SSSS-powered intelligence engine deployable to any POSIX host.",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -68,7 +68,7 @@
68
68
  "homepage": "https://github.com/gregiteen/total-recall#readme",
69
69
  "dependencies": {
70
70
  "@simplewebauthn/server": "^13.3.2",
71
- "@ssss/cli": "github:gregiteen/ssss#v0.10.1",
71
+ "@ssss/cli": "npm:@gregiteen/ssss-cli@0.10.3",
72
72
  "bcrypt": "^6.0.0",
73
73
  "better-sqlite3": "^12.11.1",
74
74
  "cookie-parser": "^1.4.7",
@@ -4,7 +4,10 @@ SearXNG metasearch plugin. Its manifest registers query, categories, deep, remem
4
4
 
5
5
  ## Configuration and commands
6
6
 
7
- Provide `SEARXNG_URL` explicitly. The CLI currently has a personal mesh fallback, which violates generic configuration and is scheduled for removal. `TR_CLI` can select a host CLI file; its derived default is unreliable after installation.
7
+ Set `total-recall csearch config set searxngUrl <instance-url>` or override with
8
+ `SEARXNG_URL`. There is no default host. Settings validate and persist in the
9
+ owning SSSS plugin record. The CLI and generator consume that configuration;
10
+ compilation does not probe external engines. `TR_CLI` may override the host CLI.
8
11
 
9
12
  ```bash
10
13
  npx total-recall plugin install creative-search
@@ -17,7 +20,11 @@ npx total-recall csearch engines
17
20
  npx total-recall csearch stats
18
21
  ```
19
22
 
20
- There is no registered `csearch config` or `csearch-config` command. CLI settings load loose JSON selected by `AGENT_DIR` or a user-config path; UI settings use localStorage. They are disconnected and some controls are ignored. The correction requires one validated SSSS configuration consumed by every surface.
23
+ `csearch config show|set <key> <value>|reset` manages canonical settings. Legacy
24
+ loose JSON is not automatically imported. The overview panel accepts the host's
25
+ canonical `config` property and shows an unset state without a request. The
26
+ standalone settings panel's localStorage controls remain disconnected; host
27
+ settings persistence and a functioning UI mount still require correction.
21
28
 
22
29
  ## Current verified limitations
23
30
 
@@ -6,11 +6,9 @@
6
6
 
7
7
  import { spawnSync } from 'node:child_process';
8
8
  import path from 'node:path';
9
- import fs from 'node:fs';
10
- import os from 'node:os';
9
+ import { loadConfig, run as configure } from './config.mjs';
11
10
 
12
- const SEARXNG_URL = process.env.SEARXNG_URL || 'http://100.64.0.1:8888';
13
- const TR_CLI = process.env.TR_CLI || new URL('../../bin/total-recall.mjs', import.meta.url).pathname;
11
+ const TR_CLI = process.env.TR_CLI || (process.env.TR_PACKAGE_ROOT && path.join(process.env.TR_PACKAGE_ROOT, 'bin/total-recall.mjs'));
14
12
 
15
13
  // Stop words for keyword extraction
16
14
  const STOP_WORDS = new Set([
@@ -25,31 +23,25 @@ const STOP_WORDS = new Set([
25
23
  'more','most','some','any','new','using','based'
26
24
  ]);
27
25
 
28
- /** Load config; returns defaults if file not found */
29
- function loadConfig() {
30
- const CONFIG_PATH = process.env.AGENT_DIR
31
- ? path.join(process.env.AGENT_DIR, 'skills', 'total-recall', 'plugins', 'creative-search', 'config.json')
32
- : path.join(os.homedir(), '.config', 'total-recall', 'creative-search', 'config.json');
33
- try { return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8')); }
34
- catch { return {}; }
35
- }
36
-
37
26
  /** Search SearXNG with query + optional category */
38
- async function searchSearXNG(query, { categories, lang, limit } = {}) {
27
+ export async function searchSearXNG(query, { categories, lang, limit, config } = {}) {
28
+ const cfg = config || await loadConfig();
29
+ if (!cfg.searxngUrl) throw new Error('Configure your instance with csearch config set searxngUrl <URL>, or SEARXNG_URL');
39
30
  const params = new URLSearchParams({ q: query, format: 'json' });
40
31
  if (lang) params.set('language', lang);
41
32
  else params.set('language', 'en');
42
33
  if (categories) {
43
34
  // Categories can be a comma-separated string or an array
44
35
  const cats = Array.isArray(categories) ? categories : categories.split(',');
45
- for (const c of cats) params.append('categories', c.trim());
36
+ params.set('categories', cats.map(c => c.trim()).join(','));
46
37
  }
47
38
  // Limit results on SearXNG side if possible (SearXNG doesn't support this parameter directly)
48
- const url = `${SEARXNG_URL}/search?${params.toString()}`;
49
- const res = await fetch(url);
39
+ const url = `${cfg.searxngUrl}/search?${params.toString()}`;
40
+ const res = await fetch(url, { signal: AbortSignal.timeout(cfg.timeoutMs) });
50
41
  if (!res.ok) throw new Error(`SearXNG returned HTTP ${res.status}`);
51
42
  const data = await res.json();
52
- const results = (data.results || []).slice(0, limit || 15);
43
+ if (!Array.isArray(data.results)) throw new Error('SearXNG JSON results missing; enable JSON output on the instance');
44
+ const results = data.results.slice(0, limit || cfg.maxResults);
53
45
  return { results, unresponsive: data.unresponsive_engines || [] };
54
46
  }
55
47
 
@@ -104,10 +96,12 @@ export async function run(argv) {
104
96
  const args = argv.slice(4);
105
97
 
106
98
  switch (subcommand) {
99
+ case 'config':
100
+ return configure([argv[0], argv[1], argv[2], ...argv.slice(4)]);
107
101
 
108
102
  // ── query ──
109
103
  case 'query': {
110
- const cfg = loadConfig();
104
+ const cfg = await loadConfig();
111
105
  let categories = (cfg.categories || ['general']).join(',');
112
106
 
113
107
  // Extract --categories flag from args (can appear anywhere)
@@ -277,7 +271,7 @@ export async function run(argv) {
277
271
  const query = args.join(' ');
278
272
  if (!query) { console.error('Usage: total-recall csearch remember <search terms>'); process.exitCode = 1; return; }
279
273
 
280
- const cfg = loadConfig();
274
+ const cfg = await loadConfig();
281
275
  const categories = (cfg.categories || ['general', 'science']).join(',');
282
276
 
283
277
  const { results } = await searchSearXNG(query, {
@@ -414,8 +408,8 @@ export async function run(argv) {
414
408
  console.log(' health Check SearXNG health');
415
409
  console.log(' engines List active engines across categories');
416
410
  console.log('\nEnvironment:');
417
- console.log(' SEARXNG_URL (default: http://100.64.0.1:8888)');
418
- console.log('\nConfig: total-recall csearch-config show');
411
+ console.log(' SEARXNG_URL (optional instance URL override; no default host)');
412
+ console.log('\nConfig: total-recall csearch config show|set <key> <value>|reset');
419
413
  return;
420
414
  }
421
- }
415
+ }
@@ -2,15 +2,10 @@
2
2
  * Creative Search config — view and update plugin settings.
3
3
  */
4
4
  import path from 'node:path';
5
- import fs from 'node:fs';
6
- import os from 'node:os';
7
-
8
- const CONFIG_PATH = process.env.AGENT_DIR
9
- ? path.join(process.env.AGENT_DIR, 'skills', 'total-recall', 'plugins', 'creative-search', 'config.json')
10
- : path.join(os.homedir(), '.config', 'total-recall', 'creative-search', 'config.json');
5
+ import { pathToFileURL } from 'node:url';
11
6
 
12
7
  const DEFAULTS = {
13
- searxngUrl: 'http://100.64.0.1:8888',
8
+ searxngUrl: '',
14
9
  defaultLang: 'en',
15
10
  maxResults: 15,
16
11
  categories: ['general', 'science'],
@@ -33,21 +28,50 @@ const CATEGORY_DESCRIPTIONS = {
33
28
  timeoutMs: 'Request timeout in milliseconds',
34
29
  };
35
30
 
36
- function loadConfig() {
37
- try { return { ...DEFAULTS, ...JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8')) }; }
38
- catch { return { ...DEFAULTS }; }
31
+ export function validateConfig(input) {
32
+ if (!input || typeof input !== 'object' || Array.isArray(input)) throw new Error('Configuration must be an object');
33
+ for (const key of Object.keys(input)) if (!Object.hasOwn(DEFAULTS, key)) throw new Error(`Unsupported setting: ${key}`);
34
+ const config = { ...DEFAULTS, ...input };
35
+ if (typeof config.searxngUrl !== 'string') throw new Error('Instance URL must be a string');
36
+ if (config.searxngUrl) {
37
+ const url = new URL(config.searxngUrl);
38
+ if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password || url.search || url.hash) throw new Error('Use HTTP(S) without URL credentials, query or fragment');
39
+ config.searxngUrl = url.href.replace(/\/$/, '');
40
+ }
41
+ for (const [key, min, max] of [['maxResults', 1, 100], ['timeoutMs', 100, 60000], ['deepResearchSteps', 1, 5]]) {
42
+ if (!Number.isInteger(config[key]) || config[key] < min || config[key] > max) throw new Error(`${key} must be ${min}..${max}`);
43
+ }
44
+ for (const key of ['categories', 'engineGroups']) if (!Array.isArray(config[key]) || config[key].some(v => typeof v !== 'string' || !v.trim())) throw new Error(`${key} must contain strings`);
45
+ for (const key of ['autoSave', 'includeSnippets']) if (typeof config[key] !== 'boolean') throw new Error(`${key} must be boolean`);
46
+ if (typeof config.defaultLang !== 'string' || !config.defaultLang.trim()) throw new Error('Language must be nonempty');
47
+ return config;
48
+ }
49
+
50
+ async function hostApi({ packageRoot = process.env.TR_PACKAGE_ROOT, projectRoot = process.cwd(), plugin } = {}) {
51
+ if (!packageRoot) throw new Error('Run through the Total Recall plugin host');
52
+ const load = name => import(pathToFileURL(path.join(packageRoot, 'src/core', name)).href);
53
+ const [{ getPlugin }, store] = await Promise.all([load('plugin-loader.mjs'), load('plugin-store.mjs')]);
54
+ const installed = plugin || getPlugin('creative-search', projectRoot);
55
+ if (!installed?.valid) throw new Error('Install Creative Search first');
56
+ return { plugin: installed, store };
57
+ }
58
+
59
+ export async function loadConfig(host) {
60
+ const { plugin, store } = await hostApi(host);
61
+ const config = store.readPluginRecord(plugin)?.search_config || {};
62
+ return validateConfig({ ...config, ...(process.env.SEARXNG_URL ? { searxngUrl: process.env.SEARXNG_URL } : {}) });
39
63
  }
40
64
 
41
- function saveConfig(config) {
42
- fs.mkdirSync(path.dirname(CONFIG_PATH), { recursive: true });
43
- fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
65
+ async function saveConfig(config) {
66
+ const { plugin, store } = await hostApi();
67
+ await store.patchPluginRecord(plugin, { search_config: validateConfig(config) });
44
68
  }
45
69
 
46
70
  export async function run(argv) {
47
71
  const sub = argv[3] || 'show';
48
72
  switch (sub) {
49
73
  case 'show': {
50
- const c = loadConfig();
74
+ const c = await loadConfig();
51
75
  console.log('\n⚙️ Creative Search — Configuration\n');
52
76
  for (const [k, v] of Object.entries(c)) {
53
77
  const desc = CATEGORY_DESCRIPTIONS[k];
@@ -55,7 +79,7 @@ export async function run(argv) {
55
79
  if (desc) console.log(` ${desc}`);
56
80
  console.log();
57
81
  }
58
- console.log(` Config file: ${CONFIG_PATH}\n`);
82
+ console.log(' Storage: SSSS plugin record\n');
59
83
  return { data: c };
60
84
  }
61
85
  case 'set': {
@@ -69,12 +93,14 @@ export async function run(argv) {
69
93
  process.exitCode = 1;
70
94
  return;
71
95
  }
72
- const c = loadConfig();
96
+ if (!Object.hasOwn(DEFAULTS, key)) throw new Error(`Unsupported setting: ${key}`);
97
+ const { plugin, store } = await hostApi();
98
+ const c = validateConfig(store.readPluginRecord(plugin)?.search_config || {});
73
99
  // Parse numbers, booleans, and arrays
74
100
  if (key === 'categories' || key === 'engineGroups') {
75
101
  c[key] = value.split(',').map(s => s.trim());
76
102
  } else if (key === 'deepResearchSteps' || key === 'maxResults' || key === 'timeoutMs') {
77
- c[key] = parseInt(value, 10);
103
+ c[key] = Number(value);
78
104
  } else if (value === 'true') {
79
105
  c[key] = true;
80
106
  } else if (value === 'false') {
@@ -84,12 +110,12 @@ export async function run(argv) {
84
110
  } else {
85
111
  c[key] = value;
86
112
  }
87
- saveConfig(c);
113
+ await saveConfig(c);
88
114
  console.log(`✅ Set ${key} = ${JSON.stringify(c[key])}`);
89
115
  return { data: { [key]: c[key] } };
90
116
  }
91
117
  case 'reset':
92
- saveConfig({ ...DEFAULTS });
118
+ await saveConfig({ ...DEFAULTS });
93
119
  console.log('✅ Config reset to defaults');
94
120
  return { data: { ...DEFAULTS } };
95
121
  default:
@@ -100,4 +126,4 @@ export async function run(argv) {
100
126
  }
101
127
  return { data: { keys: Object.keys(CATEGORY_DESCRIPTIONS) } };
102
128
  }
103
- }
129
+ }
@@ -1,44 +1,13 @@
1
- /**
2
- * Creative Search — compiled agent context generator.
3
- * Called during `total-recall compile` to inject search status into agent surfaces.
4
- * @module @total-recall/plugin-creative-search/generator
5
- */
1
+ import { loadConfig } from './config.mjs';
6
2
 
7
- const SEARXNG_URL = process.env.SEARXNG_URL || 'http://100.64.0.1:8888';
8
-
9
- export async function generateContext() {
3
+ // Compilation reads settings only; external health probes are explicit CLI work.
4
+ export async function generateContext(host) {
10
5
  try {
11
- // Multi-category health check
12
- const cats = ['general', 'science', 'news', 'it'];
13
- const results = [];
14
- for (const cat of cats) {
15
- const start = Date.now();
16
- const res = await fetch(`${SEARXNG_URL}/search?q=health+check&format=json&language=en&categories=${cat}`);
17
- const ms = Date.now() - start;
18
- if (res.ok) {
19
- const data = await res.json();
20
- const engines = new Set();
21
- for (const r of data.results || []) {
22
- for (const e of r.engines || []) engines.add(e);
23
- }
24
- results.push({ category: cat, ms, count: data.results?.length || 0, engines: engines.size });
25
- } else {
26
- results.push({ category: cat, ms, count: 0, engines: 0 });
27
- }
28
- }
29
-
30
- const avgMs = Math.round(results.reduce((s, r) => s + r.ms, 0) / results.length);
31
- const totalEngines = results.reduce((s, r) => s + r.engines, 0);
32
- const online = results.filter(r => r.count > 0).length;
33
-
34
- return {
35
- section: 'creative-search',
36
- text: `Creative Search (SearXNG metasearch): online (${online}/${cats.length} categories, ~${avgMs}ms avg), ` +
37
- `~${totalEngines} engines available across ${cats.length} categories. ` +
38
- `Search: total-recall csearch query <terms> | Deep research: total-recall csearch deep <topic> | ` +
39
- `Save to TR memory: total-recall csearch remember <terms>.`,
40
- };
6
+ const config = await loadConfig(host);
7
+ return config.searxngUrl
8
+ ? 'Creative Search: configured SearXNG metasearch. Query: total-recall csearch query <terms>. Health: total-recall csearch health.'
9
+ : 'Creative Search: instance URL unset. Configure with total-recall csearch config set searxngUrl <URL>.';
41
10
  } catch {
42
- return { section: 'creative-search', text: '⚠️ Creative Search: SearXNG health check failed.' };
11
+ return 'Creative Search: configuration unavailable; inspect total-recall csearch config show.';
43
12
  }
44
- }
13
+ }
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://github.com/gregiteen/total-recall/blob/main/metadata.plugin.schema.json",
3
3
  "id": "creative-search",
4
4
  "name": "Creative Search",
5
- "version": "2.0.0",
5
+ "version": "2.1.0",
6
6
  "description": "SearXNG-powered creative metasearch with categories, deep research, TR memory integration, engine stats, and health monitoring.",
7
7
  "author": "Total Recall",
8
8
  "license": "MIT",
@@ -11,6 +11,7 @@
11
11
  "command": "csearch",
12
12
  "handler": "./cli.mjs",
13
13
  "subcommands": [
14
+ { "name": "config", "description": "Validated instance settings in SSSS" },
14
15
  { "name": "query", "description": "Search for terms" },
15
16
  { "name": "health", "description": "Check SearXNG health" },
16
17
  { "name": "engines", "description": "List active engines" },
@@ -41,6 +42,7 @@
41
42
  "module": "ui/csearch-main.js",
42
43
  "description": "Creative Search overview with branding, engine health, and quick commands",
43
44
  "events": [],
45
+ "props": { "config": { "type": "object", "description": "Host-supplied canonical search settings" } },
44
46
  "tokens": ["color-primary", "color-accent", "color-secondary", "color-success", "color-error", "color-warning", "color-text", "color-text-secondary", "color-text-tertiary", "color-surface", "color-surface-secondary", "color-border", "color-on-primary", "radius-sm", "radius-md", "radius-full", "spacing-xs", "spacing-sm", "spacing-md", "spacing-lg", "spacing-xl", "typography-body-size", "typography-h2-size", "typography-h3-size", "typography-caption-size", "font-family-ui", "font-family-mono"],
45
47
  "optional_tokens": []
46
48
  },
@@ -56,4 +58,4 @@
56
58
  }
57
59
  ]
58
60
  }
59
- }
61
+ }
@@ -236,7 +236,6 @@ const ENGINE_GROUPS = {
236
236
  'Multimedia': ['flickr', 'unsplash'],
237
237
  };
238
238
 
239
- const STORAGE_KEY = 'csearch-settings';
240
239
 
241
240
  export class CSearchMain extends HTMLElement {
242
241
  constructor() {
@@ -256,29 +255,33 @@ export class CSearchMain extends HTMLElement {
256
255
  }
257
256
 
258
257
  _loadSettings() {
259
- let categories = ['general', 'science'];
260
- try {
261
- const raw = localStorage.getItem(STORAGE_KEY);
262
- if (raw) {
263
- const parsed = JSON.parse(raw);
264
- if (parsed.categories && parsed.categories.length) categories = parsed.categories;
265
- }
266
- } catch {}
258
+ const categories = this.config.categories || ['general', 'science'];
267
259
  const container = this.shadowRoot.getElementById('categoriesList');
268
260
  container.innerHTML = categories.map(c => `<span class="tag">${c}</span>`).join('');
269
261
  }
270
262
 
263
+ set config(value) {
264
+ this._config = value || {};
265
+ if (this.isConnected) { this._loadSettings(); this._checkHealth(); }
266
+ }
267
+
268
+ get config() { return this._config || {}; }
269
+
271
270
  async _checkHealth() {
272
271
  const indicator = this.shadowRoot.getElementById('healthIndicator');
272
+ const base = this.config.searxngUrl;
273
+ if (!base) { indicator.textContent = 'Not configured'; this.shadowRoot.getElementById('engineCount').textContent = '—'; return; }
273
274
  try {
274
- const res = await fetch('http://100.64.0.1:8888/search?q=health&format=json&categories=general&engines=duckduckgo', {
275
+ const url = new URL(base);
276
+ if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password) throw new Error('Invalid instance URL');
277
+ const res = await fetch(`${url.href.replace(/\/$/, '')}/search?q=health&format=json&categories=general`, {
275
278
  signal: AbortSignal.timeout(5000),
276
279
  });
277
280
  if (res.ok) {
278
281
  const data = await res.json();
279
- const engineCount = data.engines ? Object.keys(data.engines).length : 0;
282
+ const engineCount = new Set((data.results || []).flatMap(result => result.engines || (result.engine ? [result.engine] : []))).size;
280
283
  indicator.textContent = engineCount > 0 ? `${engineCount} engines` : '✓ Online';
281
- this.shadowRoot.getElementById('engineCount').textContent = engineCount || '25+';
284
+ this.shadowRoot.getElementById('engineCount').textContent = engineCount || '—';
282
285
  } else {
283
286
  indicator.textContent = '⚠ Error';
284
287
  }
@@ -308,4 +311,4 @@ export class CSearchMain extends HTMLElement {
308
311
  }
309
312
 
310
313
  customElements.define('csearch-main', CSearchMain);
311
- export default CSearchMain;
314
+ export default CSearchMain;
@@ -0,0 +1,30 @@
1
+ # Skill Manager
2
+
3
+ The Total Recall daemon runs this plugin daily at 03:00 in the node's local time. A task stays idle until its
4
+ SSSS plugin record explicitly selects the current mesh node. Node identities,
5
+ skill roots and repository ownership are instance configuration.
6
+
7
+ Install on the intended node with `total-recall plugin install skill-manager`.
8
+ Create a configuration file on that node:
9
+
10
+ ```json
11
+ {
12
+ "node": "build-box",
13
+ "autoApply": false,
14
+ "maxTokens": 900,
15
+ "roots": [{ "path": "/absolute/skills/root", "scope": "global" }]
16
+ }
17
+ ```
18
+
19
+ For a repository skill root use `scope: "repository"` and an explicit
20
+ `repoRoot`. Run `total-recall skill-manager configure <file>` there, using
21
+ `total-recall mesh exec <node> '<command>'` for remote control. Configuration,
22
+ last results and schedule slots persist through SSSS operations. `status`
23
+ reports them; `audit` inspects without applying; `optimize` follows autoApply.
24
+
25
+ Only changed packages are reprocessed; changes to references or configuration
26
+ invalidate cached results. Automatic application is lossless: retained source,
27
+ reference hashes and essential rules remain available. Ambiguous boundaries,
28
+ ownership mismatch, symlinks or changed optimized instructions need review.
29
+ There is no model-generated rewrite or implicit traversal of other repositories.
30
+ The daemon records actual command exits, including review (2) and errors (1).
@@ -0,0 +1,104 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import crypto from 'node:crypto';
4
+ import { pathToFileURL } from 'node:url';
5
+
6
+ const digest = value => crypto.createHash('sha256').update(value).digest('hex');
7
+
8
+ export function validateConfig(input) {
9
+ if (!input || typeof input !== 'object' || Array.isArray(input)) throw new Error('Configuration must be an object');
10
+ if (typeof input.node !== 'string' || !input.node.trim()) throw new Error('Select a mesh node');
11
+ if (typeof input.autoApply !== 'boolean') throw new Error('autoApply must be explicitly true or false');
12
+ if (!Number.isInteger(input.maxTokens) || input.maxTokens < 100 || input.maxTokens > 10000) throw new Error('maxTokens must be 100..10000');
13
+ if (!Array.isArray(input.roots) || !input.roots.length) throw new Error('Authorize at least one skill root');
14
+ const roots = input.roots.map(root => {
15
+ if (!root || !path.isAbsolute(root.path || '')) throw new Error('Skill root must be an absolute path on the selected node');
16
+ if (!['global', 'repository'].includes(root.scope)) throw new Error('Root scope must be global or repository');
17
+ const physical = fs.realpathSync(root.path);
18
+ if (!fs.statSync(physical).isDirectory()) throw new Error('Skill root must be a directory');
19
+ if (physical.split(path.sep).some(part => ['node_modules', 'memory-vault', 'memory-derived', '.git'].includes(part))) throw new Error('Unauthorized skill root');
20
+ if (root.scope === 'global') return { path: physical, scope: 'global' };
21
+ if (!path.isAbsolute(root.repoRoot || '')) throw new Error('Repository root must be explicit');
22
+ const repoRoot = fs.realpathSync(root.repoRoot);
23
+ if (!physical.startsWith(repoRoot + path.sep)) throw new Error('Skill root must belong to its repository');
24
+ return { path: physical, scope: 'repository', repoRoot };
25
+ });
26
+ return { node: input.node.trim(), autoApply: input.autoApply, maxTokens: input.maxTokens, roots };
27
+ }
28
+
29
+ // Include supporting references: a changed requirement invalidates the cached audit.
30
+ export function packageHash(dir) {
31
+ const entries = [];
32
+ function visit(folder) {
33
+ for (const entry of fs.readdirSync(folder, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
34
+ if (entry.name.startsWith('.') || ['node_modules', 'memory-vault', 'memory-derived'].includes(entry.name)) continue;
35
+ const target = path.join(folder, entry.name);
36
+ if (entry.isSymbolicLink()) throw new Error('Skill package contains a symlink; review required');
37
+ if (entry.isDirectory()) visit(target);
38
+ else if (entry.isFile()) entries.push([path.relative(dir, target), digest(fs.readFileSync(target))]);
39
+ }
40
+ }
41
+ visit(dir);
42
+ return digest(JSON.stringify(entries));
43
+ }
44
+
45
+ export async function inspect(config, previous, api, { apply = false } = {}) {
46
+ const configHash = digest(JSON.stringify(config));
47
+ const cached = previous?.configHash === configHash && previous?.applied === apply ? previous.packages || {} : {};
48
+ const packages = {}, results = [];
49
+ for (const root of config.roots) {
50
+ const options = { maxTokens: config.maxTokens, apply, ...(root.scope === 'global' ? { globalRoot: root.path } : { repoRoot: root.repoRoot }) };
51
+ for (const dir of api.skillDirectories(root.path)) {
52
+ try {
53
+ const before = packageHash(dir);
54
+ if (cached[dir]?.hash === before) {
55
+ packages[dir] = cached[dir];
56
+ results.push({ ...cached[dir].result, cached: true });
57
+ continue;
58
+ }
59
+ const result = api.optimizeSkills([dir], options)[0];
60
+ results.push(result);
61
+ packages[dir] = { hash: packageHash(dir), result: result.status === 'applied' ? { ...result, status: 'current', reason: 'previously-applied', saved: 0 } : result };
62
+ } catch (err) { results.push({ path: dir, status: 'error', reason: err.message }); }
63
+ }
64
+ }
65
+ return { at: new Date().toISOString(), configHash, applied: apply, packages, results,
66
+ counts: results.reduce((counts, result) => ({ ...counts, [result.status]: (counts[result.status] || 0) + 1 }), {}) };
67
+ }
68
+
69
+ export async function run(argv) {
70
+ const packageRoot = process.env.TR_PACKAGE_ROOT;
71
+ if (!packageRoot) throw new Error('Run through total-recall skill-manager');
72
+ const load = module => import(pathToFileURL(path.join(packageRoot, 'src/core', module)).href);
73
+ const [{ getPlugin }, store, mesh, optimizer, { taskRunsOnNode }] = await Promise.all([
74
+ load('plugin-loader.mjs'), load('plugin-store.mjs'), load('mesh.mjs'), load('skill-optimizer.mjs'), load('plugin-tasks.mjs')
75
+ ]);
76
+ const plugin = getPlugin('skill-manager');
77
+ if (!plugin?.valid) throw new Error('Install a valid skill-manager plugin first');
78
+ const command = argv[3] || 'status';
79
+ let record = store.readPluginRecord(plugin);
80
+ if (command === 'configure') {
81
+ if (argv.length !== 5) throw new Error('Usage: skill-manager configure <JSON file on selected node>');
82
+ const config = validateConfig(JSON.parse(fs.readFileSync(argv[4], 'utf8')));
83
+ if (!taskRunsOnNode({ command: 'optimize', placement: 'selected-node' }, { task_nodes: { optimize: config.node } }, mesh.getMeshSelf())) {
84
+ throw new Error('Configure on the selected node using mesh exec <node>; no remote paths are guessed');
85
+ }
86
+ await store.patchPluginRecord(plugin, { optimizer_config: config, task_nodes: { ...record?.task_nodes, optimize: config.node } });
87
+ console.log(JSON.stringify({ configured: true, ...config }));
88
+ return;
89
+ }
90
+ if (command === 'status') {
91
+ console.log(JSON.stringify({ configured: !!record?.optimizer_config, node: record?.task_nodes?.optimize || null,
92
+ config: record?.optimizer_config || null, lastReport: record?.optimizer_report || null }));
93
+ return;
94
+ }
95
+ if (!['audit', 'optimize'].includes(command)) throw new Error('Unknown skill-manager command');
96
+ if (!record?.optimizer_config) throw new Error('Configure authorized roots and a selected node first');
97
+ if (!taskRunsOnNode({ command: 'optimize', placement: 'selected-node' }, record, mesh.getMeshSelf())) throw new Error('This is not the selected mesh node');
98
+ const config = validateConfig(record.optimizer_config);
99
+ const report = await inspect(config, record.optimizer_report, optimizer, { apply: command === 'optimize' && config.autoApply });
100
+ await store.patchPluginRecord(plugin, { optimizer_report: report });
101
+ console.log(JSON.stringify(report));
102
+ if (report.counts.error) process.exitCode = 1;
103
+ else if (report.counts.review) process.exitCode = 2;
104
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "id": "skill-manager",
3
+ "name": "Skill Manager",
4
+ "version": "1.0.0",
5
+ "description": "Conservative skill optimization on an explicitly selected daemon node and authorized roots.",
6
+ "license": "MIT",
7
+ "use_cases": ["skills", "automation"],
8
+ "cli": {
9
+ "command": "skill-manager",
10
+ "handler": "./cli.mjs",
11
+ "subcommands": [
12
+ { "name": "configure", "description": "Select a mesh node and authorized skill roots from a JSON configuration" },
13
+ { "name": "status", "description": "Show selection, configuration and last report" },
14
+ { "name": "audit", "description": "Inspect changed skill packages without applying changes" },
15
+ { "name": "optimize", "description": "Run configured conservative optimization on the selected node" }
16
+ ]
17
+ },
18
+ "tasks": [{
19
+ "intent": "Inspect changed skills and apply only authorized lossless optimization",
20
+ "schedule": "0 3 * * *",
21
+ "command": "optimize",
22
+ "placement": "selected-node"
23
+ }]
24
+ }
@@ -0,0 +1,20 @@
1
+ ---
2
+ name: decision
3
+ description: Design and use typed decisions for routing, classification, ranking and verification; configure Jev through the existing decision capability.
4
+ repo_scoped: false
5
+ ---
6
+
7
+ # Decision
8
+
9
+ Use one decision capability for closed choices, yes/no probabilities and ordered scores. Jev is a configurable engine; use a generative model when the task needs prose. Typed output still needs validation and domain evaluation.
10
+
11
+ Discover the current repository's decision plugin, configuration, credential resolver and local overlay. Confirm installed commands before invoking them. Resolve model, endpoint, timeout, budget and gates from config or flags; keep credentials in the existing secret store. Public skills contain portable methods, not repository or account instances.
12
+
13
+ Validate typed answers before branching. Choice/Score confidence differs from Noul's probability of yes; never default missing certainty to success. Calibrate outcome gates using representative cases. Missing config, invalid output, timeout or provider failure takes the deterministic fallback. Required instructions, scope and permissions stay enforced in code; model advice cannot override them.
14
+
15
+ Read only the reference needed now:
16
+
17
+ - [Integration and typed response contract](references/integration.md)
18
+ - [Skill routing and use cases](references/use-cases.md)
19
+
20
+ Keep shared state small; batch independent questions and compute arithmetic in code. Test synthetic success, abstention and failure cases before integration. Live provider evidence is separate from mocks. Record model/version, decision and measured outcome without secrets or unnecessary state. Preserve existing human approval requirements.