brainclaw 0.19.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/LICENSE +74 -0
- package/README.md +226 -0
- package/dist/cli.js +1037 -0
- package/dist/commands/accept.js +149 -0
- package/dist/commands/adapter-openclaw-import.js +75 -0
- package/dist/commands/add-step.js +35 -0
- package/dist/commands/agent-board.js +106 -0
- package/dist/commands/audit.js +35 -0
- package/dist/commands/bootstrap.js +34 -0
- package/dist/commands/capability.js +104 -0
- package/dist/commands/changes.js +112 -0
- package/dist/commands/check-constraints.js +63 -0
- package/dist/commands/claim-resource.js +54 -0
- package/dist/commands/claim.js +92 -0
- package/dist/commands/complete-step.js +34 -0
- package/dist/commands/constraint.js +44 -0
- package/dist/commands/context-diff.js +32 -0
- package/dist/commands/context.js +63 -0
- package/dist/commands/decision.js +45 -0
- package/dist/commands/delete-plan.js +20 -0
- package/dist/commands/diff.js +99 -0
- package/dist/commands/doctor.js +1275 -0
- package/dist/commands/enable-agent.js +63 -0
- package/dist/commands/env.js +46 -0
- package/dist/commands/estimation-report.js +167 -0
- package/dist/commands/explore.js +47 -0
- package/dist/commands/export.js +381 -0
- package/dist/commands/handoff.js +63 -0
- package/dist/commands/history.js +22 -0
- package/dist/commands/hooks.js +123 -0
- package/dist/commands/init.js +356 -0
- package/dist/commands/install-hooks.js +115 -0
- package/dist/commands/instruction.js +56 -0
- package/dist/commands/list-agents.js +44 -0
- package/dist/commands/list-claims.js +45 -0
- package/dist/commands/list-instructions.js +50 -0
- package/dist/commands/list-plans.js +48 -0
- package/dist/commands/mcp-worker.js +12 -0
- package/dist/commands/mcp.js +2272 -0
- package/dist/commands/memory.js +283 -0
- package/dist/commands/metrics.js +175 -0
- package/dist/commands/plan-resource.js +62 -0
- package/dist/commands/plan.js +76 -0
- package/dist/commands/prune-candidates.js +36 -0
- package/dist/commands/prune.js +48 -0
- package/dist/commands/pull.js +25 -0
- package/dist/commands/push.js +28 -0
- package/dist/commands/rebuild.js +14 -0
- package/dist/commands/reflect-runtime-note.js +74 -0
- package/dist/commands/reflect.js +286 -0
- package/dist/commands/register-agent.js +29 -0
- package/dist/commands/reject.js +52 -0
- package/dist/commands/release-claim.js +41 -0
- package/dist/commands/release-claims.js +67 -0
- package/dist/commands/review.js +242 -0
- package/dist/commands/rollback.js +156 -0
- package/dist/commands/runtime-note.js +144 -0
- package/dist/commands/runtime-status.js +49 -0
- package/dist/commands/search.js +36 -0
- package/dist/commands/session-end.js +187 -0
- package/dist/commands/session-start.js +147 -0
- package/dist/commands/set-trust.js +92 -0
- package/dist/commands/setup.js +446 -0
- package/dist/commands/show-candidate.js +31 -0
- package/dist/commands/star-candidate.js +28 -0
- package/dist/commands/status.js +133 -0
- package/dist/commands/sync.js +159 -0
- package/dist/commands/tool.js +126 -0
- package/dist/commands/trap.js +74 -0
- package/dist/commands/update-handoff.js +23 -0
- package/dist/commands/update-plan.js +37 -0
- package/dist/commands/upgrade.js +382 -0
- package/dist/commands/use-candidate.js +35 -0
- package/dist/commands/version.js +96 -0
- package/dist/commands/watch.js +215 -0
- package/dist/commands/whoami.js +104 -0
- package/dist/core/agent-context.js +340 -0
- package/dist/core/agent-files.js +874 -0
- package/dist/core/agent-integrations.js +135 -0
- package/dist/core/agent-inventory.js +401 -0
- package/dist/core/agent-registry.js +420 -0
- package/dist/core/ai-agent-detection.js +140 -0
- package/dist/core/audit.js +85 -0
- package/dist/core/bootstrap.js +658 -0
- package/dist/core/brainclaw-version.js +433 -0
- package/dist/core/candidates.js +137 -0
- package/dist/core/circuit-breaker.js +118 -0
- package/dist/core/claims.js +72 -0
- package/dist/core/config.js +86 -0
- package/dist/core/context-diff.js +122 -0
- package/dist/core/context.js +1212 -0
- package/dist/core/contradictions.js +270 -0
- package/dist/core/coordination.js +86 -0
- package/dist/core/cross-project.js +99 -0
- package/dist/core/duplicates.js +72 -0
- package/dist/core/event-log.js +152 -0
- package/dist/core/events.js +56 -0
- package/dist/core/execution-context.js +204 -0
- package/dist/core/freshness.js +87 -0
- package/dist/core/global-registry.js +182 -0
- package/dist/core/host.js +10 -0
- package/dist/core/identity.js +151 -0
- package/dist/core/ids.js +56 -0
- package/dist/core/input-validation.js +81 -0
- package/dist/core/instructions.js +117 -0
- package/dist/core/io.js +191 -0
- package/dist/core/json-store.js +63 -0
- package/dist/core/lifecycle.js +45 -0
- package/dist/core/lock.js +129 -0
- package/dist/core/logger.js +49 -0
- package/dist/core/machine-profile.js +332 -0
- package/dist/core/markdown.js +120 -0
- package/dist/core/memory-git.js +133 -0
- package/dist/core/migration.js +247 -0
- package/dist/core/project-registry.js +64 -0
- package/dist/core/reflection-safety.js +21 -0
- package/dist/core/repo-analysis.js +133 -0
- package/dist/core/reputation.js +409 -0
- package/dist/core/runtime.js +134 -0
- package/dist/core/schema.js +580 -0
- package/dist/core/search.js +115 -0
- package/dist/core/security.js +66 -0
- package/dist/core/setup-state.js +50 -0
- package/dist/core/state.js +83 -0
- package/dist/core/store-resolution.js +119 -0
- package/dist/core/sync-remote.js +83 -0
- package/dist/core/traps.js +86 -0
- package/package.json +60 -0
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import readline from 'node:readline/promises';
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
import { runInit } from './init.js';
|
|
6
|
+
import { detectAiAgent } from '../core/ai-agent-detection.js';
|
|
7
|
+
import { buildMachineProfile, saveMachineProfile, loadMachineProfile } from '../core/machine-profile.js';
|
|
8
|
+
import { buildAgentInventory, saveAgentInventory, loadAgentInventory } from '../core/agent-inventory.js';
|
|
9
|
+
import { ensureClaudeCodeUserSettings, ensureClaudeCodeUserCommand, ensureCursorMcpConfig, ensureWindsurfMcpConfig, ensureAntigravityMcpConfig, ensureContinueUserMcpConfig, ensureCodexMcpConfig, writeDetectedAgentAutoConfig, describeAutoConfigWrite, ensureGitignoreEntries, collectWorkspaceGitignoreEntries, } from '../core/agent-files.js';
|
|
10
|
+
import { MEMORY_DIR, memoryExists, ensureMemoryDir } from '../core/io.js';
|
|
11
|
+
import { saveConfig, defaultConfig } from '../core/config.js';
|
|
12
|
+
import { readSetupState, resolveHomeDir, writeSetupState } from '../core/setup-state.js';
|
|
13
|
+
import { writeDetectedAgentHooks } from './hooks.js';
|
|
14
|
+
export { readSetupState } from '../core/setup-state.js';
|
|
15
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
16
|
+
export const ALL_KNOWN_AGENTS = [
|
|
17
|
+
'claude-code',
|
|
18
|
+
'cursor',
|
|
19
|
+
'windsurf',
|
|
20
|
+
'github-copilot',
|
|
21
|
+
'cline',
|
|
22
|
+
'codex',
|
|
23
|
+
'opencode',
|
|
24
|
+
'antigravity',
|
|
25
|
+
'continue',
|
|
26
|
+
'roo',
|
|
27
|
+
];
|
|
28
|
+
const BRAINCLAW_ASCII = [
|
|
29
|
+
'',
|
|
30
|
+
' ╔╗ ┬─┐┌─┐┬┌┐┌╔═╗╦ ╔═╗╦ ╦',
|
|
31
|
+
' ╠╩╗├┬┘├─┤││││║ ║ ╠═╣║║║',
|
|
32
|
+
' ╚═╝┴└─┴ ┴┴┘└┘╚═╝╩═╝╴ ╴╚╩╝',
|
|
33
|
+
'',
|
|
34
|
+
].join('\n');
|
|
35
|
+
// ─── Step 0: Git check ────────────────────────────────────────────────────────
|
|
36
|
+
export function checkGitPresence() {
|
|
37
|
+
const result = spawnSync('git', ['--version'], { encoding: 'utf-8' });
|
|
38
|
+
return !result.error && result.status === 0;
|
|
39
|
+
}
|
|
40
|
+
// ─── Step 1: Roots ────────────────────────────────────────────────────────────
|
|
41
|
+
function getDefaultRoots(env = process.env) {
|
|
42
|
+
const home = resolveHomeDir(env) ?? '';
|
|
43
|
+
const candidates = ['Projects', 'dev', 'code', 'repos'].map((d) => path.join(home, d));
|
|
44
|
+
const existing = candidates.filter((p) => {
|
|
45
|
+
try {
|
|
46
|
+
return fs.statSync(p).isDirectory();
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
return existing.length > 0 ? existing.join(',') : '';
|
|
53
|
+
}
|
|
54
|
+
export function parseRoots(input, env = process.env) {
|
|
55
|
+
const home = resolveHomeDir(env) ?? '';
|
|
56
|
+
const results = [];
|
|
57
|
+
for (const raw of input.split(',')) {
|
|
58
|
+
const trimmed = raw.trim();
|
|
59
|
+
if (!trimmed)
|
|
60
|
+
continue;
|
|
61
|
+
const resolved = trimmed.startsWith('~') ? trimmed.replace(/^~/, home) : trimmed;
|
|
62
|
+
try {
|
|
63
|
+
if (fs.statSync(resolved).isDirectory()) {
|
|
64
|
+
results.push(resolved);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
console.warn(` ⚠ Not a directory: ${resolved}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
console.warn(` ⚠ Path not found: ${resolved}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return results;
|
|
75
|
+
}
|
|
76
|
+
// ─── Step 2: Scan repos ───────────────────────────────────────────────────────
|
|
77
|
+
export function scanGitRepos(roots) {
|
|
78
|
+
const repos = [];
|
|
79
|
+
const seen = new Set();
|
|
80
|
+
for (const root of roots) {
|
|
81
|
+
const candidates = [root];
|
|
82
|
+
try {
|
|
83
|
+
const entries = fs.readdirSync(root, { withFileTypes: true });
|
|
84
|
+
for (const entry of entries) {
|
|
85
|
+
if (!entry.isDirectory())
|
|
86
|
+
continue;
|
|
87
|
+
candidates.push(path.join(root, entry.name));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// skip unreadable dirs
|
|
92
|
+
}
|
|
93
|
+
for (const candidate of candidates) {
|
|
94
|
+
const repoPath = path.resolve(candidate);
|
|
95
|
+
if (seen.has(repoPath))
|
|
96
|
+
continue;
|
|
97
|
+
if (isBrainclawInternalPath(repoPath))
|
|
98
|
+
continue;
|
|
99
|
+
if (!fs.existsSync(path.join(repoPath, '.git')))
|
|
100
|
+
continue;
|
|
101
|
+
seen.add(repoPath);
|
|
102
|
+
repos.push({
|
|
103
|
+
path: repoPath,
|
|
104
|
+
name: path.basename(repoPath) || repoPath,
|
|
105
|
+
alreadyInitialised: memoryExists(repoPath),
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return repos;
|
|
110
|
+
}
|
|
111
|
+
function isBrainclawInternalPath(candidate) {
|
|
112
|
+
const parts = path.resolve(candidate).split(path.sep).filter(Boolean);
|
|
113
|
+
return parts.includes(MEMORY_DIR);
|
|
114
|
+
}
|
|
115
|
+
// ─── Step 3: Repo selection ───────────────────────────────────────────────────
|
|
116
|
+
export function parseRepoSelection(choice, repos, cwd = process.cwd()) {
|
|
117
|
+
const c = choice.trim().toLowerCase();
|
|
118
|
+
if (c === 'a' || c === 'all')
|
|
119
|
+
return repos;
|
|
120
|
+
if (c === 'c' || c === 'current') {
|
|
121
|
+
const curr = repos.find((r) => r.path === cwd)
|
|
122
|
+
?? repos.find((r) => cwd.startsWith(r.path + path.sep));
|
|
123
|
+
return curr ? [curr] : [];
|
|
124
|
+
}
|
|
125
|
+
const indices = c
|
|
126
|
+
.split(',')
|
|
127
|
+
.map((n) => parseInt(n.trim(), 10) - 1)
|
|
128
|
+
.filter((i) => !isNaN(i) && i >= 0 && i < repos.length);
|
|
129
|
+
return indices.map((i) => repos[i]);
|
|
130
|
+
}
|
|
131
|
+
// ─── Step 4: Agent selection ──────────────────────────────────────────────────
|
|
132
|
+
export function parseAgentSelection(choice, detected) {
|
|
133
|
+
const c = choice.trim().toLowerCase();
|
|
134
|
+
if (c === 'a' || c === 'all')
|
|
135
|
+
return [...ALL_KNOWN_AGENTS];
|
|
136
|
+
if (c === 'd' || c === 'detected')
|
|
137
|
+
return detected ? [detected] : [];
|
|
138
|
+
return c.split(',').map((a) => a.trim()).filter((a) => ALL_KNOWN_AGENTS.includes(a));
|
|
139
|
+
}
|
|
140
|
+
// ─── Step 5: Global install ───────────────────────────────────────────────────
|
|
141
|
+
export function initUserStore(home, env = process.env) {
|
|
142
|
+
if (!home)
|
|
143
|
+
return [];
|
|
144
|
+
const written = [];
|
|
145
|
+
// Ensure ~/.brainclaw/ directory and subdirs exist
|
|
146
|
+
const userStorePath = path.join(home, '.brainclaw');
|
|
147
|
+
ensureMemoryDir(home);
|
|
148
|
+
// Check if config.yaml already exists (idempotent)
|
|
149
|
+
const configPath = path.join(userStorePath, 'config.yaml');
|
|
150
|
+
if (fs.existsSync(configPath)) {
|
|
151
|
+
return [];
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
// Write a minimal config.yaml for the user store
|
|
155
|
+
const defaultCfg = defaultConfig('user-global');
|
|
156
|
+
saveConfig(defaultCfg, home);
|
|
157
|
+
// Append store_type: user to the config.yaml (pattern already used in tests)
|
|
158
|
+
fs.appendFileSync(configPath, 'store_type: user\n');
|
|
159
|
+
written.push(configPath);
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
// Non-fatal: if user store init fails, continue with agent setup
|
|
163
|
+
console.warn(`Warning: failed to initialize user store at ${configPath}:`, err instanceof Error ? err.message : String(err));
|
|
164
|
+
}
|
|
165
|
+
return written;
|
|
166
|
+
}
|
|
167
|
+
export function runGlobalInstall(selectedAgents, env = process.env) {
|
|
168
|
+
const home = resolveHomeDir(env);
|
|
169
|
+
const written = [];
|
|
170
|
+
// Initialize user-global store first
|
|
171
|
+
if (home) {
|
|
172
|
+
written.push(...initUserStore(home, env));
|
|
173
|
+
}
|
|
174
|
+
// Generate machine profile if missing
|
|
175
|
+
try {
|
|
176
|
+
const existing = loadMachineProfile();
|
|
177
|
+
if (!existing) {
|
|
178
|
+
const profile = buildMachineProfile();
|
|
179
|
+
const profilePath = saveMachineProfile(profile);
|
|
180
|
+
written.push(profilePath);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
// Non-fatal: machine profile is optional
|
|
185
|
+
}
|
|
186
|
+
// Generate agent inventory if missing
|
|
187
|
+
try {
|
|
188
|
+
const existingInv = loadAgentInventory();
|
|
189
|
+
if (!existingInv) {
|
|
190
|
+
const inventory = buildAgentInventory();
|
|
191
|
+
const inventoryPath = saveAgentInventory(inventory);
|
|
192
|
+
written.push(inventoryPath);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
// Non-fatal: agent inventory is optional
|
|
197
|
+
}
|
|
198
|
+
if (selectedAgents.includes('claude-code')) {
|
|
199
|
+
const s = ensureClaudeCodeUserSettings(home, env);
|
|
200
|
+
if (s && (s.created || s.updated))
|
|
201
|
+
written.push(s.filePath);
|
|
202
|
+
const c = ensureClaudeCodeUserCommand(home);
|
|
203
|
+
if (c && (c.created || c.updated))
|
|
204
|
+
written.push(c.filePath);
|
|
205
|
+
}
|
|
206
|
+
if (selectedAgents.includes('cursor')) {
|
|
207
|
+
const r = ensureCursorMcpConfig(home);
|
|
208
|
+
if (r && (r.created || r.updated))
|
|
209
|
+
written.push(r.filePath);
|
|
210
|
+
}
|
|
211
|
+
if (selectedAgents.includes('windsurf')) {
|
|
212
|
+
const r = ensureWindsurfMcpConfig(home);
|
|
213
|
+
if (r && (r.created || r.updated))
|
|
214
|
+
written.push(r.filePath);
|
|
215
|
+
}
|
|
216
|
+
if (selectedAgents.includes('antigravity')) {
|
|
217
|
+
const r = ensureAntigravityMcpConfig(home);
|
|
218
|
+
if (r && (r.created || r.updated))
|
|
219
|
+
written.push(r.filePath);
|
|
220
|
+
}
|
|
221
|
+
if (selectedAgents.includes('continue')) {
|
|
222
|
+
const r = ensureContinueUserMcpConfig(home);
|
|
223
|
+
if (r && (r.created || r.updated))
|
|
224
|
+
written.push(r.filePath);
|
|
225
|
+
}
|
|
226
|
+
if (selectedAgents.includes('codex')) {
|
|
227
|
+
const r = ensureCodexMcpConfig(home, env);
|
|
228
|
+
if (r && (r.created || r.updated))
|
|
229
|
+
written.push(r.filePath);
|
|
230
|
+
}
|
|
231
|
+
return written;
|
|
232
|
+
}
|
|
233
|
+
// ─── Step 6: Init repos + configure agents ────────────────────────────────────
|
|
234
|
+
export async function initReposAndConfigureAgents(selectedRepos, selectedAgents, env = process.env) {
|
|
235
|
+
const initialisedRepos = [];
|
|
236
|
+
const configActions = [];
|
|
237
|
+
for (const repo of selectedRepos) {
|
|
238
|
+
if (repo.alreadyInitialised) {
|
|
239
|
+
console.log(` [skip] ${repo.name} — already initialised`);
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
console.log(` → Initialising ${repo.name}...`);
|
|
243
|
+
await runInit({ yes: true, skipAgentBootstrap: true, skipSetupRequirement: true, cwd: repo.path });
|
|
244
|
+
initialisedRepos.push(repo.path);
|
|
245
|
+
}
|
|
246
|
+
for (const repo of selectedRepos) {
|
|
247
|
+
const gitignoreEntries = new Set();
|
|
248
|
+
for (const agentName of selectedAgents) {
|
|
249
|
+
const configs = writeDetectedAgentAutoConfig(agentName, repo.path, env);
|
|
250
|
+
for (const entry of collectWorkspaceGitignoreEntries(repo.path, configs)) {
|
|
251
|
+
gitignoreEntries.add(entry);
|
|
252
|
+
}
|
|
253
|
+
for (const config of configs) {
|
|
254
|
+
const msg = describeAutoConfigWrite(config);
|
|
255
|
+
if (msg) {
|
|
256
|
+
console.log(` ${msg}`);
|
|
257
|
+
configActions.push(msg);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
const hooks = writeDetectedAgentHooks(agentName, repo.name, repo.path);
|
|
261
|
+
for (const hook of hooks) {
|
|
262
|
+
gitignoreEntries.add(hook.relativePath);
|
|
263
|
+
}
|
|
264
|
+
for (const hook of hooks) {
|
|
265
|
+
if (hook.created) {
|
|
266
|
+
const msg = `✔ Created hook at ${hook.relativePath}`;
|
|
267
|
+
console.log(` ${msg}`);
|
|
268
|
+
configActions.push(msg);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (gitignoreEntries.size > 0) {
|
|
273
|
+
ensureGitignoreEntries(repo.path, [...gitignoreEntries]);
|
|
274
|
+
console.log(` ✔ Updated .gitignore with generated agent files (${[...gitignoreEntries].join(', ')})`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return { initialisedRepos, configActions };
|
|
278
|
+
}
|
|
279
|
+
// ─── Step 7: Reload reminder ──────────────────────────────────────────────────
|
|
280
|
+
export function printReloadReminder(detectedAgent) {
|
|
281
|
+
console.log('');
|
|
282
|
+
console.log('✔ Setup complete! Reload your AI agent session to activate brainclaw MCP tools.');
|
|
283
|
+
if (detectedAgent === 'claude-code') {
|
|
284
|
+
console.log(' → In VS Code: Cmd/Ctrl+Shift+P → "Claude: Reload MCP Servers"');
|
|
285
|
+
}
|
|
286
|
+
else if (detectedAgent === 'cursor') {
|
|
287
|
+
console.log(' → In Cursor: restart the editor');
|
|
288
|
+
}
|
|
289
|
+
else if (detectedAgent === 'windsurf') {
|
|
290
|
+
console.log(' → In Windsurf: restart the editor');
|
|
291
|
+
}
|
|
292
|
+
else if (detectedAgent === 'continue') {
|
|
293
|
+
console.log(' → In VS Code: reload the Continue extension');
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
console.log(' → Restart your AI coding agent to pick up the new MCP configuration.');
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
// ─── Main CLI wizard ──────────────────────────────────────────────────────────
|
|
300
|
+
export async function runSetup(options = {}) {
|
|
301
|
+
const env = process.env;
|
|
302
|
+
// Step 0: Git check
|
|
303
|
+
if (!checkGitPresence()) {
|
|
304
|
+
console.error(BRAINCLAW_ASCII);
|
|
305
|
+
console.error('brainclaw needs git to work.');
|
|
306
|
+
console.error('Install git from https://git-scm.com and try again.');
|
|
307
|
+
process.exit(1);
|
|
308
|
+
}
|
|
309
|
+
// Check if already run
|
|
310
|
+
const existingState = readSetupState(env);
|
|
311
|
+
if (existingState && !options.yes && process.stdin.isTTY) {
|
|
312
|
+
const date = new Date(existingState.completed_at).toLocaleDateString();
|
|
313
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
314
|
+
try {
|
|
315
|
+
const answer = (await rl.question(`Setup already run on ${date}. Re-run? [y/N]: `)).trim().toLowerCase();
|
|
316
|
+
if (answer !== 'y' && answer !== 'yes') {
|
|
317
|
+
console.log('Aborted.');
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
finally {
|
|
322
|
+
rl.close();
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
// Step 1: Project roots
|
|
326
|
+
let rootsInput;
|
|
327
|
+
if (options.roots) {
|
|
328
|
+
rootsInput = options.roots;
|
|
329
|
+
}
|
|
330
|
+
else if (options.yes || !process.stdin.isTTY) {
|
|
331
|
+
rootsInput = process.cwd();
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
const defaultRoots = getDefaultRoots(env);
|
|
335
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
336
|
+
try {
|
|
337
|
+
const prompt = defaultRoots
|
|
338
|
+
? `Where are your projects? [${defaultRoots}]: `
|
|
339
|
+
: 'Where are your projects? Enter one or more root directories (comma-separated): ';
|
|
340
|
+
const answer = (await rl.question(prompt)).trim();
|
|
341
|
+
rootsInput = answer || defaultRoots || process.cwd();
|
|
342
|
+
}
|
|
343
|
+
finally {
|
|
344
|
+
rl.close();
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
const roots = parseRoots(rootsInput, env);
|
|
348
|
+
if (roots.length === 0) {
|
|
349
|
+
console.error('No valid project root directories found. Aborting.');
|
|
350
|
+
process.exit(1);
|
|
351
|
+
}
|
|
352
|
+
// Step 2: Scan repos
|
|
353
|
+
console.log('\nScanning for git repositories...');
|
|
354
|
+
const repos = scanGitRepos(roots);
|
|
355
|
+
if (repos.length === 0) {
|
|
356
|
+
console.log('No git repositories found in the specified roots.');
|
|
357
|
+
console.log('Tip: run `brainclaw init` from within a project directory.');
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
console.log(`Found ${repos.length} repository candidate(s):`);
|
|
361
|
+
repos.forEach((r, i) => {
|
|
362
|
+
const status = r.alreadyInitialised ? '[✔ init]' : '[ ]';
|
|
363
|
+
console.log(` ${i + 1}) ${status} ${r.name} (${r.path})`);
|
|
364
|
+
});
|
|
365
|
+
// Step 3: Repo selection
|
|
366
|
+
let repoChoice;
|
|
367
|
+
if (options.repos) {
|
|
368
|
+
repoChoice = options.repos;
|
|
369
|
+
}
|
|
370
|
+
else if (options.yes || !process.stdin.isTTY) {
|
|
371
|
+
repoChoice = 'all';
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
375
|
+
try {
|
|
376
|
+
repoChoice = (await rl.question('\nInitialise: (a)ll, (c)urrent dir, or enter numbers e.g. 1,3 [all]: ')).trim() || 'all';
|
|
377
|
+
}
|
|
378
|
+
finally {
|
|
379
|
+
rl.close();
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
const selectedRepos = parseRepoSelection(repoChoice, repos);
|
|
383
|
+
if (selectedRepos.length === 0) {
|
|
384
|
+
console.log('No repositories selected. Aborting.');
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
console.log(`\nSelected ${selectedRepos.length} repository(s).`);
|
|
388
|
+
// Step 4: Agent detection & selection
|
|
389
|
+
const detectedAi = detectAiAgent(env);
|
|
390
|
+
const detectedName = detectedAi?.name;
|
|
391
|
+
console.log('');
|
|
392
|
+
if (detectedName) {
|
|
393
|
+
console.log(`Detected AI agent: ${detectedName}`);
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
console.log('No AI agent detected automatically.');
|
|
397
|
+
}
|
|
398
|
+
console.log('Supported agents:');
|
|
399
|
+
ALL_KNOWN_AGENTS.forEach((a, i) => {
|
|
400
|
+
const tag = a === detectedName ? ' ← detected' : '';
|
|
401
|
+
console.log(` ${i + 1}) ${a}${tag}`);
|
|
402
|
+
});
|
|
403
|
+
let agentChoice;
|
|
404
|
+
if (options.agents) {
|
|
405
|
+
agentChoice = options.agents;
|
|
406
|
+
}
|
|
407
|
+
else if (options.yes || !process.stdin.isTTY) {
|
|
408
|
+
agentChoice = detectedName ? 'detected' : 'all';
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
const defaultChoice = detectedName ? 'detected' : 'all';
|
|
412
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
413
|
+
try {
|
|
414
|
+
agentChoice = (await rl.question(`Configure agents: (d)etected, (a)ll, or numbers e.g. 1,3 [${defaultChoice}]: `)).trim() || defaultChoice;
|
|
415
|
+
}
|
|
416
|
+
finally {
|
|
417
|
+
rl.close();
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
const selectedAgents = parseAgentSelection(agentChoice, detectedName);
|
|
421
|
+
console.log(`Selected agents: ${selectedAgents.length === 0 ? '(none)' : selectedAgents.join(', ')}`);
|
|
422
|
+
// Step 5: Global install
|
|
423
|
+
console.log('\n→ Installing global brainclaw prerequisites...');
|
|
424
|
+
const written = runGlobalInstall(selectedAgents, env);
|
|
425
|
+
if (written.length > 0) {
|
|
426
|
+
for (const f of written)
|
|
427
|
+
console.log(` ✔ ${f}`);
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
console.log(' (all global prerequisites already up to date)');
|
|
431
|
+
}
|
|
432
|
+
// Step 6: Init repos + configure agents
|
|
433
|
+
console.log('\n→ Initialising repositories and configuring agents...');
|
|
434
|
+
const { initialisedRepos, configActions } = await initReposAndConfigureAgents(selectedRepos, selectedAgents, env);
|
|
435
|
+
// Save state
|
|
436
|
+
writeSetupState({
|
|
437
|
+
completed_at: new Date().toISOString(),
|
|
438
|
+
roots,
|
|
439
|
+
initialised_repos: initialisedRepos,
|
|
440
|
+
global_configs_written: selectedAgents,
|
|
441
|
+
}, env);
|
|
442
|
+
// Step 7: Reload reminder
|
|
443
|
+
printReloadReminder(detectedName);
|
|
444
|
+
void configActions;
|
|
445
|
+
}
|
|
446
|
+
//# sourceMappingURL=setup.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { memoryExists } from '../core/io.js';
|
|
2
|
+
import { loadCandidate, listCandidates } from '../core/candidates.js';
|
|
3
|
+
export function runShowCandidate(id, options = {}) {
|
|
4
|
+
if (!memoryExists()) {
|
|
5
|
+
console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
|
|
6
|
+
process.exit(1);
|
|
7
|
+
}
|
|
8
|
+
try {
|
|
9
|
+
const candidate = loadCandidate(id);
|
|
10
|
+
if (!options.related) {
|
|
11
|
+
console.log(JSON.stringify(candidate, null, 2));
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
// Find related candidates by shared tags or related_paths
|
|
15
|
+
const allPending = listCandidates('pending').filter(c => c.id !== id);
|
|
16
|
+
const cTags = new Set(candidate.tags ?? []);
|
|
17
|
+
const cPaths = new Set(candidate.related_paths ?? []);
|
|
18
|
+
const related = allPending.filter(c => {
|
|
19
|
+
const sharedTag = (c.tags ?? []).some(t => cTags.has(t));
|
|
20
|
+
const sharedPath = (c.related_paths ?? []).some(p => cPaths.has(p));
|
|
21
|
+
return sharedTag || sharedPath;
|
|
22
|
+
});
|
|
23
|
+
console.log(JSON.stringify({ ...candidate, related_candidates: related }, null, 2));
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
27
|
+
console.error(`Error: ${msg}`);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=show-candidate.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { memoryExists } from '../core/io.js';
|
|
2
|
+
import { addCandidateStar } from '../core/candidates.js';
|
|
3
|
+
import { loadConfig } from '../core/config.js';
|
|
4
|
+
export function runStarCandidate(id, options = {}) {
|
|
5
|
+
if (!memoryExists()) {
|
|
6
|
+
console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
|
|
7
|
+
process.exit(1);
|
|
8
|
+
}
|
|
9
|
+
const actor = options.by ?? process.env.USER ?? process.env.USERNAME ?? 'unknown';
|
|
10
|
+
try {
|
|
11
|
+
const { candidate, added } = addCandidateStar(id, actor);
|
|
12
|
+
const threshold = loadConfig().reflective_memory?.promotion_stars_threshold ?? 3;
|
|
13
|
+
if (!added) {
|
|
14
|
+
console.log(`✔ Candidate [${id}] was already starred by ${actor} (${candidate.star_count} star(s))`);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const recommendation = candidate.star_count >= threshold
|
|
18
|
+
? ' Promotion is now recommended.'
|
|
19
|
+
: '';
|
|
20
|
+
console.log(`✔ Candidate [${id}] starred by ${actor} (${candidate.star_count}/${threshold})${recommendation}`);
|
|
21
|
+
}
|
|
22
|
+
catch (e) {
|
|
23
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
24
|
+
console.error(`Error: ${msg}`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=star-candidate.js.map
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { listAgentIdentities } from '../core/agent-registry.js';
|
|
2
|
+
import { loadConfig } from '../core/config.js';
|
|
3
|
+
import { resolveCurrentHostId } from '../core/host.js';
|
|
4
|
+
import { listClaims } from '../core/claims.js';
|
|
5
|
+
import { loadInstructions } from '../core/instructions.js';
|
|
6
|
+
import { buildReputationSnapshot } from '../core/reputation.js';
|
|
7
|
+
import { listRuntimeNotes } from '../core/runtime.js';
|
|
8
|
+
import { loadState } from '../core/state.js';
|
|
9
|
+
import { isTrapActive, listOperationalTraps } from '../core/traps.js';
|
|
10
|
+
import { generateMarkdown } from '../core/markdown.js';
|
|
11
|
+
import { memoryExists } from '../core/io.js';
|
|
12
|
+
function printHumanStatus(state, config) {
|
|
13
|
+
const activePlans = state.plan_items.filter((plan) => plan.status !== 'done' && plan.status !== 'dropped');
|
|
14
|
+
const activeInstructions = loadInstructions().filter((entry) => entry.active);
|
|
15
|
+
const activeClaims = listClaims().filter((claim) => claim.status === 'active');
|
|
16
|
+
const registeredAgents = listAgentIdentities();
|
|
17
|
+
const currentHost = resolveCurrentHostId();
|
|
18
|
+
const sharedRuntimeNotes = listRuntimeNotes({ visibility: 'shared' });
|
|
19
|
+
const machineRuntimeNotes = listRuntimeNotes({ visibility: 'machine' });
|
|
20
|
+
const machineTraps = listOperationalTraps({ visibility: 'machine' });
|
|
21
|
+
const activeSharedTraps = state.known_traps.filter((trap) => isTrapActive(trap));
|
|
22
|
+
const resolvedSharedTraps = state.known_traps.filter((trap) => trap.status === 'resolved');
|
|
23
|
+
const activeMachineTraps = machineTraps.filter((trap) => isTrapActive(trap));
|
|
24
|
+
const counts = {
|
|
25
|
+
instructions: activeInstructions.length,
|
|
26
|
+
claims: activeClaims.length,
|
|
27
|
+
runtime: sharedRuntimeNotes.length + machineRuntimeNotes.length,
|
|
28
|
+
plans: activePlans.length,
|
|
29
|
+
constraints: state.active_constraints.length,
|
|
30
|
+
decisions: state.recent_decisions.length,
|
|
31
|
+
traps: activeSharedTraps.length + activeMachineTraps.length,
|
|
32
|
+
handoffs: state.open_handoffs.length,
|
|
33
|
+
};
|
|
34
|
+
const total = counts.instructions + counts.claims + counts.runtime + counts.plans + counts.constraints + counts.decisions + counts.traps + counts.handoffs;
|
|
35
|
+
console.log(`Project memory: ${total} item(s)`);
|
|
36
|
+
console.log('');
|
|
37
|
+
console.log(` Project ID : ${config.project_id ?? 'unknown'}`);
|
|
38
|
+
console.log(` Current agent: ${config.current_agent ?? 'unknown'}${config.current_agent_id ? ` (${config.current_agent_id})` : ''}`);
|
|
39
|
+
console.log(` Registered : ${registeredAgents.length} agent(s)`);
|
|
40
|
+
console.log(` Storage dir : ${config.storage_dir}`);
|
|
41
|
+
console.log(` Topology : ${config.topology}`);
|
|
42
|
+
console.log(` Project mode: ${config.project_mode}`);
|
|
43
|
+
console.log(` Strategy : ${config.projects.strategy}`);
|
|
44
|
+
console.log(` Instructions: ${counts.instructions}`);
|
|
45
|
+
console.log(` Claims : ${counts.claims}`);
|
|
46
|
+
console.log(` Runtime : ${sharedRuntimeNotes.length} shared, ${machineRuntimeNotes.length} machine-local (${currentHost})`);
|
|
47
|
+
console.log(` Plans : ${counts.plans}`);
|
|
48
|
+
console.log(` Constraints : ${counts.constraints}`);
|
|
49
|
+
console.log(` Decisions : ${counts.decisions}`);
|
|
50
|
+
console.log(` Traps : ${activeSharedTraps.length} active shared, ${resolvedSharedTraps.length} resolved shared, ${activeMachineTraps.length} active machine-local (${currentHost})`);
|
|
51
|
+
console.log(` Handoffs : ${counts.handoffs}`);
|
|
52
|
+
if (config.project_mode === 'multi-project') {
|
|
53
|
+
console.log(` Projects : ${config.projects.known.length}`);
|
|
54
|
+
}
|
|
55
|
+
// Show recent items (last 3 per section)
|
|
56
|
+
const sections = [
|
|
57
|
+
{ label: 'Shared instructions', items: activeInstructions },
|
|
58
|
+
{ label: 'Active claims', items: activeClaims },
|
|
59
|
+
{ label: 'Shared plan', items: activePlans },
|
|
60
|
+
{ label: 'Active constraints', items: state.active_constraints },
|
|
61
|
+
{ label: 'Recent decisions', items: state.recent_decisions },
|
|
62
|
+
{ label: 'Known traps', items: activeSharedTraps },
|
|
63
|
+
{ label: 'Open handoffs', items: state.open_handoffs },
|
|
64
|
+
];
|
|
65
|
+
for (const section of sections) {
|
|
66
|
+
if (section.items.length > 0) {
|
|
67
|
+
console.log('');
|
|
68
|
+
console.log(` ${section.label}:`);
|
|
69
|
+
const recent = section.items.slice(-3);
|
|
70
|
+
for (const item of recent) {
|
|
71
|
+
const text = 'text' in item ? item.text : '';
|
|
72
|
+
console.log(` [${item.id}] ${text}`);
|
|
73
|
+
}
|
|
74
|
+
if (section.items.length > 3) {
|
|
75
|
+
console.log(` ... and ${section.items.length - 3} more`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Warnings
|
|
80
|
+
const openHandoffs = state.open_handoffs.filter(h => h.status === 'open');
|
|
81
|
+
if (openHandoffs.length > 0) {
|
|
82
|
+
console.log('');
|
|
83
|
+
console.log(` ⚠ ${openHandoffs.length} open handoff(s) pending`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
export function runStatus(options = {}) {
|
|
87
|
+
if (!memoryExists()) {
|
|
88
|
+
console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
const config = loadConfig();
|
|
92
|
+
const state = loadState();
|
|
93
|
+
const currentHost = resolveCurrentHostId();
|
|
94
|
+
const sharedRuntimeNotes = listRuntimeNotes({ visibility: 'shared' });
|
|
95
|
+
const machineRuntimeNotes = listRuntimeNotes({ visibility: 'machine' });
|
|
96
|
+
const machineTraps = listOperationalTraps({ visibility: 'machine' });
|
|
97
|
+
const activeSharedTraps = state.known_traps.filter((trap) => isTrapActive(trap));
|
|
98
|
+
const resolvedSharedTraps = state.known_traps.filter((trap) => trap.status === 'resolved');
|
|
99
|
+
const activeMachineTraps = machineTraps.filter((trap) => isTrapActive(trap));
|
|
100
|
+
const registeredAgents = listAgentIdentities();
|
|
101
|
+
const reputation = buildReputationSnapshot();
|
|
102
|
+
if (options.json) {
|
|
103
|
+
console.log(JSON.stringify({
|
|
104
|
+
config,
|
|
105
|
+
agents: {
|
|
106
|
+
current_name: config.current_agent,
|
|
107
|
+
current_id: config.current_agent_id,
|
|
108
|
+
registered: registeredAgents,
|
|
109
|
+
},
|
|
110
|
+
state,
|
|
111
|
+
runtime: {
|
|
112
|
+
current_host: currentHost,
|
|
113
|
+
shared: sharedRuntimeNotes.length,
|
|
114
|
+
machine_local: machineRuntimeNotes.length,
|
|
115
|
+
},
|
|
116
|
+
traps: {
|
|
117
|
+
shared_active: activeSharedTraps.length,
|
|
118
|
+
shared_resolved: resolvedSharedTraps.length,
|
|
119
|
+
machine_local_active: activeMachineTraps.length,
|
|
120
|
+
total_shared: state.known_traps.length,
|
|
121
|
+
total_machine_local: machineTraps.length,
|
|
122
|
+
},
|
|
123
|
+
reputation,
|
|
124
|
+
}, null, 2));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (options.markdown) {
|
|
128
|
+
console.log(generateMarkdown(state));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
printHumanStatus(state, config);
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=status.js.map
|