total-recall-brain 3.40.0 → 3.40.2
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/README.md +152 -41
- package/bin/total-recall.mjs +16 -76
- package/docs/reference/cli-reference.md +10 -10
- package/extension/background.js +6 -2
- package/extension/config.js +8 -0
- package/extension/lib/brain-client.js +29 -4
- package/extension/options/options.html +1 -0
- package/extension/sidepanel/sidepanel.html +1 -0
- package/frontend/dist/assets/{index-CvQRdT2j.css → index-AGUes-VG.css} +1 -1
- package/frontend/dist/assets/index-Bt5iVPZj.js +5618 -0
- package/frontend/dist/index.html +2 -2
- package/frontend/src/api/keys.ts +9 -2
- package/frontend/src/api/plugins.ts +4 -0
- package/frontend/src/components/plugins/card/CardEngine.tsx +2 -1
- package/frontend/src/components/plugins/card/card.css +1 -1
- package/frontend/src/pages/IntegrationsPage.tsx +315 -8
- package/frontend/src/pages/PluginsPage.tsx +532 -87
- package/package.json +6 -5
- package/scaffold/.agent/skills/meta-harness/references/optimized/original.md +1 -1
- package/scaffold/.agent/skills/meta-harness/references/optimized/section-09.md +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +10 -10
- package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +1 -6
- package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +0 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/browser-session.mjs +309 -4
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +44 -34
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/google-oauth.mjs +6 -4
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/provider-catalog.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/provider-rotation-recipes.mjs +118 -4
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/routes/auth.mjs +2 -2
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-env-export.mjs +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-remote-deploy.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-rotate.mjs +24 -6
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/card.js +164 -123
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/README.md +65 -0
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/cli.mjs +76 -0
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/package.json +19 -0
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/plugin.json +290 -0
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/adapters/dsh.mjs +446 -0
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/documentation.mjs +152 -0
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/executor.mjs +71 -0
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/runtime.mjs +36 -0
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/setup.mjs +35 -0
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/worker.mjs +175 -0
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/card.js +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/LICENSE +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/README.md +84 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/cli.mjs +79 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/check.mjs +6 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/report.mjs +6 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/run-core.mjs +27 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/snapshot.mjs +163 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/check.mjs +757 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/config.schema.json +86 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/detect.mjs +89 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report-data.mjs +145 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report.mjs +210 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/state.mjs +20 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +2869 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +28 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/plugin.json +186 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/DESIGN.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/card.js +272 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/code-quality-report.mjs +129 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/LICENSE +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/README.md +73 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/cli.mjs +375 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/examples/commands/echo-tool.mjs +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/examples/commands/group.mjs +262 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/examples/commands/project-id.mjs +74 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +2869 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +31 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +221 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/schemas/cli_command.schema.json +107 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/skills/composable-cli/SKILL.md +63 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/command.mjs +313 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/compile.mjs +19 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/compose.mjs +144 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/contract.mjs +110 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/library.mjs +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/registry.mjs +192 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/resolve.mjs +221 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/risk.mjs +126 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/ssss-documents.mjs +29 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/DESIGN.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/card.js +273 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/LICENSE +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/README.md +20 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/cli.mjs +310 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +2826 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +11 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/plugin.json +62 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/card.js +297 -0
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/LICENSE +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/README.md +43 -0
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/cli.mjs +258 -0
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +4005 -0
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package.json +20 -0
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/plugin.json +125 -0
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/ui/DESIGN.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/ui/card.js +278 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/README.md +9 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/cli.mjs +6 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +4721 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +3 -21
- package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +14 -1
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/mesh.mjs +10 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/headscale-client.mjs +5 -1
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/leader-election.mjs +4 -13
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh-access-verify.mjs +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh-files.mjs +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh.mjs +2 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/test-support/memory-root.mjs +14 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/LICENSE +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/README.md +35 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +124 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +4097 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +12 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +165 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/ingest.mjs +127 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/DESIGN.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +279 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/panel.js +539 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/LICENSE +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/README.md +67 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/cli.mjs +932 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +3256 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/package.json +25 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +297 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/scripts/build-ui.mjs +4 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/skills/security/SKILL.md +37 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/src/android.mjs +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/src/devices.mjs +94 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/src/machines.mjs +164 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/src/scanner.mjs +41 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/src/windows.mjs +42 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/DESIGN.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/card.js +95 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/panel.js +98 -0
- package/scaffold/.agent/skills/total-recall/references/auth.md +1 -1
- package/src/cli/backup.mjs +71 -3
- package/src/cli/brief.mjs +33 -9
- package/src/cli/command.mjs +2 -7
- package/src/cli/config.mjs +1 -0
- package/src/cli/connect.mjs +5 -0
- package/src/cli/context.mjs +2 -1
- package/src/cli/daemon.mjs +36 -1
- package/src/cli/doctor.mjs +6 -0
- package/src/cli/harness.mjs +3 -0
- package/src/cli/help.mjs +26 -0
- package/src/cli/init.mjs +4 -4
- package/src/cli/memory-entry.mjs +1 -0
- package/src/cli/memory-init.mjs +2 -0
- package/src/cli/plugin/index.mjs +11 -0
- package/src/cli/plugin/install.mjs +2 -1
- package/src/cli/rebuild.mjs +3 -1
- package/src/cli/startup.mjs +2 -1
- package/src/cli/update.mjs +19 -0
- package/src/core/automatic-brain-updates.mjs +67 -0
- package/src/core/brain-updates.mjs +21 -2
- package/src/core/cli-commands.mjs +71 -0
- package/src/core/cli-maintenance.mjs +45 -0
- package/src/core/cli-quick-reference.mjs +20 -0
- package/src/core/cli-semantic-index.mjs +722 -0
- package/src/core/context-compiler.mjs +27 -1
- package/src/core/crons.mjs +2 -13
- package/src/core/daemon-control.mjs +6 -4
- package/src/core/daemon-loop.mjs +12 -8
- package/src/core/default-plugins.mjs +5 -0
- package/src/core/global-brain-updates.mjs +39 -0
- package/src/core/mesh-brain-updates.mjs +30 -0
- package/src/core/meta-harness.mjs +108 -0
- package/src/core/plugin-dependencies.mjs +2 -1
- package/src/core/plugin-loader.mjs +2 -1
- package/src/core/plugin-runner.mjs +12 -4
- package/src/core/plugin-setup.mjs +20 -0
- package/src/core/plugin-store.mjs +22 -4
- package/src/core/plugin-tasks.mjs +1 -1
- package/src/core/project-brain.mjs +2 -0
- package/src/core/project-status.mjs +64 -0
- package/src/core/required-plugins.mjs +46 -0
- package/src/core/scaffold-plugins.mjs +17 -3
- package/src/core/source-brain-updates.mjs +47 -0
- package/src/core/startup-health.mjs +5 -3
- package/src/core/surface.mjs +44 -10
- package/src/server/routes/extension.mjs +53 -8
- package/templates/onboarding-interview.md +1 -1
- package/frontend/dist/assets/index-SOVUlZqm.js +0 -5596
package/src/cli/brief.mjs
CHANGED
|
@@ -14,7 +14,10 @@ const argl = Array.isArray(args) ? args : [];
|
|
|
14
14
|
const asJson = argl.includes('--json');
|
|
15
15
|
const withMesh = argl.includes('--mesh');
|
|
16
16
|
if (argl.includes('--help') || argl.includes('-h')) {
|
|
17
|
-
console.log(`Usage: total-recall brief [--json] [--mesh]
|
|
17
|
+
console.log(`Usage: total-recall brief [--json] [--mesh] [--all-projects]
|
|
18
|
+
|
|
19
|
+
--all-projects inventories outstanding trackers and daemon tasks across every
|
|
20
|
+
registered root, with source paths and dates; it does not claim live readiness.
|
|
18
21
|
|
|
19
22
|
Read-only brief for the start of a session in the current repo. Composes:
|
|
20
23
|
project-id, group, command list, secret catalog/rotation-due/shared/tracking-health
|
|
@@ -23,6 +26,27 @@ the compiled rules line, docs/projects trackers, openwiki, and the repo's own
|
|
|
23
26
|
.agent/skills/start/SKILL.md, which you follow next when it exists.`);
|
|
24
27
|
return;
|
|
25
28
|
}
|
|
29
|
+
if(argl.includes('--all-projects')){
|
|
30
|
+
const {outstandingProjectStatus,formatOutstandingProjectStatus}=await import('../core/project-status.mjs');
|
|
31
|
+
const report=outstandingProjectStatus();
|
|
32
|
+
if(argl.includes('--output')){
|
|
33
|
+
const at=argl.indexOf('--output');if(!argl[at+1] || argl[at+1].startsWith('--'))throw new Error('--output requires a file path');
|
|
34
|
+
const output=path.resolve(argl[at+1]);fs.mkdirSync(path.dirname(output),{recursive:true,mode:0o700});
|
|
35
|
+
fs.writeFileSync(output,output.endsWith('.json')?JSON.stringify(report,null,2):formatOutstandingProjectStatus(report),{mode:0o600});
|
|
36
|
+
console.log(JSON.stringify({output,rootCount:report.rootCount,projectCount:report.projectCount,taskCount:report.tasks.length}));return report;
|
|
37
|
+
}
|
|
38
|
+
if(argl.includes('--summary')){
|
|
39
|
+
console.log(JSON.stringify({generatedAt:report.generatedAt,rootCount:report.rootCount,projectCount:report.projectCount,openCheckboxCount:report.openCheckboxCount,evidence:report.evidence,projects:report.projects.filter(row=>row.projects.length).map(row=>({name:row.name,projects:row.projects.map(p=>({id:p.id,state:p.state,status:p.status,open:p.openCount,modifiedAt:p.modifiedAt}))})),tasks:report.tasks.reduce((counts,task)=>{const key=task.slug==='onboarding-interview'?'onboarding-template':`${task.status}:${task.executor}`;counts[key]=(counts[key] || 0)+1;return counts;},{}),missing:report.missing},null,2));return report;
|
|
40
|
+
}
|
|
41
|
+
if(asJson)console.log(JSON.stringify(report,null,2));
|
|
42
|
+
else {
|
|
43
|
+
console.log(`${report.rootCount} roots; ${report.projectCount} outstanding project trackers; ${report.openCheckboxCount} unchecked items; ${report.tasks.length} outstanding daemon tasks.\n${report.evidence}`);
|
|
44
|
+
for(const repo of report.projects.filter(row=>row.projects.length))for(const project of repo.projects)console.log(`${repo.name} / ${project.id}: ${project.state}, ${project.status}, ${project.openCount ?? '?'} open — ${project.tracker || project.folder}`);
|
|
45
|
+
for(const task of report.tasks)console.log(`Task ${task.slug}: ${task.status} — ${task.intent}`);
|
|
46
|
+
for(const row of report.missing)console.log(`Missing root: ${row.root}`);
|
|
47
|
+
}
|
|
48
|
+
return report;
|
|
49
|
+
}
|
|
26
50
|
|
|
27
51
|
// Run a Total Recall verb (async, bounded). Output is parsed for counts only.
|
|
28
52
|
const tr = (verbArgs, ms = 45000) => new Promise((resolve) => {
|
|
@@ -51,12 +75,12 @@ const [pid, groupsRes, cmds, catalog, due, shared, tracking, tasks, secretList,
|
|
|
51
75
|
tr(['project-id', '--json']),
|
|
52
76
|
tr(['group', 'list', '--json']),
|
|
53
77
|
tr(['command', 'list', '--json']),
|
|
54
|
-
tr(['secret', 'catalog']),
|
|
55
|
-
tr(['secret', 'rotation-due']),
|
|
56
|
-
tr(['secret', 'shared']),
|
|
57
|
-
tr(['secret', 'tracking-health']),
|
|
78
|
+
tr(['auth', 'secret', 'catalog']),
|
|
79
|
+
tr(['auth', 'secret', 'rotation-due']),
|
|
80
|
+
tr(['auth', 'secret', 'shared']),
|
|
81
|
+
tr(['auth', 'secret', 'tracking-health']),
|
|
58
82
|
tr(['task', 'list']),
|
|
59
|
-
tr(['secret', 'list']),
|
|
83
|
+
tr(['auth', 'secret', 'list']),
|
|
60
84
|
tr(['mesh', 'status', '--json']),
|
|
61
85
|
withMesh ? tr(['mesh', 'ping', '--json'], 60000) : Promise.resolve(null),
|
|
62
86
|
]);
|
|
@@ -154,9 +178,9 @@ const harnesses = await queryHarnessUsage().catch(() => []);
|
|
|
154
178
|
const warnings = [];
|
|
155
179
|
if (!runtime.ready) warnings.push('Shared runtime readiness failed or unknown: total-recall startup check --json.');
|
|
156
180
|
if (!project?.project_id) warnings.push('No project brain here (total-recall init) — memory, secrets and rules are global-only.');
|
|
157
|
-
if (secrets.rotation_due && secrets.rotation_due !== 0) warnings.push(`Secrets overdue for rotation: ${secrets.rotation_due} (total-recall secret rotation-due).`);
|
|
158
|
-
if (secrets.shared_values && secrets.shared_values !== 0) warnings.push(`Credential values reused across keys/apps: ${secrets.shared_values} group(s) (total-recall secret shared).`);
|
|
159
|
-
if (secrets.untracked && secrets.untracked !== 0) warnings.push(`Secrets without usage/account tracking: ${secrets.untracked} (total-recall secret tracking-health).`);
|
|
181
|
+
if (secrets.rotation_due && secrets.rotation_due !== 0) warnings.push(`Secrets overdue for rotation: ${secrets.rotation_due} (total-recall auth secret rotation-due).`);
|
|
182
|
+
if (secrets.shared_values && secrets.shared_values !== 0) warnings.push(`Credential values reused across keys/apps: ${secrets.shared_values} group(s) (total-recall auth secret shared).`);
|
|
183
|
+
if (secrets.untracked && secrets.untracked !== 0) warnings.push(`Secrets without usage/account tracking: ${secrets.untracked} (total-recall auth secret tracking-health).`);
|
|
160
184
|
if (rules && (now - new Date(rules.compiled)) > 7 * 86400000) warnings.push(`Instruction surfaces last compiled ${rules.compiled.slice(0, 10)} — run total-recall compile.`);
|
|
161
185
|
for (const w of openwiki) if (w.updated && (now - new Date(w.updated)) > 30 * 86400000) warnings.push(`OpenWiki ${w.dir} last updated ${w.updated.slice(0, 10)} — may be stale.`);
|
|
162
186
|
if (meshStatus && meshStatus.configured === false) warnings.push(`Mesh not configured: ${meshStatus.reason || 'unknown'}.`);
|
package/src/cli/command.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
5
5
|
import { resolveBrainLayer } from '../core/config.mjs';
|
|
6
6
|
import { isSafeRelativePath, validatePluginManifest } from '../core/plugin-loader.mjs';
|
|
7
|
+
import { CLI_COMMANDS } from '../core/cli-commands.mjs';
|
|
7
8
|
|
|
8
9
|
const COMMAND_NAME = /^[a-z][a-z0-9-]{0,63}$/;
|
|
9
10
|
|
|
@@ -204,13 +205,7 @@ export async function run(argv = []) {
|
|
|
204
205
|
* dispatcher itself so this list cannot drift.
|
|
205
206
|
*/
|
|
206
207
|
export function builtinCommandNames() {
|
|
207
|
-
|
|
208
|
-
const bin = fs.readFileSync(new URL('../../bin/total-recall.mjs', import.meta.url), 'utf8');
|
|
209
|
-
const block = bin.match(/^const COMMANDS = \{([\s\S]*?)^\};/m)?.[1] || '';
|
|
210
|
-
return new Set([...block.matchAll(/^\s+'?([a-z][a-z0-9-]*)'?\s*:/gm)].map((m) => m[1]));
|
|
211
|
-
} catch {
|
|
212
|
-
return new Set();
|
|
213
|
-
}
|
|
208
|
+
return new Set(Object.keys(CLI_COMMANDS));
|
|
214
209
|
}
|
|
215
210
|
|
|
216
211
|
const RISKS = new Set(['read', 'write', 'money', 'dns-cert', 'production-deploy', 'secret-revoke']);
|
package/src/cli/config.mjs
CHANGED
|
@@ -30,6 +30,7 @@ const KEY_MAP = {
|
|
|
30
30
|
// default when every rule should load for every action instead of being
|
|
31
31
|
// narrowed by applicability.
|
|
32
32
|
'context_budget': { file: 'surface', path: 'context.budget', type: 'number' },
|
|
33
|
+
'surface_bootstrap_budget': { file: 'surface', path: 'budget.bootstrap_total_tokens', type: 'number' },
|
|
33
34
|
};
|
|
34
35
|
|
|
35
36
|
function resolveKeyInfo(key) {
|
package/src/cli/connect.mjs
CHANGED
|
@@ -115,6 +115,11 @@ export const CLIENTS = {
|
|
|
115
115
|
// Grok discovers repository skills in <project>/.agents/skills.
|
|
116
116
|
skillsProjection: { scope: 'project', dir: path.join('.agents', 'skills') }
|
|
117
117
|
},
|
|
118
|
+
muse: {
|
|
119
|
+
label: 'Muse Code',
|
|
120
|
+
mode: 'symlink',
|
|
121
|
+
target: 'AGENTS.md'
|
|
122
|
+
},
|
|
118
123
|
replit: {
|
|
119
124
|
label: 'Replit Agent',
|
|
120
125
|
mode: 'file',
|
package/src/cli/context.mjs
CHANGED
|
@@ -41,8 +41,9 @@ export default async function context(args = []) {
|
|
|
41
41
|
const nodes = own ? mergeGlobalRuleNodes(getNodes(path.join(own, 'memory-vault')), global ? getNodes(path.join(global, 'memory-vault')) : []) :
|
|
42
42
|
getNodes(path.join(global, 'memory-vault')).map(n => ({ ...n, _layer: 'global' }));
|
|
43
43
|
const skillsDir = path.dirname(own || global);
|
|
44
|
+
const derivedDir = own ? path.join(own, 'memory-derived') : (global ? path.join(global, 'memory-derived') : null);
|
|
44
45
|
const result = await compileContext({ query, actions, nodes, includeKnowledge, budget: { total: budget }, projectRoot: process.cwd(),
|
|
45
|
-
contributions: legacyRuleContributions(skillsDir), versionInputs: surfaceInputsHash({ skillsDir }) });
|
|
46
|
+
derivedDir, contributions: legacyRuleContributions(skillsDir), versionInputs: surfaceInputsHash({ skillsDir }) });
|
|
46
47
|
if (debug && format === 'text') throw new Error('--debug requires --format json');
|
|
47
48
|
const response = capsuleResponse(result, { total: budget, debug, format });
|
|
48
49
|
if (!response.ready && !debug) {
|
package/src/cli/daemon.mjs
CHANGED
|
@@ -56,8 +56,10 @@ function printHelp() {
|
|
|
56
56
|
|
|
57
57
|
Commands:
|
|
58
58
|
start Start the background Active Intelligence Daemon
|
|
59
|
+
ensure Enable the managed daemon; --restart reloads current source
|
|
59
60
|
stop Stop the background daemon
|
|
60
61
|
status Show daemon status + recent log entries
|
|
62
|
+
logs Read daemon logs (--lines N, default 50)
|
|
61
63
|
|
|
62
64
|
The daemon runs a durable task loop that:
|
|
63
65
|
• Ingests IDE conversation logs (Claude Code, Codex, Gemini, etc.)
|
|
@@ -207,6 +209,39 @@ export default async function daemon(args) {
|
|
|
207
209
|
const useLaunchd = !useSystemd && hasLaunchd();
|
|
208
210
|
|
|
209
211
|
switch (subcommand) {
|
|
212
|
+
case 'ensure': {
|
|
213
|
+
if(!useSystemd)throw new Error('Managed daemon ensure currently requires systemd on the daemon host');
|
|
214
|
+
// A detached worker can hold the same brain lock while systemd reports a
|
|
215
|
+
// successful short-lived start. Stop that verified owner before takeover.
|
|
216
|
+
const managed=spawnSync('systemctl',['show','total-recall-daemon','--property=MainPID','--value'],{encoding:'utf8'});
|
|
217
|
+
if(managed.status!==0)throw new Error('Cannot inspect managed daemon');
|
|
218
|
+
const owner=readPid(),managedPid=Number(managed.stdout.trim());
|
|
219
|
+
if(owner && (owner!==managedPid || args.includes('--restart'))){
|
|
220
|
+
if(owner===managedPid)execFileSync('systemctl',['stop','total-recall-daemon'],{stdio:'inherit'});
|
|
221
|
+
else if(!await stopDaemon())throw new Error('Cannot stop verified detached daemon');
|
|
222
|
+
}
|
|
223
|
+
execFileSync('systemctl',['enable','--now','total-recall-daemon'],{stdio:'inherit'});
|
|
224
|
+
await new Promise(resolve=>setTimeout(resolve,3000));
|
|
225
|
+
const active=spawnSync('systemctl',['is-active','--quiet','total-recall-daemon']);
|
|
226
|
+
const current=spawnSync('systemctl',['show','total-recall-daemon','--property=MainPID','--value'],{encoding:'utf8'});
|
|
227
|
+
const pid=readPid();
|
|
228
|
+
if(active.status!==0 || !pid || pid!==Number(current.stdout?.trim()))throw new Error('Managed daemon did not retain ownership of the brain lock');
|
|
229
|
+
console.log(JSON.stringify({ok:true,manager:'systemd',pid,enabled:true}));
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
case 'logs': {
|
|
233
|
+
const index = args.indexOf('--lines');
|
|
234
|
+
const lines = index < 0 ? 50 : Number(args[index + 1]);
|
|
235
|
+
if (!Number.isInteger(lines) || lines < 1 || lines > 10000) throw new Error('--lines must be an integer from 1 to 10000');
|
|
236
|
+
if (useSystemd) {
|
|
237
|
+
const result = spawnSync('journalctl', ['-u', 'total-recall-daemon.service', '-n', String(lines), '--no-pager'], { stdio: 'inherit' });
|
|
238
|
+
if (result.error || result.status !== 0) throw new Error('Could not read the daemon journal');
|
|
239
|
+
} else {
|
|
240
|
+
if (!fs.existsSync(LOG_FILE)) throw new Error('No daemon log exists');
|
|
241
|
+
console.log(fs.readFileSync(LOG_FILE, 'utf8').trimEnd().split('\n').slice(-lines).join('\n'));
|
|
242
|
+
}
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
210
245
|
case 'start':
|
|
211
246
|
if (useSystemd) systemdStart();
|
|
212
247
|
else if (useLaunchd) launchctlStart();
|
|
@@ -224,7 +259,7 @@ export default async function daemon(args) {
|
|
|
224
259
|
break;
|
|
225
260
|
default:
|
|
226
261
|
console.error(` Unknown daemon command: ${subcommand}`);
|
|
227
|
-
console.error(` Valid commands: start, stop, status`);
|
|
262
|
+
console.error(` Valid commands: start, ensure, stop, status, logs`);
|
|
228
263
|
process.exit(1);
|
|
229
264
|
}
|
|
230
265
|
}
|
package/src/cli/doctor.mjs
CHANGED
|
@@ -18,6 +18,7 @@ import { fileURLToPath, URL } from 'node:url';
|
|
|
18
18
|
import { getGlobalBrainDir } from './agent-dir.mjs';
|
|
19
19
|
import { processIdentity, verifyListener } from '../core/startup-health.mjs';
|
|
20
20
|
import { loadKnownRepoRoots } from '../core/skills-registry.mjs';
|
|
21
|
+
import { inspectRequiredPlugins } from '../core/required-plugins.mjs';
|
|
21
22
|
|
|
22
23
|
// ─── Formatting & UI Helpers ────────────────────────────────────────────────
|
|
23
24
|
function log(msg) { console.log(` ${msg}`); }
|
|
@@ -91,6 +92,11 @@ export default async function doctor() {
|
|
|
91
92
|
|
|
92
93
|
let warnings = 0;
|
|
93
94
|
let failures = 0;
|
|
95
|
+
const { resolveBrainDir } = await import('./agent-dir.mjs');
|
|
96
|
+
for (const brain of new Set([getGlobalBrainDir(), resolveBrainDir()])) {
|
|
97
|
+
const required = inspectRequiredPlugins(brain);
|
|
98
|
+
if (required.status !== 'ready') { logFail(`${brain}: ${required.errors.join('; ')}`); failures++; }
|
|
99
|
+
}
|
|
94
100
|
|
|
95
101
|
// ── Step 1: System Hardware Check ──
|
|
96
102
|
hr();
|
package/src/cli/harness.mjs
CHANGED
|
@@ -23,6 +23,9 @@ Available Harness IDs:
|
|
|
23
23
|
grok xAI Grok Build CLI (Autonomous coding, inspection, shell execution)
|
|
24
24
|
gemini Google Gemini CLI (Fast utilities & completion chaining)
|
|
25
25
|
ollama Ollama Local LLM (Local neural reasoning, zero API cost)
|
|
26
|
+
hermes Hermes Agent Runtime (Autonomous supervisor & messaging gateway)
|
|
27
|
+
dsh DeepSeek Harness DSH (High-throughput reasoning engine)
|
|
28
|
+
openclaw OpenClaw Agent Bridge (Multi-channel messaging & tool execution)
|
|
26
29
|
|
|
27
30
|
Examples:
|
|
28
31
|
npx total-recall harness list
|
package/src/cli/help.mjs
CHANGED
|
@@ -122,6 +122,32 @@ export default async function run(argv) {
|
|
|
122
122
|
console.log('\n' + '─'.repeat(60) + '\n');
|
|
123
123
|
}
|
|
124
124
|
} else {
|
|
125
|
+
// Semantic CLI search fallback across all core, plugin, and composed verbs
|
|
126
|
+
try {
|
|
127
|
+
const { queryCliSemanticIndex } = await import('../core/cli-semantic-index.mjs');
|
|
128
|
+
const matches = await queryCliSemanticIndex(requestedTopic || topic, { limit: 6, minScore: 0.25 });
|
|
129
|
+
if (matches.length > 0) {
|
|
130
|
+
if (isJson) {
|
|
131
|
+
console.log(JSON.stringify({ topic: requestedTopic || topic, matches }, null, 2));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
console.log(`\n\x1b[34m=== Total Recall Semantic Help: "${requestedTopic || topic}" ===\x1b[0m\n`);
|
|
135
|
+
console.log(' Top matching CLI verbs:\n');
|
|
136
|
+
for (const m of matches) {
|
|
137
|
+
const badge = m.category === 'plugin'
|
|
138
|
+
? `\x1b[35m[plugin:${m.namespace}]\x1b[0m`
|
|
139
|
+
: (m.category === 'composed' ? `\x1b[36m[composed]\x1b[0m` : `\x1b[32m[core]\x1b[0m`);
|
|
140
|
+
console.log(` • \x1b[1m${m.verb}\x1b[0m ${badge}`);
|
|
141
|
+
console.log(` ${m.description}`);
|
|
142
|
+
console.log(` \x1b[2mUsage: ${m.usage}\x1b[0m\n`);
|
|
143
|
+
}
|
|
144
|
+
console.log('─'.repeat(60) + '\n');
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
} catch {
|
|
148
|
+
/* Fallback to default not found message */
|
|
149
|
+
}
|
|
150
|
+
|
|
125
151
|
console.error(` ❌ Error: No help section found for topic or command: "${topic}"`);
|
|
126
152
|
console.error(` Run 'npx total-recall help' to see the complete list of commands.`);
|
|
127
153
|
process.exit(1);
|
package/src/cli/init.mjs
CHANGED
|
@@ -244,10 +244,10 @@ export default async function init(args) {
|
|
|
244
244
|
logOk(`Base directory structure ready (${created} created, ${dirs.length - created} already existed)`);
|
|
245
245
|
|
|
246
246
|
// Auth is a required scaffold plugin. Existing installations are preserved.
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
247
|
+
if (!opts.dryRun) {
|
|
248
|
+
const { ensureRequiredPlugins } = await import('../core/required-plugins.mjs');
|
|
249
|
+
ensureRequiredPlugins(brainDir);
|
|
250
|
+
}
|
|
251
251
|
|
|
252
252
|
// Every scaffold plugin's node_modules are wired in Step 2.5, after the skill
|
|
253
253
|
// tree has been copied into the brain (see the note there).
|
package/src/cli/memory-entry.mjs
CHANGED
|
@@ -26,6 +26,7 @@ const handlers = {
|
|
|
26
26
|
|
|
27
27
|
export async function main(args = []) {
|
|
28
28
|
const [command, ...rest] = args;
|
|
29
|
+
if (command === 'secret' || command === 'secrets') throw new Error('Credential commands belong to total-recall auth secret');
|
|
29
30
|
if (!command || ['help', '--help', '-h'].includes(command)) {
|
|
30
31
|
console.log(`Total Recall — portable memory and instructions
|
|
31
32
|
Usage: total-recall <command> [options]
|
package/src/cli/memory-init.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { getGlobalAgentDir } from './agent-dir.mjs';
|
|
6
6
|
import { registerProjectBrain, writeBrainIdentity, VAULT_CATEGORIES } from '../core/memory-brain.mjs';
|
|
7
|
+
import { ensureRequiredPlugins } from '../core/required-plugins.mjs';
|
|
7
8
|
|
|
8
9
|
export default async function init(args = []) {
|
|
9
10
|
if (args.includes('--help') || args.includes('-h')) {
|
|
@@ -24,6 +25,7 @@ export default async function init(args = []) {
|
|
|
24
25
|
for (const relative of ['config', 'plugins', 'memory-derived', ...VAULT_CATEGORIES.map(c => `memory-vault/${c}`)]) {
|
|
25
26
|
fs.mkdirSync(path.join(brainDir, relative), { recursive: true });
|
|
26
27
|
}
|
|
28
|
+
ensureRequiredPlugins(brainDir);
|
|
27
29
|
const skillFile = path.join(brainDir, 'SKILL.md');
|
|
28
30
|
if (!fs.existsSync(skillFile)) {
|
|
29
31
|
// Resolve the template by PATH, not `new URL(..., import.meta.url)`. Under a
|
package/src/cli/plugin/index.mjs
CHANGED
|
@@ -25,6 +25,7 @@ Usage:
|
|
|
25
25
|
Commands:
|
|
26
26
|
list Installed plugins (project & global) with source and sharing
|
|
27
27
|
library [--plugin ID] Installed CLI operation library (--json for agents)
|
|
28
|
+
refresh Rebuild installed command inventories and instruction surfaces
|
|
28
29
|
on <id> / off <id> Enable/disable host execution; compile catalogs and surfaces
|
|
29
30
|
available [query] Plugins bundled with this Total Recall package
|
|
30
31
|
peers [query] Plugins your mesh peers share (queried live)
|
|
@@ -38,6 +39,7 @@ Commands:
|
|
|
38
39
|
<git-url> a git repository with plugin.json
|
|
39
40
|
<path> a local directory
|
|
40
41
|
--link, -l Symlink a local directory instead of copying
|
|
42
|
+
--replace Replace an install, retain previous source and restore on failure
|
|
41
43
|
--global, -g Install for every project on this machine
|
|
42
44
|
share <id> Offer an installed plugin by direct link and mesh
|
|
43
45
|
unshare <id> Stop offering it
|
|
@@ -83,6 +85,15 @@ export async function run(argv = []) {
|
|
|
83
85
|
|
|
84
86
|
const command = args[0];
|
|
85
87
|
const rest = args.slice(1);
|
|
88
|
+
if (command === 'refresh') {
|
|
89
|
+
const { recompileAfterPluginMutation } = await import('../../core/plugin-store.mjs');
|
|
90
|
+
const result = await recompileAfterPluginMutation(process.cwd(), rest.includes('--global'));
|
|
91
|
+
const { setupInstalledPlugins } = await import('../../core/plugin-setup.mjs');
|
|
92
|
+
const setup = result.ok ? await setupInstalledPlugins() : {ok:false,results:[]};
|
|
93
|
+
console.log(JSON.stringify({...result,setup,ok:result.ok && setup.ok}));
|
|
94
|
+
if (!result.ok || !setup.ok) process.exitCode = 1;
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
86
97
|
if (command === 'on' || command === 'off') {
|
|
87
98
|
const result = await setPluginActive(rest[0], command === 'on');
|
|
88
99
|
console.log(JSON.stringify(result));
|
|
@@ -13,11 +13,12 @@ export async function installPlugin(args = []) {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
try {
|
|
16
|
-
const result = await install(source, { projectRoot: process.cwd(), global: isGlobal, link: isLink });
|
|
16
|
+
const result = await install(source, { projectRoot: process.cwd(), global: isGlobal, link: isLink, replace:args.includes('--replace') });
|
|
17
17
|
const from = result.source.kind === 'peer' ? `peer ${result.source.peer_hostname}` : result.source.kind;
|
|
18
18
|
console.log(`\n✅ Installed '${result.plugin.name}' (id: ${result.plugin.id}, v${result.plugin.version}) from ${from}`);
|
|
19
19
|
console.log(` Location: ${result.plugin.dir}`);
|
|
20
20
|
console.log(` sha256: ${result.sha256}`);
|
|
21
|
+
if(result.previousDirectory)console.log(` Previous source retained: ${result.previousDirectory}`);
|
|
21
22
|
if (result.dependencies?.installed) {
|
|
22
23
|
console.log(` Dependencies: installed ${result.dependencies.packages.length} package(s)`);
|
|
23
24
|
} else if (result.dependencies?.reason && !['no package.json', 'no dependencies declared'].includes(result.dependencies.reason)) {
|
package/src/cli/rebuild.mjs
CHANGED
|
@@ -72,13 +72,14 @@ export async function runRebuild(options = {}) {
|
|
|
72
72
|
if (fs.existsSync(derivedDir)) {
|
|
73
73
|
for (const entry of fs.readdirSync(derivedDir)) {
|
|
74
74
|
if (entry.startsWith('embeddings.db')) continue; // also skips -wal / -shm
|
|
75
|
+
if (entry === 'cli-semantic-index.json') continue;
|
|
75
76
|
fs.rmSync(path.join(derivedDir, entry), { recursive: true, force: true });
|
|
76
77
|
}
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
console.log('🏗️ Recompiling surface from canonical vault...');
|
|
80
81
|
try {
|
|
81
|
-
const stats = await compileSurface({ vaultDir, skillsDir, derivedDir, instructionsFile, force:
|
|
82
|
+
const stats = await compileSurface({ vaultDir, skillsDir, derivedDir, instructionsFile, force: Boolean(options.force) });
|
|
82
83
|
console.log(`✅ Processed ${stats.nodesProcessed} canonical memory nodes.`);
|
|
83
84
|
console.log(`✅ Injected memory into ${stats.skillsInjected} skill files.`);
|
|
84
85
|
console.log(`✅ Rebuilt graph-index.jsonl, memory-layers.jsonl, and skill-routes.jsonl.`);
|
|
@@ -163,6 +164,7 @@ export default async function cli(args) {
|
|
|
163
164
|
const { layer, remainingArgs } = parseLayerFlag(args);
|
|
164
165
|
const options = {
|
|
165
166
|
check: remainingArgs.includes('--check'),
|
|
167
|
+
force: remainingArgs.includes('--force'),
|
|
166
168
|
layer,
|
|
167
169
|
};
|
|
168
170
|
let code = await runRebuild(options);
|
package/src/cli/startup.mjs
CHANGED
|
@@ -35,7 +35,8 @@ export default async function startup(args = []) {
|
|
|
35
35
|
const report = await inspectStartup(options);
|
|
36
36
|
if (args.includes('--json')) console.log(JSON.stringify(report, null, 2));
|
|
37
37
|
else {
|
|
38
|
-
console.log(`Startup: server ${report.server?.status || 'unknown'}; brain ${report.brain?.status || 'unknown'}; daemon ${report.daemon?.status || 'unknown'}; SSSS tooling ${report.ssss?.status || 'unknown'}; app ${report.app?.status || 'unknown'}`);
|
|
38
|
+
console.log(`Startup: required plugins ${report.required_plugins?.status || 'unknown'}; server ${report.server?.status || 'unknown'}; brain ${report.brain?.status || 'unknown'}; daemon ${report.daemon?.status || 'unknown'}; SSSS tooling ${report.ssss?.status || 'unknown'}; app ${report.app?.status || 'unknown'}`);
|
|
39
|
+
for (const error of report.required_plugins?.errors || []) console.log(`Required plugin: ${error}`);
|
|
39
40
|
const harnesses = await queryHarnessUsage().catch(() => []);
|
|
40
41
|
const availH = harnesses.filter(h => h.available);
|
|
41
42
|
const readyH = availH.filter(h => h.authed);
|
package/src/cli/update.mjs
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
import { brainDir } from '../core/config.mjs';
|
|
19
19
|
import { installAutoPull } from '../core/autopull-install.mjs';
|
|
20
20
|
import { updateAllBrains } from '../core/brain-updates.mjs';
|
|
21
|
+
import { automaticUpdateStatus, runAutomaticBrainUpdates } from '../core/automatic-brain-updates.mjs';
|
|
21
22
|
|
|
22
23
|
/** @param {string[]} args */
|
|
23
24
|
export function parseArgs(args) {
|
|
@@ -32,6 +33,9 @@ export function parseArgs(args) {
|
|
|
32
33
|
noBuild: false,
|
|
33
34
|
allBrains: false,
|
|
34
35
|
json: false,
|
|
36
|
+
automatic: false,
|
|
37
|
+
status: false,
|
|
38
|
+
localOnly: false,
|
|
35
39
|
};
|
|
36
40
|
for (let i = 0; i < args.length; i++) {
|
|
37
41
|
const a = args[i];
|
|
@@ -45,6 +49,9 @@ export function parseArgs(args) {
|
|
|
45
49
|
else if (a === '--no-build') opts.noBuild = true;
|
|
46
50
|
else if (a === '--all-brains') opts.allBrains = true;
|
|
47
51
|
else if (a === '--json') opts.json = true;
|
|
52
|
+
else if (a === '--automatic') opts.automatic = true;
|
|
53
|
+
else if (a === '--status') opts.status = true;
|
|
54
|
+
else if (a === '--local-only') opts.localOnly = true;
|
|
48
55
|
}
|
|
49
56
|
return opts;
|
|
50
57
|
}
|
|
@@ -64,6 +71,10 @@ function printHelp() {
|
|
|
64
71
|
total-recall update --all-brains --apply [--json]
|
|
65
72
|
Pin the same published versions, use npm/pnpm, replace shared dependency
|
|
66
73
|
links with independent local installs, and verify every resolved version.
|
|
74
|
+
total-recall update --automatic [--json]
|
|
75
|
+
Daemon maintenance: update both packages, ensure defaults and refresh surfaces.
|
|
76
|
+
total-recall update --status [--json]
|
|
77
|
+
Read the actual last automatic maintenance report without starting work.
|
|
67
78
|
|
|
68
79
|
This brain's own checkout (git):
|
|
69
80
|
total-recall update --install-autopull [--port N] [--no-build] [--dry-run]
|
|
@@ -89,6 +100,14 @@ export default async function update(args = []) {
|
|
|
89
100
|
return;
|
|
90
101
|
}
|
|
91
102
|
|
|
103
|
+
if(opts.status){const status=automaticUpdateStatus(brainDir);console.log(JSON.stringify(status,null,2));return status;}
|
|
104
|
+
if(opts.automatic){
|
|
105
|
+
const report=await runAutomaticBrainUpdates({brainDir,force:opts.force,fleet:!opts.localOnly});
|
|
106
|
+
console.log(JSON.stringify(report,null,2));
|
|
107
|
+
if(report.failed)process.exitCode=1;
|
|
108
|
+
return report;
|
|
109
|
+
}
|
|
110
|
+
|
|
92
111
|
if (opts.allBrains) {
|
|
93
112
|
const summary = await updateAllBrains({ brainDir, roots: opts.repos,
|
|
94
113
|
dryRun: !opts.apply || opts.dryRun });
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/** The existing TR daemon maintains both published brain packages, not only TR. */
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { updateAllBrains } from './brain-updates.mjs';
|
|
5
|
+
import { isPackageAutoUpdateEnabled } from './package-auto-update.mjs';
|
|
6
|
+
import { maintainCliSurfaces } from './cli-maintenance.mjs';
|
|
7
|
+
import { runBounded, cliEntry } from './startup-health.mjs';
|
|
8
|
+
import { logger } from './logger.mjs';
|
|
9
|
+
import { readProcessCommand, shouldHonorPidLock } from './pid-lock.mjs';
|
|
10
|
+
import { updateGlobalBrainPackages } from './global-brain-updates.mjs';
|
|
11
|
+
import { updateMeshBrainPackages } from './mesh-brain-updates.mjs';
|
|
12
|
+
import { updateSourceBrainPackages } from './source-brain-updates.mjs';
|
|
13
|
+
|
|
14
|
+
export function automaticUpdateStatus(brainDir) {
|
|
15
|
+
let last;
|
|
16
|
+
try { last=JSON.parse(fs.readFileSync(path.join(brainDir,'memory-derived/automatic-brain-updates.json'),'utf8')); } catch {}
|
|
17
|
+
return {enabled:isPackageAutoUpdateEnabled(), intervalMs:60*60*1000, last:last || null};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Keep synchronous package-manager work outside the daemon's event loop. This
|
|
21
|
+
// timer runs even when the task queue is empty; it does not create a scheduler.
|
|
22
|
+
export function startAutomaticBrainUpdates({brainDir,intervalMs=60*60*1000,run=runBounded,onError=message=>logger.error('automatic-brain-updates',message)}={}) {
|
|
23
|
+
let busy=false,stopped=false;
|
|
24
|
+
const tick=async()=>{
|
|
25
|
+
if(busy || stopped)return;
|
|
26
|
+
busy=true;
|
|
27
|
+
try {
|
|
28
|
+
const result=await run(process.execPath,[cliEntry,'update','--automatic','--json'],{cwd:path.resolve(brainDir,'../../..'),timeoutMs:55*60*1000,maxBytes:8*1024*1024});
|
|
29
|
+
if(result.code!==0)onError(result.reason || result.output || 'Automatic update failed');
|
|
30
|
+
} catch(error){onError(error.message);} finally{busy=false;}
|
|
31
|
+
};
|
|
32
|
+
// Wake frequently enough to retry failures; successful checks remain hourly.
|
|
33
|
+
const timer=setInterval(tick,Math.min(intervalMs,5*60*1000));timer.unref?.();void tick();
|
|
34
|
+
return {tick,stop(){stopped=true;clearInterval(timer);}};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function runAutomaticBrainUpdates({brainDir,fleet=false,force=false,skipThrottle=false,intervalMs=60*60*1000,update=updateAllBrains,compile=maintainCliSurfaces,globalUpdate=updateGlobalBrainPackages,meshUpdate=updateMeshBrainPackages,sourceUpdate=updateSourceBrainPackages,now=Date.now}={}) {
|
|
38
|
+
if(!force && !isPackageAutoUpdateEnabled())return {skipped:true,reason:'disabled',results:[]};
|
|
39
|
+
const file=path.join(brainDir,'memory-derived/automatic-brain-updates.json');
|
|
40
|
+
let saved;try{saved=JSON.parse(fs.readFileSync(file,'utf8'));}catch{}
|
|
41
|
+
const delay=saved?.failed?Math.min(intervalMs,5*60*1000):intervalMs;
|
|
42
|
+
if(!force && !skipThrottle && saved?.checkedAt && now()>=saved.checkedAt && now()-saved.checkedAt<delay)return {skipped:true,reason:'throttled',results:[]};
|
|
43
|
+
fs.mkdirSync(path.dirname(file),{recursive:true});
|
|
44
|
+
const lock=`${file}.lock`;
|
|
45
|
+
let fd;
|
|
46
|
+
if(fs.existsSync(lock)){
|
|
47
|
+
const text=fs.readFileSync(lock,'utf8');
|
|
48
|
+
const verdict=Number(text)===process.pid ? {honor:true} : shouldHonorPidLock(Number(text),{isAlive:pid=>{try{process.kill(pid,0);return true;}catch{return false;}},readCommand:readProcessCommand,entryHint:'total-recall'});
|
|
49
|
+
if(!verdict.honor && fs.readFileSync(lock,'utf8')===text)fs.unlinkSync(lock);
|
|
50
|
+
}
|
|
51
|
+
try {fd=fs.openSync(lock,'wx',0o600);fs.writeFileSync(fd,String(process.pid));}
|
|
52
|
+
catch(error){if(error.code==='EEXIST')return {skipped:true,reason:'maintenance-already-running',failed:1,results:[]};throw error;}
|
|
53
|
+
try {
|
|
54
|
+
let report;
|
|
55
|
+
try {
|
|
56
|
+
const summary=await update({brainDir,dryRun:false,ensureDefaults:true});
|
|
57
|
+
const global=summary.versions?await globalUpdate({versions:summary.versions}):{failed:1,results:[],error:'Cannot maintain global packages without verified published versions'};
|
|
58
|
+
const sources=summary.versions?await sourceUpdate({brainDir,versions:summary.versions}):{failed:1,results:[]};
|
|
59
|
+
const mesh=fleet && summary.versions?await meshUpdate({versions:summary.versions,version:summary.versions['total-recall-brain']}):{failed:0,results:[],scope:'local-only'};
|
|
60
|
+
const changed=summary.updated>0 || global.results.some(row=>row.status==='updated') || sources.results.some(row=>row.status==='updated') || JSON.stringify(saved?.versions)!==JSON.stringify(summary.versions);
|
|
61
|
+
const surfaces=await compile({brainDir,force:changed});
|
|
62
|
+
report={...summary,global,sources,mesh,surfaces,failed:(summary.failed || 0)+global.failed+sources.failed+mesh.failed+(surfaces.ok?0:surfaces.results.filter(row=>row.status==='failed').length),checkedAt:now()};
|
|
63
|
+
}catch(error){report={failed:1,error:error.message,checkedAt:now(),results:[]};}
|
|
64
|
+
const temporary=`${file}.${process.pid}.tmp`;fs.writeFileSync(temporary,JSON.stringify(report,null,2));fs.renameSync(temporary,file);
|
|
65
|
+
return report;
|
|
66
|
+
} finally {fs.closeSync(fd);fs.unlinkSync(lock);}
|
|
67
|
+
}
|
|
@@ -8,11 +8,14 @@ import { createHash } from 'node:crypto';
|
|
|
8
8
|
import { listUpdateRoots, resolveRegistryFiles, readRegistryEntries, rootsFromEnv } from './package-auto-update.mjs';
|
|
9
9
|
import { discoverPlugins } from './plugin-loader.mjs';
|
|
10
10
|
import { comparePackageVersions } from './package-version.mjs';
|
|
11
|
+
import { inspectRequiredPlugins } from './required-plugins.mjs';
|
|
12
|
+
import { ensureRequiredPlugins } from './required-plugins.mjs';
|
|
13
|
+
import YAML from 'yaml';
|
|
11
14
|
|
|
12
15
|
export const BRAIN_PACKAGES = ['total-recall-brain', '@gregiteen/ssss-cli'];
|
|
13
16
|
const sections = ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies'];
|
|
14
17
|
const ignored = new Set(['node_modules', 'vendor', 'dist', 'build', 'coverage', 'venv',
|
|
15
|
-
'test', 'tests', '__tests__', 'fixtures', 'tmp', 'scratch']);
|
|
18
|
+
'test', 'tests', '__tests__', 'fixtures', 'tmp', 'scratch', 'scaffold']);
|
|
16
19
|
const canonical = key => key === '@ssss/cli' ? '@gregiteen/ssss-cli' : key;
|
|
17
20
|
const read = file => JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
18
21
|
const isLink = file => { try { return fs.lstatSync(file).isSymbolicLink(); } catch { return false; } };
|
|
@@ -68,7 +71,12 @@ export function brainInstallOwner(root) {
|
|
|
68
71
|
const file = path.join(dir, 'package.json');
|
|
69
72
|
if (fs.existsSync(file)) {
|
|
70
73
|
const pkg = read(file);
|
|
71
|
-
|
|
74
|
+
let patterns=Array.isArray(pkg.workspaces)?pkg.workspaces:pkg.workspaces?.packages || [];
|
|
75
|
+
const pnpm=path.join(dir,'pnpm-workspace.yaml');
|
|
76
|
+
if(fs.existsSync(pnpm))patterns=[...patterns,...(YAML.parse(fs.readFileSync(pnpm,'utf8'))?.packages || [])];
|
|
77
|
+
const relative=path.relative(dir,root).split(path.sep).join('/');
|
|
78
|
+
const belongs=patterns.some(pattern=>typeof pattern==='string' && !pattern.startsWith('!') && new RegExp('^'+pattern.split('/').map(part=>part==='**'?'.*':part.split('*').map(chunk=>chunk.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')).join('[^/]*')).join('/')+'$').test(relative));
|
|
79
|
+
if (dir === root || belongs) owner = dir;
|
|
72
80
|
}
|
|
73
81
|
if (dir === path.dirname(dir)) break;
|
|
74
82
|
}
|
|
@@ -90,6 +98,17 @@ export async function updateAllBrains(opts = {}) {
|
|
|
90
98
|
const exec = opts.exec || spawnSync;
|
|
91
99
|
const roots = listUpdateRoots(opts).map(row => row.root);
|
|
92
100
|
const errors = [];
|
|
101
|
+
const brains = roots.map(root => ({ root, brain: path.join(root, '.agent', 'skills', 'total-recall') }))
|
|
102
|
+
.filter(({ brain }) => fs.existsSync(brain));
|
|
103
|
+
if (opts.brainDir) brains.push({ root: opts.brainDir, brain: opts.brainDir });
|
|
104
|
+
for (const { root, brain } of brains) {
|
|
105
|
+
if (!dryRun && opts.ensureDefaults === true) {
|
|
106
|
+
try { ensureRequiredPlugins(brain); }
|
|
107
|
+
catch(error){errors.push({root,status:'failed',error:error.message});continue;}
|
|
108
|
+
}
|
|
109
|
+
const required = inspectRequiredPlugins(brain);
|
|
110
|
+
if (required.status !== 'ready') errors.push({ root, status: 'failed', error: required.errors.join('; ') });
|
|
111
|
+
}
|
|
93
112
|
const requested = [...(opts.roots || []), ...rootsFromEnv(),
|
|
94
113
|
...resolveRegistryFiles(opts).flatMap(file => readRegistryEntries(file).map(row => row.path)).filter(Boolean)];
|
|
95
114
|
for (const root of new Set(requested.map(root => path.resolve(root)))) {
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/** Canonical built-in CLI routing inventory. */
|
|
2
|
+
export const CLI_COMMANDS = {
|
|
3
|
+
app: 'app/index.mjs',
|
|
4
|
+
init: 'init.mjs',
|
|
5
|
+
setup: 'setup.mjs',
|
|
6
|
+
start: 'start.mjs',
|
|
7
|
+
startup: 'startup.mjs',
|
|
8
|
+
deploy: 'deploy.mjs',
|
|
9
|
+
doctor: 'doctor.mjs',
|
|
10
|
+
backup: 'backup.mjs',
|
|
11
|
+
dream: 'dream.mjs',
|
|
12
|
+
lint: 'lint.mjs',
|
|
13
|
+
daemon: 'daemon.mjs',
|
|
14
|
+
restore: 'restore.mjs',
|
|
15
|
+
upgrade: 'upgrade.mjs',
|
|
16
|
+
update: 'update.mjs',
|
|
17
|
+
friction: 'friction.mjs',
|
|
18
|
+
chat: 'chat.mjs',
|
|
19
|
+
status: 'status.mjs',
|
|
20
|
+
remember: 'remember.mjs',
|
|
21
|
+
forget: 'forget.mjs',
|
|
22
|
+
edit: 'edit.mjs',
|
|
23
|
+
recall: 'recall.mjs',
|
|
24
|
+
context: 'context.mjs',
|
|
25
|
+
rules: 'rules.mjs',
|
|
26
|
+
search: 'recall.mjs',
|
|
27
|
+
research: 'research.mjs',
|
|
28
|
+
proposals: 'proposals.mjs',
|
|
29
|
+
proposal: 'proposals.mjs',
|
|
30
|
+
task: 'task.mjs',
|
|
31
|
+
share: 'share.mjs',
|
|
32
|
+
'generate-pat': 'generate-pat.mjs',
|
|
33
|
+
key: 'key.mjs',
|
|
34
|
+
keys: 'key.mjs',
|
|
35
|
+
pat: 'key.mjs',
|
|
36
|
+
'hash-password': 'hash-password.mjs',
|
|
37
|
+
'reset-password': 'reset-password.mjs',
|
|
38
|
+
compile: 'rebuild.mjs',
|
|
39
|
+
rebuild: 'rebuild.mjs',
|
|
40
|
+
snapshot: 'snapshot.mjs',
|
|
41
|
+
migrate: 'migrate.mjs',
|
|
42
|
+
backfill: 'backfill.mjs',
|
|
43
|
+
ingest: 'ingest.mjs',
|
|
44
|
+
import: 'import-rules.mjs',
|
|
45
|
+
connect: 'connect.mjs',
|
|
46
|
+
sync: 'sync.mjs',
|
|
47
|
+
relay: 'relay.mjs',
|
|
48
|
+
uninstall: 'uninstall.mjs',
|
|
49
|
+
map: 'map.mjs',
|
|
50
|
+
brain: 'brain.mjs',
|
|
51
|
+
config: 'config.mjs',
|
|
52
|
+
skill: 'skill.mjs',
|
|
53
|
+
'project-id': 'project-id.mjs',
|
|
54
|
+
api: 'api.mjs',
|
|
55
|
+
brief: 'brief.mjs',
|
|
56
|
+
group: 'group.mjs',
|
|
57
|
+
groups: 'group.mjs',
|
|
58
|
+
help: 'help.mjs',
|
|
59
|
+
collab: 'collab.mjs',
|
|
60
|
+
command: 'command.mjs',
|
|
61
|
+
export: 'export.mjs',
|
|
62
|
+
harness: 'harness.mjs',
|
|
63
|
+
watch: 'rebuild.mjs',
|
|
64
|
+
agent: 'agent.mjs',
|
|
65
|
+
agents: 'agent.mjs',
|
|
66
|
+
new: 'new.mjs',
|
|
67
|
+
plugin: 'plugin/index.mjs',
|
|
68
|
+
plugins: 'plugin/index.mjs',
|
|
69
|
+
scaffold: 'plugin/scaffold-cmd.mjs',
|
|
70
|
+
'scaffold-plugin': 'plugin/scaffold-cmd.mjs',
|
|
71
|
+
};
|