brainclaw 0.19.11 → 0.19.12
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 +3 -0
- package/dist/commands/env.js +7 -1
- package/dist/commands/mcp.js +7 -1
- package/dist/commands/version.js +3 -1
- package/dist/commands/whoami.js +11 -1
- package/dist/core/brainclaw-version.js +303 -62
- package/docs/cli.md +10 -1
- package/docs/integrations/mcp.md +11 -6
- package/docs/quickstart.md +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -129,6 +129,7 @@ After that, the agent should stay on Brainclaw's MCP path for live state:
|
|
|
129
129
|
|
|
130
130
|
```text
|
|
131
131
|
bclaw_session_start -> open session + return board/context
|
|
132
|
+
bclaw_get_execution_context -> inspect local tooling + notice Brainclaw package updates
|
|
132
133
|
bclaw_get_context -> fetch fresh prompt-ready context for a path
|
|
133
134
|
bclaw_list_plans -> inspect shared work
|
|
134
135
|
bclaw_claim -> claim scope before editing
|
|
@@ -177,6 +178,8 @@ If you want a machine-level CLI for operator workflows, debugging, or repeated l
|
|
|
177
178
|
npm install -g brainclaw
|
|
178
179
|
```
|
|
179
180
|
|
|
181
|
+
By default, Brainclaw's update checks for end-user installs follow the public npm `latest` channel. Projects that need a different track can override `brainclaw_update_source`, for example to use `prelaunch` or a local tarball channel.
|
|
182
|
+
|
|
180
183
|
If you are working from source while developing Brainclaw itself:
|
|
181
184
|
|
|
182
185
|
```bash
|
package/dist/commands/env.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { memoryExists } from '../core/io.js';
|
|
2
2
|
import { loadConfig } from '../core/config.js';
|
|
3
3
|
import { assessAgentIntegrationReadiness } from '../core/agent-integrations.js';
|
|
4
|
-
import { assessBrainclawVersion } from '../core/brainclaw-version.js';
|
|
4
|
+
import { assessBrainclawVersion, checkBrainclawInstallableUpdate, renderBrainclawInstallableUpdateNotice, } from '../core/brainclaw-version.js';
|
|
5
5
|
import { buildExecutionContext, compactExecutionContext, renderExecutionContextSummary } from '../core/execution-context.js';
|
|
6
6
|
import { buildAgentToolingContext, renderAgentToolingSummary } from '../core/agent-context.js';
|
|
7
7
|
export function runEnv(options = {}) {
|
|
@@ -14,11 +14,14 @@ export function runEnv(options = {}) {
|
|
|
14
14
|
const config = loadConfig(cwd);
|
|
15
15
|
const integrationReadiness = assessAgentIntegrationReadiness(config, cwd);
|
|
16
16
|
const brainclawVersion = assessBrainclawVersion(config);
|
|
17
|
+
const installableUpdate = checkBrainclawInstallableUpdate(config, cwd, { useDefaultNpmSource: true });
|
|
18
|
+
const installableUpdateNotice = renderBrainclawInstallableUpdateNotice(installableUpdate);
|
|
17
19
|
const agentTooling = options.agentTooling ? buildAgentToolingContext({ cwd }) : undefined;
|
|
18
20
|
if (options.json) {
|
|
19
21
|
console.log(JSON.stringify({
|
|
20
22
|
execution_context: executionContext,
|
|
21
23
|
brainclaw_version: brainclawVersion,
|
|
24
|
+
installable_update: installableUpdate,
|
|
22
25
|
declared_agent_integrations: config.agent_integrations,
|
|
23
26
|
integration_readiness: integrationReadiness,
|
|
24
27
|
...(agentTooling ? { agent_tooling: agentTooling } : {}),
|
|
@@ -33,6 +36,9 @@ export function runEnv(options = {}) {
|
|
|
33
36
|
console.log(`Upgrade benefits: ${brainclawVersion.upgrade_message}`);
|
|
34
37
|
}
|
|
35
38
|
}
|
|
39
|
+
if (installableUpdateNotice) {
|
|
40
|
+
console.log(installableUpdateNotice);
|
|
41
|
+
}
|
|
36
42
|
console.log(`Declared agent integrations: ${config.agent_integrations.declarations.length}`);
|
|
37
43
|
const missingDeclarations = integrationReadiness.filter((entry) => !entry.ready);
|
|
38
44
|
if (missingDeclarations.length > 0) {
|
package/dist/commands/mcp.js
CHANGED
|
@@ -7,6 +7,7 @@ import { buildAgentToolingContext, renderAgentToolingSummary } from '../core/age
|
|
|
7
7
|
import { buildCoordinationSnapshot } from '../core/coordination.js';
|
|
8
8
|
import { buildContext, renderContextMarkdown, renderContextPromptTemplate } from '../core/context.js';
|
|
9
9
|
import { buildExecutionContext, renderExecutionContextSummary } from '../core/execution-context.js';
|
|
10
|
+
import { checkBrainclawInstallableUpdate, renderBrainclawInstallableUpdateNotice } from '../core/brainclaw-version.js';
|
|
10
11
|
import { loadConfig } from '../core/config.js';
|
|
11
12
|
import { loadState, persistState } from '../core/state.js';
|
|
12
13
|
import { memoryExists } from '../core/io.js';
|
|
@@ -82,7 +83,7 @@ export const MCP_READ_TOOLS = [
|
|
|
82
83
|
},
|
|
83
84
|
{
|
|
84
85
|
name: 'bclaw_get_execution_context',
|
|
85
|
-
description: 'Inspect the local execution environment and optionally agent tooling signals.',
|
|
86
|
+
description: 'Inspect the local execution environment, installable Brainclaw update channel, and optionally agent tooling signals.',
|
|
86
87
|
inputSchema: {
|
|
87
88
|
type: 'object',
|
|
88
89
|
properties: {
|
|
@@ -1133,15 +1134,20 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1133
1134
|
}
|
|
1134
1135
|
if (name === 'bclaw_get_execution_context') {
|
|
1135
1136
|
const executionContext = buildExecutionContext({ cwd });
|
|
1137
|
+
const config = loadConfig(cwd);
|
|
1138
|
+
const installableUpdate = checkBrainclawInstallableUpdate(config, cwd, { useDefaultNpmSource: true });
|
|
1139
|
+
const installableUpdateNotice = renderBrainclawInstallableUpdateNotice(installableUpdate);
|
|
1136
1140
|
const agentTooling = args.includeAgentTooling ? buildAgentToolingContext({ cwd }) : undefined;
|
|
1137
1141
|
const text = [
|
|
1138
1142
|
renderExecutionContextSummary(executionContext, true),
|
|
1143
|
+
...(installableUpdateNotice ? ['', installableUpdateNotice] : []),
|
|
1139
1144
|
...(agentTooling ? ['', renderAgentToolingSummary(agentTooling)] : []),
|
|
1140
1145
|
].join('\n');
|
|
1141
1146
|
return {
|
|
1142
1147
|
content: [{ type: 'text', text }],
|
|
1143
1148
|
structuredContent: {
|
|
1144
1149
|
execution_context: executionContext,
|
|
1150
|
+
installable_update: installableUpdate,
|
|
1145
1151
|
...(agentTooling ? { agent_tooling: agentTooling } : {}),
|
|
1146
1152
|
},
|
|
1147
1153
|
};
|
package/dist/commands/version.js
CHANGED
|
@@ -31,7 +31,9 @@ export function runVersion(options = {}) {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
const assessment = assessBrainclawVersion(config);
|
|
34
|
-
const updateCheck = options.check
|
|
34
|
+
const updateCheck = options.check
|
|
35
|
+
? checkBrainclawInstallableUpdate(config, cwd, { useDefaultNpmSource: true })
|
|
36
|
+
: undefined;
|
|
35
37
|
const result = {
|
|
36
38
|
initialized,
|
|
37
39
|
...assessment,
|
package/dist/commands/whoami.js
CHANGED
|
@@ -4,7 +4,7 @@ import { loadConfig } from '../core/config.js';
|
|
|
4
4
|
import { resolveCurrentHostId } from '../core/host.js';
|
|
5
5
|
import { buildOperationalIdentity } from '../core/identity.js';
|
|
6
6
|
import { assessAgentIntegrationReadiness } from '../core/agent-integrations.js';
|
|
7
|
-
import { assessBrainclawVersion } from '../core/brainclaw-version.js';
|
|
7
|
+
import { assessBrainclawVersion, checkBrainclawInstallableUpdate, renderBrainclawInstallableUpdateNotice, } from '../core/brainclaw-version.js';
|
|
8
8
|
import { buildExecutionContext, compactExecutionContext } from '../core/execution-context.js';
|
|
9
9
|
import { buildAgentToolingContext } from '../core/agent-context.js';
|
|
10
10
|
export function runWhoami(options = {}) {
|
|
@@ -18,6 +18,8 @@ export function runWhoami(options = {}) {
|
|
|
18
18
|
const executionContext = compactExecutionContext(buildExecutionContext({ cwd }));
|
|
19
19
|
const integrationReadiness = assessAgentIntegrationReadiness(config, cwd);
|
|
20
20
|
const brainclawVersion = assessBrainclawVersion(config);
|
|
21
|
+
const installableUpdate = checkBrainclawInstallableUpdate(config, cwd, { useDefaultNpmSource: true });
|
|
22
|
+
const installableUpdateNotice = renderBrainclawInstallableUpdateNotice(installableUpdate);
|
|
21
23
|
const agentTooling = buildAgentToolingContext({ cwd });
|
|
22
24
|
let identity;
|
|
23
25
|
try {
|
|
@@ -45,6 +47,7 @@ export function runWhoami(options = {}) {
|
|
|
45
47
|
env_session: process.env.BRAINCLAW_SESSION_ID ?? null,
|
|
46
48
|
env_host: process.env.BRAINCLAW_HOST_ID ?? null,
|
|
47
49
|
brainclaw_version: brainclawVersion,
|
|
50
|
+
installable_update: installableUpdate,
|
|
48
51
|
declared_agent_integrations: config.agent_integrations,
|
|
49
52
|
integration_readiness: integrationReadiness,
|
|
50
53
|
execution_context: executionContext,
|
|
@@ -80,6 +83,13 @@ export function runWhoami(options = {}) {
|
|
|
80
83
|
if (result.brainclaw_version.status !== 'ok') {
|
|
81
84
|
console.log(` Version : ${result.brainclaw_version.message}`);
|
|
82
85
|
}
|
|
86
|
+
if (installableUpdateNotice) {
|
|
87
|
+
const lines = installableUpdateNotice.split('\n');
|
|
88
|
+
console.log(` Update : ${lines[0]}`);
|
|
89
|
+
for (const line of lines.slice(1)) {
|
|
90
|
+
console.log(` ${line}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
83
93
|
console.log(` Declared integrations: ${result.declared_agent_integrations.declarations.length}`);
|
|
84
94
|
const missingIntegrations = result.integration_readiness.filter((entry) => !entry.ready);
|
|
85
95
|
if (missingIntegrations.length > 0) {
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { spawnSync } from 'node:child_process';
|
|
4
5
|
import { fileURLToPath } from 'node:url';
|
|
5
6
|
import { BrainclawLocalReleaseManifestSchema, } from './schema.js';
|
|
6
7
|
export const DEFAULT_LOCAL_RELEASES_DIR = '.releases';
|
|
7
8
|
export const DEFAULT_LOCAL_RELEASE_MANIFEST_PATH = `${DEFAULT_LOCAL_RELEASES_DIR}/brainclaw-local.json`;
|
|
9
|
+
export const DEFAULT_NPM_UPDATE_PACKAGE = 'brainclaw';
|
|
10
|
+
export const DEFAULT_NPM_UPDATE_DIST_TAG = 'latest';
|
|
11
|
+
export const DEFAULT_INSTALLABLE_UPDATE_CACHE_TTL_MS = 6 * 60 * 60 * 1000;
|
|
8
12
|
const SEMVER_RE = /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
13
|
+
const DEFAULT_NPM_UPDATE_SOURCE = {
|
|
14
|
+
type: 'npm',
|
|
15
|
+
package_name: DEFAULT_NPM_UPDATE_PACKAGE,
|
|
16
|
+
dist_tag: DEFAULT_NPM_UPDATE_DIST_TAG,
|
|
17
|
+
};
|
|
9
18
|
let cachedCliVersion;
|
|
10
19
|
export function getInstalledBrainclawVersion() {
|
|
11
20
|
if (cachedCliVersion) {
|
|
@@ -87,8 +96,10 @@ export function assessBrainclawVersion(config) {
|
|
|
87
96
|
message,
|
|
88
97
|
};
|
|
89
98
|
}
|
|
90
|
-
export function checkBrainclawInstallableUpdate(config, cwd) {
|
|
91
|
-
const source = config?.brainclaw_update_source
|
|
99
|
+
export function checkBrainclawInstallableUpdate(config, cwd, options = {}) {
|
|
100
|
+
const source = config?.brainclaw_update_source
|
|
101
|
+
?? (options.useDefaultNpmSource ? DEFAULT_NPM_UPDATE_SOURCE : undefined);
|
|
102
|
+
const defaultSource = !config?.brainclaw_update_source && source?.type === 'npm';
|
|
92
103
|
if (!source) {
|
|
93
104
|
return {
|
|
94
105
|
checked: false,
|
|
@@ -103,22 +114,181 @@ export function checkBrainclawInstallableUpdate(config, cwd) {
|
|
|
103
114
|
};
|
|
104
115
|
}
|
|
105
116
|
if (source.type === 'npm') {
|
|
106
|
-
|
|
107
|
-
|
|
117
|
+
return checkNpmInstallableUpdate(source, config, cwd, options, defaultSource);
|
|
118
|
+
}
|
|
119
|
+
return checkLocalPackInstallableUpdate(source.manifest_path, config, cwd);
|
|
120
|
+
}
|
|
121
|
+
export function renderBrainclawInstallableUpdateNotice(updateCheck) {
|
|
122
|
+
if (!updateCheck || updateCheck.status !== 'update_available') {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
const lines = [updateCheck.message];
|
|
126
|
+
if (updateCheck.install_command) {
|
|
127
|
+
lines.push(`Install: ${updateCheck.install_command}`);
|
|
128
|
+
}
|
|
129
|
+
if (updateCheck.release_notes) {
|
|
130
|
+
lines.push(`Why update: ${updateCheck.release_notes}`);
|
|
131
|
+
}
|
|
132
|
+
return lines.join('\n');
|
|
133
|
+
}
|
|
134
|
+
export function publishLocalBrainclawRelease(cwd, options = {}) {
|
|
135
|
+
const workspacePackage = readWorkspaceBrainclawPackage(cwd);
|
|
136
|
+
const outputDir = path.resolve(cwd, options.outputDir ?? DEFAULT_LOCAL_RELEASES_DIR);
|
|
137
|
+
const manifestPath = path.resolve(cwd, options.manifestPath ?? DEFAULT_LOCAL_RELEASE_MANIFEST_PATH);
|
|
138
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
139
|
+
fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
|
|
140
|
+
const packResult = spawnSync(resolveNpmCommand(), resolveNpmPackArgs(outputDir), {
|
|
141
|
+
cwd,
|
|
142
|
+
encoding: 'utf-8',
|
|
143
|
+
timeout: 120000,
|
|
144
|
+
});
|
|
145
|
+
if (packResult.error) {
|
|
146
|
+
throw new Error(`Failed to run npm pack: ${packResult.error.message}`);
|
|
147
|
+
}
|
|
148
|
+
if (packResult.status !== 0) {
|
|
149
|
+
throw new Error(firstNonEmptyLine(packResult.stderr) ?? firstNonEmptyLine(packResult.stdout) ?? 'npm pack failed');
|
|
150
|
+
}
|
|
151
|
+
const artifactFilename = parsePackedFilename(packResult.stdout);
|
|
152
|
+
if (!artifactFilename) {
|
|
153
|
+
throw new Error('npm pack did not report the generated tarball filename.');
|
|
154
|
+
}
|
|
155
|
+
const artifactAbsolutePath = path.join(outputDir, artifactFilename);
|
|
156
|
+
if (!fs.existsSync(artifactAbsolutePath)) {
|
|
157
|
+
throw new Error(`npm pack reported ${artifactFilename}, but the tarball was not found in ${outputDir}.`);
|
|
158
|
+
}
|
|
159
|
+
const manifestArtifactPath = toManifestRelativePath(path.relative(path.dirname(manifestPath), artifactAbsolutePath));
|
|
160
|
+
const projectArtifactPath = toManifestRelativePath(path.relative(cwd, artifactAbsolutePath));
|
|
161
|
+
const projectManifestPath = toPortablePath(path.relative(cwd, manifestPath));
|
|
162
|
+
const installCommand = `npm install -g "${projectArtifactPath}"`;
|
|
163
|
+
const releaseNotes = options.releaseNotes?.trim() || null;
|
|
164
|
+
const manifest = BrainclawLocalReleaseManifestSchema.parse({
|
|
165
|
+
version: 1,
|
|
166
|
+
channel: 'local-pack',
|
|
167
|
+
package_name: workspacePackage.name,
|
|
168
|
+
latest_installable_version: workspacePackage.version,
|
|
169
|
+
published_at: new Date().toISOString(),
|
|
170
|
+
artifact_path: manifestArtifactPath,
|
|
171
|
+
install_command: installCommand,
|
|
172
|
+
release_notes: releaseNotes ?? undefined,
|
|
173
|
+
});
|
|
174
|
+
fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf-8');
|
|
175
|
+
return {
|
|
176
|
+
package_name: workspacePackage.name,
|
|
177
|
+
workspace_version: workspacePackage.version,
|
|
178
|
+
manifest_path: projectManifestPath,
|
|
179
|
+
artifact_path: projectArtifactPath,
|
|
180
|
+
install_command: installCommand,
|
|
181
|
+
release_notes: releaseNotes,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function checkNpmInstallableUpdate(source, config, cwd, options, defaultSource) {
|
|
185
|
+
const packageName = source.package_name?.trim() || DEFAULT_NPM_UPDATE_PACKAGE;
|
|
186
|
+
const distTag = source.dist_tag?.trim() || DEFAULT_NPM_UPDATE_DIST_TAG;
|
|
187
|
+
if (packageName.length === 0) {
|
|
108
188
|
return {
|
|
109
189
|
checked: false,
|
|
110
190
|
source_type: 'npm',
|
|
111
|
-
source_description:
|
|
191
|
+
source_description: null,
|
|
112
192
|
latest_installable_version: null,
|
|
113
193
|
artifact_path: null,
|
|
114
194
|
install_command: null,
|
|
115
195
|
release_notes: null,
|
|
116
|
-
status: '
|
|
117
|
-
message: '
|
|
196
|
+
status: 'invalid_config',
|
|
197
|
+
message: 'brainclaw_update_source.package_name must not be empty.',
|
|
198
|
+
default_source: defaultSource,
|
|
118
199
|
};
|
|
119
200
|
}
|
|
120
|
-
|
|
121
|
-
|
|
201
|
+
if (distTag.length === 0) {
|
|
202
|
+
return {
|
|
203
|
+
checked: false,
|
|
204
|
+
source_type: 'npm',
|
|
205
|
+
source_description: packageName,
|
|
206
|
+
latest_installable_version: null,
|
|
207
|
+
artifact_path: null,
|
|
208
|
+
install_command: null,
|
|
209
|
+
release_notes: null,
|
|
210
|
+
status: 'invalid_config',
|
|
211
|
+
message: 'brainclaw_update_source.dist_tag must not be empty.',
|
|
212
|
+
default_source: defaultSource,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
const lookup = (options.npmLookup ?? lookupNpmDistTags)(packageName, {
|
|
217
|
+
cwd,
|
|
218
|
+
now: options.now,
|
|
219
|
+
cacheTtlMs: options.cacheTtlMs,
|
|
220
|
+
});
|
|
221
|
+
const latestVersion = normalizeConfiguredVersion(lookup.dist_tags[distTag]);
|
|
222
|
+
if (!latestVersion) {
|
|
223
|
+
return {
|
|
224
|
+
checked: true,
|
|
225
|
+
source_type: 'npm',
|
|
226
|
+
source_description: formatNpmSourceDescription(packageName, distTag, defaultSource),
|
|
227
|
+
latest_installable_version: null,
|
|
228
|
+
artifact_path: null,
|
|
229
|
+
install_command: null,
|
|
230
|
+
release_notes: config?.brainclaw_upgrade_message?.trim() || null,
|
|
231
|
+
status: 'check_failed',
|
|
232
|
+
message: `The npm channel ${packageName}@${distTag} did not resolve to a valid semver release.`,
|
|
233
|
+
checked_at: lookup.checked_at,
|
|
234
|
+
cached: lookup.cached,
|
|
235
|
+
default_source: defaultSource,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
const installCommand = config?.brainclaw_upgrade_command?.trim()
|
|
239
|
+
|| `npm install -g ${packageName}@${latestVersion}`;
|
|
240
|
+
const releaseNotes = config?.brainclaw_upgrade_message?.trim() || null;
|
|
241
|
+
const installedVersion = getInstalledBrainclawVersion();
|
|
242
|
+
if (compareVersions(installedVersion, latestVersion) < 0) {
|
|
243
|
+
return {
|
|
244
|
+
checked: true,
|
|
245
|
+
source_type: 'npm',
|
|
246
|
+
source_description: formatNpmSourceDescription(packageName, distTag, defaultSource),
|
|
247
|
+
latest_installable_version: latestVersion,
|
|
248
|
+
artifact_path: null,
|
|
249
|
+
install_command: installCommand,
|
|
250
|
+
release_notes: releaseNotes,
|
|
251
|
+
status: 'update_available',
|
|
252
|
+
message: `A newer installable brainclaw build is available from npm: ${latestVersion} (installed ${installedVersion}).`,
|
|
253
|
+
checked_at: lookup.checked_at,
|
|
254
|
+
cached: lookup.cached,
|
|
255
|
+
default_source: defaultSource,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
checked: true,
|
|
260
|
+
source_type: 'npm',
|
|
261
|
+
source_description: formatNpmSourceDescription(packageName, distTag, defaultSource),
|
|
262
|
+
latest_installable_version: latestVersion,
|
|
263
|
+
artifact_path: null,
|
|
264
|
+
install_command: installCommand,
|
|
265
|
+
release_notes: releaseNotes,
|
|
266
|
+
status: 'up_to_date',
|
|
267
|
+
message: `Installed brainclaw ${installedVersion} is up to date for npm channel ${packageName}@${distTag}.`,
|
|
268
|
+
checked_at: lookup.checked_at,
|
|
269
|
+
cached: lookup.cached,
|
|
270
|
+
default_source: defaultSource,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
275
|
+
return {
|
|
276
|
+
checked: true,
|
|
277
|
+
source_type: 'npm',
|
|
278
|
+
source_description: formatNpmSourceDescription(packageName, distTag, defaultSource),
|
|
279
|
+
latest_installable_version: null,
|
|
280
|
+
artifact_path: null,
|
|
281
|
+
install_command: null,
|
|
282
|
+
release_notes: config?.brainclaw_upgrade_message?.trim() || null,
|
|
283
|
+
status: 'check_failed',
|
|
284
|
+
message: `Failed to check npm installable updates: ${message}`,
|
|
285
|
+
default_source: defaultSource,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
function checkLocalPackInstallableUpdate(manifestPath, config, cwd) {
|
|
290
|
+
const trimmedManifestPath = manifestPath.trim();
|
|
291
|
+
if (trimmedManifestPath.length === 0) {
|
|
122
292
|
return {
|
|
123
293
|
checked: false,
|
|
124
294
|
source_type: 'local-pack',
|
|
@@ -129,11 +299,12 @@ export function checkBrainclawInstallableUpdate(config, cwd) {
|
|
|
129
299
|
release_notes: null,
|
|
130
300
|
status: 'invalid_config',
|
|
131
301
|
message: 'brainclaw_update_source.manifest_path must not be empty.',
|
|
302
|
+
default_source: false,
|
|
132
303
|
};
|
|
133
304
|
}
|
|
134
|
-
const resolvedManifestPath = path.isAbsolute(
|
|
135
|
-
?
|
|
136
|
-
: path.resolve(cwd,
|
|
305
|
+
const resolvedManifestPath = path.isAbsolute(trimmedManifestPath)
|
|
306
|
+
? trimmedManifestPath
|
|
307
|
+
: path.resolve(cwd, trimmedManifestPath);
|
|
137
308
|
if (!fs.existsSync(resolvedManifestPath)) {
|
|
138
309
|
return {
|
|
139
310
|
checked: true,
|
|
@@ -145,6 +316,7 @@ export function checkBrainclawInstallableUpdate(config, cwd) {
|
|
|
145
316
|
release_notes: null,
|
|
146
317
|
status: 'check_failed',
|
|
147
318
|
message: `The configured local-pack manifest was not found: ${resolvedManifestPath}`,
|
|
319
|
+
default_source: false,
|
|
148
320
|
};
|
|
149
321
|
}
|
|
150
322
|
try {
|
|
@@ -162,6 +334,7 @@ export function checkBrainclawInstallableUpdate(config, cwd) {
|
|
|
162
334
|
release_notes: manifest.release_notes?.trim() || config?.brainclaw_upgrade_message?.trim() || null,
|
|
163
335
|
status: 'check_failed',
|
|
164
336
|
message: `The local-pack manifest has an invalid latest_installable_version: ${manifest.latest_installable_version}`,
|
|
337
|
+
default_source: false,
|
|
165
338
|
};
|
|
166
339
|
}
|
|
167
340
|
const artifactPath = manifest.artifact_path
|
|
@@ -182,6 +355,7 @@ export function checkBrainclawInstallableUpdate(config, cwd) {
|
|
|
182
355
|
release_notes: releaseNotes,
|
|
183
356
|
status: 'update_available',
|
|
184
357
|
message: `A newer installable brainclaw build is available: ${latestVersion} (installed ${installedVersion}).`,
|
|
358
|
+
default_source: false,
|
|
185
359
|
};
|
|
186
360
|
}
|
|
187
361
|
return {
|
|
@@ -194,6 +368,7 @@ export function checkBrainclawInstallableUpdate(config, cwd) {
|
|
|
194
368
|
release_notes: releaseNotes,
|
|
195
369
|
status: 'up_to_date',
|
|
196
370
|
message: `Installed brainclaw ${installedVersion} is up to date for the configured local-pack channel.`,
|
|
371
|
+
default_source: false,
|
|
197
372
|
};
|
|
198
373
|
}
|
|
199
374
|
catch (error) {
|
|
@@ -208,59 +383,10 @@ export function checkBrainclawInstallableUpdate(config, cwd) {
|
|
|
208
383
|
release_notes: null,
|
|
209
384
|
status: 'check_failed',
|
|
210
385
|
message: `Failed to read the configured local-pack manifest: ${message}`,
|
|
386
|
+
default_source: false,
|
|
211
387
|
};
|
|
212
388
|
}
|
|
213
389
|
}
|
|
214
|
-
export function publishLocalBrainclawRelease(cwd, options = {}) {
|
|
215
|
-
const workspacePackage = readWorkspaceBrainclawPackage(cwd);
|
|
216
|
-
const outputDir = path.resolve(cwd, options.outputDir ?? DEFAULT_LOCAL_RELEASES_DIR);
|
|
217
|
-
const manifestPath = path.resolve(cwd, options.manifestPath ?? DEFAULT_LOCAL_RELEASE_MANIFEST_PATH);
|
|
218
|
-
fs.mkdirSync(outputDir, { recursive: true });
|
|
219
|
-
fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
|
|
220
|
-
const packResult = spawnSync(resolveNpmCommand(), resolveNpmPackArgs(outputDir), {
|
|
221
|
-
cwd,
|
|
222
|
-
encoding: 'utf-8',
|
|
223
|
-
timeout: 120000,
|
|
224
|
-
});
|
|
225
|
-
if (packResult.error) {
|
|
226
|
-
throw new Error(`Failed to run npm pack: ${packResult.error.message}`);
|
|
227
|
-
}
|
|
228
|
-
if (packResult.status !== 0) {
|
|
229
|
-
throw new Error(firstNonEmptyLine(packResult.stderr) ?? firstNonEmptyLine(packResult.stdout) ?? 'npm pack failed');
|
|
230
|
-
}
|
|
231
|
-
const artifactFilename = parsePackedFilename(packResult.stdout);
|
|
232
|
-
if (!artifactFilename) {
|
|
233
|
-
throw new Error('npm pack did not report the generated tarball filename.');
|
|
234
|
-
}
|
|
235
|
-
const artifactAbsolutePath = path.join(outputDir, artifactFilename);
|
|
236
|
-
if (!fs.existsSync(artifactAbsolutePath)) {
|
|
237
|
-
throw new Error(`npm pack reported ${artifactFilename}, but the tarball was not found in ${outputDir}.`);
|
|
238
|
-
}
|
|
239
|
-
const manifestArtifactPath = toManifestRelativePath(path.relative(path.dirname(manifestPath), artifactAbsolutePath));
|
|
240
|
-
const projectArtifactPath = toManifestRelativePath(path.relative(cwd, artifactAbsolutePath));
|
|
241
|
-
const projectManifestPath = toPortablePath(path.relative(cwd, manifestPath));
|
|
242
|
-
const installCommand = `npm install -g "${projectArtifactPath}"`;
|
|
243
|
-
const releaseNotes = options.releaseNotes?.trim() || null;
|
|
244
|
-
const manifest = BrainclawLocalReleaseManifestSchema.parse({
|
|
245
|
-
version: 1,
|
|
246
|
-
channel: 'local-pack',
|
|
247
|
-
package_name: workspacePackage.name,
|
|
248
|
-
latest_installable_version: workspacePackage.version,
|
|
249
|
-
published_at: new Date().toISOString(),
|
|
250
|
-
artifact_path: manifestArtifactPath,
|
|
251
|
-
install_command: installCommand,
|
|
252
|
-
release_notes: releaseNotes ?? undefined,
|
|
253
|
-
});
|
|
254
|
-
fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf-8');
|
|
255
|
-
return {
|
|
256
|
-
package_name: workspacePackage.name,
|
|
257
|
-
workspace_version: workspacePackage.version,
|
|
258
|
-
manifest_path: projectManifestPath,
|
|
259
|
-
artifact_path: projectArtifactPath,
|
|
260
|
-
install_command: installCommand,
|
|
261
|
-
release_notes: releaseNotes,
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
390
|
function resolveNpmCommand() {
|
|
265
391
|
return process.platform === 'win32' ? (process.env.ComSpec?.trim() || 'cmd.exe') : 'npm';
|
|
266
392
|
}
|
|
@@ -270,6 +396,12 @@ function resolveNpmPackArgs(outputDir) {
|
|
|
270
396
|
}
|
|
271
397
|
return ['pack', '--json', '--pack-destination', outputDir];
|
|
272
398
|
}
|
|
399
|
+
function resolveNpmViewArgs(packageName) {
|
|
400
|
+
if (process.platform === 'win32') {
|
|
401
|
+
return ['/d', '/s', '/c', 'npm', 'view', packageName, 'dist-tags', '--json'];
|
|
402
|
+
}
|
|
403
|
+
return ['view', packageName, 'dist-tags', '--json'];
|
|
404
|
+
}
|
|
273
405
|
function findOwnPackageJson() {
|
|
274
406
|
let currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
275
407
|
while (true) {
|
|
@@ -331,6 +463,115 @@ function parsePackedFilename(stdout) {
|
|
|
331
463
|
return firstNonEmptyLine(stdout);
|
|
332
464
|
}
|
|
333
465
|
}
|
|
466
|
+
function lookupNpmDistTags(packageName, options) {
|
|
467
|
+
const now = options.now ?? new Date();
|
|
468
|
+
const cachePath = resolveNpmUpdateCachePath(packageName);
|
|
469
|
+
const ttlMs = options.cacheTtlMs ?? DEFAULT_INSTALLABLE_UPDATE_CACHE_TTL_MS;
|
|
470
|
+
const cached = readCachedNpmDistTags(cachePath, packageName, now, ttlMs);
|
|
471
|
+
if (cached) {
|
|
472
|
+
return cached;
|
|
473
|
+
}
|
|
474
|
+
const viewResult = spawnSync(resolveNpmCommand(), resolveNpmViewArgs(packageName), {
|
|
475
|
+
cwd: options.cwd,
|
|
476
|
+
encoding: 'utf-8',
|
|
477
|
+
timeout: 15000,
|
|
478
|
+
});
|
|
479
|
+
if (viewResult.error) {
|
|
480
|
+
throw new Error(`npm view failed: ${viewResult.error.message}`);
|
|
481
|
+
}
|
|
482
|
+
if (viewResult.status !== 0) {
|
|
483
|
+
throw new Error(firstNonEmptyLine(viewResult.stderr) ?? firstNonEmptyLine(viewResult.stdout) ?? 'npm view failed');
|
|
484
|
+
}
|
|
485
|
+
const distTags = parseNpmDistTags(viewResult.stdout);
|
|
486
|
+
const result = {
|
|
487
|
+
dist_tags: distTags,
|
|
488
|
+
checked_at: now.toISOString(),
|
|
489
|
+
cached: false,
|
|
490
|
+
};
|
|
491
|
+
writeCachedNpmDistTags(cachePath, {
|
|
492
|
+
version: 1,
|
|
493
|
+
package_name: packageName,
|
|
494
|
+
fetched_at: result.checked_at,
|
|
495
|
+
dist_tags: distTags,
|
|
496
|
+
});
|
|
497
|
+
return result;
|
|
498
|
+
}
|
|
499
|
+
function parseNpmDistTags(stdout) {
|
|
500
|
+
let parsed;
|
|
501
|
+
try {
|
|
502
|
+
parsed = JSON.parse(stdout);
|
|
503
|
+
}
|
|
504
|
+
catch (error) {
|
|
505
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
506
|
+
throw new Error(`npm view returned invalid JSON: ${message}`);
|
|
507
|
+
}
|
|
508
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
509
|
+
throw new Error('npm view did not return a dist-tag object.');
|
|
510
|
+
}
|
|
511
|
+
const distTags = {};
|
|
512
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
513
|
+
if (typeof value === 'string' && value.trim().length > 0) {
|
|
514
|
+
distTags[key] = value.trim();
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
if (Object.keys(distTags).length === 0) {
|
|
518
|
+
throw new Error('npm view returned an empty dist-tag object.');
|
|
519
|
+
}
|
|
520
|
+
return distTags;
|
|
521
|
+
}
|
|
522
|
+
function resolveNpmUpdateCachePath(packageName) {
|
|
523
|
+
const safeName = packageName.replace(/[^a-z0-9._-]+/gi, '_').replace(/^_+|_+$/g, '') || DEFAULT_NPM_UPDATE_PACKAGE;
|
|
524
|
+
return path.join(os.homedir(), '.brainclaw', 'cache', 'installable-updates', `${safeName}.json`);
|
|
525
|
+
}
|
|
526
|
+
function readCachedNpmDistTags(cachePath, packageName, now, ttlMs) {
|
|
527
|
+
if (!fs.existsSync(cachePath)) {
|
|
528
|
+
return undefined;
|
|
529
|
+
}
|
|
530
|
+
try {
|
|
531
|
+
const parsed = JSON.parse(fs.readFileSync(cachePath, 'utf-8'));
|
|
532
|
+
if (parsed.version !== 1
|
|
533
|
+
|| parsed.package_name !== packageName
|
|
534
|
+
|| typeof parsed.fetched_at !== 'string'
|
|
535
|
+
|| !parsed.dist_tags
|
|
536
|
+
|| typeof parsed.dist_tags !== 'object'
|
|
537
|
+
|| Array.isArray(parsed.dist_tags)) {
|
|
538
|
+
return undefined;
|
|
539
|
+
}
|
|
540
|
+
const fetchedAt = Date.parse(parsed.fetched_at);
|
|
541
|
+
if (!Number.isFinite(fetchedAt) || now.getTime() - fetchedAt > ttlMs) {
|
|
542
|
+
return undefined;
|
|
543
|
+
}
|
|
544
|
+
const distTags = {};
|
|
545
|
+
for (const [key, value] of Object.entries(parsed.dist_tags)) {
|
|
546
|
+
if (typeof value === 'string' && value.trim().length > 0) {
|
|
547
|
+
distTags[key] = value.trim();
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
if (Object.keys(distTags).length === 0) {
|
|
551
|
+
return undefined;
|
|
552
|
+
}
|
|
553
|
+
return {
|
|
554
|
+
dist_tags: distTags,
|
|
555
|
+
checked_at: parsed.fetched_at,
|
|
556
|
+
cached: true,
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
catch {
|
|
560
|
+
return undefined;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
function writeCachedNpmDistTags(cachePath, document) {
|
|
564
|
+
try {
|
|
565
|
+
fs.mkdirSync(path.dirname(cachePath), { recursive: true });
|
|
566
|
+
fs.writeFileSync(cachePath, `${JSON.stringify(document, null, 2)}\n`, 'utf-8');
|
|
567
|
+
}
|
|
568
|
+
catch {
|
|
569
|
+
// Cache writes are best-effort only.
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
function formatNpmSourceDescription(packageName, distTag, defaultSource) {
|
|
573
|
+
return defaultSource ? `${packageName}@${distTag} (default npm channel)` : `${packageName}@${distTag}`;
|
|
574
|
+
}
|
|
334
575
|
function firstNonEmptyLine(value) {
|
|
335
576
|
return value
|
|
336
577
|
.split(/\r?\n/)
|
package/docs/cli.md
CHANGED
|
@@ -133,7 +133,7 @@ brainclaw bootstrap --apply -y
|
|
|
133
133
|
|
|
134
134
|
### `brainclaw env`
|
|
135
135
|
|
|
136
|
-
Display environment
|
|
136
|
+
Display environment, tooling detection, and installable Brainclaw update information.
|
|
137
137
|
|
|
138
138
|
| Option | Description |
|
|
139
139
|
|---|---|
|
|
@@ -146,6 +146,8 @@ brainclaw env --json
|
|
|
146
146
|
brainclaw env --agent-tooling
|
|
147
147
|
```
|
|
148
148
|
|
|
149
|
+
When the project does not pin a local release channel, `brainclaw env` checks the public npm `latest` channel for installable updates. Projects can override that with `brainclaw_update_source`, for example to use `prelaunch` or a local-pack manifest.
|
|
150
|
+
|
|
149
151
|
---
|
|
150
152
|
|
|
151
153
|
## Memory Management
|
|
@@ -1253,3 +1255,10 @@ brainclaw version
|
|
|
1253
1255
|
brainclaw version --check
|
|
1254
1256
|
brainclaw version --publish-local --release-notes "Add estimation-report command"
|
|
1255
1257
|
```
|
|
1258
|
+
|
|
1259
|
+
`brainclaw version --check` now follows this order:
|
|
1260
|
+
|
|
1261
|
+
- if `brainclaw_update_source` is configured, use it
|
|
1262
|
+
- otherwise, fall back to the public npm channel `brainclaw@latest`
|
|
1263
|
+
|
|
1264
|
+
This keeps end-user installs aware of published npm releases without requiring a local tarball channel. To keep beta testers on a different channel, set `brainclaw_update_source` to `type: npm` with a different `dist_tag`, such as `prelaunch`.
|
package/docs/integrations/mcp.md
CHANGED
|
@@ -16,16 +16,19 @@ MCP matters because Brainclaw's value is mostly in dynamic state:
|
|
|
16
16
|
|
|
17
17
|
Static files still help, but they age immediately. MCP is the stronger path for live coordination.
|
|
18
18
|
|
|
19
|
+
That now also includes Brainclaw's own install channel state: `bclaw_get_execution_context` surfaces whether a newer npm or local-pack build is available, so the agent can notice upgrades without relying on a human to run `brainclaw version --check`.
|
|
20
|
+
|
|
19
21
|
## Recommended Agent Pattern
|
|
20
22
|
|
|
21
23
|
The default dynamic workflow is:
|
|
22
24
|
|
|
23
25
|
1. `bclaw_session_start` to open work and get the current board/context
|
|
24
|
-
2. `
|
|
25
|
-
3. `
|
|
26
|
-
4. `
|
|
27
|
-
5. `
|
|
28
|
-
6. `
|
|
26
|
+
2. `bclaw_get_execution_context` early in the session when the agent needs local tooling signals or package update visibility
|
|
27
|
+
3. `bclaw_get_context` when the target path or task changes
|
|
28
|
+
4. `bclaw_list_plans` and `bclaw_list_claims` to inspect active work
|
|
29
|
+
5. `bclaw_claim` before editing
|
|
30
|
+
6. `bclaw_write_note` for runtime observations
|
|
31
|
+
7. `bclaw_session_end` to close cleanly and hand work off
|
|
29
32
|
|
|
30
33
|
This keeps session continuity inside Brainclaw instead of pushing the agent back to manual CLI usage.
|
|
31
34
|
|
|
@@ -35,7 +38,7 @@ This keeps session continuity inside Brainclaw instead of pushing the agent back
|
|
|
35
38
|
|---|---|
|
|
36
39
|
| `bclaw_get_context` | Ranked prompt-ready context, supports `digest: true` |
|
|
37
40
|
| `bclaw_bootstrap` | Derive brownfield bootstrap signals, return adaptive interview prompts, accept structured interview answers, and preview/apply a selective import proposal |
|
|
38
|
-
| `bclaw_get_execution_context` | Inspect local execution context and agent tooling |
|
|
41
|
+
| `bclaw_get_execution_context` | Inspect local execution context, installable update status, and agent tooling |
|
|
39
42
|
| `bclaw_write_note` | Record a runtime note, supports `autoReflect: true` |
|
|
40
43
|
| `bclaw_read_handoff` | Read active handoffs |
|
|
41
44
|
| `bclaw_get_agent_board` | Coordination snapshot |
|
|
@@ -65,6 +68,8 @@ brainclaw mcp
|
|
|
65
68
|
|
|
66
69
|
In practice, most agents pick this up through generated MCP config such as `.mcp.json`, `~/.cursor/mcp.json`, or other agent-specific config files written by `brainclaw setup`, `brainclaw init`, or `brainclaw export`.
|
|
67
70
|
|
|
71
|
+
By default, installable update checks use the public npm channel `brainclaw@latest`. Projects that need a different channel can override `brainclaw_update_source`, for example with `type: npm` and `dist_tag: prelaunch`, or with `type: local-pack` for local tarball workflows.
|
|
72
|
+
|
|
68
73
|
## Bootstrap Through MCP
|
|
69
74
|
|
|
70
75
|
For agent-first onboarding, `bclaw_bootstrap` is the nominal path:
|
package/docs/quickstart.md
CHANGED
|
@@ -39,6 +39,7 @@ After the workspace is initialized, the nominal flow is:
|
|
|
39
39
|
|
|
40
40
|
```text
|
|
41
41
|
bclaw_session_start -> open a session and return current board/context
|
|
42
|
+
bclaw_get_execution_context -> inspect local tooling and notice package updates
|
|
42
43
|
bclaw_get_context -> fetch fresh prompt-ready context for the target path
|
|
43
44
|
bclaw_list_plans -> inspect active work
|
|
44
45
|
bclaw_claim -> claim scope before editing
|
|
@@ -48,6 +49,8 @@ bclaw_session_end -> close session cleanly and hand work off
|
|
|
48
49
|
|
|
49
50
|
Use native agent files such as `AGENTS.md`, `CLAUDE.md`, or Cursor rules as local workflow guidance, not as the only source of live state.
|
|
50
51
|
|
|
52
|
+
Unless the project overrides `brainclaw_update_source`, `bclaw_get_execution_context` checks the public npm `latest` channel so the agent can notice when a newer Brainclaw release is available.
|
|
53
|
+
|
|
51
54
|
## Path 2: CLI-Oriented Agent Or Fallback Workflow
|
|
52
55
|
|
|
53
56
|
Use this path when the agent does not have a good MCP integration yet, or when a human needs to drive the workflow directly.
|