metheus-governance-mcp-cli 0.2.74 → 0.2.75

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 +148 -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,20 @@ 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 answer = String(await ui.ask(`${colorText(">", "32")} ${defaultLabel ? `[${defaultLabel}] ` : ""}`) || "").trim();
262
375
  if (!answer && defaultLabel) {
263
376
  return list[defaultIndex];
264
377
  }
@@ -267,7 +380,7 @@ async function promptChoice(ui, title, options, { defaultIndex = 0, allowCancel
267
380
  if (Number.isFinite(numeric) && numeric >= 1 && numeric <= list.length) {
268
381
  return list[numeric - 1];
269
382
  }
270
- process.stdout.write("Select a valid number.\n");
383
+ process.stdout.write(`${colorText("Select a valid number.\n", "31")}`);
271
384
  }
272
385
  }
273
386
 
@@ -2254,6 +2367,9 @@ async function removeTokenOnlyProvider(ui, provider, flags, deps) {
2254
2367
  }
2255
2368
 
2256
2369
  async function runBotSetup(ui, flags, deps) {
2370
+ if (shouldRenderPromptChrome(flags)) {
2371
+ ui.setFlow("BOT SETUP", "Guided local bot management");
2372
+ }
2257
2373
  const provider = await selectProvider(ui, flags.provider, deps);
2258
2374
  const telegramActions = [
2259
2375
  { value: "list", label: "List bots" },
@@ -2384,6 +2500,9 @@ async function runBotShow(ui, flags, deps, explicitProvider = "") {
2384
2500
  }
2385
2501
 
2386
2502
  async function runBotAdd(ui, flags, deps) {
2503
+ if (shouldRenderPromptChrome(flags)) {
2504
+ ui.setFlow("BOT ADD", "Create a local bot entry from server bot identity and local token");
2505
+ }
2387
2506
  const provider = await selectProvider(ui, flags.provider, deps);
2388
2507
  if (provider === "telegram") {
2389
2508
  await addTelegramBot(ui, flags, deps);
@@ -2393,6 +2512,9 @@ async function runBotAdd(ui, flags, deps) {
2393
2512
  }
2394
2513
 
2395
2514
  async function runBotEdit(ui, flags, deps) {
2515
+ if (shouldRenderPromptChrome(flags)) {
2516
+ ui.setFlow("BOT EDIT", "Update an existing local bot entry");
2517
+ }
2396
2518
  const provider = await selectProvider(ui, flags.provider, deps);
2397
2519
  if (provider === "telegram") {
2398
2520
  await editTelegramBot(ui, flags, deps);
@@ -2402,6 +2524,9 @@ async function runBotEdit(ui, flags, deps) {
2402
2524
  }
2403
2525
 
2404
2526
  async function runBotRemove(ui, flags, deps) {
2527
+ if (shouldRenderPromptChrome(flags)) {
2528
+ ui.setFlow("BOT REMOVE", "Delete a local bot entry");
2529
+ }
2405
2530
  const provider = await selectProvider(ui, flags.provider, deps);
2406
2531
  if (provider === "telegram") {
2407
2532
  const nonInteractive = boolFromRaw(flags["non-interactive"] ?? flags.yes, false);
@@ -2427,11 +2552,17 @@ async function runBotRemove(ui, flags, deps) {
2427
2552
  }
2428
2553
 
2429
2554
  async function runBotVerify(ui, flags, deps) {
2555
+ if (shouldRenderPromptChrome(flags)) {
2556
+ ui.setFlow("BOT VERIFY", "Check local token, server binding, and linked routes");
2557
+ }
2430
2558
  const provider = await selectProvider(ui, flags.provider, deps);
2431
2559
  await verifyProviderEntry(ui, provider, flags, deps);
2432
2560
  }
2433
2561
 
2434
2562
  async function runBotSetDefault(ui, flags, deps) {
2563
+ if (shouldRenderPromptChrome(flags)) {
2564
+ ui.setFlow("BOT SET-DEFAULT", "Choose the default Telegram local entry");
2565
+ }
2435
2566
  const provider = await selectProvider(ui, flags.provider, deps);
2436
2567
  if (provider !== "telegram") {
2437
2568
  throw new Error("bot set-default currently supports only --provider telegram");
@@ -2461,6 +2592,9 @@ async function runBotSetDefault(ui, flags, deps) {
2461
2592
  }
2462
2593
 
2463
2594
  async function runBotMigrate(ui, flags, deps) {
2595
+ if (shouldRenderPromptChrome(flags)) {
2596
+ ui.setFlow("BOT MIGRATE", "Convert legacy Telegram token config into a named entry");
2597
+ }
2464
2598
  const provider = String(flags.provider || "").trim()
2465
2599
  ? requireDependency(deps, "normalizeBotProvider")(flags.provider)
2466
2600
  : "telegram";
@@ -2511,6 +2645,9 @@ async function runBotMigrate(ui, flags, deps) {
2511
2645
  }
2512
2646
 
2513
2647
  async function runBotGlobal(ui, flags, deps) {
2648
+ if (shouldRenderPromptChrome(flags)) {
2649
+ ui.setFlow("BOT GLOBAL", "Edit Telegram global local settings");
2650
+ }
2514
2651
  const provider = String(flags.provider || "").trim()
2515
2652
  ? requireDependency(deps, "normalizeBotProvider")(flags.provider)
2516
2653
  : "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.75",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [