From 92c28a956de32c631612c5550a0f1677f07bdd77 Mon Sep 17 00:00:00 2001 From: gavrielc Date: Wed, 22 Apr 2026 00:11:35 +0300 Subject: [PATCH] feat(setup): run init-first-agent after Telegram pairing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pair-telegram only identifies the chat and operator — it returns PLATFORM_ID and ADMIN_USER_ID but doesn't create the agent group, grant owner, or send the welcome. scripts/init-first-agent.ts does that, matching the pattern the /new-setup skill already uses for channel wiring. Also prompts for the agent's own name (default: Nano), overridable via NANOCLAW_AGENT_NAME. displayName is hoisted out of the cli-agent block so both cli-agent and channel wiring share the value. Co-Authored-By: Claude Opus 4.7 (1M context) --- setup/auto.ts | 75 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 68 insertions(+), 7 deletions(-) diff --git a/setup/auto.ts b/setup/auto.ts index 12a3070..e76d9cf 100644 --- a/setup/auto.ts +++ b/setup/auto.ts @@ -7,9 +7,11 @@ * module check). This driver picks up from there. * * Config via env: - * NANOCLAW_DISPLAY_NAME operator name for the CLI agent — skips the - * interactive prompt before cli-agent. If unset, - * the driver asks, defaulting to $USER. + * NANOCLAW_DISPLAY_NAME how the agents address the operator — skips the + * prompt. Defaults to $USER. + * NANOCLAW_AGENT_NAME name for the messaging-channel agent (Telegram, + * etc.) — skips the prompt. Defaults to "Nano". + * (The CLI scratch agent is always "Terminal Agent".) * NANOCLAW_SKIP comma-separated step names to skip * (environment|container|onecli|auth|mounts| * service|cli-agent|channel|verify) @@ -28,6 +30,7 @@ import { spawn, spawnSync } from 'child_process'; import { createInterface } from 'readline/promises'; const CLI_AGENT_NAME = 'Terminal Agent'; +const DEFAULT_AGENT_NAME = 'Nano'; type Fields = Record; type StepResult = { ok: boolean; fields: Fields; exitCode: number }; @@ -130,6 +133,18 @@ async function askDisplayName(fallback: string): Promise { } } +async function askAgentName(fallback: string): Promise { + const rl = createInterface({ input: process.stdin, output: process.stdout }); + try { + const answer = await rl.question( + `\nWhat should your agent be called? [${fallback}]: `, + ); + return answer.trim() || fallback; + } finally { + rl.close(); + } +} + async function askChannelChoice(): Promise<'telegram' | 'skip'> { const rl = createInterface({ input: process.stdin, output: process.stdout }); try { @@ -150,6 +165,15 @@ function runBashScript(relPath: string): Promise { }); } +function runTsxScript(relPath: string, args: string[] = []): Promise { + return new Promise((resolve) => { + const child = spawn('pnpm', ['exec', 'tsx', relPath, ...args], { + stdio: 'inherit', + }); + child.on('close', (code) => resolve(code ?? 1)); + }); +} + function fail(msg: string, hint?: string): never { console.error(`\n[setup:auto] ${msg}`); if (hint) console.error(` ${hint}`); @@ -251,21 +275,26 @@ async function main(): Promise { } } - if (!skip.has('cli-agent')) { + // Resolved once, reused by cli-agent + channel wiring. + let displayName: string | undefined; + const needsDisplayName = !skip.has('cli-agent') || !skip.has('channel'); + if (needsDisplayName) { const fallback = process.env.USER?.trim() || 'Operator'; const preset = process.env.NANOCLAW_DISPLAY_NAME?.trim(); - const displayName = preset || (await askDisplayName(fallback)); + displayName = preset || (await askDisplayName(fallback)); + } + if (!skip.has('cli-agent')) { const res = await runStep('cli-agent', [ '--display-name', - displayName, + displayName!, '--agent-name', CLI_AGENT_NAME, ]); if (!res.ok) { fail( 'CLI agent wiring failed', - `Re-run \`pnpm exec tsx scripts/init-cli-agent.ts --display-name "${displayName}" --agent-name "${CLI_AGENT_NAME}"\` to fix.`, + `Re-run \`pnpm exec tsx scripts/init-cli-agent.ts --display-name "${displayName!}" --agent-name "${CLI_AGENT_NAME}"\` to fix.`, ); } } @@ -295,6 +324,38 @@ async function main(): Promise { 'Re-run `pnpm exec tsx setup/index.ts --step pair-telegram -- --intent main`.', ); } + + const platformId = pair.fields.PLATFORM_ID; + const adminUserId = pair.fields.ADMIN_USER_ID; + if (!platformId || !adminUserId) { + fail( + 'pair-telegram succeeded but did not return PLATFORM_ID and ADMIN_USER_ID.', + 'Re-run `pnpm exec tsx setup/index.ts --step pair-telegram -- --intent main` and capture the success block.', + ); + } + + const agentName = + process.env.NANOCLAW_AGENT_NAME?.trim() || + (await askAgentName(DEFAULT_AGENT_NAME)); + + console.log('\n── wiring first agent ──────────────────────────'); + const initCode = await runTsxScript('scripts/init-first-agent.ts', [ + '--channel', 'telegram', + '--user-id', adminUserId, + '--platform-id', platformId, + '--display-name', displayName!, + '--agent-name', agentName, + ]); + 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}"\`.`, + ); + } + + console.log( + `\n[setup:auto] Telegram is wired. ${agentName} will DM you a welcome shortly.`, + ); } }