metheus-governance-mcp-cli 0.2.52 → 0.2.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -12
- package/cli.mjs +291 -79
- package/package.json +1 -1
- package/postinstall.mjs +25 -19
package/README.md
CHANGED
|
@@ -20,23 +20,35 @@ Compatibility note: legacy command alias `metheus-governance-mcp` is still suppo
|
|
|
20
20
|
npm install -g metheus-governance-mcp-cli@latest
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
Install creates
|
|
23
|
+
Install creates local provider settings templates here:
|
|
24
24
|
|
|
25
25
|
- `~/.metheus/telegram.env`
|
|
26
|
+
- `~/.metheus/slack.env`
|
|
27
|
+
- `~/.metheus/kakaotalk.env`
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
These files are for local provider bot secrets only.
|
|
28
30
|
|
|
29
31
|
- Store locally:
|
|
30
32
|
- `TELEGRAM_BOT_TOKEN`
|
|
31
|
-
-
|
|
32
|
-
- `
|
|
33
|
+
- `SLACK_BOT_TOKEN`
|
|
34
|
+
- `KAKAOTALK_BOT_TOKEN`
|
|
35
|
+
- Server-side Metheus stores project chat destination metadata separately:
|
|
36
|
+
- provider
|
|
37
|
+
- `chat_id` / channel / room identifier
|
|
33
38
|
- label / active state
|
|
34
|
-
- Do not put project
|
|
39
|
+
- Do not put project chat destination identifiers in local env files.
|
|
35
40
|
|
|
36
|
-
Example
|
|
41
|
+
Example templates:
|
|
37
42
|
|
|
38
43
|
```env
|
|
44
|
+
# ~/.metheus/telegram.env
|
|
39
45
|
TELEGRAM_BOT_TOKEN=
|
|
46
|
+
|
|
47
|
+
# ~/.metheus/slack.env
|
|
48
|
+
SLACK_BOT_TOKEN=
|
|
49
|
+
|
|
50
|
+
# ~/.metheus/kakaotalk.env
|
|
51
|
+
KAKAOTALK_BOT_TOKEN=
|
|
40
52
|
```
|
|
41
53
|
|
|
42
54
|
## One command bootstrap (recommended)
|
|
@@ -69,11 +81,13 @@ Recommended for Codex/Claude/Gemini/Antigravity/Cursor multi-workspace sessions:
|
|
|
69
81
|
metheus-governance-mcp-cli setup --project-id <project_uuid> --ctxpack-key "<ctxpack_key>" --base-url https://metheus.gesiaplatform.com --workspace-dir auto
|
|
70
82
|
```
|
|
71
83
|
|
|
72
|
-
`setup` also ensures
|
|
84
|
+
`setup` also ensures local provider templates exist:
|
|
73
85
|
|
|
74
86
|
- `~/.metheus/telegram.env`
|
|
87
|
+
- `~/.metheus/slack.env`
|
|
88
|
+
- `~/.metheus/kakaotalk.env`
|
|
75
89
|
|
|
76
|
-
Fill only
|
|
90
|
+
Fill only provider bot tokens locally. Project chat destination identifiers should be managed on the Metheus server as project chat destinations, not as local env values and not inside legacy Chat Hooks/webhooks.
|
|
77
91
|
|
|
78
92
|
Gemini CLI note:
|
|
79
93
|
- `gemini mcp` commands require Gemini auth to be configured first (`GEMINI_API_KEY` or `~/.gemini/settings.json` auth).
|
|
@@ -105,7 +119,8 @@ metheus-governance-mcp-cli doctor --project-id <project_uuid> --base-url https:/
|
|
|
105
119
|
|
|
106
120
|
Checks:
|
|
107
121
|
- auth token status (+ auto refresh attempt)
|
|
108
|
-
- local
|
|
122
|
+
- local provider env template presence
|
|
123
|
+
- local provider token presence for active project destinations
|
|
109
124
|
- codex/claude/gemini/antigravity/cursor registration state
|
|
110
125
|
- gateway `tools/list` reachability
|
|
111
126
|
- `project.summary` access
|
|
@@ -113,10 +128,14 @@ Checks:
|
|
|
113
128
|
- smoke calls: `workitem.list`, `evidence.list`, `decision.list`
|
|
114
129
|
|
|
115
130
|
Direct bot posting:
|
|
116
|
-
- `me.send-bot-message` uses
|
|
131
|
+
- `me.send-bot-message` uses local provider tokens from `~/.metheus/<provider>.env`
|
|
117
132
|
- it does not use a server-stored bot token
|
|
118
|
-
- the destination
|
|
119
|
-
- if multiple active
|
|
133
|
+
- the destination identifier is resolved from the current project's saved Chat Destinations on the Metheus server
|
|
134
|
+
- if multiple active destinations exist for the same provider, pass `destination_id` or `destination_label`
|
|
135
|
+
- direct local delivery is implemented today for:
|
|
136
|
+
- Telegram
|
|
137
|
+
- Slack
|
|
138
|
+
- KakaoTalk profiles and destinations can be stored now, but direct local delivery is not implemented yet
|
|
120
139
|
|
|
121
140
|
## Use in MCP
|
|
122
141
|
|
package/cli.mjs
CHANGED
|
@@ -15,7 +15,27 @@ const DEFAULT_SITE_URL = "https://metheus.gesiaplatform.com";
|
|
|
15
15
|
const DEFAULT_BASE_URL = `${DEFAULT_SITE_URL}/governance/mcp`;
|
|
16
16
|
const DEFAULT_SERVER_NAME = "metheus-governance-mcp";
|
|
17
17
|
const AUTH_STORE_RELATIVE_PATH = path.join(".metheus", "governance-mcp-auth.json");
|
|
18
|
-
const
|
|
18
|
+
const PROVIDER_ENV_CONFIG = {
|
|
19
|
+
telegram: {
|
|
20
|
+
relativePath: path.join(".metheus", "telegram.env"),
|
|
21
|
+
tokenKey: "TELEGRAM_BOT_TOKEN",
|
|
22
|
+
label: "Telegram",
|
|
23
|
+
verifyURL: "https://api.telegram.org",
|
|
24
|
+
},
|
|
25
|
+
slack: {
|
|
26
|
+
relativePath: path.join(".metheus", "slack.env"),
|
|
27
|
+
tokenKey: "SLACK_BOT_TOKEN",
|
|
28
|
+
label: "Slack",
|
|
29
|
+
verifyURL: "https://slack.com/api/auth.test",
|
|
30
|
+
},
|
|
31
|
+
kakaotalk: {
|
|
32
|
+
relativePath: path.join(".metheus", "kakaotalk.env"),
|
|
33
|
+
tokenKey: "KAKAOTALK_BOT_TOKEN",
|
|
34
|
+
label: "KakaoTalk",
|
|
35
|
+
verifyURL: "",
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
const PROVIDER_ENV_ORDER = Object.keys(PROVIDER_ENV_CONFIG);
|
|
19
39
|
const SELF_UPDATE_STATE_RELATIVE_PATH = path.join(".metheus", "governance-mcp-cli-update.json");
|
|
20
40
|
const CTXPACK_CACHE_RELATIVE_DIR = path.join(".metheus", "ctxpack-cache");
|
|
21
41
|
const CTXPACK_META_FILENAME = ".metheus_ctxpack_sync.json";
|
|
@@ -58,8 +78,8 @@ function printUsage() {
|
|
|
58
78
|
` ${ALLOW_HOME_WORKSPACE_ENV_KEY}=1 to allow using home directory as workspace root (disabled by default).`,
|
|
59
79
|
" If env is missing, stored token file is used:",
|
|
60
80
|
` ${AUTH_STORE_RELATIVE_PATH}`,
|
|
61
|
-
" Local
|
|
62
|
-
` ${
|
|
81
|
+
" Local provider bot token templates are stored under:",
|
|
82
|
+
...PROVIDER_ENV_ORDER.map((provider) => ` ${PROVIDER_ENV_CONFIG[provider].relativePath}`),
|
|
63
83
|
"",
|
|
64
84
|
].join("\n"),
|
|
65
85
|
);
|
|
@@ -479,36 +499,57 @@ function authStoreFilePath() {
|
|
|
479
499
|
return resolveHomeFilePath(AUTH_STORE_RELATIVE_PATH);
|
|
480
500
|
}
|
|
481
501
|
|
|
482
|
-
function
|
|
483
|
-
|
|
502
|
+
function normalizeBotProvider(rawValue) {
|
|
503
|
+
const value = String(rawValue || "").trim().toLowerCase();
|
|
504
|
+
if (Object.prototype.hasOwnProperty.call(PROVIDER_ENV_CONFIG, value)) {
|
|
505
|
+
return value;
|
|
506
|
+
}
|
|
507
|
+
return "telegram";
|
|
484
508
|
}
|
|
485
509
|
|
|
486
|
-
function
|
|
510
|
+
function providerEnvConfig(provider) {
|
|
511
|
+
return PROVIDER_ENV_CONFIG[normalizeBotProvider(provider)];
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function providerEnvFilePath(provider) {
|
|
515
|
+
return resolveHomeFilePath(providerEnvConfig(provider).relativePath);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function providerEnvTemplate(provider) {
|
|
519
|
+
const config = providerEnvConfig(provider);
|
|
487
520
|
return [
|
|
488
|
-
|
|
521
|
+
`# Metheus local ${config.label} bot settings`,
|
|
489
522
|
"# Keep this file on your machine only. Do not commit it.",
|
|
490
|
-
|
|
491
|
-
"# Project
|
|
523
|
+
`# Store only the ${config.label} bot token locally.`,
|
|
524
|
+
"# Project chat destinations must be managed on the Metheus server.",
|
|
492
525
|
"",
|
|
493
|
-
|
|
526
|
+
`${config.tokenKey}=`,
|
|
494
527
|
"",
|
|
495
528
|
].join("\n");
|
|
496
529
|
}
|
|
497
530
|
|
|
498
|
-
function
|
|
499
|
-
const filePath =
|
|
531
|
+
function ensureProviderEnvTemplate(provider) {
|
|
532
|
+
const filePath = providerEnvFilePath(provider);
|
|
500
533
|
try {
|
|
501
534
|
if (fs.existsSync(filePath)) {
|
|
502
535
|
return { filePath, created: false, existed: true };
|
|
503
536
|
}
|
|
504
537
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
505
|
-
fs.writeFileSync(filePath,
|
|
538
|
+
fs.writeFileSync(filePath, providerEnvTemplate(provider), "utf8");
|
|
506
539
|
return { filePath, created: true, existed: false };
|
|
507
540
|
} catch (err) {
|
|
508
541
|
return { filePath, created: false, existed: false, error: String(err?.message || err) };
|
|
509
542
|
}
|
|
510
543
|
}
|
|
511
544
|
|
|
545
|
+
function ensureAllProviderEnvTemplates() {
|
|
546
|
+
const out = {};
|
|
547
|
+
for (const provider of PROVIDER_ENV_ORDER) {
|
|
548
|
+
out[provider] = ensureProviderEnvTemplate(provider);
|
|
549
|
+
}
|
|
550
|
+
return out;
|
|
551
|
+
}
|
|
552
|
+
|
|
512
553
|
function parseSimpleEnvText(rawText) {
|
|
513
554
|
const out = {};
|
|
514
555
|
const lines = String(rawText || "").split(/\r?\n/);
|
|
@@ -531,12 +572,16 @@ function parseSimpleEnvText(rawText) {
|
|
|
531
572
|
return out;
|
|
532
573
|
}
|
|
533
574
|
|
|
534
|
-
function
|
|
535
|
-
const
|
|
575
|
+
function loadProviderEnvConfig(provider) {
|
|
576
|
+
const normalizedProvider = normalizeBotProvider(provider);
|
|
577
|
+
const config = providerEnvConfig(normalizedProvider);
|
|
578
|
+
const ensured = ensureProviderEnvTemplate(normalizedProvider);
|
|
536
579
|
const filePath = ensured.filePath;
|
|
537
580
|
if (ensured.error) {
|
|
538
581
|
return {
|
|
539
582
|
ok: false,
|
|
583
|
+
provider: normalizedProvider,
|
|
584
|
+
providerLabel: config.label,
|
|
540
585
|
filePath,
|
|
541
586
|
error: ensured.error,
|
|
542
587
|
token: "",
|
|
@@ -545,23 +590,29 @@ function loadTelegramEnvConfig() {
|
|
|
545
590
|
try {
|
|
546
591
|
const raw = fs.readFileSync(filePath, "utf8");
|
|
547
592
|
const parsed = parseSimpleEnvText(raw);
|
|
548
|
-
const token = String(parsed.
|
|
593
|
+
const token = String(parsed[config.tokenKey] || "").trim();
|
|
549
594
|
if (!token) {
|
|
550
595
|
return {
|
|
551
596
|
ok: false,
|
|
597
|
+
provider: normalizedProvider,
|
|
598
|
+
providerLabel: config.label,
|
|
552
599
|
filePath,
|
|
553
|
-
error:
|
|
600
|
+
error: `${config.tokenKey} is missing in ${filePath}`,
|
|
554
601
|
token: "",
|
|
555
602
|
};
|
|
556
603
|
}
|
|
557
604
|
return {
|
|
558
605
|
ok: true,
|
|
606
|
+
provider: normalizedProvider,
|
|
607
|
+
providerLabel: config.label,
|
|
559
608
|
filePath,
|
|
560
609
|
token,
|
|
561
610
|
};
|
|
562
611
|
} catch (err) {
|
|
563
612
|
return {
|
|
564
613
|
ok: false,
|
|
614
|
+
provider: normalizedProvider,
|
|
615
|
+
providerLabel: config.label,
|
|
565
616
|
filePath,
|
|
566
617
|
error: String(err?.message || err),
|
|
567
618
|
token: "",
|
|
@@ -569,6 +620,132 @@ function loadTelegramEnvConfig() {
|
|
|
569
620
|
}
|
|
570
621
|
}
|
|
571
622
|
|
|
623
|
+
function telegramEnvFilePath() {
|
|
624
|
+
return providerEnvFilePath("telegram");
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function telegramEnvTemplate() {
|
|
628
|
+
return providerEnvTemplate("telegram");
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function ensureTelegramEnvTemplate() {
|
|
632
|
+
return ensureProviderEnvTemplate("telegram");
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function loadTelegramEnvConfig() {
|
|
636
|
+
return loadProviderEnvConfig("telegram");
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
async function verifyLocalProviderToken(provider, envConfig, timeoutSeconds) {
|
|
640
|
+
const normalizedProvider = normalizeBotProvider(provider);
|
|
641
|
+
if (!envConfig?.ok) {
|
|
642
|
+
return {
|
|
643
|
+
ok: false,
|
|
644
|
+
detail: envConfig?.error || "local provider env is not ready",
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
if (normalizedProvider === "telegram") {
|
|
648
|
+
const me = safeObject(
|
|
649
|
+
await getJSONWithoutAuth(`https://api.telegram.org/bot${envConfig.token}/getMe`, timeoutSeconds),
|
|
650
|
+
);
|
|
651
|
+
if (Boolean(me.ok) && safeObject(me.result).id) {
|
|
652
|
+
return {
|
|
653
|
+
ok: true,
|
|
654
|
+
detail: `reachable as @${String(safeObject(me.result).username || "").trim() || "-"}`,
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
return {
|
|
658
|
+
ok: false,
|
|
659
|
+
detail: "getMe returned an unexpected payload",
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
if (normalizedProvider === "slack") {
|
|
663
|
+
const response = await postJSONWithoutAuth(
|
|
664
|
+
"https://slack.com/api/auth.test",
|
|
665
|
+
timeoutSeconds,
|
|
666
|
+
{},
|
|
667
|
+
{
|
|
668
|
+
authorization: `Bearer ${envConfig.token}`,
|
|
669
|
+
},
|
|
670
|
+
);
|
|
671
|
+
const responseJSON = parseJSONText(response.bodyText);
|
|
672
|
+
if (response.statusCode >= 200 && response.statusCode < 300 && responseJSON?.ok) {
|
|
673
|
+
const team = String(responseJSON.team || "").trim() || "-";
|
|
674
|
+
const user = String(responseJSON.user || "").trim() || "-";
|
|
675
|
+
return {
|
|
676
|
+
ok: true,
|
|
677
|
+
detail: `reachable as ${user} on ${team}`,
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
return {
|
|
681
|
+
ok: false,
|
|
682
|
+
detail: String(responseJSON?.error || response.bodyText || "auth.test failed").trim(),
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
return {
|
|
686
|
+
ok: false,
|
|
687
|
+
detail: `${providerEnvConfig(normalizedProvider).label} local verification is not implemented yet`,
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
async function deliverLocalProviderMessage({
|
|
692
|
+
provider,
|
|
693
|
+
token,
|
|
694
|
+
destination,
|
|
695
|
+
text,
|
|
696
|
+
disableWebPagePreview,
|
|
697
|
+
replyToMessageID,
|
|
698
|
+
timeoutSeconds,
|
|
699
|
+
}) {
|
|
700
|
+
const normalizedProvider = normalizeBotProvider(provider);
|
|
701
|
+
if (normalizedProvider === "telegram") {
|
|
702
|
+
const requestURL = `https://api.telegram.org/bot${token}/sendMessage`;
|
|
703
|
+
const payload = {
|
|
704
|
+
chat_id: destination.chatID,
|
|
705
|
+
text,
|
|
706
|
+
disable_web_page_preview: disableWebPagePreview,
|
|
707
|
+
};
|
|
708
|
+
if (replyToMessageID > 0) {
|
|
709
|
+
payload.reply_to_message_id = replyToMessageID;
|
|
710
|
+
}
|
|
711
|
+
const response = await postJSONWithoutAuth(requestURL, timeoutSeconds, payload);
|
|
712
|
+
const responseJSON = parseJSONText(response.bodyText);
|
|
713
|
+
return {
|
|
714
|
+
statusCode: response.statusCode,
|
|
715
|
+
body: responseJSON || response.bodyText,
|
|
716
|
+
ok: response.statusCode >= 200 && response.statusCode < 300 && Boolean(responseJSON?.ok ?? true),
|
|
717
|
+
url: sanitizeTelegramAPIURL(requestURL),
|
|
718
|
+
replySupported: true,
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
if (normalizedProvider === "slack") {
|
|
722
|
+
const requestURL = "https://slack.com/api/chat.postMessage";
|
|
723
|
+
const payload = {
|
|
724
|
+
channel: destination.chatID,
|
|
725
|
+
text,
|
|
726
|
+
unfurl_links: disableWebPagePreview === false,
|
|
727
|
+
unfurl_media: disableWebPagePreview === false,
|
|
728
|
+
};
|
|
729
|
+
const response = await postJSONWithoutAuth(
|
|
730
|
+
requestURL,
|
|
731
|
+
timeoutSeconds,
|
|
732
|
+
payload,
|
|
733
|
+
{
|
|
734
|
+
authorization: `Bearer ${token}`,
|
|
735
|
+
},
|
|
736
|
+
);
|
|
737
|
+
const responseJSON = parseJSONText(response.bodyText);
|
|
738
|
+
return {
|
|
739
|
+
statusCode: response.statusCode,
|
|
740
|
+
body: responseJSON || response.bodyText,
|
|
741
|
+
ok: response.statusCode >= 200 && response.statusCode < 300 && Boolean(responseJSON?.ok ?? false),
|
|
742
|
+
url: requestURL,
|
|
743
|
+
replySupported: false,
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
throw new Error(`${providerEnvConfig(normalizedProvider).label} local delivery is not implemented yet`);
|
|
747
|
+
}
|
|
748
|
+
|
|
572
749
|
function resolveWorkspaceDir(rawPath) {
|
|
573
750
|
const input = String(rawPath || "").trim();
|
|
574
751
|
if (input) {
|
|
@@ -2199,28 +2376,14 @@ async function runDoctor(flags) {
|
|
|
2199
2376
|
includeDrafts: true,
|
|
2200
2377
|
});
|
|
2201
2378
|
const rows = [];
|
|
2202
|
-
|
|
2203
|
-
const
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
await getJSONWithoutAuth(`https://api.telegram.org/bot${telegramEnv.token}/getMe`, timeoutSeconds),
|
|
2211
|
-
);
|
|
2212
|
-
if (Boolean(me.ok) && safeObject(me.result).id) {
|
|
2213
|
-
addDoctorCheck(
|
|
2214
|
-
rows,
|
|
2215
|
-
"ok",
|
|
2216
|
-
"local telegram bot",
|
|
2217
|
-
`reachable as @${String(safeObject(me.result).username || "").trim() || "-"}`,
|
|
2218
|
-
);
|
|
2219
|
-
} else {
|
|
2220
|
-
addDoctorCheck(rows, "warn", "local telegram bot", "getMe returned an unexpected payload");
|
|
2221
|
-
}
|
|
2222
|
-
} catch (err) {
|
|
2223
|
-
addDoctorCheck(rows, "warn", "local telegram bot", String(err?.message || err));
|
|
2379
|
+
const providerTemplates = ensureAllProviderEnvTemplates();
|
|
2380
|
+
for (const provider of PROVIDER_ENV_ORDER) {
|
|
2381
|
+
const ensured = providerTemplates[provider];
|
|
2382
|
+
const label = providerEnvConfig(provider).label;
|
|
2383
|
+
if (ensured.error) {
|
|
2384
|
+
addDoctorCheck(rows, "warn", `local ${provider} env`, `${ensured.error} (${ensured.filePath})`);
|
|
2385
|
+
} else {
|
|
2386
|
+
addDoctorCheck(rows, "ok", `local ${provider} env`, `template ready (${ensured.filePath})`);
|
|
2224
2387
|
}
|
|
2225
2388
|
}
|
|
2226
2389
|
|
|
@@ -2240,6 +2403,42 @@ async function runDoctor(flags) {
|
|
|
2240
2403
|
"auth token",
|
|
2241
2404
|
`configured (${resolved.source || "unknown"}${tokenExpiryIso(token) ? `, expires ${tokenExpiryIso(token)}` : ""})`,
|
|
2242
2405
|
);
|
|
2406
|
+
|
|
2407
|
+
if (context.projectID && isUUID(context.projectID)) {
|
|
2408
|
+
try {
|
|
2409
|
+
const destinationURL = `${normalizeSiteBaseURL(context.baseURL)}/api/v1/projects/${encodeURIComponent(context.projectID)}/chat-destinations`;
|
|
2410
|
+
const destinations = ensureArray(await getJSONWithAuth(destinationURL, timeoutSeconds, token))
|
|
2411
|
+
.map(normalizeChatDestination)
|
|
2412
|
+
.filter((item) => item.isActive && item.chatID);
|
|
2413
|
+
const activeProviders = Array.from(new Set(destinations.map((item) => normalizeBotProvider(item.provider))));
|
|
2414
|
+
if (activeProviders.length === 0) {
|
|
2415
|
+
addDoctorCheck(rows, "warn", "project chat destinations", "no active project chat destinations configured");
|
|
2416
|
+
} else {
|
|
2417
|
+
addDoctorCheck(rows, "ok", "project chat destinations", activeProviders.join(", "));
|
|
2418
|
+
}
|
|
2419
|
+
for (const provider of activeProviders) {
|
|
2420
|
+
const envConfig = loadProviderEnvConfig(provider);
|
|
2421
|
+
if (!envConfig.ok) {
|
|
2422
|
+
addDoctorCheck(rows, "warn", `local ${provider} token`, `${envConfig.error} (${envConfig.filePath})`);
|
|
2423
|
+
continue;
|
|
2424
|
+
}
|
|
2425
|
+
addDoctorCheck(rows, "ok", `local ${provider} token`, `configured (${envConfig.filePath})`);
|
|
2426
|
+
try {
|
|
2427
|
+
const verification = await verifyLocalProviderToken(provider, envConfig, timeoutSeconds);
|
|
2428
|
+
addDoctorCheck(
|
|
2429
|
+
rows,
|
|
2430
|
+
verification.ok ? "ok" : "warn",
|
|
2431
|
+
`local ${provider} bot`,
|
|
2432
|
+
verification.detail,
|
|
2433
|
+
);
|
|
2434
|
+
} catch (err) {
|
|
2435
|
+
addDoctorCheck(rows, "warn", `local ${provider} bot`, String(err?.message || err));
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
} catch (err) {
|
|
2439
|
+
addDoctorCheck(rows, "warn", "project chat destinations", String(err?.message || err));
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2243
2442
|
}
|
|
2244
2443
|
|
|
2245
2444
|
for (const cliBin of MCP_CLIENTS) {
|
|
@@ -4086,24 +4285,26 @@ function normalizeChatDestination(record) {
|
|
|
4086
4285
|
};
|
|
4087
4286
|
}
|
|
4088
4287
|
|
|
4089
|
-
function selectProjectChatDestination(destinations, selectors = {}) {
|
|
4288
|
+
function selectProjectChatDestination(destinations, selectors = {}, provider = "telegram") {
|
|
4289
|
+
const normalizedProvider = normalizeBotProvider(provider);
|
|
4290
|
+
const providerLabel = providerEnvConfig(normalizedProvider).label;
|
|
4090
4291
|
const list = ensureArray(destinations)
|
|
4091
4292
|
.map(normalizeChatDestination)
|
|
4092
|
-
.filter((item) => item.provider ===
|
|
4293
|
+
.filter((item) => item.provider === normalizedProvider && item.chatID && item.isActive);
|
|
4093
4294
|
const destinationID = String(selectors.destinationID || "").trim();
|
|
4094
4295
|
const destinationLabel = String(selectors.destinationLabel || "").trim().toLowerCase();
|
|
4095
4296
|
|
|
4096
4297
|
if (destinationID) {
|
|
4097
4298
|
const match = list.find((item) => item.id === destinationID);
|
|
4098
4299
|
if (!match) {
|
|
4099
|
-
throw new Error(
|
|
4300
|
+
throw new Error(`${providerLabel} project chat destination ${destinationID} was not found or is inactive`);
|
|
4100
4301
|
}
|
|
4101
4302
|
return match;
|
|
4102
4303
|
}
|
|
4103
4304
|
if (destinationLabel) {
|
|
4104
4305
|
const match = list.find((item) => item.label.toLowerCase() === destinationLabel);
|
|
4105
4306
|
if (!match) {
|
|
4106
|
-
throw new Error(
|
|
4307
|
+
throw new Error(`${providerLabel} project chat destination label "${selectors.destinationLabel}" was not found or is inactive`);
|
|
4107
4308
|
}
|
|
4108
4309
|
return match;
|
|
4109
4310
|
}
|
|
@@ -4111,11 +4312,11 @@ function selectProjectChatDestination(destinations, selectors = {}) {
|
|
|
4111
4312
|
return list[0];
|
|
4112
4313
|
}
|
|
4113
4314
|
if (list.length === 0) {
|
|
4114
|
-
throw new Error(
|
|
4315
|
+
throw new Error(`no active ${providerLabel} chat destination is configured for this project`);
|
|
4115
4316
|
}
|
|
4116
4317
|
const labels = list.map((item) => item.label || item.id).filter(Boolean);
|
|
4117
4318
|
throw new Error(
|
|
4118
|
-
`multiple active
|
|
4319
|
+
`multiple active ${providerLabel} chat destinations exist for this project; pass destination_id or destination_label (${labels.join(", ")})`,
|
|
4119
4320
|
);
|
|
4120
4321
|
}
|
|
4121
4322
|
|
|
@@ -4148,11 +4349,6 @@ async function handleLocalBotMessageToolCall({
|
|
|
4148
4349
|
throw new Error("project_id is required for local bot delivery; set --project-id during setup or include project_id");
|
|
4149
4350
|
}
|
|
4150
4351
|
|
|
4151
|
-
const telegramEnv = loadTelegramEnvConfig();
|
|
4152
|
-
if (!telegramEnv.ok) {
|
|
4153
|
-
throw new Error(`local telegram env is not ready (${telegramEnv.error})`);
|
|
4154
|
-
}
|
|
4155
|
-
|
|
4156
4352
|
const siteBaseURL = normalizeSiteBaseURL(args.baseURL);
|
|
4157
4353
|
const encodedBotID = encodeURIComponent(botID);
|
|
4158
4354
|
const encodedProjectID = encodeURIComponent(projectID);
|
|
@@ -4166,12 +4362,13 @@ async function handleLocalBotMessageToolCall({
|
|
|
4166
4362
|
if (bot.is_active === false || bot.isActive === false) {
|
|
4167
4363
|
throw new Error("bot is inactive");
|
|
4168
4364
|
}
|
|
4365
|
+
const provider = normalizeBotProvider(bot.provider);
|
|
4169
4366
|
|
|
4170
4367
|
const destinations = ensureArray(await getJSONWithAuth(destinationURL, args.timeoutSeconds, token));
|
|
4171
4368
|
const destination = selectProjectChatDestination(destinations, {
|
|
4172
4369
|
destinationID,
|
|
4173
4370
|
destinationLabel,
|
|
4174
|
-
});
|
|
4371
|
+
}, provider);
|
|
4175
4372
|
|
|
4176
4373
|
const disableWebPagePreview = boolFromRaw(
|
|
4177
4374
|
Object.prototype.hasOwnProperty.call(toolArgs, "disable_web_page_preview")
|
|
@@ -4188,28 +4385,31 @@ async function handleLocalBotMessageToolCall({
|
|
|
4188
4385
|
throw new Error("reply_to_message_id must be a positive integer");
|
|
4189
4386
|
}
|
|
4190
4387
|
|
|
4191
|
-
const
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
text,
|
|
4195
|
-
disable_web_page_preview: disableWebPagePreview,
|
|
4196
|
-
};
|
|
4197
|
-
if (replyToMessageID > 0) {
|
|
4198
|
-
telegramPayload.reply_to_message_id = replyToMessageID;
|
|
4388
|
+
const providerEnv = loadProviderEnvConfig(provider);
|
|
4389
|
+
if (!providerEnv.ok) {
|
|
4390
|
+
throw new Error(`local ${provider} env is not ready (${providerEnv.error})`);
|
|
4199
4391
|
}
|
|
4200
4392
|
|
|
4201
|
-
const
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4393
|
+
const delivery = await deliverLocalProviderMessage({
|
|
4394
|
+
provider,
|
|
4395
|
+
token: providerEnv.token,
|
|
4396
|
+
destination,
|
|
4397
|
+
text,
|
|
4398
|
+
disableWebPagePreview,
|
|
4399
|
+
replyToMessageID,
|
|
4400
|
+
timeoutSeconds: args.timeoutSeconds,
|
|
4401
|
+
});
|
|
4402
|
+
if (!delivery.ok) {
|
|
4403
|
+
const responseJSON = safeObject(delivery.body);
|
|
4205
4404
|
const errorDetail =
|
|
4206
|
-
String(responseJSON
|
|
4207
|
-
|
|
4405
|
+
String(responseJSON.description || responseJSON.error || JSON.stringify(delivery.body || "")).trim()
|
|
4406
|
+
|| `${provider} api status ${delivery.statusCode}`;
|
|
4407
|
+
if (delivery.statusCode === 401 || /unauthorized|invalid_auth/i.test(errorDetail)) {
|
|
4208
4408
|
throw new Error(
|
|
4209
|
-
`local
|
|
4409
|
+
`local ${provider} delivery failed (Unauthorized). Check ${providerEnvConfig(provider).tokenKey} in ${providerEnv.filePath}`,
|
|
4210
4410
|
);
|
|
4211
4411
|
}
|
|
4212
|
-
throw new Error(`local
|
|
4412
|
+
throw new Error(`local ${provider} delivery failed (${errorDetail})`);
|
|
4213
4413
|
}
|
|
4214
4414
|
|
|
4215
4415
|
return jsonRpcResult(
|
|
@@ -4217,7 +4417,7 @@ async function handleLocalBotMessageToolCall({
|
|
|
4217
4417
|
buildLocalToolRPCResult("me.send-bot-message", {
|
|
4218
4418
|
ok: true,
|
|
4219
4419
|
local_delivery: true,
|
|
4220
|
-
provider
|
|
4420
|
+
provider,
|
|
4221
4421
|
bot_id: String(bot.id || botID).trim(),
|
|
4222
4422
|
bot_name: String(bot.name || "").trim(),
|
|
4223
4423
|
bot_role: String(bot.bot_role || bot.botRole || "").trim(),
|
|
@@ -4225,9 +4425,10 @@ async function handleLocalBotMessageToolCall({
|
|
|
4225
4425
|
destination_id: destination.id,
|
|
4226
4426
|
destination_label: destination.label,
|
|
4227
4427
|
chat_id: destination.chatID,
|
|
4228
|
-
status:
|
|
4229
|
-
url:
|
|
4230
|
-
body:
|
|
4428
|
+
status: delivery.statusCode,
|
|
4429
|
+
url: delivery.url,
|
|
4430
|
+
body: delivery.body,
|
|
4431
|
+
reply_supported: delivery.replySupported,
|
|
4231
4432
|
}),
|
|
4232
4433
|
);
|
|
4233
4434
|
}
|
|
@@ -5704,7 +5905,7 @@ function runSetupInternal(flags, options = {}) {
|
|
|
5704
5905
|
const context = resolveSetupContext(flags);
|
|
5705
5906
|
const clients = [...MCP_CLIENTS];
|
|
5706
5907
|
const results = [];
|
|
5707
|
-
const
|
|
5908
|
+
const providerTemplates = ensureAllProviderEnvTemplates();
|
|
5708
5909
|
|
|
5709
5910
|
for (const cliBin of clients) {
|
|
5710
5911
|
if (!commandExists(cliBin)) continue;
|
|
@@ -5754,12 +5955,16 @@ function runSetupInternal(flags, options = {}) {
|
|
|
5754
5955
|
process.stdout.write(`Fallback: ${context.workspaceFallbackDir} (METHEUS_WORKSPACE_DIR)\n`);
|
|
5755
5956
|
}
|
|
5756
5957
|
process.stdout.write(`Project: ${context.projectID || "auto-detect from .metheus_ctxpack_sync.json"}\n`);
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5958
|
+
for (const provider of PROVIDER_ENV_ORDER) {
|
|
5959
|
+
const ensured = providerTemplates[provider];
|
|
5960
|
+
const label = providerEnvConfig(provider).label;
|
|
5961
|
+
if (ensured.error) {
|
|
5962
|
+
process.stdout.write(`${label}: template unavailable (${ensured.error})\n`);
|
|
5963
|
+
} else {
|
|
5964
|
+
process.stdout.write(
|
|
5965
|
+
`${label}: ${ensured.created ? "template created" : "template ready"} (${ensured.filePath})\n`,
|
|
5966
|
+
);
|
|
5967
|
+
}
|
|
5763
5968
|
}
|
|
5764
5969
|
if (context.ctxpackKey) {
|
|
5765
5970
|
process.stdout.write(`Ctxpack: ${context.ctxpackKey}\n`);
|
|
@@ -5782,12 +5987,17 @@ function runSelftest(flags = {}) {
|
|
|
5782
5987
|
const checks = [];
|
|
5783
5988
|
const push = (name, ok, detail = "") => checks.push({ name, ok: Boolean(ok), detail: String(detail || "") });
|
|
5784
5989
|
|
|
5785
|
-
const parsedEnv = parseSimpleEnvText("\n# comment\nTELEGRAM_BOT_TOKEN=\"abc:123\"\nEMPTY=\n");
|
|
5990
|
+
const parsedEnv = parseSimpleEnvText("\n# comment\nTELEGRAM_BOT_TOKEN=\"abc:123\"\nSLACK_BOT_TOKEN=xoxb-test\nEMPTY=\n");
|
|
5786
5991
|
push(
|
|
5787
5992
|
"telegram_env_parse_token",
|
|
5788
5993
|
String(parsedEnv.TELEGRAM_BOT_TOKEN || "") === "abc:123",
|
|
5789
5994
|
`token=${String(parsedEnv.TELEGRAM_BOT_TOKEN || "(missing)")}`,
|
|
5790
5995
|
);
|
|
5996
|
+
push(
|
|
5997
|
+
"slack_env_parse_token",
|
|
5998
|
+
String(parsedEnv.SLACK_BOT_TOKEN || "") === "xoxb-test",
|
|
5999
|
+
`token=${String(parsedEnv.SLACK_BOT_TOKEN || "(missing)")}`,
|
|
6000
|
+
);
|
|
5791
6001
|
|
|
5792
6002
|
try {
|
|
5793
6003
|
const selected = selectProjectChatDestination(
|
|
@@ -5796,6 +6006,7 @@ function runSelftest(flags = {}) {
|
|
|
5796
6006
|
{ id: "dest-2", provider: "slack", label: "Slack", chat_id: "C123", is_active: true },
|
|
5797
6007
|
],
|
|
5798
6008
|
{},
|
|
6009
|
+
"telegram",
|
|
5799
6010
|
);
|
|
5800
6011
|
push(
|
|
5801
6012
|
"telegram_destination_select_single_active",
|
|
@@ -5813,6 +6024,7 @@ function runSelftest(flags = {}) {
|
|
|
5813
6024
|
{ id: "dest-2", provider: "telegram", label: "Room B", chat_id: "-1002", is_active: true },
|
|
5814
6025
|
],
|
|
5815
6026
|
{},
|
|
6027
|
+
"telegram",
|
|
5816
6028
|
);
|
|
5817
6029
|
push("telegram_destination_multi_requires_selector", false, "selection unexpectedly succeeded");
|
|
5818
6030
|
} catch (err) {
|
package/package.json
CHANGED
package/postinstall.mjs
CHANGED
|
@@ -4,38 +4,44 @@ import fs from "node:fs";
|
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const PROVIDERS = [
|
|
8
|
+
["telegram", "TELEGRAM_BOT_TOKEN", "Telegram"],
|
|
9
|
+
["slack", "SLACK_BOT_TOKEN", "Slack"],
|
|
10
|
+
["kakaotalk", "KAKAOTALK_BOT_TOKEN", "KakaoTalk"],
|
|
11
|
+
];
|
|
8
12
|
|
|
9
|
-
function resolveTargetPath() {
|
|
13
|
+
function resolveTargetPath(provider) {
|
|
10
14
|
const home = String(process.env.USERPROFILE || process.env.HOME || os.homedir() || "").trim();
|
|
11
15
|
if (!home) return "";
|
|
12
|
-
return path.join(home,
|
|
16
|
+
return path.join(home, ".metheus", `${provider}.env`);
|
|
13
17
|
}
|
|
14
18
|
|
|
15
|
-
function template() {
|
|
19
|
+
function template(label, tokenKey) {
|
|
16
20
|
return [
|
|
17
|
-
|
|
21
|
+
`# Metheus local ${label} bot settings`,
|
|
18
22
|
"# Keep this file on your machine only. Do not commit it.",
|
|
19
|
-
|
|
20
|
-
"# Project
|
|
23
|
+
`# Store only the ${label} bot token locally.`,
|
|
24
|
+
"# Project chat destinations must be managed on the Metheus server.",
|
|
21
25
|
"",
|
|
22
|
-
|
|
26
|
+
`${tokenKey}=`,
|
|
23
27
|
"",
|
|
24
28
|
].join("\n");
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
function main() {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
for (const [provider, tokenKey, label] of PROVIDERS) {
|
|
33
|
+
const filePath = resolveTargetPath(provider);
|
|
34
|
+
if (!filePath) continue;
|
|
35
|
+
try {
|
|
36
|
+
if (fs.existsSync(filePath)) continue;
|
|
37
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
38
|
+
fs.writeFileSync(filePath, template(label, tokenKey), "utf8");
|
|
39
|
+
process.stdout.write(`[metheus-governance-mcp-cli] created ${filePath}\n`);
|
|
40
|
+
} catch (err) {
|
|
41
|
+
process.stderr.write(
|
|
42
|
+
`[metheus-governance-mcp-cli] could not create ${filePath}: ${String(err?.message || err)}\n`,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
39
45
|
}
|
|
40
46
|
}
|
|
41
47
|
|