feat: add quickstart onboarding defaults
This commit is contained in:
@@ -24,6 +24,20 @@ Follow‑up reconfiguration:
|
|||||||
clawdbot configure
|
clawdbot configure
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## QuickStart vs Advanced
|
||||||
|
|
||||||
|
The wizard starts with **QuickStart** (defaults) vs **Advanced** (full control).
|
||||||
|
|
||||||
|
**QuickStart** keeps the defaults:
|
||||||
|
- Local gateway (loopback)
|
||||||
|
- Workspace default (or existing workspace)
|
||||||
|
- Gateway port **18789**
|
||||||
|
- Gateway auth **Off** (loopback only)
|
||||||
|
- Tailscale exposure **Off**
|
||||||
|
- Telegram + WhatsApp DMs default to **allowlist** (you’ll be prompted for a number)
|
||||||
|
|
||||||
|
**Advanced** exposes every step (mode, workspace, gateway, providers, daemon, skills).
|
||||||
|
|
||||||
## What the wizard does
|
## What the wizard does
|
||||||
|
|
||||||
**Local mode (default)** walks you through:
|
**Local mode (default)** walks you through:
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
DEFAULT_GATEWAY_DAEMON_RUNTIME,
|
DEFAULT_GATEWAY_DAEMON_RUNTIME,
|
||||||
isGatewayDaemonRuntime,
|
isGatewayDaemonRuntime,
|
||||||
} from "../commands/daemon-runtime.js";
|
} from "../commands/daemon-runtime.js";
|
||||||
|
import { resolveControlUiLinks } from "../commands/onboard-helpers.js";
|
||||||
import {
|
import {
|
||||||
createConfigIO,
|
createConfigIO,
|
||||||
loadConfig,
|
loadConfig,
|
||||||
@@ -30,7 +31,6 @@ import { resolveGatewayProgramArguments } from "../daemon/program-args.js";
|
|||||||
import { resolveGatewayService } from "../daemon/service.js";
|
import { resolveGatewayService } from "../daemon/service.js";
|
||||||
import { callGateway } from "../gateway/call.js";
|
import { callGateway } from "../gateway/call.js";
|
||||||
import { resolveGatewayBindHost } from "../gateway/net.js";
|
import { resolveGatewayBindHost } from "../gateway/net.js";
|
||||||
import { resolveControlUiLinks } from "../commands/onboard-helpers.js";
|
|
||||||
import {
|
import {
|
||||||
formatPortDiagnostics,
|
formatPortDiagnostics,
|
||||||
inspectPortUsage,
|
inspectPortUsage,
|
||||||
@@ -48,10 +48,14 @@ type ConfigSummary = {
|
|||||||
exists: boolean;
|
exists: boolean;
|
||||||
valid: boolean;
|
valid: boolean;
|
||||||
issues?: Array<{ path: string; message: string }>;
|
issues?: Array<{ path: string; message: string }>;
|
||||||
|
controlUi?: {
|
||||||
|
enabled?: boolean;
|
||||||
|
basePath?: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
type GatewayStatusSummary = {
|
type GatewayStatusSummary = {
|
||||||
bindMode: string;
|
bindMode: "auto" | "lan" | "tailnet" | "loopback";
|
||||||
bindHost: string | null;
|
bindHost: string | null;
|
||||||
port: number;
|
port: number;
|
||||||
portSource: "service args" | "env/config";
|
portSource: "service args" | "env/config";
|
||||||
@@ -372,6 +376,9 @@ async function gatherDaemonStatus(opts: {
|
|||||||
exists: cliSnapshot?.exists ?? false,
|
exists: cliSnapshot?.exists ?? false,
|
||||||
valid: cliSnapshot?.valid ?? true,
|
valid: cliSnapshot?.valid ?? true,
|
||||||
...(cliSnapshot?.issues?.length ? { issues: cliSnapshot.issues } : {}),
|
...(cliSnapshot?.issues?.length ? { issues: cliSnapshot.issues } : {}),
|
||||||
|
...(cliCfg.gateway?.controlUi
|
||||||
|
? { controlUi: cliCfg.gateway.controlUi }
|
||||||
|
: {}),
|
||||||
};
|
};
|
||||||
const daemonConfigSummary: ConfigSummary = {
|
const daemonConfigSummary: ConfigSummary = {
|
||||||
path: daemonSnapshot?.path ?? daemonConfigPath,
|
path: daemonSnapshot?.path ?? daemonConfigPath,
|
||||||
@@ -380,6 +387,9 @@ async function gatherDaemonStatus(opts: {
|
|||||||
...(daemonSnapshot?.issues?.length
|
...(daemonSnapshot?.issues?.length
|
||||||
? { issues: daemonSnapshot.issues }
|
? { issues: daemonSnapshot.issues }
|
||||||
: {}),
|
: {}),
|
||||||
|
...(daemonCfg.gateway?.controlUi
|
||||||
|
? { controlUi: daemonCfg.gateway.controlUi }
|
||||||
|
: {}),
|
||||||
};
|
};
|
||||||
const configMismatch = cliConfigSummary.path !== daemonConfigSummary.path;
|
const configMismatch = cliConfigSummary.path !== daemonConfigSummary.path;
|
||||||
|
|
||||||
@@ -390,7 +400,11 @@ async function gatherDaemonStatus(opts: {
|
|||||||
? "service args"
|
? "service args"
|
||||||
: "env/config";
|
: "env/config";
|
||||||
|
|
||||||
const bindMode = daemonCfg.gateway?.bind ?? "loopback";
|
const bindMode = (daemonCfg.gateway?.bind ?? "loopback") as
|
||||||
|
| "auto"
|
||||||
|
| "lan"
|
||||||
|
| "tailnet"
|
||||||
|
| "loopback";
|
||||||
const bindHost = resolveGatewayBindHost(bindMode);
|
const bindHost = resolveGatewayBindHost(bindMode);
|
||||||
const tailnetIPv4 = pickPrimaryTailnetIPv4();
|
const tailnetIPv4 = pickPrimaryTailnetIPv4();
|
||||||
const probeHost = pickProbeHostForBind(bindMode, tailnetIPv4);
|
const probeHost = pickProbeHostForBind(bindMode, tailnetIPv4);
|
||||||
@@ -587,7 +601,12 @@ function printDaemonStatus(status: DaemonStatus, opts: { json: boolean }) {
|
|||||||
if (runtimeLine) {
|
if (runtimeLine) {
|
||||||
defaultRuntime.log(`Runtime: ${runtimeLine}`);
|
defaultRuntime.log(`Runtime: ${runtimeLine}`);
|
||||||
}
|
}
|
||||||
if (rpc && !rpc.ok && service.loaded && service.runtime?.status === "running") {
|
if (
|
||||||
|
rpc &&
|
||||||
|
!rpc.ok &&
|
||||||
|
service.loaded &&
|
||||||
|
service.runtime?.status === "running"
|
||||||
|
) {
|
||||||
defaultRuntime.log(
|
defaultRuntime.log(
|
||||||
"Warm-up: launch agents can take a few seconds. Try again shortly.",
|
"Warm-up: launch agents can take a few seconds. Try again shortly.",
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,9 +3,38 @@ import {
|
|||||||
CLAUDE_CLI_PROFILE_ID,
|
CLAUDE_CLI_PROFILE_ID,
|
||||||
CODEX_CLI_PROFILE_ID,
|
CODEX_CLI_PROFILE_ID,
|
||||||
} from "../agents/auth-profiles.js";
|
} from "../agents/auth-profiles.js";
|
||||||
|
import { colorize, isRich, theme } from "../terminal/theme.js";
|
||||||
import type { AuthChoice } from "./onboard-types.js";
|
import type { AuthChoice } from "./onboard-types.js";
|
||||||
|
|
||||||
export type AuthChoiceOption = { value: AuthChoice; label: string };
|
export type AuthChoiceOption = {
|
||||||
|
value: AuthChoice;
|
||||||
|
label: string;
|
||||||
|
hint?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function formatOAuthHint(expires?: number): string {
|
||||||
|
const rich = isRich();
|
||||||
|
if (!expires) {
|
||||||
|
return colorize(rich, theme.muted, "token unavailable");
|
||||||
|
}
|
||||||
|
const now = Date.now();
|
||||||
|
const remaining = expires - now;
|
||||||
|
if (remaining <= 0) {
|
||||||
|
return colorize(rich, theme.error, "token expired");
|
||||||
|
}
|
||||||
|
const minutes = Math.round(remaining / (60 * 1000));
|
||||||
|
const duration =
|
||||||
|
minutes >= 120
|
||||||
|
? `${Math.round(minutes / 60)}h`
|
||||||
|
: minutes >= 60
|
||||||
|
? "1h"
|
||||||
|
: `${Math.max(minutes, 1)}m`;
|
||||||
|
const label = `token ok · expires in ${duration}`;
|
||||||
|
if (minutes <= 10) {
|
||||||
|
return colorize(rich, theme.warn, label);
|
||||||
|
}
|
||||||
|
return colorize(rich, theme.success, label);
|
||||||
|
}
|
||||||
|
|
||||||
export function buildAuthChoiceOptions(params: {
|
export function buildAuthChoiceOptions(params: {
|
||||||
store: AuthProfileStore;
|
store: AuthProfileStore;
|
||||||
@@ -13,24 +42,26 @@ export function buildAuthChoiceOptions(params: {
|
|||||||
}): AuthChoiceOption[] {
|
}): AuthChoiceOption[] {
|
||||||
const options: AuthChoiceOption[] = [];
|
const options: AuthChoiceOption[] = [];
|
||||||
|
|
||||||
const claudeCli = params.store.profiles[CLAUDE_CLI_PROFILE_ID];
|
|
||||||
if (claudeCli?.type === "oauth") {
|
|
||||||
options.push({
|
|
||||||
value: "claude-cli",
|
|
||||||
label: "Anthropic OAuth (Claude CLI)",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
options.push({ value: "oauth", label: "Anthropic OAuth (Claude Pro/Max)" });
|
|
||||||
|
|
||||||
const codexCli = params.store.profiles[CODEX_CLI_PROFILE_ID];
|
const codexCli = params.store.profiles[CODEX_CLI_PROFILE_ID];
|
||||||
if (codexCli?.type === "oauth") {
|
if (codexCli?.type === "oauth") {
|
||||||
options.push({
|
options.push({
|
||||||
value: "codex-cli",
|
value: "codex-cli",
|
||||||
label: "OpenAI Codex OAuth (Codex CLI)",
|
label: "OpenAI Codex OAuth (Codex CLI)",
|
||||||
|
hint: formatOAuthHint(codexCli.expires),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const claudeCli = params.store.profiles[CLAUDE_CLI_PROFILE_ID];
|
||||||
|
if (claudeCli?.type === "oauth") {
|
||||||
|
options.push({
|
||||||
|
value: "claude-cli",
|
||||||
|
label: "Anthropic OAuth (Claude CLI)",
|
||||||
|
hint: formatOAuthHint(claudeCli.expires),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
options.push({ value: "oauth", label: "Anthropic OAuth (Claude Pro/Max)" });
|
||||||
|
|
||||||
options.push({
|
options.push({
|
||||||
value: "openai-codex",
|
value: "openai-codex",
|
||||||
label: "OpenAI Codex (ChatGPT OAuth)",
|
label: "OpenAI Codex (ChatGPT OAuth)",
|
||||||
|
|||||||
@@ -11,12 +11,18 @@ import {
|
|||||||
} from "../config/config.js";
|
} from "../config/config.js";
|
||||||
import { GATEWAY_LAUNCH_AGENT_LABEL } from "../daemon/constants.js";
|
import { GATEWAY_LAUNCH_AGENT_LABEL } from "../daemon/constants.js";
|
||||||
import { readLastGatewayErrorLine } from "../daemon/diagnostics.js";
|
import { readLastGatewayErrorLine } from "../daemon/diagnostics.js";
|
||||||
|
import { resolveGatewayProgramArguments } from "../daemon/program-args.js";
|
||||||
import { resolveGatewayService } from "../daemon/service.js";
|
import { resolveGatewayService } from "../daemon/service.js";
|
||||||
import { buildGatewayConnectionDetails } from "../gateway/call.js";
|
import { buildGatewayConnectionDetails } from "../gateway/call.js";
|
||||||
import { formatPortDiagnostics, inspectPortUsage } from "../infra/ports.js";
|
import { formatPortDiagnostics, inspectPortUsage } from "../infra/ports.js";
|
||||||
import type { RuntimeEnv } from "../runtime.js";
|
import type { RuntimeEnv } from "../runtime.js";
|
||||||
import { defaultRuntime } from "../runtime.js";
|
import { defaultRuntime } from "../runtime.js";
|
||||||
import { resolveUserPath, sleep } from "../utils.js";
|
import { resolveUserPath, sleep } from "../utils.js";
|
||||||
|
import {
|
||||||
|
DEFAULT_GATEWAY_DAEMON_RUNTIME,
|
||||||
|
GATEWAY_DAEMON_RUNTIME_OPTIONS,
|
||||||
|
type GatewayDaemonRuntime,
|
||||||
|
} from "./daemon-runtime.js";
|
||||||
import { maybeRepairAnthropicOAuthProfileId } from "./doctor-auth.js";
|
import { maybeRepairAnthropicOAuthProfileId } from "./doctor-auth.js";
|
||||||
import {
|
import {
|
||||||
buildGatewayRuntimeHints,
|
buildGatewayRuntimeHints,
|
||||||
@@ -56,13 +62,7 @@ import {
|
|||||||
DEFAULT_WORKSPACE,
|
DEFAULT_WORKSPACE,
|
||||||
printWizardHeader,
|
printWizardHeader,
|
||||||
} from "./onboard-helpers.js";
|
} from "./onboard-helpers.js";
|
||||||
import { resolveGatewayProgramArguments } from "../daemon/program-args.js";
|
|
||||||
import { ensureSystemdUserLingerInteractive } from "./systemd-linger.js";
|
import { ensureSystemdUserLingerInteractive } from "./systemd-linger.js";
|
||||||
import {
|
|
||||||
DEFAULT_GATEWAY_DAEMON_RUNTIME,
|
|
||||||
GATEWAY_DAEMON_RUNTIME_OPTIONS,
|
|
||||||
type GatewayDaemonRuntime,
|
|
||||||
} from "./daemon-runtime.js";
|
|
||||||
|
|
||||||
function resolveMode(cfg: ClawdbotConfig): "local" | "remote" {
|
function resolveMode(cfg: ClawdbotConfig): "local" | "remote" {
|
||||||
return cfg.gateway?.mode === "remote" ? "remote" : "local";
|
return cfg.gateway?.mode === "remote" ? "remote" : "local";
|
||||||
|
|||||||
@@ -470,10 +470,69 @@ async function maybeConfigureDmPolicies(params: {
|
|||||||
return cfg;
|
return cfg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function promptTelegramAllowFrom(params: {
|
||||||
|
cfg: ClawdbotConfig;
|
||||||
|
prompter: WizardPrompter;
|
||||||
|
accountId: string;
|
||||||
|
}): Promise<ClawdbotConfig> {
|
||||||
|
const { cfg, prompter, accountId } = params;
|
||||||
|
const resolved = resolveTelegramAccount({ cfg, accountId });
|
||||||
|
const existingAllowFrom = resolved.config.allowFrom ?? [];
|
||||||
|
const entry = await prompter.text({
|
||||||
|
message: "Telegram allowFrom (user id)",
|
||||||
|
placeholder: "123456789",
|
||||||
|
initialValue: existingAllowFrom[0]
|
||||||
|
? String(existingAllowFrom[0])
|
||||||
|
: undefined,
|
||||||
|
validate: (value) => {
|
||||||
|
const raw = String(value ?? "").trim();
|
||||||
|
if (!raw) return "Required";
|
||||||
|
if (!/^\d+$/.test(raw)) return "Use a numeric Telegram user id";
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const normalized = String(entry).trim();
|
||||||
|
const merged = [
|
||||||
|
...existingAllowFrom.map((item) => String(item).trim()).filter(Boolean),
|
||||||
|
normalized,
|
||||||
|
];
|
||||||
|
const unique = [...new Set(merged)];
|
||||||
|
|
||||||
|
if (accountId === DEFAULT_ACCOUNT_ID) {
|
||||||
|
return {
|
||||||
|
...cfg,
|
||||||
|
telegram: {
|
||||||
|
...cfg.telegram,
|
||||||
|
enabled: true,
|
||||||
|
dmPolicy: "allowlist",
|
||||||
|
allowFrom: unique,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...cfg,
|
||||||
|
telegram: {
|
||||||
|
...cfg.telegram,
|
||||||
|
enabled: true,
|
||||||
|
accounts: {
|
||||||
|
...cfg.telegram?.accounts,
|
||||||
|
[accountId]: {
|
||||||
|
...cfg.telegram?.accounts?.[accountId],
|
||||||
|
enabled: cfg.telegram?.accounts?.[accountId]?.enabled ?? true,
|
||||||
|
dmPolicy: "allowlist",
|
||||||
|
allowFrom: unique,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async function promptWhatsAppAllowFrom(
|
async function promptWhatsAppAllowFrom(
|
||||||
cfg: ClawdbotConfig,
|
cfg: ClawdbotConfig,
|
||||||
_runtime: RuntimeEnv,
|
_runtime: RuntimeEnv,
|
||||||
prompter: WizardPrompter,
|
prompter: WizardPrompter,
|
||||||
|
options?: { forceAllowlist?: boolean },
|
||||||
): Promise<ClawdbotConfig> {
|
): Promise<ClawdbotConfig> {
|
||||||
const existingPolicy = cfg.whatsapp?.dmPolicy ?? "pairing";
|
const existingPolicy = cfg.whatsapp?.dmPolicy ?? "pairing";
|
||||||
const existingAllowFrom = cfg.whatsapp?.allowFrom ?? [];
|
const existingAllowFrom = cfg.whatsapp?.allowFrom ?? [];
|
||||||
@@ -481,6 +540,47 @@ async function promptWhatsAppAllowFrom(
|
|||||||
existingAllowFrom.length > 0 ? existingAllowFrom.join(", ") : "unset";
|
existingAllowFrom.length > 0 ? existingAllowFrom.join(", ") : "unset";
|
||||||
const existingResponsePrefix = cfg.messages?.responsePrefix;
|
const existingResponsePrefix = cfg.messages?.responsePrefix;
|
||||||
|
|
||||||
|
if (options?.forceAllowlist) {
|
||||||
|
const entry = await prompter.text({
|
||||||
|
message: "Your WhatsApp number (E.164)",
|
||||||
|
placeholder: "+15555550123",
|
||||||
|
initialValue: existingAllowFrom[0],
|
||||||
|
validate: (value) => {
|
||||||
|
const raw = String(value ?? "").trim();
|
||||||
|
if (!raw) return "Required";
|
||||||
|
const normalized = normalizeE164(raw);
|
||||||
|
if (!normalized) return `Invalid number: ${raw}`;
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const normalized = normalizeE164(String(entry).trim());
|
||||||
|
const merged = [
|
||||||
|
...existingAllowFrom
|
||||||
|
.filter((item) => item !== "*")
|
||||||
|
.map((item) => normalizeE164(item))
|
||||||
|
.filter(Boolean),
|
||||||
|
normalized,
|
||||||
|
];
|
||||||
|
const unique = [...new Set(merged.filter(Boolean))];
|
||||||
|
let next = setWhatsAppSelfChatMode(cfg, true);
|
||||||
|
next = setWhatsAppDmPolicy(next, "allowlist");
|
||||||
|
next = setWhatsAppAllowFrom(next, unique);
|
||||||
|
if (existingResponsePrefix === undefined) {
|
||||||
|
next = setMessagesResponsePrefix(next, "[clawdbot]");
|
||||||
|
}
|
||||||
|
await prompter.note(
|
||||||
|
[
|
||||||
|
"Allowlist mode enabled.",
|
||||||
|
`- allowFrom includes ${normalized}`,
|
||||||
|
existingResponsePrefix === undefined
|
||||||
|
? "- responsePrefix set to [clawdbot]"
|
||||||
|
: "- responsePrefix left unchanged",
|
||||||
|
].join("\n"),
|
||||||
|
"WhatsApp allowlist",
|
||||||
|
);
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
await prompter.note(
|
await prompter.note(
|
||||||
[
|
[
|
||||||
"WhatsApp direct chats are gated by `whatsapp.dmPolicy` + `whatsapp.allowFrom`.",
|
"WhatsApp direct chats are gated by `whatsapp.dmPolicy` + `whatsapp.allowFrom`.",
|
||||||
@@ -562,7 +662,7 @@ async function promptWhatsAppAllowFrom(
|
|||||||
}
|
}
|
||||||
if (policy === "disabled") return next;
|
if (policy === "disabled") return next;
|
||||||
|
|
||||||
const options =
|
const allowOptions =
|
||||||
existingAllowFrom.length > 0
|
existingAllowFrom.length > 0
|
||||||
? ([
|
? ([
|
||||||
{ value: "keep", label: "Keep current allowFrom" },
|
{ value: "keep", label: "Keep current allowFrom" },
|
||||||
@@ -579,8 +679,11 @@ async function promptWhatsAppAllowFrom(
|
|||||||
|
|
||||||
const mode = (await prompter.select({
|
const mode = (await prompter.select({
|
||||||
message: "WhatsApp allowFrom (optional pre-allowlist)",
|
message: "WhatsApp allowFrom (optional pre-allowlist)",
|
||||||
options: options.map((opt) => ({ value: opt.value, label: opt.label })),
|
options: allowOptions.map((opt) => ({
|
||||||
})) as (typeof options)[number]["value"];
|
value: opt.value,
|
||||||
|
label: opt.label,
|
||||||
|
})),
|
||||||
|
})) as (typeof allowOptions)[number]["value"];
|
||||||
|
|
||||||
if (mode === "keep") {
|
if (mode === "keep") {
|
||||||
// Keep allowFrom as-is.
|
// Keep allowFrom as-is.
|
||||||
@@ -631,6 +734,8 @@ type SetupProvidersOptions = {
|
|||||||
whatsappAccountId?: string;
|
whatsappAccountId?: string;
|
||||||
promptWhatsAppAccountId?: boolean;
|
promptWhatsAppAccountId?: boolean;
|
||||||
onWhatsAppAccountId?: (accountId: string) => void;
|
onWhatsAppAccountId?: (accountId: string) => void;
|
||||||
|
forceAllowFromProviders?: ProviderChoice[];
|
||||||
|
skipDmPolicyPrompt?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function setupProviders(
|
export async function setupProviders(
|
||||||
@@ -639,6 +744,12 @@ export async function setupProviders(
|
|||||||
prompter: WizardPrompter,
|
prompter: WizardPrompter,
|
||||||
options?: SetupProvidersOptions,
|
options?: SetupProvidersOptions,
|
||||||
): Promise<ClawdbotConfig> {
|
): Promise<ClawdbotConfig> {
|
||||||
|
const forceAllowFromProviders = new Set(
|
||||||
|
options?.forceAllowFromProviders ?? [],
|
||||||
|
);
|
||||||
|
const forceTelegramAllowFrom = forceAllowFromProviders.has("telegram");
|
||||||
|
const forceWhatsAppAllowFrom = forceAllowFromProviders.has("whatsapp");
|
||||||
|
|
||||||
let whatsappAccountId =
|
let whatsappAccountId =
|
||||||
options?.whatsappAccountId?.trim() || resolveDefaultWhatsAppAccountId(cfg);
|
options?.whatsappAccountId?.trim() || resolveDefaultWhatsAppAccountId(cfg);
|
||||||
let whatsappLinked = await detectWhatsAppLinked(cfg, whatsappAccountId);
|
let whatsappLinked = await detectWhatsAppLinked(cfg, whatsappAccountId);
|
||||||
@@ -854,7 +965,9 @@ export async function setupProviders(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
next = await promptWhatsAppAllowFrom(next, runtime, prompter);
|
next = await promptWhatsAppAllowFrom(next, runtime, prompter, {
|
||||||
|
forceAllowlist: forceWhatsAppAllowFrom,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selection.includes("telegram")) {
|
if (selection.includes("telegram")) {
|
||||||
@@ -962,6 +1075,14 @@ export async function setupProviders(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (forceTelegramAllowFrom) {
|
||||||
|
next = await promptTelegramAllowFrom({
|
||||||
|
cfg: next,
|
||||||
|
prompter,
|
||||||
|
accountId: telegramAccountId,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selection.includes("discord")) {
|
if (selection.includes("discord")) {
|
||||||
@@ -1414,7 +1535,9 @@ export async function setupProviders(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
next = await maybeConfigureDmPolicies({ cfg: next, selection, prompter });
|
if (!options?.skipDmPolicyPrompt) {
|
||||||
|
next = await maybeConfigureDmPolicies({ cfg: next, selection, prompter });
|
||||||
|
}
|
||||||
|
|
||||||
if (options?.allowDisable) {
|
if (options?.allowDisable) {
|
||||||
if (!selection.includes("telegram") && telegramConfigured) {
|
if (!selection.includes("telegram") && telegramConfigured) {
|
||||||
|
|||||||
@@ -1,31 +1,35 @@
|
|||||||
import { withProgress } from "../../cli/progress.js";
|
import { withProgress } from "../../cli/progress.js";
|
||||||
import { callGateway } from "../../gateway/call.js";
|
|
||||||
import { listChatProviders } from "../../providers/registry.js";
|
|
||||||
import { defaultRuntime, type RuntimeEnv } from "../../runtime.js";
|
|
||||||
import { formatDocsLink } from "../../terminal/links.js";
|
|
||||||
import { theme } from "../../terminal/theme.js";
|
|
||||||
import {
|
import {
|
||||||
type ChatProvider,
|
type ClawdbotConfig,
|
||||||
formatProviderAccountLabel,
|
readConfigFileSnapshot,
|
||||||
requireValidConfig,
|
} from "../../config/config.js";
|
||||||
} from "./shared.js";
|
|
||||||
import {
|
import {
|
||||||
listDiscordAccountIds,
|
listDiscordAccountIds,
|
||||||
resolveDiscordAccount,
|
resolveDiscordAccount,
|
||||||
} from "../../discord/accounts.js";
|
} from "../../discord/accounts.js";
|
||||||
|
import { callGateway } from "../../gateway/call.js";
|
||||||
import {
|
import {
|
||||||
listIMessageAccountIds,
|
listIMessageAccountIds,
|
||||||
resolveIMessageAccount,
|
resolveIMessageAccount,
|
||||||
} from "../../imessage/accounts.js";
|
} from "../../imessage/accounts.js";
|
||||||
|
import { formatAge } from "../../infra/provider-summary.js";
|
||||||
|
import { listChatProviders } from "../../providers/registry.js";
|
||||||
|
import { defaultRuntime, type RuntimeEnv } from "../../runtime.js";
|
||||||
import {
|
import {
|
||||||
listSignalAccountIds,
|
listSignalAccountIds,
|
||||||
resolveSignalAccount,
|
resolveSignalAccount,
|
||||||
} from "../../signal/accounts.js";
|
} from "../../signal/accounts.js";
|
||||||
import { listSlackAccountIds, resolveSlackAccount } from "../../slack/accounts.js";
|
import {
|
||||||
|
listSlackAccountIds,
|
||||||
|
resolveSlackAccount,
|
||||||
|
} from "../../slack/accounts.js";
|
||||||
import {
|
import {
|
||||||
listTelegramAccountIds,
|
listTelegramAccountIds,
|
||||||
resolveTelegramAccount,
|
resolveTelegramAccount,
|
||||||
} from "../../telegram/accounts.js";
|
} from "../../telegram/accounts.js";
|
||||||
|
import { formatDocsLink } from "../../terminal/links.js";
|
||||||
|
import { theme } from "../../terminal/theme.js";
|
||||||
|
import { normalizeE164 } from "../../utils.js";
|
||||||
import {
|
import {
|
||||||
listWhatsAppAccountIds,
|
listWhatsAppAccountIds,
|
||||||
resolveWhatsAppAccount,
|
resolveWhatsAppAccount,
|
||||||
@@ -35,9 +39,11 @@ import {
|
|||||||
readWebSelfId,
|
readWebSelfId,
|
||||||
webAuthExists,
|
webAuthExists,
|
||||||
} from "../../web/session.js";
|
} from "../../web/session.js";
|
||||||
import { formatAge } from "../../infra/provider-summary.js";
|
import {
|
||||||
import { normalizeE164 } from "../../utils.js";
|
type ChatProvider,
|
||||||
import { readConfigFileSnapshot, type ClawdbotConfig } from "../../config/config.js";
|
formatProviderAccountLabel,
|
||||||
|
requireValidConfig,
|
||||||
|
} from "./shared.js";
|
||||||
|
|
||||||
export type ProvidersStatusOptions = {
|
export type ProvidersStatusOptions = {
|
||||||
json?: boolean;
|
json?: boolean;
|
||||||
@@ -80,10 +86,16 @@ export function formatGatewayProvidersStatusLines(
|
|||||||
if (typeof account.tokenSource === "string" && account.tokenSource) {
|
if (typeof account.tokenSource === "string" && account.tokenSource) {
|
||||||
bits.push(`token:${account.tokenSource}`);
|
bits.push(`token:${account.tokenSource}`);
|
||||||
}
|
}
|
||||||
if (typeof account.botTokenSource === "string" && account.botTokenSource) {
|
if (
|
||||||
|
typeof account.botTokenSource === "string" &&
|
||||||
|
account.botTokenSource
|
||||||
|
) {
|
||||||
bits.push(`bot:${account.botTokenSource}`);
|
bits.push(`bot:${account.botTokenSource}`);
|
||||||
}
|
}
|
||||||
if (typeof account.appTokenSource === "string" && account.appTokenSource) {
|
if (
|
||||||
|
typeof account.appTokenSource === "string" &&
|
||||||
|
account.appTokenSource
|
||||||
|
) {
|
||||||
bits.push(`app:${account.appTokenSource}`);
|
bits.push(`app:${account.appTokenSource}`);
|
||||||
}
|
}
|
||||||
if (typeof account.baseUrl === "string" && account.baseUrl) {
|
if (typeof account.baseUrl === "string" && account.baseUrl) {
|
||||||
@@ -176,10 +188,16 @@ async function formatConfigProvidersStatusLines(
|
|||||||
if (typeof account.tokenSource === "string" && account.tokenSource) {
|
if (typeof account.tokenSource === "string" && account.tokenSource) {
|
||||||
bits.push(`token:${account.tokenSource}`);
|
bits.push(`token:${account.tokenSource}`);
|
||||||
}
|
}
|
||||||
if (typeof account.botTokenSource === "string" && account.botTokenSource) {
|
if (
|
||||||
|
typeof account.botTokenSource === "string" &&
|
||||||
|
account.botTokenSource
|
||||||
|
) {
|
||||||
bits.push(`bot:${account.botTokenSource}`);
|
bits.push(`bot:${account.botTokenSource}`);
|
||||||
}
|
}
|
||||||
if (typeof account.appTokenSource === "string" && account.appTokenSource) {
|
if (
|
||||||
|
typeof account.appTokenSource === "string" &&
|
||||||
|
account.appTokenSource
|
||||||
|
) {
|
||||||
bits.push(`app:${account.appTokenSource}`);
|
bits.push(`app:${account.appTokenSource}`);
|
||||||
}
|
}
|
||||||
if (typeof account.baseUrl === "string" && account.baseUrl) {
|
if (typeof account.baseUrl === "string" && account.baseUrl) {
|
||||||
@@ -242,7 +260,8 @@ async function formatConfigProvidersStatusLines(
|
|||||||
name: account.name,
|
name: account.name,
|
||||||
enabled: account.enabled,
|
enabled: account.enabled,
|
||||||
configured:
|
configured:
|
||||||
Boolean(account.botToken?.trim()) && Boolean(account.appToken?.trim()),
|
Boolean(account.botToken?.trim()) &&
|
||||||
|
Boolean(account.appToken?.trim()),
|
||||||
botTokenSource: account.botTokenSource,
|
botTokenSource: account.botTokenSource,
|
||||||
appTokenSource: account.appTokenSource,
|
appTokenSource: account.appTokenSource,
|
||||||
};
|
};
|
||||||
@@ -259,11 +278,18 @@ async function formatConfigProvidersStatusLines(
|
|||||||
}),
|
}),
|
||||||
imessage: listIMessageAccountIds(cfg).map((accountId) => {
|
imessage: listIMessageAccountIds(cfg).map((accountId) => {
|
||||||
const account = resolveIMessageAccount({ cfg, accountId });
|
const account = resolveIMessageAccount({ cfg, accountId });
|
||||||
|
const imsgConfigured = Boolean(
|
||||||
|
account.config.cliPath ||
|
||||||
|
account.config.dbPath ||
|
||||||
|
account.config.allowFrom ||
|
||||||
|
account.config.service ||
|
||||||
|
account.config.region,
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
accountId: account.accountId,
|
accountId: account.accountId,
|
||||||
name: account.name,
|
name: account.name,
|
||||||
enabled: account.enabled,
|
enabled: account.enabled,
|
||||||
configured: account.configured,
|
configured: imsgConfigured,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
} satisfies Partial<Record<ChatProvider, Array<Record<string, unknown>>>>;
|
} satisfies Partial<Record<ChatProvider, Array<Record<string, unknown>>>>;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
resolveStorePath,
|
resolveStorePath,
|
||||||
type SessionEntry,
|
type SessionEntry,
|
||||||
} from "../config/sessions.js";
|
} from "../config/sessions.js";
|
||||||
|
import { resolveGatewayService } from "../daemon/service.js";
|
||||||
import { buildGatewayConnectionDetails, callGateway } from "../gateway/call.js";
|
import { buildGatewayConnectionDetails, callGateway } from "../gateway/call.js";
|
||||||
import { info } from "../globals.js";
|
import { info } from "../globals.js";
|
||||||
import { buildProviderSummary } from "../infra/provider-summary.js";
|
import { buildProviderSummary } from "../infra/provider-summary.js";
|
||||||
@@ -20,7 +21,6 @@ import {
|
|||||||
loadProviderUsageSummary,
|
loadProviderUsageSummary,
|
||||||
} from "../infra/provider-usage.js";
|
} from "../infra/provider-usage.js";
|
||||||
import { peekSystemEvents } from "../infra/system-events.js";
|
import { peekSystemEvents } from "../infra/system-events.js";
|
||||||
import { resolveGatewayService } from "../daemon/service.js";
|
|
||||||
import type { RuntimeEnv } from "../runtime.js";
|
import type { RuntimeEnv } from "../runtime.js";
|
||||||
import { resolveWhatsAppAccount } from "../web/accounts.js";
|
import { resolveWhatsAppAccount } from "../web/accounts.js";
|
||||||
import { resolveHeartbeatSeconds } from "../web/reconnect.js";
|
import { resolveHeartbeatSeconds } from "../web/reconnect.js";
|
||||||
@@ -29,8 +29,8 @@ import {
|
|||||||
logWebSelfId,
|
logWebSelfId,
|
||||||
webAuthExists,
|
webAuthExists,
|
||||||
} from "../web/session.js";
|
} from "../web/session.js";
|
||||||
import { resolveControlUiLinks } from "./onboard-helpers.js";
|
|
||||||
import type { HealthSummary } from "./health.js";
|
import type { HealthSummary } from "./health.js";
|
||||||
|
import { resolveControlUiLinks } from "./onboard-helpers.js";
|
||||||
|
|
||||||
export type SessionStatus = {
|
export type SessionStatus = {
|
||||||
key: string;
|
key: string;
|
||||||
|
|||||||
@@ -1,18 +1,33 @@
|
|||||||
import { type ClawdbotConfig, loadConfig } from "../config/config.js";
|
import { type ClawdbotConfig, loadConfig } from "../config/config.js";
|
||||||
import { resolveTelegramAccount, listTelegramAccountIds } from "../telegram/accounts.js";
|
import {
|
||||||
import { resolveDiscordAccount, listDiscordAccountIds } from "../discord/accounts.js";
|
listDiscordAccountIds,
|
||||||
import { resolveSlackAccount, listSlackAccountIds } from "../slack/accounts.js";
|
resolveDiscordAccount,
|
||||||
import { resolveSignalAccount, listSignalAccountIds } from "../signal/accounts.js";
|
} from "../discord/accounts.js";
|
||||||
import { resolveIMessageAccount, listIMessageAccountIds } from "../imessage/accounts.js";
|
import {
|
||||||
|
listIMessageAccountIds,
|
||||||
|
resolveIMessageAccount,
|
||||||
|
} from "../imessage/accounts.js";
|
||||||
|
import { DEFAULT_ACCOUNT_ID } from "../routing/session-key.js";
|
||||||
|
import {
|
||||||
|
listSignalAccountIds,
|
||||||
|
resolveSignalAccount,
|
||||||
|
} from "../signal/accounts.js";
|
||||||
|
import { listSlackAccountIds, resolveSlackAccount } from "../slack/accounts.js";
|
||||||
|
import {
|
||||||
|
listTelegramAccountIds,
|
||||||
|
resolveTelegramAccount,
|
||||||
|
} from "../telegram/accounts.js";
|
||||||
import { theme } from "../terminal/theme.js";
|
import { theme } from "../terminal/theme.js";
|
||||||
import { normalizeE164 } from "../utils.js";
|
import { normalizeE164 } from "../utils.js";
|
||||||
|
import {
|
||||||
|
listWhatsAppAccountIds,
|
||||||
|
resolveWhatsAppAccount,
|
||||||
|
} from "../web/accounts.js";
|
||||||
import {
|
import {
|
||||||
getWebAuthAgeMs,
|
getWebAuthAgeMs,
|
||||||
readWebSelfId,
|
readWebSelfId,
|
||||||
webAuthExists,
|
webAuthExists,
|
||||||
} from "../web/session.js";
|
} from "../web/session.js";
|
||||||
import { listWhatsAppAccountIds, resolveWhatsAppAccount } from "../web/accounts.js";
|
|
||||||
import { DEFAULT_ACCOUNT_ID } from "../routing/session-key.js";
|
|
||||||
|
|
||||||
export type ProviderSummaryOptions = {
|
export type ProviderSummaryOptions = {
|
||||||
colorize?: boolean;
|
colorize?: boolean;
|
||||||
@@ -66,7 +81,11 @@ export async function buildProviderSummary(
|
|||||||
const dmPolicy =
|
const dmPolicy =
|
||||||
account.dmPolicy ?? effective.whatsapp?.dmPolicy ?? "pairing";
|
account.dmPolicy ?? effective.whatsapp?.dmPolicy ?? "pairing";
|
||||||
details.push(`dm:${dmPolicy}`);
|
details.push(`dm:${dmPolicy}`);
|
||||||
const allowFrom = (account.allowFrom ?? effective.whatsapp?.allowFrom ?? [])
|
const allowFrom = (
|
||||||
|
account.allowFrom ??
|
||||||
|
effective.whatsapp?.allowFrom ??
|
||||||
|
[]
|
||||||
|
)
|
||||||
.map(normalizeE164)
|
.map(normalizeE164)
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.slice(0, 2);
|
.slice(0, 2);
|
||||||
@@ -237,7 +256,15 @@ export async function buildProviderSummary(
|
|||||||
const accounts = listIMessageAccountIds(effective).map((accountId) =>
|
const accounts = listIMessageAccountIds(effective).map((accountId) =>
|
||||||
resolveIMessageAccount({ cfg: effective, accountId }),
|
resolveIMessageAccount({ cfg: effective, accountId }),
|
||||||
);
|
);
|
||||||
const configuredAccounts = accounts.filter((account) => account.configured);
|
const configuredAccounts = accounts.filter((account) =>
|
||||||
|
Boolean(
|
||||||
|
account.config.cliPath ||
|
||||||
|
account.config.dbPath ||
|
||||||
|
account.config.allowFrom ||
|
||||||
|
account.config.service ||
|
||||||
|
account.config.region,
|
||||||
|
),
|
||||||
|
);
|
||||||
const imessageConfigured = configuredAccounts.length > 0;
|
const imessageConfigured = configuredAccounts.length > 0;
|
||||||
lines.push(
|
lines.push(
|
||||||
imessageConfigured
|
imessageConfigured
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export type ResolvedWhatsAppAccount = {
|
|||||||
authDir: string;
|
authDir: string;
|
||||||
isLegacyAuthDir: boolean;
|
isLegacyAuthDir: boolean;
|
||||||
selfChatMode?: boolean;
|
selfChatMode?: boolean;
|
||||||
|
dmPolicy?: WhatsAppAccountConfig["dmPolicy"];
|
||||||
allowFrom?: string[];
|
allowFrom?: string[];
|
||||||
groupAllowFrom?: string[];
|
groupAllowFrom?: string[];
|
||||||
groupPolicy?: GroupPolicy;
|
groupPolicy?: GroupPolicy;
|
||||||
@@ -107,6 +108,7 @@ export function resolveWhatsAppAccount(params: {
|
|||||||
authDir,
|
authDir,
|
||||||
isLegacyAuthDir: isLegacy,
|
isLegacyAuthDir: isLegacy,
|
||||||
selfChatMode: accountCfg?.selfChatMode ?? params.cfg.whatsapp?.selfChatMode,
|
selfChatMode: accountCfg?.selfChatMode ?? params.cfg.whatsapp?.selfChatMode,
|
||||||
|
dmPolicy: accountCfg?.dmPolicy ?? params.cfg.whatsapp?.dmPolicy,
|
||||||
allowFrom: accountCfg?.allowFrom ?? params.cfg.whatsapp?.allowFrom,
|
allowFrom: accountCfg?.allowFrom ?? params.cfg.whatsapp?.allowFrom,
|
||||||
groupAllowFrom:
|
groupAllowFrom:
|
||||||
accountCfg?.groupAllowFrom ?? params.cfg.whatsapp?.groupAllowFrom,
|
accountCfg?.groupAllowFrom ?? params.cfg.whatsapp?.groupAllowFrom,
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import { ensureSystemdUserLingerInteractive } from "../commands/systemd-linger.j
|
|||||||
import type { ClawdbotConfig } from "../config/config.js";
|
import type { ClawdbotConfig } from "../config/config.js";
|
||||||
import {
|
import {
|
||||||
CONFIG_PATH_CLAWDBOT,
|
CONFIG_PATH_CLAWDBOT,
|
||||||
|
DEFAULT_GATEWAY_PORT,
|
||||||
readConfigFileSnapshot,
|
readConfigFileSnapshot,
|
||||||
resolveGatewayPort,
|
resolveGatewayPort,
|
||||||
writeConfigFile,
|
writeConfigFile,
|
||||||
@@ -111,6 +112,38 @@ export async function runOnboardingWizard(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const flowHint =
|
||||||
|
"Configure anything via the Clawdbot configuration wizard anytime.";
|
||||||
|
let flow = (await prompter.select({
|
||||||
|
message: "Onboarding mode",
|
||||||
|
options: [
|
||||||
|
{ value: "quickstart", label: "QuickStart", hint: flowHint },
|
||||||
|
{ value: "advanced", label: "Advanced", hint: flowHint },
|
||||||
|
],
|
||||||
|
initialValue: "quickstart",
|
||||||
|
})) as "quickstart" | "advanced";
|
||||||
|
|
||||||
|
if (opts.mode === "remote" && flow === "quickstart") {
|
||||||
|
await prompter.note(
|
||||||
|
"QuickStart only supports local gateways. Switching to Advanced mode.",
|
||||||
|
"QuickStart",
|
||||||
|
);
|
||||||
|
flow = "advanced";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flow === "quickstart") {
|
||||||
|
await prompter.note(
|
||||||
|
[
|
||||||
|
"Gateway port: 18789",
|
||||||
|
"Gateway bind: Loopback (127.0.0.1)",
|
||||||
|
"Gateway auth: Off (loopback only)",
|
||||||
|
"Tailscale exposure: Off",
|
||||||
|
"Direct to chat providers.",
|
||||||
|
].join("\n"),
|
||||||
|
"QuickStart defaults",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const localPort = resolveGatewayPort(baseConfig);
|
const localPort = resolveGatewayPort(baseConfig);
|
||||||
const localUrl = `ws://127.0.0.1:${localPort}`;
|
const localUrl = `ws://127.0.0.1:${localPort}`;
|
||||||
const localProbe = await probeGatewayReachable({
|
const localProbe = await probeGatewayReachable({
|
||||||
@@ -130,27 +163,29 @@ export async function runOnboardingWizard(
|
|||||||
|
|
||||||
const mode =
|
const mode =
|
||||||
opts.mode ??
|
opts.mode ??
|
||||||
((await prompter.select({
|
(flow === "quickstart"
|
||||||
message: "What do you want to set up?",
|
? "local"
|
||||||
options: [
|
: ((await prompter.select({
|
||||||
{
|
message: "What do you want to set up?",
|
||||||
value: "local",
|
options: [
|
||||||
label: "Local gateway (this machine)",
|
{
|
||||||
hint: localProbe.ok
|
value: "local",
|
||||||
? `Gateway reachable (${localUrl})`
|
label: "Local gateway (this machine)",
|
||||||
: `No gateway detected (${localUrl})`,
|
hint: localProbe.ok
|
||||||
},
|
? `Gateway reachable (${localUrl})`
|
||||||
{
|
: `No gateway detected (${localUrl})`,
|
||||||
value: "remote",
|
},
|
||||||
label: "Remote gateway (info-only)",
|
{
|
||||||
hint: !remoteUrl
|
value: "remote",
|
||||||
? "No remote URL configured yet"
|
label: "Remote gateway (info-only)",
|
||||||
: remoteProbe?.ok
|
hint: !remoteUrl
|
||||||
? `Gateway reachable (${remoteUrl})`
|
? "No remote URL configured yet"
|
||||||
: `Configured but unreachable (${remoteUrl})`,
|
: remoteProbe?.ok
|
||||||
},
|
? `Gateway reachable (${remoteUrl})`
|
||||||
],
|
: `Configured but unreachable (${remoteUrl})`,
|
||||||
})) as OnboardMode);
|
},
|
||||||
|
],
|
||||||
|
})) as OnboardMode));
|
||||||
|
|
||||||
if (mode === "remote") {
|
if (mode === "remote") {
|
||||||
let nextConfig = await promptRemoteGatewayConfig(baseConfig, prompter);
|
let nextConfig = await promptRemoteGatewayConfig(baseConfig, prompter);
|
||||||
@@ -163,10 +198,12 @@ export async function runOnboardingWizard(
|
|||||||
|
|
||||||
const workspaceInput =
|
const workspaceInput =
|
||||||
opts.workspace ??
|
opts.workspace ??
|
||||||
(await prompter.text({
|
(flow === "quickstart"
|
||||||
message: "Workspace directory",
|
? (baseConfig.agent?.workspace ?? DEFAULT_WORKSPACE)
|
||||||
initialValue: baseConfig.agent?.workspace ?? DEFAULT_WORKSPACE,
|
: await prompter.text({
|
||||||
}));
|
message: "Workspace directory",
|
||||||
|
initialValue: baseConfig.agent?.workspace ?? DEFAULT_WORKSPACE,
|
||||||
|
}));
|
||||||
|
|
||||||
const workspaceDir = resolveUserPath(
|
const workspaceDir = resolveUserPath(
|
||||||
workspaceInput.trim() || DEFAULT_WORKSPACE,
|
workspaceInput.trim() || DEFAULT_WORKSPACE,
|
||||||
@@ -201,60 +238,79 @@ export async function runOnboardingWizard(
|
|||||||
|
|
||||||
await warnIfModelConfigLooksOff(nextConfig, prompter);
|
await warnIfModelConfigLooksOff(nextConfig, prompter);
|
||||||
|
|
||||||
const portRaw = await prompter.text({
|
const port =
|
||||||
message: "Gateway port",
|
flow === "quickstart"
|
||||||
initialValue: String(localPort),
|
? DEFAULT_GATEWAY_PORT
|
||||||
validate: (value) =>
|
: Number.parseInt(
|
||||||
Number.isFinite(Number(value)) ? undefined : "Invalid port",
|
String(
|
||||||
});
|
await prompter.text({
|
||||||
const port = Number.parseInt(String(portRaw), 10);
|
message: "Gateway port",
|
||||||
|
initialValue: String(localPort),
|
||||||
|
validate: (value) =>
|
||||||
|
Number.isFinite(Number(value)) ? undefined : "Invalid port",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
10,
|
||||||
|
);
|
||||||
|
|
||||||
let bind = (await prompter.select({
|
let bind = (
|
||||||
message: "Gateway bind",
|
flow === "quickstart"
|
||||||
options: [
|
? "loopback"
|
||||||
{ value: "loopback", label: "Loopback (127.0.0.1)" },
|
: ((await prompter.select({
|
||||||
{ value: "lan", label: "LAN" },
|
message: "Gateway bind",
|
||||||
{ value: "tailnet", label: "Tailnet" },
|
options: [
|
||||||
{ value: "auto", label: "Auto" },
|
{ value: "loopback", label: "Loopback (127.0.0.1)" },
|
||||||
],
|
{ value: "lan", label: "LAN" },
|
||||||
})) as "loopback" | "lan" | "tailnet" | "auto";
|
{ value: "tailnet", label: "Tailnet" },
|
||||||
|
{ value: "auto", label: "Auto" },
|
||||||
|
],
|
||||||
|
})) as "loopback" | "lan" | "tailnet" | "auto")
|
||||||
|
) as "loopback" | "lan" | "tailnet" | "auto";
|
||||||
|
|
||||||
let authMode = (await prompter.select({
|
let authMode = (
|
||||||
message: "Gateway auth",
|
flow === "quickstart"
|
||||||
options: [
|
? "off"
|
||||||
{
|
: ((await prompter.select({
|
||||||
value: "off",
|
message: "Gateway auth",
|
||||||
label: "Off (loopback only)",
|
options: [
|
||||||
hint: "Recommended for single-machine setups",
|
{
|
||||||
},
|
value: "off",
|
||||||
{
|
label: "Off (loopback only)",
|
||||||
value: "token",
|
hint: "Recommended for single-machine setups",
|
||||||
label: "Token",
|
},
|
||||||
hint: "Use for multi-machine access or non-loopback binds",
|
{
|
||||||
},
|
value: "token",
|
||||||
{ value: "password", label: "Password" },
|
label: "Token",
|
||||||
],
|
hint: "Use for multi-machine access or non-loopback binds",
|
||||||
})) as GatewayAuthChoice;
|
},
|
||||||
|
{ value: "password", label: "Password" },
|
||||||
|
],
|
||||||
|
})) as GatewayAuthChoice)
|
||||||
|
) as GatewayAuthChoice;
|
||||||
|
|
||||||
const tailscaleMode = (await prompter.select({
|
const tailscaleMode = (
|
||||||
message: "Tailscale exposure",
|
flow === "quickstart"
|
||||||
options: [
|
? "off"
|
||||||
{ value: "off", label: "Off", hint: "No Tailscale exposure" },
|
: ((await prompter.select({
|
||||||
{
|
message: "Tailscale exposure",
|
||||||
value: "serve",
|
options: [
|
||||||
label: "Serve",
|
{ value: "off", label: "Off", hint: "No Tailscale exposure" },
|
||||||
hint: "Private HTTPS for your tailnet (devices on Tailscale)",
|
{
|
||||||
},
|
value: "serve",
|
||||||
{
|
label: "Serve",
|
||||||
value: "funnel",
|
hint: "Private HTTPS for your tailnet (devices on Tailscale)",
|
||||||
label: "Funnel",
|
},
|
||||||
hint: "Public HTTPS via Tailscale Funnel (internet)",
|
{
|
||||||
},
|
value: "funnel",
|
||||||
],
|
label: "Funnel",
|
||||||
})) as "off" | "serve" | "funnel";
|
hint: "Public HTTPS via Tailscale Funnel (internet)",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})) as "off" | "serve" | "funnel")
|
||||||
|
) as "off" | "serve" | "funnel";
|
||||||
|
|
||||||
let tailscaleResetOnExit = false;
|
let tailscaleResetOnExit = false;
|
||||||
if (tailscaleMode !== "off") {
|
if (tailscaleMode !== "off" && flow !== "quickstart") {
|
||||||
await prompter.note(
|
await prompter.note(
|
||||||
[
|
[
|
||||||
"Docs:",
|
"Docs:",
|
||||||
@@ -348,6 +404,9 @@ export async function runOnboardingWizard(
|
|||||||
|
|
||||||
nextConfig = await setupProviders(nextConfig, runtime, prompter, {
|
nextConfig = await setupProviders(nextConfig, runtime, prompter, {
|
||||||
allowSignalInstall: true,
|
allowSignalInstall: true,
|
||||||
|
forceAllowFromProviders:
|
||||||
|
flow === "quickstart" ? ["telegram", "whatsapp"] : [],
|
||||||
|
skipDmPolicyPrompt: flow === "quickstart",
|
||||||
});
|
});
|
||||||
|
|
||||||
await writeConfigFile(nextConfig);
|
await writeConfigFile(nextConfig);
|
||||||
|
|||||||
Reference in New Issue
Block a user