@locusai/locus-telegram 0.21.6

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.
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Locus CLI command passthrough — maps every Telegram command to
3
+ * the corresponding `locus` CLI invocation via `@locusai/sdk`.
4
+ *
5
+ * Long-running commands stream output by editing the Telegram message
6
+ * in place at regular intervals.
7
+ */
8
+ import { invokeLocusStream } from "@locusai/sdk";
9
+ import { formatCommandResult, formatStreamingMessage, } from "../ui/format.js";
10
+ import { planKeyboard, reviewKeyboard, runCompleteKeyboard, statusKeyboard, } from "../ui/keyboards.js";
11
+ import { reviewStartedMessage, runStartedMessage, } from "../ui/messages.js";
12
+ // ─── Command Map ────────────────────────────────────────────────────────────
13
+ /** Maps Telegram command names to locus CLI arguments. */
14
+ const COMMAND_MAP = {
15
+ run: ["run"],
16
+ status: ["status"],
17
+ issues: ["issue", "list"],
18
+ issue: ["issue", "show"],
19
+ sprint: ["sprint"],
20
+ plan: ["plan"],
21
+ review: ["review"],
22
+ iterate: ["iterate"],
23
+ discuss: ["discuss"],
24
+ exec: ["exec"],
25
+ logs: ["logs"],
26
+ config: ["config"],
27
+ artifacts: ["artifacts"],
28
+ };
29
+ /** Commands that produce long-running streaming output. */
30
+ const STREAMING_COMMANDS = new Set([
31
+ "run",
32
+ "plan",
33
+ "review",
34
+ "iterate",
35
+ "discuss",
36
+ "exec",
37
+ ]);
38
+ /** Minimum interval between message edits (ms) to avoid Telegram rate limits. */
39
+ const EDIT_INTERVAL = 2000;
40
+ // ─── Handlers ───────────────────────────────────────────────────────────────
41
+ /**
42
+ * Execute a locus CLI command, streaming output to the Telegram chat.
43
+ */
44
+ export async function handleLocusCommand(ctx, command, args) {
45
+ const cliArgs = COMMAND_MAP[command];
46
+ if (!cliArgs) {
47
+ await ctx.reply(`Unknown command: /${command}`);
48
+ return;
49
+ }
50
+ const fullArgs = [...cliArgs, ...args];
51
+ const displayCmd = `locus ${fullArgs.join(" ")}`;
52
+ const isStreaming = STREAMING_COMMANDS.has(command);
53
+ // Send pre-messages for specific commands
54
+ if (command === "run" && args.length > 0) {
55
+ await ctx.reply(runStartedMessage(args.join(" ")), { parse_mode: "HTML" });
56
+ }
57
+ else if (command === "review" && args.length > 0) {
58
+ await ctx.reply(reviewStartedMessage(Number(args[0])), {
59
+ parse_mode: "HTML",
60
+ });
61
+ }
62
+ if (isStreaming) {
63
+ await handleStreamingCommand(ctx, displayCmd, fullArgs, command, args);
64
+ }
65
+ else {
66
+ await handleBufferedCommand(ctx, displayCmd, fullArgs, command);
67
+ }
68
+ }
69
+ /**
70
+ * Handle a streaming command — edit the message in place as output arrives.
71
+ */
72
+ async function handleStreamingCommand(ctx, displayCmd, fullArgs, command, args) {
73
+ const child = invokeLocusStream(fullArgs);
74
+ let output = "";
75
+ let lastEditTime = 0;
76
+ let editTimer = null;
77
+ // Send initial "running" message
78
+ const msg = await ctx.reply(formatStreamingMessage(displayCmd, "", false), {
79
+ parse_mode: "HTML",
80
+ });
81
+ const editMessage = async () => {
82
+ const now = Date.now();
83
+ if (now - lastEditTime < EDIT_INTERVAL)
84
+ return;
85
+ lastEditTime = now;
86
+ try {
87
+ await ctx.api.editMessageText(msg.chat.id, msg.message_id, formatStreamingMessage(displayCmd, output, false), { parse_mode: "HTML" });
88
+ }
89
+ catch {
90
+ // Edit can fail if content hasn't changed — ignore
91
+ }
92
+ };
93
+ child.stdout?.on("data", (chunk) => {
94
+ output += chunk.toString();
95
+ // Debounce edits
96
+ if (editTimer)
97
+ clearTimeout(editTimer);
98
+ editTimer = setTimeout(editMessage, EDIT_INTERVAL);
99
+ });
100
+ child.stderr?.on("data", (chunk) => {
101
+ output += chunk.toString();
102
+ });
103
+ await new Promise((resolve) => {
104
+ child.on("close", async (exitCode) => {
105
+ if (editTimer)
106
+ clearTimeout(editTimer);
107
+ // Final edit with complete output
108
+ try {
109
+ await ctx.api.editMessageText(msg.chat.id, msg.message_id, formatStreamingMessage(displayCmd, output, true), { parse_mode: "HTML" });
110
+ }
111
+ catch {
112
+ // ignore edit failures
113
+ }
114
+ // Send keyboard for post-command actions
115
+ const keyboard = getPostCommandKeyboard(command, args, exitCode ?? 0);
116
+ if (keyboard) {
117
+ await ctx.reply(exitCode === 0 ? "What would you like to do next?" : "Command failed.", { reply_markup: keyboard });
118
+ }
119
+ resolve();
120
+ });
121
+ });
122
+ }
123
+ /**
124
+ * Handle a buffered command — collect all output, then send once.
125
+ */
126
+ async function handleBufferedCommand(ctx, displayCmd, fullArgs, command) {
127
+ const child = invokeLocusStream(fullArgs);
128
+ let output = "";
129
+ child.stdout?.on("data", (chunk) => {
130
+ output += chunk.toString();
131
+ });
132
+ child.stderr?.on("data", (chunk) => {
133
+ output += chunk.toString();
134
+ });
135
+ await new Promise((resolve) => {
136
+ child.on("close", async (exitCode) => {
137
+ const result = formatCommandResult(displayCmd, output, exitCode ?? 0);
138
+ const keyboard = getPostCommandKeyboard(command, [], exitCode ?? 0);
139
+ await ctx.reply(result, {
140
+ parse_mode: "HTML",
141
+ reply_markup: keyboard ?? undefined,
142
+ });
143
+ resolve();
144
+ });
145
+ });
146
+ }
147
+ // ─── Post-Command Keyboards ────────────────────────────────────────────────
148
+ function getPostCommandKeyboard(command, args, exitCode) {
149
+ if (exitCode !== 0)
150
+ return null;
151
+ switch (command) {
152
+ case "plan":
153
+ return planKeyboard();
154
+ case "review":
155
+ if (args.length > 0) {
156
+ return reviewKeyboard(Number(args[0]));
157
+ }
158
+ return null;
159
+ case "run":
160
+ if (args.length > 0) {
161
+ return runCompleteKeyboard(Number(args[0]));
162
+ }
163
+ return runCompleteKeyboard();
164
+ case "status":
165
+ return statusKeyboard();
166
+ default:
167
+ return null;
168
+ }
169
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Service management commands — PM2 lifecycle control via Telegram.
3
+ */
4
+ import type { Context } from "grammy";
5
+ /** /service <subcommand> — manage the bot process */
6
+ export declare function handleService(ctx: Context, args: string[]): Promise<void>;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Service management commands — PM2 lifecycle control via Telegram.
3
+ */
4
+ import { formatError, formatSuccess, codeBlock } from "../ui/format.js";
5
+ import { pm2Start, pm2Stop, pm2Restart, pm2Delete, pm2Status, pm2Logs, } from "../pm2.js";
6
+ import { serviceStatusMessage, serviceNotRunningMessage } from "../ui/messages.js";
7
+ /** /service <subcommand> — manage the bot process */
8
+ export async function handleService(ctx, args) {
9
+ const subcommand = args[0] ?? "status";
10
+ try {
11
+ switch (subcommand) {
12
+ case "start": {
13
+ const result = pm2Start();
14
+ await ctx.reply(formatSuccess(result), { parse_mode: "HTML" });
15
+ break;
16
+ }
17
+ case "stop": {
18
+ const result = pm2Stop();
19
+ await ctx.reply(formatSuccess(result), { parse_mode: "HTML" });
20
+ break;
21
+ }
22
+ case "restart": {
23
+ const result = pm2Restart();
24
+ await ctx.reply(formatSuccess(result), { parse_mode: "HTML" });
25
+ break;
26
+ }
27
+ case "delete": {
28
+ const result = pm2Delete();
29
+ await ctx.reply(formatSuccess(result), { parse_mode: "HTML" });
30
+ break;
31
+ }
32
+ case "status": {
33
+ const status = pm2Status();
34
+ if (!status) {
35
+ await ctx.reply(serviceNotRunningMessage(), {
36
+ parse_mode: "HTML",
37
+ });
38
+ }
39
+ else {
40
+ await ctx.reply(serviceStatusMessage(status.status, status.pid, status.uptime, status.memory, status.restarts), { parse_mode: "HTML" });
41
+ }
42
+ break;
43
+ }
44
+ case "logs": {
45
+ const lines = args[1] ? Number(args[1]) : 50;
46
+ const logs = pm2Logs(lines);
47
+ await ctx.reply(codeBlock(logs), { parse_mode: "HTML" });
48
+ break;
49
+ }
50
+ default: {
51
+ await ctx.reply(formatError("Unknown service command. Use: start, stop, restart, delete, status, logs"), { parse_mode: "HTML" });
52
+ }
53
+ }
54
+ }
55
+ catch (error) {
56
+ await ctx.reply(formatError("Service command failed", String(error)), { parse_mode: "HTML" });
57
+ }
58
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Environment configuration for the Telegram bot.
3
+ *
4
+ * Required env vars:
5
+ * LOCUS_TELEGRAM_BOT_TOKEN — Telegram Bot API token from @BotFather
6
+ * LOCUS_TELEGRAM_CHAT_ID — Comma-separated list of allowed chat IDs
7
+ */
8
+ export interface TelegramConfig {
9
+ botToken: string;
10
+ allowedChatIds: number[];
11
+ }
12
+ export declare function loadTelegramConfig(): TelegramConfig;
package/dist/config.js ADDED
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Environment configuration for the Telegram bot.
3
+ *
4
+ * Required env vars:
5
+ * LOCUS_TELEGRAM_BOT_TOKEN — Telegram Bot API token from @BotFather
6
+ * LOCUS_TELEGRAM_CHAT_ID — Comma-separated list of allowed chat IDs
7
+ */
8
+ export function loadTelegramConfig() {
9
+ const botToken = process.env.LOCUS_TELEGRAM_BOT_TOKEN;
10
+ if (!botToken) {
11
+ throw new Error("LOCUS_TELEGRAM_BOT_TOKEN is required. Get one from @BotFather on Telegram.");
12
+ }
13
+ const chatIdRaw = process.env.LOCUS_TELEGRAM_CHAT_ID;
14
+ if (!chatIdRaw) {
15
+ throw new Error("LOCUS_TELEGRAM_CHAT_ID is required. Send /start to your bot and use the chat ID.");
16
+ }
17
+ const allowedChatIds = chatIdRaw
18
+ .split(",")
19
+ .map((id) => id.trim())
20
+ .filter((id) => id.length > 0)
21
+ .map((id) => {
22
+ const parsed = Number.parseInt(id, 10);
23
+ if (Number.isNaN(parsed)) {
24
+ throw new Error(`Invalid chat ID: "${id}". Must be a number.`);
25
+ }
26
+ return parsed;
27
+ });
28
+ if (allowedChatIds.length === 0) {
29
+ throw new Error("LOCUS_TELEGRAM_CHAT_ID must contain at least one chat ID.");
30
+ }
31
+ return { botToken, allowedChatIds };
32
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Main entry point for locus-telegram.
3
+ *
4
+ * Dispatches to either:
5
+ * - PM2 service management (start/stop/restart/status/logs)
6
+ * - Direct bot execution (bot)
7
+ *
8
+ * Usage:
9
+ * locus pkg telegram start → start bot via PM2
10
+ * locus pkg telegram stop → stop bot via PM2
11
+ * locus pkg telegram restart → restart bot
12
+ * locus pkg telegram status → show PM2 process status
13
+ * locus pkg telegram logs → tail PM2 logs
14
+ * locus pkg telegram bot → run bot directly (foreground)
15
+ */
16
+ export declare function main(args: string[]): Promise<void>;
package/dist/index.js ADDED
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Main entry point for locus-telegram.
3
+ *
4
+ * Dispatches to either:
5
+ * - PM2 service management (start/stop/restart/status/logs)
6
+ * - Direct bot execution (bot)
7
+ *
8
+ * Usage:
9
+ * locus pkg telegram start → start bot via PM2
10
+ * locus pkg telegram stop → stop bot via PM2
11
+ * locus pkg telegram restart → restart bot
12
+ * locus pkg telegram status → show PM2 process status
13
+ * locus pkg telegram logs → tail PM2 logs
14
+ * locus pkg telegram bot → run bot directly (foreground)
15
+ */
16
+ import { createLogger } from "@locusai/sdk";
17
+ import { createBot } from "./bot.js";
18
+ import { loadTelegramConfig } from "./config.js";
19
+ import { pm2Start, pm2Stop, pm2Restart, pm2Delete, pm2Status, pm2Logs, } from "./pm2.js";
20
+ const logger = createLogger("telegram");
21
+ export async function main(args) {
22
+ const command = args[0] ?? "help";
23
+ switch (command) {
24
+ case "start":
25
+ return handleStart();
26
+ case "stop":
27
+ return handleStop();
28
+ case "restart":
29
+ return handleRestart();
30
+ case "delete":
31
+ return handleDelete();
32
+ case "status":
33
+ return handleStatus();
34
+ case "logs":
35
+ return handleLogs(args.slice(1));
36
+ case "bot":
37
+ return handleBot();
38
+ case "help":
39
+ case "--help":
40
+ case "-h":
41
+ return printHelp();
42
+ default:
43
+ console.error(`Unknown command: ${command}`);
44
+ printHelp();
45
+ process.exit(1);
46
+ }
47
+ }
48
+ // ─── PM2 Service Commands ───────────────────────────────────────────────────
49
+ function handleStart() {
50
+ const result = pm2Start();
51
+ logger.info(result);
52
+ }
53
+ function handleStop() {
54
+ const result = pm2Stop();
55
+ logger.info(result);
56
+ }
57
+ function handleRestart() {
58
+ const result = pm2Restart();
59
+ logger.info(result);
60
+ }
61
+ function handleDelete() {
62
+ const result = pm2Delete();
63
+ logger.info(result);
64
+ }
65
+ function handleStatus() {
66
+ const status = pm2Status();
67
+ if (!status) {
68
+ logger.warn("Bot is not running");
69
+ return;
70
+ }
71
+ const uptimeStr = status.uptime
72
+ ? `${Math.floor((Date.now() - status.uptime) / 1000)}s`
73
+ : "N/A";
74
+ const memStr = status.memory
75
+ ? `${(status.memory / (1024 * 1024)).toFixed(1)}MB`
76
+ : "N/A";
77
+ console.log(`
78
+ Name: ${status.name}
79
+ Status: ${status.status}
80
+ PID: ${status.pid ?? "N/A"}
81
+ Uptime: ${uptimeStr}
82
+ Memory: ${memStr}
83
+ Restarts: ${status.restarts}
84
+ `);
85
+ }
86
+ function handleLogs(args) {
87
+ const lines = args[0] ? Number(args[0]) : 50;
88
+ const logs = pm2Logs(lines);
89
+ console.log(logs);
90
+ }
91
+ // ─── Direct Bot Execution ───────────────────────────────────────────────────
92
+ async function handleBot() {
93
+ const config = loadTelegramConfig();
94
+ const bot = createBot(config);
95
+ logger.info("Starting Telegram bot...");
96
+ logger.info(`Allowed chat IDs: ${config.allowedChatIds.join(", ")}`);
97
+ // Graceful shutdown
98
+ const shutdown = () => {
99
+ logger.info("Shutting down bot...");
100
+ bot.stop();
101
+ process.exit(0);
102
+ };
103
+ process.on("SIGINT", shutdown);
104
+ process.on("SIGTERM", shutdown);
105
+ await bot.start({
106
+ onStart: () => {
107
+ logger.info("Bot is running. Send /help in Telegram to get started.");
108
+ },
109
+ });
110
+ }
111
+ // ─── Help ───────────────────────────────────────────────────────────────────
112
+ function printHelp() {
113
+ console.log(`
114
+ locus-telegram — Remote-control Locus via Telegram
115
+
116
+ Usage:
117
+ locus pkg telegram <command>
118
+
119
+ Commands:
120
+ start Start the bot via PM2 (background)
121
+ stop Stop the bot
122
+ restart Restart the bot
123
+ delete Remove the bot from PM2
124
+ status Show bot process status
125
+ logs [n] Show last n lines of logs (default: 50)
126
+ bot Run the bot directly (foreground)
127
+ help Show this help message
128
+
129
+ Environment Variables:
130
+ LOCUS_TELEGRAM_BOT_TOKEN Telegram Bot API token (from @BotFather)
131
+ LOCUS_TELEGRAM_CHAT_ID Comma-separated list of allowed chat IDs
132
+
133
+ Examples:
134
+ # Set up environment
135
+ export LOCUS_TELEGRAM_BOT_TOKEN="123456:ABC-DEF..."
136
+ export LOCUS_TELEGRAM_CHAT_ID="12345678"
137
+
138
+ # Start in background
139
+ locus pkg telegram start
140
+
141
+ # Run in foreground (for development)
142
+ locus pkg telegram bot
143
+ `);
144
+ }
package/dist/pm2.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * PM2 programmatic wrapper for managing the Telegram bot process.
3
+ *
4
+ * Uses pm2 package API to start/stop/restart/status/logs the bot.
5
+ */
6
+ export declare function pm2Start(): string;
7
+ export declare function pm2Stop(): string;
8
+ export declare function pm2Restart(): string;
9
+ export declare function pm2Delete(): string;
10
+ export interface Pm2Status {
11
+ name: string;
12
+ status: string;
13
+ pid: number | null;
14
+ uptime: number | null;
15
+ memory: number | null;
16
+ restarts: number;
17
+ }
18
+ export declare function pm2Status(): Pm2Status | null;
19
+ export declare function pm2Logs(lines?: number): string;
package/dist/pm2.js ADDED
@@ -0,0 +1,112 @@
1
+ /**
2
+ * PM2 programmatic wrapper for managing the Telegram bot process.
3
+ *
4
+ * Uses pm2 package API to start/stop/restart/status/logs the bot.
5
+ */
6
+ import { execSync } from "node:child_process";
7
+ import { fileURLToPath } from "node:url";
8
+ import { dirname, join } from "node:path";
9
+ const PROCESS_NAME = "locus-telegram";
10
+ function getPm2Bin() {
11
+ // Try local node_modules first, then global
12
+ try {
13
+ const result = execSync("which pm2", {
14
+ encoding: "utf-8",
15
+ stdio: ["pipe", "pipe", "pipe"],
16
+ }).trim();
17
+ if (result)
18
+ return result;
19
+ }
20
+ catch {
21
+ // fall through
22
+ }
23
+ // Try npx as fallback
24
+ return "npx pm2";
25
+ }
26
+ function pm2Exec(args) {
27
+ const pm2 = getPm2Bin();
28
+ try {
29
+ return execSync(`${pm2} ${args}`, {
30
+ encoding: "utf-8",
31
+ stdio: ["pipe", "pipe", "pipe"],
32
+ env: process.env,
33
+ });
34
+ }
35
+ catch (error) {
36
+ const err = error;
37
+ throw new Error(err.stderr?.trim() || err.message || "PM2 command failed");
38
+ }
39
+ }
40
+ function getBotScriptPath() {
41
+ const currentFile = fileURLToPath(import.meta.url);
42
+ const packageRoot = dirname(dirname(currentFile));
43
+ return join(packageRoot, "bin", "locus-telegram.js");
44
+ }
45
+ export function pm2Start() {
46
+ const script = getBotScriptPath();
47
+ const pm2 = getPm2Bin();
48
+ // Pass env vars through to the PM2 process
49
+ const envArgs = [];
50
+ if (process.env.LOCUS_TELEGRAM_BOT_TOKEN) {
51
+ envArgs.push("--env", `LOCUS_TELEGRAM_BOT_TOKEN=${process.env.LOCUS_TELEGRAM_BOT_TOKEN}`);
52
+ }
53
+ try {
54
+ // Check if already running
55
+ const list = pm2Exec("jlist");
56
+ const processes = JSON.parse(list);
57
+ const existing = processes.find((p) => p.name === PROCESS_NAME);
58
+ if (existing) {
59
+ pm2Exec(`restart ${PROCESS_NAME}`);
60
+ return `Restarted ${PROCESS_NAME}`;
61
+ }
62
+ }
63
+ catch {
64
+ // Not running, start fresh
65
+ }
66
+ execSync(`${pm2} start ${JSON.stringify(script)} --name ${PROCESS_NAME} -- bot`, {
67
+ encoding: "utf-8",
68
+ stdio: "inherit",
69
+ env: process.env,
70
+ });
71
+ return `Started ${PROCESS_NAME}`;
72
+ }
73
+ export function pm2Stop() {
74
+ pm2Exec(`stop ${PROCESS_NAME}`);
75
+ return `Stopped ${PROCESS_NAME}`;
76
+ }
77
+ export function pm2Restart() {
78
+ pm2Exec(`restart ${PROCESS_NAME}`);
79
+ return `Restarted ${PROCESS_NAME}`;
80
+ }
81
+ export function pm2Delete() {
82
+ pm2Exec(`delete ${PROCESS_NAME}`);
83
+ return `Deleted ${PROCESS_NAME}`;
84
+ }
85
+ export function pm2Status() {
86
+ try {
87
+ const list = pm2Exec("jlist");
88
+ const processes = JSON.parse(list);
89
+ const proc = processes.find((p) => p.name === PROCESS_NAME);
90
+ if (!proc)
91
+ return null;
92
+ return {
93
+ name: PROCESS_NAME,
94
+ status: proc.pm2_env?.status ?? "unknown",
95
+ pid: proc.pid ?? null,
96
+ uptime: proc.pm2_env?.pm_uptime ?? null,
97
+ memory: proc.monit?.memory ?? null,
98
+ restarts: proc.pm2_env?.restart_time ?? 0,
99
+ };
100
+ }
101
+ catch {
102
+ return null;
103
+ }
104
+ }
105
+ export function pm2Logs(lines = 50) {
106
+ try {
107
+ return pm2Exec(`logs ${PROCESS_NAME} --nostream --lines ${lines}`);
108
+ }
109
+ catch {
110
+ return "No logs available.";
111
+ }
112
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Output formatting utilities for Telegram messages.
3
+ *
4
+ * Telegram supports a subset of Markdown (MarkdownV2) and HTML.
5
+ * We use HTML mode for reliability — fewer escaping issues than MarkdownV2.
6
+ */
7
+ /** Escape HTML special characters for Telegram HTML mode. */
8
+ export declare function escapeHtml(text: string): string;
9
+ /** Wrap text in a code block. */
10
+ export declare function codeBlock(text: string, language?: string): string;
11
+ /** Wrap text in inline code. */
12
+ export declare function inlineCode(text: string): string;
13
+ /** Bold text. */
14
+ export declare function bold(text: string): string;
15
+ /** Italic text. */
16
+ export declare function italic(text: string): string;
17
+ /** Truncate text to a max length, appending "..." if truncated. */
18
+ export declare function truncate(text: string, maxLength?: number): string;
19
+ /** Split long output into multiple messages if needed. */
20
+ export declare function splitMessage(text: string): string[];
21
+ /** Format a command result as a Telegram message. */
22
+ export declare function formatCommandResult(command: string, output: string, exitCode: number): string;
23
+ /** Format a streaming progress message. */
24
+ export declare function formatStreamingMessage(command: string, output: string, isComplete: boolean): string;
25
+ /** Format an error message. */
26
+ export declare function formatError(message: string, detail?: string): string;
27
+ /** Format a success message. */
28
+ export declare function formatSuccess(message: string): string;
29
+ /** Format an info message. */
30
+ export declare function formatInfo(message: string): string;