Files
nanoclaw/setup/index.ts
T
Koshkoshinsk 2017589683 feat(telegram): self-contained pairing for chat ownership verification
BotFather issues bot tokens with no user binding, so anyone who guesses the
bot's username can DM it and get registered as a channel. Pairing closes that
gap: setup issues a one-time 4-digit code, the operator echoes it back from
the chat they want to register, and the inbound interceptor binds
admin_user_id before the message reaches the router.

- src/channels/telegram-pairing.ts: JSON-backed store with createPairing,
  tryConsume, getStatus, waitForPairing (fs.watch + poll fallback)
- src/channels/telegram.ts: wraps bridge.setup with an onInbound interceptor
  that consumes pairing codes and upserts messaging_groups
- setup/pair-telegram.ts: CLI step issues a code and waits up to 5 min for
  the operator to echo it back, emitting PLATFORM_ID/IS_GROUP/ADMIN_USER_ID
- Skill docs: /setup reorders mounts -> service -> wire (pairing needs a
  live polling adapter); /manage-channels and /add-telegram-v2 use pairing
  instead of asking the user to discover chat IDs

All other channels still bind admin via install-time identity (OAuth/QR/token);
pairing is Telegram-only. The bridge, router, and other adapters are untouched.
2026-04-13 12:27:02 +00:00

61 lines
1.6 KiB
TypeScript

/**
* Setup CLI entry point.
* Usage: npx tsx setup/index.ts --step <name> [args...]
*/
import { log } from '../src/log.js';
import { emitStatus } from './status.js';
const STEPS: Record<
string,
() => Promise<{ run: (args: string[]) => Promise<void> }>
> = {
timezone: () => import('./timezone.js'),
environment: () => import('./environment.js'),
container: () => import('./container.js'),
groups: () => import('./groups.js'),
register: () => import('./register.js'),
'pair-telegram': () => import('./pair-telegram.js'),
mounts: () => import('./mounts.js'),
service: () => import('./service.js'),
verify: () => import('./verify.js'),
};
async function main(): Promise<void> {
const args = process.argv.slice(2);
const stepIdx = args.indexOf('--step');
if (stepIdx === -1 || !args[stepIdx + 1]) {
console.error(
`Usage: npx tsx setup/index.ts --step <${Object.keys(STEPS).join('|')}> [args...]`,
);
process.exit(1);
}
const stepName = args[stepIdx + 1];
const stepArgs = args.filter(
(a, i) => i !== stepIdx && i !== stepIdx + 1 && a !== '--',
);
const loader = STEPS[stepName];
if (!loader) {
console.error(`Unknown step: ${stepName}`);
console.error(`Available steps: ${Object.keys(STEPS).join(', ')}`);
process.exit(1);
}
try {
const mod = await loader();
await mod.run(stepArgs);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
log.error('Setup step failed', { err, step: stepName });
emitStatus(stepName.toUpperCase(), {
STATUS: 'failed',
ERROR: message,
});
process.exit(1);
}
}
main();