total-recall-brain 3.28.2 → 3.30.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 +24 -1
- package/docs/ARCHITECTURE.md +26 -18
- package/extension/background.js +259 -84
- package/extension/content-script.css +7 -13
- package/extension/content-script.js +252 -477
- package/extension/icons/icon-128.png +0 -0
- package/extension/icons/icon-16.png +0 -0
- package/extension/icons/icon-32.png +0 -0
- package/extension/icons/icon-48.png +0 -0
- package/extension/icons/mark.svg +6 -0
- package/extension/lib/brain-client.js +160 -73
- package/extension/lib/tokens.css +187 -0
- package/extension/lib/ui-helpers.js +229 -0
- package/extension/lib/ui-helpers.spec.mjs +94 -0
- package/extension/manifest.json +20 -5
- package/extension/options/options.css +64 -218
- package/extension/options/options.html +91 -43
- package/extension/options/options.js +208 -85
- package/extension/sidepanel/sidepanel.css +268 -970
- package/extension/sidepanel/sidepanel.html +200 -166
- package/extension/sidepanel/sidepanel.js +788 -942
- package/frontend/dist/assets/index-DGMNZi_1.js +5742 -0
- package/frontend/dist/index.html +1 -1
- package/frontend/src/api/plugins.ts +124 -92
- package/frontend/src/components/ResearchAgendaTab.spec.tsx +11 -0
- package/frontend/src/components/ResearchAgendaTab.tsx +19 -14
- package/frontend/src/pages/PluginsPage.spec.tsx +87 -16
- package/frontend/src/pages/PluginsPage.tsx +454 -1218
- package/package.json +2 -1
- package/plugins/code-quality/README.md +25 -0
- package/plugins/code-quality/cli.mjs +25 -0
- package/plugins/code-quality/plugin.json +17 -0
- package/plugins/git-sentinel/README.md +27 -0
- package/{scaffold/.agent/plugins → plugins}/git-sentinel/cli.mjs +8 -5
- package/plugins/git-sentinel/plugin.json +18 -0
- package/plugins/system-monitor/README.md +26 -0
- package/{scaffold/.agent/plugins → plugins}/system-monitor/cli.mjs +5 -4
- package/{scaffold/.agent/plugins → plugins}/system-monitor/generator.mjs +4 -5
- package/plugins/system-monitor/plugin.json +29 -0
- package/scaffold/.agent/skills/meta-harness/SKILL.md +4 -4
- package/scaffold/.agent/skills/plugins/SKILL.md +53 -0
- package/scaffold/.agent/skills/total-recall/scripts/brain-state.json +1 -0
- package/src/cli/agent.mjs +1 -1
- package/src/cli/app/index.mjs +493 -0
- package/src/cli/brain.mjs +33 -3
- package/src/cli/command.mjs +80 -41
- package/src/cli/harness.mjs +1 -1
- package/src/cli/init.mjs +3 -1
- package/src/cli/mesh.mjs +118 -12
- package/src/cli/plugin/create.mjs +144 -25
- package/src/cli/plugin/index.mjs +83 -53
- package/src/cli/plugin/install.mjs +10 -127
- package/src/cli/plugin/list.mjs +44 -44
- package/src/cli/plugin/remove.mjs +23 -30
- package/src/cli/plugin/scaffold-cmd.mjs +30 -0
- package/src/cli/plugin/search.mjs +108 -23
- package/src/cli/rebuild.mjs +37 -1
- package/src/cli/recall.mjs +17 -2
- package/src/cli/research.mjs +4 -6
- package/src/cli/share.mjs +2 -0
- package/src/core/app-deploy/apply.mjs +386 -0
- package/src/core/app-deploy/design-tokens.mjs +217 -0
- package/src/core/app-deploy/plan.mjs +249 -0
- package/src/core/app-deploy/resolve.mjs +295 -0
- package/src/core/app-deploy/source.mjs +337 -0
- package/src/core/app-deploy/standalone.mjs +248 -0
- package/src/core/app-deploy/upgrade.mjs +256 -0
- package/src/core/app-deploy/verify.mjs +213 -0
- package/src/core/bound-hosts.mjs +1 -1
- package/src/core/brain-registry.mjs +146 -0
- package/src/core/brain-state.json +1 -0
- package/src/core/daemon-loop.mjs +30 -26
- package/src/core/dream.mjs +5 -17
- package/src/core/fact-seeker.mjs +53 -368
- package/src/core/lan-discovery.mjs +3 -3
- package/src/core/mesh-access-sync.mjs +134 -0
- package/src/core/mesh-access.mjs +18 -3
- package/src/core/mesh-discover.mjs +173 -0
- package/src/core/mesh.mjs +131 -32
- package/src/core/optimizer.mjs +4 -41
- package/src/core/plugin-bundle.mjs +167 -0
- package/src/core/plugin-context.mjs +8 -13
- package/src/core/plugin-loader.mjs +272 -68
- package/src/core/plugin-peers.mjs +163 -0
- package/src/core/plugin-public.mjs +85 -0
- package/src/core/plugin-runner-child.mjs +24 -0
- package/src/core/plugin-runner.mjs +102 -0
- package/src/core/plugin-store.mjs +384 -0
- package/src/core/plugin-tasks.mjs +212 -0
- package/src/core/post-mortem.mjs +40 -9
- package/src/core/research-gate.mjs +182 -0
- package/src/core/research-queue.mjs +47 -7
- package/src/core/research-surface.mjs +211 -0
- package/src/core/research.mjs +25 -32
- package/src/core/scheduler.mjs +67 -153
- package/src/core/schema.mjs +129 -0
- package/src/core/search.mjs +12 -1
- package/src/core/secret-integration-research.mjs +12 -8
- package/src/core/secrets-store.mjs +32 -2
- package/src/core/session-watcher.mjs +6 -0
- package/src/core/surface.mjs +64 -3
- package/src/core/task-executors.mjs +8 -45
- package/src/core/vault-cache.mjs +70 -4
- package/src/server/api.mjs +21 -0
- package/src/server/rest.mjs +2 -0
- package/src/server/route-manifest.json +21 -5
- package/src/server/routes/collab.mjs +38 -11
- package/src/server/routes/extension.mjs +3 -2
- package/src/server/routes/memory.mjs +24 -2
- package/src/server/routes/mesh.mjs +7 -7
- package/src/server/routes/plugins-mesh.mjs +49 -0
- package/src/server/routes/plugins.mjs +91 -426
- package/src/server/routes/research.mjs +5 -2
- package/src/server/routes/share.mjs +2 -0
- package/src/server/tools.mjs +29 -2
- package/extension/icons/README.md +0 -27
- package/extension/lib/preconfigured.js +0 -5
- package/extension/popup/popup.css +0 -227
- package/extension/popup/popup.html +0 -50
- package/extension/popup/popup.js +0 -175
- package/frontend/dist/assets/index-U7o-qU99.js +0 -5742
- package/scaffold/.agent/plugins/.gitkeep +0 -0
- package/scaffold/.agent/plugins/code-quality/README.md +0 -16
- package/scaffold/.agent/plugins/code-quality/cli.mjs +0 -15
- package/scaffold/.agent/plugins/code-quality/plugin.json +0 -34
- package/scaffold/.agent/plugins/git-sentinel/README.md +0 -16
- package/scaffold/.agent/plugins/git-sentinel/plugin.json +0 -34
- package/scaffold/.agent/plugins/meta-harness/README.md +0 -16
- package/scaffold/.agent/plugins/meta-harness/cli.mjs +0 -34
- package/scaffold/.agent/plugins/meta-harness/plugin.json +0 -34
- package/scaffold/.agent/plugins/system-monitor/README.md +0 -16
- package/scaffold/.agent/plugins/system-monitor/plugin.json +0 -38
package/src/cli/plugin/list.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { listInstalledPlugins } from '../../core/plugin-store.mjs';
|
|
2
|
+
import { projectPluginsDir, globalPluginsDir } from '../../core/plugin-loader.mjs';
|
|
2
3
|
|
|
3
4
|
function stripAnsi(str) {
|
|
4
5
|
return String(str || '').replace(/\x1b\[[0-9;]*m/g, '');
|
|
@@ -10,10 +11,14 @@ function padAnsi(str, targetLen) {
|
|
|
10
11
|
return String(str || '') + ' '.repeat(paddingNeeded);
|
|
11
12
|
}
|
|
12
13
|
|
|
14
|
+
function clip(str, len) {
|
|
15
|
+
const s = String(str || '');
|
|
16
|
+
return s.length > len ? s.slice(0, len - 1) + '…' : s;
|
|
17
|
+
}
|
|
18
|
+
|
|
13
19
|
export async function listPlugins(args = []) {
|
|
14
20
|
const isJson = args.includes('--json');
|
|
15
|
-
const
|
|
16
|
-
const plugins = discoverPlugins(projectRoot);
|
|
21
|
+
const plugins = listInstalledPlugins(process.cwd());
|
|
17
22
|
|
|
18
23
|
if (isJson) {
|
|
19
24
|
console.log(JSON.stringify(plugins, null, 2));
|
|
@@ -23,61 +28,56 @@ export async function listPlugins(args = []) {
|
|
|
23
28
|
console.log(`\n🔌 Total Recall — Installed Plugins\n`);
|
|
24
29
|
|
|
25
30
|
if (plugins.length === 0) {
|
|
26
|
-
console.log(
|
|
27
|
-
console.log(
|
|
31
|
+
console.log(` No plugins installed in ${projectPluginsDir(process.cwd())}`);
|
|
32
|
+
console.log(` or globally in ${globalPluginsDir()}.`);
|
|
33
|
+
console.log(' See what you can install: npx total-recall plugin available\n');
|
|
28
34
|
return;
|
|
29
35
|
}
|
|
30
36
|
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
37
|
+
const cols = [
|
|
38
|
+
['Plugin ID', 22],
|
|
39
|
+
['Version', 9],
|
|
40
|
+
['Scope', 8],
|
|
41
|
+
['Source', 22],
|
|
42
|
+
['Shared', 7],
|
|
43
|
+
['Status', 10]
|
|
44
|
+
];
|
|
45
|
+
const line = (l, m, r) => l + cols.map(([, w]) => '─'.repeat(w + 2)).join(m) + r;
|
|
46
|
+
const row = (cells) => '│ ' + cells.map((c, i) => padAnsi(c, cols[i][1])).join(' │ ') + ' │';
|
|
40
47
|
|
|
41
|
-
console.log(
|
|
42
|
-
console.log(
|
|
43
|
-
|
|
44
|
-
`${padAnsi('Name', colName)} │ ` +
|
|
45
|
-
`${padAnsi('Version', colVer)} │ ` +
|
|
46
|
-
`${padAnsi('Status', colStatus)} │ ` +
|
|
47
|
-
`${padAnsi('SSSS Categories', colCats)} │`
|
|
48
|
-
);
|
|
49
|
-
console.log(midBorder);
|
|
48
|
+
console.log(line('┌', '┬', '┐'));
|
|
49
|
+
console.log(row(cols.map(([h]) => h)));
|
|
50
|
+
console.log(line('├', '┼', '┤'));
|
|
50
51
|
|
|
51
52
|
for (const p of plugins) {
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
`${padAnsi(catStr, colCats)} │`
|
|
65
|
-
);
|
|
53
|
+
const source = p.source.kind === 'peer' ? `peer ${p.source.peer_hostname}` : p.source.kind;
|
|
54
|
+
const status = !p.valid
|
|
55
|
+
? '\x1b[31minvalid\x1b[0m'
|
|
56
|
+
: p.modified_since_install ? '\x1b[33mmodified\x1b[0m' : '\x1b[32mok\x1b[0m';
|
|
57
|
+
console.log(row([
|
|
58
|
+
`\x1b[1m${clip(p.id, 22)}\x1b[0m`,
|
|
59
|
+
`v${p.version}`,
|
|
60
|
+
p.scope,
|
|
61
|
+
clip(source, 22),
|
|
62
|
+
p.shared ? 'yes' : 'no',
|
|
63
|
+
status
|
|
64
|
+
]));
|
|
66
65
|
}
|
|
67
66
|
|
|
68
|
-
console.log(
|
|
67
|
+
console.log(line('└', '┴', '┘'));
|
|
69
68
|
|
|
70
|
-
// Show detailed validation errors if any
|
|
71
69
|
const invalid = plugins.filter(p => !p.valid);
|
|
72
70
|
if (invalid.length > 0) {
|
|
73
|
-
console.log(`\n⚠️
|
|
71
|
+
console.log(`\n⚠️ Manifest problems:`);
|
|
74
72
|
for (const inv of invalid) {
|
|
75
|
-
console.log(`
|
|
76
|
-
for (const err of inv.errors) {
|
|
77
|
-
console.log(` - ${err}`);
|
|
78
|
-
}
|
|
73
|
+
console.log(` ${inv.id}:`);
|
|
74
|
+
for (const err of inv.errors) console.log(` - ${err}`);
|
|
79
75
|
}
|
|
80
76
|
}
|
|
77
|
+
const modified = plugins.filter(p => p.modified_since_install);
|
|
78
|
+
if (modified.length > 0) {
|
|
79
|
+
console.log(`\nℹ️ Changed on disk since install (content hash differs): ${modified.map(p => p.id).join(', ')}`);
|
|
80
|
+
}
|
|
81
81
|
|
|
82
82
|
console.log();
|
|
83
83
|
}
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import os from 'node:os';
|
|
1
|
+
import { uninstallPlugin, setPluginShared } from '../../core/plugin-store.mjs';
|
|
2
|
+
import { publicPluginShareUrl } from '../../core/plugin-public.mjs';
|
|
4
3
|
|
|
5
4
|
export async function removePlugin(args = []) {
|
|
6
5
|
const isGlobal = args.includes('--global') || args.includes('-g');
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
const id = cleanArgs[0];
|
|
6
|
+
const id = args.find(a => !a.startsWith('-'));
|
|
10
7
|
|
|
11
8
|
if (!id) {
|
|
12
9
|
console.error('❌ Error: Missing plugin ID to remove.');
|
|
@@ -14,38 +11,34 @@ export async function removePlugin(args = []) {
|
|
|
14
11
|
process.exit(1);
|
|
15
12
|
}
|
|
16
13
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
let targetPath = null;
|
|
25
|
-
for (const dir of searchDirs) {
|
|
26
|
-
const candidate = path.join(dir, id);
|
|
27
|
-
if (fs.existsSync(candidate)) {
|
|
28
|
-
targetPath = candidate;
|
|
29
|
-
break;
|
|
30
|
-
}
|
|
14
|
+
try {
|
|
15
|
+
const result = await uninstallPlugin(id, { projectRoot: process.cwd(), global: isGlobal ? true : undefined });
|
|
16
|
+
console.log(`\n✅ Removed plugin '${id}' (${result.scope}) from ${result.dir}\n`);
|
|
17
|
+
} catch (err) {
|
|
18
|
+
console.error(`❌ ${err.message}`);
|
|
19
|
+
process.exit(1);
|
|
31
20
|
}
|
|
21
|
+
}
|
|
32
22
|
|
|
33
|
-
|
|
34
|
-
|
|
23
|
+
export async function sharePlugin(args = [], shared = true) {
|
|
24
|
+
const id = args.find(a => !a.startsWith('-'));
|
|
25
|
+
if (!id) {
|
|
26
|
+
console.error(`❌ Error: Missing plugin ID. Usage: total-recall plugin ${shared ? 'share' : 'unshare'} <id>`);
|
|
35
27
|
process.exit(1);
|
|
36
28
|
}
|
|
37
|
-
|
|
38
29
|
try {
|
|
39
|
-
const
|
|
40
|
-
if (
|
|
41
|
-
|
|
42
|
-
|
|
30
|
+
const plugin = await setPluginShared(id, shared, { projectRoot: process.cwd() });
|
|
31
|
+
if (shared) {
|
|
32
|
+
console.log(`\n✅ '${id}' is now shared (sha256 ${plugin.sha256?.slice(0, 16)}…).`);
|
|
33
|
+
const url = publicPluginShareUrl(id, plugin.sha256);
|
|
34
|
+
if (url) console.log(` Send this link to another Total Recall user: ${url}`);
|
|
35
|
+
else console.log(' Set TR_PUBLIC_BASE_URL to your public HTTPS origin to print a share link.');
|
|
36
|
+
console.log(' Mesh peers can also install it with: npx total-recall plugin install peer:<this-node>/' + id + '\n');
|
|
43
37
|
} else {
|
|
44
|
-
|
|
45
|
-
console.log(`\n✅ Removed plugin '${id}' from ${targetPath}\n`);
|
|
38
|
+
console.log(`\n✅ '${id}' is no longer shared.\n`);
|
|
46
39
|
}
|
|
47
40
|
} catch (err) {
|
|
48
|
-
console.error(`❌
|
|
41
|
+
console.error(`❌ ${err.message}`);
|
|
49
42
|
process.exit(1);
|
|
50
43
|
}
|
|
51
44
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Scaffold CLI command handler
|
|
4
|
+
* Handles:
|
|
5
|
+
* npx total-recall scaffold-plugin <id> [options]
|
|
6
|
+
* npx total-recall scaffold plugin <id> [options]
|
|
7
|
+
* npx total-recall scaffold repo <path> [options]
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createPlugin } from './create.mjs';
|
|
11
|
+
|
|
12
|
+
export async function run(argv = []) {
|
|
13
|
+
let args = Array.isArray(argv) ? argv.slice(2) : [];
|
|
14
|
+
if (args[0] === 'scaffold' || args[0] === 'scaffold-plugin') {
|
|
15
|
+
args = args.slice(1);
|
|
16
|
+
}
|
|
17
|
+
if (args[0] === 'plugin') {
|
|
18
|
+
args = args.slice(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
await createPlugin(args);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default async function (args = []) {
|
|
25
|
+
let cleanArgs = Array.isArray(args) ? args : [];
|
|
26
|
+
if (cleanArgs[0] === 'plugin') {
|
|
27
|
+
cleanArgs = cleanArgs.slice(1);
|
|
28
|
+
}
|
|
29
|
+
await createPlugin(cleanArgs);
|
|
30
|
+
}
|
|
@@ -1,37 +1,122 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { listAvailableBundled } from '../../core/plugin-store.mjs';
|
|
2
|
+
import { listPeerPlugins } from '../../core/plugin-peers.mjs';
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
const isJson = args.includes(
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
p.tags.some(t => t.toLowerCase().includes(query))
|
|
15
|
-
);
|
|
4
|
+
function parseArgs(args) {
|
|
5
|
+
const isJson = args.includes('--json');
|
|
6
|
+
let useCase = null;
|
|
7
|
+
const words = [];
|
|
8
|
+
for (let i = 0; i < args.length; i++) {
|
|
9
|
+
if (args[i] === '--json') continue;
|
|
10
|
+
if (args[i] === '--use-case' && args[i + 1]) {
|
|
11
|
+
useCase = args[++i];
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
words.push(args[i]);
|
|
16
15
|
}
|
|
16
|
+
return { isJson, useCase, query: words.join(' ').toLowerCase().trim() };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function matches(p, query, useCase) {
|
|
20
|
+
if (useCase && !(p.use_cases || []).includes(useCase)) return false;
|
|
21
|
+
if (!query) return true;
|
|
22
|
+
return [p.id, p.name, p.description, ...(p.use_cases || [])]
|
|
23
|
+
.some((v) => String(v || '').toLowerCase().includes(query));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function printPlugin(p, installHint) {
|
|
27
|
+
const installed = p.installed ? ' \x1b[32m(installed)\x1b[0m' : '';
|
|
28
|
+
console.log(`\x1b[1;36m● ${p.name}\x1b[0m (id: \x1b[33m${p.id}\x1b[0m, v${p.version})${installed}`);
|
|
29
|
+
console.log(` ${p.description}`);
|
|
30
|
+
if (p.use_cases?.length) console.log(` Use cases: ${p.use_cases.join(', ')}`);
|
|
31
|
+
if (p.sha256) console.log(` sha256: ${p.sha256.slice(0, 16)}… (${p.file_count} files, ${p.size_bytes} bytes)`);
|
|
32
|
+
if (!p.installed) console.log(` Install: \x1b[32mnpx total-recall plugin install ${installHint}\x1b[0m`);
|
|
33
|
+
console.log();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** `plugin available` — plugins bundled with this Total Recall package. */
|
|
37
|
+
export async function listAvailable(args = []) {
|
|
38
|
+
const { isJson, useCase, query } = parseArgs(args);
|
|
39
|
+
const results = listAvailableBundled(process.cwd()).filter((p) => matches(p, query, useCase));
|
|
17
40
|
|
|
18
41
|
if (isJson) {
|
|
19
42
|
console.log(JSON.stringify(results, null, 2));
|
|
20
43
|
return;
|
|
21
44
|
}
|
|
22
45
|
|
|
23
|
-
console.log(
|
|
46
|
+
console.log('\n📦 Total Recall — Bundled Plugins\n');
|
|
24
47
|
if (results.length === 0) {
|
|
25
|
-
console.log(
|
|
48
|
+
console.log(`No bundled plugins match${query ? ` "${query}"` : ''}${useCase ? ` for use case "${useCase}"` : ''}.\n`);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
for (const p of results) printPlugin(p, p.id);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** `plugin peers` — plugins shared by mesh peers, asked live. */
|
|
55
|
+
export async function listPeers(args = []) {
|
|
56
|
+
const { isJson, useCase, query } = parseArgs(args);
|
|
57
|
+
const result = await listPeerPlugins();
|
|
58
|
+
for (const peer of result.peers) {
|
|
59
|
+
peer.plugins = peer.plugins.filter((p) => matches(p, query, useCase));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (isJson) {
|
|
63
|
+
console.log(JSON.stringify(result, null, 2));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
console.log('\n🛰️ Total Recall — Plugins Shared on the Mesh\n');
|
|
68
|
+
if (!result.mesh.available) {
|
|
69
|
+
console.log(' This node is not connected to a mesh. Set one up with: npx total-recall mesh enroll (see: npx total-recall mesh --help)\n');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (result.peers.length === 0) {
|
|
73
|
+
console.log(' No other nodes on the mesh.\n');
|
|
26
74
|
return;
|
|
27
75
|
}
|
|
76
|
+
for (const peer of result.peers) {
|
|
77
|
+
const label = peer.status === 'ok'
|
|
78
|
+
? `\x1b[32m${peer.plugins.length} shared\x1b[0m`
|
|
79
|
+
: `\x1b[33m${peer.status}\x1b[0m${peer.error ? ` — ${peer.error}` : ''}`;
|
|
80
|
+
console.log(`\x1b[1m${peer.hostname}\x1b[0m (${peer.ip}) ${label}`);
|
|
81
|
+
for (const p of peer.plugins) {
|
|
82
|
+
console.log(` ● ${p.name} (id: \x1b[33m${p.id}\x1b[0m, v${p.version})`);
|
|
83
|
+
console.log(` ${p.description}`);
|
|
84
|
+
if (p.use_cases.length) console.log(` Use cases: ${p.use_cases.join(', ')}`);
|
|
85
|
+
console.log(` sha256 ${p.sha256.slice(0, 16)}… · ${p.file_count} files · ${p.size_bytes} bytes`);
|
|
86
|
+
console.log(` Install: \x1b[32mnpx total-recall plugin install peer:${peer.hostname}/${p.id}\x1b[0m`);
|
|
87
|
+
}
|
|
88
|
+
console.log();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** `plugin search <query>` — bundled plugins plus whatever mesh peers share. */
|
|
93
|
+
export async function searchPlugins(args = []) {
|
|
94
|
+
const { isJson, useCase, query } = parseArgs(args);
|
|
95
|
+
const bundled = listAvailableBundled(process.cwd()).filter((p) => matches(p, query, useCase));
|
|
96
|
+
const peers = await listPeerPlugins();
|
|
97
|
+
const shared = [];
|
|
98
|
+
for (const peer of peers.peers) {
|
|
99
|
+
for (const p of peer.plugins) {
|
|
100
|
+
if (matches(p, query, useCase)) shared.push({ ...p, peer: peer.hostname, source: `peer:${peer.hostname}/${p.id}` });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
28
103
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
104
|
+
if (isJson) {
|
|
105
|
+
console.log(JSON.stringify({ bundled, peers: shared }, null, 2));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
console.log(`\n🔎 Plugins matching "${query || '*'}"${useCase ? ` (use case: ${useCase})` : ''}\n`);
|
|
110
|
+
if (bundled.length === 0 && shared.length === 0) {
|
|
111
|
+
console.log('No plugins found.\n');
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (bundled.length) {
|
|
115
|
+
console.log('Bundled:\n');
|
|
116
|
+
for (const p of bundled) printPlugin(p, p.id);
|
|
117
|
+
}
|
|
118
|
+
if (shared.length) {
|
|
119
|
+
console.log('Shared on the mesh:\n');
|
|
120
|
+
for (const p of shared) printPlugin({ ...p, name: `${p.name} — from ${p.peer}` }, p.source);
|
|
36
121
|
}
|
|
37
122
|
}
|
package/src/cli/rebuild.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { compileSurface } from '../core/surface.mjs';
|
|
|
4
4
|
import { detectIndexDrift } from '../core/drift-detector.mjs';
|
|
5
5
|
import { resolveAgentDir, resolveBrainDir, parseLayerFlag } from './agent-dir.mjs';
|
|
6
6
|
import { logger } from '../core/logger.mjs';
|
|
7
|
+
import { listLocalBrains } from '../core/brain-registry.mjs';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* SSSS Projection Rebuild Command
|
|
@@ -107,12 +108,47 @@ export async function runRebuild(options = {}) {
|
|
|
107
108
|
}
|
|
108
109
|
}
|
|
109
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Recompile every registered project brain's surfaces. Each project's rules
|
|
113
|
+
* are the global rules plus its own (see mergeGlobalRuleNodes), so a global
|
|
114
|
+
* change is not done until every project has been rebuilt. Incremental: a
|
|
115
|
+
* project whose vault and the global rules are unchanged is skipped.
|
|
116
|
+
*/
|
|
117
|
+
export async function compileRegisteredProjects({ brains = listLocalBrains(), compile = compileSurface } = {}) {
|
|
118
|
+
let failed = 0;
|
|
119
|
+
for (const brain of brains) {
|
|
120
|
+
if (brain.kind !== 'project' || !brain.exists) continue;
|
|
121
|
+
const agentDir = path.dirname(path.dirname(brain.brainDir));
|
|
122
|
+
try {
|
|
123
|
+
const stats = await compile({
|
|
124
|
+
vaultDir: path.join(brain.brainDir, 'memory-vault'),
|
|
125
|
+
skillsDir: path.join(agentDir, 'skills'),
|
|
126
|
+
derivedDir: path.join(brain.brainDir, 'memory-derived'),
|
|
127
|
+
instructionsFile: path.join(agentDir, 'INSTRUCTIONS.md'),
|
|
128
|
+
});
|
|
129
|
+
console.log(`✅ ${brain.name}: ${stats.skipped ? 'unchanged' : `rules recompiled (${stats.skillsInjected} surfaces)`}`);
|
|
130
|
+
} catch (err) {
|
|
131
|
+
failed += 1;
|
|
132
|
+
console.error(`❌ ${brain.name}: ${err.message}`);
|
|
133
|
+
logger.error('rebuild', `Project compile failed for ${brain.brainDir}: ${err.message}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return failed;
|
|
137
|
+
}
|
|
138
|
+
|
|
110
139
|
export default async function cli(args) {
|
|
111
140
|
const { layer, remainingArgs } = parseLayerFlag(args);
|
|
112
141
|
const options = {
|
|
113
142
|
check: remainingArgs.includes('--check'),
|
|
114
143
|
layer,
|
|
115
144
|
};
|
|
116
|
-
|
|
145
|
+
let code = await runRebuild(options);
|
|
146
|
+
// Global rules land in every project's surfaces, so a global compile (what
|
|
147
|
+
// `remember --global` runs) or an explicit --all rebuilds each project too.
|
|
148
|
+
if (code === 0 && !options.check && (layer === 'global' || remainingArgs.includes('--all'))) {
|
|
149
|
+
console.log('--------------------------');
|
|
150
|
+
console.log('🔁 Recompiling registered project brains...');
|
|
151
|
+
if (await compileRegisteredProjects()) code = 1;
|
|
152
|
+
}
|
|
117
153
|
process.exit(code);
|
|
118
154
|
}
|
package/src/cli/recall.mjs
CHANGED
|
@@ -106,7 +106,7 @@ export default async function recall(args) {
|
|
|
106
106
|
|
|
107
107
|
// Determine which brains to search
|
|
108
108
|
const brains = getBothBrains();
|
|
109
|
-
|
|
109
|
+
let searchTargets = [];
|
|
110
110
|
|
|
111
111
|
if (layer === 'global' || layer === 'auto') {
|
|
112
112
|
if (brains.global) {
|
|
@@ -119,8 +119,23 @@ export default async function recall(args) {
|
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
// Brains switched off with `brain off` are not read. Filtered here, after
|
|
123
|
+
// layer selection, so an explicit --layer on an off brain says why it came
|
|
124
|
+
// back empty instead of silently searching nothing.
|
|
125
|
+
const { isBrainEnabled, readBrainToggles } = await import('../core/brain-registry.mjs');
|
|
126
|
+
const toggles = readBrainToggles();
|
|
127
|
+
const skipped = searchTargets.filter((t) => !isBrainEnabled(t.brainDir, { toggles }));
|
|
128
|
+
if (skipped.length) {
|
|
129
|
+
searchTargets = searchTargets.filter((t) => !skipped.includes(t));
|
|
130
|
+
for (const t of skipped) {
|
|
131
|
+
console.error(` ⏸ ${t.label} brain is switched off (npx total-recall brain on ${t.label === 'global' ? 'global' : t.brainDir})`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
122
135
|
if (searchTargets.length === 0) {
|
|
123
|
-
console.error(
|
|
136
|
+
console.error(skipped.length
|
|
137
|
+
? 'Every brain in scope is switched off. Run `npx total-recall brain list` to see them.'
|
|
138
|
+
: 'No brain found. Run `npx total-recall init` to create one.');
|
|
124
139
|
process.exit(1);
|
|
125
140
|
}
|
|
126
141
|
|
package/src/cli/research.mjs
CHANGED
|
@@ -51,11 +51,9 @@ function getStatusBadge(status) {
|
|
|
51
51
|
|
|
52
52
|
function getPhaseLabel(phase) {
|
|
53
53
|
switch (phase) {
|
|
54
|
-
case 'acquisition': return 'Acquisition (1/
|
|
55
|
-
case 'deliberation': return 'Deliberation (2/
|
|
56
|
-
case 'improvement': return 'Refinement & Clarity (3/
|
|
57
|
-
case 'monitoring': return 'Sources & Feeds (4/5)';
|
|
58
|
-
case 'expansion': return 'Tangent Expansion (5/5)';
|
|
54
|
+
case 'acquisition': return 'Acquisition (1/3)';
|
|
55
|
+
case 'deliberation': return 'Deliberation (2/3)';
|
|
56
|
+
case 'improvement': return 'Refinement & Clarity (3/3)';
|
|
59
57
|
default: return phase || 'unknown';
|
|
60
58
|
}
|
|
61
59
|
}
|
|
@@ -174,7 +172,7 @@ function handleAdd(args, resolvedBrainDir) {
|
|
|
174
172
|
}
|
|
175
173
|
|
|
176
174
|
try {
|
|
177
|
-
const item = addToQueue({ topic, priority, notes, brainDir: resolvedBrainDir });
|
|
175
|
+
const item = addToQueue({ topic, priority, notes, brainDir: resolvedBrainDir, origin: 'user', requested_via: 'cli' });
|
|
178
176
|
console.log();
|
|
179
177
|
console.log(` ${green('✅ Successfully enqueued research project:')}`);
|
|
180
178
|
console.log(` ${bold('Topic:')} "${item.topic}"`);
|
package/src/cli/share.mjs
CHANGED