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.
Files changed (128) hide show
  1. package/LICENSE +74 -0
  2. package/README.md +226 -0
  3. package/dist/cli.js +1037 -0
  4. package/dist/commands/accept.js +149 -0
  5. package/dist/commands/adapter-openclaw-import.js +75 -0
  6. package/dist/commands/add-step.js +35 -0
  7. package/dist/commands/agent-board.js +106 -0
  8. package/dist/commands/audit.js +35 -0
  9. package/dist/commands/bootstrap.js +34 -0
  10. package/dist/commands/capability.js +104 -0
  11. package/dist/commands/changes.js +112 -0
  12. package/dist/commands/check-constraints.js +63 -0
  13. package/dist/commands/claim-resource.js +54 -0
  14. package/dist/commands/claim.js +92 -0
  15. package/dist/commands/complete-step.js +34 -0
  16. package/dist/commands/constraint.js +44 -0
  17. package/dist/commands/context-diff.js +32 -0
  18. package/dist/commands/context.js +63 -0
  19. package/dist/commands/decision.js +45 -0
  20. package/dist/commands/delete-plan.js +20 -0
  21. package/dist/commands/diff.js +99 -0
  22. package/dist/commands/doctor.js +1275 -0
  23. package/dist/commands/enable-agent.js +63 -0
  24. package/dist/commands/env.js +46 -0
  25. package/dist/commands/estimation-report.js +167 -0
  26. package/dist/commands/explore.js +47 -0
  27. package/dist/commands/export.js +381 -0
  28. package/dist/commands/handoff.js +63 -0
  29. package/dist/commands/history.js +22 -0
  30. package/dist/commands/hooks.js +123 -0
  31. package/dist/commands/init.js +356 -0
  32. package/dist/commands/install-hooks.js +115 -0
  33. package/dist/commands/instruction.js +56 -0
  34. package/dist/commands/list-agents.js +44 -0
  35. package/dist/commands/list-claims.js +45 -0
  36. package/dist/commands/list-instructions.js +50 -0
  37. package/dist/commands/list-plans.js +48 -0
  38. package/dist/commands/mcp-worker.js +12 -0
  39. package/dist/commands/mcp.js +2272 -0
  40. package/dist/commands/memory.js +283 -0
  41. package/dist/commands/metrics.js +175 -0
  42. package/dist/commands/plan-resource.js +62 -0
  43. package/dist/commands/plan.js +76 -0
  44. package/dist/commands/prune-candidates.js +36 -0
  45. package/dist/commands/prune.js +48 -0
  46. package/dist/commands/pull.js +25 -0
  47. package/dist/commands/push.js +28 -0
  48. package/dist/commands/rebuild.js +14 -0
  49. package/dist/commands/reflect-runtime-note.js +74 -0
  50. package/dist/commands/reflect.js +286 -0
  51. package/dist/commands/register-agent.js +29 -0
  52. package/dist/commands/reject.js +52 -0
  53. package/dist/commands/release-claim.js +41 -0
  54. package/dist/commands/release-claims.js +67 -0
  55. package/dist/commands/review.js +242 -0
  56. package/dist/commands/rollback.js +156 -0
  57. package/dist/commands/runtime-note.js +144 -0
  58. package/dist/commands/runtime-status.js +49 -0
  59. package/dist/commands/search.js +36 -0
  60. package/dist/commands/session-end.js +187 -0
  61. package/dist/commands/session-start.js +147 -0
  62. package/dist/commands/set-trust.js +92 -0
  63. package/dist/commands/setup.js +446 -0
  64. package/dist/commands/show-candidate.js +31 -0
  65. package/dist/commands/star-candidate.js +28 -0
  66. package/dist/commands/status.js +133 -0
  67. package/dist/commands/sync.js +159 -0
  68. package/dist/commands/tool.js +126 -0
  69. package/dist/commands/trap.js +74 -0
  70. package/dist/commands/update-handoff.js +23 -0
  71. package/dist/commands/update-plan.js +37 -0
  72. package/dist/commands/upgrade.js +382 -0
  73. package/dist/commands/use-candidate.js +35 -0
  74. package/dist/commands/version.js +96 -0
  75. package/dist/commands/watch.js +215 -0
  76. package/dist/commands/whoami.js +104 -0
  77. package/dist/core/agent-context.js +340 -0
  78. package/dist/core/agent-files.js +874 -0
  79. package/dist/core/agent-integrations.js +135 -0
  80. package/dist/core/agent-inventory.js +401 -0
  81. package/dist/core/agent-registry.js +420 -0
  82. package/dist/core/ai-agent-detection.js +140 -0
  83. package/dist/core/audit.js +85 -0
  84. package/dist/core/bootstrap.js +658 -0
  85. package/dist/core/brainclaw-version.js +433 -0
  86. package/dist/core/candidates.js +137 -0
  87. package/dist/core/circuit-breaker.js +118 -0
  88. package/dist/core/claims.js +72 -0
  89. package/dist/core/config.js +86 -0
  90. package/dist/core/context-diff.js +122 -0
  91. package/dist/core/context.js +1212 -0
  92. package/dist/core/contradictions.js +270 -0
  93. package/dist/core/coordination.js +86 -0
  94. package/dist/core/cross-project.js +99 -0
  95. package/dist/core/duplicates.js +72 -0
  96. package/dist/core/event-log.js +152 -0
  97. package/dist/core/events.js +56 -0
  98. package/dist/core/execution-context.js +204 -0
  99. package/dist/core/freshness.js +87 -0
  100. package/dist/core/global-registry.js +182 -0
  101. package/dist/core/host.js +10 -0
  102. package/dist/core/identity.js +151 -0
  103. package/dist/core/ids.js +56 -0
  104. package/dist/core/input-validation.js +81 -0
  105. package/dist/core/instructions.js +117 -0
  106. package/dist/core/io.js +191 -0
  107. package/dist/core/json-store.js +63 -0
  108. package/dist/core/lifecycle.js +45 -0
  109. package/dist/core/lock.js +129 -0
  110. package/dist/core/logger.js +49 -0
  111. package/dist/core/machine-profile.js +332 -0
  112. package/dist/core/markdown.js +120 -0
  113. package/dist/core/memory-git.js +133 -0
  114. package/dist/core/migration.js +247 -0
  115. package/dist/core/project-registry.js +64 -0
  116. package/dist/core/reflection-safety.js +21 -0
  117. package/dist/core/repo-analysis.js +133 -0
  118. package/dist/core/reputation.js +409 -0
  119. package/dist/core/runtime.js +134 -0
  120. package/dist/core/schema.js +580 -0
  121. package/dist/core/search.js +115 -0
  122. package/dist/core/security.js +66 -0
  123. package/dist/core/setup-state.js +50 -0
  124. package/dist/core/state.js +83 -0
  125. package/dist/core/store-resolution.js +119 -0
  126. package/dist/core/sync-remote.js +83 -0
  127. package/dist/core/traps.js +86 -0
  128. package/package.json +60 -0
@@ -0,0 +1,420 @@
1
+ import crypto from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { loadConfig, saveConfig } from './config.js';
6
+ import { nowISO } from './ids.js';
7
+ import { MEMORY_DIR, resolveEntityDir } from './io.js';
8
+ import { JsonStore } from './json-store.js';
9
+ import { AgentIdentityDocumentSchema, } from './schema.js';
10
+ import { logger } from './logger.js';
11
+ // agents/ stays at top level in entity model (already entity-aligned)
12
+ const TRUST_ORDER = ['observer', 'contributor', 'trusted', 'curator'];
13
+ export class AgentIdentityResolutionError extends Error {
14
+ kind = 'identity_error';
15
+ details;
16
+ constructor(message, details) {
17
+ super(message);
18
+ this.details = details;
19
+ }
20
+ }
21
+ export class AgentTrustError extends Error {
22
+ kind = 'trust_error';
23
+ details;
24
+ constructor(message, details) {
25
+ super(message);
26
+ this.details = details;
27
+ }
28
+ }
29
+ export function generateAgentId() {
30
+ return `agt_${crypto.randomUUID().replace(/-/g, '')}`;
31
+ }
32
+ export function resolveDefaultAgentName(env = process.env) {
33
+ return env.BRAINCLAW_AGENT?.trim()
34
+ || env.OPENCLAW_AGENT?.trim()
35
+ || env.USERNAME?.trim()
36
+ || env.USER?.trim()
37
+ || 'unknown-agent';
38
+ }
39
+ function agentsDir(cwd, preferredDirName) {
40
+ return resolveEntityDir('agents', cwd ?? process.cwd(), 'read', preferredDirName);
41
+ }
42
+ function ensureAgentsDir(cwd, preferredDirName) {
43
+ const dir = agentsDir(cwd, preferredDirName);
44
+ if (!fs.existsSync(dir)) {
45
+ fs.mkdirSync(dir, { recursive: true });
46
+ }
47
+ }
48
+ function agentStore(cwd, preferredDirName) {
49
+ return new JsonStore({
50
+ dirPath: agentsDir(cwd, preferredDirName),
51
+ documentType: 'agent_identity',
52
+ getId: (agent) => agent.agent_id,
53
+ sort: (a, b) => a.created_at.localeCompare(b.created_at) || a.agent_name.localeCompare(b.agent_name),
54
+ });
55
+ }
56
+ function normalizeAgentName(agentName) {
57
+ return agentName.trim().toLowerCase();
58
+ }
59
+ function normalizeCapability(capability) {
60
+ const normalized = capability.trim().toLowerCase();
61
+ return normalized.length > 0 ? normalized : undefined;
62
+ }
63
+ function normalizeCapabilities(capabilities) {
64
+ const seen = new Set();
65
+ const normalized = [];
66
+ for (const capability of capabilities ?? []) {
67
+ const next = normalizeCapability(capability);
68
+ if (!next || seen.has(next)) {
69
+ continue;
70
+ }
71
+ seen.add(next);
72
+ normalized.push(next);
73
+ }
74
+ return normalized;
75
+ }
76
+ function mergeCapabilities(existing, next) {
77
+ return normalizeCapabilities([...existing, ...next]);
78
+ }
79
+ function resolveEnvAgentName(env = process.env) {
80
+ const value = env.BRAINCLAW_AGENT?.trim() || env.OPENCLAW_AGENT?.trim();
81
+ return value && value.length > 0 ? value : undefined;
82
+ }
83
+ function codexHome(env = process.env) {
84
+ const explicit = env.CODEX_HOME?.trim();
85
+ return explicit && explicit.length > 0 ? explicit : path.join(os.homedir(), '.codex');
86
+ }
87
+ function agentKeyPath(agentId, env = process.env) {
88
+ return path.join(codexHome(env), 'brainclaw', 'keys', `${agentId}.ed25519.pem`);
89
+ }
90
+ function ensureParentDir(filepath) {
91
+ const dir = path.dirname(filepath);
92
+ if (!fs.existsSync(dir)) {
93
+ fs.mkdirSync(dir, { recursive: true });
94
+ }
95
+ }
96
+ function fingerprintPublicKey(publicKey) {
97
+ return crypto.createHash('sha256').update(publicKey).digest('hex');
98
+ }
99
+ function buildIdentityKey(agentId, env = process.env, forceRegenerate = false) {
100
+ const filepath = agentKeyPath(agentId, env);
101
+ const createdAt = nowISO();
102
+ let publicKeyPem;
103
+ if (!forceRegenerate && fs.existsSync(filepath)) {
104
+ const privateKey = crypto.createPrivateKey(fs.readFileSync(filepath, 'utf-8'));
105
+ publicKeyPem = crypto.createPublicKey(privateKey).export({ type: 'spki', format: 'pem' }).toString();
106
+ }
107
+ else {
108
+ const generated = crypto.generateKeyPairSync('ed25519');
109
+ const privateKeyPem = generated.privateKey.export({ type: 'pkcs8', format: 'pem' }).toString();
110
+ publicKeyPem = generated.publicKey.export({ type: 'spki', format: 'pem' }).toString();
111
+ ensureParentDir(filepath);
112
+ fs.writeFileSync(filepath, privateKeyPem, 'utf-8');
113
+ }
114
+ return {
115
+ algorithm: 'ed25519',
116
+ public_key: publicKeyPem,
117
+ fingerprint: fingerprintPublicKey(publicKeyPem),
118
+ created_at: createdAt,
119
+ };
120
+ }
121
+ function withIdentityKey(agent, env = process.env, forceRegenerate = false) {
122
+ return {
123
+ ...agent,
124
+ identity_key: buildIdentityKey(agent.agent_id, env, forceRegenerate),
125
+ };
126
+ }
127
+ export function loadAgentIdentity(agentId, cwd, preferredDirName) {
128
+ return agentStore(cwd, preferredDirName).load(agentId);
129
+ }
130
+ export function saveAgentIdentity(agent, cwd, preferredDirName) {
131
+ ensureAgentsDir(cwd, preferredDirName);
132
+ agentStore(cwd, preferredDirName).save(AgentIdentityDocumentSchema.parse(agent));
133
+ }
134
+ export function listAgentIdentities(cwd, preferredDirName) {
135
+ return agentStore(cwd, preferredDirName).list();
136
+ }
137
+ export function findAgentIdentityByName(agentName, cwd, preferredDirName) {
138
+ const normalized = normalizeAgentName(agentName);
139
+ return listAgentIdentities(cwd, preferredDirName).find((agent) => normalizeAgentName(agent.agent_name) === normalized);
140
+ }
141
+ export function findAgentIdentityById(agentId, cwd, preferredDirName) {
142
+ try {
143
+ return loadAgentIdentity(agentId, cwd, preferredDirName);
144
+ }
145
+ catch (err) {
146
+ logger.debug('Failed to find agent identity by ID:', agentId, err);
147
+ return undefined;
148
+ }
149
+ }
150
+ export function registerAgentIdentity(input) {
151
+ const normalizedCapabilities = normalizeCapabilities(input.capabilities);
152
+ const existing = findAgentIdentityByName(input.agentName, input.cwd, input.preferredDirName);
153
+ if (existing) {
154
+ let updated = existing;
155
+ if (input.kind && existing.kind !== input.kind) {
156
+ updated = { ...updated, kind: input.kind };
157
+ }
158
+ if (input.trustLevel && existing.trust_level !== input.trustLevel) {
159
+ // Never downgrade trust — only upgrade
160
+ if (hasMinimumTrustLevel(input.trustLevel, existing.trust_level ?? 'contributor')) {
161
+ updated = { ...updated, trust_level: input.trustLevel };
162
+ }
163
+ }
164
+ if (input.replaceCapabilities) {
165
+ updated = { ...updated, capabilities: normalizedCapabilities };
166
+ }
167
+ else if (normalizedCapabilities.length > 0) {
168
+ updated = { ...updated, capabilities: mergeCapabilities(existing.capabilities ?? [], normalizedCapabilities) };
169
+ }
170
+ if (input.generateFingerprint) {
171
+ updated = withIdentityKey(updated, input.env, true);
172
+ }
173
+ if (JSON.stringify(updated) !== JSON.stringify(existing)) {
174
+ saveAgentIdentity(updated, input.cwd, input.preferredDirName);
175
+ }
176
+ return updated;
177
+ }
178
+ let created = {
179
+ schema_version: 2,
180
+ version: 1,
181
+ agent_id: generateAgentId(),
182
+ agent_name: input.agentName.trim(),
183
+ created_at: nowISO(),
184
+ kind: input.kind ?? 'unknown',
185
+ trust_level: input.trustLevel ?? 'contributor',
186
+ capabilities: normalizedCapabilities,
187
+ };
188
+ if (input.generateFingerprint) {
189
+ created = withIdentityKey(created, input.env, true);
190
+ }
191
+ saveAgentIdentity(created, input.cwd, input.preferredDirName);
192
+ return created;
193
+ }
194
+ export function resolveCurrentAgentIdentity(cwd, preferredDirName) {
195
+ // env var takes priority over config — allows AI agent to self-identify
196
+ const envAgentId = (process.env.BRAINCLAW_AGENT_ID ?? '').trim();
197
+ const envAgentName = (process.env.BRAINCLAW_AGENT_NAME ?? process.env.BRAINCLAW_AGENT ?? '').trim();
198
+ if (envAgentId) {
199
+ const byEnvId = findAgentIdentityById(envAgentId, cwd, preferredDirName);
200
+ if (byEnvId)
201
+ return byEnvId;
202
+ }
203
+ if (envAgentName) {
204
+ const byEnvName = findAgentIdentityByName(envAgentName, cwd, preferredDirName);
205
+ if (byEnvName)
206
+ return byEnvName;
207
+ }
208
+ const config = loadConfig(cwd, preferredDirName);
209
+ if (config.current_agent_id) {
210
+ const byId = findAgentIdentityById(config.current_agent_id, cwd, preferredDirName);
211
+ if (byId) {
212
+ return byId;
213
+ }
214
+ }
215
+ if (config.current_agent) {
216
+ return findAgentIdentityByName(config.current_agent, cwd, preferredDirName);
217
+ }
218
+ return undefined;
219
+ }
220
+ export function resolveRegisteredAgentIdentity(options = {}) {
221
+ const agentId = options.agentId?.trim();
222
+ const agentName = options.agentName?.trim();
223
+ const cwd = options.cwd;
224
+ const preferredDirName = options.preferredDirName;
225
+ const env = options.env ?? process.env;
226
+ if (agentId && agentName) {
227
+ const byId = findAgentIdentityById(agentId, cwd, preferredDirName);
228
+ if (!byId) {
229
+ return undefined;
230
+ }
231
+ if (normalizeAgentName(byId.agent_name) !== normalizeAgentName(agentName)) {
232
+ throw new AgentIdentityResolutionError(`Agent '${agentName}' does not match registered id '${agentId}'.`, { agent_id: agentId, agent_name: agentName });
233
+ }
234
+ return byId;
235
+ }
236
+ if (agentId) {
237
+ return findAgentIdentityById(agentId, cwd, preferredDirName);
238
+ }
239
+ if (agentName) {
240
+ return findAgentIdentityByName(agentName, cwd, preferredDirName);
241
+ }
242
+ if (options.allowCurrent !== false) {
243
+ const current = resolveCurrentAgentIdentity(cwd, preferredDirName);
244
+ if (current) {
245
+ return current;
246
+ }
247
+ }
248
+ if (options.allowEnv !== false) {
249
+ const envAgent = resolveEnvAgentName(env);
250
+ if (envAgent) {
251
+ return findAgentIdentityByName(envAgent, cwd, preferredDirName);
252
+ }
253
+ }
254
+ return undefined;
255
+ }
256
+ export function requireRegisteredAgentIdentity(options = {}) {
257
+ const agentId = options.agentId?.trim();
258
+ const agentName = options.agentName?.trim();
259
+ const cwd = options.cwd;
260
+ const preferredDirName = options.preferredDirName;
261
+ const env = options.env ?? process.env;
262
+ if (agentId && agentName) {
263
+ const resolved = resolveRegisteredAgentIdentity(options);
264
+ if (!resolved) {
265
+ throw new AgentIdentityResolutionError(`Registered agent '${agentName}' [${agentId}] not found.`, { agent_id: agentId, agent_name: agentName });
266
+ }
267
+ return resolved;
268
+ }
269
+ if (agentId) {
270
+ const resolved = findAgentIdentityById(agentId, cwd, preferredDirName);
271
+ if (!resolved) {
272
+ throw new AgentIdentityResolutionError(`Registered agent id '${agentId}' not found.`, { agent_id: agentId });
273
+ }
274
+ return resolved;
275
+ }
276
+ if (agentName) {
277
+ const resolved = findAgentIdentityByName(agentName, cwd, preferredDirName);
278
+ if (!resolved) {
279
+ throw new AgentIdentityResolutionError(`Agent '${agentName}' is not registered. Run \`brainclaw register-agent ${agentName}\`.`, { agent_name: agentName });
280
+ }
281
+ return resolved;
282
+ }
283
+ const current = options.allowCurrent !== false
284
+ ? resolveCurrentAgentIdentity(cwd, preferredDirName)
285
+ : undefined;
286
+ if (current) {
287
+ return current;
288
+ }
289
+ if (options.allowEnv !== false) {
290
+ const envAgent = resolveEnvAgentName(env);
291
+ if (envAgent) {
292
+ const resolved = findAgentIdentityByName(envAgent, cwd, preferredDirName);
293
+ if (!resolved) {
294
+ throw new AgentIdentityResolutionError(`Environment agent '${envAgent}' is not registered.`, { agent_name: envAgent });
295
+ }
296
+ return resolved;
297
+ }
298
+ }
299
+ throw new AgentIdentityResolutionError('No registered agent identity resolved. Use --agent/--agent-id or configure a current agent with `brainclaw register-agent <name> --set-current`.');
300
+ }
301
+ export function resolveAgentScope(agentName, cwd, preferredDirName) {
302
+ const explicit = agentName?.trim();
303
+ if (explicit) {
304
+ return explicit;
305
+ }
306
+ return loadConfig(cwd, preferredDirName).current_agent?.trim() || undefined;
307
+ }
308
+ /**
309
+ * Returns the current model identifier if declared, from:
310
+ * 1. $BRAINCLAW_MODEL env var (explicit per-session declaration)
311
+ * 2. registered agent document model field
312
+ * 3. undefined (not tracked)
313
+ */
314
+ export function resolveCurrentModel(cwd) {
315
+ const fromEnv = process.env.BRAINCLAW_MODEL?.trim();
316
+ if (fromEnv)
317
+ return fromEnv;
318
+ const identity = resolveCurrentAgentIdentity(cwd);
319
+ return identity?.model;
320
+ }
321
+ /**
322
+ * Returns the name of the current agent, with priority:
323
+ * 1. $BRAINCLAW_AGENT_NAME env var (AI agent self-declaration)
324
+ * 2. $BRAINCLAW_AGENT env var (legacy alias)
325
+ * 3. config.current_agent (project owner / human default)
326
+ * 4. OS user (last-resort fallback)
327
+ */
328
+ export function resolveCurrentAgentName(cwd) {
329
+ const fromEnv = (process.env.BRAINCLAW_AGENT_NAME ?? process.env.BRAINCLAW_AGENT)?.trim();
330
+ if (fromEnv)
331
+ return fromEnv;
332
+ const fromConfig = loadConfig(cwd).current_agent?.trim();
333
+ if (fromConfig)
334
+ return fromConfig;
335
+ return process.env.USER ?? process.env.USERNAME ?? 'unknown';
336
+ }
337
+ export function requireOperationalAgentIdentity(agentName, cwd, preferredDirName) {
338
+ return requireRegisteredAgentIdentity({
339
+ agentName,
340
+ cwd,
341
+ preferredDirName,
342
+ allowCurrent: true,
343
+ allowEnv: true,
344
+ });
345
+ }
346
+ export function resolveExistingCurrentAgent(cwd) {
347
+ for (const dirName of [MEMORY_DIR]) {
348
+ try {
349
+ const config = loadConfig(cwd, dirName);
350
+ if (config.current_agent_id) {
351
+ const byId = findAgentIdentityById(config.current_agent_id, cwd, dirName);
352
+ if (byId) {
353
+ return byId;
354
+ }
355
+ }
356
+ if (config.current_agent) {
357
+ const byName = findAgentIdentityByName(config.current_agent, cwd, dirName);
358
+ if (byName) {
359
+ return byName;
360
+ }
361
+ }
362
+ }
363
+ catch (err) {
364
+ logger.debug('Ignoring missing or malformed config while searching for current agent:', err);
365
+ }
366
+ }
367
+ return undefined;
368
+ }
369
+ export function setCurrentAgentIdentity(agent, cwd, preferredDirName) {
370
+ const config = loadConfig(cwd, preferredDirName);
371
+ config.current_agent = agent.agent_name;
372
+ config.current_agent_id = agent.agent_id;
373
+ saveConfig(config, cwd, preferredDirName);
374
+ }
375
+ export function hasElevatedAgent(cwd) {
376
+ return listAgentIdentities(cwd).some((agent) => agent.trust_level === 'trusted' || agent.trust_level === 'curator');
377
+ }
378
+ export function hasMinimumTrustLevel(level, required) {
379
+ return TRUST_ORDER.indexOf(level) >= TRUST_ORDER.indexOf(required);
380
+ }
381
+ export function requireMinimumTrustLevel(identity, required) {
382
+ const current = identity.trust_level ?? 'contributor';
383
+ if (!hasMinimumTrustLevel(current, required)) {
384
+ throw new AgentTrustError(`Insufficient trust: agent '${identity.agent_name}' has level '${current}', '${required}' required.`, {
385
+ agent_id: identity.agent_id,
386
+ agent_name: identity.agent_name,
387
+ current_level: current,
388
+ required_level: required,
389
+ });
390
+ }
391
+ }
392
+ export function setAgentTrustLevel(agentNameOrId, level, cwd) {
393
+ const agent = findAgentIdentityByName(agentNameOrId, cwd) ?? findAgentIdentityById(agentNameOrId, cwd);
394
+ if (!agent) {
395
+ throw new Error(`Agent '${agentNameOrId}' not found. Register first with \`brainclaw register-agent\`.`);
396
+ }
397
+ const updated = { ...agent, trust_level: level };
398
+ saveAgentIdentity(updated, cwd);
399
+ return updated;
400
+ }
401
+ export function getAgentTrustLevel(agentNameOrId, cwd) {
402
+ const byName = findAgentIdentityByName(agentNameOrId, cwd);
403
+ if (byName)
404
+ return byName.trust_level ?? 'contributor';
405
+ const byId = findAgentIdentityById(agentNameOrId, cwd);
406
+ return byId?.trust_level ?? 'contributor';
407
+ }
408
+ export function agentCanWriteDirect(agentNameOrId, cwd) {
409
+ if (!agentNameOrId)
410
+ return false;
411
+ const level = getAgentTrustLevel(agentNameOrId, cwd);
412
+ return level === 'trusted' || level === 'curator';
413
+ }
414
+ export function agentCanCurate(agentNameOrId, cwd) {
415
+ if (!agentNameOrId)
416
+ return false;
417
+ const level = getAgentTrustLevel(agentNameOrId, cwd);
418
+ return level === 'curator';
419
+ }
420
+ //# sourceMappingURL=agent-registry.js.map
@@ -0,0 +1,140 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ /**
5
+ * Detects the AI coding agent running in the current environment by inspecting
6
+ * environment variables and well-known config paths. Returns the first confident
7
+ * match, or undefined if no agent is detected.
8
+ *
9
+ * Detection order (highest confidence first):
10
+ * 1. BRAINCLAW_AGENT env var (explicit override)
11
+ * 2. GitHub Copilot (VSCODE_GIT_IPC_HANDLE or VSCODE_INJECTION, then GITHUB_COPILOT_*)
12
+ * 3. Claude Code (CLAUDE_CODE_VERSION or ANTHROPIC_AI_PRODUCT)
13
+ * 4. Cursor (CURSOR_TRACE_ID or CURSOR_*)
14
+ * 5. Windsurf (WINDSURF_*)
15
+ * 6. Cline (CLINE_*)
16
+ * 7. Codex CLI (~/.codex/ directory exists)
17
+ * 8. OpenCode (OPENCODE_* env or ~/.config/opencode/)
18
+ * 9. Antigravity / Gemini CLI (ANTIGRAVITY_* env or ~/.gemini/antigravity/)
19
+ * 10. Continue (CONTINUE_*)
20
+ * 11. Roo Code (ROO_*)
21
+ */
22
+ export function detectAiAgent(env = process.env, homeDir = os.homedir()) {
23
+ // Explicit override
24
+ if (env.BRAINCLAW_AGENT?.trim()) {
25
+ return {
26
+ name: env.BRAINCLAW_AGENT.trim(),
27
+ kind: 'agent',
28
+ trust_level: 'trusted',
29
+ detection_source: 'BRAINCLAW_AGENT env var',
30
+ };
31
+ }
32
+ // GitHub Copilot — VS Code extension or Copilot Chat
33
+ if (env.GITHUB_COPILOT_TOKEN ||
34
+ env.GITHUB_COPILOT_PRODUCT ||
35
+ (env.VSCODE_GIT_IPC_HANDLE && (env.AGENT_NAME?.toLowerCase().includes('copilot') || env.GH_COPILOT_AGENT))) {
36
+ return {
37
+ name: 'github-copilot',
38
+ kind: 'agent',
39
+ trust_level: 'trusted',
40
+ detection_source: 'GITHUB_COPILOT_* env var',
41
+ };
42
+ }
43
+ // Claude Code (Anthropic's CLI coding agent)
44
+ if (env.CLAUDE_CODE_VERSION || env.ANTHROPIC_AI_PRODUCT === 'claude-code') {
45
+ return {
46
+ name: 'claude-code',
47
+ kind: 'agent',
48
+ trust_level: 'trusted',
49
+ detection_source: 'CLAUDE_CODE_VERSION env var',
50
+ };
51
+ }
52
+ // Cursor IDE
53
+ if (env.CURSOR_TRACE_ID || env.CURSOR_SESSION_ID || env.CURSOR_CHANNEL) {
54
+ return {
55
+ name: 'cursor',
56
+ kind: 'agent',
57
+ trust_level: 'trusted',
58
+ detection_source: 'CURSOR_* env var',
59
+ };
60
+ }
61
+ // Windsurf
62
+ if (env.WINDSURF_SESSION_ID || env.WINDSURF_AGENT) {
63
+ return {
64
+ name: 'windsurf',
65
+ kind: 'agent',
66
+ trust_level: 'trusted',
67
+ detection_source: 'WINDSURF_* env var',
68
+ };
69
+ }
70
+ // Cline (VS Code extension)
71
+ if (env.CLINE_AGENT || env.CLINE_SESSION_ID) {
72
+ return {
73
+ name: 'cline',
74
+ kind: 'agent',
75
+ trust_level: 'trusted',
76
+ detection_source: 'CLINE_* env var',
77
+ };
78
+ }
79
+ // OpenAI Codex CLI (~/.codex/ presence)
80
+ if (fs.existsSync(path.join(homeDir, '.codex'))) {
81
+ return {
82
+ name: 'codex',
83
+ kind: 'agent',
84
+ trust_level: 'trusted',
85
+ detection_source: '~/.codex directory',
86
+ };
87
+ }
88
+ // OpenCode
89
+ if (env.OPENCODE_SESSION_ID || env.OPENCODE_AGENT || fs.existsSync(path.join(homeDir, '.config', 'opencode'))) {
90
+ return {
91
+ name: 'opencode',
92
+ kind: 'agent',
93
+ trust_level: 'trusted',
94
+ detection_source: env.OPENCODE_SESSION_ID || env.OPENCODE_AGENT ? 'OPENCODE_* env var' : '~/.config/opencode directory',
95
+ };
96
+ }
97
+ // Antigravity (Google Gemini CLI)
98
+ if (env.ANTIGRAVITY_SESSION_ID || env.ANTIGRAVITY_AGENT || fs.existsSync(path.join(homeDir, '.gemini', 'antigravity'))) {
99
+ return {
100
+ name: 'antigravity',
101
+ kind: 'agent',
102
+ trust_level: 'trusted',
103
+ detection_source: env.ANTIGRAVITY_SESSION_ID || env.ANTIGRAVITY_AGENT ? 'ANTIGRAVITY_* env var' : '~/.gemini/antigravity directory',
104
+ };
105
+ }
106
+ // Continue.dev
107
+ if (env.CONTINUE_AGENT || env.CONTINUE_SESSION_ID) {
108
+ return {
109
+ name: 'continue',
110
+ kind: 'agent',
111
+ trust_level: 'trusted',
112
+ detection_source: 'CONTINUE_* env var',
113
+ };
114
+ }
115
+ // Roo Code
116
+ if (env.ROO_AGENT || env.ROO_SESSION_ID) {
117
+ return {
118
+ name: 'roo',
119
+ kind: 'agent',
120
+ trust_level: 'trusted',
121
+ detection_source: 'ROO_* env var',
122
+ };
123
+ }
124
+ return undefined;
125
+ }
126
+ /**
127
+ * Detects whether brainclaw is running inside a WSL (Windows Subsystem for Linux)
128
+ * environment. Useful to warn users that the install is WSL-local and not
129
+ * accessible from a Windows terminal.
130
+ */
131
+ export function detectWslEnvironment(env = process.env) {
132
+ if (env.WSL_DISTRO_NAME) {
133
+ return { isWsl: true, distro: env.WSL_DISTRO_NAME, detection_source: 'WSL_DISTRO_NAME env var' };
134
+ }
135
+ if (env.WSL_INTEROP) {
136
+ return { isWsl: true, distro: 'unknown', detection_source: 'WSL_INTEROP env var' };
137
+ }
138
+ return undefined;
139
+ }
140
+ //# sourceMappingURL=ai-agent-detection.js.map
@@ -0,0 +1,85 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { memoryDir } from './io.js';
4
+ import { nowISO } from './ids.js';
5
+ import { logger } from './logger.js';
6
+ import { appendEvent } from './event-log.js';
7
+ const AUDIT_LOG_FILE = 'audit.log';
8
+ /** Map audit actions to event-log actions (subset that overlaps) */
9
+ const AUDIT_TO_EVENT_ACTION = {
10
+ create: 'create',
11
+ update: 'update',
12
+ delete: 'delete',
13
+ accept: 'accept',
14
+ reject: 'reject',
15
+ claim: 'claim',
16
+ release_claim: 'release_claim',
17
+ session_start: 'session_start',
18
+ session_end: 'session_end',
19
+ rollback: 'rollback',
20
+ };
21
+ function auditLogPath(cwd) {
22
+ return path.join(memoryDir(cwd), AUDIT_LOG_FILE);
23
+ }
24
+ export function appendAuditEntry(entry, cwd) {
25
+ try {
26
+ const full = {
27
+ timestamp: nowISO(),
28
+ actor: entry.actor,
29
+ action: entry.action,
30
+ item_id: entry.item_id,
31
+ item_type: entry.item_type,
32
+ before: entry.before,
33
+ after: entry.after,
34
+ actor_id: entry.actor_id,
35
+ reason: entry.reason,
36
+ };
37
+ // Remove undefined fields for compactness
38
+ const line = JSON.stringify(Object.fromEntries(Object.entries(full).filter(([, v]) => v !== undefined)));
39
+ fs.appendFileSync(auditLogPath(cwd), line + '\n', 'utf-8');
40
+ // Mirror to structured event log
41
+ const eventAction = AUDIT_TO_EVENT_ACTION[entry.action];
42
+ if (eventAction) {
43
+ appendEvent({
44
+ action: eventAction,
45
+ item_type: entry.item_type ?? 'state',
46
+ item_id: entry.item_id,
47
+ agent: entry.actor,
48
+ agent_id: entry.actor_id,
49
+ }, cwd);
50
+ }
51
+ }
52
+ catch (err) {
53
+ logger.debug('Failed to write audit log entry:', err);
54
+ }
55
+ }
56
+ export function readAuditLog(options = {}, cwd) {
57
+ const logPath = auditLogPath(cwd);
58
+ if (!fs.existsSync(logPath))
59
+ return [];
60
+ const lines = fs.readFileSync(logPath, 'utf-8').split('\n').filter(Boolean);
61
+ let entries = [];
62
+ for (const line of lines) {
63
+ try {
64
+ entries.push(JSON.parse(line));
65
+ }
66
+ catch (err) {
67
+ logger.debug('Skipping malformed audit log entry:', err);
68
+ }
69
+ }
70
+ if (options.since) {
71
+ entries = entries.filter(e => e.timestamp >= options.since);
72
+ }
73
+ if (options.actor) {
74
+ const a = options.actor.toLowerCase();
75
+ entries = entries.filter(e => e.actor.toLowerCase() === a || e.actor_id?.toLowerCase() === a);
76
+ }
77
+ if (options.action) {
78
+ entries = entries.filter(e => e.action === options.action);
79
+ }
80
+ if (options.itemId) {
81
+ entries = entries.filter(e => e.item_id === options.itemId);
82
+ }
83
+ return entries;
84
+ }
85
+ //# sourceMappingURL=audit.js.map