@zhin.js/adapter-github 3.0.2 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/CHANGELOG.md +646 -0
  2. package/README.md +55 -203
  3. package/adapters/github.ts +51 -0
  4. package/agent/skills/github.md +2 -2
  5. package/agent/tools/bind.ts +2 -2
  6. package/agent/tools/create_pr.ts +2 -2
  7. package/agent/tools/install.ts +2 -2
  8. package/agent/tools/patch_file.ts +2 -2
  9. package/agent/tools/prepare_workspace.ts +2 -2
  10. package/agent/tools/push_branch.ts +2 -2
  11. package/agent/tools/star.ts +2 -2
  12. package/agent/tools/subscribe.ts +2 -2
  13. package/agent/tools/subscriptions.ts +2 -2
  14. package/agent/tools/unbind.ts +2 -2
  15. package/agent/tools/unsubscribe.ts +2 -2
  16. package/agent/tools/whoami.ts +2 -2
  17. package/lib/agent-prompt.d.ts +2 -0
  18. package/lib/{src/agent-prompt.js → agent-prompt.js} +0 -1
  19. package/lib/endpoint.d.ts +48 -0
  20. package/lib/endpoint.js +165 -0
  21. package/lib/gh-client.d.ts +300 -0
  22. package/lib/{src/gh-client.js → gh-client.js} +1 -2
  23. package/lib/github-agent-deps.d.ts +47 -0
  24. package/lib/github-agent-deps.js +43 -0
  25. package/lib/github-bot-handlers.d.ts +26 -0
  26. package/lib/{src/github-bot-handlers.js → github-bot-handlers.js} +0 -1
  27. package/lib/github-channel-context.d.ts +16 -0
  28. package/lib/{src/github-channel-context.js → github-channel-context.js} +0 -1
  29. package/lib/github-tool-handlers.d.ts +17 -0
  30. package/lib/{src/github-tool-handlers.js → github-tool-handlers.js} +14 -8
  31. package/lib/index.d.ts +9 -0
  32. package/lib/index.js +9 -0
  33. package/lib/oauth-users.d.ts +33 -0
  34. package/lib/oauth-users.js +38 -0
  35. package/lib/protocol.d.ts +87 -0
  36. package/lib/protocol.js +241 -0
  37. package/lib/types.d.ts +149 -0
  38. package/lib/{src/types.js → types.js} +0 -1
  39. package/lib/webhook.d.ts +13 -0
  40. package/lib/webhook.js +87 -0
  41. package/lib/workspace-manager.d.ts +21 -0
  42. package/lib/{src/workspace-manager.js → workspace-manager.js} +46 -1
  43. package/package.json +53 -26
  44. package/plugin.ts +52 -0
  45. package/schema.json +94 -0
  46. package/src/endpoint.ts +166 -175
  47. package/src/gh-client.ts +1 -1
  48. package/src/github-agent-deps.ts +66 -11
  49. package/src/github-tool-handlers.ts +20 -7
  50. package/src/index.ts +55 -137
  51. package/src/oauth-users.ts +47 -0
  52. package/src/protocol.ts +367 -0
  53. package/src/webhook.ts +125 -0
  54. package/src/workspace-manager.ts +54 -0
  55. package/lib/agent/tools/bind.js +0 -12
  56. package/lib/agent/tools/bind.js.map +0 -1
  57. package/lib/agent/tools/create_pr.js +0 -19
  58. package/lib/agent/tools/create_pr.js.map +0 -1
  59. package/lib/agent/tools/install.js +0 -12
  60. package/lib/agent/tools/install.js.map +0 -1
  61. package/lib/agent/tools/patch_file.js +0 -18
  62. package/lib/agent/tools/patch_file.js.map +0 -1
  63. package/lib/agent/tools/prepare_workspace.js +0 -14
  64. package/lib/agent/tools/prepare_workspace.js.map +0 -1
  65. package/lib/agent/tools/push_branch.js +0 -17
  66. package/lib/agent/tools/push_branch.js.map +0 -1
  67. package/lib/agent/tools/star.js +0 -15
  68. package/lib/agent/tools/star.js.map +0 -1
  69. package/lib/agent/tools/subscribe.js +0 -16
  70. package/lib/agent/tools/subscribe.js.map +0 -1
  71. package/lib/agent/tools/subscriptions.js +0 -13
  72. package/lib/agent/tools/subscriptions.js.map +0 -1
  73. package/lib/agent/tools/unbind.js +0 -12
  74. package/lib/agent/tools/unbind.js.map +0 -1
  75. package/lib/agent/tools/unsubscribe.js +0 -15
  76. package/lib/agent/tools/unsubscribe.js.map +0 -1
  77. package/lib/agent/tools/whoami.js +0 -12
  78. package/lib/agent/tools/whoami.js.map +0 -1
  79. package/lib/src/adapter.js +0 -423
  80. package/lib/src/adapter.js.map +0 -1
  81. package/lib/src/agent-prompt.js.map +0 -1
  82. package/lib/src/endpoint.js +0 -189
  83. package/lib/src/endpoint.js.map +0 -1
  84. package/lib/src/gh-client.js.map +0 -1
  85. package/lib/src/github-agent-deps.js +0 -13
  86. package/lib/src/github-agent-deps.js.map +0 -1
  87. package/lib/src/github-bot-handlers.js.map +0 -1
  88. package/lib/src/github-channel-context.js.map +0 -1
  89. package/lib/src/github-tool-handlers.js.map +0 -1
  90. package/lib/src/index.js +0 -104
  91. package/lib/src/index.js.map +0 -1
  92. package/lib/src/register-github-mcp.js +0 -41
  93. package/lib/src/register-github-mcp.js.map +0 -1
  94. package/lib/src/segment-mapper.js +0 -2
  95. package/lib/src/segment-mapper.js.map +0 -1
  96. package/lib/src/types.js.map +0 -1
  97. package/lib/src/workspace-manager.js.map +0 -1
  98. package/plugin.yml +0 -3
  99. package/src/adapter.ts +0 -454
  100. package/src/register-github-mcp.ts +0 -67
  101. package/src/segment-mapper.ts +0 -1
package/lib/webhook.js ADDED
@@ -0,0 +1,87 @@
1
+ import { formatCompact, getLogger } from '@zhin.js/logger';
2
+ import { headerValue, parseIssueCommentInbound, parsePrReviewCommentInbound, parsePrReviewInbound, readTextBody, verifyWebhookSignature, } from './protocol.js';
3
+ const logger = getLogger('github');
4
+ export function registerGithubWebhookRoutes(http, handler) {
5
+ const routePath = handler.config.webhookPath;
6
+ return [
7
+ http.route('POST', routePath, async (request, response) => {
8
+ await handleGithubWebhookRequest(request, response, handler);
9
+ }, { summary: 'GitHub webhook', tags: ['github'] }),
10
+ ];
11
+ }
12
+ export async function handleGithubWebhookRequest(request, response, handler) {
13
+ const secret = handler.config.webhookSecret;
14
+ if (!secret) {
15
+ response.writeHead(503, { 'Content-Type': 'application/json' });
16
+ response.end(JSON.stringify({ error: 'Webhook not configured' }));
17
+ return;
18
+ }
19
+ const signature = headerValue(request.headers, 'x-hub-signature-256');
20
+ const event = headerValue(request.headers, 'x-github-event');
21
+ const deliveryId = headerValue(request.headers, 'x-github-delivery');
22
+ if (!signature || !event) {
23
+ response.writeHead(400, { 'Content-Type': 'application/json' });
24
+ response.end(JSON.stringify({ error: 'Missing signature or event header' }));
25
+ return;
26
+ }
27
+ try {
28
+ const rawBody = await readTextBody(request);
29
+ if (!verifyWebhookSignature(secret, rawBody, signature)) {
30
+ logger.warn(formatCompact({
31
+ op: 'webhook',
32
+ ok: false,
33
+ error: 'invalid signature',
34
+ delivery: deliveryId,
35
+ }));
36
+ response.writeHead(401, { 'Content-Type': 'application/json' });
37
+ response.end(JSON.stringify({ error: 'Invalid signature' }));
38
+ return;
39
+ }
40
+ let payload;
41
+ try {
42
+ payload = rawBody ? JSON.parse(rawBody) : {};
43
+ }
44
+ catch {
45
+ response.writeHead(400, { 'Content-Type': 'application/json' });
46
+ response.end(JSON.stringify({ error: 'Invalid JSON' }));
47
+ return;
48
+ }
49
+ response.writeHead(200, { 'Content-Type': 'application/json' });
50
+ response.end(JSON.stringify({ ok: true }));
51
+ void dispatchGithubWebhookPayload(event, payload, handler).catch((e) => {
52
+ logger.error(`Webhook 事件处理失败 (${event}):`, e);
53
+ });
54
+ }
55
+ catch (error) {
56
+ logger.error('Webhook error:', error);
57
+ if (!response.headersSent) {
58
+ response.writeHead(500, { 'Content-Type': 'application/json' });
59
+ response.end(JSON.stringify({ error: 'Internal Server Error' }));
60
+ }
61
+ }
62
+ }
63
+ export async function dispatchGithubWebhookPayload(event, payload, handler) {
64
+ if (!payload || typeof payload !== 'object')
65
+ return;
66
+ const body = payload;
67
+ const repo = body.repository?.full_name;
68
+ logger.debug(`Webhook: ${event}${body.action ? `.${body.action}` : ''} ${repo || ''}`);
69
+ if (event === 'issue_comment') {
70
+ const inbound = parseIssueCommentInbound(payload);
71
+ if (inbound)
72
+ handler.admit(inbound);
73
+ return;
74
+ }
75
+ if (event === 'pull_request_review_comment') {
76
+ const inbound = parsePrReviewCommentInbound(payload);
77
+ if (inbound)
78
+ handler.admit(inbound);
79
+ return;
80
+ }
81
+ if (event === 'pull_request_review') {
82
+ const inbound = parsePrReviewInbound(payload);
83
+ if (inbound)
84
+ handler.admit(inbound);
85
+ }
86
+ // Cross-adapter subscription fan-out deferred (needs multi-adapter send in Runtime).
87
+ }
@@ -0,0 +1,21 @@
1
+ import type { GhClient, GitHubBotIdentity } from './gh-client.js';
2
+ /**
3
+ * 校验 GitHub "owner/name" 全名,防止路径穿越与 git 选项注入。
4
+ * 返回通过校验的原值,便于调用方直接使用「已消毒」的变量。
5
+ */
6
+ export declare function assertRepoFullName(repo: string): string;
7
+ /**
8
+ * 校验 git ref 名称,拒绝选项注入(`-` 前缀)与 git 非法字符。
9
+ */
10
+ export declare function assertGitRefName(ref: string): string;
11
+ export declare class WorkspaceManager {
12
+ private readonly gh;
13
+ private readonly rootDir;
14
+ constructor(gh: GhClient, rootDir: string);
15
+ getRepoPath(repo: string): string;
16
+ private runGit;
17
+ configureBotGit(repoPath: string, identity: GitHubBotIdentity): Promise<void>;
18
+ ensureRepo(repo: string): Promise<string>;
19
+ checkoutBranch(repo: string, branch: string, baseRef: string): Promise<string>;
20
+ commitAndPush(repo: string, branch: string, message: string): Promise<string>;
21
+ }
@@ -4,6 +4,45 @@
4
4
  import { spawn } from 'node:child_process';
5
5
  import fs from 'node:fs';
6
6
  import path from 'node:path';
7
+ const REPO_FULL_NAME_RE = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
8
+ const GIT_REF_ILLEGAL_CHARS = new Set('~^:?*[\\`;');
9
+ function containsIllegalGitRefCharacter(ref) {
10
+ return [...ref].some((character) => {
11
+ const code = character.charCodeAt(0);
12
+ return code <= 31
13
+ || code === 127
14
+ || /\s/u.test(character)
15
+ || GIT_REF_ILLEGAL_CHARS.has(character);
16
+ });
17
+ }
18
+ /**
19
+ * 校验 GitHub "owner/name" 全名,防止路径穿越与 git 选项注入。
20
+ * 返回通过校验的原值,便于调用方直接使用「已消毒」的变量。
21
+ */
22
+ export function assertRepoFullName(repo) {
23
+ if (typeof repo !== 'string' ||
24
+ !REPO_FULL_NAME_RE.test(repo) ||
25
+ repo.split('/').some((seg) => seg === '' || seg.startsWith('-') || /^\.+$/.test(seg))) {
26
+ throw new TypeError(`非法的仓库全名: ${JSON.stringify(repo)}`);
27
+ }
28
+ return repo;
29
+ }
30
+ /**
31
+ * 校验 git ref 名称,拒绝选项注入(`-` 前缀)与 git 非法字符。
32
+ */
33
+ export function assertGitRefName(ref) {
34
+ if (typeof ref !== 'string' || ref.length === 0) {
35
+ throw new TypeError(`非法的 git ref: ${JSON.stringify(ref)}`);
36
+ }
37
+ if (ref.startsWith('-') ||
38
+ ref.includes('..') ||
39
+ containsIllegalGitRefCharacter(ref) ||
40
+ ref.endsWith('/') ||
41
+ ref.endsWith('.')) {
42
+ throw new TypeError(`非法的 git ref: ${JSON.stringify(ref)}`);
43
+ }
44
+ return ref;
45
+ }
7
46
  export class WorkspaceManager {
8
47
  gh;
9
48
  rootDir;
@@ -12,6 +51,7 @@ export class WorkspaceManager {
12
51
  this.rootDir = rootDir;
13
52
  }
14
53
  getRepoPath(repo) {
54
+ repo = assertRepoFullName(repo);
15
55
  const [owner, name] = repo.split('/');
16
56
  return path.join(this.rootDir, owner, name);
17
57
  }
@@ -47,6 +87,7 @@ export class WorkspaceManager {
47
87
  ]).then(() => undefined);
48
88
  }
49
89
  async ensureRepo(repo) {
90
+ repo = assertRepoFullName(repo);
50
91
  if (!this.gh.isAppAuth) {
51
92
  throw new Error('GitHub App 认证未配置,无法托管工作区');
52
93
  }
@@ -70,6 +111,9 @@ export class WorkspaceManager {
70
111
  return repoPath;
71
112
  }
72
113
  async checkoutBranch(repo, branch, baseRef) {
114
+ repo = assertRepoFullName(repo);
115
+ branch = assertGitRefName(branch);
116
+ baseRef = assertGitRefName(baseRef);
73
117
  const repoPath = await this.ensureRepo(repo);
74
118
  const localBranches = await this.runGit(repoPath, ['branch', '--list', branch]);
75
119
  if (localBranches.trim()) {
@@ -89,6 +133,8 @@ export class WorkspaceManager {
89
133
  return repoPath;
90
134
  }
91
135
  async commitAndPush(repo, branch, message) {
136
+ repo = assertRepoFullName(repo);
137
+ branch = assertGitRefName(branch);
92
138
  const repoPath = this.getRepoPath(repo);
93
139
  if (!fs.existsSync(repoPath)) {
94
140
  throw new Error(`工作区不存在: ${repoPath},请先 github_prepare_workspace`);
@@ -106,4 +152,3 @@ export class WorkspaceManager {
106
152
  return `已 push 到 origin/${branch}`;
107
153
  }
108
154
  }
109
- //# sourceMappingURL=workspace-manager.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-github",
3
- "version": "3.0.2",
4
- "description": "Zhin.js adapter for GitHub (gh CLI) treat issues/PRs as chat channels, full repo management, webhook notifications",
3
+ "version": "4.0.0",
4
+ "description": "Zhin.js GitHub adapter for Plugin Runtime (App auth + webhook)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
7
7
  "types": "./lib/index.d.ts",
@@ -18,15 +18,6 @@
18
18
  },
19
19
  "./package.json": "./package.json"
20
20
  },
21
- "files": [
22
- "src",
23
- "lib",
24
- "agent",
25
- "client",
26
- "dist",
27
- "plugin.yml",
28
- "README.md"
29
- ],
30
21
  "keywords": [
31
22
  "zhin",
32
23
  "zhin.js",
@@ -44,21 +35,29 @@
44
35
  "url": "https://github.com/lc-cn"
45
36
  },
46
37
  "license": "MIT",
47
- "devDependencies": {
48
- "typescript": "^6.0.3",
49
- "zod": "^4.4.3",
50
- "@zhin.js/agent": "1.0.3",
51
- "@zhin.js/host-router": "2.0.3",
52
- "zhin.js": "4.1.2"
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/zhinjs/zhin.git",
41
+ "directory": "plugins/adapters/github"
42
+ },
43
+ "dependencies": {
44
+ "@zhin.js/adapter": "1.1.0",
45
+ "@zhin.js/core": "1.4.0",
46
+ "@zhin.js/host-http": "1.0.2",
47
+ "@zhin.js/logger": "1.0.75",
48
+ "@zhin.js/plugin-runtime": "1.1.0"
53
49
  },
54
50
  "peerDependencies": {
55
51
  "zod": "^4.0.0",
56
- "@zhin.js/host-router": "2.0.3",
57
- "zhin.js": "4.1.2",
58
- "@zhin.js/agent": "1.0.3"
52
+ "@zhin.js/adapter": "1.1.0",
53
+ "@zhin.js/agent": "1.0.5",
54
+ "@zhin.js/core": "1.4.0",
55
+ "@zhin.js/host-http": "1.0.2",
56
+ "@zhin.js/plugin-runtime": "1.1.0",
57
+ "zhin.js": "5.0.0"
59
58
  },
60
59
  "peerDependenciesMeta": {
61
- "@zhin.js/host-router": {
60
+ "zhin.js": {
62
61
  "optional": true
63
62
  },
64
63
  "@zhin.js/agent": {
@@ -68,11 +67,24 @@
68
67
  "optional": true
69
68
  }
70
69
  },
71
- "repository": {
72
- "type": "git",
73
- "url": "git+https://github.com/zhinjs/zhin.git",
74
- "directory": "plugins/adapters/github"
70
+ "devDependencies": {
71
+ "@types/node": "^26.1.0",
72
+ "typescript": "^6.0.3",
73
+ "vitest": "^4.1.10",
74
+ "zod": "^4.4.3",
75
+ "@zhin.js/agent": "1.0.5",
76
+ "zhin.js": "5.0.0"
75
77
  },
78
+ "files": [
79
+ "adapters",
80
+ "plugin.ts",
81
+ "schema.json",
82
+ "src",
83
+ "lib",
84
+ "agent",
85
+ "README.md",
86
+ "CHANGELOG.md"
87
+ ],
76
88
  "publishConfig": {
77
89
  "access": "public",
78
90
  "registry": "https://registry.npmjs.org"
@@ -80,8 +92,23 @@
80
92
  "engines": {
81
93
  "node": "^20.19.0 || >=22.12.0"
82
94
  },
95
+ "zhin": {
96
+ "protocol": 1,
97
+ "type": "plugin",
98
+ "entry": "./plugin.ts",
99
+ "engine": "^1.0.0",
100
+ "runtime": "trusted",
101
+ "features": [
102
+ {
103
+ "package": "@zhin.js/adapter",
104
+ "api": "^1.0.0"
105
+ }
106
+ ],
107
+ "plugins": []
108
+ },
83
109
  "scripts": {
84
110
  "build": "tsc",
85
- "clean": "rimraf lib"
111
+ "clean": "rimraf lib",
112
+ "test": "NODE_OPTIONS=--experimental-strip-types vitest run --root ../../.. plugins/adapters/github/tests"
86
113
  }
87
114
  }
package/plugin.ts ADDED
@@ -0,0 +1,52 @@
1
+ import { definePlugin, databaseHostToken } from '@zhin.js/plugin-runtime';
2
+ import { defineGithubOauthUsersTable } from './src/oauth-users.js';
3
+
4
+ /**
5
+ * github_subscriptions — repo event subscriptions per chat channel
6
+ * (used by github_subscriptions agent tool; schema matches legacy defineModel).
7
+ */
8
+ const GITHUB_SUBSCRIPTIONS_SCHEMA = {
9
+ id: { type: 'integer', primary: true },
10
+ repo: { type: 'text', nullable: false },
11
+ events: { type: 'json', default: [] },
12
+ target_id: { type: 'text', nullable: false },
13
+ target_type: { type: 'text', nullable: false },
14
+ adapter: { type: 'text', nullable: false },
15
+ endpoint: { type: 'text', nullable: false },
16
+ } as const;
17
+
18
+ /**
19
+ * Plugin Runtime GitHub adapter.
20
+ * - Endpoint: `adapters/github.ts`
21
+ * - OAuth user tokens: define `github_oauth_users` when DatabaseHost is present
22
+ */
23
+ export default definePlugin({
24
+ name: 'github',
25
+ metadata: {
26
+ displayName: 'GitHub Adapter',
27
+ },
28
+ setup(context) {
29
+ if (context.resources.has(databaseHostToken)) {
30
+ const host = context.resources.use(databaseHostToken);
31
+ defineGithubOauthUsersTable(host);
32
+ host.define('github_subscriptions', { ...GITHUB_SUBSCRIPTIONS_SCHEMA });
33
+ }
34
+
35
+ // Agent prompt contributor (orchestrator/deferred-worker GitHub guidance).
36
+ // `zhin.js/agent` is an optional peer — skip silently on IM-only installs.
37
+ let cancelled = false;
38
+ let unregister: (() => void) | undefined;
39
+ void Promise.all([
40
+ import('zhin.js/agent'),
41
+ import('./src/agent-prompt.js'),
42
+ ]).then(([agent, prompt]) => {
43
+ if (cancelled) return;
44
+ agent.registerAgentPromptContributor(prompt.createGithubAgentPromptContributor());
45
+ unregister = () => agent.unregisterAgentPromptContributor('github');
46
+ }).catch(() => { /* optional peer not installed */ });
47
+ return () => {
48
+ cancelled = true;
49
+ unregister?.();
50
+ };
51
+ },
52
+ });
package/schema.json ADDED
@@ -0,0 +1,94 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "type": "object",
4
+ "additionalProperties": false,
5
+ "properties": {
6
+ "host": {
7
+ "type": "string",
8
+ "description": "GitHub Enterprise hostname (default github.com)"
9
+ },
10
+ "webhook_path": {
11
+ "type": "string",
12
+ "default": "/github/webhook"
13
+ },
14
+ "webhookPath": {
15
+ "type": "string",
16
+ "default": "/github/webhook"
17
+ },
18
+ "poll_interval": {
19
+ "type": "number",
20
+ "default": 60,
21
+ "description": "Deferred: polling fallback was removed in the Plugin Runtime migration; currently parsed but unused"
22
+ },
23
+ "auto_reply_repos": {
24
+ "type": "array",
25
+ "items": {
26
+ "type": "string"
27
+ },
28
+ "description": "Repos whose Issue/PR comments auto-trigger AI without @bot"
29
+ },
30
+ "bot_login": {
31
+ "type": "string",
32
+ "description": "Override App bot login (default {slug}[bot])"
33
+ },
34
+ "workspace_root": {
35
+ "type": "string",
36
+ "description": "Managed git workspace root"
37
+ },
38
+ "endpoints": {
39
+ "type": "array",
40
+ "description": "多账号:一个插件实例挂多个 endpoint",
41
+ "items": {
42
+ "type": "object",
43
+ "additionalProperties": true,
44
+ "properties": {
45
+ "name": {
46
+ "type": "string",
47
+ "description": "GitHub App bot name"
48
+ },
49
+ "app_id": {
50
+ "type": [
51
+ "string",
52
+ "number"
53
+ ],
54
+ "description": "GitHub App ID"
55
+ },
56
+ "appId": {
57
+ "type": [
58
+ "string",
59
+ "number"
60
+ ],
61
+ "description": "GitHub App ID (camelCase alias)"
62
+ },
63
+ "private_key": {
64
+ "type": "string",
65
+ "description": "GitHub App private key (PEM content or file path)"
66
+ },
67
+ "privateKey": {
68
+ "type": "string",
69
+ "description": "GitHub App private key (camelCase alias)"
70
+ },
71
+ "webhook_secret": {
72
+ "type": "string",
73
+ "description": "Webhook HMAC secret; enables httpHostToken POST route"
74
+ },
75
+ "webhookSecret": {
76
+ "type": "string",
77
+ "description": "Webhook HMAC secret (camelCase alias)"
78
+ }
79
+ },
80
+ "required": [
81
+ "name"
82
+ ]
83
+ }
84
+ },
85
+ "commandPrefix": {
86
+ "type": "string",
87
+ "default": "",
88
+ "description": "命令前缀(默认 '' 无前缀,任意文本按命令匹配;如 '/' 要求 / 开头)。endpoints[i] 可逐项覆盖"
89
+ }
90
+ },
91
+ "required": [
92
+ "endpoints"
93
+ ]
94
+ }