Files
Evandro Fonseca Junior d1d04001d0
Release / release (push) Failing after 5s
Validate / validate (push) Failing after 5m21s
chore: fork @llblab/[email protected] as @evandrodevbr/oh-my-pi-telegram
Fork of upstream llblab/[email protected].0 (SHA afe09c5) repackaged as a
drop-in extension for can1357/oh-my-pi (omp >= 16.5.2).

Changes vs upstream:
- Rename package: @llblab/pi-telegram -> @evandrodevbr/oh-my-pi-telegram
- Bump version: 0.22.0 -> 0.22.0-evandro.1
- Update repository/homepage/bugs URLs to evandrodevbr
- Add omp field in package.json (displayName + compatibleWith)
- README: prepend 'Oh My Pi (omp) Compatibility' section with install
  commands, verified-components table, upstream lineage, and rationale
- CHANGELOG: add '0.22.0-evandro.1' entry under Unreleased
- docs/: rename @llblab/pi-telegram imports to @evandrodevbr/oh-my-pi-telegram
- tests/public-api.test.ts: rename package import paths

Gates re-run on the fork:
- npm run typecheck  -> tsc --noEmit (success)
- npm test           -> 1229 pass / 0 fail / 1 platform-only skip
- npm run audit      -> found 0 vulnerabilities
- npm run pack:check -> evandrodevbr-oh-my-pi-telegram-0.22.0-evandro.1.tgz

Verified:
- omp install --link ./pi-telegram -> Linked OK
- omp plugin list -> @evandrodevbr/[email protected].0-evandro.1

Upstream lineage: badlogic/pi-telegram -> llblab/pi-telegram (0.22.0) -> evandrodevbr/oh-my-pi-telegram (this fork)
2026-07-15 14:18:16 -03:00

159 lines
4.7 KiB
TypeScript

/**
* Pi prompt-template bridge helpers
* Zones: pi agent prompts, telegram controls, filesystem
* Discovers Pi prompt-template slash commands and expands them before Telegram queue dispatch
*/
import { readFileSync } from "node:fs";
import type { PiSlashCommandInfo } from "./pi.ts";
export interface TelegramPromptTemplateCommand {
command: string;
description?: string;
path: string;
}
export type TelegramPromptTemplateReader = (path: string) => string;
const TELEGRAM_BOT_COMMAND_NAME_PATTERN = /^[a-z0-9_]{1,32}$/;
function stripPromptTemplateFrontmatter(content: string): string {
if (!content.startsWith("---")) return content;
const lines = content.split("\n");
if (lines[0]?.trim() !== "---") return content;
for (let index = 1; index < lines.length; index += 1) {
if (lines[index]?.trim() === "---")
return lines.slice(index + 1).join("\n");
}
return content;
}
export function parsePromptTemplateArgs(argsString: string): string[] {
const args: string[] = [];
let current = "";
let quote: string | undefined;
for (const char of argsString) {
if (quote) {
if (char === quote) {
quote = undefined;
} else {
current += char;
}
continue;
}
if (char === '"' || char === "'") {
quote = char;
continue;
}
if (char === " " || char === "\t") {
if (current) args.push(current);
current = "";
continue;
}
current += char;
}
if (current) args.push(current);
return args;
}
export function substitutePromptTemplateArgs(
content: string,
args: readonly string[],
): string {
let result = content.replace(/\$(\d+)/g, (_, num: string) => {
const index = Number.parseInt(num, 10) - 1;
return args[index] ?? "";
});
result = result.replace(
/\$\{@:(\d+)(?::(\d+))?\}/g,
(_, startValue: string, lengthValue: string | undefined) => {
const start = Math.max(Number.parseInt(startValue, 10) - 1, 0);
if (lengthValue) {
const length = Number.parseInt(lengthValue, 10);
return args.slice(start, start + length).join(" ");
}
return args.slice(start).join(" ");
},
);
const allArgs = args.join(" ");
return result.replace(/\$ARGUMENTS/g, allArgs).replace(/\$@/g, allArgs);
}
export function isTelegramPromptTemplateCommandName(name: string): boolean {
return TELEGRAM_BOT_COMMAND_NAME_PATTERN.test(name);
}
export function mapPiPromptTemplateNameToTelegramCommandName(
name: string,
): string | undefined {
const command = name
.toLowerCase()
.replace(/[^a-z0-9_]+/g, "_")
.replace(/_+/g, "_")
.replace(/^_+|_+$/g, "")
.slice(0, 32)
.replace(/_+$/g, "");
return isTelegramPromptTemplateCommandName(command) ? command : undefined;
}
export interface TelegramPromptTemplateCommandGetterDeps {
getCommands: () => readonly PiSlashCommandInfo[];
reservedCommandNames?: readonly string[];
getReservedCommandNames?: () => readonly string[];
}
export function getTelegramPromptTemplateCommands(
commands: readonly PiSlashCommandInfo[],
reservedNames: ReadonlySet<string> = new Set(),
): TelegramPromptTemplateCommand[] {
const seen = new Set<string>();
const promptCommands: TelegramPromptTemplateCommand[] = [];
for (const command of commands) {
if (command.source !== "prompt") continue;
const telegramCommand = mapPiPromptTemplateNameToTelegramCommandName(
command.name,
);
if (!telegramCommand) continue;
if (reservedNames.has(telegramCommand)) continue;
if (seen.has(telegramCommand)) continue;
const sourcePath = command.sourceInfo?.path;
if (!sourcePath) continue;
seen.add(telegramCommand);
promptCommands.push({
command: telegramCommand,
description: command.description,
path: sourcePath,
});
}
return promptCommands.sort((a, b) => a.command.localeCompare(b.command));
}
export function createTelegramPromptTemplateCommandGetter(
deps: TelegramPromptTemplateCommandGetterDeps,
): () => TelegramPromptTemplateCommand[] {
return () => {
return getTelegramPromptTemplateCommands(
deps.getCommands(),
new Set([
...(deps.reservedCommandNames ?? []),
...(deps.getReservedCommandNames?.() ?? []),
]),
);
};
}
export function expandTelegramPromptTemplateCommand(
commandName: string,
args: string,
commands: readonly TelegramPromptTemplateCommand[],
readTemplate: TelegramPromptTemplateReader = (path) =>
readFileSync(path, "utf-8"),
): string | undefined {
const command = commands.find(
(candidate) => candidate.command === commandName,
);
if (!command) return undefined;
const content = stripPromptTemplateFrontmatter(readTemplate(command.path));
return substitutePromptTemplateArgs(content, parsePromptTemplateArgs(args));
}