mirror of
https://github.com/qwibitai/nanoclaw.git
synced 2026-06-12 18:11:51 +08:00
setup: add ← Back option to Teams channel flow
Stacked on #2269 (back-nav scaffolding) plus the Telegram and Slack PRs. They share the same scaffolding file from #2269 — they don't compile without it, so they have to stack. Both Teams paths already had a brightSelect at the right place, so we just extend each with a Back option — no new prompts: - Existing-credentials path: Yes/No confirm becomes Yes/No/Back - Fresh-setup path: the very first stepGate ("How did that go?") gets a 4th option. Subsequent stepGates keep the original 3 options so we never lose mid-flow state. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -462,7 +462,7 @@ async function main(): Promise<void> {
|
||||
} else if (channelChoice === 'signal') {
|
||||
await runSignalChannel(displayName!);
|
||||
} else if (channelChoice === 'teams') {
|
||||
await runTeamsChannel(displayName!);
|
||||
result = await runTeamsChannel(displayName!);
|
||||
} else if (channelChoice === 'slack') {
|
||||
result = await runSlackChannel(displayName!);
|
||||
} else if (channelChoice === 'imessage') {
|
||||
|
||||
+43
-12
@@ -30,6 +30,7 @@ import path from 'path';
|
||||
import * as p from '@clack/prompts';
|
||||
import k from 'kleur';
|
||||
|
||||
import { BACK_TO_CHANNEL_SELECTION, type ChannelFlowResult } from '../lib/back-nav.js';
|
||||
import { brightSelect } from '../lib/bright-select.js';
|
||||
import { confirmThenOpen } from '../lib/browser.js';
|
||||
import {
|
||||
@@ -57,18 +58,24 @@ interface Collected {
|
||||
agentName?: string;
|
||||
}
|
||||
|
||||
export async function runTeamsChannel(_displayName: string): Promise<void> {
|
||||
export async function runTeamsChannel(_displayName: string): Promise<ChannelFlowResult> {
|
||||
const collected: Collected = {};
|
||||
const completed: string[] = [];
|
||||
|
||||
const existingAppId = readEnvKey('TEAMS_APP_ID');
|
||||
const existingPassword = readEnvKey('TEAMS_APP_PASSWORD');
|
||||
if (existingAppId && existingPassword) {
|
||||
const reuse = ensureAnswer(await p.confirm({
|
||||
const choice = ensureAnswer(await brightSelect<'yes' | 'no' | 'back'>({
|
||||
message: `Found existing Teams credentials (App ID: ${existingAppId.slice(0, 8)}…). Use them?`,
|
||||
initialValue: true,
|
||||
options: [
|
||||
{ value: 'yes', label: 'Yes, use the existing credentials' },
|
||||
{ value: 'no', label: "No, set up new ones" },
|
||||
{ value: 'back', label: '← Back to channel selection' },
|
||||
],
|
||||
initialValue: 'yes',
|
||||
}));
|
||||
if (reuse) {
|
||||
if (choice === 'back') return BACK_TO_CHANNEL_SELECTION;
|
||||
if (choice === 'yes') {
|
||||
collected.appId = existingAppId;
|
||||
collected.appPassword = existingPassword;
|
||||
collected.appType = (readEnvKey('TEAMS_APP_TYPE') as 'SingleTenant' | 'MultiTenant') || 'MultiTenant';
|
||||
@@ -85,7 +92,8 @@ export async function runTeamsChannel(_displayName: string): Promise<void> {
|
||||
|
||||
printIntro();
|
||||
|
||||
await confirmPrereqs({ collected, completed });
|
||||
const prereqsResult = await confirmPrereqs({ collected, completed });
|
||||
if (prereqsResult === 'back') return BACK_TO_CHANNEL_SELECTION;
|
||||
await stepPublicUrl({ collected, completed });
|
||||
await stepAppRegistration({ collected, completed });
|
||||
await stepClientSecret({ collected, completed });
|
||||
@@ -116,7 +124,7 @@ function printIntro(): void {
|
||||
);
|
||||
}
|
||||
|
||||
async function confirmPrereqs(args: { collected: Collected; completed: string[] }): Promise<void> {
|
||||
async function confirmPrereqs(args: { collected: Collected; completed: string[] }): Promise<'continue' | 'back'> {
|
||||
note(
|
||||
[
|
||||
'Before we start, confirm you have:',
|
||||
@@ -131,13 +139,36 @@ async function confirmPrereqs(args: { collected: Collected; completed: string[]
|
||||
'Prereqs',
|
||||
);
|
||||
|
||||
await stepGate({
|
||||
stepName: 'teams-prereqs',
|
||||
stepDescription: 'confirming they have the right Microsoft 365 tenant and tunnel',
|
||||
reshow: () => confirmPrereqs(args),
|
||||
args,
|
||||
});
|
||||
// Back-aware variant of stepGate — Back is only offered on the very first
|
||||
// step of the Teams flow so users can bail out before any state is taken.
|
||||
while (true) {
|
||||
const choice = ensureAnswer(
|
||||
await brightSelect<'done' | 'help' | 'reshow' | 'back'>({
|
||||
message: 'How did that go?',
|
||||
options: [
|
||||
{ value: 'done', label: "Done — let's continue" },
|
||||
{ value: 'help', label: 'Stuck — hand me off to Claude' },
|
||||
{ value: 'reshow', label: 'Show me the steps again' },
|
||||
{ value: 'back', label: '← Back to channel selection' },
|
||||
],
|
||||
}),
|
||||
);
|
||||
if (choice === 'back') return 'back';
|
||||
if (choice === 'done') break;
|
||||
if (choice === 'help') {
|
||||
await offerHandoff({
|
||||
step: 'teams-prereqs',
|
||||
stepDescription: 'confirming they have the right Microsoft 365 tenant and tunnel',
|
||||
args,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
if (choice === 'reshow') {
|
||||
return confirmPrereqs(args);
|
||||
}
|
||||
}
|
||||
args.completed.push('Prereqs confirmed.');
|
||||
return 'continue';
|
||||
}
|
||||
|
||||
// ─── step: public URL ──────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user