refactor(setup): own pair-telegram.ts in this branch with clean output

Previously setup:auto parsed pair-telegram's machine-readable status
blocks and rendered a banner on top. Fork the script instead: check
in setup/pair-telegram.ts with a focused 4-digit banner, a short
wrong-attempt line, and a single final PAIR_TELEGRAM status block
(kept so the parent driver still picks up PLATFORM_ID and
PAIRED_USER_ID via parseStatus).

Drop pair-telegram.ts from add-telegram.sh's copy list so the local
version isn't overwritten on re-runs. The other adapter files
(telegram.ts, telegram-pairing.ts, etc.) still come from the channels
branch.

Also fix a latent bug: auto.ts was reading ADMIN_USER_ID from the
success block, but the actual field name is PAIRED_USER_ID —
init-first-agent would have been called with --user-id "".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gavrielc
2026-04-22 00:27:43 +03:00
parent 356a4d0a9f
commit e24ecbf8b0
3 changed files with 133 additions and 122 deletions

View File

@@ -81,119 +81,6 @@ function runStep(name: string, extra: string[] = []): Promise<StepResult> {
});
}
/**
* Variant of runStep for `pair-telegram`. The step emits machine-readable
* status blocks (PAIR_TELEGRAM_ISSUED, PAIR_TELEGRAM_ATTEMPT, etc.) meant
* for the /setup skill to parse and relay. Running it directly leaves the
* operator staring at noisy blocks — this filters them and renders a
* focused banner around the 4-digit code instead.
*/
function runPairTelegram(intent: string): Promise<StepResult> {
return new Promise((resolve) => {
console.log('\n── pair-telegram ───────────────────────────────');
const args = [
'exec', 'tsx', 'setup/index.ts',
'--step', 'pair-telegram',
'--', '--intent', intent,
];
const child = spawn('pnpm', args, { stdio: ['inherit', 'pipe', 'inherit'] });
let buf = '';
let partial = '';
let inBlock = false;
let blockType = '';
let blockFields: Record<string, string> = {};
function handleLine(line: string): void {
if (line.startsWith('=== NANOCLAW SETUP:')) {
inBlock = true;
blockType = line.replace('=== NANOCLAW SETUP:', '').replace('===', '').trim();
blockFields = {};
return;
}
if (line.startsWith('=== END ===')) {
inBlock = false;
renderBlock(blockType, blockFields);
return;
}
if (inBlock) {
const idx = line.indexOf(':');
if (idx > -1) {
blockFields[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
}
return;
}
process.stdout.write(line + '\n');
}
function renderBlock(type: string, fields: Record<string, string>): void {
switch (type) {
case 'PAIR_TELEGRAM_ISSUED':
printCodeBanner(fields.CODE ?? '????');
break;
case 'PAIR_TELEGRAM_NEW_CODE':
console.log('\n Previous code invalidated. New code:');
printCodeBanner(fields.CODE ?? '????');
break;
case 'PAIR_TELEGRAM_ATTEMPT':
console.log(
` Got "${fields.RECEIVED_CODE ?? '?'}" — doesn't match. A new code is on its way.`,
);
break;
case 'PAIR_TELEGRAM':
if (fields.STATUS === 'success') {
console.log('\n ✓ Telegram paired.');
} else if (fields.STATUS === 'failed') {
console.log(`\n ✗ Pairing failed: ${fields.ERROR ?? 'unknown'}`);
}
break;
default: {
// Forward unknown blocks verbatim (forward-compat).
const lines = [`=== NANOCLAW SETUP: ${type} ===`];
for (const [k, v] of Object.entries(fields)) lines.push(`${k}: ${v}`);
lines.push('=== END ===');
process.stdout.write(lines.join('\n') + '\n');
}
}
}
child.stdout.on('data', (chunk: Buffer) => {
const s = chunk.toString('utf-8');
buf += s;
partial += s;
const lines = partial.split('\n');
partial = lines.pop() ?? '';
for (const line of lines) handleLine(line);
});
child.on('close', (code) => {
if (partial) handleLine(partial);
const fields = parseStatus(buf);
resolve({
ok: code === 0 && fields.STATUS === 'success',
fields,
exitCode: code ?? 1,
});
});
});
}
function printCodeBanner(code: string): void {
// Double-space between digits for readability in a 4-digit code.
const digits = code.trim().split('').join(' ');
const content = [
'',
` PAIRING CODE: ${digits}`,
'',
' Send these digits from Telegram to your bot.',
'',
];
const width = Math.max(...content.map((l) => l.length));
const top = ' ╔' + '═'.repeat(width + 2) + '╗';
const bot = ' ╚' + '═'.repeat(width + 2) + '╝';
const mid = content.map((l) => ' ║ ' + l.padEnd(width) + ' ║');
console.log(['', top, ...mid, bot, ''].join('\n'));
}
/**
* After installing Docker, this process's supplementary groups are still
* frozen from login — subsequent steps that talk to /var/run/docker.sock
@@ -423,7 +310,7 @@ async function main(): Promise<void> {
);
}
const pair = await runPairTelegram('main');
const pair = await runStep('pair-telegram', ['--intent', 'main']);
if (!pair.ok) {
fail(
'Telegram pairing failed.',
@@ -432,10 +319,10 @@ async function main(): Promise<void> {
}
const platformId = pair.fields.PLATFORM_ID;
const adminUserId = pair.fields.ADMIN_USER_ID;
if (!platformId || !adminUserId) {
const pairedUserId = pair.fields.PAIRED_USER_ID;
if (!platformId || !pairedUserId) {
fail(
'pair-telegram succeeded but did not return PLATFORM_ID and ADMIN_USER_ID.',
'pair-telegram succeeded but did not return PLATFORM_ID and PAIRED_USER_ID.',
'Re-run `pnpm exec tsx setup/index.ts --step pair-telegram -- --intent main` and capture the success block.',
);
}
@@ -447,7 +334,7 @@ async function main(): Promise<void> {
console.log('\n── wiring first agent ──────────────────────────');
const initCode = await runTsxScript('scripts/init-first-agent.ts', [
'--channel', 'telegram',
'--user-id', adminUserId,
'--user-id', pairedUserId,
'--platform-id', platformId,
'--display-name', displayName!,
'--agent-name', agentName,
@@ -455,7 +342,7 @@ async function main(): Promise<void> {
if (initCode !== 0) {
fail(
'Wiring the Telegram agent failed.',
`Re-run \`pnpm exec tsx scripts/init-first-agent.ts --channel telegram --user-id "${adminUserId}" --platform-id "${platformId}" --display-name "${displayName!}" --agent-name "${agentName}"\`.`,
`Re-run \`pnpm exec tsx scripts/init-first-agent.ts --channel telegram --user-id "${pairedUserId}" --platform-id "${platformId}" --display-name "${displayName!}" --agent-name "${agentName}"\`.`,
);
}