metheus-governance-mcp-cli 0.2.74 → 0.2.76

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 (2) hide show
  1. package/lib/bot-commands.mjs +149 -11
  2. package/package.json +1 -1
@@ -108,6 +108,70 @@ function maskSecret(rawValue) {
108
108
  return `${text.slice(0, 4)}...${text.slice(-4)}`;
109
109
  }
110
110
 
111
+ function supportsANSIColors() {
112
+ if (process.env.NO_COLOR) return false;
113
+ if (!process.stdout?.isTTY) return false;
114
+ const term = String(process.env.TERM || "").trim().toLowerCase();
115
+ return term !== "dumb";
116
+ }
117
+
118
+ function colorText(text, colorCode) {
119
+ const value = String(text || "");
120
+ if (!value || !supportsANSIColors()) return value;
121
+ return `\u001b[${colorCode}m${value}\u001b[0m`;
122
+ }
123
+
124
+ function padRight(text, width) {
125
+ const value = String(text || "");
126
+ if (value.length >= width) return value.slice(0, width);
127
+ return `${value}${" ".repeat(width - value.length)}`;
128
+ }
129
+
130
+ function wrapAsciiLine(text, width) {
131
+ const value = String(text || "");
132
+ if (!value) return [""];
133
+ const words = value.split(/\s+/).filter(Boolean);
134
+ if (!words.length) return [""];
135
+ const lines = [];
136
+ let current = "";
137
+ words.forEach((word) => {
138
+ const candidate = current ? `${current} ${word}` : word;
139
+ if (candidate.length <= width) {
140
+ current = candidate;
141
+ return;
142
+ }
143
+ if (current) {
144
+ lines.push(current);
145
+ current = word;
146
+ return;
147
+ }
148
+ let remaining = word;
149
+ while (remaining.length > width) {
150
+ lines.push(remaining.slice(0, width));
151
+ remaining = remaining.slice(width);
152
+ }
153
+ current = remaining;
154
+ });
155
+ if (current) lines.push(current);
156
+ return lines.length ? lines : [""];
157
+ }
158
+
159
+ function formatAsciiBlock(title, lines = [], { accentColor = "36" } = {}) {
160
+ const contentWidth = 62;
161
+ const blockLines = [
162
+ title ? String(title || "").trim() : "",
163
+ ...ensureArray(lines).flatMap((line) => wrapAsciiLine(line, contentWidth)),
164
+ ].filter((line, index, all) => !(index > 0 && !line && !all[index - 1]));
165
+ const border = `+${"-".repeat(contentWidth + 2)}+`;
166
+ const formatted = [colorText(border, accentColor)];
167
+ blockLines.forEach((line, index) => {
168
+ const rendered = `| ${padRight(line, contentWidth)} |`;
169
+ formatted.push(index === 0 ? colorText(rendered, accentColor) : rendered);
170
+ });
171
+ formatted.push(colorText(border, accentColor));
172
+ return `${formatted.join("\n")}\n`;
173
+ }
174
+
111
175
  function formatEnvValue(rawValue) {
112
176
  const text = String(rawValue ?? "");
113
177
  if (!text) return "";
@@ -125,6 +189,13 @@ function providerTokenKey(provider, deps) {
125
189
  return safeObject(requireDependency(deps, "providerEnvConfig")(provider)).tokenKey || "";
126
190
  }
127
191
 
192
+ function shouldRenderPromptChrome(flags) {
193
+ const parsedFlags = safeObject(flags);
194
+ if (boolFromRaw(parsedFlags["non-interactive"] ?? parsedFlags.yes, false)) return false;
195
+ if (boolFromRaw(parsedFlags.json, false)) return false;
196
+ return true;
197
+ }
198
+
128
199
  function printBotUsage(deps) {
129
200
  const cliName = String(deps?.cliName || "metheus-governance-mcp-cli").trim() || "metheus-governance-mcp-cli";
130
201
  process.stdout.write(
@@ -156,6 +227,24 @@ function printBotUsage(deps) {
156
227
  }
157
228
 
158
229
  function createPrompter() {
230
+ let flowTitle = "";
231
+ let flowSubtitle = "";
232
+ let stepIndex = 0;
233
+ function write(text) {
234
+ process.stdout.write(String(text || ""));
235
+ }
236
+ function printFlowBanner() {
237
+ if (!flowTitle) return;
238
+ const lines = flowSubtitle ? [flowSubtitle] : [];
239
+ write(`\n${formatAsciiBlock(flowTitle, lines, { accentColor: "35" })}`);
240
+ }
241
+ function beginPrompt(title, lines = []) {
242
+ stepIndex += 1;
243
+ const bannerTitle = flowTitle
244
+ ? `${flowTitle} | STEP ${stepIndex}`
245
+ : `STEP ${stepIndex}`;
246
+ write(`\n${formatAsciiBlock(bannerTitle, [title, ...ensureArray(lines)], { accentColor: "36" })}`);
247
+ }
159
248
  const scriptedPromptAnswersRaw = String(process.env.METHEUS_SCRIPTED_PROMPT_ANSWERS || "").trim();
160
249
  if (scriptedPromptAnswersRaw) {
161
250
  let scriptedAnswers = [];
@@ -167,17 +256,31 @@ function createPrompter() {
167
256
  }
168
257
  let answerIndex = 0;
169
258
  return {
259
+ setFlow(title, subtitle = "") {
260
+ flowTitle = String(title || "").trim();
261
+ flowSubtitle = String(subtitle || "").trim();
262
+ stepIndex = 0;
263
+ printFlowBanner();
264
+ },
265
+ beginPrompt,
170
266
  ask(promptText) {
171
- process.stdout.write(promptText);
267
+ write(promptText);
172
268
  const answer = answerIndex < scriptedAnswers.length ? scriptedAnswers[answerIndex] : "";
173
269
  answerIndex += 1;
174
- process.stdout.write(`${answer}\n`);
270
+ write(`${answer}\n`);
175
271
  return Promise.resolve(String(answer || ""));
176
272
  },
177
273
  close() {},
178
274
  };
179
275
  }
180
276
  return {
277
+ setFlow(title, subtitle = "") {
278
+ flowTitle = String(title || "").trim();
279
+ flowSubtitle = String(subtitle || "").trim();
280
+ stepIndex = 0;
281
+ printFlowBanner();
282
+ },
283
+ beginPrompt,
181
284
  ask(promptText) {
182
285
  return new Promise((resolve) => {
183
286
  const rl = readline.createInterface({
@@ -195,8 +298,13 @@ function createPrompter() {
195
298
  }
196
299
 
197
300
  async function promptLine(ui, promptText, defaultValue = "") {
301
+ ui.beginPrompt(promptText, [
302
+ String(defaultValue || "").trim()
303
+ ? `Press Enter to keep the current value: ${defaultValue}`
304
+ : "Type a value and press Enter.",
305
+ ]);
198
306
  const suffix = String(defaultValue || "").trim() ? ` [${defaultValue}]` : "";
199
- const answer = await ui.ask(`${promptText}${suffix}: `);
307
+ const answer = await ui.ask(`${colorText(">", "32")} ${promptText}${suffix}: `);
200
308
  const text = String(answer || "").trim();
201
309
  return text || String(defaultValue || "").trim();
202
310
  }
@@ -205,18 +313,19 @@ async function promptRequiredLine(ui, promptText, defaultValue = "") {
205
313
  while (true) {
206
314
  const answer = await promptLine(ui, promptText, defaultValue);
207
315
  if (answer) return answer;
208
- process.stdout.write("Value is required.\n");
316
+ process.stdout.write(`${colorText("Value is required.\n", "31")}`);
209
317
  }
210
318
  }
211
319
 
212
320
  async function promptYesNo(ui, promptText, defaultValue = true) {
321
+ ui.beginPrompt(promptText, ["Choose y or n, then press Enter."]);
213
322
  const hint = defaultValue ? "Y/n" : "y/N";
214
323
  while (true) {
215
- const answer = String(await ui.ask(`${promptText} [${hint}]: `) || "").trim().toLowerCase();
324
+ const answer = String(await ui.ask(`${colorText(">", "32")} ${promptText} [${hint}]: `) || "").trim().toLowerCase();
216
325
  if (!answer) return defaultValue;
217
326
  if (["y", "yes", "1", "true"].includes(answer)) return true;
218
327
  if (["n", "no", "0", "false"].includes(answer)) return false;
219
- process.stdout.write("Choose y or n.\n");
328
+ process.stdout.write(`${colorText("Choose y or n.\n", "31")}`);
220
329
  }
221
330
  }
222
331
 
@@ -249,16 +358,21 @@ async function promptChoice(ui, title, options, { defaultIndex = 0, allowCancel
249
358
  if (!list.length) {
250
359
  throw new Error(`no options available for ${title}`);
251
360
  }
361
+ const guidance = [
362
+ "Choose one option by number and press Enter.",
363
+ allowCancel ? "Enter 0 to cancel." : "",
364
+ ].filter(Boolean);
365
+ ui.beginPrompt(title, guidance);
252
366
  while (true) {
253
- process.stdout.write(`${title}\n`);
254
367
  list.forEach((option, index) => {
255
- process.stdout.write(` ${index + 1}. ${formatChoiceLabel(option)}\n`);
368
+ process.stdout.write(` ${colorText(`[${index + 1}]`, "33")} ${formatChoiceLabel(option)}\n`);
256
369
  });
257
370
  if (allowCancel) {
258
- process.stdout.write(" 0. Cancel\n");
371
+ process.stdout.write(` ${colorText("[0]", "33")} Cancel\n`);
259
372
  }
260
373
  const defaultLabel = defaultIndex >= 0 && defaultIndex < list.length ? String(defaultIndex + 1) : "";
261
- const answer = String(await ui.ask(`> ${defaultLabel ? `[${defaultLabel}] ` : ""}`) || "").trim();
374
+ const promptLabel = defaultLabel ? `default=${defaultLabel}` : "enter number";
375
+ const answer = String(await ui.ask(`${colorText(">", "32")} ${promptLabel}: `) || "").trim();
262
376
  if (!answer && defaultLabel) {
263
377
  return list[defaultIndex];
264
378
  }
@@ -267,7 +381,7 @@ async function promptChoice(ui, title, options, { defaultIndex = 0, allowCancel
267
381
  if (Number.isFinite(numeric) && numeric >= 1 && numeric <= list.length) {
268
382
  return list[numeric - 1];
269
383
  }
270
- process.stdout.write("Select a valid number.\n");
384
+ process.stdout.write(`${colorText("Select a valid number.\n", "31")}`);
271
385
  }
272
386
  }
273
387
 
@@ -2254,6 +2368,9 @@ async function removeTokenOnlyProvider(ui, provider, flags, deps) {
2254
2368
  }
2255
2369
 
2256
2370
  async function runBotSetup(ui, flags, deps) {
2371
+ if (shouldRenderPromptChrome(flags)) {
2372
+ ui.setFlow("BOT SETUP", "Guided local bot management");
2373
+ }
2257
2374
  const provider = await selectProvider(ui, flags.provider, deps);
2258
2375
  const telegramActions = [
2259
2376
  { value: "list", label: "List bots" },
@@ -2384,6 +2501,9 @@ async function runBotShow(ui, flags, deps, explicitProvider = "") {
2384
2501
  }
2385
2502
 
2386
2503
  async function runBotAdd(ui, flags, deps) {
2504
+ if (shouldRenderPromptChrome(flags)) {
2505
+ ui.setFlow("BOT ADD", "Create a local bot entry from server bot identity and local token");
2506
+ }
2387
2507
  const provider = await selectProvider(ui, flags.provider, deps);
2388
2508
  if (provider === "telegram") {
2389
2509
  await addTelegramBot(ui, flags, deps);
@@ -2393,6 +2513,9 @@ async function runBotAdd(ui, flags, deps) {
2393
2513
  }
2394
2514
 
2395
2515
  async function runBotEdit(ui, flags, deps) {
2516
+ if (shouldRenderPromptChrome(flags)) {
2517
+ ui.setFlow("BOT EDIT", "Update an existing local bot entry");
2518
+ }
2396
2519
  const provider = await selectProvider(ui, flags.provider, deps);
2397
2520
  if (provider === "telegram") {
2398
2521
  await editTelegramBot(ui, flags, deps);
@@ -2402,6 +2525,9 @@ async function runBotEdit(ui, flags, deps) {
2402
2525
  }
2403
2526
 
2404
2527
  async function runBotRemove(ui, flags, deps) {
2528
+ if (shouldRenderPromptChrome(flags)) {
2529
+ ui.setFlow("BOT REMOVE", "Delete a local bot entry");
2530
+ }
2405
2531
  const provider = await selectProvider(ui, flags.provider, deps);
2406
2532
  if (provider === "telegram") {
2407
2533
  const nonInteractive = boolFromRaw(flags["non-interactive"] ?? flags.yes, false);
@@ -2427,11 +2553,17 @@ async function runBotRemove(ui, flags, deps) {
2427
2553
  }
2428
2554
 
2429
2555
  async function runBotVerify(ui, flags, deps) {
2556
+ if (shouldRenderPromptChrome(flags)) {
2557
+ ui.setFlow("BOT VERIFY", "Check local token, server binding, and linked routes");
2558
+ }
2430
2559
  const provider = await selectProvider(ui, flags.provider, deps);
2431
2560
  await verifyProviderEntry(ui, provider, flags, deps);
2432
2561
  }
2433
2562
 
2434
2563
  async function runBotSetDefault(ui, flags, deps) {
2564
+ if (shouldRenderPromptChrome(flags)) {
2565
+ ui.setFlow("BOT SET-DEFAULT", "Choose the default Telegram local entry");
2566
+ }
2435
2567
  const provider = await selectProvider(ui, flags.provider, deps);
2436
2568
  if (provider !== "telegram") {
2437
2569
  throw new Error("bot set-default currently supports only --provider telegram");
@@ -2461,6 +2593,9 @@ async function runBotSetDefault(ui, flags, deps) {
2461
2593
  }
2462
2594
 
2463
2595
  async function runBotMigrate(ui, flags, deps) {
2596
+ if (shouldRenderPromptChrome(flags)) {
2597
+ ui.setFlow("BOT MIGRATE", "Convert legacy Telegram token config into a named entry");
2598
+ }
2464
2599
  const provider = String(flags.provider || "").trim()
2465
2600
  ? requireDependency(deps, "normalizeBotProvider")(flags.provider)
2466
2601
  : "telegram";
@@ -2511,6 +2646,9 @@ async function runBotMigrate(ui, flags, deps) {
2511
2646
  }
2512
2647
 
2513
2648
  async function runBotGlobal(ui, flags, deps) {
2649
+ if (shouldRenderPromptChrome(flags)) {
2650
+ ui.setFlow("BOT GLOBAL", "Edit Telegram global local settings");
2651
+ }
2514
2652
  const provider = String(flags.provider || "").trim()
2515
2653
  ? requireDependency(deps, "normalizeBotProvider")(flags.provider)
2516
2654
  : "telegram";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metheus-governance-mcp-cli",
3
- "version": "0.2.74",
3
+ "version": "0.2.76",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [