feat(setup): run init-first-agent after Telegram pairing

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) <noreply@anthropic.com>
This commit is contained in:
gavrielc
2026-04-22 00:11:35 +03:00
parent 9c7e1d02af
commit 92c28a956d

View File

@@ -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<string, string>;
type StepResult = { ok: boolean; fields: Fields; exitCode: number };
@@ -130,6 +133,18 @@ async function askDisplayName(fallback: string): Promise<string> {
}
}
async function askAgentName(fallback: string): Promise<string> {
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<number> {
});
}
function runTsxScript(relPath: string, args: string[] = []): Promise<number> {
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<void> {
}
}
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<void> {
'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.`,
);
}
}