Merge remote-tracking branch 'upstream/main' into fix/setup-reuse-existing-env
# Conflicts: # setup/channels/imessage.ts # setup/channels/telegram.ts
This commit is contained in:
@@ -31,6 +31,7 @@ import { brightSelect } from '../lib/bright-select.js';
|
||||
import { confirmThenOpen } from '../lib/browser.js';
|
||||
import { askOperatorRole } from '../lib/role-prompt.js';
|
||||
import { ensureAnswer, fail, runQuietChild } from '../lib/runner.js';
|
||||
import { note } from '../lib/theme.js';
|
||||
|
||||
const DEFAULT_AGENT_NAME = 'Nano';
|
||||
const DISCORD_API = 'https://discord.com/api/v10';
|
||||
@@ -155,7 +156,7 @@ async function askHasBotToken(): Promise<boolean> {
|
||||
|
||||
async function walkThroughBotCreation(): Promise<void> {
|
||||
const url = 'https://discord.com/developers/applications';
|
||||
p.note(
|
||||
note(
|
||||
[
|
||||
"You'll create a Discord bot in the Developer Portal. It's free and takes about a minute.",
|
||||
'',
|
||||
@@ -184,7 +185,7 @@ function showTokenLocationReminder(hasExistingBot: boolean): void {
|
||||
// to find it — tokens in the Dev Portal aren't visible after first reveal,
|
||||
// and "Reset Token" issues a new one.
|
||||
if (hasExistingBot) {
|
||||
p.note(
|
||||
note(
|
||||
[
|
||||
"Where to find your bot token:",
|
||||
'',
|
||||
@@ -216,7 +217,7 @@ async function walkThroughServerCreation(): Promise<void> {
|
||||
// the web client and rely on the + button being visible. The steps below
|
||||
// are the same whether they're in the desktop app or the browser.
|
||||
const url = 'https://discord.com/channels/@me';
|
||||
p.note(
|
||||
note(
|
||||
[
|
||||
"A Discord server is just a private space for you and the bot. Free and takes 30 seconds.",
|
||||
'',
|
||||
@@ -254,6 +255,7 @@ async function collectDiscordToken(): Promise<string> {
|
||||
const answer = ensureAnswer(
|
||||
await p.password({
|
||||
message: 'Paste your bot token',
|
||||
clearOnError: true,
|
||||
validate: (v) => {
|
||||
const t = (v ?? '').trim();
|
||||
if (!t) return 'Token is required';
|
||||
@@ -404,7 +406,7 @@ async function resolveOwnerUserId(
|
||||
}
|
||||
|
||||
async function promptForUserIdWithDevMode(): Promise<string> {
|
||||
p.note(
|
||||
note(
|
||||
[
|
||||
"To get your Discord user ID:",
|
||||
'',
|
||||
@@ -442,7 +444,7 @@ async function promptInviteBot(
|
||||
`&scope=bot` +
|
||||
`&permissions=${INVITE_PERMISSIONS}`;
|
||||
|
||||
p.note(
|
||||
note(
|
||||
[
|
||||
`@${botUsername} needs to share a server with you before it can DM you.`,
|
||||
'',
|
||||
|
||||
@@ -36,7 +36,7 @@ import * as setupLog from '../logs.js';
|
||||
import { brightSelect } from '../lib/bright-select.js';
|
||||
import { askOperatorRole } from '../lib/role-prompt.js';
|
||||
import { ensureAnswer, fail, runQuietChild } from '../lib/runner.js';
|
||||
import { wrapForGutter } from '../lib/theme.js';
|
||||
import { note, wrapForGutter } from '../lib/theme.js';
|
||||
|
||||
const DEFAULT_AGENT_NAME = 'Nano';
|
||||
|
||||
@@ -189,7 +189,7 @@ async function walkThroughFullDiskAccess(): Promise<void> {
|
||||
}
|
||||
const nodeDir = path.dirname(nodePath);
|
||||
|
||||
p.note(
|
||||
note(
|
||||
wrapForGutter(
|
||||
[
|
||||
`iMessage needs Full Disk Access granted to the Node binary:`,
|
||||
@@ -235,7 +235,7 @@ async function collectRemoteCreds(): Promise<RemoteCreds> {
|
||||
}
|
||||
}
|
||||
|
||||
p.note(
|
||||
note(
|
||||
[
|
||||
"Photon is a separate service that owns an iMessage account and",
|
||||
"exposes it over HTTP. NanoClaw will talk to it via its API.",
|
||||
@@ -263,6 +263,7 @@ async function collectRemoteCreds(): Promise<RemoteCreds> {
|
||||
const keyAnswer = ensureAnswer(
|
||||
await p.password({
|
||||
message: 'Photon API key',
|
||||
clearOnError: true,
|
||||
validate: (v) => ((v ?? '').trim() ? undefined : 'API key is required'),
|
||||
}),
|
||||
);
|
||||
@@ -277,7 +278,7 @@ async function collectRemoteCreds(): Promise<RemoteCreds> {
|
||||
}
|
||||
|
||||
async function askOperatorHandle(): Promise<string> {
|
||||
p.note(
|
||||
note(
|
||||
[
|
||||
"What phone number or email do you iMessage with?",
|
||||
"That's where your assistant will send its welcome message.",
|
||||
|
||||
@@ -44,6 +44,7 @@ import {
|
||||
writeStepEntry,
|
||||
} from '../lib/runner.js';
|
||||
import { askOperatorRole } from '../lib/role-prompt.js';
|
||||
import { note } from '../lib/theme.js';
|
||||
|
||||
const DEFAULT_AGENT_NAME = 'Nano';
|
||||
|
||||
@@ -139,7 +140,7 @@ async function ensureSignalCli(): Promise<void> {
|
||||
if (!probe.error && probe.status === 0) return;
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
p.note(
|
||||
note(
|
||||
[
|
||||
"NanoClaw talks to Signal through signal-cli, which isn't installed yet.",
|
||||
'',
|
||||
@@ -152,7 +153,7 @@ async function ensureSignalCli(): Promise<void> {
|
||||
'signal-cli not found',
|
||||
);
|
||||
} else {
|
||||
p.note(
|
||||
note(
|
||||
[
|
||||
"NanoClaw talks to Signal through signal-cli, which isn't installed yet.",
|
||||
'',
|
||||
|
||||
@@ -28,7 +28,7 @@ import * as setupLog from '../logs.js';
|
||||
import { confirmThenOpen } from '../lib/browser.js';
|
||||
import { askOperatorRole } from '../lib/role-prompt.js';
|
||||
import { ensureAnswer, fail, runQuietChild } from '../lib/runner.js';
|
||||
import { wrapForGutter } from '../lib/theme.js';
|
||||
import { note, wrapForGutter } from '../lib/theme.js';
|
||||
|
||||
const SLACK_API = 'https://slack.com/api';
|
||||
const SLACK_APPS_URL = 'https://api.slack.com/apps';
|
||||
@@ -121,7 +121,7 @@ export async function runSlackChannel(displayName: string): Promise<void> {
|
||||
}
|
||||
|
||||
async function walkThroughAppCreation(): Promise<void> {
|
||||
p.note(
|
||||
note(
|
||||
[
|
||||
"You'll create a Slack app that the assistant talks through.",
|
||||
"Free and stays inside the workspaces you pick.",
|
||||
@@ -166,6 +166,7 @@ async function collectBotToken(): Promise<string> {
|
||||
const answer = ensureAnswer(
|
||||
await p.password({
|
||||
message: 'Paste your Slack bot token',
|
||||
clearOnError: true,
|
||||
validate: (v) => {
|
||||
const t = (v ?? '').trim();
|
||||
if (!t) return 'Token is required';
|
||||
@@ -199,6 +200,7 @@ async function collectSigningSecret(): Promise<string> {
|
||||
const answer = ensureAnswer(
|
||||
await p.password({
|
||||
message: 'Paste your Slack signing secret',
|
||||
clearOnError: true,
|
||||
validate: (v) => {
|
||||
const t = (v ?? '').trim();
|
||||
if (!t) return 'Signing secret is required';
|
||||
@@ -286,7 +288,7 @@ async function validateSlackToken(token: string): Promise<WorkspaceInfo> {
|
||||
}
|
||||
|
||||
async function collectSlackUserId(): Promise<string> {
|
||||
p.note(
|
||||
note(
|
||||
[
|
||||
"To get your Slack member ID:",
|
||||
'',
|
||||
@@ -391,7 +393,7 @@ async function resolveAgentName(): Promise<string> {
|
||||
}
|
||||
|
||||
function showPostInstallChecklist(info: WorkspaceInfo): void {
|
||||
p.note(
|
||||
note(
|
||||
wrapForGutter(
|
||||
[
|
||||
`Your agent is wired to Slack and a welcome DM is on its way.`,
|
||||
|
||||
@@ -40,6 +40,7 @@ import {
|
||||
} from '../lib/claude-handoff.js';
|
||||
import { ensureAnswer, fail, runQuietChild } from '../lib/runner.js';
|
||||
import { buildTeamsAppPackage } from '../lib/teams-manifest.js';
|
||||
import { note } from '../lib/theme.js';
|
||||
import * as setupLog from '../logs.js';
|
||||
|
||||
const CHANNEL = 'teams';
|
||||
@@ -101,7 +102,7 @@ export async function runTeamsChannel(_displayName: string): Promise<void> {
|
||||
// ─── step: intro / prereqs ──────────────────────────────────────────────
|
||||
|
||||
function printIntro(): void {
|
||||
p.note(
|
||||
note(
|
||||
[
|
||||
'Setting up Teams is more involved than the other channels — about',
|
||||
'7 steps across the Azure portal and Teams admin.',
|
||||
@@ -115,7 +116,7 @@ function printIntro(): void {
|
||||
}
|
||||
|
||||
async function confirmPrereqs(args: { collected: Collected; completed: string[] }): Promise<void> {
|
||||
p.note(
|
||||
note(
|
||||
[
|
||||
'Before we start, confirm you have:',
|
||||
'',
|
||||
@@ -141,7 +142,7 @@ async function confirmPrereqs(args: { collected: Collected; completed: string[]
|
||||
// ─── step: public URL ──────────────────────────────────────────────────
|
||||
|
||||
async function stepPublicUrl(args: { collected: Collected; completed: string[] }): Promise<void> {
|
||||
p.note(
|
||||
note(
|
||||
[
|
||||
"Azure Bot Service delivers messages to an HTTPS endpoint you",
|
||||
"control. The endpoint needs to reach this machine's webhook",
|
||||
@@ -197,7 +198,7 @@ async function stepAppRegistration(args: {
|
||||
collected: Collected;
|
||||
completed: string[];
|
||||
}): Promise<void> {
|
||||
p.note(
|
||||
note(
|
||||
[
|
||||
`1. In ${AZURE_PORTAL_URL}, search "App registrations" → "New registration"`,
|
||||
'2. Name it (e.g. "NanoClaw")',
|
||||
@@ -281,7 +282,7 @@ async function stepClientSecret(args: {
|
||||
collected: Collected;
|
||||
completed: string[];
|
||||
}): Promise<void> {
|
||||
p.note(
|
||||
note(
|
||||
[
|
||||
`1. In your app registration, open "Certificates & secrets"`,
|
||||
'2. Click "New client secret"',
|
||||
@@ -298,6 +299,7 @@ async function stepClientSecret(args: {
|
||||
const answer = ensureAnswer(
|
||||
await p.password({
|
||||
message: 'Paste the client secret Value',
|
||||
clearOnError: true,
|
||||
validate: validateWithHelpEscape((v) => {
|
||||
const t = (v ?? '').trim();
|
||||
if (!t) return 'Required';
|
||||
@@ -350,7 +352,7 @@ async function stepAzureBot(args: {
|
||||
` --appid ${args.collected.appId} \\\n` +
|
||||
` ${tenantFlag}--endpoint "${endpoint}"`;
|
||||
|
||||
p.note(
|
||||
note(
|
||||
[
|
||||
`In ${AZURE_PORTAL_URL}, search "Azure Bot" → Create.`,
|
||||
'',
|
||||
@@ -387,7 +389,7 @@ async function stepEnableTeamsChannel(args: {
|
||||
collected: Collected;
|
||||
completed: string[];
|
||||
}): Promise<void> {
|
||||
p.note(
|
||||
note(
|
||||
[
|
||||
'1. Open your Azure Bot resource → Channels',
|
||||
'2. Click Microsoft Teams → Accept terms → Apply',
|
||||
@@ -457,7 +459,7 @@ async function stepSideload(args: {
|
||||
completed: string[];
|
||||
zipPath: string;
|
||||
}): Promise<void> {
|
||||
p.note(
|
||||
note(
|
||||
[
|
||||
'1. Open Microsoft Teams',
|
||||
'2. Go to Apps → Manage your apps → Upload an app',
|
||||
@@ -523,7 +525,7 @@ async function finishWithHandoff(
|
||||
collected: Collected,
|
||||
completed: string[],
|
||||
): Promise<void> {
|
||||
p.note(
|
||||
note(
|
||||
[
|
||||
'The Teams adapter is live and the service is running.',
|
||||
'',
|
||||
@@ -552,7 +554,7 @@ async function finishWithHandoff(
|
||||
);
|
||||
|
||||
if (choice === 'self') {
|
||||
p.note(
|
||||
note(
|
||||
[
|
||||
' 1. Find your bot in Teams (search by name, or via the sideloaded',
|
||||
' app) and send it a message ("hi" is fine)',
|
||||
|
||||
@@ -33,7 +33,7 @@ import {
|
||||
spawnStep,
|
||||
writeStepEntry,
|
||||
} from '../lib/runner.js';
|
||||
import { brandBold } from '../lib/theme.js';
|
||||
import { brandBold, note } from '../lib/theme.js';
|
||||
|
||||
const DEFAULT_AGENT_NAME = 'Nano';
|
||||
|
||||
@@ -47,7 +47,7 @@ export async function runTelegramChannel(displayName: string): Promise<void> {
|
||||
// installed, or the bot's web profile if not. tg://resolve?domain= is
|
||||
// more direct but silently fails when the scheme isn't registered.
|
||||
const botUrl = `https://t.me/${botUsername}`;
|
||||
p.note(
|
||||
note(
|
||||
[
|
||||
`Opening @${botUsername} in Telegram so it's ready when the pairing code shows up.`,
|
||||
'',
|
||||
@@ -144,7 +144,7 @@ async function collectTelegramToken(): Promise<string> {
|
||||
}
|
||||
}
|
||||
|
||||
p.note(
|
||||
note(
|
||||
[
|
||||
"Your assistant talks to you through a Telegram bot you create.",
|
||||
"Here's how:",
|
||||
@@ -162,6 +162,7 @@ async function collectTelegramToken(): Promise<string> {
|
||||
const answer = ensureAnswer(
|
||||
await p.password({
|
||||
message: 'Paste your bot token',
|
||||
clearOnError: true,
|
||||
validate: (v) => {
|
||||
if (!v || !v.trim()) return "Token is required";
|
||||
if (!/^[0-9]+:[A-Za-z0-9_-]{35,}$/.test(v.trim())) {
|
||||
@@ -252,7 +253,7 @@ async function runPairTelegram(): Promise<
|
||||
} else {
|
||||
stopSpinner("Old code expired. Here's a fresh one.");
|
||||
}
|
||||
p.note(formatCodeCard(block.fields.CODE ?? '????'), 'Secret code');
|
||||
note(formatCodeCard(block.fields.CODE ?? '????'), 'Secret code');
|
||||
s.start('Waiting for you to send the code from Telegram…');
|
||||
spinnerActive = true;
|
||||
} else if (block.type === 'PAIR_TELEGRAM_ATTEMPT') {
|
||||
|
||||
@@ -46,7 +46,7 @@ import {
|
||||
writeStepEntry,
|
||||
} from '../lib/runner.js';
|
||||
import { askOperatorRole } from '../lib/role-prompt.js';
|
||||
import { brandBold } from '../lib/theme.js';
|
||||
import { brandBold, note } from '../lib/theme.js';
|
||||
|
||||
const DEFAULT_AGENT_NAME = 'Nano';
|
||||
const AUTH_CREDS_PATH = path.join(process.cwd(), 'store', 'auth', 'creds.json');
|
||||
@@ -171,7 +171,7 @@ async function askAuthMethod(): Promise<AuthMethod> {
|
||||
}
|
||||
|
||||
async function askPhoneNumber(): Promise<string> {
|
||||
p.note(
|
||||
note(
|
||||
[
|
||||
"Enter your phone number the way WhatsApp expects it:",
|
||||
'',
|
||||
@@ -249,7 +249,7 @@ async function runWhatsAppAuth(
|
||||
} else if (block.type === 'WHATSAPP_AUTH_PAIRING_CODE') {
|
||||
const code = block.fields.CODE ?? '????';
|
||||
stopSpinner('Your pairing code is ready.');
|
||||
p.note(formatPairingCard(code), 'Pairing code');
|
||||
note(formatPairingCard(code), 'Pairing code');
|
||||
s.start('Waiting for you to enter the code…');
|
||||
spinnerActive = true;
|
||||
} else if (block.type === 'WHATSAPP_AUTH') {
|
||||
@@ -395,7 +395,7 @@ async function restartService(): Promise<void> {
|
||||
}
|
||||
|
||||
async function askChatPhone(authedPhone: string): Promise<string> {
|
||||
p.note(
|
||||
note(
|
||||
[
|
||||
`Authenticated with ${k.cyan('+' + authedPhone)}.`,
|
||||
'',
|
||||
|
||||
Reference in New Issue
Block a user