biaws 0.5.3 → 0.6.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/README.md +4 -0
- package/docs/chatgpt.md +19 -0
- package/docs/instance-lifecycle.md +14 -0
- package/docs/releasing.md +8 -0
- package/package.json +1 -1
- package/src/cli/workspace/agent/configure/chatgpt.js +1 -0
- package/src/commands/agent.js +1 -1
- package/src/commands/configure/chatgpt.js +17 -0
- package/src/commands/instance/setup.js +5 -1
- package/src/commands/instance/status.js +11 -1
- package/src/commands/instance/update.js +13 -2
- package/src/instance/command.js +1 -0
- package/src/instance/mcp.js +169 -0
- package/src/instance/release.js +10 -8
- package/src/instance/service.js +17 -4
package/README.md
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
CLI do Bondia Workspaces para administrar instalações, configurar o acesso
|
|
4
4
|
global e operar os recursos do workspace associado à pasta atual.
|
|
5
5
|
|
|
6
|
+
Setup/update aceitam MCP HTTP/OAuth opcional. Veja
|
|
7
|
+
[configuração ChatGPT](docs/chatgpt.md) e o
|
|
8
|
+
[runbook de operação](../docs/chatgpt-operations.md).
|
|
9
|
+
|
|
6
10
|
## Instalação
|
|
7
11
|
|
|
8
12
|
```bash
|
package/docs/chatgpt.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Conexão direta ao ChatGPT
|
|
2
|
+
|
|
3
|
+
Habilite com `biaws admin instance setup --mcp-http` ou
|
|
4
|
+
`biaws admin instance update NOME --mcp-http`. URL pública exige HTTPS; loopback
|
|
5
|
+
HTTP é apenas para desenvolvimento. Atualizar uma instância continua sujeito ao
|
|
6
|
+
fluxo de backup do comando update. O CLI gera segredo aleatório dedicado em
|
|
7
|
+
arquivo 0600 por instância e preserva-o em reconfigurações.
|
|
8
|
+
|
|
9
|
+
`update NOME --no-mcp-http` desabilita a entrada, preservando replica set,
|
|
10
|
+
configuração e dados OAuth. `status NOME --diagnose-mcp --json` verifica discovery,
|
|
11
|
+
issuer/resource, PKCE, API e challenge sem solicitar senha. Os resultados refletem
|
|
12
|
+
o acesso desta máquina; não comprovam acesso pela infraestrutura do ChatGPT.
|
|
13
|
+
|
|
14
|
+
`biaws workspace agent configure chatgpt --url https://instancia.example --json`
|
|
15
|
+
mostra a URL /mcp e os passos de login/consentimento no navegador. Acrescente
|
|
16
|
+
`--diagnose` para verificações de rede. Esse comando não exige API key nem
|
|
17
|
+
escreve credenciais no projeto. Codex/Claude continuam com stdio.
|
|
18
|
+
A URL pública e o workspace autorizado são escolhas diferentes: cada conexão
|
|
19
|
+
OAuth é vinculada no servidor ao workspace selecionado no consentimento.
|
|
@@ -137,3 +137,17 @@ O restore valida checksum, descriptografia, versão e entradas do tar antes de
|
|
|
137
137
|
substituir dados. Portas, URLs e caminhos de storage do destino são
|
|
138
138
|
preservados. Em falhas após a pausa, o mecanismo tenta recompor os serviços e
|
|
139
139
|
mantém o erro original como evidência.
|
|
140
|
+
|
|
141
|
+
O schema 2 inclui a versão MCP implantável e aceita a leitura dos snapshots de
|
|
142
|
+
schema 1. Use CLI >=0.6.0; uma mudança isolada de MCP passa a exigir update.
|
|
143
|
+
Start/stop/status/update conservam o overlay e o perfil persistidos por instância.
|
|
144
|
+
Ao desabilitar, o update para o container MCP e mantém o replica set e suas chaves.
|
|
145
|
+
Os scripts gerados e os comandos de backup/restore usam o mesmo seletor de Compose.
|
|
146
|
+
|
|
147
|
+
Backup criptografado inclui o segredo dedicado e todo o banco, incluindo chaves
|
|
148
|
+
de assinatura, concessões e tokens. Restore mantém URL, flags e paths do destino
|
|
149
|
+
e restaura o segredo em arquivo privado. Se a origem pública mudou, remove os
|
|
150
|
+
dados OAuth e as chaves de assinatura restaurados antes de reiniciar: cada usuário
|
|
151
|
+
deve autorizar uma nova conexão. O snapshot de release continua invalidado até
|
|
152
|
+
um update bem-sucedido. Restore não habilita MCP automaticamente em um destino
|
|
153
|
+
que não estava configurado para ele.
|
package/docs/releasing.md
CHANGED
|
@@ -48,6 +48,14 @@ global. Depois da publicação, confirme também `npx biaws@<versão> --help`.
|
|
|
48
48
|
|
|
49
49
|
## Compatibilidade e migração
|
|
50
50
|
|
|
51
|
+
A série `0.6.0` suporta os manifests de plataforma schema 1 e 2 e fixa
|
|
52
|
+
`biaws-mcp@0.15.0`. Publique esse MCP antes de `biaws@0.6.0`; o CLI antigo
|
|
53
|
+
não deve aplicar o manifesto schema 2. Atualizar somente o CLI não atualiza
|
|
54
|
+
instâncias. Para MCP HTTP, use setup/update com `--mcp-http` e os requisitos
|
|
55
|
+
HTTPS/replica set de [operação ChatGPT](../../docs/chatgpt-operations.md).
|
|
56
|
+
`workspace agent configure chatgpt --url https://<host> --json` fornece
|
|
57
|
+
instruções sem API key ou senha; a conta é autenticada no consentimento OAuth.
|
|
58
|
+
|
|
51
59
|
O shebang canônico está em `biaws-cli/bin/biaws.js`. O pacote npm cria o comando
|
|
52
60
|
`biaws`; no checkout, `npm --prefix biaws-cli link` oferece a mesma experiência.
|
|
53
61
|
O antigo `node biaws-cli/src/index.js ...` continua funcional durante a série
|
package/package.json
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "../../../../commands/configure/chatgpt.js";
|
package/src/commands/agent.js
CHANGED
|
@@ -11,7 +11,7 @@ import { runSkillsCommand } from "./skills.js";
|
|
|
11
11
|
const CODEX_BEGIN = "# BEGIN BIAWS MANAGED MCP";
|
|
12
12
|
const CODEX_END = "# END BIAWS MANAGED MCP";
|
|
13
13
|
export const MCP_PACKAGE_NAME = "biaws-mcp";
|
|
14
|
-
export const MCP_PACKAGE_VERSION = "0.
|
|
14
|
+
export const MCP_PACKAGE_VERSION = "0.15.0";
|
|
15
15
|
export const MCP_PACKAGE_SPEC = `${MCP_PACKAGE_NAME}@${MCP_PACKAGE_VERSION}`;
|
|
16
16
|
const MCP_COMMAND = "npx";
|
|
17
17
|
const MCP_ARGS = ["--yes", MCP_PACKAGE_SPEC];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Flags } from "@oclif/core";
|
|
2
|
+
import { BaseCommand } from "../../baseCommands.js";
|
|
3
|
+
import { chatgptConnectionInstructions, diagnoseMcp } from "../../instance/mcp.js";
|
|
4
|
+
|
|
5
|
+
export default class ConfigureChatgpt extends BaseCommand {
|
|
6
|
+
static description = "orienta conexão direta ao ChatGPT usando OAuth, sem capturar senha ou chave";
|
|
7
|
+
static flags = {
|
|
8
|
+
url: Flags.string({ description: "origem pública HTTPS da instância", required: true }),
|
|
9
|
+
diagnose: Flags.boolean({ description: "verifica discovery, saúde e challenge a partir desta máquina" }),
|
|
10
|
+
json: Flags.boolean({ description: "emite JSON" }),
|
|
11
|
+
};
|
|
12
|
+
async run() {
|
|
13
|
+
const { flags } = await this.parse(ConfigureChatgpt);
|
|
14
|
+
const result = flags.diagnose ? await diagnoseMcp(flags.url) : chatgptConnectionInstructions(flags.url);
|
|
15
|
+
this.output({ json: flags.json }).result(flags.json ? result : JSON.stringify(result, null, 2));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
const setupFlags = {
|
|
15
15
|
...contextFlags,
|
|
16
16
|
...wizardFlags,
|
|
17
|
+
"mcp-http": Flags.boolean({ allowNo: true, description: "habilita MCP HTTP/OAuth (HTTPS público ou loopback)" }),
|
|
17
18
|
name: Flags.string({ char: "n", description: "nome da instância" }),
|
|
18
19
|
"admin-email": Flags.string({
|
|
19
20
|
description: "e-mail do administrador inicial",
|
|
@@ -287,7 +288,10 @@ export default class InstanceSetup extends LocalInstanceCommand {
|
|
|
287
288
|
terminal: this.adapters.terminal,
|
|
288
289
|
});
|
|
289
290
|
const configuration = await buildSetupConfiguration(
|
|
290
|
-
|
|
291
|
+
{
|
|
292
|
+
...Object.fromEntries(definition.questions.map((question) => [question.name, plan.get(question.name)])),
|
|
293
|
+
mcpHttp: flags["mcp-http"],
|
|
294
|
+
},
|
|
291
295
|
context,
|
|
292
296
|
this.adapters.filesystem,
|
|
293
297
|
);
|
|
@@ -3,12 +3,14 @@ import { Flags } from "@oclif/core";
|
|
|
3
3
|
import { LocalInstanceCommand } from "../../baseCommands.js";
|
|
4
4
|
import { contextFlags, contextInput, instanceArgument, writeResult } from "../../instance/command.js";
|
|
5
5
|
import { getInstance, operateInstance } from "../../instance/service.js";
|
|
6
|
+
import { diagnoseMcp, mcpInstanceSummary } from "../../instance/mcp.js";
|
|
6
7
|
|
|
7
8
|
export default class InstanceStatus extends LocalInstanceCommand {
|
|
8
9
|
static description = "consulta o estado dos containers de uma instância";
|
|
9
10
|
static args = { instance: instanceArgument };
|
|
10
11
|
static flags = {
|
|
11
12
|
...contextFlags,
|
|
13
|
+
"diagnose-mcp": Flags.boolean({ description: "valida discovery e saúde da integração OAuth/MCP" }),
|
|
12
14
|
json: Flags.boolean({ description: "emite JSON" }),
|
|
13
15
|
};
|
|
14
16
|
|
|
@@ -17,6 +19,14 @@ export default class InstanceStatus extends LocalInstanceCommand {
|
|
|
17
19
|
const context = await this.localContext(contextInput(flags, args.instance));
|
|
18
20
|
const instance = await getInstance(context, this.adapters.filesystem, args.instance);
|
|
19
21
|
const result = await operateInstance(instance, context, this.adapters.processRunner, "status");
|
|
20
|
-
|
|
22
|
+
result.mcp = mcpInstanceSummary(instance);
|
|
23
|
+
if (flags["diagnose-mcp"]) result.mcp.diagnostics = await diagnoseMcp(instance.publicUrl);
|
|
24
|
+
writeResult(
|
|
25
|
+
this,
|
|
26
|
+
result,
|
|
27
|
+
(value) =>
|
|
28
|
+
(value.output || `Nenhum container ativo para ${value.instance}.`) + "\n" + JSON.stringify(value.mcp, null, 2),
|
|
29
|
+
flags.json,
|
|
30
|
+
);
|
|
21
31
|
}
|
|
22
32
|
}
|
|
@@ -2,6 +2,7 @@ import { Flags } from "@oclif/core";
|
|
|
2
2
|
|
|
3
3
|
import { LocalInstanceCommand } from "../../baseCommands.js";
|
|
4
4
|
import { contextFlags, contextInput, instanceArgument } from "../../instance/command.js";
|
|
5
|
+
import { configureMcpInstance } from "../../instance/mcp.js";
|
|
5
6
|
import {
|
|
6
7
|
backupArguments,
|
|
7
8
|
executeInstanceScript,
|
|
@@ -85,6 +86,11 @@ export default class InstanceUpdate extends LocalInstanceCommand {
|
|
|
85
86
|
static args = { instance: instanceArgument };
|
|
86
87
|
static flags = {
|
|
87
88
|
...contextFlags,
|
|
89
|
+
"mcp-http": Flags.boolean({
|
|
90
|
+
allowNo: true,
|
|
91
|
+
description: "habilita/desabilita MCP HTTP, preservando configuração",
|
|
92
|
+
exclusive: ["check"],
|
|
93
|
+
}),
|
|
88
94
|
"backup-output": Flags.string({
|
|
89
95
|
description: "arquivo de destino do backup anterior à atualização",
|
|
90
96
|
}),
|
|
@@ -109,19 +115,24 @@ export default class InstanceUpdate extends LocalInstanceCommand {
|
|
|
109
115
|
async run() {
|
|
110
116
|
const { args, flags } = await this.parse(InstanceUpdate);
|
|
111
117
|
const context = await this.localContext(contextInput(flags, args.instance));
|
|
112
|
-
|
|
118
|
+
let instance = await getInstance(context, this.adapters.filesystem, args.instance);
|
|
113
119
|
const versionStatus = await getInstanceUpdateStatus(instance, context, this.adapters.filesystem);
|
|
114
120
|
if (flags.check) {
|
|
115
121
|
this.output({ json: flags.json }).result(versionCheckResult(instance, versionStatus, flags.json));
|
|
116
122
|
return;
|
|
117
123
|
}
|
|
118
|
-
if (!versionStatus.updateRequired && !flags.force) {
|
|
124
|
+
if (!versionStatus.updateRequired && !flags.force && flags["mcp-http"] === undefined) {
|
|
119
125
|
this.output({ json: flags.json }).result(unchangedResult(instance, versionStatus, flags.json));
|
|
120
126
|
return;
|
|
121
127
|
}
|
|
122
128
|
|
|
123
129
|
await validateInstanceUpdate(instance, context, this.adapters.processRunner);
|
|
124
130
|
const backupOutput = await createUpdateBackup(this, instance, context, flags);
|
|
131
|
+
if (flags["mcp-http"] !== undefined) {
|
|
132
|
+
await configureMcpInstance(instance.envFile, flags["mcp-http"], this.adapters.filesystem);
|
|
133
|
+
instance = await getInstance(context, this.adapters.filesystem, args.instance);
|
|
134
|
+
await validateInstanceUpdate(instance, context, this.adapters.processRunner);
|
|
135
|
+
}
|
|
125
136
|
|
|
126
137
|
await updateInstance(
|
|
127
138
|
instance,
|
package/src/instance/command.js
CHANGED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
import { CliError } from "../core/errors.js";
|
|
4
|
+
import { parseEnv } from "../core/context.js";
|
|
5
|
+
import { atomicWrite } from "./release.js";
|
|
6
|
+
|
|
7
|
+
export function mcpPublicConfiguration(publicUrl) {
|
|
8
|
+
let url;
|
|
9
|
+
try {
|
|
10
|
+
url = new URL(publicUrl);
|
|
11
|
+
} catch {
|
|
12
|
+
throw new CliError("Informe uma origem pública HTTPS válida.", { code: "INVALID_MCP_PUBLIC_URL" });
|
|
13
|
+
}
|
|
14
|
+
if (
|
|
15
|
+
url.username ||
|
|
16
|
+
url.password ||
|
|
17
|
+
url.pathname !== "/" ||
|
|
18
|
+
url.search ||
|
|
19
|
+
url.hash ||
|
|
20
|
+
(url.protocol !== "https:" &&
|
|
21
|
+
!(url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)))
|
|
22
|
+
) {
|
|
23
|
+
throw new CliError("MCP exige HTTPS público; HTTP é permitido somente em loopback para desenvolvimento.", {
|
|
24
|
+
code: "INVALID_MCP_PUBLIC_URL",
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
publicUrl: url.origin,
|
|
29
|
+
resource: url.origin + "/mcp",
|
|
30
|
+
issuer: url.origin + "/api/auth",
|
|
31
|
+
scheme: url.protocol.slice(0, -1),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export function mcpInstanceSummary(instance) {
|
|
35
|
+
return {
|
|
36
|
+
configured: instance.env.BIAWS_MCP_CONFIGURED === "true",
|
|
37
|
+
enabled: instance.env.BIAWS_MCP_HTTP_ENABLED === "true",
|
|
38
|
+
resource: instance.env.BIAWS_MCP_PUBLIC_URL || "",
|
|
39
|
+
issuer: instance.env.BIAWS_PUBLIC_URL ? instance.env.BIAWS_PUBLIC_URL + "/api/auth" : "",
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function replaceEnvironment(contents, values) {
|
|
43
|
+
let next = contents;
|
|
44
|
+
for (const [key, value] of Object.entries(values)) {
|
|
45
|
+
const line = key + "=" + value;
|
|
46
|
+
const pattern = new RegExp("^" + key + "=.*$", "gmu");
|
|
47
|
+
next = pattern.test(next) ? next.replace(pattern, () => line) : next.replace(/\s*$/u, "") + "\n" + line + "\n";
|
|
48
|
+
}
|
|
49
|
+
return next;
|
|
50
|
+
}
|
|
51
|
+
export async function configureMcpInstance(
|
|
52
|
+
envFile,
|
|
53
|
+
enabled,
|
|
54
|
+
filesystem,
|
|
55
|
+
{ uid = process.getuid?.() ?? 1000, gid = process.getgid?.() ?? 1000 } = {},
|
|
56
|
+
) {
|
|
57
|
+
const contents = await filesystem.readFile(envFile, "utf8");
|
|
58
|
+
const env = parseEnv(contents);
|
|
59
|
+
const configured = enabled || env.BIAWS_MCP_CONFIGURED === "true";
|
|
60
|
+
const values = { BIAWS_MCP_HTTP_ENABLED: String(enabled) };
|
|
61
|
+
if (configured) {
|
|
62
|
+
const config = mcpPublicConfiguration(env.BIAWS_PUBLIC_URL);
|
|
63
|
+
const secretPath = env.BIAWS_MCP_SERVICE_SECRET_PATH || path.join(path.dirname(envFile), ".mcp-service-secret");
|
|
64
|
+
if (/[\r\n$#"'\\]/u.test(secretPath))
|
|
65
|
+
throw new CliError("Caminho de segredo incompatível com Compose.", { code: "INVALID_MCP_SECRET_PATH" });
|
|
66
|
+
let secret;
|
|
67
|
+
try {
|
|
68
|
+
secret = await filesystem.readFile(secretPath, "utf8");
|
|
69
|
+
} catch (error) {
|
|
70
|
+
if (error.code !== "ENOENT") throw error;
|
|
71
|
+
secret = randomBytes(32).toString("hex");
|
|
72
|
+
await filesystem.writeFile(secretPath, secret, { mode: 0o600, flag: "wx" });
|
|
73
|
+
}
|
|
74
|
+
if (secret.trim().length < 32 || secret.length > 4096)
|
|
75
|
+
throw new CliError("Segredo MCP inválido; use um arquivo dedicado de 32 a 4096 caracteres.", {
|
|
76
|
+
code: "INVALID_MCP_SERVICE_SECRET",
|
|
77
|
+
});
|
|
78
|
+
await filesystem.chmod(secretPath, 0o600);
|
|
79
|
+
Object.assign(values, {
|
|
80
|
+
BIAWS_MCP_CONFIGURED: "true",
|
|
81
|
+
BIAWS_MCP_PUBLIC_URL: config.resource,
|
|
82
|
+
BIAWS_PUBLIC_SCHEME: config.scheme,
|
|
83
|
+
BIAWS_MCP_SERVICE_SECRET_PATH: secretPath,
|
|
84
|
+
BIAWS_MCP_UID: env.BIAWS_MCP_UID || uid,
|
|
85
|
+
BIAWS_MCP_GID: env.BIAWS_MCP_GID || gid,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
await atomicWrite(envFile, replaceEnvironment(contents, values), filesystem);
|
|
89
|
+
return { configured, enabled, ...(configured ? mcpPublicConfiguration(env.BIAWS_PUBLIC_URL) : {}) };
|
|
90
|
+
}
|
|
91
|
+
export async function diagnoseMcp(publicUrl, { fetchImpl = fetch } = {}) {
|
|
92
|
+
const config = mcpPublicConfiguration(publicUrl);
|
|
93
|
+
const checks = [];
|
|
94
|
+
const get = async (name, url) => {
|
|
95
|
+
try {
|
|
96
|
+
const response = await fetchImpl(url, { redirect: "error", signal: AbortSignal.timeout(5000) });
|
|
97
|
+
if (!response.ok) throw new Error("HTTP " + response.status);
|
|
98
|
+
const result = await response.json();
|
|
99
|
+
checks.push({ name, ok: true });
|
|
100
|
+
return result;
|
|
101
|
+
} catch {
|
|
102
|
+
checks.push({ name, ok: false, message: "Não foi possível ler " + url + ". Verifique TLS, proxy e serviço." });
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
const protectedResource = await get(
|
|
107
|
+
"resource-discovery",
|
|
108
|
+
config.publicUrl + "/.well-known/oauth-protected-resource/mcp",
|
|
109
|
+
);
|
|
110
|
+
const authorization = await get(
|
|
111
|
+
"authorization-discovery",
|
|
112
|
+
config.publicUrl + "/.well-known/oauth-authorization-server/api/auth",
|
|
113
|
+
);
|
|
114
|
+
if (protectedResource)
|
|
115
|
+
checks.push({
|
|
116
|
+
name: "resource-binding",
|
|
117
|
+
ok:
|
|
118
|
+
protectedResource.resource === config.resource &&
|
|
119
|
+
protectedResource.authorization_servers?.includes(config.issuer),
|
|
120
|
+
});
|
|
121
|
+
if (authorization)
|
|
122
|
+
checks.push({
|
|
123
|
+
name: "issuer-and-pkce",
|
|
124
|
+
ok: authorization.issuer === config.issuer && authorization.code_challenge_methods_supported?.includes("S256"),
|
|
125
|
+
});
|
|
126
|
+
await get("api-health", config.publicUrl + "/api/health");
|
|
127
|
+
try {
|
|
128
|
+
const response = await fetchImpl(config.resource, {
|
|
129
|
+
method: "POST",
|
|
130
|
+
redirect: "error",
|
|
131
|
+
signal: AbortSignal.timeout(5000),
|
|
132
|
+
headers: { "content-type": "application/json", accept: "application/json, text/event-stream" },
|
|
133
|
+
body: JSON.stringify({
|
|
134
|
+
jsonrpc: "2.0",
|
|
135
|
+
id: 1,
|
|
136
|
+
method: "initialize",
|
|
137
|
+
params: { protocolVersion: "2025-11-25", capabilities: {}, clientInfo: { name: "biaws-doctor", version: "1" } },
|
|
138
|
+
}),
|
|
139
|
+
});
|
|
140
|
+
checks.push({
|
|
141
|
+
name: "mcp-oauth-challenge",
|
|
142
|
+
ok:
|
|
143
|
+
response.status === 401 &&
|
|
144
|
+
(response.headers.get("www-authenticate") || "").includes("oauth-protected-resource"),
|
|
145
|
+
});
|
|
146
|
+
await response.body?.cancel();
|
|
147
|
+
} catch {
|
|
148
|
+
checks.push({ name: "mcp-oauth-challenge", ok: false });
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
...config,
|
|
152
|
+
ok: checks.every((check) => check.ok),
|
|
153
|
+
checks,
|
|
154
|
+
observation: "Verificações realizadas a partir desta máquina. Acessibilidade pelo ChatGPT ainda não comprovada.",
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
export function chatgptConnectionInstructions(publicUrl) {
|
|
158
|
+
const config = mcpPublicConfiguration(publicUrl);
|
|
159
|
+
return {
|
|
160
|
+
...config,
|
|
161
|
+
authentication: "OAuth",
|
|
162
|
+
steps: [
|
|
163
|
+
"No ChatGPT, crie uma conexão MCP personalizada com a URL do recurso e autenticação OAuth.",
|
|
164
|
+
"Autentique no navegador com sua conta BIAWS, escolha o workspace e consinta os acessos.",
|
|
165
|
+
"Para outro workspace, crie outra autorização. Revogue conexões em Minha conta no BIAWS.",
|
|
166
|
+
],
|
|
167
|
+
catalog: "O plugin distribuível não implica elegibilidade ou publicação no catálogo.",
|
|
168
|
+
};
|
|
169
|
+
}
|
package/src/instance/release.js
CHANGED
|
@@ -2,7 +2,8 @@ import path from "node:path";
|
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { CliError } from "../core/errors.js";
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const LEGACY_COMPONENTS = ["biaws-api", "biaws-ui", "biaws-monitor-executor"];
|
|
6
|
+
const COMPONENTS = [...LEGACY_COMPONENTS, "biaws-mcp"];
|
|
6
7
|
const VERSION = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/u;
|
|
7
8
|
|
|
8
9
|
function invalid(message) {
|
|
@@ -11,22 +12,23 @@ function invalid(message) {
|
|
|
11
12
|
|
|
12
13
|
export function validateRelease(value) {
|
|
13
14
|
if (!value || typeof value !== "object" || Array.isArray(value)) invalid("Manifesto de release inválido.");
|
|
14
|
-
if (value.schemaVersion
|
|
15
|
+
if (![1, 2].includes(value.schemaVersion)) invalid("schemaVersion do manifesto não suportada; atualize o CLI.");
|
|
16
|
+
const expected = value.schemaVersion === 1 ? LEGACY_COMPONENTS : COMPONENTS;
|
|
15
17
|
if (typeof value.version !== "string" || !VERSION.test(value.version)) invalid("Versão da release inválida.");
|
|
16
18
|
if (!Number.isSafeInteger(value.deploymentRevision) || value.deploymentRevision < 1) {
|
|
17
19
|
invalid("deploymentRevision deve ser um inteiro positivo.");
|
|
18
20
|
}
|
|
19
|
-
if (!value.components || Object.keys(value.components).sort().join() !== [...
|
|
20
|
-
invalid("O manifesto deve conter
|
|
21
|
+
if (!value.components || Object.keys(value.components).sort().join() !== [...expected].sort().join()) {
|
|
22
|
+
invalid("O manifesto deve conter exatamente os componentes do schema declarado.");
|
|
21
23
|
}
|
|
22
24
|
const components = {};
|
|
23
|
-
for (const component of
|
|
25
|
+
for (const component of expected) {
|
|
24
26
|
const version = value.components[component];
|
|
25
27
|
if (typeof version !== "string" || !VERSION.test(version)) invalid(`Versão inválida: ${component}.`);
|
|
26
28
|
components[component] = version;
|
|
27
29
|
}
|
|
28
30
|
return Object.freeze({
|
|
29
|
-
schemaVersion:
|
|
31
|
+
schemaVersion: value.schemaVersion,
|
|
30
32
|
version: value.version,
|
|
31
33
|
deploymentRevision: value.deploymentRevision,
|
|
32
34
|
components: Object.freeze(components),
|
|
@@ -55,7 +57,7 @@ export async function readRelease(file, filesystem, { optional = false } = {}) {
|
|
|
55
57
|
|
|
56
58
|
export async function readAvailableRelease(root, filesystem) {
|
|
57
59
|
const release = await readRelease(path.join(root, "release.json"), filesystem);
|
|
58
|
-
for (const component of
|
|
60
|
+
for (const component of Object.keys(release.components)) {
|
|
59
61
|
const manifest = JSON.parse(await filesystem.readFile(path.join(root, component, "package.json"), "utf8"));
|
|
60
62
|
if (manifest.version !== release.components[component]) {
|
|
61
63
|
invalid(`release.json diverge de ${component}/package.json; atualize o manifesto antes de implantar.`);
|
|
@@ -66,7 +68,7 @@ export async function readAvailableRelease(root, filesystem) {
|
|
|
66
68
|
|
|
67
69
|
export function releaseDifferences(current, available) {
|
|
68
70
|
const differences = [];
|
|
69
|
-
for (const field of ["version", "deploymentRevision", ...COMPONENTS]) {
|
|
71
|
+
for (const field of ["schemaVersion", "version", "deploymentRevision", ...COMPONENTS]) {
|
|
70
72
|
const before = COMPONENTS.includes(field) ? current?.components[field] : current?.[field];
|
|
71
73
|
const after = COMPONENTS.includes(field) ? available.components[field] : available[field];
|
|
72
74
|
if (before !== after) differences.push({ field, current: before ?? null, available: after });
|
package/src/instance/service.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import os from "node:os";
|
|
3
|
+
import { mcpInstanceSummary, mcpPublicConfiguration } from "./mcp.js";
|
|
3
4
|
import {
|
|
4
5
|
appliedReleasePath,
|
|
5
6
|
readAvailableRelease,
|
|
@@ -137,6 +138,7 @@ export async function listInstances(context, filesystem) {
|
|
|
137
138
|
uiPort: Number(env.BIAWS_UI_PORT || 4_400),
|
|
138
139
|
publicUrl: env.BIAWS_PUBLIC_URL || `http://localhost:${env.BIAWS_UI_PORT || 4_400}`,
|
|
139
140
|
storage: Object.values(STORAGE_FIELDS).some((key) => env[key]) ? "directories" : "volumes",
|
|
141
|
+
mcp: mcpInstanceSummary({ env }),
|
|
140
142
|
env,
|
|
141
143
|
});
|
|
142
144
|
}
|
|
@@ -238,11 +240,14 @@ export async function buildSetupConfiguration(values, context, filesystem) {
|
|
|
238
240
|
const storageChanged = Boolean(
|
|
239
241
|
previousStorage && Object.keys(STORAGE_FIELDS).some((key) => previousStorage[key] !== storage[key]),
|
|
240
242
|
);
|
|
243
|
+
const publicUrl = validatePublicUrl(values.publicUrl || existing?.publicUrl || `http://localhost:${ports.uiPort}`);
|
|
244
|
+
if (values.mcpHttp === true || (values.mcpHttp === undefined && existing?.mcp.enabled))
|
|
245
|
+
mcpPublicConfiguration(publicUrl);
|
|
241
246
|
return Object.freeze({
|
|
242
247
|
name,
|
|
243
248
|
...ports,
|
|
244
249
|
...storage,
|
|
245
|
-
publicUrl
|
|
250
|
+
publicUrl,
|
|
246
251
|
storage: values.storage,
|
|
247
252
|
storageChanged,
|
|
248
253
|
adminEmail: String(values.adminEmail).trim(),
|
|
@@ -257,6 +262,7 @@ export async function buildSetupConfiguration(values, context, filesystem) {
|
|
|
257
262
|
apiKeyRateLimitMax: integer(values.apiKeyRateLimitMax, "Rate limit da chave de API"),
|
|
258
263
|
apiKeyRateLimitWindow: integer(values.apiKeyRateLimitWindow, "Janela da chave de API"),
|
|
259
264
|
existing: Boolean(existing),
|
|
265
|
+
mcpHttp: values.mcpHttp,
|
|
260
266
|
});
|
|
261
267
|
}
|
|
262
268
|
|
|
@@ -289,6 +295,7 @@ export function setupArguments(configuration, context) {
|
|
|
289
295
|
String(configuration.apiKeyRateLimitWindow),
|
|
290
296
|
];
|
|
291
297
|
if (configuration.disableRateLimit) args.push("--disable-rate-limit");
|
|
298
|
+
if (configuration.mcpHttp !== undefined) args.push(configuration.mcpHttp ? "--mcp-http" : "--no-mcp-http");
|
|
292
299
|
if (configuration.storage === "volumes") args.push("--use-docker-volumes");
|
|
293
300
|
else {
|
|
294
301
|
for (const [name, flag] of [
|
|
@@ -344,6 +351,10 @@ export function composeArguments(instance, context, operation) {
|
|
|
344
351
|
"--project-name",
|
|
345
352
|
`biaws-${instance.name}`,
|
|
346
353
|
];
|
|
354
|
+
if (instance.env?.BIAWS_MCP_CONFIGURED === "true") {
|
|
355
|
+
base.push("--file", path.join(context.repositoryRoot, "compose.mcp.yaml"));
|
|
356
|
+
if (instance.env.BIAWS_MCP_HTTP_ENABLED === "true" || operation === "stop") base.push("--profile", "mcp-http");
|
|
357
|
+
}
|
|
347
358
|
if (operation === "services") return [...base, "ps", "--all", "--services"];
|
|
348
359
|
if (operation === "start") return [...base, "up", "-d", "--wait"];
|
|
349
360
|
if (operation === "update") return [...base, "up", "-d", "--build", "--wait"];
|
|
@@ -376,9 +387,11 @@ export async function updateInstance(instance, context, filesystem, processRunne
|
|
|
376
387
|
throw new CliError("A release mudou após a verificação; execute update novamente.", { code: "RELEASE_CHANGED" });
|
|
377
388
|
}
|
|
378
389
|
const existing = await operateInstance(instance, context, processRunner, "services", { environment });
|
|
379
|
-
const services =
|
|
380
|
-
|
|
381
|
-
|
|
390
|
+
const services = ["mongo", "api", "ui"];
|
|
391
|
+
if (existing.output.split(/\s+/u).includes("monitor-executor")) services.push("monitor-executor");
|
|
392
|
+
if (instance.env.BIAWS_MCP_HTTP_ENABLED === "true") services.push("mcp");
|
|
393
|
+
else if (instance.env.BIAWS_MCP_CONFIGURED === "true")
|
|
394
|
+
await operateInstance(instance, context, processRunner, "stop", { services: ["mcp"], environment });
|
|
382
395
|
const result = await operateInstance(instance, context, processRunner, "update", {
|
|
383
396
|
environment: { ...environment, BIAWS_VERSION: normalized.version },
|
|
384
397
|
services,
|