agent-auth-broker 0.0.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.
- package/dist/index.js +2064 -0
- package/dist/index.js.map +1 -0
- package/package.json +46 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2064 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command as Command11 } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands/init.ts
|
|
7
|
+
import fs2 from "fs";
|
|
8
|
+
import { Command } from "commander";
|
|
9
|
+
import { stringify as stringifyYaml2 } from "yaml";
|
|
10
|
+
|
|
11
|
+
// src/utils.ts
|
|
12
|
+
import fs from "fs";
|
|
13
|
+
import path from "path";
|
|
14
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
15
|
+
var DEFAULT_CONFIG_NAME = "broker.yaml";
|
|
16
|
+
function resolveConfigPath(configOption) {
|
|
17
|
+
if (configOption) {
|
|
18
|
+
return path.resolve(configOption);
|
|
19
|
+
}
|
|
20
|
+
let dir = process.cwd();
|
|
21
|
+
while (true) {
|
|
22
|
+
const candidate = path.join(dir, DEFAULT_CONFIG_NAME);
|
|
23
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
24
|
+
const parent = path.dirname(dir);
|
|
25
|
+
if (parent === dir) break;
|
|
26
|
+
dir = parent;
|
|
27
|
+
}
|
|
28
|
+
return path.resolve(DEFAULT_CONFIG_NAME);
|
|
29
|
+
}
|
|
30
|
+
function readRawConfig(configPath) {
|
|
31
|
+
if (!fs.existsSync(configPath)) {
|
|
32
|
+
throw new Error(`\u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728: ${configPath}`);
|
|
33
|
+
}
|
|
34
|
+
const raw = fs.readFileSync(configPath, "utf-8");
|
|
35
|
+
return parseYaml(raw);
|
|
36
|
+
}
|
|
37
|
+
function writeConfig(configPath, config) {
|
|
38
|
+
const yaml = stringifyYaml(config, { lineWidth: 120 });
|
|
39
|
+
fs.writeFileSync(configPath, yaml, "utf-8");
|
|
40
|
+
}
|
|
41
|
+
function log(message) {
|
|
42
|
+
console.log(message);
|
|
43
|
+
}
|
|
44
|
+
function logError(message) {
|
|
45
|
+
console.error(`\u274C ${message}`);
|
|
46
|
+
}
|
|
47
|
+
function logSuccess(message) {
|
|
48
|
+
console.log(`\u2705 ${message}`);
|
|
49
|
+
}
|
|
50
|
+
function logWarn(message) {
|
|
51
|
+
console.log(`\u26A0\uFE0F ${message}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/commands/init.ts
|
|
55
|
+
var initCommand = new Command("init").description("\u521D\u59CB\u5316 broker.yaml \u914D\u7F6E\u6587\u4EF6").option("-c, --config <path>", "\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84", void 0).option("--force", "\u8986\u76D6\u5DF2\u5B58\u5728\u7684\u914D\u7F6E\u6587\u4EF6", false).action((opts) => {
|
|
56
|
+
const configPath = resolveConfigPath(opts.config);
|
|
57
|
+
if (fs2.existsSync(configPath) && !opts.force) {
|
|
58
|
+
logWarn(`\u914D\u7F6E\u6587\u4EF6\u5DF2\u5B58\u5728: ${configPath}`);
|
|
59
|
+
logWarn("\u4F7F\u7528 --force \u8986\u76D6");
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const template = {
|
|
63
|
+
version: "1",
|
|
64
|
+
agents: [
|
|
65
|
+
{
|
|
66
|
+
id: "my-agent",
|
|
67
|
+
name: "My AI Agent"
|
|
68
|
+
}
|
|
69
|
+
],
|
|
70
|
+
credentials: [
|
|
71
|
+
{
|
|
72
|
+
id: "github-main",
|
|
73
|
+
connector: "github",
|
|
74
|
+
token: "${GITHUB_TOKEN}"
|
|
75
|
+
}
|
|
76
|
+
],
|
|
77
|
+
policies: [
|
|
78
|
+
{
|
|
79
|
+
agent: "my-agent",
|
|
80
|
+
credential: "github-main",
|
|
81
|
+
actions: ["*"]
|
|
82
|
+
}
|
|
83
|
+
],
|
|
84
|
+
audit: {
|
|
85
|
+
enabled: true,
|
|
86
|
+
output: "stdout"
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
const yaml = stringifyYaml2(template, { lineWidth: 120 });
|
|
90
|
+
fs2.writeFileSync(configPath, yaml, "utf-8");
|
|
91
|
+
logSuccess(`\u5DF2\u521B\u5EFA\u914D\u7F6E\u6587\u4EF6: ${configPath}`);
|
|
92
|
+
log_hint();
|
|
93
|
+
});
|
|
94
|
+
function log_hint() {
|
|
95
|
+
console.log(`
|
|
96
|
+
\u4E0B\u4E00\u6B65\uFF1A
|
|
97
|
+
1. \u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF: export GITHUB_TOKEN=your_token
|
|
98
|
+
2. \u9A8C\u8BC1\u914D\u7F6E: broker validate
|
|
99
|
+
3. \u8BCA\u65AD\u8FDE\u63A5: broker diagnose
|
|
100
|
+
4. \u542F\u52A8\u670D\u52A1: broker serve
|
|
101
|
+
`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// src/commands/validate.ts
|
|
105
|
+
import { Command as Command2 } from "commander";
|
|
106
|
+
|
|
107
|
+
// ../../packages/local-runtime/src/config-loader.ts
|
|
108
|
+
import fs3 from "fs";
|
|
109
|
+
import path2 from "path";
|
|
110
|
+
import { parse as parseYaml2 } from "yaml";
|
|
111
|
+
import { z } from "zod";
|
|
112
|
+
var RateLimitSchema = z.object({
|
|
113
|
+
max_calls: z.number().int().positive(),
|
|
114
|
+
window_seconds: z.number().int().positive()
|
|
115
|
+
}).optional();
|
|
116
|
+
var ParamConstraintSchema = z.record(
|
|
117
|
+
z.string(),
|
|
118
|
+
z.object({ pattern: z.string().optional() })
|
|
119
|
+
);
|
|
120
|
+
var AgentSchema = z.object({
|
|
121
|
+
id: z.string().min(1),
|
|
122
|
+
name: z.string().min(1),
|
|
123
|
+
token_hash: z.string().optional(),
|
|
124
|
+
token_prefix: z.string().optional()
|
|
125
|
+
});
|
|
126
|
+
var CredentialSchema = z.object({
|
|
127
|
+
id: z.string().min(1),
|
|
128
|
+
connector: z.string().min(1),
|
|
129
|
+
token: z.string().optional(),
|
|
130
|
+
encrypted: z.string().optional()
|
|
131
|
+
});
|
|
132
|
+
var PolicySchema = z.object({
|
|
133
|
+
agent: z.string().min(1),
|
|
134
|
+
credential: z.string().min(1),
|
|
135
|
+
actions: z.array(z.string()).default(["*"]),
|
|
136
|
+
param_constraints: ParamConstraintSchema.optional(),
|
|
137
|
+
rate_limit: RateLimitSchema,
|
|
138
|
+
expires_at: z.string().datetime().optional()
|
|
139
|
+
});
|
|
140
|
+
var AuditSchema = z.object({
|
|
141
|
+
enabled: z.boolean().default(true),
|
|
142
|
+
output: z.enum(["stdout", "file"]).default("stdout"),
|
|
143
|
+
file: z.string().optional()
|
|
144
|
+
}).default({ enabled: true, output: "stdout" });
|
|
145
|
+
var BrokerConfigSchema = z.object({
|
|
146
|
+
version: z.string().default("1"),
|
|
147
|
+
encryption_key: z.string().optional(),
|
|
148
|
+
agents: z.array(AgentSchema).min(1),
|
|
149
|
+
credentials: z.array(CredentialSchema).min(1),
|
|
150
|
+
policies: z.array(PolicySchema).min(1),
|
|
151
|
+
audit: AuditSchema
|
|
152
|
+
});
|
|
153
|
+
function resolveEnvVars(value) {
|
|
154
|
+
return value.replace(/\$\{([^}]+)\}/g, (_, envName) => {
|
|
155
|
+
const envValue = process.env[envName];
|
|
156
|
+
if (envValue === void 0) {
|
|
157
|
+
throw new Error(`\u73AF\u5883\u53D8\u91CF ${envName} \u672A\u8BBE\u7F6E`);
|
|
158
|
+
}
|
|
159
|
+
return envValue;
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
function resolveEnvVarsInObject(obj) {
|
|
163
|
+
if (typeof obj === "string") {
|
|
164
|
+
return resolveEnvVars(obj);
|
|
165
|
+
}
|
|
166
|
+
if (Array.isArray(obj)) {
|
|
167
|
+
return obj.map((item) => resolveEnvVarsInObject(item));
|
|
168
|
+
}
|
|
169
|
+
if (obj !== null && typeof obj === "object") {
|
|
170
|
+
const result = {};
|
|
171
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
172
|
+
result[key] = resolveEnvVarsInObject(value);
|
|
173
|
+
}
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
return obj;
|
|
177
|
+
}
|
|
178
|
+
function loadConfig(configPath) {
|
|
179
|
+
const absolutePath = path2.resolve(configPath);
|
|
180
|
+
if (!fs3.existsSync(absolutePath)) {
|
|
181
|
+
throw new Error(`\u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728: ${absolutePath}`);
|
|
182
|
+
}
|
|
183
|
+
const raw = fs3.readFileSync(absolutePath, "utf-8");
|
|
184
|
+
const parsed = parseYaml2(raw);
|
|
185
|
+
const resolved = resolveEnvVarsInObject(parsed);
|
|
186
|
+
const result = BrokerConfigSchema.parse(resolved);
|
|
187
|
+
for (const cred of result.credentials) {
|
|
188
|
+
if (!cred.token && !cred.encrypted) {
|
|
189
|
+
throw new Error(`\u51ED\u8BC1 "${cred.id}" \u5FC5\u987B\u8BBE\u7F6E token \u6216 encrypted \u5B57\u6BB5`);
|
|
190
|
+
}
|
|
191
|
+
if (cred.encrypted && !result.encryption_key) {
|
|
192
|
+
throw new Error(`\u51ED\u8BC1 "${cred.id}" \u4F7F\u7528\u52A0\u5BC6\u5B58\u50A8\uFF0C\u4F46\u672A\u8BBE\u7F6E encryption_key`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const agentIds = new Set(result.agents.map((a) => a.id));
|
|
196
|
+
const credentialIds = new Set(result.credentials.map((c) => c.id));
|
|
197
|
+
for (const policy of result.policies) {
|
|
198
|
+
if (!agentIds.has(policy.agent)) {
|
|
199
|
+
throw new Error(`\u7B56\u7565\u5F15\u7528\u4E86\u4E0D\u5B58\u5728\u7684 agent: "${policy.agent}"`);
|
|
200
|
+
}
|
|
201
|
+
if (!credentialIds.has(policy.credential)) {
|
|
202
|
+
throw new Error(`\u7B56\u7565\u5F15\u7528\u4E86\u4E0D\u5B58\u5728\u7684 credential: "${policy.credential}"`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return result;
|
|
206
|
+
}
|
|
207
|
+
function validateConfigFile(configPath) {
|
|
208
|
+
const errors = [];
|
|
209
|
+
try {
|
|
210
|
+
const absolutePath = path2.resolve(configPath);
|
|
211
|
+
if (!fs3.existsSync(absolutePath)) {
|
|
212
|
+
return { valid: false, errors: [`\u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728: ${absolutePath}`] };
|
|
213
|
+
}
|
|
214
|
+
const raw = fs3.readFileSync(absolutePath, "utf-8");
|
|
215
|
+
const parsed = parseYaml2(raw);
|
|
216
|
+
const result = BrokerConfigSchema.safeParse(parsed);
|
|
217
|
+
if (!result.success) {
|
|
218
|
+
for (const issue of result.error.issues) {
|
|
219
|
+
errors.push(`${issue.path.join(".")}: ${issue.message}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
} catch (err) {
|
|
223
|
+
errors.push(err instanceof Error ? err.message : String(err));
|
|
224
|
+
}
|
|
225
|
+
return { valid: errors.length === 0, errors };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ../../packages/local-runtime/src/local-store.ts
|
|
229
|
+
var LocalStore = class {
|
|
230
|
+
agents;
|
|
231
|
+
credentials;
|
|
232
|
+
policies;
|
|
233
|
+
audit;
|
|
234
|
+
encryptionKey;
|
|
235
|
+
constructor(config) {
|
|
236
|
+
this.agents = new Map(config.agents.map((a) => [a.id, a]));
|
|
237
|
+
this.credentials = new Map(config.credentials.map((c) => [c.id, c]));
|
|
238
|
+
this.policies = config.policies;
|
|
239
|
+
this.audit = config.audit;
|
|
240
|
+
this.encryptionKey = config.encryption_key;
|
|
241
|
+
}
|
|
242
|
+
getAgent(id) {
|
|
243
|
+
return this.agents.get(id);
|
|
244
|
+
}
|
|
245
|
+
listAgents() {
|
|
246
|
+
return Array.from(this.agents.values());
|
|
247
|
+
}
|
|
248
|
+
getCredential(id) {
|
|
249
|
+
return this.credentials.get(id);
|
|
250
|
+
}
|
|
251
|
+
listCredentials() {
|
|
252
|
+
return Array.from(this.credentials.values());
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* 查找 agent 对 connector 的策略
|
|
256
|
+
*/
|
|
257
|
+
findPolicy(agentId, connectorId) {
|
|
258
|
+
return this.policies.find((p) => {
|
|
259
|
+
if (p.agent !== agentId) return false;
|
|
260
|
+
const cred = this.credentials.get(p.credential);
|
|
261
|
+
if (!cred || cred.connector !== connectorId) return false;
|
|
262
|
+
if (p.expires_at && new Date(p.expires_at) < /* @__PURE__ */ new Date()) return false;
|
|
263
|
+
return true;
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* 获取 agent 的所有活跃策略
|
|
268
|
+
*/
|
|
269
|
+
getAgentPolicies(agentId) {
|
|
270
|
+
const result = [];
|
|
271
|
+
for (const policy of this.policies) {
|
|
272
|
+
if (policy.agent !== agentId) continue;
|
|
273
|
+
if (policy.expires_at && new Date(policy.expires_at) < /* @__PURE__ */ new Date()) continue;
|
|
274
|
+
const cred = this.credentials.get(policy.credential);
|
|
275
|
+
if (!cred) continue;
|
|
276
|
+
result.push({ ...policy, credentialConfig: cred });
|
|
277
|
+
}
|
|
278
|
+
return result;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* 通过 token hash 查找对应的 agent
|
|
282
|
+
*/
|
|
283
|
+
findAgentByTokenHash(hash) {
|
|
284
|
+
return this.listAgents().find((a) => a.token_hash === hash);
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* 重新加载配置
|
|
288
|
+
*/
|
|
289
|
+
reload(config) {
|
|
290
|
+
this.agents = new Map(config.agents.map((a) => [a.id, a]));
|
|
291
|
+
this.credentials = new Map(config.credentials.map((c) => [c.id, c]));
|
|
292
|
+
this.policies = config.policies;
|
|
293
|
+
this.audit = config.audit;
|
|
294
|
+
this.encryptionKey = config.encryption_key;
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
// ../../packages/connectors/src/github/index.ts
|
|
299
|
+
var GITHUB_API = "https://api.github.com";
|
|
300
|
+
async function githubRequest(method, path3, token, body) {
|
|
301
|
+
const res = await fetch(`${GITHUB_API}${path3}`, {
|
|
302
|
+
method,
|
|
303
|
+
headers: {
|
|
304
|
+
Authorization: `Bearer ${token}`,
|
|
305
|
+
Accept: "application/vnd.github.v3+json",
|
|
306
|
+
"Content-Type": "application/json",
|
|
307
|
+
"User-Agent": "agent-auth-broker/1.0"
|
|
308
|
+
},
|
|
309
|
+
body: body ? JSON.stringify(body) : void 0
|
|
310
|
+
});
|
|
311
|
+
const data = await res.json().catch(() => ({}));
|
|
312
|
+
return { status: res.status, data };
|
|
313
|
+
}
|
|
314
|
+
function ok(data, status) {
|
|
315
|
+
return { success: true, data, httpStatus: status };
|
|
316
|
+
}
|
|
317
|
+
function fail(message, code, status) {
|
|
318
|
+
return { success: false, error: { code, message }, httpStatus: status };
|
|
319
|
+
}
|
|
320
|
+
var githubConnector = {
|
|
321
|
+
info: {
|
|
322
|
+
id: "github",
|
|
323
|
+
name: "GitHub",
|
|
324
|
+
description: "GitHub \u4ED3\u5E93\u3001Issue\u3001PR \u64CD\u4F5C",
|
|
325
|
+
authType: "oauth2"
|
|
326
|
+
},
|
|
327
|
+
getActions() {
|
|
328
|
+
return [
|
|
329
|
+
{ id: "list_repos", name: "\u5217\u51FA\u4ED3\u5E93", description: "\u5217\u51FA\u5DF2\u6388\u6743\u7528\u6237\u7684\u4ED3\u5E93", inputSchema: { type: "object", properties: { per_page: { type: "number" }, page: { type: "number" } } } },
|
|
330
|
+
{ id: "get_repo", name: "\u83B7\u53D6\u4ED3\u5E93", description: "\u83B7\u53D6\u4ED3\u5E93\u4FE1\u606F", inputSchema: { type: "object", required: ["repo"], properties: { repo: { type: "string", description: "owner/repo" } } } },
|
|
331
|
+
{ id: "list_issues", name: "\u5217\u51FA Issue", description: "\u5217\u51FA\u4ED3\u5E93\u7684 Issue", inputSchema: { type: "object", required: ["repo"], properties: { repo: { type: "string" }, state: { type: "string", enum: ["open", "closed", "all"] }, per_page: { type: "number" } } } },
|
|
332
|
+
{ id: "get_issue", name: "\u83B7\u53D6 Issue", description: "\u83B7\u53D6\u5355\u4E2A Issue \u8BE6\u60C5", inputSchema: { type: "object", required: ["repo", "issue_number"], properties: { repo: { type: "string" }, issue_number: { type: "number" } } } },
|
|
333
|
+
{ id: "create_issue", name: "\u521B\u5EFA Issue", description: "\u5728\u4ED3\u5E93\u4E2D\u521B\u5EFA Issue", inputSchema: { type: "object", required: ["repo", "title"], properties: { repo: { type: "string" }, title: { type: "string" }, body: { type: "string" }, labels: { type: "array", items: { type: "string" } } } } },
|
|
334
|
+
{ id: "comment_issue", name: "\u8BC4\u8BBA Issue", description: "\u5728 Issue \u4E0A\u6DFB\u52A0\u8BC4\u8BBA", inputSchema: { type: "object", required: ["repo", "issue_number", "body"], properties: { repo: { type: "string" }, issue_number: { type: "number" }, body: { type: "string" } } } },
|
|
335
|
+
{ id: "list_prs", name: "\u5217\u51FA PR", description: "\u5217\u51FA\u4ED3\u5E93\u7684 Pull Request", inputSchema: { type: "object", required: ["repo"], properties: { repo: { type: "string" }, state: { type: "string", enum: ["open", "closed", "all"] }, per_page: { type: "number" } } } },
|
|
336
|
+
{ id: "create_pr", name: "\u521B\u5EFA PR", description: "\u521B\u5EFA Pull Request", inputSchema: { type: "object", required: ["repo", "title", "head", "base"], properties: { repo: { type: "string" }, title: { type: "string" }, head: { type: "string" }, base: { type: "string" }, body: { type: "string" }, draft: { type: "boolean" } } } },
|
|
337
|
+
{ id: "get_file", name: "\u83B7\u53D6\u6587\u4EF6", description: "\u83B7\u53D6\u4ED3\u5E93\u4E2D\u7684\u6587\u4EF6\u5185\u5BB9", inputSchema: { type: "object", required: ["repo", "path"], properties: { repo: { type: "string" }, path: { type: "string" }, ref: { type: "string" } } } },
|
|
338
|
+
{ id: "search_code", name: "\u641C\u7D22\u4EE3\u7801", description: "\u5728 GitHub \u4E0A\u641C\u7D22\u4EE3\u7801", inputSchema: { type: "object", required: ["q"], properties: { q: { type: "string" }, per_page: { type: "number" } } } }
|
|
339
|
+
];
|
|
340
|
+
},
|
|
341
|
+
async execute(action, params, credential) {
|
|
342
|
+
const token = credential.accessToken;
|
|
343
|
+
switch (action) {
|
|
344
|
+
case "list_repos": {
|
|
345
|
+
const { per_page = 30, page = 1 } = params;
|
|
346
|
+
const r = await githubRequest("GET", `/user/repos?per_page=${per_page}&page=${page}&sort=updated`, token);
|
|
347
|
+
return r.status === 200 ? ok(r.data, r.status) : fail("Failed to list repos", "GITHUB_ERROR", r.status);
|
|
348
|
+
}
|
|
349
|
+
case "get_repo": {
|
|
350
|
+
const r = await githubRequest("GET", `/repos/${params.repo}`, token);
|
|
351
|
+
return r.status === 200 ? ok(r.data, r.status) : fail("Repo not found", "NOT_FOUND", r.status);
|
|
352
|
+
}
|
|
353
|
+
case "list_issues": {
|
|
354
|
+
const { repo, state = "open", per_page = 30 } = params;
|
|
355
|
+
const r = await githubRequest("GET", `/repos/${repo}/issues?state=${state}&per_page=${per_page}`, token);
|
|
356
|
+
return r.status === 200 ? ok(r.data, r.status) : fail("Failed to list issues", "GITHUB_ERROR", r.status);
|
|
357
|
+
}
|
|
358
|
+
case "get_issue": {
|
|
359
|
+
const r = await githubRequest("GET", `/repos/${params.repo}/issues/${params.issue_number}`, token);
|
|
360
|
+
return r.status === 200 ? ok(r.data, r.status) : fail("Issue not found", "NOT_FOUND", r.status);
|
|
361
|
+
}
|
|
362
|
+
case "create_issue": {
|
|
363
|
+
const { repo, ...body } = params;
|
|
364
|
+
const r = await githubRequest("POST", `/repos/${repo}/issues`, token, body);
|
|
365
|
+
return r.status === 201 ? ok(r.data, r.status) : fail("Failed to create issue", "GITHUB_ERROR", r.status);
|
|
366
|
+
}
|
|
367
|
+
case "comment_issue": {
|
|
368
|
+
const { repo, issue_number, body } = params;
|
|
369
|
+
const r = await githubRequest("POST", `/repos/${repo}/issues/${issue_number}/comments`, token, { body });
|
|
370
|
+
return r.status === 201 ? ok(r.data, r.status) : fail("Failed to add comment", "GITHUB_ERROR", r.status);
|
|
371
|
+
}
|
|
372
|
+
case "list_prs": {
|
|
373
|
+
const { repo, state = "open", per_page = 30 } = params;
|
|
374
|
+
const r = await githubRequest("GET", `/repos/${repo}/pulls?state=${state}&per_page=${per_page}`, token);
|
|
375
|
+
return r.status === 200 ? ok(r.data, r.status) : fail("Failed to list PRs", "GITHUB_ERROR", r.status);
|
|
376
|
+
}
|
|
377
|
+
case "create_pr": {
|
|
378
|
+
const { repo, ...body } = params;
|
|
379
|
+
const r = await githubRequest("POST", `/repos/${repo}/pulls`, token, body);
|
|
380
|
+
return r.status === 201 ? ok(r.data, r.status) : fail("Failed to create PR", "GITHUB_ERROR", r.status);
|
|
381
|
+
}
|
|
382
|
+
case "get_file": {
|
|
383
|
+
const { repo, path: path3, ref } = params;
|
|
384
|
+
const query = ref ? `?ref=${ref}` : "";
|
|
385
|
+
const r = await githubRequest("GET", `/repos/${repo}/contents/${path3}${query}`, token);
|
|
386
|
+
if (r.status === 200) {
|
|
387
|
+
const file = r.data;
|
|
388
|
+
if (file.content && file.encoding === "base64") {
|
|
389
|
+
file.content = Buffer.from(file.content.replace(/\n/g, ""), "base64").toString("utf8");
|
|
390
|
+
}
|
|
391
|
+
return ok(file, r.status);
|
|
392
|
+
}
|
|
393
|
+
return fail("File not found", "NOT_FOUND", r.status);
|
|
394
|
+
}
|
|
395
|
+
case "search_code": {
|
|
396
|
+
const { q, per_page = 10 } = params;
|
|
397
|
+
const r = await githubRequest("GET", `/search/code?q=${encodeURIComponent(q)}&per_page=${per_page}`, token);
|
|
398
|
+
return r.status === 200 ? ok(r.data, r.status) : fail("Search failed", "GITHUB_ERROR", r.status);
|
|
399
|
+
}
|
|
400
|
+
default:
|
|
401
|
+
return fail(`Unknown action: ${action}`, "UNKNOWN_ACTION");
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
// ../../packages/connectors/src/registry.ts
|
|
407
|
+
var connectors = /* @__PURE__ */ new Map([
|
|
408
|
+
["github", githubConnector]
|
|
409
|
+
]);
|
|
410
|
+
function getConnector(id) {
|
|
411
|
+
return connectors.get(id);
|
|
412
|
+
}
|
|
413
|
+
function listConnectors() {
|
|
414
|
+
return Array.from(connectors.values());
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// ../../packages/connectors/src/scopes.ts
|
|
418
|
+
var CONNECTOR_SCOPES = {
|
|
419
|
+
github: {
|
|
420
|
+
"github:read": [
|
|
421
|
+
"github:list_repos",
|
|
422
|
+
"github:get_repo",
|
|
423
|
+
"github:list_issues",
|
|
424
|
+
"github:get_issue",
|
|
425
|
+
"github:list_prs",
|
|
426
|
+
"github:get_file",
|
|
427
|
+
"github:search_code"
|
|
428
|
+
],
|
|
429
|
+
"github:write": [
|
|
430
|
+
"github:create_issue",
|
|
431
|
+
"github:comment_issue",
|
|
432
|
+
"github:create_pr"
|
|
433
|
+
]
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
function expandScopes(actions) {
|
|
437
|
+
if (actions.length === 1 && actions[0] === "*") return actions;
|
|
438
|
+
const expanded = /* @__PURE__ */ new Set();
|
|
439
|
+
for (const action of actions) {
|
|
440
|
+
let found = false;
|
|
441
|
+
for (const scopes of Object.values(CONNECTOR_SCOPES)) {
|
|
442
|
+
if (action in scopes) {
|
|
443
|
+
for (const a of scopes[action]) {
|
|
444
|
+
expanded.add(a);
|
|
445
|
+
}
|
|
446
|
+
found = true;
|
|
447
|
+
break;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
if (!found) {
|
|
451
|
+
expanded.add(action);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return Array.from(expanded);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// ../../packages/local-runtime/src/rate-limiter.ts
|
|
458
|
+
var RateLimiter = class {
|
|
459
|
+
/** key = `${agentId}:${credentialId}` */
|
|
460
|
+
windows = /* @__PURE__ */ new Map();
|
|
461
|
+
/**
|
|
462
|
+
* 检查是否允许请求
|
|
463
|
+
* @returns { allowed: true } 或 { allowed: false, retryAfterMs }
|
|
464
|
+
*/
|
|
465
|
+
check(agentId, credentialId, config) {
|
|
466
|
+
const key = `${agentId}:${credentialId}`;
|
|
467
|
+
const now = Date.now();
|
|
468
|
+
const windowMs = config.window_seconds * 1e3;
|
|
469
|
+
const windowStart = now - windowMs;
|
|
470
|
+
let entry = this.windows.get(key);
|
|
471
|
+
if (!entry) {
|
|
472
|
+
entry = { timestamps: [] };
|
|
473
|
+
this.windows.set(key, entry);
|
|
474
|
+
}
|
|
475
|
+
entry.timestamps = entry.timestamps.filter((t) => t > windowStart);
|
|
476
|
+
if (entry.timestamps.length >= config.max_calls) {
|
|
477
|
+
const earliest = entry.timestamps[0];
|
|
478
|
+
const retryAfterMs = earliest + windowMs - now;
|
|
479
|
+
return { allowed: false, retryAfterMs: Math.max(retryAfterMs, 0) };
|
|
480
|
+
}
|
|
481
|
+
entry.timestamps.push(now);
|
|
482
|
+
return { allowed: true };
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* 清除所有计数器
|
|
486
|
+
*/
|
|
487
|
+
reset() {
|
|
488
|
+
this.windows.clear();
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
// ../../packages/local-runtime/src/local-permission.ts
|
|
493
|
+
var globalRateLimiter = new RateLimiter();
|
|
494
|
+
function checkLocalPermission(input, store) {
|
|
495
|
+
const { agentId, connectorId, action } = input;
|
|
496
|
+
const fullAction = `${connectorId}:${action}`;
|
|
497
|
+
const agent = store.getAgent(agentId);
|
|
498
|
+
if (!agent) {
|
|
499
|
+
return { result: "DENIED_AGENT_INACTIVE", message: "Agent \u4E0D\u5B58\u5728" };
|
|
500
|
+
}
|
|
501
|
+
const policy = store.findPolicy(agentId, connectorId);
|
|
502
|
+
if (!policy) {
|
|
503
|
+
return { result: "DENIED_NO_POLICY", message: `\u672A\u627E\u5230 agent "${agentId}" \u5BF9 connector "${connectorId}" \u7684\u7B56\u7565` };
|
|
504
|
+
}
|
|
505
|
+
if (policy.expires_at && new Date(policy.expires_at) < /* @__PURE__ */ new Date()) {
|
|
506
|
+
return { result: "DENIED_NO_POLICY", message: `\u7B56\u7565\u5DF2\u8FC7\u671F\uFF08${policy.expires_at}\uFF09` };
|
|
507
|
+
}
|
|
508
|
+
const expandedActions = expandScopes(policy.actions);
|
|
509
|
+
const actionsAllowAll = expandedActions.length === 0 || expandedActions.length === 1 && expandedActions[0] === "*";
|
|
510
|
+
if (!actionsAllowAll && !expandedActions.includes(fullAction)) {
|
|
511
|
+
return {
|
|
512
|
+
result: "DENIED_ACTION_NOT_ALLOWED",
|
|
513
|
+
message: `\u64CD\u4F5C "${fullAction}" \u4E0D\u5728\u5141\u8BB8\u5217\u8868\u4E2D`
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
if (policy.param_constraints && input.params) {
|
|
517
|
+
for (const [key, constraint] of Object.entries(policy.param_constraints)) {
|
|
518
|
+
const paramValue = input.params[key];
|
|
519
|
+
if (constraint.pattern && typeof paramValue === "string") {
|
|
520
|
+
const regex = new RegExp(constraint.pattern);
|
|
521
|
+
if (!regex.test(paramValue)) {
|
|
522
|
+
return {
|
|
523
|
+
result: "DENIED_PARAM_CONSTRAINT",
|
|
524
|
+
message: `\u53C2\u6570 "${key}" \u7684\u503C "${paramValue}" \u4E0D\u5339\u914D\u6A21\u5F0F "${constraint.pattern}"`
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
if (policy.rate_limit) {
|
|
531
|
+
const rateCheck = globalRateLimiter.check(agentId, policy.credential, policy.rate_limit);
|
|
532
|
+
if (!rateCheck.allowed) {
|
|
533
|
+
const retrySeconds = Math.ceil(rateCheck.retryAfterMs / 1e3);
|
|
534
|
+
return {
|
|
535
|
+
result: "DENIED_ACTION_NOT_ALLOWED",
|
|
536
|
+
message: `\u901F\u7387\u9650\u5236\uFF1A\u8D85\u8FC7 ${policy.rate_limit.max_calls} \u6B21/${policy.rate_limit.window_seconds}\u79D2\uFF0C\u8BF7 ${retrySeconds} \u79D2\u540E\u91CD\u8BD5`
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
const credentialId = policy.credential;
|
|
541
|
+
return { result: "ALLOWED", credentialId };
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// ../../packages/crypto/src/index.ts
|
|
545
|
+
import crypto from "crypto";
|
|
546
|
+
var ALGORITHM = "aes-256-gcm";
|
|
547
|
+
var IV_LENGTH = 12;
|
|
548
|
+
var TAG_LENGTH = 16;
|
|
549
|
+
function getMasterKey() {
|
|
550
|
+
const key = process.env.BROKER_MASTER_KEY;
|
|
551
|
+
if (!key) throw new Error("BROKER_MASTER_KEY is not set");
|
|
552
|
+
const buf = Buffer.from(key, "hex");
|
|
553
|
+
if (buf.length !== 32) throw new Error("BROKER_MASTER_KEY must be 32 bytes (64 hex chars)");
|
|
554
|
+
return buf;
|
|
555
|
+
}
|
|
556
|
+
function decryptWithKey(encoded, key) {
|
|
557
|
+
const buf = Buffer.from(encoded, "base64");
|
|
558
|
+
const iv = buf.subarray(0, IV_LENGTH);
|
|
559
|
+
const tag = buf.subarray(IV_LENGTH, IV_LENGTH + TAG_LENGTH);
|
|
560
|
+
const ciphertext = buf.subarray(IV_LENGTH + TAG_LENGTH);
|
|
561
|
+
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
|
|
562
|
+
decipher.setAuthTag(tag);
|
|
563
|
+
return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
564
|
+
}
|
|
565
|
+
function decryptCredential(encrypted) {
|
|
566
|
+
const mek = getMasterKey();
|
|
567
|
+
const dek = decryptWithKey(encrypted.encryptionKeyId, mek);
|
|
568
|
+
const plaintext = decryptWithKey(encrypted.encryptedData, dek);
|
|
569
|
+
return JSON.parse(plaintext.toString("utf8"));
|
|
570
|
+
}
|
|
571
|
+
function generateAgentToken() {
|
|
572
|
+
const bytes = crypto.randomBytes(32);
|
|
573
|
+
const token = `agnt_${bytes.toString("base64url")}`;
|
|
574
|
+
const prefix = token.substring(0, 12);
|
|
575
|
+
return { token, prefix };
|
|
576
|
+
}
|
|
577
|
+
function hashToken(token) {
|
|
578
|
+
return crypto.createHash("sha256").update(token).digest("hex");
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// ../../packages/local-runtime/src/local-vault.ts
|
|
582
|
+
function loadLocalCredential(credentialId, store) {
|
|
583
|
+
const cred = store.getCredential(credentialId);
|
|
584
|
+
if (!cred) {
|
|
585
|
+
throw new Error(`\u51ED\u8BC1\u4E0D\u5B58\u5728: ${credentialId}`);
|
|
586
|
+
}
|
|
587
|
+
if (cred.token) {
|
|
588
|
+
return {
|
|
589
|
+
accessToken: cred.token,
|
|
590
|
+
tokenType: "bearer"
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
if (cred.encrypted) {
|
|
594
|
+
if (!store.encryptionKey) {
|
|
595
|
+
throw new Error(`\u51ED\u8BC1 "${credentialId}" \u4F7F\u7528\u52A0\u5BC6\u5B58\u50A8\uFF0C\u4F46\u914D\u7F6E\u4E2D\u672A\u8BBE\u7F6E encryption_key`);
|
|
596
|
+
}
|
|
597
|
+
const prevKey = process.env.BROKER_MASTER_KEY;
|
|
598
|
+
try {
|
|
599
|
+
process.env.BROKER_MASTER_KEY = store.encryptionKey;
|
|
600
|
+
const decrypted = decryptCredential({
|
|
601
|
+
encryptedData: cred.encrypted,
|
|
602
|
+
encryptionKeyId: ""
|
|
603
|
+
// 单层加密模式,encrypted 包含完整密文
|
|
604
|
+
});
|
|
605
|
+
return decrypted;
|
|
606
|
+
} finally {
|
|
607
|
+
if (prevKey !== void 0) {
|
|
608
|
+
process.env.BROKER_MASTER_KEY = prevKey;
|
|
609
|
+
} else {
|
|
610
|
+
delete process.env.BROKER_MASTER_KEY;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
throw new Error(`\u51ED\u8BC1 "${credentialId}" \u672A\u914D\u7F6E token \u6216 encrypted`);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// ../../packages/local-runtime/src/local-audit.ts
|
|
618
|
+
import fs4 from "fs";
|
|
619
|
+
var SENSITIVE_KEYS = ["token", "secret", "password", "key", "credential"];
|
|
620
|
+
function sanitizeParams(params) {
|
|
621
|
+
return Object.fromEntries(
|
|
622
|
+
Object.entries(params).map(([k, v]) => [
|
|
623
|
+
k,
|
|
624
|
+
SENSITIVE_KEYS.some((s) => k.toLowerCase().includes(s)) ? "[REDACTED]" : v
|
|
625
|
+
])
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
var LocalAuditLogger = class {
|
|
629
|
+
config;
|
|
630
|
+
constructor(config) {
|
|
631
|
+
this.config = config;
|
|
632
|
+
}
|
|
633
|
+
log(entry) {
|
|
634
|
+
if (!this.config.enabled) return;
|
|
635
|
+
const record = {
|
|
636
|
+
...entry,
|
|
637
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
638
|
+
params: sanitizeParams(entry.params)
|
|
639
|
+
};
|
|
640
|
+
const line = JSON.stringify(record);
|
|
641
|
+
if (this.config.output === "file" && this.config.file) {
|
|
642
|
+
fs4.appendFileSync(this.config.file, line + "\n", "utf-8");
|
|
643
|
+
} else {
|
|
644
|
+
console.error(`[broker-audit] ${line}`);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
// ../../packages/local-runtime/src/local-broker.ts
|
|
650
|
+
var LocalBroker = class {
|
|
651
|
+
store;
|
|
652
|
+
audit;
|
|
653
|
+
constructor(store) {
|
|
654
|
+
this.store = store;
|
|
655
|
+
this.audit = new LocalAuditLogger(store.audit);
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* 列出 agent 被授权的所有工具
|
|
659
|
+
*/
|
|
660
|
+
listTools(agentId, connectorFilter) {
|
|
661
|
+
const policies = this.store.getAgentPolicies(agentId);
|
|
662
|
+
const tools = [];
|
|
663
|
+
for (const policy of policies) {
|
|
664
|
+
const connectorId = policy.credentialConfig.connector;
|
|
665
|
+
if (connectorFilter && connectorId !== connectorFilter) continue;
|
|
666
|
+
const conn = getConnector(connectorId);
|
|
667
|
+
if (!conn) continue;
|
|
668
|
+
const actions = conn.getActions();
|
|
669
|
+
const expandedActions = expandScopes(policy.actions);
|
|
670
|
+
const actionsAllowAll = expandedActions.length === 0 || expandedActions.length === 1 && expandedActions[0] === "*";
|
|
671
|
+
for (const action of actions) {
|
|
672
|
+
const fullAction = `${connectorId}:${action.id}`;
|
|
673
|
+
if (!actionsAllowAll && !expandedActions.includes(fullAction)) continue;
|
|
674
|
+
tools.push({
|
|
675
|
+
connector: connectorId,
|
|
676
|
+
connectorName: conn.info.name,
|
|
677
|
+
credentialName: policy.credentialConfig.id,
|
|
678
|
+
action: action.id,
|
|
679
|
+
actionName: action.name,
|
|
680
|
+
description: action.description
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
return tools;
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* 调用工具
|
|
688
|
+
*/
|
|
689
|
+
async callTool(agentId, connectorId, action, params) {
|
|
690
|
+
const permCheck = checkLocalPermission(
|
|
691
|
+
{ agentId, connectorId, action, params },
|
|
692
|
+
this.store
|
|
693
|
+
);
|
|
694
|
+
if (permCheck.result !== "ALLOWED" || !permCheck.credentialId) {
|
|
695
|
+
this.audit.log({
|
|
696
|
+
agentId,
|
|
697
|
+
connectorId,
|
|
698
|
+
action: `${connectorId}:${action}`,
|
|
699
|
+
params,
|
|
700
|
+
permissionResult: permCheck.result,
|
|
701
|
+
responseStatus: 403
|
|
702
|
+
});
|
|
703
|
+
return {
|
|
704
|
+
success: false,
|
|
705
|
+
error: permCheck.message,
|
|
706
|
+
permissionResult: permCheck.result
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
const connector = getConnector(connectorId);
|
|
710
|
+
if (!connector) {
|
|
711
|
+
return { success: false, error: `\u672A\u77E5\u7684 connector: ${connectorId}` };
|
|
712
|
+
}
|
|
713
|
+
try {
|
|
714
|
+
const credential = loadLocalCredential(permCheck.credentialId, this.store);
|
|
715
|
+
const result = await connector.execute(action, params, credential);
|
|
716
|
+
this.audit.log({
|
|
717
|
+
agentId,
|
|
718
|
+
connectorId,
|
|
719
|
+
action: `${connectorId}:${action}`,
|
|
720
|
+
params,
|
|
721
|
+
permissionResult: "ALLOWED",
|
|
722
|
+
responseStatus: result.httpStatus ?? (result.success ? 200 : 500),
|
|
723
|
+
errorMessage: result.success ? void 0 : result.error?.message
|
|
724
|
+
});
|
|
725
|
+
return {
|
|
726
|
+
success: result.success,
|
|
727
|
+
data: result.data,
|
|
728
|
+
error: result.error?.message
|
|
729
|
+
};
|
|
730
|
+
} catch (err) {
|
|
731
|
+
const message = err instanceof Error ? err.message : "Internal error";
|
|
732
|
+
this.audit.log({
|
|
733
|
+
agentId,
|
|
734
|
+
connectorId,
|
|
735
|
+
action: `${connectorId}:${action}`,
|
|
736
|
+
params,
|
|
737
|
+
permissionResult: "ALLOWED",
|
|
738
|
+
responseStatus: 500,
|
|
739
|
+
errorMessage: message
|
|
740
|
+
});
|
|
741
|
+
return { success: false, error: message };
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
};
|
|
745
|
+
|
|
746
|
+
// ../../packages/local-runtime/src/token-auth.ts
|
|
747
|
+
function authenticateByToken(token, store) {
|
|
748
|
+
const hash = hashToken(token);
|
|
749
|
+
return store.findAgentByTokenHash(hash) ?? null;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// ../../packages/local-runtime/src/config-watcher.ts
|
|
753
|
+
import fs5 from "fs";
|
|
754
|
+
var ConfigWatcher = class {
|
|
755
|
+
constructor(configPath, store, opts) {
|
|
756
|
+
this.configPath = configPath;
|
|
757
|
+
this.store = store;
|
|
758
|
+
this.debounceMs = opts?.debounceMs ?? 300;
|
|
759
|
+
}
|
|
760
|
+
watcher = null;
|
|
761
|
+
debounceTimer = null;
|
|
762
|
+
debounceMs;
|
|
763
|
+
start() {
|
|
764
|
+
if (this.watcher) return;
|
|
765
|
+
this.watcher = fs5.watch(this.configPath, (_eventType) => {
|
|
766
|
+
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
767
|
+
this.debounceTimer = setTimeout(() => this.reload(), this.debounceMs);
|
|
768
|
+
});
|
|
769
|
+
this.watcher.on("error", (err) => {
|
|
770
|
+
console.error(`[config-watcher] \u76D1\u89C6\u6587\u4EF6\u51FA\u9519: ${err.message}`);
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
stop() {
|
|
774
|
+
if (this.debounceTimer) {
|
|
775
|
+
clearTimeout(this.debounceTimer);
|
|
776
|
+
this.debounceTimer = null;
|
|
777
|
+
}
|
|
778
|
+
if (this.watcher) {
|
|
779
|
+
this.watcher.close();
|
|
780
|
+
this.watcher = null;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
reload() {
|
|
784
|
+
try {
|
|
785
|
+
const config = loadConfig(this.configPath);
|
|
786
|
+
this.store.reload(config);
|
|
787
|
+
console.error(`[config-watcher] \u914D\u7F6E\u5DF2\u91CD\u8F7D: ${this.configPath}`);
|
|
788
|
+
} catch (err) {
|
|
789
|
+
console.error(`[config-watcher] \u91CD\u8F7D\u5931\u8D25\uFF08\u4FDD\u7559\u65E7\u914D\u7F6E\uFF09: ${err instanceof Error ? err.message : String(err)}`);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
|
|
794
|
+
// src/commands/validate.ts
|
|
795
|
+
var validateCommand = new Command2("validate").description("\u9A8C\u8BC1 broker.yaml \u914D\u7F6E\u6587\u4EF6\u683C\u5F0F").option("-c, --config <path>", "\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84", void 0).action((opts) => {
|
|
796
|
+
const configPath = resolveConfigPath(opts.config);
|
|
797
|
+
console.log(`\u9A8C\u8BC1\u914D\u7F6E\u6587\u4EF6: ${configPath}`);
|
|
798
|
+
const { valid, errors } = validateConfigFile(configPath);
|
|
799
|
+
if (valid) {
|
|
800
|
+
logSuccess("\u914D\u7F6E\u6587\u4EF6\u683C\u5F0F\u6B63\u786E");
|
|
801
|
+
} else {
|
|
802
|
+
logError("\u914D\u7F6E\u6587\u4EF6\u5B58\u5728\u4EE5\u4E0B\u95EE\u9898:");
|
|
803
|
+
for (const err of errors) {
|
|
804
|
+
console.log(` - ${err}`);
|
|
805
|
+
}
|
|
806
|
+
process.exitCode = 1;
|
|
807
|
+
}
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
// src/commands/diagnose.ts
|
|
811
|
+
import { Command as Command3 } from "commander";
|
|
812
|
+
var diagnoseCommand = new Command3("diagnose").description("\u8BCA\u65AD\u914D\u7F6E\u548C\u51ED\u8BC1\u8FDE\u63A5\u72B6\u6001").option("-c, --config <path>", "\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84", void 0).action(async (opts) => {
|
|
813
|
+
const configPath = resolveConfigPath(opts.config);
|
|
814
|
+
log("1. \u52A0\u8F7D\u914D\u7F6E...");
|
|
815
|
+
let config;
|
|
816
|
+
try {
|
|
817
|
+
config = loadConfig(configPath);
|
|
818
|
+
logSuccess(`\u914D\u7F6E\u52A0\u8F7D\u6210\u529F (${config.agents.length} agents, ${config.credentials.length} credentials, ${config.policies.length} policies)`);
|
|
819
|
+
} catch (err) {
|
|
820
|
+
logError(`\u914D\u7F6E\u52A0\u8F7D\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
|
|
821
|
+
process.exitCode = 1;
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
log("\n2. \u68C0\u67E5 Connector...");
|
|
825
|
+
const connectors2 = listConnectors();
|
|
826
|
+
const registeredIds = new Set(connectors2.map((c) => c.info.id));
|
|
827
|
+
for (const cred of config.credentials) {
|
|
828
|
+
if (registeredIds.has(cred.connector)) {
|
|
829
|
+
logSuccess(`Connector "${cred.connector}" \u5DF2\u6CE8\u518C`);
|
|
830
|
+
} else {
|
|
831
|
+
logError(`Connector "${cred.connector}" \u672A\u6CE8\u518C`);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
log("\n3. \u6D4B\u8BD5\u51ED\u8BC1...");
|
|
835
|
+
const store = new LocalStore(config);
|
|
836
|
+
const broker = new LocalBroker(store);
|
|
837
|
+
for (const cred of config.credentials) {
|
|
838
|
+
const testAction = getTestAction(cred.connector);
|
|
839
|
+
if (!testAction) {
|
|
840
|
+
logWarn(`\u51ED\u8BC1 "${cred.id}" (${cred.connector}): \u65E0\u6D4B\u8BD5\u64CD\u4F5C`);
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
843
|
+
const policy = config.policies.find((p) => p.credential === cred.id);
|
|
844
|
+
if (!policy) {
|
|
845
|
+
logWarn(`\u51ED\u8BC1 "${cred.id}": \u65E0\u5173\u8054\u7B56\u7565\uFF0C\u8DF3\u8FC7\u6D4B\u8BD5`);
|
|
846
|
+
continue;
|
|
847
|
+
}
|
|
848
|
+
try {
|
|
849
|
+
const result = await broker.callTool(policy.agent, cred.connector, testAction, {});
|
|
850
|
+
if (result.success) {
|
|
851
|
+
logSuccess(`\u51ED\u8BC1 "${cred.id}" (${cred.connector}): \u8FDE\u63A5\u6B63\u5E38`);
|
|
852
|
+
} else {
|
|
853
|
+
logError(`\u51ED\u8BC1 "${cred.id}" (${cred.connector}): ${result.error}`);
|
|
854
|
+
}
|
|
855
|
+
} catch (err) {
|
|
856
|
+
logError(`\u51ED\u8BC1 "${cred.id}" (${cred.connector}): ${err instanceof Error ? err.message : String(err)}`);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
log("\n\u8BCA\u65AD\u5B8C\u6210");
|
|
860
|
+
});
|
|
861
|
+
function getTestAction(connector) {
|
|
862
|
+
const testActions = {
|
|
863
|
+
github: "list_repos",
|
|
864
|
+
feishu: "get_user_info"
|
|
865
|
+
};
|
|
866
|
+
return testActions[connector];
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// src/commands/serve.ts
|
|
870
|
+
import { Command as Command4 } from "commander";
|
|
871
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
872
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
873
|
+
import {
|
|
874
|
+
CallToolRequestSchema,
|
|
875
|
+
ListToolsRequestSchema
|
|
876
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
877
|
+
var serveCommand = new Command4("serve").description("\u542F\u52A8 MCP Server\uFF08stdio \u6A21\u5F0F\uFF09").option("-c, --config <path>", "\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84", void 0).option("-a, --agent <id>", "Agent ID\uFF08\u9ED8\u8BA4\u4F7F\u7528\u914D\u7F6E\u4E2D\u7684\u7B2C\u4E00\u4E2A agent\uFF09", void 0).action(async (opts) => {
|
|
878
|
+
const configPath = resolveConfigPath(opts.config);
|
|
879
|
+
let config;
|
|
880
|
+
try {
|
|
881
|
+
config = loadConfig(configPath);
|
|
882
|
+
} catch (err) {
|
|
883
|
+
logError(`\u914D\u7F6E\u52A0\u8F7D\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
|
|
884
|
+
process.exitCode = 1;
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
const store = new LocalStore(config);
|
|
888
|
+
const broker = new LocalBroker(store);
|
|
889
|
+
let agentId;
|
|
890
|
+
const envToken = process.env.BROKER_AGENT_TOKEN;
|
|
891
|
+
if (envToken) {
|
|
892
|
+
const matched = authenticateByToken(envToken, store);
|
|
893
|
+
if (!matched) {
|
|
894
|
+
logError("BROKER_AGENT_TOKEN \u8BA4\u8BC1\u5931\u8D25\uFF1Atoken \u4E0D\u5339\u914D\u4EFB\u4F55 agent");
|
|
895
|
+
process.exitCode = 1;
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
agentId = matched.id;
|
|
899
|
+
console.error(`[broker-cli] Token \u8BA4\u8BC1\u6210\u529F: ${matched.name} (${agentId})`);
|
|
900
|
+
} else {
|
|
901
|
+
agentId = opts.agent ?? config.agents[0].id;
|
|
902
|
+
}
|
|
903
|
+
const agent = store.getAgent(agentId);
|
|
904
|
+
if (!agent) {
|
|
905
|
+
logError(`Agent "${agentId}" \u4E0D\u5B58\u5728`);
|
|
906
|
+
process.exitCode = 1;
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
console.error(`[broker-cli] \u4F7F\u7528\u914D\u7F6E: ${configPath}`);
|
|
910
|
+
console.error(`[broker-cli] Agent: ${agent.name} (${agentId})`);
|
|
911
|
+
const server = new Server(
|
|
912
|
+
{ name: "agent-auth-broker", version: "0.1.0" },
|
|
913
|
+
{ capabilities: { tools: {} } }
|
|
914
|
+
);
|
|
915
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
916
|
+
const tools = broker.listTools(agentId);
|
|
917
|
+
const mcpTools = [
|
|
918
|
+
{
|
|
919
|
+
name: "broker_call",
|
|
920
|
+
description: "\u901A\u8FC7 Auth Broker \u8C03\u7528\u7B2C\u4E09\u65B9\u670D\u52A1\uFF08GitHub\u3001\u98DE\u4E66\u7B49\uFF09\uFF0C\u51ED\u8BC1\u7531 Broker \u5B89\u5168\u7BA1\u7406",
|
|
921
|
+
inputSchema: {
|
|
922
|
+
type: "object",
|
|
923
|
+
required: ["connector", "action", "params"],
|
|
924
|
+
properties: {
|
|
925
|
+
connector: { type: "string", description: '\u76EE\u6807\u670D\u52A1\u540D\u79F0\uFF0C\u5982 "github"' },
|
|
926
|
+
action: { type: "string", description: '\u64CD\u4F5C\u540D\u79F0\uFF0C\u5982 "create_issue"' },
|
|
927
|
+
params: { type: "object", description: "\u64CD\u4F5C\u53C2\u6570" }
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
},
|
|
931
|
+
{
|
|
932
|
+
name: "broker_list_tools",
|
|
933
|
+
description: "\u5217\u51FA\u5F53\u524D Agent \u88AB\u6388\u6743\u7684\u6240\u6709\u5DE5\u5177",
|
|
934
|
+
inputSchema: {
|
|
935
|
+
type: "object",
|
|
936
|
+
properties: {
|
|
937
|
+
connector: { type: "string", description: "\u53EF\u9009\uFF0C\u8FC7\u6EE4\u7279\u5B9A connector \u7684\u5DE5\u5177" }
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
];
|
|
942
|
+
const toolMap = /* @__PURE__ */ new Map();
|
|
943
|
+
for (const t of tools) {
|
|
944
|
+
const toolName = `${t.connector}_${t.action}`;
|
|
945
|
+
if (toolMap.has(toolName)) continue;
|
|
946
|
+
toolMap.set(toolName, {
|
|
947
|
+
name: toolName,
|
|
948
|
+
description: `[${t.connectorName}] ${t.description}\uFF08\u51ED\u8BC1\uFF1A${t.credentialName}\uFF09`,
|
|
949
|
+
inputSchema: {
|
|
950
|
+
type: "object",
|
|
951
|
+
description: `\u8C03\u7528 ${t.connectorName} \u7684 ${t.actionName} \u64CD\u4F5C`
|
|
952
|
+
}
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
return { tools: [...mcpTools, ...toolMap.values()] };
|
|
956
|
+
});
|
|
957
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
958
|
+
const { name, arguments: args = {} } = request.params;
|
|
959
|
+
const params = args;
|
|
960
|
+
try {
|
|
961
|
+
if (name === "broker_list_tools") {
|
|
962
|
+
const tools = broker.listTools(agentId, params.connector);
|
|
963
|
+
return {
|
|
964
|
+
content: [{ type: "text", text: JSON.stringify(tools, null, 2) }]
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
let connector;
|
|
968
|
+
let action;
|
|
969
|
+
let callParams;
|
|
970
|
+
if (name === "broker_call") {
|
|
971
|
+
connector = params.connector;
|
|
972
|
+
action = params.action;
|
|
973
|
+
callParams = params.params ?? {};
|
|
974
|
+
} else {
|
|
975
|
+
const allTools = broker.listTools(agentId);
|
|
976
|
+
const matched = allTools.find((t) => `${t.connector}_${t.action}` === name);
|
|
977
|
+
if (!matched) {
|
|
978
|
+
return {
|
|
979
|
+
content: [{ type: "text", text: `No permission for tool: ${name}` }],
|
|
980
|
+
isError: true
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
connector = matched.connector;
|
|
984
|
+
action = matched.action;
|
|
985
|
+
callParams = params;
|
|
986
|
+
}
|
|
987
|
+
const result = await broker.callTool(agentId, connector, action, callParams);
|
|
988
|
+
if (!result.success) {
|
|
989
|
+
const errorText = result.permissionResult ? `Permission denied (${result.permissionResult}): ${result.error}` : `Error: ${result.error}`;
|
|
990
|
+
return { content: [{ type: "text", text: errorText }], isError: true };
|
|
991
|
+
}
|
|
992
|
+
return {
|
|
993
|
+
content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }]
|
|
994
|
+
};
|
|
995
|
+
} catch (err) {
|
|
996
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
997
|
+
return { content: [{ type: "text", text: `Internal error: ${message}` }], isError: true };
|
|
998
|
+
}
|
|
999
|
+
});
|
|
1000
|
+
const watcher = new ConfigWatcher(configPath, store);
|
|
1001
|
+
watcher.start();
|
|
1002
|
+
console.error("[broker-cli] \u914D\u7F6E\u70ED\u91CD\u8F7D\u5DF2\u542F\u7528");
|
|
1003
|
+
const transport = new StdioServerTransport();
|
|
1004
|
+
await server.connect(transport);
|
|
1005
|
+
console.error("[broker-cli] MCP Server started (stdio mode)");
|
|
1006
|
+
process.on("SIGINT", () => {
|
|
1007
|
+
watcher.stop();
|
|
1008
|
+
process.exit(0);
|
|
1009
|
+
});
|
|
1010
|
+
process.on("SIGTERM", () => {
|
|
1011
|
+
watcher.stop();
|
|
1012
|
+
process.exit(0);
|
|
1013
|
+
});
|
|
1014
|
+
});
|
|
1015
|
+
|
|
1016
|
+
// src/commands/credential.ts
|
|
1017
|
+
import { Command as Command5 } from "commander";
|
|
1018
|
+
var credentialCommand = new Command5("credential").description("\u51ED\u8BC1\u7BA1\u7406");
|
|
1019
|
+
credentialCommand.command("add").description("\u6DFB\u52A0\u51ED\u8BC1").argument("<connector>", "\u670D\u52A1\u7C7B\u578B (\u5982 github)").option("-c, --config <path>", "\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84", void 0).option("--id <id>", "\u51ED\u8BC1 ID\uFF08\u9ED8\u8BA4\u4E3A connector \u540D\u79F0\uFF09", void 0).option("--env <name>", "\u73AF\u5883\u53D8\u91CF\u540D\u79F0\uFF08\u4F7F\u7528 ${ENV_VAR} \u5F15\u7528\uFF09", void 0).option("--token <token>", "\u76F4\u63A5\u6307\u5B9A token \u503C", void 0).action((connector, opts) => {
|
|
1020
|
+
const configPath = resolveConfigPath(opts.config);
|
|
1021
|
+
const credentialId = opts.id ?? `${connector}-main`;
|
|
1022
|
+
const connectors2 = listConnectors();
|
|
1023
|
+
const supported = connectors2.find((c) => c.info.id === connector);
|
|
1024
|
+
if (!supported) {
|
|
1025
|
+
logError(`\u4E0D\u652F\u6301\u7684 connector: "${connector}"`);
|
|
1026
|
+
log(`\u652F\u6301\u7684 connector: ${connectors2.map((c) => c.info.id).join(", ")}`);
|
|
1027
|
+
process.exitCode = 1;
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
let tokenValue;
|
|
1031
|
+
if (opts.env) {
|
|
1032
|
+
tokenValue = `\${${opts.env}}`;
|
|
1033
|
+
} else if (opts.token) {
|
|
1034
|
+
tokenValue = opts.token;
|
|
1035
|
+
logWarn("\u76F4\u63A5\u6307\u5B9A token \u503C\u4E0D\u5B89\u5168\uFF0C\u5EFA\u8BAE\u4F7F\u7528 --env \u5F15\u7528\u73AF\u5883\u53D8\u91CF");
|
|
1036
|
+
} else {
|
|
1037
|
+
logError("\u8BF7\u6307\u5B9A --env <ENV_VAR> \u6216 --token <value>");
|
|
1038
|
+
process.exitCode = 1;
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
const config = readRawConfig(configPath);
|
|
1042
|
+
const credentials = config.credentials ?? [];
|
|
1043
|
+
const existing = credentials.find((c) => c.id === credentialId);
|
|
1044
|
+
if (existing) {
|
|
1045
|
+
logWarn(`\u51ED\u8BC1 "${credentialId}" \u5DF2\u5B58\u5728\uFF0C\u5C06\u88AB\u66F4\u65B0`);
|
|
1046
|
+
existing.token = tokenValue;
|
|
1047
|
+
delete existing.encrypted;
|
|
1048
|
+
} else {
|
|
1049
|
+
credentials.push({
|
|
1050
|
+
id: credentialId,
|
|
1051
|
+
connector,
|
|
1052
|
+
token: tokenValue
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
config.credentials = credentials;
|
|
1056
|
+
writeConfig(configPath, config);
|
|
1057
|
+
logSuccess(`\u5DF2\u6DFB\u52A0\u51ED\u8BC1 "${credentialId}" (${connector})`);
|
|
1058
|
+
});
|
|
1059
|
+
credentialCommand.command("list").description("\u5217\u51FA\u6240\u6709\u51ED\u8BC1").option("-c, --config <path>", "\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84", void 0).action((opts) => {
|
|
1060
|
+
const configPath = resolveConfigPath(opts.config);
|
|
1061
|
+
const config = readRawConfig(configPath);
|
|
1062
|
+
const credentials = config.credentials ?? [];
|
|
1063
|
+
if (credentials.length === 0) {
|
|
1064
|
+
log("\u6682\u65E0\u51ED\u8BC1");
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
log(`\u51ED\u8BC1\u5217\u8868 (${credentials.length}):`);
|
|
1068
|
+
for (const cred of credentials) {
|
|
1069
|
+
const source = cred.token?.startsWith("${") ? `env: ${cred.token}` : cred.encrypted ? "encrypted" : "plaintext";
|
|
1070
|
+
log(` - ${cred.id} (${cred.connector}) [${source}]`);
|
|
1071
|
+
}
|
|
1072
|
+
});
|
|
1073
|
+
credentialCommand.command("remove").description("\u79FB\u9664\u51ED\u8BC1").argument("<id>", "\u51ED\u8BC1 ID").option("-c, --config <path>", "\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84", void 0).action((id, opts) => {
|
|
1074
|
+
const configPath = resolveConfigPath(opts.config);
|
|
1075
|
+
const config = readRawConfig(configPath);
|
|
1076
|
+
const credentials = config.credentials ?? [];
|
|
1077
|
+
const index = credentials.findIndex((c) => c.id === id);
|
|
1078
|
+
if (index === -1) {
|
|
1079
|
+
logError(`\u51ED\u8BC1 "${id}" \u4E0D\u5B58\u5728`);
|
|
1080
|
+
process.exitCode = 1;
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
credentials.splice(index, 1);
|
|
1084
|
+
config.credentials = credentials;
|
|
1085
|
+
writeConfig(configPath, config);
|
|
1086
|
+
logSuccess(`\u5DF2\u79FB\u9664\u51ED\u8BC1 "${id}"`);
|
|
1087
|
+
});
|
|
1088
|
+
|
|
1089
|
+
// src/commands/agent.ts
|
|
1090
|
+
import { Command as Command6 } from "commander";
|
|
1091
|
+
var agentCommand = new Command6("agent").description("Agent \u7BA1\u7406");
|
|
1092
|
+
agentCommand.command("create").description("\u521B\u5EFA Agent").argument("<id>", "Agent ID").option("-c, --config <path>", "\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84", void 0).option("-n, --name <name>", "Agent \u540D\u79F0", void 0).action((id, opts) => {
|
|
1093
|
+
const configPath = resolveConfigPath(opts.config);
|
|
1094
|
+
const config = readRawConfig(configPath);
|
|
1095
|
+
const agents = config.agents ?? [];
|
|
1096
|
+
if (agents.find((a) => a.id === id)) {
|
|
1097
|
+
logError(`Agent "${id}" \u5DF2\u5B58\u5728`);
|
|
1098
|
+
process.exitCode = 1;
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
agents.push({
|
|
1102
|
+
id,
|
|
1103
|
+
name: opts.name ?? id
|
|
1104
|
+
});
|
|
1105
|
+
config.agents = agents;
|
|
1106
|
+
writeConfig(configPath, config);
|
|
1107
|
+
logSuccess(`\u5DF2\u521B\u5EFA Agent "${id}"`);
|
|
1108
|
+
});
|
|
1109
|
+
agentCommand.command("list").description("\u5217\u51FA\u6240\u6709 Agent").option("-c, --config <path>", "\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84", void 0).action((opts) => {
|
|
1110
|
+
const configPath = resolveConfigPath(opts.config);
|
|
1111
|
+
const config = readRawConfig(configPath);
|
|
1112
|
+
const agents = config.agents ?? [];
|
|
1113
|
+
if (agents.length === 0) {
|
|
1114
|
+
log("\u6682\u65E0 Agent");
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
log(`Agent \u5217\u8868 (${agents.length}):`);
|
|
1118
|
+
for (const agent of agents) {
|
|
1119
|
+
log(` - ${agent.id} (${agent.name})`);
|
|
1120
|
+
}
|
|
1121
|
+
});
|
|
1122
|
+
agentCommand.command("remove").description("\u79FB\u9664 Agent").argument("<id>", "Agent ID").option("-c, --config <path>", "\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84", void 0).action((id, opts) => {
|
|
1123
|
+
const configPath = resolveConfigPath(opts.config);
|
|
1124
|
+
const config = readRawConfig(configPath);
|
|
1125
|
+
const agents = config.agents ?? [];
|
|
1126
|
+
const index = agents.findIndex((a) => a.id === id);
|
|
1127
|
+
if (index === -1) {
|
|
1128
|
+
logError(`Agent "${id}" \u4E0D\u5B58\u5728`);
|
|
1129
|
+
process.exitCode = 1;
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
agents.splice(index, 1);
|
|
1133
|
+
config.agents = agents;
|
|
1134
|
+
writeConfig(configPath, config);
|
|
1135
|
+
logSuccess(`\u5DF2\u79FB\u9664 Agent "${id}"`);
|
|
1136
|
+
});
|
|
1137
|
+
|
|
1138
|
+
// src/commands/policy.ts
|
|
1139
|
+
import { Command as Command7 } from "commander";
|
|
1140
|
+
var policyCommand = new Command7("policy").description("\u7B56\u7565\u7BA1\u7406");
|
|
1141
|
+
policyCommand.command("set").description("\u8BBE\u7F6E\u6216\u66F4\u65B0\u7B56\u7565").argument("<agent>", "Agent ID").argument("<credential>", "\u51ED\u8BC1 ID").option("-c, --config <path>", "\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84", void 0).option("--actions <actions>", '\u5141\u8BB8\u7684\u64CD\u4F5C\u5217\u8868\uFF0C\u9017\u53F7\u5206\u9694\uFF08"*" \u8868\u793A\u5168\u90E8\u5141\u8BB8\uFF09', "*").action((agentId, credentialId, opts) => {
|
|
1142
|
+
const configPath = resolveConfigPath(opts.config);
|
|
1143
|
+
const config = readRawConfig(configPath);
|
|
1144
|
+
const policies = config.policies ?? [];
|
|
1145
|
+
const actions = opts.actions === "*" ? ["*"] : opts.actions.split(",").map((a) => a.trim());
|
|
1146
|
+
const existing = policies.find((p) => p.agent === agentId && p.credential === credentialId);
|
|
1147
|
+
if (existing) {
|
|
1148
|
+
existing.actions = actions;
|
|
1149
|
+
logSuccess(`\u5DF2\u66F4\u65B0\u7B56\u7565: ${agentId} -> ${credentialId}`);
|
|
1150
|
+
} else {
|
|
1151
|
+
policies.push({
|
|
1152
|
+
agent: agentId,
|
|
1153
|
+
credential: credentialId,
|
|
1154
|
+
actions
|
|
1155
|
+
});
|
|
1156
|
+
logSuccess(`\u5DF2\u521B\u5EFA\u7B56\u7565: ${agentId} -> ${credentialId}`);
|
|
1157
|
+
}
|
|
1158
|
+
config.policies = policies;
|
|
1159
|
+
writeConfig(configPath, config);
|
|
1160
|
+
});
|
|
1161
|
+
policyCommand.command("list").description("\u5217\u51FA\u6240\u6709\u7B56\u7565").option("-c, --config <path>", "\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84", void 0).action((opts) => {
|
|
1162
|
+
const configPath = resolveConfigPath(opts.config);
|
|
1163
|
+
const config = readRawConfig(configPath);
|
|
1164
|
+
const policies = config.policies ?? [];
|
|
1165
|
+
if (policies.length === 0) {
|
|
1166
|
+
log("\u6682\u65E0\u7B56\u7565");
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
log(`\u7B56\u7565\u5217\u8868 (${policies.length}):`);
|
|
1170
|
+
for (const policy of policies) {
|
|
1171
|
+
const actionsStr = policy.actions.includes("*") ? "\u6240\u6709\u64CD\u4F5C" : policy.actions.join(", ");
|
|
1172
|
+
log(` - ${policy.agent} -> ${policy.credential}: [${actionsStr}]`);
|
|
1173
|
+
}
|
|
1174
|
+
});
|
|
1175
|
+
policyCommand.command("remove").description("\u79FB\u9664\u7B56\u7565").argument("<agent>", "Agent ID").argument("<credential>", "\u51ED\u8BC1 ID").option("-c, --config <path>", "\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84", void 0).action((agentId, credentialId, opts) => {
|
|
1176
|
+
const configPath = resolveConfigPath(opts.config);
|
|
1177
|
+
const config = readRawConfig(configPath);
|
|
1178
|
+
const policies = config.policies ?? [];
|
|
1179
|
+
const index = policies.findIndex((p) => p.agent === agentId && p.credential === credentialId);
|
|
1180
|
+
if (index === -1) {
|
|
1181
|
+
logError(`\u7B56\u7565 "${agentId} -> ${credentialId}" \u4E0D\u5B58\u5728`);
|
|
1182
|
+
process.exitCode = 1;
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
policies.splice(index, 1);
|
|
1186
|
+
config.policies = policies;
|
|
1187
|
+
writeConfig(configPath, config);
|
|
1188
|
+
logSuccess(`\u5DF2\u79FB\u9664\u7B56\u7565: ${agentId} -> ${credentialId}`);
|
|
1189
|
+
});
|
|
1190
|
+
|
|
1191
|
+
// src/commands/ui.ts
|
|
1192
|
+
import { Command as Command8 } from "commander";
|
|
1193
|
+
|
|
1194
|
+
// src/ui/server.ts
|
|
1195
|
+
import http from "http";
|
|
1196
|
+
|
|
1197
|
+
// src/ui/html.ts
|
|
1198
|
+
function getHtml() {
|
|
1199
|
+
return `<!DOCTYPE html>
|
|
1200
|
+
<html lang="zh-CN">
|
|
1201
|
+
<head>
|
|
1202
|
+
<meta charset="UTF-8">
|
|
1203
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1204
|
+
<title>Agent Auth Broker \u2014 File Mode UI</title>
|
|
1205
|
+
<style>
|
|
1206
|
+
#broker-ui {
|
|
1207
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
1208
|
+
max-width: 960px;
|
|
1209
|
+
margin: 0 auto;
|
|
1210
|
+
padding: 24px;
|
|
1211
|
+
color: #1a1a1a;
|
|
1212
|
+
background: #fafafa;
|
|
1213
|
+
min-height: 100vh;
|
|
1214
|
+
}
|
|
1215
|
+
#broker-ui * { box-sizing: border-box; }
|
|
1216
|
+
#broker-ui h1 { font-size: 1.5rem; margin: 0 0 24px; }
|
|
1217
|
+
#broker-ui .tabs {
|
|
1218
|
+
display: flex;
|
|
1219
|
+
border-bottom: 2px solid #e5e7eb;
|
|
1220
|
+
margin-bottom: 24px;
|
|
1221
|
+
gap: 4px;
|
|
1222
|
+
}
|
|
1223
|
+
#broker-ui .tab {
|
|
1224
|
+
padding: 8px 20px;
|
|
1225
|
+
cursor: pointer;
|
|
1226
|
+
border: none;
|
|
1227
|
+
background: none;
|
|
1228
|
+
font-size: 0.95rem;
|
|
1229
|
+
color: #6b7280;
|
|
1230
|
+
border-bottom: 2px solid transparent;
|
|
1231
|
+
margin-bottom: -2px;
|
|
1232
|
+
transition: color 0.15s, border-color 0.15s;
|
|
1233
|
+
}
|
|
1234
|
+
#broker-ui .tab:hover { color: #374151; }
|
|
1235
|
+
#broker-ui .tab.active {
|
|
1236
|
+
color: #2563eb;
|
|
1237
|
+
border-bottom-color: #2563eb;
|
|
1238
|
+
font-weight: 600;
|
|
1239
|
+
}
|
|
1240
|
+
#broker-ui .panel { display: none; }
|
|
1241
|
+
#broker-ui .panel.active { display: block; }
|
|
1242
|
+
#broker-ui table {
|
|
1243
|
+
width: 100%;
|
|
1244
|
+
border-collapse: collapse;
|
|
1245
|
+
margin-bottom: 20px;
|
|
1246
|
+
background: #fff;
|
|
1247
|
+
border-radius: 8px;
|
|
1248
|
+
overflow: hidden;
|
|
1249
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
|
|
1250
|
+
}
|
|
1251
|
+
#broker-ui th, #broker-ui td {
|
|
1252
|
+
text-align: left;
|
|
1253
|
+
padding: 10px 14px;
|
|
1254
|
+
border-bottom: 1px solid #f0f0f0;
|
|
1255
|
+
font-size: 0.9rem;
|
|
1256
|
+
}
|
|
1257
|
+
#broker-ui th {
|
|
1258
|
+
background: #f9fafb;
|
|
1259
|
+
font-weight: 600;
|
|
1260
|
+
color: #374151;
|
|
1261
|
+
}
|
|
1262
|
+
#broker-ui .form-card {
|
|
1263
|
+
background: #fff;
|
|
1264
|
+
padding: 20px;
|
|
1265
|
+
border-radius: 8px;
|
|
1266
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
|
|
1267
|
+
margin-bottom: 20px;
|
|
1268
|
+
}
|
|
1269
|
+
#broker-ui .form-card h3 {
|
|
1270
|
+
margin: 0 0 16px;
|
|
1271
|
+
font-size: 1rem;
|
|
1272
|
+
color: #374151;
|
|
1273
|
+
}
|
|
1274
|
+
#broker-ui .form-row {
|
|
1275
|
+
display: flex;
|
|
1276
|
+
gap: 12px;
|
|
1277
|
+
margin-bottom: 12px;
|
|
1278
|
+
align-items: flex-end;
|
|
1279
|
+
}
|
|
1280
|
+
#broker-ui .form-group {
|
|
1281
|
+
display: flex;
|
|
1282
|
+
flex-direction: column;
|
|
1283
|
+
gap: 4px;
|
|
1284
|
+
flex: 1;
|
|
1285
|
+
}
|
|
1286
|
+
#broker-ui label {
|
|
1287
|
+
font-size: 0.82rem;
|
|
1288
|
+
color: #6b7280;
|
|
1289
|
+
font-weight: 500;
|
|
1290
|
+
}
|
|
1291
|
+
#broker-ui input, #broker-ui select {
|
|
1292
|
+
padding: 7px 10px;
|
|
1293
|
+
border: 1px solid #d1d5db;
|
|
1294
|
+
border-radius: 6px;
|
|
1295
|
+
font-size: 0.9rem;
|
|
1296
|
+
}
|
|
1297
|
+
#broker-ui input:focus, #broker-ui select:focus {
|
|
1298
|
+
outline: none;
|
|
1299
|
+
border-color: #2563eb;
|
|
1300
|
+
box-shadow: 0 0 0 2px rgba(37,99,235,0.1);
|
|
1301
|
+
}
|
|
1302
|
+
#broker-ui button {
|
|
1303
|
+
cursor: pointer;
|
|
1304
|
+
font-size: 0.9rem;
|
|
1305
|
+
}
|
|
1306
|
+
#broker-ui .btn {
|
|
1307
|
+
padding: 7px 16px;
|
|
1308
|
+
border: none;
|
|
1309
|
+
border-radius: 6px;
|
|
1310
|
+
font-weight: 500;
|
|
1311
|
+
}
|
|
1312
|
+
#broker-ui .btn-primary {
|
|
1313
|
+
background: #2563eb;
|
|
1314
|
+
color: #fff;
|
|
1315
|
+
}
|
|
1316
|
+
#broker-ui .btn-primary:hover { background: #1d4ed8; }
|
|
1317
|
+
#broker-ui .btn-danger {
|
|
1318
|
+
background: #ef4444;
|
|
1319
|
+
color: #fff;
|
|
1320
|
+
padding: 4px 10px;
|
|
1321
|
+
font-size: 0.82rem;
|
|
1322
|
+
}
|
|
1323
|
+
#broker-ui .btn-danger:hover { background: #dc2626; }
|
|
1324
|
+
#broker-ui .btn-secondary {
|
|
1325
|
+
background: #f3f4f6;
|
|
1326
|
+
color: #374151;
|
|
1327
|
+
border: 1px solid #d1d5db;
|
|
1328
|
+
}
|
|
1329
|
+
#broker-ui .btn-secondary:hover { background: #e5e7eb; }
|
|
1330
|
+
#broker-ui .yaml-preview {
|
|
1331
|
+
background: #1e293b;
|
|
1332
|
+
color: #e2e8f0;
|
|
1333
|
+
padding: 16px;
|
|
1334
|
+
border-radius: 8px;
|
|
1335
|
+
font-family: 'Menlo', 'Consolas', monospace;
|
|
1336
|
+
font-size: 0.85rem;
|
|
1337
|
+
line-height: 1.6;
|
|
1338
|
+
white-space: pre-wrap;
|
|
1339
|
+
overflow-x: auto;
|
|
1340
|
+
max-height: 600px;
|
|
1341
|
+
overflow-y: auto;
|
|
1342
|
+
}
|
|
1343
|
+
#broker-ui .toast {
|
|
1344
|
+
position: fixed;
|
|
1345
|
+
bottom: 24px;
|
|
1346
|
+
right: 24px;
|
|
1347
|
+
padding: 10px 20px;
|
|
1348
|
+
border-radius: 8px;
|
|
1349
|
+
color: #fff;
|
|
1350
|
+
font-size: 0.9rem;
|
|
1351
|
+
opacity: 0;
|
|
1352
|
+
transition: opacity 0.3s;
|
|
1353
|
+
z-index: 1000;
|
|
1354
|
+
}
|
|
1355
|
+
#broker-ui .toast.show { opacity: 1; }
|
|
1356
|
+
#broker-ui .toast.success { background: #059669; }
|
|
1357
|
+
#broker-ui .toast.error { background: #dc2626; }
|
|
1358
|
+
#broker-ui .empty {
|
|
1359
|
+
text-align: center;
|
|
1360
|
+
color: #9ca3af;
|
|
1361
|
+
padding: 32px;
|
|
1362
|
+
font-size: 0.9rem;
|
|
1363
|
+
}
|
|
1364
|
+
#broker-ui .header {
|
|
1365
|
+
display: flex;
|
|
1366
|
+
justify-content: space-between;
|
|
1367
|
+
align-items: center;
|
|
1368
|
+
margin-bottom: 24px;
|
|
1369
|
+
}
|
|
1370
|
+
#broker-ui .header .status {
|
|
1371
|
+
font-size: 0.82rem;
|
|
1372
|
+
color: #6b7280;
|
|
1373
|
+
}
|
|
1374
|
+
#broker-ui .badge {
|
|
1375
|
+
display: inline-block;
|
|
1376
|
+
padding: 2px 8px;
|
|
1377
|
+
border-radius: 9999px;
|
|
1378
|
+
font-size: 0.75rem;
|
|
1379
|
+
font-weight: 500;
|
|
1380
|
+
}
|
|
1381
|
+
#broker-ui .badge-blue { background: #dbeafe; color: #1d4ed8; }
|
|
1382
|
+
#broker-ui .badge-green { background: #dcfce7; color: #15803d; }
|
|
1383
|
+
</style>
|
|
1384
|
+
</head>
|
|
1385
|
+
<body>
|
|
1386
|
+
<div id="broker-ui">
|
|
1387
|
+
<div class="header">
|
|
1388
|
+
<h1>Agent Auth Broker</h1>
|
|
1389
|
+
<span class="status" id="config-path"></span>
|
|
1390
|
+
</div>
|
|
1391
|
+
<div class="tabs">
|
|
1392
|
+
<button class="tab active" data-tab="agents">Agents</button>
|
|
1393
|
+
<button class="tab" data-tab="credentials">Credentials</button>
|
|
1394
|
+
<button class="tab" data-tab="policies">Policies</button>
|
|
1395
|
+
<button class="tab" data-tab="yaml">YAML</button>
|
|
1396
|
+
</div>
|
|
1397
|
+
|
|
1398
|
+
<!-- Agents Panel -->
|
|
1399
|
+
<div class="panel active" id="panel-agents">
|
|
1400
|
+
<div class="form-card">
|
|
1401
|
+
<h3>Add Agent</h3>
|
|
1402
|
+
<div class="form-row">
|
|
1403
|
+
<div class="form-group">
|
|
1404
|
+
<label>ID</label>
|
|
1405
|
+
<input id="agent-id" placeholder="my-agent">
|
|
1406
|
+
</div>
|
|
1407
|
+
<div class="form-group">
|
|
1408
|
+
<label>Name</label>
|
|
1409
|
+
<input id="agent-name" placeholder="My AI Agent">
|
|
1410
|
+
</div>
|
|
1411
|
+
<button class="btn btn-primary" onclick="addAgent()">Add</button>
|
|
1412
|
+
</div>
|
|
1413
|
+
</div>
|
|
1414
|
+
<table id="agents-table">
|
|
1415
|
+
<thead><tr><th>ID</th><th>Name</th><th></th></tr></thead>
|
|
1416
|
+
<tbody></tbody>
|
|
1417
|
+
</table>
|
|
1418
|
+
</div>
|
|
1419
|
+
|
|
1420
|
+
<!-- Credentials Panel -->
|
|
1421
|
+
<div class="panel" id="panel-credentials">
|
|
1422
|
+
<div class="form-card">
|
|
1423
|
+
<h3>Add Credential</h3>
|
|
1424
|
+
<div class="form-row">
|
|
1425
|
+
<div class="form-group">
|
|
1426
|
+
<label>ID</label>
|
|
1427
|
+
<input id="cred-id" placeholder="github-main">
|
|
1428
|
+
</div>
|
|
1429
|
+
<div class="form-group">
|
|
1430
|
+
<label>Connector</label>
|
|
1431
|
+
<select id="cred-connector"></select>
|
|
1432
|
+
</div>
|
|
1433
|
+
<div class="form-group">
|
|
1434
|
+
<label>Token (env var reference)</label>
|
|
1435
|
+
<input id="cred-token" placeholder="\${GITHUB_TOKEN}">
|
|
1436
|
+
</div>
|
|
1437
|
+
<button class="btn btn-primary" onclick="addCredential()">Add</button>
|
|
1438
|
+
</div>
|
|
1439
|
+
</div>
|
|
1440
|
+
<table id="credentials-table">
|
|
1441
|
+
<thead><tr><th>ID</th><th>Connector</th><th>Token</th><th></th></tr></thead>
|
|
1442
|
+
<tbody></tbody>
|
|
1443
|
+
</table>
|
|
1444
|
+
</div>
|
|
1445
|
+
|
|
1446
|
+
<!-- Policies Panel -->
|
|
1447
|
+
<div class="panel" id="panel-policies">
|
|
1448
|
+
<div class="form-card">
|
|
1449
|
+
<h3>Add Policy</h3>
|
|
1450
|
+
<div class="form-row">
|
|
1451
|
+
<div class="form-group">
|
|
1452
|
+
<label>Agent</label>
|
|
1453
|
+
<select id="policy-agent"></select>
|
|
1454
|
+
</div>
|
|
1455
|
+
<div class="form-group">
|
|
1456
|
+
<label>Credential</label>
|
|
1457
|
+
<select id="policy-credential"></select>
|
|
1458
|
+
</div>
|
|
1459
|
+
<div class="form-group">
|
|
1460
|
+
<label>Actions (comma-separated, * for all)</label>
|
|
1461
|
+
<input id="policy-actions" placeholder="*" value="*">
|
|
1462
|
+
</div>
|
|
1463
|
+
<button class="btn btn-primary" onclick="addPolicy()">Add</button>
|
|
1464
|
+
</div>
|
|
1465
|
+
</div>
|
|
1466
|
+
<table id="policies-table">
|
|
1467
|
+
<thead><tr><th>Agent</th><th>Credential</th><th>Actions</th><th></th></tr></thead>
|
|
1468
|
+
<tbody></tbody>
|
|
1469
|
+
</table>
|
|
1470
|
+
</div>
|
|
1471
|
+
|
|
1472
|
+
<!-- YAML Preview Panel -->
|
|
1473
|
+
<div class="panel" id="panel-yaml">
|
|
1474
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
|
|
1475
|
+
<h3 style="margin:0;font-size:1rem;color:#374151;">broker.yaml</h3>
|
|
1476
|
+
<button class="btn btn-secondary" onclick="refreshYaml()">Refresh</button>
|
|
1477
|
+
</div>
|
|
1478
|
+
<pre class="yaml-preview" id="yaml-content"></pre>
|
|
1479
|
+
</div>
|
|
1480
|
+
|
|
1481
|
+
<div class="toast" id="toast"></div>
|
|
1482
|
+
</div>
|
|
1483
|
+
|
|
1484
|
+
<script>
|
|
1485
|
+
const API = '';
|
|
1486
|
+
|
|
1487
|
+
// Tab switching
|
|
1488
|
+
document.querySelectorAll('#broker-ui .tab').forEach(tab => {
|
|
1489
|
+
tab.addEventListener('click', () => {
|
|
1490
|
+
document.querySelectorAll('#broker-ui .tab').forEach(t => t.classList.remove('active'));
|
|
1491
|
+
document.querySelectorAll('#broker-ui .panel').forEach(p => p.classList.remove('active'));
|
|
1492
|
+
tab.classList.add('active');
|
|
1493
|
+
document.getElementById('panel-' + tab.dataset.tab).classList.add('active');
|
|
1494
|
+
if (tab.dataset.tab === 'yaml') refreshYaml();
|
|
1495
|
+
if (tab.dataset.tab === 'policies') refreshSelects();
|
|
1496
|
+
});
|
|
1497
|
+
});
|
|
1498
|
+
|
|
1499
|
+
function toast(msg, type) {
|
|
1500
|
+
const el = document.getElementById('toast');
|
|
1501
|
+
el.textContent = msg;
|
|
1502
|
+
el.className = 'toast show ' + type;
|
|
1503
|
+
setTimeout(() => el.classList.remove('show'), 2500);
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
async function api(method, path, body) {
|
|
1507
|
+
const opts = { method, headers: { 'Content-Type': 'application/json' } };
|
|
1508
|
+
if (body) opts.body = JSON.stringify(body);
|
|
1509
|
+
const res = await fetch(API + path, opts);
|
|
1510
|
+
if (!res.ok) {
|
|
1511
|
+
const text = await res.text();
|
|
1512
|
+
throw new Error(text || res.statusText);
|
|
1513
|
+
}
|
|
1514
|
+
return res.json();
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
// ===== Agents =====
|
|
1518
|
+
async function loadAgents() {
|
|
1519
|
+
try {
|
|
1520
|
+
const data = await api('GET', '/api/agents');
|
|
1521
|
+
const tbody = document.querySelector('#agents-table tbody');
|
|
1522
|
+
if (!data.length) {
|
|
1523
|
+
tbody.innerHTML = '<tr><td colspan="3" class="empty">No agents configured</td></tr>';
|
|
1524
|
+
return;
|
|
1525
|
+
}
|
|
1526
|
+
tbody.innerHTML = data.map(a =>
|
|
1527
|
+
'<tr><td>' + esc(a.id) + '</td><td>' + esc(a.name) + '</td>' +
|
|
1528
|
+
'<td><button class="btn btn-danger" onclick="deleteAgent(\\'' + esc(a.id) + '\\')">Delete</button></td></tr>'
|
|
1529
|
+
).join('');
|
|
1530
|
+
} catch (e) { toast(e.message, 'error'); }
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
async function addAgent() {
|
|
1534
|
+
const id = document.getElementById('agent-id').value.trim();
|
|
1535
|
+
const name = document.getElementById('agent-name').value.trim();
|
|
1536
|
+
if (!id || !name) return toast('ID and Name are required', 'error');
|
|
1537
|
+
try {
|
|
1538
|
+
await api('POST', '/api/agents', { id, name });
|
|
1539
|
+
document.getElementById('agent-id').value = '';
|
|
1540
|
+
document.getElementById('agent-name').value = '';
|
|
1541
|
+
toast('Agent added', 'success');
|
|
1542
|
+
loadAgents();
|
|
1543
|
+
} catch (e) { toast(e.message, 'error'); }
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
async function deleteAgent(id) {
|
|
1547
|
+
try {
|
|
1548
|
+
await api('DELETE', '/api/agents/' + id);
|
|
1549
|
+
toast('Agent deleted', 'success');
|
|
1550
|
+
loadAgents();
|
|
1551
|
+
} catch (e) { toast(e.message, 'error'); }
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
// ===== Credentials =====
|
|
1555
|
+
async function loadCredentials() {
|
|
1556
|
+
try {
|
|
1557
|
+
const data = await api('GET', '/api/credentials');
|
|
1558
|
+
const tbody = document.querySelector('#credentials-table tbody');
|
|
1559
|
+
if (!data.length) {
|
|
1560
|
+
tbody.innerHTML = '<tr><td colspan="4" class="empty">No credentials configured</td></tr>';
|
|
1561
|
+
return;
|
|
1562
|
+
}
|
|
1563
|
+
tbody.innerHTML = data.map(c =>
|
|
1564
|
+
'<tr><td>' + esc(c.id) + '</td><td><span class="badge badge-blue">' + esc(c.connector) + '</span></td>' +
|
|
1565
|
+
'<td>' + esc(c.token || '(encrypted)') + '</td>' +
|
|
1566
|
+
'<td><button class="btn btn-danger" onclick="deleteCredential(\\'' + esc(c.id) + '\\')">Delete</button></td></tr>'
|
|
1567
|
+
).join('');
|
|
1568
|
+
} catch (e) { toast(e.message, 'error'); }
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
async function addCredential() {
|
|
1572
|
+
const id = document.getElementById('cred-id').value.trim();
|
|
1573
|
+
const connector = document.getElementById('cred-connector').value;
|
|
1574
|
+
const token = document.getElementById('cred-token').value.trim();
|
|
1575
|
+
if (!id || !connector || !token) return toast('All fields are required', 'error');
|
|
1576
|
+
try {
|
|
1577
|
+
await api('POST', '/api/credentials', { id, connector, token });
|
|
1578
|
+
document.getElementById('cred-id').value = '';
|
|
1579
|
+
document.getElementById('cred-token').value = '';
|
|
1580
|
+
toast('Credential added', 'success');
|
|
1581
|
+
loadCredentials();
|
|
1582
|
+
} catch (e) { toast(e.message, 'error'); }
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
async function deleteCredential(id) {
|
|
1586
|
+
try {
|
|
1587
|
+
await api('DELETE', '/api/credentials/' + id);
|
|
1588
|
+
toast('Credential deleted', 'success');
|
|
1589
|
+
loadCredentials();
|
|
1590
|
+
} catch (e) { toast(e.message, 'error'); }
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
// ===== Policies =====
|
|
1594
|
+
async function loadPolicies() {
|
|
1595
|
+
try {
|
|
1596
|
+
const data = await api('GET', '/api/policies');
|
|
1597
|
+
const tbody = document.querySelector('#policies-table tbody');
|
|
1598
|
+
if (!data.length) {
|
|
1599
|
+
tbody.innerHTML = '<tr><td colspan="4" class="empty">No policies configured</td></tr>';
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1602
|
+
tbody.innerHTML = data.map(p => {
|
|
1603
|
+
const acts = Array.isArray(p.actions) ? p.actions.join(', ') : String(p.actions);
|
|
1604
|
+
return '<tr><td>' + esc(p.agent) + '</td><td>' + esc(p.credential) + '</td>' +
|
|
1605
|
+
'<td><span class="badge badge-green">' + esc(acts) + '</span></td>' +
|
|
1606
|
+
'<td><button class="btn btn-danger" onclick="deletePolicy(\\'' + esc(p.agent) + '\\',\\'' + esc(p.credential) + '\\')">Delete</button></td></tr>';
|
|
1607
|
+
}).join('');
|
|
1608
|
+
} catch (e) { toast(e.message, 'error'); }
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
async function addPolicy() {
|
|
1612
|
+
const agent = document.getElementById('policy-agent').value;
|
|
1613
|
+
const credential = document.getElementById('policy-credential').value;
|
|
1614
|
+
const actionsStr = document.getElementById('policy-actions').value.trim();
|
|
1615
|
+
if (!agent || !credential || !actionsStr) return toast('All fields are required', 'error');
|
|
1616
|
+
const actions = actionsStr === '*' ? ['*'] : actionsStr.split(',').map(s => s.trim()).filter(Boolean);
|
|
1617
|
+
try {
|
|
1618
|
+
await api('POST', '/api/policies', { agent, credential, actions });
|
|
1619
|
+
toast('Policy added', 'success');
|
|
1620
|
+
loadPolicies();
|
|
1621
|
+
} catch (e) { toast(e.message, 'error'); }
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
async function deletePolicy(agent, credential) {
|
|
1625
|
+
try {
|
|
1626
|
+
await api('DELETE', '/api/policies', { agent, credential });
|
|
1627
|
+
toast('Policy deleted', 'success');
|
|
1628
|
+
loadPolicies();
|
|
1629
|
+
} catch (e) { toast(e.message, 'error'); }
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
// ===== YAML =====
|
|
1633
|
+
async function refreshYaml() {
|
|
1634
|
+
try {
|
|
1635
|
+
const data = await api('GET', '/api/config');
|
|
1636
|
+
document.getElementById('yaml-content').textContent = data.yaml;
|
|
1637
|
+
} catch (e) { toast(e.message, 'error'); }
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
// ===== Helpers =====
|
|
1641
|
+
function esc(s) { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
|
|
1642
|
+
|
|
1643
|
+
async function refreshSelects() {
|
|
1644
|
+
try {
|
|
1645
|
+
const agents = await api('GET', '/api/agents');
|
|
1646
|
+
const creds = await api('GET', '/api/credentials');
|
|
1647
|
+
const pAgent = document.getElementById('policy-agent');
|
|
1648
|
+
const pCred = document.getElementById('policy-credential');
|
|
1649
|
+
pAgent.innerHTML = agents.map(a => '<option value="' + esc(a.id) + '">' + esc(a.id) + '</option>').join('');
|
|
1650
|
+
pCred.innerHTML = creds.map(c => '<option value="' + esc(c.id) + '">' + esc(c.id) + '</option>').join('');
|
|
1651
|
+
} catch (e) {}
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
async function loadConnectors() {
|
|
1655
|
+
try {
|
|
1656
|
+
const data = await api('GET', '/api/connectors');
|
|
1657
|
+
const sel = document.getElementById('cred-connector');
|
|
1658
|
+
sel.innerHTML = data.map(c => '<option value="' + esc(c.id) + '">' + esc(c.name) + '</option>').join('');
|
|
1659
|
+
} catch (e) {}
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
// Initial load
|
|
1663
|
+
loadAgents();
|
|
1664
|
+
loadCredentials();
|
|
1665
|
+
loadPolicies();
|
|
1666
|
+
loadConnectors();
|
|
1667
|
+
|
|
1668
|
+
// Show config path
|
|
1669
|
+
api('GET', '/api/config').then(d => {
|
|
1670
|
+
document.getElementById('config-path').textContent = d.path || '';
|
|
1671
|
+
}).catch(() => {});
|
|
1672
|
+
</script>
|
|
1673
|
+
</body>
|
|
1674
|
+
</html>`;
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
// src/ui/handlers.ts
|
|
1678
|
+
import { stringify as stringifyYaml3 } from "yaml";
|
|
1679
|
+
function createHandlers(configPath) {
|
|
1680
|
+
function getConfig() {
|
|
1681
|
+
return readRawConfig(configPath);
|
|
1682
|
+
}
|
|
1683
|
+
function saveConfig(config) {
|
|
1684
|
+
writeConfig(configPath, config);
|
|
1685
|
+
}
|
|
1686
|
+
return {
|
|
1687
|
+
// GET /api/config — 返回脱敏的 YAML 和路径
|
|
1688
|
+
getConfigInfo() {
|
|
1689
|
+
const config = getConfig();
|
|
1690
|
+
const sanitized = JSON.parse(JSON.stringify(config));
|
|
1691
|
+
const creds = sanitized.credentials ?? [];
|
|
1692
|
+
for (const c of creds) {
|
|
1693
|
+
if (c.token && !c.token.startsWith("${")) {
|
|
1694
|
+
c.token = "***";
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
const yaml = stringifyYaml3(sanitized, { lineWidth: 120 });
|
|
1698
|
+
return { yaml, path: configPath };
|
|
1699
|
+
},
|
|
1700
|
+
// GET /api/agents
|
|
1701
|
+
listAgents() {
|
|
1702
|
+
const config = getConfig();
|
|
1703
|
+
return config.agents ?? [];
|
|
1704
|
+
},
|
|
1705
|
+
// POST /api/agents
|
|
1706
|
+
addAgent(body) {
|
|
1707
|
+
const config = getConfig();
|
|
1708
|
+
const agents = config.agents ?? [];
|
|
1709
|
+
if (agents.find((a) => a.id === body.id)) {
|
|
1710
|
+
throw new Error(`Agent "${body.id}" already exists`);
|
|
1711
|
+
}
|
|
1712
|
+
agents.push({ id: body.id, name: body.name });
|
|
1713
|
+
config.agents = agents;
|
|
1714
|
+
saveConfig(config);
|
|
1715
|
+
return { ok: true };
|
|
1716
|
+
},
|
|
1717
|
+
// DELETE /api/agents/:id
|
|
1718
|
+
deleteAgent(id) {
|
|
1719
|
+
const config = getConfig();
|
|
1720
|
+
const agents = config.agents ?? [];
|
|
1721
|
+
const index = agents.findIndex((a) => a.id === id);
|
|
1722
|
+
if (index === -1) throw new Error(`Agent "${id}" not found`);
|
|
1723
|
+
agents.splice(index, 1);
|
|
1724
|
+
config.agents = agents;
|
|
1725
|
+
const policies = config.policies ?? [];
|
|
1726
|
+
config.policies = policies.filter((p) => p.agent !== id);
|
|
1727
|
+
saveConfig(config);
|
|
1728
|
+
return { ok: true };
|
|
1729
|
+
},
|
|
1730
|
+
// GET /api/credentials
|
|
1731
|
+
listCredentials() {
|
|
1732
|
+
const config = getConfig();
|
|
1733
|
+
const creds = config.credentials ?? [];
|
|
1734
|
+
return creds.map((c) => ({
|
|
1735
|
+
id: c.id,
|
|
1736
|
+
connector: c.connector,
|
|
1737
|
+
token: c.token ? c.token.startsWith("${") ? c.token : "***" : void 0
|
|
1738
|
+
}));
|
|
1739
|
+
},
|
|
1740
|
+
// POST /api/credentials
|
|
1741
|
+
addCredential(body) {
|
|
1742
|
+
const config = getConfig();
|
|
1743
|
+
const creds = config.credentials ?? [];
|
|
1744
|
+
if (creds.find((c) => c.id === body.id)) {
|
|
1745
|
+
throw new Error(`Credential "${body.id}" already exists`);
|
|
1746
|
+
}
|
|
1747
|
+
creds.push({ id: body.id, connector: body.connector, token: body.token });
|
|
1748
|
+
config.credentials = creds;
|
|
1749
|
+
saveConfig(config);
|
|
1750
|
+
return { ok: true };
|
|
1751
|
+
},
|
|
1752
|
+
// DELETE /api/credentials/:id
|
|
1753
|
+
deleteCredential(id) {
|
|
1754
|
+
const config = getConfig();
|
|
1755
|
+
const creds = config.credentials ?? [];
|
|
1756
|
+
const index = creds.findIndex((c) => c.id === id);
|
|
1757
|
+
if (index === -1) throw new Error(`Credential "${id}" not found`);
|
|
1758
|
+
creds.splice(index, 1);
|
|
1759
|
+
config.credentials = creds;
|
|
1760
|
+
const policies = config.policies ?? [];
|
|
1761
|
+
config.policies = policies.filter((p) => p.credential !== id);
|
|
1762
|
+
saveConfig(config);
|
|
1763
|
+
return { ok: true };
|
|
1764
|
+
},
|
|
1765
|
+
// GET /api/policies
|
|
1766
|
+
listPolicies() {
|
|
1767
|
+
const config = getConfig();
|
|
1768
|
+
return config.policies ?? [];
|
|
1769
|
+
},
|
|
1770
|
+
// POST /api/policies
|
|
1771
|
+
addPolicy(body) {
|
|
1772
|
+
const config = getConfig();
|
|
1773
|
+
const policies = config.policies ?? [];
|
|
1774
|
+
const existing = policies.find((p) => p.agent === body.agent && p.credential === body.credential);
|
|
1775
|
+
if (existing) {
|
|
1776
|
+
existing.actions = body.actions;
|
|
1777
|
+
} else {
|
|
1778
|
+
policies.push({ agent: body.agent, credential: body.credential, actions: body.actions });
|
|
1779
|
+
}
|
|
1780
|
+
config.policies = policies;
|
|
1781
|
+
saveConfig(config);
|
|
1782
|
+
return { ok: true };
|
|
1783
|
+
},
|
|
1784
|
+
// DELETE /api/policies
|
|
1785
|
+
deletePolicy(body) {
|
|
1786
|
+
const config = getConfig();
|
|
1787
|
+
const policies = config.policies ?? [];
|
|
1788
|
+
const index = policies.findIndex((p) => p.agent === body.agent && p.credential === body.credential);
|
|
1789
|
+
if (index === -1) throw new Error("Policy not found");
|
|
1790
|
+
policies.splice(index, 1);
|
|
1791
|
+
config.policies = policies;
|
|
1792
|
+
saveConfig(config);
|
|
1793
|
+
return { ok: true };
|
|
1794
|
+
},
|
|
1795
|
+
// GET /api/connectors
|
|
1796
|
+
listConnectorsInfo() {
|
|
1797
|
+
return listConnectors().map((c) => ({ id: c.info.id, name: c.info.name }));
|
|
1798
|
+
},
|
|
1799
|
+
// POST /api/validate
|
|
1800
|
+
validateConfig() {
|
|
1801
|
+
try {
|
|
1802
|
+
return validateConfigFile(configPath);
|
|
1803
|
+
} catch (e) {
|
|
1804
|
+
return { valid: false, errors: [e.message] };
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
};
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
// src/ui/server.ts
|
|
1811
|
+
function startServer(configPath, port) {
|
|
1812
|
+
const handlers = createHandlers(configPath);
|
|
1813
|
+
const server = http.createServer(async (req, res) => {
|
|
1814
|
+
const url = new URL(req.url ?? "/", `http://localhost:${port}`);
|
|
1815
|
+
const method = req.method ?? "GET";
|
|
1816
|
+
const pathname = url.pathname;
|
|
1817
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1818
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
|
|
1819
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
1820
|
+
if (method === "OPTIONS") {
|
|
1821
|
+
res.writeHead(204);
|
|
1822
|
+
res.end();
|
|
1823
|
+
return;
|
|
1824
|
+
}
|
|
1825
|
+
try {
|
|
1826
|
+
if (method === "GET" && pathname === "/") {
|
|
1827
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
1828
|
+
res.end(getHtml());
|
|
1829
|
+
return;
|
|
1830
|
+
}
|
|
1831
|
+
if (pathname.startsWith("/api/")) {
|
|
1832
|
+
const body = method !== "GET" ? await readBody(req) : void 0;
|
|
1833
|
+
let result;
|
|
1834
|
+
if (method === "GET" && pathname === "/api/config") {
|
|
1835
|
+
result = handlers.getConfigInfo();
|
|
1836
|
+
} else if (method === "GET" && pathname === "/api/agents") {
|
|
1837
|
+
result = handlers.listAgents();
|
|
1838
|
+
} else if (method === "POST" && pathname === "/api/agents") {
|
|
1839
|
+
result = handlers.addAgent(body);
|
|
1840
|
+
} else if (method === "DELETE" && pathname.startsWith("/api/agents/")) {
|
|
1841
|
+
const id = pathname.slice("/api/agents/".length);
|
|
1842
|
+
result = handlers.deleteAgent(decodeURIComponent(id));
|
|
1843
|
+
} else if (method === "GET" && pathname === "/api/credentials") {
|
|
1844
|
+
result = handlers.listCredentials();
|
|
1845
|
+
} else if (method === "POST" && pathname === "/api/credentials") {
|
|
1846
|
+
result = handlers.addCredential(body);
|
|
1847
|
+
} else if (method === "DELETE" && pathname.startsWith("/api/credentials/")) {
|
|
1848
|
+
const id = pathname.slice("/api/credentials/".length);
|
|
1849
|
+
result = handlers.deleteCredential(decodeURIComponent(id));
|
|
1850
|
+
} else if (method === "GET" && pathname === "/api/policies") {
|
|
1851
|
+
result = handlers.listPolicies();
|
|
1852
|
+
} else if (method === "POST" && pathname === "/api/policies") {
|
|
1853
|
+
result = handlers.addPolicy(body);
|
|
1854
|
+
} else if (method === "DELETE" && pathname === "/api/policies") {
|
|
1855
|
+
result = handlers.deletePolicy(body);
|
|
1856
|
+
} else if (method === "GET" && pathname === "/api/connectors") {
|
|
1857
|
+
result = handlers.listConnectorsInfo();
|
|
1858
|
+
} else if (method === "POST" && pathname === "/api/validate") {
|
|
1859
|
+
result = handlers.validateConfig();
|
|
1860
|
+
} else {
|
|
1861
|
+
res.writeHead(404);
|
|
1862
|
+
res.end(JSON.stringify({ error: "Not found" }));
|
|
1863
|
+
return;
|
|
1864
|
+
}
|
|
1865
|
+
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
1866
|
+
res.end(JSON.stringify(result));
|
|
1867
|
+
return;
|
|
1868
|
+
}
|
|
1869
|
+
res.writeHead(404);
|
|
1870
|
+
res.end("Not found");
|
|
1871
|
+
} catch (err) {
|
|
1872
|
+
const message = err instanceof Error ? err.message : "Internal server error";
|
|
1873
|
+
res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
|
|
1874
|
+
res.end(JSON.stringify({ error: message }));
|
|
1875
|
+
}
|
|
1876
|
+
});
|
|
1877
|
+
server.listen(port, () => {
|
|
1878
|
+
console.log(`Broker UI running at http://localhost:${port}`);
|
|
1879
|
+
console.log(`Config: ${configPath}`);
|
|
1880
|
+
console.log("Press Ctrl+C to stop");
|
|
1881
|
+
});
|
|
1882
|
+
return server;
|
|
1883
|
+
}
|
|
1884
|
+
function readBody(req) {
|
|
1885
|
+
return new Promise((resolve, reject) => {
|
|
1886
|
+
const chunks = [];
|
|
1887
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
1888
|
+
req.on("end", () => {
|
|
1889
|
+
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
1890
|
+
try {
|
|
1891
|
+
resolve(raw ? JSON.parse(raw) : {});
|
|
1892
|
+
} catch {
|
|
1893
|
+
reject(new Error("Invalid JSON body"));
|
|
1894
|
+
}
|
|
1895
|
+
});
|
|
1896
|
+
req.on("error", reject);
|
|
1897
|
+
});
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
// src/commands/ui.ts
|
|
1901
|
+
var uiCommand = new Command8("ui").description("\u542F\u52A8 File Mode Web UI\uFF0C\u53EF\u89C6\u5316\u7BA1\u7406 broker.yaml").option("-c, --config <path>", "\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84", void 0).option("-p, --port <port>", "\u670D\u52A1\u7AEF\u53E3", "3200").action((opts) => {
|
|
1902
|
+
const configPath = resolveConfigPath(opts.config);
|
|
1903
|
+
const port = parseInt(opts.port, 10);
|
|
1904
|
+
if (isNaN(port) || port < 1 || port > 65535) {
|
|
1905
|
+
console.error("Invalid port number");
|
|
1906
|
+
process.exitCode = 1;
|
|
1907
|
+
return;
|
|
1908
|
+
}
|
|
1909
|
+
startServer(configPath, port);
|
|
1910
|
+
});
|
|
1911
|
+
|
|
1912
|
+
// src/commands/token.ts
|
|
1913
|
+
import { Command as Command9 } from "commander";
|
|
1914
|
+
var tokenCommand = new Command9("token").description("Agent Token \u7BA1\u7406");
|
|
1915
|
+
tokenCommand.command("generate").description("\u4E3A Agent \u751F\u6210\u8BA4\u8BC1 Token").argument("<agent>", "Agent ID").option("-c, --config <path>", "\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84", void 0).option("-f, --force", "\u8986\u76D6\u5DF2\u6709 token", false).action((agentId, opts) => {
|
|
1916
|
+
const configPath = resolveConfigPath(opts.config);
|
|
1917
|
+
const config = readRawConfig(configPath);
|
|
1918
|
+
const agents = config.agents ?? [];
|
|
1919
|
+
const agent = agents.find((a) => a.id === agentId);
|
|
1920
|
+
if (!agent) {
|
|
1921
|
+
logError(`Agent "${agentId}" \u4E0D\u5B58\u5728`);
|
|
1922
|
+
process.exitCode = 1;
|
|
1923
|
+
return;
|
|
1924
|
+
}
|
|
1925
|
+
if (agent.token_hash && !opts.force) {
|
|
1926
|
+
logError(`Agent "${agentId}" \u5DF2\u6709 token\uFF08prefix: ${agent.token_prefix}\uFF09\uFF0C\u4F7F\u7528 --force \u8986\u76D6`);
|
|
1927
|
+
process.exitCode = 1;
|
|
1928
|
+
return;
|
|
1929
|
+
}
|
|
1930
|
+
const { token, prefix } = generateAgentToken();
|
|
1931
|
+
agent.token_hash = hashToken(token);
|
|
1932
|
+
agent.token_prefix = prefix;
|
|
1933
|
+
config.agents = agents;
|
|
1934
|
+
writeConfig(configPath, config);
|
|
1935
|
+
logSuccess(`Token \u5DF2\u751F\u6210`);
|
|
1936
|
+
log("");
|
|
1937
|
+
log(` Token: ${token}`);
|
|
1938
|
+
log(` Prefix: ${prefix}`);
|
|
1939
|
+
log("");
|
|
1940
|
+
log(" \u8BF7\u59A5\u5584\u4FDD\u5B58\u6B64 token\uFF0C\u5B83\u4E0D\u4F1A\u518D\u6B21\u663E\u793A\u3002");
|
|
1941
|
+
log(" \u5728 MCP \u914D\u7F6E\u4E2D\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF\uFF1A");
|
|
1942
|
+
log(` BROKER_AGENT_TOKEN="${token}"`);
|
|
1943
|
+
});
|
|
1944
|
+
tokenCommand.command("revoke").description("\u64A4\u9500 Agent \u7684 Token").argument("<agent>", "Agent ID").option("-c, --config <path>", "\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84", void 0).action((agentId, opts) => {
|
|
1945
|
+
const configPath = resolveConfigPath(opts.config);
|
|
1946
|
+
const config = readRawConfig(configPath);
|
|
1947
|
+
const agents = config.agents ?? [];
|
|
1948
|
+
const agent = agents.find((a) => a.id === agentId);
|
|
1949
|
+
if (!agent) {
|
|
1950
|
+
logError(`Agent "${agentId}" \u4E0D\u5B58\u5728`);
|
|
1951
|
+
process.exitCode = 1;
|
|
1952
|
+
return;
|
|
1953
|
+
}
|
|
1954
|
+
if (!agent.token_hash) {
|
|
1955
|
+
logError(`Agent "${agentId}" \u6CA1\u6709 token`);
|
|
1956
|
+
process.exitCode = 1;
|
|
1957
|
+
return;
|
|
1958
|
+
}
|
|
1959
|
+
delete agent.token_hash;
|
|
1960
|
+
delete agent.token_prefix;
|
|
1961
|
+
config.agents = agents;
|
|
1962
|
+
writeConfig(configPath, config);
|
|
1963
|
+
logSuccess(`\u5DF2\u64A4\u9500 Agent "${agentId}" \u7684 token`);
|
|
1964
|
+
});
|
|
1965
|
+
tokenCommand.command("list").description("\u5217\u51FA\u6240\u6709 Agent \u7684 Token \u72B6\u6001").option("-c, --config <path>", "\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84", void 0).action((opts) => {
|
|
1966
|
+
const configPath = resolveConfigPath(opts.config);
|
|
1967
|
+
const config = readRawConfig(configPath);
|
|
1968
|
+
const agents = config.agents ?? [];
|
|
1969
|
+
if (agents.length === 0) {
|
|
1970
|
+
log("\u6682\u65E0 Agent");
|
|
1971
|
+
return;
|
|
1972
|
+
}
|
|
1973
|
+
log(`Agent Token \u72B6\u6001 (${agents.length}):`);
|
|
1974
|
+
for (const agent of agents) {
|
|
1975
|
+
if (agent.token_hash) {
|
|
1976
|
+
log(` - ${agent.id} (${agent.name}): token set (prefix: ${agent.token_prefix})`);
|
|
1977
|
+
} else {
|
|
1978
|
+
log(` - ${agent.id} (${agent.name}): no token`);
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
});
|
|
1982
|
+
|
|
1983
|
+
// src/commands/test.ts
|
|
1984
|
+
import { Command as Command10 } from "commander";
|
|
1985
|
+
var testCommand = new Command10("test").description("\u6D4B\u8BD5\u8C03\u7528 connector \u64CD\u4F5C").argument("<connector>", 'Connector \u540D\u79F0\uFF0C\u5982 "github"').argument("<action>", '\u64CD\u4F5C\u540D\u79F0\uFF0C\u5982 "list_repos"').option("-c, --config <path>", "\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84", void 0).option("-a, --agent <id>", "Agent ID", void 0).option("-p, --params <json>", "\u64CD\u4F5C\u53C2\u6570\uFF08JSON \u683C\u5F0F\uFF09", "{}").option("--dry-run", "\u4EC5\u6267\u884C\u6743\u9650\u68C0\u67E5\uFF0C\u4E0D\u5B9E\u9645\u8C03\u7528 API", false).action(async (connector, action, opts) => {
|
|
1986
|
+
const configPath = resolveConfigPath(opts.config);
|
|
1987
|
+
let config;
|
|
1988
|
+
try {
|
|
1989
|
+
config = loadConfig(configPath);
|
|
1990
|
+
} catch (err) {
|
|
1991
|
+
logError(`\u914D\u7F6E\u52A0\u8F7D\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
|
|
1992
|
+
process.exitCode = 1;
|
|
1993
|
+
return;
|
|
1994
|
+
}
|
|
1995
|
+
const store = new LocalStore(config);
|
|
1996
|
+
let agentId;
|
|
1997
|
+
const envToken = process.env.BROKER_AGENT_TOKEN;
|
|
1998
|
+
if (envToken) {
|
|
1999
|
+
const matched = authenticateByToken(envToken, store);
|
|
2000
|
+
if (!matched) {
|
|
2001
|
+
logError("BROKER_AGENT_TOKEN \u8BA4\u8BC1\u5931\u8D25");
|
|
2002
|
+
process.exitCode = 1;
|
|
2003
|
+
return;
|
|
2004
|
+
}
|
|
2005
|
+
agentId = matched.id;
|
|
2006
|
+
} else {
|
|
2007
|
+
agentId = opts.agent ?? config.agents[0].id;
|
|
2008
|
+
}
|
|
2009
|
+
let params;
|
|
2010
|
+
try {
|
|
2011
|
+
params = JSON.parse(opts.params);
|
|
2012
|
+
} catch {
|
|
2013
|
+
logError("\u53C2\u6570\u683C\u5F0F\u9519\u8BEF\uFF0C\u8BF7\u63D0\u4F9B\u6709\u6548\u7684 JSON");
|
|
2014
|
+
process.exitCode = 1;
|
|
2015
|
+
return;
|
|
2016
|
+
}
|
|
2017
|
+
log(`Agent: ${agentId}`);
|
|
2018
|
+
log(`Connector: ${connector}`);
|
|
2019
|
+
log(`Action: ${action}`);
|
|
2020
|
+
log(`Params: ${JSON.stringify(params)}`);
|
|
2021
|
+
log("");
|
|
2022
|
+
const permResult = checkLocalPermission(
|
|
2023
|
+
{ agentId, connectorId: connector, action, params },
|
|
2024
|
+
store
|
|
2025
|
+
);
|
|
2026
|
+
if (permResult.result !== "ALLOWED") {
|
|
2027
|
+
logError(`\u6743\u9650\u68C0\u67E5\u5931\u8D25: ${permResult.result}`);
|
|
2028
|
+
log(` ${permResult.message ?? ""}`);
|
|
2029
|
+
process.exitCode = 1;
|
|
2030
|
+
return;
|
|
2031
|
+
}
|
|
2032
|
+
logSuccess("\u6743\u9650\u68C0\u67E5\u901A\u8FC7");
|
|
2033
|
+
if (opts.dryRun) {
|
|
2034
|
+
log("(dry-run \u6A21\u5F0F\uFF0C\u8DF3\u8FC7\u5B9E\u9645\u8C03\u7528)");
|
|
2035
|
+
return;
|
|
2036
|
+
}
|
|
2037
|
+
log("");
|
|
2038
|
+
log("\u6267\u884C\u4E2D...");
|
|
2039
|
+
const broker = new LocalBroker(store);
|
|
2040
|
+
const result = await broker.callTool(agentId, connector, action, params);
|
|
2041
|
+
if (result.success) {
|
|
2042
|
+
logSuccess("\u8C03\u7528\u6210\u529F");
|
|
2043
|
+
log(JSON.stringify(result.data, null, 2));
|
|
2044
|
+
} else {
|
|
2045
|
+
logError(`\u8C03\u7528\u5931\u8D25: ${result.error}`);
|
|
2046
|
+
process.exitCode = 1;
|
|
2047
|
+
}
|
|
2048
|
+
});
|
|
2049
|
+
|
|
2050
|
+
// src/index.ts
|
|
2051
|
+
var program = new Command11();
|
|
2052
|
+
program.name("broker").description("Agent Auth Broker CLI \u2014 AI Agent \u51ED\u8BC1\u7BA1\u7406\u4E0E\u6388\u6743\u4EE3\u7406").version("0.0.1");
|
|
2053
|
+
program.addCommand(initCommand);
|
|
2054
|
+
program.addCommand(validateCommand);
|
|
2055
|
+
program.addCommand(diagnoseCommand);
|
|
2056
|
+
program.addCommand(serveCommand);
|
|
2057
|
+
program.addCommand(credentialCommand);
|
|
2058
|
+
program.addCommand(agentCommand);
|
|
2059
|
+
program.addCommand(policyCommand);
|
|
2060
|
+
program.addCommand(uiCommand);
|
|
2061
|
+
program.addCommand(tokenCommand);
|
|
2062
|
+
program.addCommand(testCommand);
|
|
2063
|
+
program.parse();
|
|
2064
|
+
//# sourceMappingURL=index.js.map
|