create-byan-agent 2.9.10 → 2.11.1
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/node_modules/byan-platform-config/README.md +107 -0
- package/node_modules/byan-platform-config/index.js +16 -0
- package/node_modules/byan-platform-config/lib/env-config.js +128 -0
- package/node_modules/byan-platform-config/lib/mcp-config.js +95 -0
- package/node_modules/byan-platform-config/lib/token-prompt.js +61 -0
- package/node_modules/byan-platform-config/lib/url-utils.js +27 -0
- package/node_modules/byan-platform-config/lib/validate.js +44 -0
- package/node_modules/byan-platform-config/package.json +42 -0
- package/node_modules/fs-extra/LICENSE +15 -0
- package/node_modules/fs-extra/README.md +294 -0
- package/node_modules/fs-extra/lib/copy/copy-sync.js +176 -0
- package/node_modules/fs-extra/lib/copy/copy.js +180 -0
- package/node_modules/fs-extra/lib/copy/index.js +7 -0
- package/node_modules/fs-extra/lib/empty/index.js +39 -0
- package/node_modules/fs-extra/lib/ensure/file.js +66 -0
- package/node_modules/fs-extra/lib/ensure/index.js +23 -0
- package/node_modules/fs-extra/lib/ensure/link.js +64 -0
- package/node_modules/fs-extra/lib/ensure/symlink-paths.js +101 -0
- package/node_modules/fs-extra/lib/ensure/symlink-type.js +34 -0
- package/node_modules/fs-extra/lib/ensure/symlink.js +92 -0
- package/node_modules/fs-extra/lib/esm.mjs +68 -0
- package/node_modules/fs-extra/lib/fs/index.js +146 -0
- package/node_modules/fs-extra/lib/index.js +16 -0
- package/node_modules/fs-extra/lib/json/index.js +16 -0
- package/node_modules/fs-extra/lib/json/jsonfile.js +11 -0
- package/node_modules/fs-extra/lib/json/output-json-sync.js +12 -0
- package/node_modules/fs-extra/lib/json/output-json.js +12 -0
- package/node_modules/fs-extra/lib/mkdirs/index.js +14 -0
- package/node_modules/fs-extra/lib/mkdirs/make-dir.js +27 -0
- package/node_modules/fs-extra/lib/mkdirs/utils.js +21 -0
- package/node_modules/fs-extra/lib/move/index.js +7 -0
- package/node_modules/fs-extra/lib/move/move-sync.js +55 -0
- package/node_modules/fs-extra/lib/move/move.js +59 -0
- package/node_modules/fs-extra/lib/output-file/index.js +31 -0
- package/node_modules/fs-extra/lib/path-exists/index.js +12 -0
- package/node_modules/fs-extra/lib/remove/index.js +17 -0
- package/node_modules/fs-extra/lib/util/async.js +29 -0
- package/node_modules/fs-extra/lib/util/stat.js +159 -0
- package/node_modules/fs-extra/lib/util/utimes.js +36 -0
- package/node_modules/fs-extra/package.json +71 -0
- package/node_modules/graceful-fs/LICENSE +15 -0
- package/node_modules/graceful-fs/README.md +143 -0
- package/node_modules/graceful-fs/clone.js +23 -0
- package/node_modules/graceful-fs/graceful-fs.js +448 -0
- package/node_modules/graceful-fs/legacy-streams.js +118 -0
- package/node_modules/graceful-fs/package.json +53 -0
- package/node_modules/graceful-fs/polyfills.js +355 -0
- package/node_modules/jsonfile/LICENSE +15 -0
- package/node_modules/jsonfile/README.md +230 -0
- package/node_modules/jsonfile/index.js +88 -0
- package/node_modules/jsonfile/package.json +40 -0
- package/node_modules/jsonfile/utils.js +18 -0
- package/node_modules/universalify/LICENSE +20 -0
- package/node_modules/universalify/README.md +76 -0
- package/node_modules/universalify/index.js +24 -0
- package/node_modules/universalify/package.json +34 -0
- package/package.json +5 -1
- package/update-byan-agent/bin/update-byan-agent.js +13 -4
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# byan-platform-config
|
|
2
|
+
|
|
3
|
+
Shared platform config primitives for BYAN. Single source of truth for
|
|
4
|
+
`.mcp.json`, `.env`, `.claude/settings.local.json`, token prompting,
|
|
5
|
+
byan_web reachability validation, and URL normalization.
|
|
6
|
+
|
|
7
|
+
Consumed by both `create-byan-agent` (installer) and `update-byan-agent`
|
|
8
|
+
(update CLI) so the two CLIs stay in sync.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install byan-platform-config
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Public API
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
const {
|
|
20
|
+
mcpConfig,
|
|
21
|
+
envConfig,
|
|
22
|
+
tokenPrompt,
|
|
23
|
+
validate,
|
|
24
|
+
urlUtils,
|
|
25
|
+
} = require('byan-platform-config');
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### mcpConfig — `.mcp.json` management
|
|
29
|
+
|
|
30
|
+
| Function | Signature | Description |
|
|
31
|
+
|----------|-----------|-------------|
|
|
32
|
+
| `ensureMcpConfig` | `(projectRoot, { apiUrl, token }) → Promise<{ path }>` | READ-MERGE-WRITE. Strips `/api` suffix, preserves other mcpServers, preserves existing byan command/args. |
|
|
33
|
+
| `readMcpConfig` | `(projectRoot) → Promise<object\|null>` | Returns parsed `.mcp.json` or `null` if missing/malformed. |
|
|
34
|
+
| `mergeByanEntry` | `(existingConfig, { apiUrl, token }) → object` | Pure merge — no I/O. Returns a new config object. |
|
|
35
|
+
|
|
36
|
+
```js
|
|
37
|
+
await mcpConfig.ensureMcpConfig('/path/to/proj', {
|
|
38
|
+
apiUrl: 'http://localhost:3737',
|
|
39
|
+
token: 'byan_abc123',
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### envConfig — `.env` and `settings.local.json`
|
|
44
|
+
|
|
45
|
+
| Function | Signature | Description |
|
|
46
|
+
|----------|-----------|-------------|
|
|
47
|
+
| `updateSettingsLocal` | `(projectRoot, envVars) → Promise<{ path }>` | Merges vars into `.claude/settings.local.json`, preserves unrelated keys. |
|
|
48
|
+
| `updateDotenv` | `(projectRoot, envVars) → Promise<{ path }>` | Appends/updates `.env`, preserves comments and blank lines. |
|
|
49
|
+
| `readEnvToken` | `(projectRoot) → Promise<string\|null>` | Reads `BYAN_API_TOKEN` with fallback chain : `.env` then `settings.local.json`. |
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
await envConfig.updateDotenv('/path/to/proj', { BYAN_API_TOKEN: 'tok' });
|
|
53
|
+
const tok = await envConfig.readEnvToken('/path/to/proj');
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### tokenPrompt — interactive prompt
|
|
57
|
+
|
|
58
|
+
| Symbol | Signature / Value | Description |
|
|
59
|
+
|--------|-------------------|-------------|
|
|
60
|
+
| `promptForToken` | `() → Promise<{ configured, apiUrl?, token? }>` | Inquirer prompt (confirm + URL + password). |
|
|
61
|
+
| `ENV_KEYS` | `['BYAN_API_TOKEN', 'BYAN_API_URL']` | Canonical env var names. |
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
const res = await tokenPrompt.promptForToken();
|
|
65
|
+
if (res.configured) { /* use res.apiUrl, res.token */ }
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### validate — reachability probe
|
|
69
|
+
|
|
70
|
+
| Function | Signature | Description |
|
|
71
|
+
|----------|-----------|-------------|
|
|
72
|
+
| `validateByanWebReachability` | `({ apiUrl, token?, timeoutMs? }) → Promise<{ reachable, status?, latencyMs?, error? }>` | GET `/api/health`. Errors surface in the result object instead of throwing. Default timeout 5000 ms. |
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
const r = await validate.validateByanWebReachability({
|
|
76
|
+
apiUrl: 'http://localhost:3737',
|
|
77
|
+
token: 'byan_abc',
|
|
78
|
+
});
|
|
79
|
+
// → { reachable: true, status: 200, latencyMs: 12 }
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### urlUtils — URL normalization
|
|
83
|
+
|
|
84
|
+
| Function | Signature | Description |
|
|
85
|
+
|----------|-----------|-------------|
|
|
86
|
+
| `stripApiSuffix` | `(url) → string` | Strips trailing `/api`, `/api/`, `/api/v1`, etc. |
|
|
87
|
+
| `buildAuthHeader` | `(token) → { Authorization } \| {}` | `ApiKey` scheme for `byan_*` tokens, `Bearer` otherwise. |
|
|
88
|
+
|
|
89
|
+
```js
|
|
90
|
+
urlUtils.stripApiSuffix('http://x:1/api'); // → 'http://x:1'
|
|
91
|
+
urlUtils.buildAuthHeader('byan_abc'); // → { Authorization: 'ApiKey byan_abc' }
|
|
92
|
+
urlUtils.buildAuthHeader('eyJ...'); // → { Authorization: 'Bearer eyJ...' }
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Test
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
npm test
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Design invariants
|
|
102
|
+
|
|
103
|
+
- READ-MERGE-WRITE on every file operation — unknown keys are preserved.
|
|
104
|
+
- `ensureMcpConfig` keeps `mcpServers.byan.command` and `.args` if already set.
|
|
105
|
+
- `validateByanWebReachability` resolves instead of rejecting — errors surface in the result object.
|
|
106
|
+
- `stripApiSuffix` is idempotent and leaves URLs without a `/api` suffix untouched.
|
|
107
|
+
- `buildAuthHeader` returns `{}` (not `{ Authorization: undefined }`) when token is missing.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* byan-platform-config — shared platform config primitives.
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for .mcp.json management, .env/settings.local.json
|
|
5
|
+
* manipulation, token prompting, byan_web reachability validation, and URL
|
|
6
|
+
* normalization. Consumed by both install/ (create-byan-agent) and
|
|
7
|
+
* update-byan-agent/ so the two CLIs do not drift.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
module.exports = {
|
|
11
|
+
mcpConfig: require('./lib/mcp-config'),
|
|
12
|
+
envConfig: require('./lib/env-config'),
|
|
13
|
+
tokenPrompt: require('./lib/token-prompt'),
|
|
14
|
+
validate: require('./lib/validate'),
|
|
15
|
+
urlUtils: require('./lib/url-utils'),
|
|
16
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* .env and .claude/settings.local.json management.
|
|
3
|
+
*
|
|
4
|
+
* updateSettingsLocal : merge env vars into .claude/settings.local.json,
|
|
5
|
+
* preserving unrelated keys (permissions, hooks, etc.).
|
|
6
|
+
* updateDotenv : append/update .env lines, preserving comments and
|
|
7
|
+
* blank lines, replacing (not duplicating) existing keys.
|
|
8
|
+
* readEnvToken : fallback chain to read BYAN_API_TOKEN for migrations.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const fs = require('fs-extra');
|
|
13
|
+
|
|
14
|
+
async function readJsonOrEmpty(filePath) {
|
|
15
|
+
if (await fs.pathExists(filePath)) {
|
|
16
|
+
try {
|
|
17
|
+
return await fs.readJson(filePath);
|
|
18
|
+
} catch {
|
|
19
|
+
return {};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {string} projectRoot
|
|
27
|
+
* @param {Record<string,string>} envVars
|
|
28
|
+
* @returns {Promise<{ path: string }>}
|
|
29
|
+
*/
|
|
30
|
+
async function updateSettingsLocal(projectRoot, envVars) {
|
|
31
|
+
const filePath = path.join(projectRoot, '.claude', 'settings.local.json');
|
|
32
|
+
const current = await readJsonOrEmpty(filePath);
|
|
33
|
+
current.env = { ...(current.env || {}), ...envVars };
|
|
34
|
+
await fs.ensureDir(path.dirname(filePath));
|
|
35
|
+
await fs.writeJson(filePath, current, { spaces: 2 });
|
|
36
|
+
return { path: filePath };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {string} projectRoot
|
|
41
|
+
* @param {Record<string,string>} envVars
|
|
42
|
+
* @returns {Promise<{ path: string }>}
|
|
43
|
+
*/
|
|
44
|
+
async function updateDotenv(projectRoot, envVars) {
|
|
45
|
+
const filePath = path.join(projectRoot, '.env');
|
|
46
|
+
let content = '';
|
|
47
|
+
if (await fs.pathExists(filePath)) {
|
|
48
|
+
content = await fs.readFile(filePath, 'utf8');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const lines = content ? content.split(/\r?\n/) : [];
|
|
52
|
+
const keys = Object.keys(envVars);
|
|
53
|
+
const kept = lines.filter((line) => {
|
|
54
|
+
const trimmed = line.trim();
|
|
55
|
+
if (!trimmed || trimmed.startsWith('#')) return true;
|
|
56
|
+
const eq = trimmed.indexOf('=');
|
|
57
|
+
if (eq < 0) return true;
|
|
58
|
+
const key = trimmed.slice(0, eq).trim();
|
|
59
|
+
return !keys.includes(key);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
while (kept.length && kept[kept.length - 1] === '') kept.pop();
|
|
63
|
+
for (const key of keys) {
|
|
64
|
+
const val = envVars[key] ?? '';
|
|
65
|
+
kept.push(`${key}=${val}`);
|
|
66
|
+
}
|
|
67
|
+
kept.push('');
|
|
68
|
+
|
|
69
|
+
await fs.writeFile(filePath, kept.join('\n'), 'utf8');
|
|
70
|
+
return { path: filePath };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Parses a single `KEY=value` line, respecting surrounding double quotes.
|
|
75
|
+
* Returns null for comment/empty/malformed lines.
|
|
76
|
+
*/
|
|
77
|
+
function parseDotenvLine(line) {
|
|
78
|
+
const trimmed = line.trim();
|
|
79
|
+
if (!trimmed || trimmed.startsWith('#')) return null;
|
|
80
|
+
const eq = trimmed.indexOf('=');
|
|
81
|
+
if (eq < 0) return null;
|
|
82
|
+
const key = trimmed.slice(0, eq).trim();
|
|
83
|
+
let val = trimmed.slice(eq + 1).trim();
|
|
84
|
+
if (val.length >= 2 && val.startsWith('"') && val.endsWith('"')) {
|
|
85
|
+
val = val.slice(1, -1);
|
|
86
|
+
}
|
|
87
|
+
return { key, value: val };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Reads BYAN_API_TOKEN from .env first, falls back to
|
|
92
|
+
* .claude/settings.local.json env.BYAN_API_TOKEN. Returns null if neither
|
|
93
|
+
* contains a non-empty value.
|
|
94
|
+
*
|
|
95
|
+
* @param {string} projectRoot
|
|
96
|
+
* @returns {Promise<string|null>}
|
|
97
|
+
*/
|
|
98
|
+
async function readEnvToken(projectRoot) {
|
|
99
|
+
const dotenvPath = path.join(projectRoot, '.env');
|
|
100
|
+
if (await fs.pathExists(dotenvPath)) {
|
|
101
|
+
const content = await fs.readFile(dotenvPath, 'utf8');
|
|
102
|
+
for (const line of content.split(/\r?\n/)) {
|
|
103
|
+
const parsed = parseDotenvLine(line);
|
|
104
|
+
if (parsed && parsed.key === 'BYAN_API_TOKEN' && parsed.value) {
|
|
105
|
+
return parsed.value;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const settingsPath = path.join(projectRoot, '.claude', 'settings.local.json');
|
|
111
|
+
if (await fs.pathExists(settingsPath)) {
|
|
112
|
+
try {
|
|
113
|
+
const settings = await fs.readJson(settingsPath);
|
|
114
|
+
const tok = settings && settings.env && settings.env.BYAN_API_TOKEN;
|
|
115
|
+
if (typeof tok === 'string' && tok.length > 0) return tok;
|
|
116
|
+
} catch {
|
|
117
|
+
// ignore malformed json
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
module.exports = {
|
|
125
|
+
updateSettingsLocal,
|
|
126
|
+
updateDotenv,
|
|
127
|
+
readEnvToken,
|
|
128
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* .mcp.json management.
|
|
3
|
+
*
|
|
4
|
+
* READ-MERGE-WRITE semantics : preserves all existing mcpServers.* entries
|
|
5
|
+
* and, if byan entry already exists, preserves its command/args. Only the
|
|
6
|
+
* env.BYAN_API_URL and env.BYAN_API_TOKEN are authoritative from caller.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const fs = require('fs-extra');
|
|
11
|
+
const { stripApiSuffix } = require('./url-utils');
|
|
12
|
+
|
|
13
|
+
const MCP_SERVER_REL_PATH = '_byan/mcp/byan-mcp-server/server.js';
|
|
14
|
+
|
|
15
|
+
async function readJsonOrEmpty(filePath) {
|
|
16
|
+
if (await fs.pathExists(filePath)) {
|
|
17
|
+
try {
|
|
18
|
+
return await fs.readJson(filePath);
|
|
19
|
+
} catch {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Reads the project's .mcp.json.
|
|
28
|
+
*
|
|
29
|
+
* @param {string} projectRoot
|
|
30
|
+
* @returns {Promise<object|null>} parsed config or null if missing/malformed.
|
|
31
|
+
*/
|
|
32
|
+
async function readMcpConfig(projectRoot) {
|
|
33
|
+
const filePath = path.join(projectRoot, '.mcp.json');
|
|
34
|
+
if (!(await fs.pathExists(filePath))) return null;
|
|
35
|
+
try {
|
|
36
|
+
return await fs.readJson(filePath);
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Pure merge — no I/O. Returns a new config object with byan entry merged.
|
|
44
|
+
* Useful for migrations that inspect the diff before writing.
|
|
45
|
+
*
|
|
46
|
+
* @param {object} existingConfig — current parsed config (may be {} or {mcpServers:{...}})
|
|
47
|
+
* @param {{ apiUrl: string, token?: string }} opts
|
|
48
|
+
* @returns {object} new merged config
|
|
49
|
+
*/
|
|
50
|
+
function mergeByanEntry(existingConfig, { apiUrl, token } = {}) {
|
|
51
|
+
const cfg = existingConfig && typeof existingConfig === 'object' ? { ...existingConfig } : {};
|
|
52
|
+
cfg.mcpServers = { ...(cfg.mcpServers || {}) };
|
|
53
|
+
|
|
54
|
+
const existing = cfg.mcpServers.byan || {};
|
|
55
|
+
const cleanUrl = stripApiSuffix(apiUrl);
|
|
56
|
+
|
|
57
|
+
const env = { ...(existing.env || {}) };
|
|
58
|
+
env.BYAN_API_URL = cleanUrl;
|
|
59
|
+
if (token && typeof token === 'string' && token.length > 0) {
|
|
60
|
+
env.BYAN_API_TOKEN = token;
|
|
61
|
+
} else {
|
|
62
|
+
delete env.BYAN_API_TOKEN;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
cfg.mcpServers.byan = {
|
|
66
|
+
command: 'node',
|
|
67
|
+
args: [MCP_SERVER_REL_PATH],
|
|
68
|
+
...existing,
|
|
69
|
+
env,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
return cfg;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Ensures .mcp.json exists with a valid byan entry. READ-MERGE-WRITE.
|
|
77
|
+
*
|
|
78
|
+
* @param {string} projectRoot
|
|
79
|
+
* @param {{ apiUrl: string, token?: string }} opts
|
|
80
|
+
* @returns {Promise<{ path: string }>}
|
|
81
|
+
*/
|
|
82
|
+
async function ensureMcpConfig(projectRoot, { apiUrl, token } = {}) {
|
|
83
|
+
const filePath = path.join(projectRoot, '.mcp.json');
|
|
84
|
+
const current = await readJsonOrEmpty(filePath);
|
|
85
|
+
const merged = mergeByanEntry(current, { apiUrl, token });
|
|
86
|
+
await fs.writeJson(filePath, merged, { spaces: 2 });
|
|
87
|
+
return { path: filePath };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = {
|
|
91
|
+
ensureMcpConfig,
|
|
92
|
+
readMcpConfig,
|
|
93
|
+
mergeByanEntry,
|
|
94
|
+
MCP_SERVER_REL_PATH,
|
|
95
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive prompt for byan_web API URL + JWT/ApiKey token.
|
|
3
|
+
*
|
|
4
|
+
* Extracted verbatim from install's byan-web-integration so the UX does
|
|
5
|
+
* not drift between create-byan-agent and update-byan-agent.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const inquirer = require('inquirer');
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
|
|
11
|
+
const DEFAULT_API_URL = 'http://localhost:3737';
|
|
12
|
+
const ENV_KEYS = ['BYAN_API_TOKEN', 'BYAN_API_URL'];
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @returns {Promise<{ configured: boolean, apiUrl?: string, token?: string }>}
|
|
16
|
+
*/
|
|
17
|
+
async function promptForToken() {
|
|
18
|
+
const { wantsToken } = await inquirer.prompt([
|
|
19
|
+
{
|
|
20
|
+
type: 'confirm',
|
|
21
|
+
name: 'wantsToken',
|
|
22
|
+
message:
|
|
23
|
+
'Connect this project to your byan_web instance ? ' +
|
|
24
|
+
chalk.yellow('(service payant — requires a paid subscription to generate a token)'),
|
|
25
|
+
default: false,
|
|
26
|
+
},
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
if (!wantsToken) return { configured: false };
|
|
30
|
+
|
|
31
|
+
const answers = await inquirer.prompt([
|
|
32
|
+
{
|
|
33
|
+
type: 'input',
|
|
34
|
+
name: 'apiUrl',
|
|
35
|
+
message: 'byan_web API URL:',
|
|
36
|
+
default: DEFAULT_API_URL,
|
|
37
|
+
validate: (v) =>
|
|
38
|
+
/^https?:\/\//.test(v.trim()) || 'Must start with http:// or https://',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
type: 'password',
|
|
42
|
+
name: 'token',
|
|
43
|
+
message: 'byan_web JWT token (from POST /api/auth/login):',
|
|
44
|
+
mask: '*',
|
|
45
|
+
validate: (v) =>
|
|
46
|
+
(typeof v === 'string' && v.trim().length > 0) || 'Token cannot be empty',
|
|
47
|
+
},
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
configured: true,
|
|
52
|
+
apiUrl: answers.apiUrl.trim(),
|
|
53
|
+
token: answers.token.trim(),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = {
|
|
58
|
+
promptForToken,
|
|
59
|
+
ENV_KEYS,
|
|
60
|
+
DEFAULT_API_URL,
|
|
61
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL utilities for BYAN platform config.
|
|
3
|
+
*
|
|
4
|
+
* stripApiSuffix : normalize apiUrl so the MCP server.js doesn't double the
|
|
5
|
+
* /api prefix (see install B5 bugfix).
|
|
6
|
+
* buildAuthHeader : ApiKey scheme for user-generated byan_ tokens,
|
|
7
|
+
* Bearer scheme for JWT tokens.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
function stripApiSuffix(url) {
|
|
11
|
+
if (typeof url !== 'string' || url.length === 0) return url;
|
|
12
|
+
// Strip trailing /api, /api/, /api/v1, /api/v1/ etc. — keep protocol/host intact.
|
|
13
|
+
return url.replace(/\/api(?:\/v\d+)?\/?$/, '');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function buildAuthHeader(token) {
|
|
17
|
+
if (!token || typeof token !== 'string' || token.length === 0) {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
const scheme = token.startsWith('byan_') ? 'ApiKey' : 'Bearer';
|
|
21
|
+
return { Authorization: `${scheme} ${token}` };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = {
|
|
25
|
+
stripApiSuffix,
|
|
26
|
+
buildAuthHeader,
|
|
27
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* byan_web reachability probe.
|
|
3
|
+
*
|
|
4
|
+
* Never throws — always resolves with a plain result object. Uses
|
|
5
|
+
* AbortController to enforce a hard timeout (default 5s).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { stripApiSuffix, buildAuthHeader } = require('./url-utils');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {{ apiUrl: string, token?: string, timeoutMs?: number }} opts
|
|
12
|
+
* @returns {Promise<{ reachable: boolean, status?: number, latencyMs?: number, error?: string }>}
|
|
13
|
+
*/
|
|
14
|
+
async function validateByanWebReachability({ apiUrl, token, timeoutMs = 5000 }) {
|
|
15
|
+
const controller = new AbortController();
|
|
16
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
17
|
+
|
|
18
|
+
const headers = buildAuthHeader(token);
|
|
19
|
+
const url = `${stripApiSuffix(apiUrl)}/api/health`;
|
|
20
|
+
const t0 = Date.now();
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const res = await fetch(url, { method: 'GET', headers, signal: controller.signal });
|
|
24
|
+
const latencyMs = Date.now() - t0;
|
|
25
|
+
clearTimeout(timer);
|
|
26
|
+
|
|
27
|
+
if (res.status >= 200 && res.status < 400) {
|
|
28
|
+
return { reachable: true, status: res.status, latencyMs };
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
reachable: true,
|
|
32
|
+
status: res.status,
|
|
33
|
+
latencyMs,
|
|
34
|
+
error: `HTTP ${res.status}`,
|
|
35
|
+
};
|
|
36
|
+
} catch (err) {
|
|
37
|
+
clearTimeout(timer);
|
|
38
|
+
return { reachable: false, error: err.message || String(err) };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = {
|
|
43
|
+
validateByanWebReachability,
|
|
44
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "byan-platform-config",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Shared platform config lib for BYAN install/update (.mcp.json, .env, token, validate, url)",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "jest"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"byan",
|
|
11
|
+
"platform-config",
|
|
12
|
+
"mcp",
|
|
13
|
+
"byan-web"
|
|
14
|
+
],
|
|
15
|
+
"author": "Yan",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/Yan-Acadenice/BYAN.git",
|
|
20
|
+
"directory": "install/packages/platform-config"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://github.com/Yan-Acadenice/BYAN#readme",
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"chalk": "^4.1.2",
|
|
28
|
+
"fs-extra": "^11.2.0",
|
|
29
|
+
"inquirer": "^8.2.5"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"jest": "^29.7.0"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=18.0.0"
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"index.js",
|
|
39
|
+
"lib/",
|
|
40
|
+
"README.md"
|
|
41
|
+
]
|
|
42
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
(The MIT License)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2011-2024 JP Richardson
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
|
|
6
|
+
(the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify,
|
|
7
|
+
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
11
|
+
|
|
12
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
|
13
|
+
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
|
|
14
|
+
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
15
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|