metheus-governance-mcp-cli 0.2.51 → 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 +294 -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) {
|
|
@@ -3967,6 +4166,7 @@ function appendProjectHintToInitialize(responseObj, args, options = {}) {
|
|
|
3967
4166
|
const ctxpackEnsureTool = displayToolNameForClient("ctxpack.ensure", useSafeToolAliases);
|
|
3968
4167
|
const ctxpackMergeBriefTool = displayToolNameForClient("ctxpack.merge.brief", useSafeToolAliases);
|
|
3969
4168
|
const ctxpackMergeExecuteTool = displayToolNameForClient("ctxpack.merge.execute", useSafeToolAliases);
|
|
4169
|
+
const projectListDestinationsTool = displayToolNameForClient("project.list-telegram-destinations", useSafeToolAliases);
|
|
3970
4170
|
const meListBotsTool = displayToolNameForClient("me.list-bots", useSafeToolAliases);
|
|
3971
4171
|
const meSendBotMessageTool = displayToolNameForClient("me.send-bot-message", useSafeToolAliases);
|
|
3972
4172
|
const hintLines = [
|
|
@@ -3980,6 +4180,8 @@ function appendProjectHintToInitialize(responseObj, args, options = {}) {
|
|
|
3980
4180
|
`- If user enters a Project ID in text (e.g., "Project ID <uuid>"), pass that exact UUID as \`project_id\` argument when calling \`${projectSummaryTool}\`.`,
|
|
3981
4181
|
`- \`${projectDescribeTool}\` and \`${projectGetTool}\` are aliases of \`${projectSummaryTool}\`.`,
|
|
3982
4182
|
`- Never handle a bare Project ID by local file/transcript search before \`${projectSummaryTool}\` succeeds.`,
|
|
4183
|
+
`- For project room/provider checks, call \`${projectListDestinationsTool}\` first. Treat those chat destinations as the normal routing source.`,
|
|
4184
|
+
"- Do not surface webhook target URLs for normal room routing unless the user explicitly asks about advanced relays or webhook debugging.",
|
|
3983
4185
|
`- Run \`${ctxpackEnsureTool}\` only after \`${projectSummaryTool}\`, and only when additional ctxpack refresh/export context is needed.`,
|
|
3984
4186
|
"- After project summary, use workitem/evidence/decision tools as follow-up.",
|
|
3985
4187
|
`- For direct bot posting, call \`${meListBotsTool}\` first, then use \`${meSendBotMessageTool}\`. The proxy delivers with your local provider token config and the current project's saved chat destination.`,
|
|
@@ -4083,24 +4285,26 @@ function normalizeChatDestination(record) {
|
|
|
4083
4285
|
};
|
|
4084
4286
|
}
|
|
4085
4287
|
|
|
4086
|
-
function selectProjectChatDestination(destinations, selectors = {}) {
|
|
4288
|
+
function selectProjectChatDestination(destinations, selectors = {}, provider = "telegram") {
|
|
4289
|
+
const normalizedProvider = normalizeBotProvider(provider);
|
|
4290
|
+
const providerLabel = providerEnvConfig(normalizedProvider).label;
|
|
4087
4291
|
const list = ensureArray(destinations)
|
|
4088
4292
|
.map(normalizeChatDestination)
|
|
4089
|
-
.filter((item) => item.provider ===
|
|
4293
|
+
.filter((item) => item.provider === normalizedProvider && item.chatID && item.isActive);
|
|
4090
4294
|
const destinationID = String(selectors.destinationID || "").trim();
|
|
4091
4295
|
const destinationLabel = String(selectors.destinationLabel || "").trim().toLowerCase();
|
|
4092
4296
|
|
|
4093
4297
|
if (destinationID) {
|
|
4094
4298
|
const match = list.find((item) => item.id === destinationID);
|
|
4095
4299
|
if (!match) {
|
|
4096
|
-
throw new Error(
|
|
4300
|
+
throw new Error(`${providerLabel} project chat destination ${destinationID} was not found or is inactive`);
|
|
4097
4301
|
}
|
|
4098
4302
|
return match;
|
|
4099
4303
|
}
|
|
4100
4304
|
if (destinationLabel) {
|
|
4101
4305
|
const match = list.find((item) => item.label.toLowerCase() === destinationLabel);
|
|
4102
4306
|
if (!match) {
|
|
4103
|
-
throw new Error(
|
|
4307
|
+
throw new Error(`${providerLabel} project chat destination label "${selectors.destinationLabel}" was not found or is inactive`);
|
|
4104
4308
|
}
|
|
4105
4309
|
return match;
|
|
4106
4310
|
}
|
|
@@ -4108,11 +4312,11 @@ function selectProjectChatDestination(destinations, selectors = {}) {
|
|
|
4108
4312
|
return list[0];
|
|
4109
4313
|
}
|
|
4110
4314
|
if (list.length === 0) {
|
|
4111
|
-
throw new Error(
|
|
4315
|
+
throw new Error(`no active ${providerLabel} chat destination is configured for this project`);
|
|
4112
4316
|
}
|
|
4113
4317
|
const labels = list.map((item) => item.label || item.id).filter(Boolean);
|
|
4114
4318
|
throw new Error(
|
|
4115
|
-
`multiple active
|
|
4319
|
+
`multiple active ${providerLabel} chat destinations exist for this project; pass destination_id or destination_label (${labels.join(", ")})`,
|
|
4116
4320
|
);
|
|
4117
4321
|
}
|
|
4118
4322
|
|
|
@@ -4145,11 +4349,6 @@ async function handleLocalBotMessageToolCall({
|
|
|
4145
4349
|
throw new Error("project_id is required for local bot delivery; set --project-id during setup or include project_id");
|
|
4146
4350
|
}
|
|
4147
4351
|
|
|
4148
|
-
const telegramEnv = loadTelegramEnvConfig();
|
|
4149
|
-
if (!telegramEnv.ok) {
|
|
4150
|
-
throw new Error(`local telegram env is not ready (${telegramEnv.error})`);
|
|
4151
|
-
}
|
|
4152
|
-
|
|
4153
4352
|
const siteBaseURL = normalizeSiteBaseURL(args.baseURL);
|
|
4154
4353
|
const encodedBotID = encodeURIComponent(botID);
|
|
4155
4354
|
const encodedProjectID = encodeURIComponent(projectID);
|
|
@@ -4163,12 +4362,13 @@ async function handleLocalBotMessageToolCall({
|
|
|
4163
4362
|
if (bot.is_active === false || bot.isActive === false) {
|
|
4164
4363
|
throw new Error("bot is inactive");
|
|
4165
4364
|
}
|
|
4365
|
+
const provider = normalizeBotProvider(bot.provider);
|
|
4166
4366
|
|
|
4167
4367
|
const destinations = ensureArray(await getJSONWithAuth(destinationURL, args.timeoutSeconds, token));
|
|
4168
4368
|
const destination = selectProjectChatDestination(destinations, {
|
|
4169
4369
|
destinationID,
|
|
4170
4370
|
destinationLabel,
|
|
4171
|
-
});
|
|
4371
|
+
}, provider);
|
|
4172
4372
|
|
|
4173
4373
|
const disableWebPagePreview = boolFromRaw(
|
|
4174
4374
|
Object.prototype.hasOwnProperty.call(toolArgs, "disable_web_page_preview")
|
|
@@ -4185,28 +4385,31 @@ async function handleLocalBotMessageToolCall({
|
|
|
4185
4385
|
throw new Error("reply_to_message_id must be a positive integer");
|
|
4186
4386
|
}
|
|
4187
4387
|
|
|
4188
|
-
const
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
text,
|
|
4192
|
-
disable_web_page_preview: disableWebPagePreview,
|
|
4193
|
-
};
|
|
4194
|
-
if (replyToMessageID > 0) {
|
|
4195
|
-
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})`);
|
|
4196
4391
|
}
|
|
4197
4392
|
|
|
4198
|
-
const
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
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);
|
|
4202
4404
|
const errorDetail =
|
|
4203
|
-
String(responseJSON
|
|
4204
|
-
|
|
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)) {
|
|
4205
4408
|
throw new Error(
|
|
4206
|
-
`local
|
|
4409
|
+
`local ${provider} delivery failed (Unauthorized). Check ${providerEnvConfig(provider).tokenKey} in ${providerEnv.filePath}`,
|
|
4207
4410
|
);
|
|
4208
4411
|
}
|
|
4209
|
-
throw new Error(`local
|
|
4412
|
+
throw new Error(`local ${provider} delivery failed (${errorDetail})`);
|
|
4210
4413
|
}
|
|
4211
4414
|
|
|
4212
4415
|
return jsonRpcResult(
|
|
@@ -4214,7 +4417,7 @@ async function handleLocalBotMessageToolCall({
|
|
|
4214
4417
|
buildLocalToolRPCResult("me.send-bot-message", {
|
|
4215
4418
|
ok: true,
|
|
4216
4419
|
local_delivery: true,
|
|
4217
|
-
provider
|
|
4420
|
+
provider,
|
|
4218
4421
|
bot_id: String(bot.id || botID).trim(),
|
|
4219
4422
|
bot_name: String(bot.name || "").trim(),
|
|
4220
4423
|
bot_role: String(bot.bot_role || bot.botRole || "").trim(),
|
|
@@ -4222,9 +4425,10 @@ async function handleLocalBotMessageToolCall({
|
|
|
4222
4425
|
destination_id: destination.id,
|
|
4223
4426
|
destination_label: destination.label,
|
|
4224
4427
|
chat_id: destination.chatID,
|
|
4225
|
-
status:
|
|
4226
|
-
url:
|
|
4227
|
-
body:
|
|
4428
|
+
status: delivery.statusCode,
|
|
4429
|
+
url: delivery.url,
|
|
4430
|
+
body: delivery.body,
|
|
4431
|
+
reply_supported: delivery.replySupported,
|
|
4228
4432
|
}),
|
|
4229
4433
|
);
|
|
4230
4434
|
}
|
|
@@ -5701,7 +5905,7 @@ function runSetupInternal(flags, options = {}) {
|
|
|
5701
5905
|
const context = resolveSetupContext(flags);
|
|
5702
5906
|
const clients = [...MCP_CLIENTS];
|
|
5703
5907
|
const results = [];
|
|
5704
|
-
const
|
|
5908
|
+
const providerTemplates = ensureAllProviderEnvTemplates();
|
|
5705
5909
|
|
|
5706
5910
|
for (const cliBin of clients) {
|
|
5707
5911
|
if (!commandExists(cliBin)) continue;
|
|
@@ -5751,12 +5955,16 @@ function runSetupInternal(flags, options = {}) {
|
|
|
5751
5955
|
process.stdout.write(`Fallback: ${context.workspaceFallbackDir} (METHEUS_WORKSPACE_DIR)\n`);
|
|
5752
5956
|
}
|
|
5753
5957
|
process.stdout.write(`Project: ${context.projectID || "auto-detect from .metheus_ctxpack_sync.json"}\n`);
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
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
|
+
}
|
|
5760
5968
|
}
|
|
5761
5969
|
if (context.ctxpackKey) {
|
|
5762
5970
|
process.stdout.write(`Ctxpack: ${context.ctxpackKey}\n`);
|
|
@@ -5779,12 +5987,17 @@ function runSelftest(flags = {}) {
|
|
|
5779
5987
|
const checks = [];
|
|
5780
5988
|
const push = (name, ok, detail = "") => checks.push({ name, ok: Boolean(ok), detail: String(detail || "") });
|
|
5781
5989
|
|
|
5782
|
-
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");
|
|
5783
5991
|
push(
|
|
5784
5992
|
"telegram_env_parse_token",
|
|
5785
5993
|
String(parsedEnv.TELEGRAM_BOT_TOKEN || "") === "abc:123",
|
|
5786
5994
|
`token=${String(parsedEnv.TELEGRAM_BOT_TOKEN || "(missing)")}`,
|
|
5787
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
|
+
);
|
|
5788
6001
|
|
|
5789
6002
|
try {
|
|
5790
6003
|
const selected = selectProjectChatDestination(
|
|
@@ -5793,6 +6006,7 @@ function runSelftest(flags = {}) {
|
|
|
5793
6006
|
{ id: "dest-2", provider: "slack", label: "Slack", chat_id: "C123", is_active: true },
|
|
5794
6007
|
],
|
|
5795
6008
|
{},
|
|
6009
|
+
"telegram",
|
|
5796
6010
|
);
|
|
5797
6011
|
push(
|
|
5798
6012
|
"telegram_destination_select_single_active",
|
|
@@ -5810,6 +6024,7 @@ function runSelftest(flags = {}) {
|
|
|
5810
6024
|
{ id: "dest-2", provider: "telegram", label: "Room B", chat_id: "-1002", is_active: true },
|
|
5811
6025
|
],
|
|
5812
6026
|
{},
|
|
6027
|
+
"telegram",
|
|
5813
6028
|
);
|
|
5814
6029
|
push("telegram_destination_multi_requires_selector", false, "selection unexpectedly succeeded");
|
|
5815
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
|
|