create-byan-agent 2.11.1 → 2.11.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.
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import fsSync from 'node:fs';
3
+ import fsPromises from 'node:fs/promises';
4
+ import nodePath from 'node:path';
2
5
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
6
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
7
  import {
@@ -94,6 +97,81 @@ async function apiRequest(path, options = {}) {
94
97
  return body;
95
98
  }
96
99
 
100
+ // Default filters — skip common build/vcs artifacts that pollute payload.
101
+ const DEFAULT_SKIP_DIRS = new Set([
102
+ '.git', 'node_modules', 'dist', 'build', '.next', 'coverage',
103
+ '__pycache__', '.venv', 'venv', '.pytest_cache', '.mypy_cache',
104
+ 'target', 'out', '.turbo', '.cache', '.DS_Store',
105
+ ]);
106
+ const DEFAULT_SKIP_FILE_PATTERNS = [
107
+ /\.log$/i, /\.sqlite$/i, /\.sqlite-journal$/i, /\.sqlite-wal$/i,
108
+ /\.lock$/i, /\.pid$/i,
109
+ ];
110
+ // Heuristic: treat as binary if content has NUL byte in first 8KB.
111
+ function looksBinary(buf) {
112
+ const sample = buf.subarray(0, Math.min(buf.length, 8192));
113
+ for (const b of sample) if (b === 0) return true;
114
+ return false;
115
+ }
116
+
117
+ // Hard limits — match W1's API guards so we fail fast client-side.
118
+ const MAX_FILES = 10000;
119
+ const MAX_TOTAL_BYTES = 100 * 1024 * 1024; // 100 MB
120
+
121
+ async function buildFilesPayload(absRoot, opts = {}) {
122
+ const skipDirs = opts.skipDirs || DEFAULT_SKIP_DIRS;
123
+ const skipPatterns = opts.skipPatterns || DEFAULT_SKIP_FILE_PATTERNS;
124
+ const maxFiles = opts.maxFiles || MAX_FILES;
125
+ const maxBytes = opts.maxBytes || MAX_TOTAL_BYTES;
126
+
127
+ const stat = await fsPromises.stat(absRoot);
128
+ if (!stat.isDirectory()) {
129
+ throw new Error(`Path is not a directory: ${absRoot}`);
130
+ }
131
+
132
+ const files = [];
133
+ let totalBytes = 0;
134
+
135
+ async function walk(dir) {
136
+ const entries = await fsPromises.readdir(dir, { withFileTypes: true });
137
+ for (const entry of entries) {
138
+ const full = nodePath.join(dir, entry.name);
139
+ if (entry.isDirectory()) {
140
+ if (skipDirs.has(entry.name)) continue;
141
+ await walk(full);
142
+ continue;
143
+ }
144
+ if (!entry.isFile()) continue;
145
+ if (skipPatterns.some((re) => re.test(entry.name))) continue;
146
+
147
+ const rel = nodePath.relative(absRoot, full).split(nodePath.sep).join('/');
148
+ const buf = await fsPromises.readFile(full);
149
+
150
+ totalBytes += buf.length;
151
+ if (files.length + 1 > maxFiles) {
152
+ throw new Error(
153
+ `Too many files (>${maxFiles}). Add to skipDirs or increase maxFiles.`
154
+ );
155
+ }
156
+ if (totalBytes > maxBytes) {
157
+ throw new Error(
158
+ `Total size exceeds ${(maxBytes / 1024 / 1024).toFixed(0)}MB. ` +
159
+ `Prune node_modules/dist/build dirs or increase maxBytes.`
160
+ );
161
+ }
162
+
163
+ if (looksBinary(buf)) {
164
+ files.push({ path: rel, content: buf.toString('base64'), encoding: 'base64' });
165
+ } else {
166
+ files.push({ path: rel, content: buf.toString('utf8'), encoding: 'utf8' });
167
+ }
168
+ }
169
+ }
170
+
171
+ await walk(absRoot);
172
+ return { files, count: files.length, totalBytes };
173
+ }
174
+
97
175
  const tools = [
98
176
  {
99
177
  name: 'byan_ping',
@@ -124,13 +202,13 @@ const tools = [
124
202
  {
125
203
  name: 'byan_import_project',
126
204
  description:
127
- 'Import a local project directory into byan_web. Scans BMAD artifacts (_bmad-output/, docs/, _bmad/_memory/). Requires auth.',
205
+ 'Import a local project directory into byan_web. Reads files from the local filesystem (client-side) and uploads them as a payload; works whether byan_web is local or remote. Skips .git, node_modules, dist, build, coverage, *.log, *.sqlite. Limits: 10000 files, 100MB total. Requires auth.',
128
206
  inputSchema: {
129
207
  type: 'object',
130
208
  properties: {
131
209
  path: {
132
210
  type: 'string',
133
- description: 'Absolute path to the project directory to import.',
211
+ description: 'Absolute path to the project directory on THIS machine (the MCP client). The API does not need filesystem access to this path.',
134
212
  },
135
213
  name: { type: 'string', description: 'Optional project name override.' },
136
214
  type: {
@@ -138,6 +216,14 @@ const tools = [
138
216
  enum: ['dev', 'training'],
139
217
  description: 'Project type. Default: dev.',
140
218
  },
219
+ maxFiles: {
220
+ type: 'number',
221
+ description: 'Override max file count (default 10000).',
222
+ },
223
+ maxBytes: {
224
+ type: 'number',
225
+ description: 'Override max total bytes (default 104857600 = 100MB).',
226
+ },
141
227
  },
142
228
  required: ['path'],
143
229
  additionalProperties: false,
@@ -820,10 +906,14 @@ const tools = [
820
906
  {
821
907
  name: 'byan_api_import_scan',
822
908
  description:
823
- 'Scan a local directory and report what would be imported. POST /api/import/scan. Requires BYAN_API_TOKEN.',
909
+ 'Scan a local directory and report what would be imported into byan_web. Reads files from the local filesystem (client-side) and uploads them as a payload; works whether byan_web is local or remote. Skips .git, node_modules, dist, build, coverage, *.log, *.sqlite. Limits: 10000 files, 100MB total. Requires auth.',
824
910
  inputSchema: {
825
911
  type: 'object',
826
- properties: { path: { type: 'string', description: 'Absolute path to scan.' } },
912
+ properties: {
913
+ path: { type: 'string', description: 'Absolute path to the directory on THIS machine (the MCP client). The API does not need filesystem access to this path.' },
914
+ maxFiles: { type: 'number', description: 'Override max file count (default 10000).' },
915
+ maxBytes: { type: 'number', description: 'Override max total bytes (default 104857600 = 100MB).' },
916
+ },
827
917
  required: ['path'],
828
918
  additionalProperties: false,
829
919
  },
@@ -831,10 +921,14 @@ const tools = [
831
921
  {
832
922
  name: 'byan_api_import_dry_run',
833
923
  description:
834
- 'Dry-run an import from a local directory (no writes). POST /api/import/dry-run. Requires BYAN_API_TOKEN.',
924
+ 'Dry-run an import from a local directory into byan_web (no writes). Reads files from the local filesystem (client-side) and uploads them as a payload; works whether byan_web is local or remote. Skips .git, node_modules, dist, build, coverage, *.log, *.sqlite. Limits: 10000 files, 100MB total. Requires auth.',
835
925
  inputSchema: {
836
926
  type: 'object',
837
- properties: { path: { type: 'string', description: 'Absolute path to dry-run.' } },
927
+ properties: {
928
+ path: { type: 'string', description: 'Absolute path to the directory on THIS machine (the MCP client). The API does not need filesystem access to this path.' },
929
+ maxFiles: { type: 'number', description: 'Override max file count (default 10000).' },
930
+ maxBytes: { type: 'number', description: 'Override max total bytes (default 104857600 = 100MB).' },
931
+ },
838
932
  required: ['path'],
839
933
  additionalProperties: false,
840
934
  },
@@ -899,10 +993,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
899
993
  if (!BYAN_API_TOKEN) {
900
994
  throw new Error('BYAN_API_TOKEN env var is required for this tool.');
901
995
  }
996
+ // Always upload files payload — works for both localhost and remote API.
997
+ // The API still accepts { path } for backward compat if caller insists,
998
+ // but the MCP client has no reason to use it (we can always read locally).
999
+ const { files } = await buildFilesPayload(args.path, {
1000
+ ...(args.maxFiles ? { maxFiles: args.maxFiles } : {}),
1001
+ ...(args.maxBytes ? { maxBytes: args.maxBytes } : {}),
1002
+ });
902
1003
  const body = await apiRequest('/api/import/project', {
903
1004
  method: 'POST',
904
1005
  body: JSON.stringify({
905
- path: args.path,
1006
+ files,
906
1007
  ...(args.name ? { name: args.name } : {}),
907
1008
  ...(args.type ? { type: args.type } : {}),
908
1009
  }),
@@ -1298,18 +1399,28 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1298
1399
 
1299
1400
  if (name === 'byan_api_import_scan') {
1300
1401
  requireToken();
1402
+ // Build files payload from client filesystem — works for remote byan_web.
1403
+ const { files } = await buildFilesPayload(args.path, {
1404
+ ...(args.maxFiles ? { maxFiles: args.maxFiles } : {}),
1405
+ ...(args.maxBytes ? { maxBytes: args.maxBytes } : {}),
1406
+ });
1301
1407
  const body = await apiRequest('/api/import/scan', {
1302
1408
  method: 'POST',
1303
- body: JSON.stringify({ path: args.path }),
1409
+ body: JSON.stringify({ files }),
1304
1410
  });
1305
1411
  return { content: [{ type: 'text', text: JSON.stringify(body, null, 2) }] };
1306
1412
  }
1307
1413
 
1308
1414
  if (name === 'byan_api_import_dry_run') {
1309
1415
  requireToken();
1416
+ // Build files payload from client filesystem — works for remote byan_web.
1417
+ const { files } = await buildFilesPayload(args.path, {
1418
+ ...(args.maxFiles ? { maxFiles: args.maxFiles } : {}),
1419
+ ...(args.maxBytes ? { maxBytes: args.maxBytes } : {}),
1420
+ });
1310
1421
  const body = await apiRequest('/api/import/dry-run', {
1311
1422
  method: 'POST',
1312
- body: JSON.stringify({ path: args.path }),
1423
+ body: JSON.stringify({ files }),
1313
1424
  });
1314
1425
  return { content: [{ type: 'text', text: JSON.stringify(body, null, 2) }] };
1315
1426
  }
@@ -1325,3 +1436,5 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1325
1436
 
1326
1437
  const transport = new StdioServerTransport();
1327
1438
  await server.connect(transport);
1439
+
1440
+ export { buildFilesPayload };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-byan-agent",
3
- "version": "2.11.1",
3
+ "version": "2.11.2",
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": {