total-recall-brain 3.33.1 → 3.34.0
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/bin/total-recall.mjs +5 -1
- package/docs/reference/cli-reference.md +11 -0
- package/package.json +2 -2
- package/plugins/creative-search/README.md +9 -2
- package/plugins/creative-search/cli.mjs +17 -23
- package/plugins/creative-search/config.mjs +46 -20
- package/plugins/creative-search/generator.mjs +9 -40
- package/plugins/creative-search/plugin.json +4 -2
- package/plugins/creative-search/ui/csearch-main.js +16 -13
- package/plugins/skill-manager/README.md +30 -0
- package/plugins/skill-manager/cli.mjs +104 -0
- package/plugins/skill-manager/plugin.json +24 -0
- package/scaffold/.agent/skills/decision/SKILL.md +20 -0
- package/scaffold/.agent/skills/decision/references/integration.md +25 -0
- package/scaffold/.agent/skills/decision/references/use-cases.md +20 -0
- package/scaffold/.agent/skills/meta-harness/SKILL.md +7 -105
- package/scaffold/.agent/skills/meta-harness/references/optimized/index.md +20 -0
- package/scaffold/.agent/skills/meta-harness/references/optimized/manifest.json +79 -0
- package/scaffold/.agent/skills/meta-harness/references/optimized/original.md +113 -0
- package/scaffold/.agent/skills/meta-harness/references/optimized/section-01.md +5 -0
- package/scaffold/.agent/skills/meta-harness/references/optimized/section-02.md +10 -0
- package/scaffold/.agent/skills/meta-harness/references/optimized/section-03.md +8 -0
- package/scaffold/.agent/skills/meta-harness/references/optimized/section-04.md +2 -0
- package/scaffold/.agent/skills/meta-harness/references/optimized/section-05.md +6 -0
- package/scaffold/.agent/skills/meta-harness/references/optimized/section-06.md +14 -0
- package/scaffold/.agent/skills/meta-harness/references/optimized/section-07.md +8 -0
- package/scaffold/.agent/skills/meta-harness/references/optimized/section-08.md +4 -0
- package/scaffold/.agent/skills/meta-harness/references/optimized/section-09.md +9 -0
- package/scaffold/.agent/skills/meta-harness/references/optimized/section-10.md +6 -0
- package/scaffold/.agent/skills/meta-harness/references/optimized/section-11.md +5 -0
- package/scaffold/.agent/skills/meta-harness/references/optimized/section-12.md +7 -0
- package/scaffold/.agent/skills/meta-harness/references/optimized/section-13.md +4 -0
- package/scaffold/.agent/skills/meta-harness/references/optimized/section-14.md +7 -0
- package/scaffold/.agent/skills/meta-harness/references/optimized/section-15.md +9 -0
- package/scaffold/.agent/skills/meta-harness/references/optimized/section-16.md +5 -0
- package/scaffold/.agent/skills/plugins/SKILL.md +7 -50
- package/scaffold/.agent/skills/plugins/references/optimized/index.md +9 -0
- package/scaffold/.agent/skills/plugins/references/optimized/manifest.json +35 -0
- package/scaffold/.agent/skills/plugins/references/optimized/original.md +58 -0
- package/scaffold/.agent/skills/plugins/references/optimized/section-01.md +5 -0
- package/scaffold/.agent/skills/plugins/references/optimized/section-02.md +25 -0
- package/scaffold/.agent/skills/plugins/references/optimized/section-03.md +12 -0
- package/scaffold/.agent/skills/plugins/references/optimized/section-04.md +7 -0
- package/scaffold/.agent/skills/plugins/references/optimized/section-05.md +5 -0
- package/scaffold/.agent/skills/project-management/SKILL.md +18 -0
- package/scaffold/.agent/skills/project-management/references/audit-template.md +91 -0
- package/scaffold/.agent/skills/project-management/references/project-documents.md +23 -0
- package/scaffold/.agent/skills/project-management/scripts/check-audit.mjs +49 -0
- package/scaffold/.agent/skills/start/SKILL.md +8 -65
- package/scaffold/.agent/skills/start/references/optimized/index.md +10 -0
- package/scaffold/.agent/skills/start/references/optimized/manifest.json +39 -0
- package/scaffold/.agent/skills/start/references/optimized/original.md +75 -0
- package/scaffold/.agent/skills/start/references/optimized/section-01.md +5 -0
- package/scaffold/.agent/skills/start/references/optimized/section-02.md +15 -0
- package/scaffold/.agent/skills/start/references/optimized/section-03.md +21 -0
- package/scaffold/.agent/skills/start/references/optimized/section-04.md +8 -0
- package/scaffold/.agent/skills/start/references/optimized/section-05.md +13 -0
- package/scaffold/.agent/skills/start/references/optimized/section-06.md +8 -0
- package/scaffold/.agent/skills/total-recall/SKILL.md +1 -1
- package/src/cli/connect.mjs +7 -14
- package/src/cli/skill-optimize.mjs +50 -0
- package/src/cli/skill-projection.mjs +22 -3
- package/src/cli/skill.mjs +7 -0
- package/src/core/plugin-context.mjs +3 -1
- package/src/core/plugin-loader.mjs +3 -0
- package/src/core/plugin-tasks.mjs +14 -3
- package/src/core/schema.mjs +2 -0
- package/src/core/skill-optimizer.mjs +190 -0
- package/templates/skill-routers.json +119 -0
package/bin/total-recall.mjs
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "3.34.0",
|
|
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.
|
|
71
|
+
"@ssss/cli": "github:gregiteen/ssss#v0.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
|
-
|
|
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
|
-
|
|
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
|
|
10
|
-
import os from 'node:os';
|
|
9
|
+
import { loadConfig, run as configure } from './config.mjs';
|
|
11
10
|
|
|
12
|
-
const
|
|
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
|
-
|
|
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 = `${
|
|
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
|
-
|
|
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
|
|
418
|
-
console.log('\nConfig: total-recall csearch
|
|
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
|
|
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: '
|
|
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
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
43
|
-
|
|
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(
|
|
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
|
-
|
|
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] =
|
|
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
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
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.
|
|
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 || '
|
|
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.
|