brainclaw 0.27.0 → 0.29.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 CHANGED
@@ -66,6 +66,8 @@ If you are documenting or integrating an agent workflow, treat MCP as the primar
66
66
 
67
67
  brainclaw is designed to sit alongside the coding agents teams are already using, not behind a separate hosted control plane.
68
68
 
69
+ ### Code Agents
70
+
69
71
  | Logo | Agent | Integration | What brainclaw configures |
70
72
  |---|---|---|---|
71
73
  | [![Claude Code](https://img.shields.io/badge/Claude_Code-111111?logo=anthropic&logoColor=white)](https://github.com/anthropics/claude-code) | **[Claude Code](https://github.com/anthropics/claude-code)** | Full | MCP + CLAUDE.md + hooks + permissions + /brainclaw command |
@@ -78,10 +80,21 @@ brainclaw is designed to sit alongside the coding agents teams are already using
78
80
  | [![Continue](https://img.shields.io/badge/Continue-2563EB?logoColor=white)](https://github.com/continuedev/continue) | **[Continue](https://github.com/continuedev/continue)** | Standard | MCP + .continue/rules/ |
79
81
  | [![Gemini CLI](https://img.shields.io/badge/Gemini_CLI-1A73E8?logo=googlegemini&logoColor=white)](https://github.com/google-gemini/gemini-cli) | **[Antigravity / Gemini CLI](https://github.com/google-gemini/gemini-cli)** | Standard | MCP + GEMINI.md |
80
82
  | [![GitHub Copilot](https://img.shields.io/badge/GitHub_Copilot-181717?logo=githubcopilot&logoColor=white)](https://github.com/features/copilot) | **[GitHub Copilot](https://github.com/features/copilot)** | Limited | copilot-instructions.md + brainclaw-context skill |
81
- | [![OpenClaw](https://img.shields.io/badge/OpenClaw-FF6B35?logoColor=white)](https://github.com/openclaw/openclaw) | **[OpenClaw](https://github.com/openclaw/openclaw)** | Limited | brainclaw skill (SKILL.md) for structured project memory |
82
83
 
83
84
  **Full** = MCP + hooks + auto-approve (context injected every prompt). **Standard** = MCP + instruction file (agent must call tools explicitly). **Limited** = skill-based (agent calls brainclaw CLI via skill instructions).
84
85
 
86
+ ### Autonomous Agents
87
+
88
+ | Logo | Agent | Workflow | What brainclaw configures |
89
+ |---|---|---|---|
90
+ | [![OpenClaw](https://img.shields.io/badge/OpenClaw-FF6B35?logoColor=white)](https://github.com/openclaw/openclaw) | **[OpenClaw](https://github.com/openclaw/openclaw)** | Task-based | brainclaw skill (SKILL.md) for structured project memory |
91
+ | [![NanoClaw](https://img.shields.io/badge/NanoClaw-4A90D9?logoColor=white)](https://github.com/qwibitai/nanoclaw) | **[NanoClaw](https://github.com/qwibitai/nanoclaw)** | Task-based | brainclaw skill — messaging agent (WhatsApp, Telegram, Slack) |
92
+ | [![NemoClaw](https://img.shields.io/badge/NemoClaw-76B900?logo=nvidia&logoColor=white)](https://github.com/NVIDIA/NemoClaw) | **[NemoClaw](https://github.com/NVIDIA/NemoClaw)** | Task-based | brainclaw skill — NVIDIA enterprise agent stack |
93
+ | [![PicoClaw](https://img.shields.io/badge/PicoClaw-00ADD8?logo=go&logoColor=white)](https://github.com/sipeed/picoclaw) | **[PicoClaw](https://github.com/sipeed/picoclaw)** | Scheduled | brainclaw skill — edge/IoT agent (Go, <10MB RAM) |
94
+ | [![ZeroClaw](https://img.shields.io/badge/ZeroClaw-B7410E?logoColor=white)](https://github.com/zeroclaw-labs/zeroclaw) | **[ZeroClaw](https://github.com/zeroclaw-labs/zeroclaw)** | Task-based | brainclaw skill — ultra-lightweight Rust agent (20+ channels) |
95
+
96
+ Autonomous agents use brainclaw via CLI skills with `--profile compact` for short sessions and constrained resources.
97
+
85
98
  brainclaw is most effective today when one agent works at a time in a given checkout and the next agent resumes from shared context, claims, and handoffs.
86
99
 
87
100
  ---
package/dist/cli.js CHANGED
@@ -1157,6 +1157,17 @@ program
1157
1157
  cwd: globalOpts.cwd,
1158
1158
  });
1159
1159
  });
1160
+ program
1161
+ .command('who')
1162
+ .description('Show active agent sessions on this workspace')
1163
+ .option('--json', 'Output as JSON')
1164
+ .option('--all', 'Include stale sessions')
1165
+ .option('--gc', 'Remove stale sessions')
1166
+ .action(async (options) => {
1167
+ const globalOpts = program.opts();
1168
+ const { runWho } = await import('./commands/who.js');
1169
+ runWho({ json: options.json, all: options.all, gc: options.gc, cwd: globalOpts.cwd });
1170
+ });
1160
1171
  program.parseAsync(process.argv).catch((err) => {
1161
1172
  console.error(err);
1162
1173
  process.exit(1);
@@ -9,6 +9,7 @@ import { buildContext, renderContextMarkdown, renderContextPromptTemplate } from
9
9
  import { buildExecutionContext, renderExecutionContextSummary } from '../core/execution-context.js';
10
10
  import { checkBrainclawInstallableUpdate, renderBrainclawInstallableUpdateNotice } from '../core/brainclaw-version.js';
11
11
  import { loadConfig } from '../core/config.js';
12
+ import { loadAllSessions, gcStaleSessions } from '../core/identity.js';
12
13
  import { loadState, persistState, saveState } from '../core/state.js';
13
14
  import { memoryExists } from '../core/io.js';
14
15
  import { generateCandidateIdWithLabel, listArchivedCandidates, listCandidates, saveCandidate } from '../core/candidates.js';
@@ -307,6 +308,17 @@ export const MCP_READ_TOOLS = [
307
308
  },
308
309
  },
309
310
  },
311
+ {
312
+ name: 'bclaw_who',
313
+ description: 'List all active agent sessions on this workspace. Shows user, agent, active project, claims, and last activity for each session.',
314
+ inputSchema: {
315
+ type: 'object',
316
+ properties: {
317
+ all: { type: 'boolean', description: 'Include stale sessions (default: false).' },
318
+ gc: { type: 'boolean', description: 'Remove stale sessions and return count.' },
319
+ },
320
+ },
321
+ },
310
322
  ];
311
323
  const MCP_WRITE_TOOLS = [
312
324
  {
@@ -1767,6 +1779,42 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
1767
1779
  structuredContent: { agent: currentAgentName, conflicts, total: conflicts.length, schema_version: SCHEMA_VERSION },
1768
1780
  };
1769
1781
  }
1782
+ if (name === 'bclaw_who') {
1783
+ // loadAllSessions and gcStaleSessions imported at top of file
1784
+ const doGc = args.gc === true;
1785
+ const showAll = args.all === true;
1786
+ if (doGc) {
1787
+ const removed = gcStaleSessions(cwd);
1788
+ return {
1789
+ content: [{ type: 'text', text: `✔ Removed ${removed} stale session(s).` }],
1790
+ structuredContent: { gc: true, removed, schema_version: SCHEMA_VERSION },
1791
+ };
1792
+ }
1793
+ const allSessions = loadAllSessions(cwd);
1794
+ const ttlMs = 4 * 60 * 60 * 1000;
1795
+ const now = Date.now();
1796
+ const sessions = showAll
1797
+ ? allSessions
1798
+ : allSessions.filter((s) => (now - Date.parse(s.last_seen_at)) <= ttlMs);
1799
+ const activeClaims = listClaims(cwd).filter((c) => c.status === 'active');
1800
+ const output = sessions.map((s) => ({
1801
+ session_id: s.session_id,
1802
+ user: s.user ?? 'unknown',
1803
+ agent: s.agent,
1804
+ agent_id: s.agent_id,
1805
+ project: s.active_project?.name ?? s.active_project?.path ?? null,
1806
+ claims: activeClaims.filter((c) => c.agent_id === s.agent_id).length,
1807
+ last_seen_at: s.last_seen_at,
1808
+ stale: (now - Date.parse(s.last_seen_at)) > ttlMs,
1809
+ }));
1810
+ const lines = sessions.length === 0
1811
+ ? 'No active sessions.'
1812
+ : output.map((s) => `${s.user} | ${s.agent} | ${s.project ?? '(root)'} | ${s.claims} claims | ${s.stale ? 'stale' : 'active'}`).join('\n');
1813
+ return {
1814
+ content: [{ type: 'text', text: lines }],
1815
+ structuredContent: { sessions: output, total: output.length, schema_version: SCHEMA_VERSION },
1816
+ };
1817
+ }
1770
1818
  if (name === 'bclaw_doctor') {
1771
1819
  // Capture doctor JSON output by redirecting console.log
1772
1820
  const captured = [];
@@ -2188,6 +2236,7 @@ export async function executeMcpToolCall(payload) {
2188
2236
  id: claimId,
2189
2237
  agent: identity.agent,
2190
2238
  agent_id: identity.agent_id,
2239
+ user: process.env.USER || process.env.USERNAME || undefined,
2191
2240
  project_id: identity.project_id,
2192
2241
  host_id: identity.host_id,
2193
2242
  session_id: identity.session_id,
@@ -1,5 +1,6 @@
1
1
  import path from 'node:path';
2
2
  import { loadActiveProject, saveActiveProject, clearActiveProject } from '../core/active-project.js';
3
+ import { loadCurrentSession, saveCurrentSession } from '../core/identity.js';
3
4
  import { memoryExists } from '../core/io.js';
4
5
  import { resolveProjectRef, resolveWorkspaceRoot } from '../core/store-resolution.js';
5
6
  import { scanNestedBrainclawProjects } from '../core/workspace-projects.js';
@@ -18,6 +19,11 @@ export function runSwitch(projectRef, options = {}) {
18
19
  }
19
20
  // --clear: remove active project
20
21
  if (options.clear) {
22
+ const session = loadCurrentSession(cwd);
23
+ if (session?.active_project) {
24
+ const { active_project: _removed, ...rest } = session;
25
+ saveCurrentSession(rest, cwd);
26
+ }
21
27
  clearActiveProject(wsRoot);
22
28
  if (options.json) {
23
29
  console.log(JSON.stringify({ cleared: true }));
@@ -47,18 +53,35 @@ export function runSwitch(projectRef, options = {}) {
47
53
  catch {
48
54
  // name is optional
49
55
  }
50
- saveActiveProject(wsRoot, {
51
- path: resolved,
52
- name: projectName,
53
- switched_at: new Date().toISOString(),
54
- switched_by: process.env.BRAINCLAW_AGENT_NAME ?? process.env.USER ?? 'unknown',
55
- });
56
+ const now = new Date().toISOString();
57
+ const session = loadCurrentSession(cwd);
58
+ const scopedToSession = options.session ?? !!session;
59
+ let scope;
60
+ if (scopedToSession && session) {
61
+ // Write to session state — only this agent sees this switch
62
+ saveCurrentSession({
63
+ ...session,
64
+ active_project: { path: resolved, name: projectName, switched_at: now },
65
+ }, cwd);
66
+ scope = 'session';
67
+ }
68
+ else {
69
+ // Fall back to global active-project.json
70
+ saveActiveProject(wsRoot, {
71
+ path: resolved,
72
+ name: projectName,
73
+ switched_at: now,
74
+ switched_by: process.env.BRAINCLAW_AGENT_NAME ?? process.env.USER ?? 'unknown',
75
+ });
76
+ scope = 'global';
77
+ }
56
78
  if (options.json) {
57
- console.log(JSON.stringify({ switched: true, path: resolved, name: projectName }));
79
+ console.log(JSON.stringify({ switched: true, path: resolved, name: projectName, scope }));
58
80
  }
59
81
  else {
60
82
  const rel = path.relative(wsRoot, resolved) || '.';
61
- console.log(`✔ Switched to ${projectName ? `"${projectName}" (${rel})` : rel}`);
83
+ const scopeHint = scope === 'session' ? ' (session-scoped)' : '';
84
+ console.log(`✔ Switched to ${projectName ? `"${projectName}" (${rel})` : rel}${scopeHint}`);
62
85
  }
63
86
  }
64
87
  function showCurrent(wsRoot, json) {
@@ -0,0 +1,96 @@
1
+ import { loadAllSessions, gcStaleSessions } from '../core/identity.js';
2
+ import { listClaims } from '../core/claims.js';
3
+ export function runWho(options = {}) {
4
+ const cwd = options.cwd ?? process.cwd();
5
+ if (options.gc) {
6
+ const removed = gcStaleSessions(cwd);
7
+ if (options.json) {
8
+ console.log(JSON.stringify({ gc: true, removed }));
9
+ }
10
+ else {
11
+ console.log(`✔ Removed ${removed} stale session(s).`);
12
+ }
13
+ return;
14
+ }
15
+ const allSessions = loadAllSessions(cwd);
16
+ const ttlMs = 4 * 60 * 60 * 1000; // 4h default
17
+ const now = Date.now();
18
+ const sessions = options.all
19
+ ? allSessions
20
+ : allSessions.filter(s => (now - Date.parse(s.last_seen_at)) <= ttlMs);
21
+ const activeClaims = listClaims(cwd).filter(c => c.status === 'active');
22
+ const enriched = sessions.map(s => {
23
+ const age = now - Date.parse(s.last_seen_at);
24
+ const stale = age > ttlMs;
25
+ const dead = s.pid ? !isPidAlive(s.pid) : false;
26
+ const status = dead ? 'dead' : stale ? 'stale' : 'active';
27
+ return {
28
+ session_id: s.session_id,
29
+ user: s.user ?? 'unknown',
30
+ agent: s.agent,
31
+ agent_id: s.agent_id,
32
+ host_id: s.host_id,
33
+ project: s.active_project?.name ?? s.active_project?.path ?? null,
34
+ claims: activeClaims.filter(c => c.agent_id === s.agent_id).length,
35
+ started_at: s.started_at,
36
+ last_seen_at: s.last_seen_at,
37
+ status,
38
+ pid: s.pid,
39
+ };
40
+ });
41
+ if (options.json) {
42
+ console.log(JSON.stringify({ sessions: enriched, total: enriched.length }, null, 2));
43
+ return;
44
+ }
45
+ if (enriched.length === 0) {
46
+ console.log('No active sessions.');
47
+ return;
48
+ }
49
+ // Table header
50
+ const header = ['USER', 'AGENT', 'PROJECT', 'CLAIMS', 'STATUS', 'LAST SEEN'];
51
+ const rows = [];
52
+ for (const s of enriched) {
53
+ const age = now - Date.parse(s.last_seen_at);
54
+ const project = s.project ?? '(workspace root)';
55
+ rows.push([
56
+ s.user,
57
+ s.agent,
58
+ project.length > 25 ? project.slice(0, 22) + '...' : project,
59
+ String(s.claims),
60
+ s.status,
61
+ formatAge(age),
62
+ ]);
63
+ }
64
+ // Calculate column widths
65
+ const widths = header.map((h, i) => Math.max(h.length, ...rows.map(r => r[i].length)));
66
+ console.log('Active sessions:\n');
67
+ console.log(' ' + header.map((h, i) => h.padEnd(widths[i])).join(' '));
68
+ console.log(' ' + widths.map(w => '─'.repeat(w)).join(' '));
69
+ for (const row of rows) {
70
+ console.log(' ' + row.map((cell, i) => cell.padEnd(widths[i])).join(' '));
71
+ }
72
+ console.log(`\n${enriched.length} session(s).`);
73
+ }
74
+ function isPidAlive(pid) {
75
+ try {
76
+ // process.kill(pid, 0) throws if process doesn't exist
77
+ process.kill(pid, 0);
78
+ return true;
79
+ }
80
+ catch {
81
+ return false;
82
+ }
83
+ }
84
+ function formatAge(ms) {
85
+ const minutes = Math.floor(ms / 60_000);
86
+ if (minutes < 1)
87
+ return 'just now';
88
+ if (minutes < 60)
89
+ return `${minutes}min ago`;
90
+ const hours = Math.floor(minutes / 60);
91
+ if (hours < 24)
92
+ return `${hours}h ago`;
93
+ const days = Math.floor(hours / 24);
94
+ return `${days}d ago`;
95
+ }
96
+ //# sourceMappingURL=who.js.map
@@ -9,137 +9,82 @@
9
9
  * C (limited) — no MCP → rich static content (plans, traps, decisions)
10
10
  */
11
11
  const PROFILES = {
12
+ // --- Code agents (interactive, IDE-driven) ---
12
13
  'claude-code': {
13
- name: 'claude-code',
14
- hasMcp: true,
15
- hasHooks: true,
16
- hasAutoApprove: true,
17
- hasSkills: true,
18
- hasRules: true,
19
- instructionFile: 'CLAUDE.md',
20
- sharedInstructionFile: true,
21
- mcpConfigScope: 'both',
22
- templateTier: 'A',
14
+ name: 'claude-code', category: 'code-agent', workflowModel: 'interactive',
15
+ hasMcp: true, hasHooks: true, hasAutoApprove: true, hasSkills: true, hasRules: true,
16
+ instructionFile: 'CLAUDE.md', sharedInstructionFile: true, mcpConfigScope: 'both', templateTier: 'A',
23
17
  },
24
18
  cursor: {
25
- name: 'cursor',
26
- hasMcp: true,
27
- hasHooks: false,
28
- hasAutoApprove: false,
29
- hasSkills: false,
30
- hasRules: true,
31
- instructionFile: '.cursor/rules/brainclaw.md',
32
- sharedInstructionFile: false,
33
- mcpConfigScope: 'machine',
34
- templateTier: 'B',
19
+ name: 'cursor', category: 'code-agent', workflowModel: 'interactive',
20
+ hasMcp: true, hasHooks: false, hasAutoApprove: false, hasSkills: false, hasRules: true,
21
+ instructionFile: '.cursor/rules/brainclaw.md', sharedInstructionFile: false, mcpConfigScope: 'machine', templateTier: 'B',
35
22
  },
36
23
  windsurf: {
37
- name: 'windsurf',
38
- hasMcp: true,
39
- hasHooks: false,
40
- hasAutoApprove: false,
41
- hasSkills: false,
42
- hasRules: true,
43
- instructionFile: '.windsurfrules',
44
- sharedInstructionFile: true,
45
- mcpConfigScope: 'machine',
46
- templateTier: 'B',
24
+ name: 'windsurf', category: 'code-agent', workflowModel: 'interactive',
25
+ hasMcp: true, hasHooks: false, hasAutoApprove: false, hasSkills: false, hasRules: true,
26
+ instructionFile: '.windsurfrules', sharedInstructionFile: true, mcpConfigScope: 'machine', templateTier: 'B',
47
27
  },
48
28
  cline: {
49
- name: 'cline',
50
- hasMcp: true,
51
- hasHooks: false,
52
- hasAutoApprove: true,
53
- hasSkills: false,
54
- hasRules: true,
55
- instructionFile: '.clinerules/brainclaw.md',
56
- sharedInstructionFile: false,
57
- mcpConfigScope: 'project',
58
- templateTier: 'B',
29
+ name: 'cline', category: 'code-agent', workflowModel: 'interactive',
30
+ hasMcp: true, hasHooks: false, hasAutoApprove: true, hasSkills: false, hasRules: true,
31
+ instructionFile: '.clinerules/brainclaw.md', sharedInstructionFile: false, mcpConfigScope: 'project', templateTier: 'B',
59
32
  },
60
33
  roo: {
61
- name: 'roo',
62
- hasMcp: true,
63
- hasHooks: false,
64
- hasAutoApprove: true,
65
- hasSkills: false,
66
- hasRules: true,
67
- instructionFile: '.roo/rules/brainclaw.md',
68
- sharedInstructionFile: false,
69
- mcpConfigScope: 'project',
70
- templateTier: 'B',
34
+ name: 'roo', category: 'code-agent', workflowModel: 'interactive',
35
+ hasMcp: true, hasHooks: false, hasAutoApprove: true, hasSkills: false, hasRules: true,
36
+ instructionFile: '.roo/rules/brainclaw.md', sharedInstructionFile: false, mcpConfigScope: 'project', templateTier: 'B',
71
37
  },
72
38
  continue: {
73
- name: 'continue',
74
- hasMcp: true,
75
- hasHooks: false,
76
- hasAutoApprove: false,
77
- hasSkills: false,
78
- hasRules: true,
79
- instructionFile: '.continue/rules/brainclaw.md',
80
- sharedInstructionFile: false,
81
- mcpConfigScope: 'both',
82
- templateTier: 'B',
39
+ name: 'continue', category: 'code-agent', workflowModel: 'interactive',
40
+ hasMcp: true, hasHooks: false, hasAutoApprove: false, hasSkills: false, hasRules: true,
41
+ instructionFile: '.continue/rules/brainclaw.md', sharedInstructionFile: false, mcpConfigScope: 'both', templateTier: 'B',
83
42
  },
84
43
  opencode: {
85
- name: 'opencode',
86
- hasMcp: true,
87
- hasHooks: false,
88
- hasAutoApprove: false,
89
- hasSkills: false,
90
- hasRules: true,
91
- instructionFile: 'AGENTS.md',
92
- sharedInstructionFile: true,
93
- mcpConfigScope: 'project',
94
- templateTier: 'B',
44
+ name: 'opencode', category: 'code-agent', workflowModel: 'interactive',
45
+ hasMcp: true, hasHooks: false, hasAutoApprove: false, hasSkills: false, hasRules: true,
46
+ instructionFile: 'AGENTS.md', sharedInstructionFile: true, mcpConfigScope: 'project', templateTier: 'B',
95
47
  },
96
48
  codex: {
97
- name: 'codex',
98
- hasMcp: true,
99
- hasHooks: false,
100
- hasAutoApprove: false,
101
- hasSkills: false,
102
- hasRules: true,
103
- instructionFile: 'AGENTS.md',
104
- sharedInstructionFile: true,
105
- mcpConfigScope: 'machine',
106
- templateTier: 'B',
49
+ name: 'codex', category: 'code-agent', workflowModel: 'task-based',
50
+ hasMcp: true, hasHooks: false, hasAutoApprove: false, hasSkills: false, hasRules: true,
51
+ instructionFile: 'AGENTS.md', sharedInstructionFile: true, mcpConfigScope: 'machine', templateTier: 'B',
107
52
  },
108
53
  antigravity: {
109
- name: 'antigravity',
110
- hasMcp: true,
111
- hasHooks: false,
112
- hasAutoApprove: false,
113
- hasSkills: false,
114
- hasRules: true,
115
- instructionFile: 'GEMINI.md',
116
- sharedInstructionFile: true,
117
- mcpConfigScope: 'machine',
118
- templateTier: 'B',
54
+ name: 'antigravity', category: 'code-agent', workflowModel: 'interactive',
55
+ hasMcp: true, hasHooks: false, hasAutoApprove: false, hasSkills: false, hasRules: true,
56
+ instructionFile: 'GEMINI.md', sharedInstructionFile: true, mcpConfigScope: 'machine', templateTier: 'B',
119
57
  },
120
58
  'github-copilot': {
121
- name: 'github-copilot',
122
- hasMcp: false,
123
- hasHooks: false,
124
- hasAutoApprove: false,
125
- hasSkills: true,
126
- hasRules: true,
127
- instructionFile: '.github/copilot-instructions.md',
128
- sharedInstructionFile: true,
129
- mcpConfigScope: 'none',
130
- templateTier: 'C',
59
+ name: 'github-copilot', category: 'code-agent', workflowModel: 'interactive',
60
+ hasMcp: false, hasHooks: false, hasAutoApprove: false, hasSkills: true, hasRules: true,
61
+ instructionFile: '.github/copilot-instructions.md', sharedInstructionFile: true, mcpConfigScope: 'none', templateTier: 'C',
131
62
  },
63
+ // --- Autonomous agents (headless, task-based or scheduled) ---
132
64
  openclaw: {
133
- name: 'openclaw',
134
- hasMcp: false,
135
- hasHooks: false,
136
- hasAutoApprove: false,
137
- hasSkills: true,
138
- hasRules: false,
139
- instructionFile: 'skills/openclaw/SKILL.md',
140
- sharedInstructionFile: false,
141
- mcpConfigScope: 'none',
142
- templateTier: 'C',
65
+ name: 'openclaw', category: 'autonomous-agent', workflowModel: 'task-based',
66
+ hasMcp: false, hasHooks: false, hasAutoApprove: false, hasSkills: true, hasRules: false,
67
+ instructionFile: 'skills/openclaw/SKILL.md', sharedInstructionFile: false, mcpConfigScope: 'none', templateTier: 'C',
68
+ },
69
+ nanoclaw: {
70
+ name: 'nanoclaw', category: 'autonomous-agent', workflowModel: 'task-based',
71
+ hasMcp: false, hasHooks: false, hasAutoApprove: false, hasSkills: true, hasRules: false,
72
+ instructionFile: 'skills/nanoclaw/SKILL.md', sharedInstructionFile: false, mcpConfigScope: 'none', templateTier: 'C',
73
+ },
74
+ nemoclaw: {
75
+ name: 'nemoclaw', category: 'autonomous-agent', workflowModel: 'task-based',
76
+ hasMcp: false, hasHooks: false, hasAutoApprove: false, hasSkills: true, hasRules: false,
77
+ instructionFile: 'skills/nemoclaw/SKILL.md', sharedInstructionFile: false, mcpConfigScope: 'none', templateTier: 'C',
78
+ },
79
+ picoclaw: {
80
+ name: 'picoclaw', category: 'autonomous-agent', workflowModel: 'scheduled',
81
+ hasMcp: false, hasHooks: false, hasAutoApprove: false, hasSkills: true, hasRules: false,
82
+ instructionFile: 'skills/picoclaw/SKILL.md', sharedInstructionFile: false, mcpConfigScope: 'none', templateTier: 'C',
83
+ },
84
+ zeroclaw: {
85
+ name: 'zeroclaw', category: 'autonomous-agent', workflowModel: 'task-based',
86
+ hasMcp: false, hasHooks: false, hasAutoApprove: false, hasSkills: true, hasRules: false,
87
+ instructionFile: 'skills/zeroclaw/SKILL.md', sharedInstructionFile: false, mcpConfigScope: 'none', templateTier: 'C',
143
88
  },
144
89
  };
145
90
  /**
@@ -12,6 +12,11 @@ const SUPPORTED_AGENT_INTEGRATION_NAMES = new Set([
12
12
  'antigravity',
13
13
  'continue',
14
14
  'roo',
15
+ 'openclaw',
16
+ 'nanoclaw',
17
+ 'nemoclaw',
18
+ 'picoclaw',
19
+ 'zeroclaw',
15
20
  ]);
16
21
  const DEFAULT_SURFACES = {
17
22
  'github-copilot': [
@@ -60,6 +65,18 @@ const DEFAULT_SURFACES = {
60
65
  'openclaw': [
61
66
  { kind: 'skill', location: 'machine', path: '.openclaw/workspace/skills/brainclaw/SKILL.md' },
62
67
  ],
68
+ 'nanoclaw': [
69
+ { kind: 'skill', location: 'workspace', path: 'skills/nanoclaw/SKILL.md' },
70
+ ],
71
+ 'nemoclaw': [
72
+ { kind: 'skill', location: 'workspace', path: 'skills/nemoclaw/SKILL.md' },
73
+ ],
74
+ 'picoclaw': [
75
+ { kind: 'skill', location: 'workspace', path: 'skills/picoclaw/SKILL.md' },
76
+ ],
77
+ 'zeroclaw': [
78
+ { kind: 'skill', location: 'workspace', path: 'skills/zeroclaw/SKILL.md' },
79
+ ],
63
80
  };
64
81
  function mergeSurfaces(current, next) {
65
82
  const merged = [...current];
@@ -8,7 +8,7 @@ import { resolveEntityDir } from './io.js';
8
8
  import { mutate } from './mutation-pipeline.js';
9
9
  import { BootstrapApplicationReceiptSchema, BootstrapInterviewAnswerSchema, BootstrapInterviewPlanSchema, BootstrapInterviewQuestionSchema, BootstrapImportPlanDocumentSchema, BootstrapProfileDocumentSchema, BootstrapSuggestionDocumentSchema, MemorySeedDocumentSchema, } from './schema.js';
10
10
  import { loadVersionedJsonFile, saveVersionedJsonFile } from './migration.js';
11
- import { analyzeRepository } from './repo-analysis.js';
11
+ import { analyzeRepository, findNestedAgentsFiles } from './repo-analysis.js';
12
12
  import { buildExecutionContext, compactExecutionContext } from './execution-context.js';
13
13
  import { buildAgentToolingContext } from './agent-context.js';
14
14
  import { createInstruction, loadInstructions, saveInstruction } from './instructions.js';
@@ -101,6 +101,7 @@ export function runBootstrapProfile(options = {}) {
101
101
  importPlan,
102
102
  lastApplication,
103
103
  reusedProfile: false,
104
+ subProjects: artifacts.subProjects,
104
105
  };
105
106
  }
106
107
  export function listBootstrapSeeds(cwd) {
@@ -183,6 +184,18 @@ export function renderBootstrapSummary(result) {
183
184
  if (result.lastApplication && !result.lastApplication.uninstalled_at) {
184
185
  lines.push(`Last bootstrap import: ${result.lastApplication.managed_artifacts.length} managed artifact(s) from ${result.lastApplication.applied_at}`);
185
186
  }
187
+ if (result.subProjects && result.subProjects.length > 0) {
188
+ lines.push('');
189
+ lines.push(`Sub-projects discovered (${result.subProjects.length}):`);
190
+ for (const sp of result.subProjects.slice(0, 20)) {
191
+ lines.push(` ${sp}`);
192
+ }
193
+ if (result.subProjects.length > 20) {
194
+ lines.push(` ... and ${result.subProjects.length - 20} more`);
195
+ }
196
+ lines.push('');
197
+ lines.push('Use: brainclaw bootstrap --for <sub-project-path> --refresh');
198
+ }
186
199
  if (result.importPlan.suggestions.length > 0) {
187
200
  lines.push('');
188
201
  lines.push('Import proposal:');
@@ -231,14 +244,19 @@ export function renderBootstrapInterview(result, audience = 'any') {
231
244
  function buildBootstrapArtifacts(input) {
232
245
  const sourcesScanned = [];
233
246
  const seeds = [];
234
- const workspace = classifyWorkspace(input.cwd);
235
- const nativeInstructionFiles = discoverNativeInstructionFiles(input.cwd);
236
- const readmePath = findFirstExisting(input.cwd, README_CANDIDATES);
247
+ // When target is an absolute directory path, use it as the scan root
248
+ // so that instruction files, README, and AGENTS.md are discovered from
249
+ // the target scope — not from the workspace root. This fixes bootstrap
250
+ // returning wrong signals for monorepo sub-projects.
251
+ const scanRoot = resolveBootstrapScanRoot(input.cwd, input.target);
252
+ const workspace = classifyWorkspace(scanRoot);
253
+ const nativeInstructionFiles = discoverNativeInstructionFiles(scanRoot);
254
+ const readmePath = findFirstExisting(scanRoot, README_CANDIDATES);
237
255
  if (readmePath) {
238
256
  sourcesScanned.push('README');
239
257
  seeds.push(...extractReadmeSeeds(readmePath, input.target));
240
258
  }
241
- const agentsPath = path.join(input.cwd, 'AGENTS.md');
259
+ const agentsPath = path.join(scanRoot, 'AGENTS.md');
242
260
  const agentsPresent = fs.existsSync(agentsPath);
243
261
  if (agentsPresent) {
244
262
  sourcesScanned.push('AGENTS.md');
@@ -246,9 +264,9 @@ function buildBootstrapArtifacts(input) {
246
264
  }
247
265
  if (nativeInstructionFiles.length > 0) {
248
266
  sourcesScanned.push('native_instructions');
249
- seeds.push(...extractNativeInstructionSeeds(nativeInstructionFiles.map((relativePath) => path.join(input.cwd, relativePath)), input.cwd, input.target));
267
+ seeds.push(...extractNativeInstructionSeeds(nativeInstructionFiles.map((relativePath) => path.join(scanRoot, relativePath)), scanRoot, input.target));
250
268
  }
251
- const manifestResult = extractManifestSeeds(input.cwd, input.target);
269
+ const manifestResult = extractManifestSeeds(scanRoot, input.target);
252
270
  if (manifestResult.seeds.length > 0) {
253
271
  sourcesScanned.push(...manifestResult.sources);
254
272
  seeds.push(...manifestResult.seeds);
@@ -265,16 +283,16 @@ function buildBootstrapArtifacts(input) {
265
283
  sourcesScanned.push('local_mcp');
266
284
  seeds.push(...extractMcpSeeds(agentTooling.mcp_servers, input.target));
267
285
  }
268
- const repoAnalysis = analyzeRepository(input.cwd);
286
+ const repoAnalysis = analyzeRepository(scanRoot);
269
287
  sourcesScanned.push('repo-analysis');
270
288
  seeds.push(...extractRepoAnalysisSeeds(repoAnalysis, input.target));
271
289
  // Additional brownfield sources (step 12)
272
- const additionalSeeds = extractAdditionalBrownfieldSeeds(input.cwd, input.target);
290
+ const additionalSeeds = extractAdditionalBrownfieldSeeds(scanRoot, input.target);
273
291
  if (additionalSeeds.seeds.length > 0) {
274
292
  sourcesScanned.push(...additionalSeeds.sources);
275
293
  seeds.push(...additionalSeeds.seeds);
276
294
  }
277
- const gitProbe = probeGit(input.cwd, input.target);
295
+ const gitProbe = probeGit(scanRoot, input.target);
278
296
  if (gitProbe.available) {
279
297
  sourcesScanned.push('git');
280
298
  seeds.push(...gitProbe.hotspotSeeds);
@@ -342,6 +360,13 @@ function buildBootstrapArtifacts(input) {
342
360
  schema_version: DERIVED_SCHEMA_VERSION,
343
361
  })),
344
362
  importPlan,
363
+ // For multi-project workspaces without a target, list discovered sub-projects
364
+ subProjects: (!input.target && repoAnalysis.recommendedMode === 'multi-project')
365
+ ? findNestedAgentsFiles(scanRoot, 8)
366
+ .filter((p) => p !== 'AGENTS.md') // exclude root AGENTS.md
367
+ .map((p) => path.dirname(p))
368
+ .filter((d) => d !== '.')
369
+ : undefined,
345
370
  };
346
371
  }
347
372
  function extractReadmeSeeds(filepath, target) {
@@ -846,6 +871,9 @@ function buildSummary(input) {
846
871
  parts.push(`Onboarding mode: ${input.onboardingMode}.`);
847
872
  parts.push(`Confidence: ${input.confidence}.`);
848
873
  parts.push(`Repository mode looks ${input.repoAnalysis.recommendedMode}.`);
874
+ if (input.repoAnalysis.recommendedMode === 'multi-project' && !input.target) {
875
+ parts.push('This is a multi-project workspace — use --for <path> to bootstrap a specific sub-project.');
876
+ }
849
877
  if (input.agentsPresent) {
850
878
  parts.push('AGENTS.md detected and summarized.');
851
879
  }
@@ -1639,6 +1667,29 @@ function normalizeTarget(target) {
1639
1667
  const trimmed = target?.trim();
1640
1668
  return trimmed && trimmed.length > 0 ? trimmed : undefined;
1641
1669
  }
1670
+ /**
1671
+ * Resolve where to scan for project files (README, AGENTS.md, manifests).
1672
+ * If target is an absolute directory path, scan from there.
1673
+ * Otherwise fall back to cwd (the workspace/store root).
1674
+ */
1675
+ function resolveBootstrapScanRoot(cwd, target) {
1676
+ if (!target)
1677
+ return cwd;
1678
+ const resolved = path.isAbsolute(target) ? target : path.resolve(cwd, target);
1679
+ try {
1680
+ if (fs.statSync(resolved).isDirectory())
1681
+ return resolved;
1682
+ }
1683
+ catch { /* not a directory or doesn't exist */ }
1684
+ // Target is a file path or glob — use its parent directory if it exists
1685
+ const parent = path.dirname(resolved);
1686
+ try {
1687
+ if (fs.statSync(parent).isDirectory())
1688
+ return parent;
1689
+ }
1690
+ catch { /* fall back to cwd */ }
1691
+ return cwd;
1692
+ }
1642
1693
  // ─── Step 12: Additional brownfield sources ──────────────────────────────────
1643
1694
  const CI_WORKFLOW_DIRS = ['.github/workflows', '.gitlab'];
1644
1695
  const CI_FILES = ['.gitlab-ci.yml', 'Jenkinsfile', '.circleci/config.yml'];