brainclaw 0.19.12 → 0.19.14

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
@@ -21,6 +21,8 @@ The best setup is to tell your agent, in natural language, to install and initia
21
21
 
22
22
  It sits alongside Copilot, Claude Code, Cursor, Codex, Windsurf, OpenCode, Antigravity/Gemini CLI and other coding agents. It does not replace them. It gives them a shared state layer they can resume from reliably across sessions.
23
23
 
24
+ brainclaw is also starting to model other local AI work surfaces on the same machine, such as ChatGPT Desktop, Claude Desktop, Claude Cowork, and Gemini web or CLI. That makes it possible to keep a project-level queue of non-code work for those surfaces, instead of treating every task as something the active coding agent must do itself.
25
+
24
26
  ---
25
27
 
26
28
  ## Why brainclaw exists
@@ -40,6 +42,8 @@ brainclaw solves this by making the repo itself agent-readable and agent-writeab
40
42
  | **Coordination state** | shared plans, file claims, runtime notes, and board views for active work |
41
43
  | **Agent-ready context** | compact, prompt-sized context built from real workspace state instead of stale instructions |
42
44
  | **Native agent files** | auto-writes `CLAUDE.md`, `AGENTS.md`, `GEMINI.md`, `.cursor/rules/`, `.windsurfrules`, and similar local guidance |
45
+ | **Machine AI surface discovery** | detects local coding agents plus desktop AI work surfaces such as ChatGPT Desktop and Gemini CLI |
46
+ | **Queued surface tasks** | stores project-scoped requests for other local AI surfaces, such as visual generation, drafting, summaries, or research |
43
47
  | **Local-first storage** | plain text + JSON, Git-friendly, no mandatory cloud, no telemetry by default |
44
48
 
45
49
  ---
@@ -146,6 +150,18 @@ brainclaw context --for src/auth/routes.ts --digest
146
150
  brainclaw status
147
151
  ```
148
152
 
153
+ And if the current coding agent wants to hand off a non-code task to another local AI surface for later:
154
+
155
+ ```bash
156
+ brainclaw surface-task create "Generate homepage hero visual" \
157
+ --target chatgpt \
158
+ --kind visual_asset \
159
+ --instructions "Create a lightweight product hero visual for the landing page." \
160
+ --output assets/hero-home.png
161
+
162
+ brainclaw surface-task list
163
+ ```
164
+
149
165
  ---
150
166
 
151
167
  ## Installation
package/dist/cli.js CHANGED
@@ -3,6 +3,7 @@ import { Command } from 'commander';
3
3
  import { runInit } from './commands/init.js';
4
4
  import { runSetup } from './commands/setup.js';
5
5
  import { runUpgrade } from './commands/upgrade.js';
6
+ import { runReconcile } from './commands/reconcile.js';
6
7
  import { getMemoryLog, rollbackMemory, hasMemoryRepo } from './core/memory-git.js';
7
8
  import { buildMachineProfile, saveMachineProfile, loadMachineProfile, renderMachineProfileSummary } from './core/machine-profile.js';
8
9
  import { buildAgentInventory, saveAgentInventory, loadAgentInventory, renderAgentInventorySummary } from './core/agent-inventory.js';
@@ -22,6 +23,7 @@ import { runCompleteStep } from './commands/complete-step.js';
22
23
  import { runUpdateHandoff } from './commands/update-handoff.js';
23
24
  import { runInstruction } from './commands/instruction.js';
24
25
  import { runListAgents } from './commands/list-agents.js';
26
+ import { runSurfaceTaskResource } from './commands/surface-task-resource.js';
25
27
  import { runListInstructions } from './commands/list-instructions.js';
26
28
  import { runDoctor } from './commands/doctor.js';
27
29
  import { runRebuild } from './commands/rebuild.js';
@@ -169,7 +171,7 @@ program
169
171
  // --- machine-profile ---
170
172
  program
171
173
  .command('machine-profile')
172
- .description('Detect and persist machine capabilities (OS, shells, git users, SSH keys, toolchains, WSL)')
174
+ .description('Detect and persist machine capabilities (OS, shells, git users, SSH keys, toolchains, WSL, AI surfaces)')
173
175
  .option('--refresh', 'Force regeneration even if profile exists')
174
176
  .option('--json', 'Output as JSON')
175
177
  .action(async (options) => {
@@ -402,6 +404,28 @@ program
402
404
  .action((id, options) => {
403
405
  runUpdatePlan(id, { ...options, actualEffort: options.actualEffort });
404
406
  });
407
+ // --- surface-task ---
408
+ program
409
+ .command('surface-task <subcommand> [args...]')
410
+ .description('Manage queued tasks for desktop AI surfaces such as ChatGPT Desktop or Claude Desktop')
411
+ .option('--json', 'Output as JSON for list')
412
+ .option('--all', 'Include completed, cancelled, and failed tasks in list')
413
+ .option('--status <status>', 'Status filter/update: queued, in_progress, completed, cancelled, failed')
414
+ .option('--target <surface>', 'Target surface, e.g. chatgpt, claude, gemini')
415
+ .option('--kind <kind>', 'Task kind: visual_asset, draft, summary, analysis, research, custom')
416
+ .option('--instructions <text>', 'Detailed instructions for the target surface')
417
+ .option('--output <paths...>', 'Expected output paths')
418
+ .option('--result <text>', 'Optional result note when updating a task')
419
+ .option('--tag <tags...>', 'Tags for this task')
420
+ .option('--path <paths...>', 'Related file paths')
421
+ .option('--agent <agent>', 'Author agent name')
422
+ .option('--agent-id <agentId>', 'Author agent id')
423
+ .action((subcommand, args, options) => {
424
+ runSurfaceTaskResource(subcommand, args, {
425
+ ...options,
426
+ agentId: options.agentId,
427
+ });
428
+ });
405
429
  // --- delete-plan ---
406
430
  program
407
431
  .command('delete-plan <id>')
@@ -915,6 +939,25 @@ program
915
939
  .action((options) => {
916
940
  runExport(options);
917
941
  });
942
+ program
943
+ .command('reconcile')
944
+ .description('Refresh machine and workspace bootstrap state after updates or onboarding on complex installs')
945
+ .option('--json', 'Output as JSON')
946
+ .option('--dry-run', 'Preview the reconciliation plan without writing machine or bootstrap state')
947
+ .option('--apply-bootstrap', 'Apply bootstrap suggestions across all selected stores after refresh')
948
+ .option('-y, --yes', 'Skip confirmation prompts for multi-store bootstrap apply')
949
+ .option('--skip-machine-profile', 'Skip machine-profile refresh')
950
+ .option('--skip-agent-inventory', 'Skip agent-inventory refresh')
951
+ .action(async (options) => {
952
+ await runReconcile({
953
+ json: options.json,
954
+ dryRun: options.dryRun,
955
+ applyBootstrap: options.applyBootstrap,
956
+ yes: options.yes,
957
+ skipMachineProfile: options.skipMachineProfile,
958
+ skipAgentInventory: options.skipAgentInventory,
959
+ });
960
+ });
918
961
  // --- hooks ---
919
962
  program
920
963
  .command('hooks')
@@ -0,0 +1,18 @@
1
+ import path from 'node:path';
2
+ import { buildClaudeDesktopExtension, renderClaudeDesktopExtensionSummary, } from '../core/claude-desktop-extension.js';
3
+ export function runClaudeDesktopExtension(options = {}) {
4
+ const cwd = options.cwd ?? process.cwd();
5
+ const result = buildClaudeDesktopExtension({
6
+ cwd,
7
+ workspaceDir: options.workspace ? path.resolve(cwd, options.workspace) : undefined,
8
+ outputFile: options.output ? path.resolve(cwd, options.output) : undefined,
9
+ projectRoot: options.projectRoot ? path.resolve(cwd, options.projectRoot) : undefined,
10
+ pack: options.pack,
11
+ });
12
+ if (options.json) {
13
+ console.log(JSON.stringify(result, null, 2));
14
+ return;
15
+ }
16
+ console.log(renderClaudeDesktopExtensionSummary(result));
17
+ }
18
+ //# sourceMappingURL=claude-desktop-extension.js.map
@@ -25,6 +25,7 @@ import { assessBrainclawVersion } from '../core/brainclaw-version.js';
25
25
  import { resolveStoreChain } from '../core/store-resolution.js';
26
26
  import { resolveCrossProjectLinks, detectCrossProjectCycles } from '../core/cross-project.js';
27
27
  import { auditLocalAgentWorkspaceFiles, ensureGitignoreEntries } from '../core/agent-files.js';
28
+ import { summarizeWorkspaceProjects } from '../core/workspace-projects.js';
28
29
  const BACKLOG_KEYWORDS = /\b(TODO|NEXT|backlog|next[\s-]step|action[\s-]item|prochaine?s?\s+étapes?|à\s+faire)\b/i;
29
30
  function hasBacklogPatterns(text) {
30
31
  const lines = text.split(/\r?\n/);
@@ -138,14 +139,19 @@ export function runDoctor(options = {}) {
138
139
  });
139
140
  }
140
141
  }
141
- if (config.project_mode === 'multi-project' && (config.projects?.known.length ?? 0) === 0) {
142
+ const workspaceProjects = summarizeWorkspaceProjects(options.cwd ?? process.cwd(), config);
143
+ if (config.project_mode === 'multi-project' && workspaceProjects.effective_project_count === 0) {
142
144
  checks.push({
143
145
  name: 'project_mode',
144
146
  status: 'warn',
145
- message: 'project_mode is multi-project but no project namespaces are configured yet.',
147
+ message: config.projects?.strategy === 'folder'
148
+ ? 'project_mode is multi-project with folder strategy but no child projects were resolved from config, registry, or nested stores yet.'
149
+ : 'project_mode is multi-project but no project namespaces are configured yet.',
146
150
  });
147
151
  if (!options.json) {
148
- console.warn('⚠ project_mode is multi-project but no project namespaces are configured yet.');
152
+ console.warn(config.projects?.strategy === 'folder'
153
+ ? '⚠ project_mode is multi-project with folder strategy but no child projects were resolved from config, registry, or nested stores yet.'
154
+ : '⚠ project_mode is multi-project but no project namespaces are configured yet.');
149
155
  }
150
156
  hasIssues = true;
151
157
  }
@@ -153,10 +159,11 @@ export function runDoctor(options = {}) {
153
159
  checks.push({
154
160
  name: 'project_mode',
155
161
  status: 'ok',
156
- message: `project_mode=${config.project_mode}, strategy=${config.projects?.strategy ?? 'manual'}, known_projects=${config.projects?.known.length ?? 0}`,
162
+ message: `project_mode=${config.project_mode}, strategy=${config.projects?.strategy ?? 'manual'}, configured_projects=${workspaceProjects.configured_projects.length}, effective_projects=${workspaceProjects.effective_project_count}`,
163
+ details: workspaceProjects,
157
164
  });
158
165
  if (!options.json) {
159
- console.log(`✔ project mode: ${config.project_mode} (${config.projects?.strategy ?? 'manual'})`);
166
+ console.log(`✔ project mode: ${config.project_mode} (${config.projects?.strategy ?? 'manual'}), effective projects=${workspaceProjects.effective_project_count}`);
160
167
  }
161
168
  }
162
169
  try {
@@ -14,6 +14,7 @@ import { renderBootstrapSummary, runBootstrapProfile } from '../core/bootstrap.j
14
14
  import { isAgentIntegrationName, upsertAgentIntegrationDeclaration } from '../core/agent-integrations.js';
15
15
  import { describeAutoConfigWrite, ensureAgentFiles, ensureGitignoreEntries, writeDetectedAgentAutoConfig } from '../core/agent-files.js';
16
16
  import { detectAiAgent, detectWslEnvironment } from '../core/ai-agent-detection.js';
17
+ import { buildAiSurfaceInventory, renderAiSurfaceUsageHints } from '../core/ai-surface-inventory.js';
17
18
  import { hasCompletedSetup } from '../core/setup-state.js';
18
19
  import { writeDetectedAgentExport } from './export.js';
19
20
  import { writeDetectedAgentHooks } from './hooks.js';
@@ -178,6 +179,20 @@ export async function runInit(options = {}) {
178
179
  if (detectedExport) {
179
180
  console.log(`\u2714 Agent instructions written to ${detectedExport.relativePath} (${detectedExport.created ? 'created' : 'updated'})`);
180
181
  }
182
+ const visibleSurfaces = buildAiSurfaceInventory().filter((surface) => surface.status !== 'not_detected');
183
+ if (visibleSurfaces.length > 0) {
184
+ console.log('✔ Other AI work surfaces detected on this machine:');
185
+ for (const surface of visibleSurfaces) {
186
+ console.log(` - ${surface.display_name} [${surface.surface_kind}, ${surface.status}]`);
187
+ }
188
+ const usageHints = renderAiSurfaceUsageHints(visibleSurfaces);
189
+ if (usageHints.length > 0) {
190
+ console.log(' Suggested uses:');
191
+ for (const line of usageHints) {
192
+ console.log(` ${line}`);
193
+ }
194
+ }
195
+ }
181
196
  for (const hook of detectedHooks) {
182
197
  console.log(`\u2714 Session hook written to ${hook.relativePath} (${hook.created ? 'created' : 'updated'})`);
183
198
  }
@@ -0,0 +1,39 @@
1
+ import { memoryExists } from '../core/io.js';
2
+ import { listAiSurfaceTasks } from '../core/ai-surface-tasks.js';
3
+ export function runListSurfaceTasks(options = {}) {
4
+ if (!memoryExists(options.cwd)) {
5
+ console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
6
+ process.exit(1);
7
+ }
8
+ let tasks = listAiSurfaceTasks(options.cwd);
9
+ if (!options.all) {
10
+ tasks = tasks.filter((task) => task.status === 'queued' || task.status === 'in_progress');
11
+ }
12
+ if (options.status) {
13
+ tasks = tasks.filter((task) => task.status === options.status);
14
+ }
15
+ if (options.target) {
16
+ const target = options.target.toLowerCase();
17
+ tasks = tasks.filter((task) => task.target_surface.toLowerCase() === target);
18
+ }
19
+ if (options.json) {
20
+ console.log(JSON.stringify(tasks, null, 2));
21
+ return;
22
+ }
23
+ if (tasks.length === 0) {
24
+ console.log('No surface tasks.');
25
+ return;
26
+ }
27
+ console.log(`${tasks.length} surface task(s):`);
28
+ console.log('');
29
+ for (const task of tasks) {
30
+ console.log(` [${task.id}] ${task.title} (${task.status}, target ${task.target_surface}, kind ${task.kind})`);
31
+ if (task.requested_outputs.length > 0) {
32
+ console.log(` outputs: ${task.requested_outputs.join(', ')}`);
33
+ }
34
+ if (task.result_note) {
35
+ console.log(` result: ${task.result_note}`);
36
+ }
37
+ }
38
+ }
39
+ //# sourceMappingURL=list-surface-tasks.js.map
@@ -0,0 +1,138 @@
1
+ import readline from 'node:readline/promises';
2
+ import { stdin as input, stdout as output } from 'node:process';
3
+ import path from 'node:path';
4
+ import { buildMachineProfile, saveMachineProfile } from '../core/machine-profile.js';
5
+ import { buildAgentInventory, saveAgentInventory } from '../core/agent-inventory.js';
6
+ import { applyBootstrapImport, runBootstrapProfile } from '../core/bootstrap.js';
7
+ import { loadConfig } from '../core/config.js';
8
+ import { memoryExists } from '../core/io.js';
9
+ import { summarizeWorkspaceProjects } from '../core/workspace-projects.js';
10
+ export async function runReconcile(options = {}) {
11
+ const cwd = path.resolve(options.cwd ?? process.cwd());
12
+ if (!memoryExists(cwd)) {
13
+ console.error('Error: .brainclaw/ not found in the current directory. Run `brainclaw init` first.');
14
+ process.exit(1);
15
+ }
16
+ const rootConfig = loadConfig(cwd);
17
+ const workspaceSummary = summarizeWorkspaceProjects(cwd, rootConfig);
18
+ const storeTargets = [
19
+ cwd,
20
+ ...workspaceSummary.uses_folder_resolution
21
+ ? workspaceSummary.discovered_projects
22
+ .filter((project) => project.source !== 'config')
23
+ .map((project) => project.path)
24
+ : [],
25
+ ];
26
+ const uniqueTargets = [...new Set(storeTargets.map((store) => path.resolve(store)))];
27
+ if (options.json && options.dryRun) {
28
+ console.log(JSON.stringify({
29
+ cwd,
30
+ mode: 'dry_run',
31
+ workspace_summary: workspaceSummary,
32
+ planned_actions: {
33
+ machine_profile_refresh: !options.skipMachineProfile,
34
+ agent_inventory_refresh: !options.skipAgentInventory,
35
+ bootstrap_refresh: uniqueTargets.map((store) => ({
36
+ cwd: store,
37
+ relative_path: path.relative(cwd, store) || '.',
38
+ apply_bootstrap: Boolean(options.applyBootstrap),
39
+ })),
40
+ },
41
+ }, null, 2));
42
+ return;
43
+ }
44
+ if (options.applyBootstrap) {
45
+ await confirmBootstrapApply(options.yes);
46
+ }
47
+ const machineProfilePath = !options.skipMachineProfile
48
+ ? saveMachineProfile(buildMachineProfile())
49
+ : null;
50
+ const agentInventoryPath = !options.skipAgentInventory
51
+ ? saveAgentInventory(buildAgentInventory())
52
+ : null;
53
+ const stores = [];
54
+ for (const store of uniqueTargets) {
55
+ const config = loadConfig(store);
56
+ if (options.applyBootstrap) {
57
+ const result = applyBootstrapImport({ cwd: store, refresh: true });
58
+ stores.push({
59
+ cwd: store,
60
+ relative_path: path.relative(cwd, store) || '.',
61
+ project_name: config.project_name,
62
+ project_mode: config.project_mode,
63
+ project_strategy: config.projects.strategy,
64
+ bootstrap_refreshed: true,
65
+ bootstrap_applied: true,
66
+ workspace_kind: result.proposal.workspace_kind,
67
+ onboarding_mode: result.proposal.onboarding_mode,
68
+ confidence: result.proposal.confidence,
69
+ suggestion_count: result.proposal.suggestion_count,
70
+ created_count: result.createdCount,
71
+ skipped_count: result.skippedCount,
72
+ });
73
+ continue;
74
+ }
75
+ const result = runBootstrapProfile({ cwd: store, refresh: true });
76
+ stores.push({
77
+ cwd: store,
78
+ relative_path: path.relative(cwd, store) || '.',
79
+ project_name: config.project_name,
80
+ project_mode: config.project_mode,
81
+ project_strategy: config.projects.strategy,
82
+ bootstrap_refreshed: true,
83
+ bootstrap_applied: false,
84
+ workspace_kind: result.profile.workspace_kind,
85
+ onboarding_mode: result.profile.onboarding_mode,
86
+ confidence: result.profile.confidence,
87
+ suggestion_count: result.importPlan.suggestion_count,
88
+ });
89
+ }
90
+ if (options.json) {
91
+ console.log(JSON.stringify({
92
+ cwd,
93
+ mode: options.applyBootstrap ? 'apply' : 'refresh',
94
+ workspace_summary: workspaceSummary,
95
+ machine_profile_refreshed: !options.skipMachineProfile,
96
+ machine_profile_path: machineProfilePath,
97
+ agent_inventory_refreshed: !options.skipAgentInventory,
98
+ agent_inventory_path: agentInventoryPath,
99
+ stores,
100
+ }, null, 2));
101
+ return;
102
+ }
103
+ if (!options.skipMachineProfile) {
104
+ console.log(`✔ Machine profile refreshed: ${machineProfilePath}`);
105
+ }
106
+ if (!options.skipAgentInventory) {
107
+ console.log(`✔ Agent inventory refreshed: ${agentInventoryPath}`);
108
+ }
109
+ console.log('');
110
+ console.log(`Reconciled ${stores.length} store(s):`);
111
+ for (const store of stores) {
112
+ const suffix = store.bootstrap_applied
113
+ ? `${store.created_count ?? 0} created, ${store.skipped_count ?? 0} skipped`
114
+ : `${store.suggestion_count ?? 0} bootstrap suggestion(s) available`;
115
+ console.log(` - ${store.relative_path} (${store.project_name}) — ${store.onboarding_mode ?? 'unknown'}, confidence=${store.confidence ?? 'unknown'}, ${suffix}`);
116
+ }
117
+ }
118
+ async function confirmBootstrapApply(yes) {
119
+ if (yes) {
120
+ return;
121
+ }
122
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
123
+ console.error('Error: bootstrap apply across multiple stores requires --yes in non-interactive mode.');
124
+ process.exit(1);
125
+ }
126
+ const rl = readline.createInterface({ input, output });
127
+ try {
128
+ const answer = await rl.question('Apply bootstrap suggestions across all selected stores? [y/N] ');
129
+ if (answer.trim().toLowerCase() !== 'y') {
130
+ console.error('Cancelled.');
131
+ process.exit(1);
132
+ }
133
+ }
134
+ finally {
135
+ rl.close();
136
+ }
137
+ }
138
+ //# sourceMappingURL=reconcile.js.map
@@ -4,6 +4,7 @@ import readline from 'node:readline/promises';
4
4
  import { spawnSync } from 'node:child_process';
5
5
  import { runInit } from './init.js';
6
6
  import { detectAiAgent } from '../core/ai-agent-detection.js';
7
+ import { buildAiSurfaceInventory, renderAiSurfaceUsageHints } from '../core/ai-surface-inventory.js';
7
8
  import { buildMachineProfile, saveMachineProfile, loadMachineProfile } from '../core/machine-profile.js';
8
9
  import { buildAgentInventory, saveAgentInventory, loadAgentInventory } from '../core/agent-inventory.js';
9
10
  import { ensureClaudeCodeUserSettings, ensureClaudeCodeUserCommand, ensureCursorMcpConfig, ensureWindsurfMcpConfig, ensureAntigravityMcpConfig, ensureContinueUserMcpConfig, ensureCodexMcpConfig, writeDetectedAgentAutoConfig, describeAutoConfigWrite, ensureGitignoreEntries, collectWorkspaceGitignoreEntries, } from '../core/agent-files.js';
@@ -388,6 +389,7 @@ export async function runSetup(options = {}) {
388
389
  // Step 4: Agent detection & selection
389
390
  const detectedAi = detectAiAgent(env);
390
391
  const detectedName = detectedAi?.name;
392
+ const detectedSurfaces = buildAiSurfaceInventory();
391
393
  console.log('');
392
394
  if (detectedName) {
393
395
  console.log(`Detected AI agent: ${detectedName}`);
@@ -395,6 +397,23 @@ export async function runSetup(options = {}) {
395
397
  else {
396
398
  console.log('No AI agent detected automatically.');
397
399
  }
400
+ const visibleSurfaces = detectedSurfaces.filter((surface) => surface.status !== 'not_detected');
401
+ if (visibleSurfaces.length > 0) {
402
+ console.log('Other AI work surfaces on this machine:');
403
+ for (const surface of visibleSurfaces) {
404
+ console.log(` - ${surface.display_name} [${surface.surface_kind}, ${surface.status}]`);
405
+ }
406
+ const usageHints = renderAiSurfaceUsageHints(visibleSurfaces);
407
+ if (usageHints.length > 0) {
408
+ console.log('');
409
+ console.log('Suggested uses:');
410
+ for (const line of usageHints) {
411
+ console.log(` ${line}`);
412
+ }
413
+ }
414
+ console.log('');
415
+ console.log('These surfaces are tracked separately from coding agents and will use tailored onboarding flows.');
416
+ }
398
417
  console.log('Supported agents:');
399
418
  ALL_KNOWN_AGENTS.forEach((a, i) => {
400
419
  const tag = a === detectedName ? ' ← detected' : '';
@@ -9,7 +9,8 @@ import { loadState } from '../core/state.js';
9
9
  import { isTrapActive, listOperationalTraps } from '../core/traps.js';
10
10
  import { generateMarkdown } from '../core/markdown.js';
11
11
  import { memoryExists } from '../core/io.js';
12
- function printHumanStatus(state, config) {
12
+ import { summarizeWorkspaceProjects } from '../core/workspace-projects.js';
13
+ function printHumanStatus(state, config, cwd) {
13
14
  const activePlans = state.plan_items.filter((plan) => plan.status !== 'done' && plan.status !== 'dropped');
14
15
  const activeInstructions = loadInstructions().filter((entry) => entry.active);
15
16
  const activeClaims = listClaims().filter((claim) => claim.status === 'active');
@@ -21,6 +22,7 @@ function printHumanStatus(state, config) {
21
22
  const activeSharedTraps = state.known_traps.filter((trap) => isTrapActive(trap));
22
23
  const resolvedSharedTraps = state.known_traps.filter((trap) => trap.status === 'resolved');
23
24
  const activeMachineTraps = machineTraps.filter((trap) => isTrapActive(trap));
25
+ const workspaceProjects = summarizeWorkspaceProjects(cwd, config);
24
26
  const counts = {
25
27
  instructions: activeInstructions.length,
26
28
  claims: activeClaims.length,
@@ -50,7 +52,7 @@ function printHumanStatus(state, config) {
50
52
  console.log(` Traps : ${activeSharedTraps.length} active shared, ${resolvedSharedTraps.length} resolved shared, ${activeMachineTraps.length} active machine-local (${currentHost})`);
51
53
  console.log(` Handoffs : ${counts.handoffs}`);
52
54
  if (config.project_mode === 'multi-project') {
53
- console.log(` Projects : ${config.projects.known.length}`);
55
+ console.log(` Projects : ${workspaceProjects.effective_project_count} effective (${workspaceProjects.strategy})`);
54
56
  }
55
57
  // Show recent items (last 3 per section)
56
58
  const sections = [
@@ -84,24 +86,27 @@ function printHumanStatus(state, config) {
84
86
  }
85
87
  }
86
88
  export function runStatus(options = {}) {
87
- if (!memoryExists()) {
89
+ const cwd = process.cwd();
90
+ if (!memoryExists(cwd)) {
88
91
  console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
89
92
  process.exit(1);
90
93
  }
91
- const config = loadConfig();
92
- const state = loadState();
94
+ const config = loadConfig(cwd);
95
+ const state = loadState(cwd);
93
96
  const currentHost = resolveCurrentHostId();
94
- const sharedRuntimeNotes = listRuntimeNotes({ visibility: 'shared' });
95
- const machineRuntimeNotes = listRuntimeNotes({ visibility: 'machine' });
96
- const machineTraps = listOperationalTraps({ visibility: 'machine' });
97
+ const sharedRuntimeNotes = listRuntimeNotes({ visibility: 'shared' }, cwd);
98
+ const machineRuntimeNotes = listRuntimeNotes({ visibility: 'machine' }, cwd);
99
+ const machineTraps = listOperationalTraps({ visibility: 'machine' }, cwd);
97
100
  const activeSharedTraps = state.known_traps.filter((trap) => isTrapActive(trap));
98
101
  const resolvedSharedTraps = state.known_traps.filter((trap) => trap.status === 'resolved');
99
102
  const activeMachineTraps = machineTraps.filter((trap) => isTrapActive(trap));
100
- const registeredAgents = listAgentIdentities();
101
- const reputation = buildReputationSnapshot();
103
+ const registeredAgents = listAgentIdentities(cwd);
104
+ const reputation = buildReputationSnapshot(cwd);
105
+ const workspaceProjects = summarizeWorkspaceProjects(cwd, config);
102
106
  if (options.json) {
103
107
  console.log(JSON.stringify({
104
108
  config,
109
+ workspace_projects: workspaceProjects,
105
110
  agents: {
106
111
  current_name: config.current_agent,
107
112
  current_id: config.current_agent_id,
@@ -125,9 +130,9 @@ export function runStatus(options = {}) {
125
130
  return;
126
131
  }
127
132
  if (options.markdown) {
128
- console.log(generateMarkdown(state));
133
+ console.log(generateMarkdown(state, cwd));
129
134
  return;
130
135
  }
131
- printHumanStatus(state, config);
136
+ printHumanStatus(state, config, cwd);
132
137
  }
133
138
  //# sourceMappingURL=status.js.map
@@ -0,0 +1,35 @@
1
+ import { runSurfaceTask } from './surface-task.js';
2
+ import { runListSurfaceTasks } from './list-surface-tasks.js';
3
+ import { runUpdateSurfaceTask } from './update-surface-task.js';
4
+ export function runSurfaceTaskResource(subcommand, args, options = {}) {
5
+ const normalized = subcommand.trim().toLowerCase();
6
+ if (normalized === 'create') {
7
+ const title = args.join(' ').trim();
8
+ if (!title) {
9
+ console.error('Error: surface-task create requires <title>.');
10
+ process.exit(1);
11
+ }
12
+ runSurfaceTask(title, options);
13
+ return;
14
+ }
15
+ if (normalized === 'list' || normalized === 'ls') {
16
+ runListSurfaceTasks(options);
17
+ return;
18
+ }
19
+ if (normalized === 'update') {
20
+ const id = args[0];
21
+ if (!id) {
22
+ console.error('Error: surface-task update requires <id>.');
23
+ process.exit(1);
24
+ }
25
+ runUpdateSurfaceTask(id, options);
26
+ return;
27
+ }
28
+ const legacyTitle = [subcommand, ...args].join(' ').trim();
29
+ if (!legacyTitle) {
30
+ console.error('Error: missing surface-task subcommand or title.');
31
+ process.exit(1);
32
+ }
33
+ runSurfaceTask(legacyTitle, options);
34
+ }
35
+ //# sourceMappingURL=surface-task-resource.js.map
@@ -0,0 +1,57 @@
1
+ import { buildOperationalIdentity } from '../core/identity.js';
2
+ import { memoryExists } from '../core/io.js';
3
+ import { generateIdWithLabel, nowISO } from '../core/ids.js';
4
+ import { requireMinimumTrustLevel, requireRegisteredAgentIdentity } from '../core/agent-registry.js';
5
+ import { saveAiSurfaceTask } from '../core/ai-surface-tasks.js';
6
+ export function runSurfaceTask(title, options = {}) {
7
+ if (!memoryExists(options.cwd)) {
8
+ console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
9
+ process.exit(1);
10
+ }
11
+ if (!options.target) {
12
+ console.error('Error: surface-task create requires --target <surface>.');
13
+ process.exit(1);
14
+ }
15
+ if (!options.instructions?.trim()) {
16
+ console.error('Error: surface-task create requires --instructions <text>.');
17
+ process.exit(1);
18
+ }
19
+ const registeredAgent = requireRegisteredAgentIdentity({
20
+ agentName: options.agent,
21
+ agentId: options.agentId,
22
+ cwd: options.cwd,
23
+ allowCurrent: true,
24
+ allowEnv: true,
25
+ });
26
+ requireMinimumTrustLevel(registeredAgent, 'contributor');
27
+ const actor = buildOperationalIdentity(registeredAgent.agent_name, options.cwd, {
28
+ agentId: registeredAgent.agent_id,
29
+ });
30
+ const ids = generateIdWithLabel('ai_surface_tasks', options.cwd);
31
+ const timestamp = nowISO();
32
+ const task = {
33
+ id: ids.id,
34
+ short_label: ids.short_label,
35
+ title: title.trim(),
36
+ instructions: options.instructions.trim(),
37
+ target_surface: options.target.trim(),
38
+ kind: options.kind ?? 'custom',
39
+ created_at: timestamp,
40
+ updated_at: timestamp,
41
+ author: actor.agent,
42
+ author_id: actor.agent_id,
43
+ project_id: actor.project_id,
44
+ session_id: actor.session_id,
45
+ status: 'queued',
46
+ requested_outputs: options.output ?? [],
47
+ related_paths: options.path,
48
+ tags: options.tag ?? [],
49
+ };
50
+ saveAiSurfaceTask(task, options.cwd);
51
+ console.log(`✔ Surface task queued: [${task.id}] ${task.title}`);
52
+ console.log(` Target: ${task.target_surface} (${task.kind})`);
53
+ if (task.requested_outputs.length > 0) {
54
+ console.log(` Outputs: ${task.requested_outputs.join(', ')}`);
55
+ }
56
+ }
57
+ //# sourceMappingURL=surface-task.js.map
@@ -0,0 +1,30 @@
1
+ import { memoryExists } from '../core/io.js';
2
+ import { loadAiSurfaceTask, saveAiSurfaceTask } from '../core/ai-surface-tasks.js';
3
+ import { nowISO } from '../core/ids.js';
4
+ export function runUpdateSurfaceTask(id, options = {}) {
5
+ if (!memoryExists(options.cwd)) {
6
+ console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
7
+ process.exit(1);
8
+ }
9
+ const task = loadAiSurfaceTask(id, options.cwd);
10
+ const timestamp = nowISO();
11
+ if (options.status) {
12
+ task.status = options.status;
13
+ if (options.status === 'in_progress' && !task.claimed_at) {
14
+ task.claimed_at = timestamp;
15
+ }
16
+ if ((options.status === 'completed' || options.status === 'failed' || options.status === 'cancelled') && !task.completed_at) {
17
+ task.completed_at = timestamp;
18
+ }
19
+ }
20
+ if (options.result !== undefined) {
21
+ task.result_note = options.result;
22
+ }
23
+ if (options.output && options.output.length > 0) {
24
+ task.requested_outputs = options.output;
25
+ }
26
+ task.updated_at = timestamp;
27
+ saveAiSurfaceTask(task, options.cwd);
28
+ console.log(`✔ Surface task updated: [${task.id}] ${task.title}`);
29
+ }
30
+ //# sourceMappingURL=update-surface-task.js.map