create-byan-agent 2.13.1 → 2.14.0
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/install/bin/create-byan-agent-v2.js +89 -2
- package/install/lib/codex-native-setup.js +156 -0
- package/install/templates/.claude/skills/byan-byan/SKILL.md +12 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/update.js +127 -0
- package/install/templates/_byan/mcp/byan-mcp-server/server.js +50 -0
- package/package.json +1 -1
|
@@ -16,10 +16,79 @@ const { generateProjectAgentsDoc } = require('../lib/project-agents-generator');
|
|
|
16
16
|
const { launchPhase2Chat, generateDefaultConfig } = require('../lib/phase2-chat');
|
|
17
17
|
const { setupByanWebIntegration, validateByanWebReachability } = require('../lib/byan-web-integration');
|
|
18
18
|
const { setupClaudeNative } = require('../lib/claude-native-setup');
|
|
19
|
+
const { setupCodexNative } = require('../lib/codex-native-setup');
|
|
19
20
|
const { setupStagingConsent } = require('../lib/staging-consent');
|
|
21
|
+
const { getLatestVersion, compareVersions } = require('../lib/utils/version-compare');
|
|
20
22
|
|
|
21
23
|
const BYAN_VERSION = require('../package.json').version;
|
|
22
24
|
|
|
25
|
+
// Versions strictly below this floor have known install bugs (e.g. v2.9.8 ships
|
|
26
|
+
// the broken node_modules filter that copies an empty MCP server dir). They
|
|
27
|
+
// are blocked outright. Above the floor, an outdated version becomes a soft
|
|
28
|
+
// prompt — the user can choose to continue or upgrade.
|
|
29
|
+
const KNOWN_BAD_VERSION_FLOOR = '2.13.1';
|
|
30
|
+
|
|
31
|
+
async function assertVersionFresh({ skip = false, timeoutMs = 5000 } = {}) {
|
|
32
|
+
if (skip || process.env.BYAN_SKIP_VERSION_CHECK === '1') return;
|
|
33
|
+
let latest;
|
|
34
|
+
try {
|
|
35
|
+
latest = await Promise.race([
|
|
36
|
+
getLatestVersion('create-byan-agent'),
|
|
37
|
+
new Promise((_, rej) => setTimeout(() => rej(new Error('npm-timeout')), timeoutMs)),
|
|
38
|
+
]);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
console.log(chalk.gray(` (version check skipped: ${err.message})`));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (compareVersions(BYAN_VERSION, latest) >= 0) return;
|
|
44
|
+
|
|
45
|
+
// Hard block when below the known-bad floor — these versions corrupt installs.
|
|
46
|
+
if (compareVersions(BYAN_VERSION, KNOWN_BAD_VERSION_FLOOR) < 0) {
|
|
47
|
+
console.error('');
|
|
48
|
+
console.error(chalk.red.bold(' BYAN installer is obsolete (known install bugs)'));
|
|
49
|
+
console.error('');
|
|
50
|
+
console.error(chalk.yellow(` Installed: ${BYAN_VERSION}`));
|
|
51
|
+
console.error(chalk.yellow(` Latest: ${latest}`));
|
|
52
|
+
console.error(chalk.yellow(` Floor: ${KNOWN_BAD_VERSION_FLOOR} (older versions silently produce empty MCP servers)`));
|
|
53
|
+
console.error('');
|
|
54
|
+
console.error(chalk.cyan(' Upgrade required:'));
|
|
55
|
+
console.error(chalk.bold(' npm i -g create-byan-agent@latest'));
|
|
56
|
+
console.error('');
|
|
57
|
+
console.error(chalk.gray(' Bypass (not recommended): --skip-version-check or BYAN_SKIP_VERSION_CHECK=1'));
|
|
58
|
+
console.error('');
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Soft prompt above the floor — propose upgrade interactively.
|
|
63
|
+
console.log('');
|
|
64
|
+
console.log(chalk.yellow.bold(` BYAN ${BYAN_VERSION} -> ${latest} available on npm`));
|
|
65
|
+
console.log(chalk.gray(' Newer release published. Continue with current, or abort to upgrade?'));
|
|
66
|
+
if (!process.stdin.isTTY) {
|
|
67
|
+
console.log(chalk.gray(' (non-TTY context — continuing without prompt)'));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const { proceed } = await inquirer.prompt([
|
|
71
|
+
{
|
|
72
|
+
type: 'list',
|
|
73
|
+
name: 'proceed',
|
|
74
|
+
message: 'How do you want to proceed ?',
|
|
75
|
+
choices: [
|
|
76
|
+
{ name: `Continue with v${BYAN_VERSION}`, value: 'continue' },
|
|
77
|
+
{ name: `Abort and upgrade (npm i -g create-byan-agent@latest)`, value: 'abort' },
|
|
78
|
+
],
|
|
79
|
+
default: 'continue',
|
|
80
|
+
},
|
|
81
|
+
]);
|
|
82
|
+
if (proceed === 'abort') {
|
|
83
|
+
console.log('');
|
|
84
|
+
console.log(chalk.cyan(' Run:'));
|
|
85
|
+
console.log(chalk.bold(' npm i -g create-byan-agent@latest'));
|
|
86
|
+
console.log(chalk.gray(' Then re-run `npx create-byan-agent`.'));
|
|
87
|
+
console.log('');
|
|
88
|
+
process.exit(0);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
23
92
|
// ASCII Art Banner
|
|
24
93
|
const banner = `
|
|
25
94
|
${chalk.blue('╔════════════════════════════════════════════════════════════╗')}
|
|
@@ -256,10 +325,12 @@ async function mergePackageJson(templateDir, projectRoot, spinner) {
|
|
|
256
325
|
}
|
|
257
326
|
|
|
258
327
|
// Main installer
|
|
259
|
-
async function install() {
|
|
328
|
+
async function install(options = {}) {
|
|
260
329
|
console.clear();
|
|
261
330
|
console.log(banner);
|
|
262
|
-
|
|
331
|
+
|
|
332
|
+
await assertVersionFresh({ skip: options.skipVersionCheck });
|
|
333
|
+
|
|
263
334
|
const projectRoot = process.cwd();
|
|
264
335
|
|
|
265
336
|
// Step 1: Detect project type
|
|
@@ -1358,6 +1429,21 @@ async function install() {
|
|
|
1358
1429
|
}
|
|
1359
1430
|
}
|
|
1360
1431
|
|
|
1432
|
+
if (needsCodex) {
|
|
1433
|
+
console.log();
|
|
1434
|
+
console.log(chalk.cyan('Codex CLI MCP setup (~/.codex/config.toml)'));
|
|
1435
|
+
try {
|
|
1436
|
+
await setupCodexNative(projectRoot);
|
|
1437
|
+
} catch (error) {
|
|
1438
|
+
console.log(chalk.red(` ✘ Codex MCP setup failed: ${error.message}`));
|
|
1439
|
+
console.log(
|
|
1440
|
+
chalk.yellow(
|
|
1441
|
+
` → Edit ~/.codex/config.toml manually to add the byan MCP entry.`
|
|
1442
|
+
)
|
|
1443
|
+
);
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1361
1447
|
if (needsClaude || needsCopilot) {
|
|
1362
1448
|
console.log();
|
|
1363
1449
|
console.log(chalk.cyan('byan_web integration (optional — service payant)'));
|
|
@@ -1753,6 +1839,7 @@ program
|
|
|
1753
1839
|
.name('create-byan-agent')
|
|
1754
1840
|
.description('Install BYAN v2.2.0 - Builder of YAN with Model Selector and multi-platform support')
|
|
1755
1841
|
.version(BYAN_VERSION)
|
|
1842
|
+
.option('--skip-version-check', 'Bypass the npm freshness guard (not recommended)')
|
|
1756
1843
|
.action(install);
|
|
1757
1844
|
|
|
1758
1845
|
// Update Command (Yanstaller v3)
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex CLI native setup — wires the BYAN MCP server into ~/.codex/config.toml
|
|
3
|
+
* during `npx create-byan-agent`.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors claude-native-setup.js but targets Codex CLI's user-level TOML config.
|
|
6
|
+
* Per Codex docs (developers.openai.com/codex/mcp), MCP entries support:
|
|
7
|
+
* command, args, env, env_vars, cwd, startup_timeout_sec, tool_timeout_sec,
|
|
8
|
+
* enabled, required, enabled_tools, disabled_tools, supports_parallel_tool_calls.
|
|
9
|
+
*
|
|
10
|
+
* Idempotent: strips any existing [mcp_servers.byan*] sections before writing
|
|
11
|
+
* the fresh block. Other servers in config.toml are preserved untouched.
|
|
12
|
+
*
|
|
13
|
+
* Never persists a token: BYAN_API_TOKEN is left blank if not supplied via env
|
|
14
|
+
* or interactive prompt; the user gets a clear edit instruction.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const fs = require('fs-extra');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const os = require('os');
|
|
20
|
+
const chalk = require('chalk');
|
|
21
|
+
|
|
22
|
+
const SERVER_NAME = 'byan';
|
|
23
|
+
|
|
24
|
+
function getCodexConfigPath() {
|
|
25
|
+
return path.join(os.homedir(), '.codex', 'config.toml');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function detectCodex() {
|
|
29
|
+
return fs.pathExists(path.join(os.homedir(), '.codex'));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// TOML literal-string escape: single quotes don't interpret backslashes,
|
|
33
|
+
// so paths and tokens land verbatim. We just refuse values containing "'".
|
|
34
|
+
function tomlLiteral(value) {
|
|
35
|
+
const s = String(value);
|
|
36
|
+
if (s.includes("'")) {
|
|
37
|
+
throw new Error(`Cannot encode value with single quote in TOML literal: ${s}`);
|
|
38
|
+
}
|
|
39
|
+
return `'${s}'`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Removes every section whose header matches [mcp_servers.<name>] or
|
|
43
|
+
// [mcp_servers.<name>.<sub>]. Lines belonging to such a section are dropped
|
|
44
|
+
// until another header (any header) is encountered.
|
|
45
|
+
function stripServerSections(content, name) {
|
|
46
|
+
const ours = new RegExp(
|
|
47
|
+
`^\\[mcp_servers\\.${name}(?:\\..+)?\\]\\s*$`
|
|
48
|
+
);
|
|
49
|
+
const anyHeader = /^\[.+\]\s*$/;
|
|
50
|
+
const out = [];
|
|
51
|
+
let inOur = false;
|
|
52
|
+
for (const raw of content.split('\n')) {
|
|
53
|
+
const line = raw;
|
|
54
|
+
if (ours.test(line)) {
|
|
55
|
+
inOur = true;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (anyHeader.test(line)) {
|
|
59
|
+
inOur = false;
|
|
60
|
+
out.push(line);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (!inOur) out.push(line);
|
|
64
|
+
}
|
|
65
|
+
// Trim trailing blank lines that may pile up after stripping
|
|
66
|
+
return out.join('\n').replace(/\n{3,}$/g, '\n\n').replace(/\s+$/, '\n');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function buildByanBlock({ serverPath, apiUrl, apiToken, startupTimeoutSec = 15 }) {
|
|
70
|
+
const lines = [
|
|
71
|
+
'',
|
|
72
|
+
'[mcp_servers.byan]',
|
|
73
|
+
`command = ${tomlLiteral('node')}`,
|
|
74
|
+
`args = [${tomlLiteral(serverPath)}]`,
|
|
75
|
+
`startup_timeout_sec = ${Number.isFinite(startupTimeoutSec) ? startupTimeoutSec : 15}`,
|
|
76
|
+
'',
|
|
77
|
+
'[mcp_servers.byan.env]',
|
|
78
|
+
`BYAN_API_URL = ${tomlLiteral(apiUrl)}`,
|
|
79
|
+
`BYAN_API_TOKEN = ${tomlLiteral(apiToken)}`,
|
|
80
|
+
'',
|
|
81
|
+
];
|
|
82
|
+
return lines.join('\n');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function patchCodexConfig(projectRoot, options = {}) {
|
|
86
|
+
const configPath = getCodexConfigPath();
|
|
87
|
+
const serverPath = path.join(
|
|
88
|
+
projectRoot,
|
|
89
|
+
'_byan',
|
|
90
|
+
'mcp',
|
|
91
|
+
'byan-mcp-server',
|
|
92
|
+
'server.js'
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
await fs.ensureDir(path.dirname(configPath));
|
|
96
|
+
|
|
97
|
+
let existing = '';
|
|
98
|
+
if (await fs.pathExists(configPath)) {
|
|
99
|
+
existing = await fs.readFile(configPath, 'utf8');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const apiUrl =
|
|
103
|
+
options.apiUrl ||
|
|
104
|
+
process.env.BYAN_API_URL ||
|
|
105
|
+
'http://localhost:3737';
|
|
106
|
+
const apiToken =
|
|
107
|
+
options.apiToken !== undefined
|
|
108
|
+
? options.apiToken
|
|
109
|
+
: (process.env.BYAN_API_TOKEN || '');
|
|
110
|
+
|
|
111
|
+
const stripped = stripServerSections(existing, SERVER_NAME);
|
|
112
|
+
const block = buildByanBlock({
|
|
113
|
+
serverPath,
|
|
114
|
+
apiUrl,
|
|
115
|
+
apiToken,
|
|
116
|
+
startupTimeoutSec: options.startupTimeoutSec,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const merged =
|
|
120
|
+
(stripped.trimEnd().length > 0 ? stripped.trimEnd() + '\n' : '') + block;
|
|
121
|
+
|
|
122
|
+
await fs.writeFile(configPath, merged, 'utf8');
|
|
123
|
+
return { path: configPath, hadExisting: existing.length > 0, tokenSet: apiToken.length > 0 };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function setupCodexNative(projectRoot, options = {}) {
|
|
127
|
+
const log = options.quiet ? () => {} : (...a) => console.log(...a);
|
|
128
|
+
|
|
129
|
+
const present = await detectCodex();
|
|
130
|
+
if (!present && !options.force) {
|
|
131
|
+
log(chalk.gray(' · Codex CLI not detected (~/.codex absent), skipping'));
|
|
132
|
+
return { skipped: true, reason: 'codex-not-detected' };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const result = await patchCodexConfig(projectRoot, options);
|
|
136
|
+
log(chalk.green(` ✓ Codex MCP entry written to ${result.path}`));
|
|
137
|
+
if (!result.tokenSet) {
|
|
138
|
+
log(
|
|
139
|
+
chalk.yellow(
|
|
140
|
+
` ⚠ BYAN_API_TOKEN left empty. Edit ${result.path} and set BYAN_API_TOKEN`
|
|
141
|
+
)
|
|
142
|
+
);
|
|
143
|
+
log(chalk.gray(' (or rerun with BYAN_API_TOKEN=byan_xxx in the env)'));
|
|
144
|
+
}
|
|
145
|
+
log(chalk.gray(' Restart Codex CLI for the new MCP server to load'));
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
module.exports = {
|
|
150
|
+
setupCodexNative,
|
|
151
|
+
patchCodexConfig,
|
|
152
|
+
stripServerSections,
|
|
153
|
+
buildByanBlock,
|
|
154
|
+
getCodexConfigPath,
|
|
155
|
+
detectCodex,
|
|
156
|
+
};
|
|
@@ -18,6 +18,18 @@ Invoke this protocol when the user :
|
|
|
18
18
|
|
|
19
19
|
If the user request is a simple question or chat, stay out of FD — respond normally.
|
|
20
20
|
|
|
21
|
+
## 1.5. Freshness check (silent, once per session)
|
|
22
|
+
|
|
23
|
+
Before responding to the user's first activation message in a session, call the MCP tool `byan_update_check` once. It is read-only and cheap (single npm registry lookup, 5s timeout, no side effects).
|
|
24
|
+
|
|
25
|
+
Behavior depending on the JSON returned :
|
|
26
|
+
|
|
27
|
+
- `updateAvailable === true` : surface a one-line notice to the user, e.g. *"BYAN {installed} is behind {latest} on npm. Run `byan_update_apply` for the upgrade command, or skip and continue."* — then proceed normally with the user's request. Do not block.
|
|
28
|
+
- `updateAvailable === false` and `note` is set (e.g. manifest missing, network error) : stay silent — do not nag.
|
|
29
|
+
- `isCurrent === true` : stay silent.
|
|
30
|
+
|
|
31
|
+
Never call `byan_update_apply` without explicit user consent. That tool returns a shell command — the user must run it themselves outside this conversation. Update is destructive (file overwrites with backup) and stays a deliberate user action.
|
|
32
|
+
|
|
21
33
|
## 2. Five-phase protocol
|
|
22
34
|
|
|
23
35
|
### Phase 1 — BRAINSTORM
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BYAN update lifecycle helper for the MCP server.
|
|
3
|
+
*
|
|
4
|
+
* Read-only check: compares the installed BYAN version (from
|
|
5
|
+
* <projectRoot>/_byan/.manifest.json, falling back to package.json) against
|
|
6
|
+
* the latest published version on the npm registry.
|
|
7
|
+
*
|
|
8
|
+
* Apply path: returns *instructions* (a shell command) rather than executing
|
|
9
|
+
* anything itself. Update is destructive (overwrites files) and must remain
|
|
10
|
+
* an explicit user action, gated through the regular yanstaller pipeline
|
|
11
|
+
* (`npx create-byan-agent update`).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import fsPromises from 'node:fs/promises';
|
|
15
|
+
import nodePath from 'node:path';
|
|
16
|
+
import https from 'node:https';
|
|
17
|
+
|
|
18
|
+
export async function checkForUpdate(projectRoot) {
|
|
19
|
+
const installed = await getInstalledVersion(projectRoot);
|
|
20
|
+
let latest;
|
|
21
|
+
let networkError;
|
|
22
|
+
try {
|
|
23
|
+
latest = await getLatestVersion('create-byan-agent');
|
|
24
|
+
} catch (err) {
|
|
25
|
+
networkError = err.message || String(err);
|
|
26
|
+
}
|
|
27
|
+
if (!latest) {
|
|
28
|
+
return {
|
|
29
|
+
installed,
|
|
30
|
+
latest: null,
|
|
31
|
+
updateAvailable: false,
|
|
32
|
+
networkError,
|
|
33
|
+
note: 'Latest version unknown — npm registry unreachable. Skipped silently.',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (installed === 'unknown') {
|
|
37
|
+
return {
|
|
38
|
+
installed,
|
|
39
|
+
latest,
|
|
40
|
+
updateAvailable: false,
|
|
41
|
+
note: '_byan/.manifest.json missing — installed version cannot be determined. The project may be a fresh manual setup; consider running `npx create-byan-agent` to write the manifest.',
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const cmp = compareVersions(installed, latest);
|
|
45
|
+
return {
|
|
46
|
+
installed,
|
|
47
|
+
latest,
|
|
48
|
+
updateAvailable: cmp < 0,
|
|
49
|
+
isCurrent: cmp >= 0,
|
|
50
|
+
delta: cmp < 0 ? 'behind' : cmp > 0 ? 'ahead' : 'same',
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function formatApplyInstructions({ preview = false, force = false } = {}) {
|
|
55
|
+
const args = ['update'];
|
|
56
|
+
if (preview) args.push('--preview');
|
|
57
|
+
if (force) args.push('--force');
|
|
58
|
+
return {
|
|
59
|
+
command: `npx create-byan-agent ${args.join(' ')}`,
|
|
60
|
+
rationale: preview
|
|
61
|
+
? 'Preview mode — no files are written. Inspects diff against the latest npm template.'
|
|
62
|
+
: 'Apply update via the yanstaller pipeline. yanstaller backs up the project, diffs vs latest npm template, and merges only non-user-modified files unless --force is set.',
|
|
63
|
+
safety: [
|
|
64
|
+
'Backup is automatic before any write.',
|
|
65
|
+
'User-modified files are preserved (unless --force).',
|
|
66
|
+
'A rollback command (`npx create-byan-agent rollback`) is available afterwards.',
|
|
67
|
+
],
|
|
68
|
+
next: 'Ask the user to run the command above. Do not execute it from inside this tool.',
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function getInstalledVersion(projectRoot) {
|
|
73
|
+
// _byan/.manifest.json is the canonical source — written by yanstaller after
|
|
74
|
+
// every install/update. byan-mcp-server/package.json carries an unrelated
|
|
75
|
+
// local version and would lie if used as fallback, so we don't fall back at
|
|
76
|
+
// all: return 'unknown' and let callers surface that honestly.
|
|
77
|
+
const manifestPath = nodePath.join(projectRoot, '_byan', '.manifest.json');
|
|
78
|
+
try {
|
|
79
|
+
const raw = await fsPromises.readFile(manifestPath, 'utf8');
|
|
80
|
+
const manifest = JSON.parse(raw);
|
|
81
|
+
if (manifest && manifest.version) return manifest.version;
|
|
82
|
+
} catch {}
|
|
83
|
+
return 'unknown';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function getLatestVersion(packageName, { timeoutMs = 5000 } = {}) {
|
|
87
|
+
const url = `https://registry.npmjs.org/${packageName}/latest`;
|
|
88
|
+
return new Promise((resolve, reject) => {
|
|
89
|
+
const req = https.get(
|
|
90
|
+
url,
|
|
91
|
+
{ headers: { Accept: 'application/json' } },
|
|
92
|
+
(res) => {
|
|
93
|
+
if (res.statusCode !== 200) {
|
|
94
|
+
reject(new Error(`npm registry returned ${res.statusCode} for ${packageName}`));
|
|
95
|
+
res.resume();
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
let data = '';
|
|
99
|
+
res.on('data', (chunk) => { data += chunk; });
|
|
100
|
+
res.on('end', () => {
|
|
101
|
+
try {
|
|
102
|
+
resolve(JSON.parse(data).version);
|
|
103
|
+
} catch (err) {
|
|
104
|
+
reject(new Error(`Failed to parse npm registry response: ${err.message}`));
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
req.on('error', (err) => reject(new Error(`Failed to reach npm registry: ${err.message}`)));
|
|
110
|
+
req.setTimeout(timeoutMs, () => {
|
|
111
|
+
req.destroy(new Error(`npm registry request timed out after ${timeoutMs}ms`));
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function compareVersions(a, b) {
|
|
117
|
+
const pa = String(a).replace(/^v/, '').split('.').map((n) => Number(n) || 0);
|
|
118
|
+
const pb = String(b).replace(/^v/, '').split('.').map((n) => Number(n) || 0);
|
|
119
|
+
const len = Math.max(pa.length, pb.length);
|
|
120
|
+
for (let i = 0; i < len; i++) {
|
|
121
|
+
const na = pa[i] || 0;
|
|
122
|
+
const nb = pb[i] || 0;
|
|
123
|
+
if (na < nb) return -1;
|
|
124
|
+
if (na > nb) return 1;
|
|
125
|
+
}
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
@@ -45,6 +45,14 @@ import {
|
|
|
45
45
|
fcCheck,
|
|
46
46
|
fcParse,
|
|
47
47
|
} from './lib/cli.js';
|
|
48
|
+
import { checkForUpdate, formatApplyInstructions } from './lib/update.js';
|
|
49
|
+
import { fileURLToPath } from 'node:url';
|
|
50
|
+
|
|
51
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
52
|
+
const __dirname = nodePath.dirname(__filename);
|
|
53
|
+
// Resolve the host project root: server.js lives at
|
|
54
|
+
// {projectRoot}/_byan/mcp/byan-mcp-server/server.js, so go up three levels.
|
|
55
|
+
const PROJECT_ROOT = nodePath.resolve(__dirname, '..', '..', '..');
|
|
48
56
|
|
|
49
57
|
const BYAN_API_URL = process.env.BYAN_API_URL || 'http://localhost:3737';
|
|
50
58
|
const BYAN_API_TOKEN = process.env.BYAN_API_TOKEN || '';
|
|
@@ -956,6 +964,35 @@ const tools = [
|
|
|
956
964
|
additionalProperties: false,
|
|
957
965
|
},
|
|
958
966
|
},
|
|
967
|
+
{
|
|
968
|
+
name: 'byan_update_check',
|
|
969
|
+
description:
|
|
970
|
+
'Check whether the BYAN platform installed in this project is up to date. Read-only. Reads the installed version from _byan/.manifest.json (fallback: package.json), fetches the latest published version from the npm registry (registry.npmjs.org/create-byan-agent), compares them, and returns { installed, latest, updateAvailable, delta }. Network failures are reported (networkError) and treated as "do not block". Use at agent activation to surface updates without nagging.',
|
|
971
|
+
inputSchema: {
|
|
972
|
+
type: 'object',
|
|
973
|
+
properties: {},
|
|
974
|
+
additionalProperties: false,
|
|
975
|
+
},
|
|
976
|
+
},
|
|
977
|
+
{
|
|
978
|
+
name: 'byan_update_apply',
|
|
979
|
+
description:
|
|
980
|
+
'Returns the exact shell command the user must run to apply a BYAN update via the yanstaller pipeline (backup, diff vs latest npm template, merge non-user-modified files). Does NOT execute anything itself — update is destructive and must remain an explicit user action. Use after byan_update_check reports updateAvailable=true and the user has consented.',
|
|
981
|
+
inputSchema: {
|
|
982
|
+
type: 'object',
|
|
983
|
+
properties: {
|
|
984
|
+
preview: {
|
|
985
|
+
type: 'boolean',
|
|
986
|
+
description: 'If true, returns the --preview command (shows the diff without writing). Default: false.',
|
|
987
|
+
},
|
|
988
|
+
force: {
|
|
989
|
+
type: 'boolean',
|
|
990
|
+
description: 'If true, returns the --force command (overrides user-modified files). Default: false. Use with caution.',
|
|
991
|
+
},
|
|
992
|
+
},
|
|
993
|
+
additionalProperties: false,
|
|
994
|
+
},
|
|
995
|
+
},
|
|
959
996
|
];
|
|
960
997
|
|
|
961
998
|
const server = new Server(
|
|
@@ -1457,6 +1494,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1457
1494
|
return { content: [{ type: 'text', text: JSON.stringify(body, null, 2) }] };
|
|
1458
1495
|
}
|
|
1459
1496
|
|
|
1497
|
+
if (name === 'byan_update_check') {
|
|
1498
|
+
const status = await checkForUpdate(PROJECT_ROOT);
|
|
1499
|
+
return { content: [{ type: 'text', text: JSON.stringify(status, null, 2) }] };
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
if (name === 'byan_update_apply') {
|
|
1503
|
+
const instructions = formatApplyInstructions({
|
|
1504
|
+
preview: args.preview === true,
|
|
1505
|
+
force: args.force === true,
|
|
1506
|
+
});
|
|
1507
|
+
return { content: [{ type: 'text', text: JSON.stringify(instructions, null, 2) }] };
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1460
1510
|
throw new Error(`Unknown tool: ${name}`);
|
|
1461
1511
|
} catch (err) {
|
|
1462
1512
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-byan-agent",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.14.0",
|
|
4
4
|
"description": "BYAN v2.8 - Intelligent AI agent creator with ELO trust system + scientific fact-check + Hermes universal dispatcher + native Claude Code integration (hooks, skills, MCP server). Multi-platform (Copilot CLI, Claude Code, Codex). Merise Agile + TDD + 64 Mantras. ~54% LLM cost savings.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|