refactor!: rename chat providers to channels
This commit is contained in:
68
src/cli/channel-auth.ts
Normal file
68
src/cli/channel-auth.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { resolveChannelDefaultAccountId } from "../channels/plugins/helpers.js";
|
||||
import {
|
||||
getChannelPlugin,
|
||||
normalizeChannelId,
|
||||
} from "../channels/plugins/index.js";
|
||||
import { DEFAULT_CHAT_CHANNEL } from "../channels/registry.js";
|
||||
import { loadConfig } from "../config/config.js";
|
||||
import { setVerbose } from "../globals.js";
|
||||
import { defaultRuntime, type RuntimeEnv } from "../runtime.js";
|
||||
|
||||
type ChannelAuthOptions = {
|
||||
channel?: string;
|
||||
account?: string;
|
||||
verbose?: boolean;
|
||||
};
|
||||
|
||||
export async function runChannelLogin(
|
||||
opts: ChannelAuthOptions,
|
||||
runtime: RuntimeEnv = defaultRuntime,
|
||||
) {
|
||||
const channelInput = opts.channel ?? DEFAULT_CHAT_CHANNEL;
|
||||
const channelId = normalizeChannelId(channelInput);
|
||||
if (!channelId) {
|
||||
throw new Error(`Unsupported channel: ${channelInput}`);
|
||||
}
|
||||
const plugin = getChannelPlugin(channelId);
|
||||
if (!plugin?.auth?.login) {
|
||||
throw new Error(`Channel ${channelId} does not support login`);
|
||||
}
|
||||
// Auth-only flow: do not mutate channel config here.
|
||||
setVerbose(Boolean(opts.verbose));
|
||||
const cfg = loadConfig();
|
||||
const accountId =
|
||||
opts.account?.trim() || resolveChannelDefaultAccountId({ plugin, cfg });
|
||||
await plugin.auth.login({
|
||||
cfg,
|
||||
accountId,
|
||||
runtime,
|
||||
verbose: Boolean(opts.verbose),
|
||||
channelInput,
|
||||
});
|
||||
}
|
||||
|
||||
export async function runChannelLogout(
|
||||
opts: ChannelAuthOptions,
|
||||
runtime: RuntimeEnv = defaultRuntime,
|
||||
) {
|
||||
const channelInput = opts.channel ?? DEFAULT_CHAT_CHANNEL;
|
||||
const channelId = normalizeChannelId(channelInput);
|
||||
if (!channelId) {
|
||||
throw new Error(`Unsupported channel: ${channelInput}`);
|
||||
}
|
||||
const plugin = getChannelPlugin(channelId);
|
||||
if (!plugin?.gateway?.logoutAccount) {
|
||||
throw new Error(`Channel ${channelId} does not support logout`);
|
||||
}
|
||||
// Auth-only flow: resolve account + clear session state only.
|
||||
const cfg = loadConfig();
|
||||
const accountId =
|
||||
opts.account?.trim() || resolveChannelDefaultAccountId({ plugin, cfg });
|
||||
const account = plugin.config.resolveAccount(cfg, accountId);
|
||||
await plugin.gateway.logoutAccount({
|
||||
cfg,
|
||||
accountId,
|
||||
account,
|
||||
runtime,
|
||||
});
|
||||
}
|
||||
@@ -1,22 +1,21 @@
|
||||
import type { Command } from "commander";
|
||||
|
||||
import { listChatChannels } from "../channels/registry.js";
|
||||
import {
|
||||
providersAddCommand,
|
||||
providersListCommand,
|
||||
providersLogsCommand,
|
||||
providersRemoveCommand,
|
||||
providersStatusCommand,
|
||||
} from "../commands/providers.js";
|
||||
channelsAddCommand,
|
||||
channelsListCommand,
|
||||
channelsLogsCommand,
|
||||
channelsRemoveCommand,
|
||||
channelsStatusCommand,
|
||||
} from "../commands/channels.js";
|
||||
import { danger } from "../globals.js";
|
||||
import { listChatProviders } from "../providers/registry.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { formatDocsLink } from "../terminal/links.js";
|
||||
import { theme } from "../terminal/theme.js";
|
||||
import { runChannelLogin, runChannelLogout } from "./channel-auth.js";
|
||||
import { hasExplicitOptions } from "./command-options.js";
|
||||
import { runProviderLogin, runProviderLogout } from "./provider-auth.js";
|
||||
|
||||
const optionNamesAdd = [
|
||||
"provider",
|
||||
"channel",
|
||||
"account",
|
||||
"name",
|
||||
"token",
|
||||
@@ -35,17 +34,16 @@ const optionNamesAdd = [
|
||||
"useEnv",
|
||||
] as const;
|
||||
|
||||
const optionNamesRemove = ["provider", "account", "delete"] as const;
|
||||
const optionNamesRemove = ["channel", "account", "delete"] as const;
|
||||
|
||||
const providerNames = listChatProviders()
|
||||
const channelNames = listChatChannels()
|
||||
.map((meta) => meta.id)
|
||||
.join("|");
|
||||
|
||||
export function registerProvidersCli(program: Command) {
|
||||
const providers = program
|
||||
.command("providers")
|
||||
.alias("provider")
|
||||
.description("Manage chat provider accounts")
|
||||
export function registerChannelsCli(program: Command) {
|
||||
const channels = program
|
||||
.command("channels")
|
||||
.description("Manage chat channel accounts")
|
||||
.addHelpText(
|
||||
"after",
|
||||
() =>
|
||||
@@ -55,54 +53,54 @@ export function registerProvidersCli(program: Command) {
|
||||
)}\n`,
|
||||
);
|
||||
|
||||
providers
|
||||
channels
|
||||
.command("list")
|
||||
.description("List configured providers + auth profiles")
|
||||
.option("--no-usage", "Skip provider usage/quota snapshots")
|
||||
.description("List configured channels + auth profiles")
|
||||
.option("--no-usage", "Skip model provider usage/quota snapshots")
|
||||
.option("--json", "Output JSON", false)
|
||||
.action(async (opts) => {
|
||||
try {
|
||||
await providersListCommand(opts, defaultRuntime);
|
||||
await channelsListCommand(opts, defaultRuntime);
|
||||
} catch (err) {
|
||||
defaultRuntime.error(String(err));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
providers
|
||||
channels
|
||||
.command("status")
|
||||
.description("Show gateway provider status (use status --deep for local)")
|
||||
.option("--probe", "Probe provider credentials", false)
|
||||
.description("Show gateway channel status (use status --deep for local)")
|
||||
.option("--probe", "Probe channel credentials", false)
|
||||
.option("--timeout <ms>", "Timeout in ms", "10000")
|
||||
.option("--json", "Output JSON", false)
|
||||
.action(async (opts) => {
|
||||
try {
|
||||
await providersStatusCommand(opts, defaultRuntime);
|
||||
await channelsStatusCommand(opts, defaultRuntime);
|
||||
} catch (err) {
|
||||
defaultRuntime.error(String(err));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
providers
|
||||
channels
|
||||
.command("logs")
|
||||
.description("Show recent provider logs from the gateway log file")
|
||||
.option("--provider <name>", `Provider (${providerNames}|all)`, "all")
|
||||
.description("Show recent channel logs from the gateway log file")
|
||||
.option("--channel <name>", `Channel (${channelNames}|all)`, "all")
|
||||
.option("--lines <n>", "Number of lines (default: 200)", "200")
|
||||
.option("--json", "Output JSON", false)
|
||||
.action(async (opts) => {
|
||||
try {
|
||||
await providersLogsCommand(opts, defaultRuntime);
|
||||
await channelsLogsCommand(opts, defaultRuntime);
|
||||
} catch (err) {
|
||||
defaultRuntime.error(String(err));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
providers
|
||||
channels
|
||||
.command("add")
|
||||
.description("Add or update a provider account")
|
||||
.option("--provider <name>", `Provider (${providerNames})`)
|
||||
.description("Add or update a channel account")
|
||||
.option("--channel <name>", `Channel (${channelNames})`)
|
||||
.option("--account <id>", "Account id (default when omitted)")
|
||||
.option("--name <name>", "Display name for this account")
|
||||
.option("--token <token>", "Bot token (Telegram/Discord)")
|
||||
@@ -122,67 +120,67 @@ export function registerProvidersCli(program: Command) {
|
||||
.action(async (opts, command) => {
|
||||
try {
|
||||
const hasFlags = hasExplicitOptions(command, optionNamesAdd);
|
||||
await providersAddCommand(opts, defaultRuntime, { hasFlags });
|
||||
await channelsAddCommand(opts, defaultRuntime, { hasFlags });
|
||||
} catch (err) {
|
||||
defaultRuntime.error(String(err));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
providers
|
||||
channels
|
||||
.command("remove")
|
||||
.description("Disable or delete a provider account")
|
||||
.option("--provider <name>", `Provider (${providerNames})`)
|
||||
.description("Disable or delete a channel account")
|
||||
.option("--channel <name>", `Channel (${channelNames})`)
|
||||
.option("--account <id>", "Account id (default when omitted)")
|
||||
.option("--delete", "Delete config entries (no prompt)", false)
|
||||
.action(async (opts, command) => {
|
||||
try {
|
||||
const hasFlags = hasExplicitOptions(command, optionNamesRemove);
|
||||
await providersRemoveCommand(opts, defaultRuntime, { hasFlags });
|
||||
await channelsRemoveCommand(opts, defaultRuntime, { hasFlags });
|
||||
} catch (err) {
|
||||
defaultRuntime.error(String(err));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
providers
|
||||
channels
|
||||
.command("login")
|
||||
.description("Link a provider account (WhatsApp Web only)")
|
||||
.option("--provider <provider>", "Provider alias (default: whatsapp)")
|
||||
.description("Link a channel account (WhatsApp Web only)")
|
||||
.option("--channel <channel>", "Channel alias (default: whatsapp)")
|
||||
.option("--account <id>", "WhatsApp account id (accountId)")
|
||||
.option("--verbose", "Verbose connection logs", false)
|
||||
.action(async (opts) => {
|
||||
try {
|
||||
await runProviderLogin(
|
||||
await runChannelLogin(
|
||||
{
|
||||
provider: opts.provider as string | undefined,
|
||||
channel: opts.channel as string | undefined,
|
||||
account: opts.account as string | undefined,
|
||||
verbose: Boolean(opts.verbose),
|
||||
},
|
||||
defaultRuntime,
|
||||
);
|
||||
} catch (err) {
|
||||
defaultRuntime.error(danger(`Provider login failed: ${String(err)}`));
|
||||
defaultRuntime.error(danger(`Channel login failed: ${String(err)}`));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
providers
|
||||
channels
|
||||
.command("logout")
|
||||
.description("Log out of a provider session (if supported)")
|
||||
.option("--provider <provider>", "Provider alias (default: whatsapp)")
|
||||
.description("Log out of a channel session (if supported)")
|
||||
.option("--channel <channel>", "Channel alias (default: whatsapp)")
|
||||
.option("--account <id>", "Account id (accountId)")
|
||||
.action(async (opts) => {
|
||||
try {
|
||||
await runProviderLogout(
|
||||
await runChannelLogout(
|
||||
{
|
||||
provider: opts.provider as string | undefined,
|
||||
channel: opts.channel as string | undefined,
|
||||
account: opts.account as string | undefined,
|
||||
},
|
||||
defaultRuntime,
|
||||
);
|
||||
} catch (err) {
|
||||
defaultRuntime.error(danger(`Provider logout failed: ${String(err)}`));
|
||||
defaultRuntime.error(danger(`Channel logout failed: ${String(err)}`));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
});
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { Command } from "commander";
|
||||
import { CHANNEL_IDS } from "../channels/registry.js";
|
||||
import { parseAbsoluteTimeMs } from "../cron/parse.js";
|
||||
import type { CronJob, CronSchedule } from "../cron/types.js";
|
||||
import { danger } from "../globals.js";
|
||||
import { PROVIDER_IDS } from "../providers/registry.js";
|
||||
import { normalizeAgentId } from "../routing/session-key.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { formatDocsLink } from "../terminal/links.js";
|
||||
@@ -10,7 +10,7 @@ import { colorize, isRich, theme } from "../terminal/theme.js";
|
||||
import type { GatewayRpcOpts } from "./gateway-rpc.js";
|
||||
import { addGatewayClientOptions, callGatewayFromCli } from "./gateway-rpc.js";
|
||||
|
||||
const CRON_PROVIDER_OPTIONS = ["last", ...PROVIDER_IDS].join("|");
|
||||
const CRON_CHANNEL_OPTIONS = ["last", ...CHANNEL_IDS].join("|");
|
||||
|
||||
async function warnIfCronSchedulerDisabled(opts: GatewayRpcOpts) {
|
||||
try {
|
||||
@@ -322,8 +322,8 @@ export function registerCronCli(program: Command) {
|
||||
.option("--timeout-seconds <n>", "Timeout seconds for agent jobs")
|
||||
.option("--deliver", "Deliver agent output", false)
|
||||
.option(
|
||||
"--provider <provider>",
|
||||
`Delivery provider (${CRON_PROVIDER_OPTIONS})`,
|
||||
"--channel <channel>",
|
||||
`Delivery channel (${CRON_CHANNEL_OPTIONS})`,
|
||||
"last",
|
||||
)
|
||||
.option(
|
||||
@@ -432,8 +432,7 @@ export function registerCronCli(program: Command) {
|
||||
? timeoutSeconds
|
||||
: undefined,
|
||||
deliver: Boolean(opts.deliver),
|
||||
provider:
|
||||
typeof opts.provider === "string" ? opts.provider : "last",
|
||||
channel: typeof opts.channel === "string" ? opts.channel : "last",
|
||||
to:
|
||||
typeof opts.to === "string" && opts.to.trim()
|
||||
? opts.to.trim()
|
||||
@@ -604,8 +603,8 @@ export function registerCronCli(program: Command) {
|
||||
.option("--timeout-seconds <n>", "Timeout seconds for agent jobs")
|
||||
.option("--deliver", "Deliver agent output", false)
|
||||
.option(
|
||||
"--provider <provider>",
|
||||
`Delivery provider (${CRON_PROVIDER_OPTIONS})`,
|
||||
"--channel <channel>",
|
||||
`Delivery channel (${CRON_CHANNEL_OPTIONS})`,
|
||||
)
|
||||
.option(
|
||||
"--to <dest>",
|
||||
@@ -717,8 +716,8 @@ export function registerCronCli(program: Command) {
|
||||
? timeoutSeconds
|
||||
: undefined,
|
||||
deliver: Boolean(opts.deliver),
|
||||
provider:
|
||||
typeof opts.provider === "string" ? opts.provider : undefined,
|
||||
channel:
|
||||
typeof opts.channel === "string" ? opts.channel : undefined,
|
||||
to: typeof opts.to === "string" ? opts.to : undefined,
|
||||
bestEffortDeliver: Boolean(opts.bestEffortDeliver),
|
||||
};
|
||||
|
||||
@@ -57,7 +57,7 @@ import { colorize, isRich, theme } from "../terminal/theme.js";
|
||||
import {
|
||||
GATEWAY_CLIENT_MODES,
|
||||
GATEWAY_CLIENT_NAMES,
|
||||
} from "../utils/message-provider.js";
|
||||
} from "../utils/message-channel.js";
|
||||
import { createDefaultDeps } from "./deps.js";
|
||||
import { withProgress } from "./progress.js";
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { logWebSelfId, sendMessageWhatsApp } from "../channels/web/index.js";
|
||||
import type { ClawdbotConfig } from "../config/config.js";
|
||||
import { sendMessageDiscord } from "../discord/send.js";
|
||||
import { sendMessageIMessage } from "../imessage/send.js";
|
||||
import type { OutboundSendDeps } from "../infra/outbound/deliver.js";
|
||||
import { sendMessageMSTeams } from "../msteams/send.js";
|
||||
import { logWebSelfId, sendMessageWhatsApp } from "../providers/web/index.js";
|
||||
import { sendMessageSignal } from "../signal/send.js";
|
||||
import { sendMessageSlack } from "../slack/send.js";
|
||||
import { sendMessageTelegram } from "../telegram/send.js";
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { Command } from "commander";
|
||||
import { resolveDefaultAgentWorkspaceDir } from "../agents/workspace.js";
|
||||
import { gatewayStatusCommand } from "../commands/gateway-status.js";
|
||||
import {
|
||||
formatHealthProviderLines,
|
||||
formatHealthChannelLines,
|
||||
type HealthSummary,
|
||||
} from "../commands/health.js";
|
||||
import { handleReset } from "../commands/onboard-helpers.js";
|
||||
@@ -47,7 +47,7 @@ import { colorize, isRich, theme } from "../terminal/theme.js";
|
||||
import {
|
||||
GATEWAY_CLIENT_MODES,
|
||||
GATEWAY_CLIENT_NAMES,
|
||||
} from "../utils/message-provider.js";
|
||||
} from "../utils/message-channel.js";
|
||||
import { resolveUserPath } from "../utils.js";
|
||||
import { forceFreePortAndWait } from "./ports.js";
|
||||
import { withProgress } from "./progress.js";
|
||||
@@ -958,10 +958,8 @@ export function registerGatewayCli(program: Command) {
|
||||
durationMs != null ? ` (${durationMs}ms)` : ""
|
||||
}`,
|
||||
);
|
||||
if (obj.providers && typeof obj.providers === "object") {
|
||||
for (const line of formatHealthProviderLines(
|
||||
obj as HealthSummary,
|
||||
)) {
|
||||
if (obj.channels && typeof obj.channels === "object") {
|
||||
for (const line of formatHealthChannelLines(obj as HealthSummary)) {
|
||||
defaultRuntime.log(line);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { callGateway } from "../gateway/call.js";
|
||||
import {
|
||||
GATEWAY_CLIENT_MODES,
|
||||
GATEWAY_CLIENT_NAMES,
|
||||
} from "../utils/message-provider.js";
|
||||
} from "../utils/message-channel.js";
|
||||
import { withProgress } from "./progress.js";
|
||||
|
||||
export type GatewayRpcOpts = {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { theme } from "../terminal/theme.js";
|
||||
import {
|
||||
GATEWAY_CLIENT_MODES,
|
||||
GATEWAY_CLIENT_NAMES,
|
||||
} from "../utils/message-provider.js";
|
||||
} from "../utils/message-channel.js";
|
||||
import {
|
||||
type CameraFacing,
|
||||
cameraTempPath,
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
import { Command } from "commander";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
const listProviderPairingRequests = vi.fn();
|
||||
const approveProviderPairingCode = vi.fn();
|
||||
const listChannelPairingRequests = vi.fn();
|
||||
const approveChannelPairingCode = vi.fn();
|
||||
const notifyPairingApproved = vi.fn();
|
||||
const pairingIdLabels: Record<string, string> = {
|
||||
telegram: "telegramUserId",
|
||||
discord: "discordUserId",
|
||||
};
|
||||
const requirePairingAdapter = vi.fn((provider: string) => ({
|
||||
idLabel: pairingIdLabels[provider] ?? "userId",
|
||||
const requirePairingAdapter = vi.fn((channel: string) => ({
|
||||
idLabel: pairingIdLabels[channel] ?? "userId",
|
||||
}));
|
||||
const listPairingProviders = vi.fn(() => ["telegram", "discord"]);
|
||||
const resolvePairingProvider = vi.fn((raw: string) => raw);
|
||||
const listPairingChannels = vi.fn(() => ["telegram", "discord"]);
|
||||
const resolvePairingChannel = vi.fn((raw: string) => raw);
|
||||
|
||||
vi.mock("../pairing/pairing-store.js", () => ({
|
||||
listProviderPairingRequests,
|
||||
approveProviderPairingCode,
|
||||
listChannelPairingRequests,
|
||||
approveChannelPairingCode,
|
||||
}));
|
||||
|
||||
vi.mock("../providers/plugins/pairing.js", () => ({
|
||||
listPairingProviders,
|
||||
resolvePairingProvider,
|
||||
vi.mock("../channels/plugins/pairing.js", () => ({
|
||||
listPairingChannels,
|
||||
resolvePairingChannel,
|
||||
notifyPairingApproved,
|
||||
requirePairingAdapter,
|
||||
}));
|
||||
@@ -33,7 +33,7 @@ vi.mock("../config/config.js", () => ({
|
||||
describe("pairing cli", () => {
|
||||
it("labels Telegram ids as telegramUserId", async () => {
|
||||
const { registerPairingCli } = await import("./pairing-cli.js");
|
||||
listProviderPairingRequests.mockResolvedValueOnce([
|
||||
listChannelPairingRequests.mockResolvedValueOnce([
|
||||
{
|
||||
id: "123",
|
||||
code: "ABC123",
|
||||
@@ -47,7 +47,7 @@ describe("pairing cli", () => {
|
||||
const program = new Command();
|
||||
program.name("test");
|
||||
registerPairingCli(program);
|
||||
await program.parseAsync(["pairing", "list", "--provider", "telegram"], {
|
||||
await program.parseAsync(["pairing", "list", "--channel", "telegram"], {
|
||||
from: "user",
|
||||
});
|
||||
expect(log).toHaveBeenCalledWith(
|
||||
@@ -55,21 +55,21 @@ describe("pairing cli", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("accepts provider as positional for list", async () => {
|
||||
it("accepts channel as positional for list", async () => {
|
||||
const { registerPairingCli } = await import("./pairing-cli.js");
|
||||
listProviderPairingRequests.mockResolvedValueOnce([]);
|
||||
listChannelPairingRequests.mockResolvedValueOnce([]);
|
||||
|
||||
const program = new Command();
|
||||
program.name("test");
|
||||
registerPairingCli(program);
|
||||
await program.parseAsync(["pairing", "list", "telegram"], { from: "user" });
|
||||
|
||||
expect(listProviderPairingRequests).toHaveBeenCalledWith("telegram");
|
||||
expect(listChannelPairingRequests).toHaveBeenCalledWith("telegram");
|
||||
});
|
||||
|
||||
it("labels Discord ids as discordUserId", async () => {
|
||||
const { registerPairingCli } = await import("./pairing-cli.js");
|
||||
listProviderPairingRequests.mockResolvedValueOnce([
|
||||
listChannelPairingRequests.mockResolvedValueOnce([
|
||||
{
|
||||
id: "999",
|
||||
code: "DEF456",
|
||||
@@ -83,7 +83,7 @@ describe("pairing cli", () => {
|
||||
const program = new Command();
|
||||
program.name("test");
|
||||
registerPairingCli(program);
|
||||
await program.parseAsync(["pairing", "list", "--provider", "discord"], {
|
||||
await program.parseAsync(["pairing", "list", "--channel", "discord"], {
|
||||
from: "user",
|
||||
});
|
||||
expect(log).toHaveBeenCalledWith(
|
||||
@@ -91,9 +91,9 @@ describe("pairing cli", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("accepts provider as positional for approve (npm-run compatible)", async () => {
|
||||
it("accepts channel as positional for approve (npm-run compatible)", async () => {
|
||||
const { registerPairingCli } = await import("./pairing-cli.js");
|
||||
approveProviderPairingCode.mockResolvedValueOnce({
|
||||
approveChannelPairingCode.mockResolvedValueOnce({
|
||||
id: "123",
|
||||
entry: {
|
||||
id: "123",
|
||||
@@ -111,8 +111,8 @@ describe("pairing cli", () => {
|
||||
from: "user",
|
||||
});
|
||||
|
||||
expect(approveProviderPairingCode).toHaveBeenCalledWith({
|
||||
provider: "telegram",
|
||||
expect(approveChannelPairingCode).toHaveBeenCalledWith({
|
||||
channel: "telegram",
|
||||
code: "ABCDEFGH",
|
||||
});
|
||||
expect(log).toHaveBeenCalledWith(expect.stringContaining("Approved"));
|
||||
|
||||
@@ -1,27 +1,26 @@
|
||||
import type { Command } from "commander";
|
||||
|
||||
import {
|
||||
listPairingChannels,
|
||||
notifyPairingApproved,
|
||||
resolvePairingChannel,
|
||||
} from "../channels/plugins/pairing.js";
|
||||
import { loadConfig } from "../config/config.js";
|
||||
import { resolvePairingIdLabel } from "../pairing/pairing-labels.js";
|
||||
import {
|
||||
approveProviderPairingCode,
|
||||
listProviderPairingRequests,
|
||||
type PairingProvider,
|
||||
approveChannelPairingCode,
|
||||
listChannelPairingRequests,
|
||||
type PairingChannel,
|
||||
} from "../pairing/pairing-store.js";
|
||||
import {
|
||||
listPairingProviders,
|
||||
notifyPairingApproved,
|
||||
resolvePairingProvider,
|
||||
} from "../providers/plugins/pairing.js";
|
||||
|
||||
const PROVIDERS: PairingProvider[] = listPairingProviders();
|
||||
const CHANNELS: PairingChannel[] = listPairingChannels();
|
||||
|
||||
function parseProvider(raw: unknown): PairingProvider {
|
||||
return resolvePairingProvider(raw);
|
||||
function parseChannel(raw: unknown): PairingChannel {
|
||||
return resolvePairingChannel(raw);
|
||||
}
|
||||
|
||||
async function notifyApproved(provider: PairingProvider, id: string) {
|
||||
async function notifyApproved(channel: PairingChannel, id: string) {
|
||||
const cfg = loadConfig();
|
||||
await notifyPairingApproved({ providerId: provider, id, cfg });
|
||||
await notifyPairingApproved({ channelId: channel, id, cfg });
|
||||
}
|
||||
|
||||
export function registerPairingCli(program: Command) {
|
||||
@@ -32,29 +31,29 @@ export function registerPairingCli(program: Command) {
|
||||
pairing
|
||||
.command("list")
|
||||
.description("List pending pairing requests")
|
||||
.option("--provider <provider>", `Provider (${PROVIDERS.join(", ")})`)
|
||||
.argument("[provider]", `Provider (${PROVIDERS.join(", ")})`)
|
||||
.option("--channel <channel>", `Channel (${CHANNELS.join(", ")})`)
|
||||
.argument("[channel]", `Channel (${CHANNELS.join(", ")})`)
|
||||
.option("--json", "Print JSON", false)
|
||||
.action(async (providerArg, opts) => {
|
||||
const providerRaw = opts.provider ?? providerArg;
|
||||
if (!providerRaw) {
|
||||
.action(async (channelArg, opts) => {
|
||||
const channelRaw = opts.channel ?? channelArg;
|
||||
if (!channelRaw) {
|
||||
throw new Error(
|
||||
`Provider required. Use --provider <provider> or pass it as the first argument (expected one of: ${PROVIDERS.join(", ")})`,
|
||||
`Channel required. Use --channel <channel> or pass it as the first argument (expected one of: ${CHANNELS.join(", ")})`,
|
||||
);
|
||||
}
|
||||
const provider = parseProvider(providerRaw);
|
||||
const requests = await listProviderPairingRequests(provider);
|
||||
const channel = parseChannel(channelRaw);
|
||||
const requests = await listChannelPairingRequests(channel);
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify({ provider, requests }, null, 2));
|
||||
console.log(JSON.stringify({ channel, requests }, null, 2));
|
||||
return;
|
||||
}
|
||||
if (requests.length === 0) {
|
||||
console.log(`No pending ${provider} pairing requests.`);
|
||||
console.log(`No pending ${channel} pairing requests.`);
|
||||
return;
|
||||
}
|
||||
for (const r of requests) {
|
||||
const meta = r.meta ? JSON.stringify(r.meta) : "";
|
||||
const idLabel = resolvePairingIdLabel(provider);
|
||||
const idLabel = resolvePairingIdLabel(channel);
|
||||
console.log(
|
||||
`${r.code} ${idLabel}=${r.id}${meta ? ` meta=${meta}` : ""} ${r.createdAt}`,
|
||||
);
|
||||
@@ -64,29 +63,26 @@ export function registerPairingCli(program: Command) {
|
||||
pairing
|
||||
.command("approve")
|
||||
.description("Approve a pairing code and allow that sender")
|
||||
.option("--provider <provider>", `Provider (${PROVIDERS.join(", ")})`)
|
||||
.argument(
|
||||
"<codeOrProvider>",
|
||||
"Pairing code (or provider when using 2 args)",
|
||||
)
|
||||
.argument("[code]", "Pairing code (when provider is passed as the 1st arg)")
|
||||
.option("--notify", "Notify the requester on the same provider", false)
|
||||
.action(async (codeOrProvider, code, opts) => {
|
||||
const providerRaw = opts.provider ?? codeOrProvider;
|
||||
const resolvedCode = opts.provider ? codeOrProvider : code;
|
||||
if (!opts.provider && !code) {
|
||||
.option("--channel <channel>", `Channel (${CHANNELS.join(", ")})`)
|
||||
.argument("<codeOrChannel>", "Pairing code (or channel when using 2 args)")
|
||||
.argument("[code]", "Pairing code (when channel is passed as the 1st arg)")
|
||||
.option("--notify", "Notify the requester on the same channel", false)
|
||||
.action(async (codeOrChannel, code, opts) => {
|
||||
const channelRaw = opts.channel ?? codeOrChannel;
|
||||
const resolvedCode = opts.channel ? codeOrChannel : code;
|
||||
if (!opts.channel && !code) {
|
||||
throw new Error(
|
||||
`Usage: clawdbot pairing approve <provider> <code> (or: clawdbot pairing approve --provider <provider> <code>)`,
|
||||
`Usage: clawdbot pairing approve <channel> <code> (or: clawdbot pairing approve --channel <channel> <code>)`,
|
||||
);
|
||||
}
|
||||
if (opts.provider && code != null) {
|
||||
if (opts.channel && code != null) {
|
||||
throw new Error(
|
||||
`Too many arguments. Use: clawdbot pairing approve --provider <provider> <code>`,
|
||||
`Too many arguments. Use: clawdbot pairing approve --channel <channel> <code>`,
|
||||
);
|
||||
}
|
||||
const provider = parseProvider(providerRaw);
|
||||
const approved = await approveProviderPairingCode({
|
||||
provider,
|
||||
const channel = parseChannel(channelRaw);
|
||||
const approved = await approveChannelPairingCode({
|
||||
channel,
|
||||
code: String(resolvedCode),
|
||||
});
|
||||
if (!approved) {
|
||||
@@ -95,10 +91,10 @@ export function registerPairingCli(program: Command) {
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`Approved ${provider} sender ${approved.id}.`);
|
||||
console.log(`Approved ${channel} sender ${approved.id}.`);
|
||||
|
||||
if (!opts.notify) return;
|
||||
await notifyApproved(provider, approved.id).catch((err) => {
|
||||
await notifyApproved(channel, approved.id).catch((err) => {
|
||||
console.log(`Failed to notify requester: ${String(err)}`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,8 +8,8 @@ const configureCommandWithSections = vi.fn();
|
||||
const setupCommand = vi.fn();
|
||||
const onboardCommand = vi.fn();
|
||||
const callGateway = vi.fn();
|
||||
const runProviderLogin = vi.fn();
|
||||
const runProviderLogout = vi.fn();
|
||||
const runChannelLogin = vi.fn();
|
||||
const runChannelLogout = vi.fn();
|
||||
const runTui = vi.fn();
|
||||
|
||||
const runtime = {
|
||||
@@ -30,7 +30,7 @@ vi.mock("../commands/configure.js", () => ({
|
||||
"model",
|
||||
"gateway",
|
||||
"daemon",
|
||||
"providers",
|
||||
"channels",
|
||||
"skills",
|
||||
"health",
|
||||
],
|
||||
@@ -40,9 +40,9 @@ vi.mock("../commands/configure.js", () => ({
|
||||
vi.mock("../commands/setup.js", () => ({ setupCommand }));
|
||||
vi.mock("../commands/onboard.js", () => ({ onboardCommand }));
|
||||
vi.mock("../runtime.js", () => ({ defaultRuntime: runtime }));
|
||||
vi.mock("./provider-auth.js", () => ({
|
||||
runProviderLogin,
|
||||
runProviderLogout,
|
||||
vi.mock("./channel-auth.js", () => ({
|
||||
runChannelLogin,
|
||||
runChannelLogout,
|
||||
}));
|
||||
vi.mock("../tui/tui.js", () => ({
|
||||
runTui,
|
||||
@@ -248,33 +248,24 @@ describe("cli program", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("runs providers login", async () => {
|
||||
it("runs channels login", async () => {
|
||||
const program = buildProgram();
|
||||
await program.parseAsync(["providers", "login", "--account", "work"], {
|
||||
await program.parseAsync(["channels", "login", "--account", "work"], {
|
||||
from: "user",
|
||||
});
|
||||
expect(runProviderLogin).toHaveBeenCalledWith(
|
||||
{ provider: undefined, account: "work", verbose: false },
|
||||
expect(runChannelLogin).toHaveBeenCalledWith(
|
||||
{ channel: undefined, account: "work", verbose: false },
|
||||
runtime,
|
||||
);
|
||||
});
|
||||
|
||||
it("runs providers logout", async () => {
|
||||
it("runs channels logout", async () => {
|
||||
const program = buildProgram();
|
||||
await program.parseAsync(["providers", "logout", "--account", "work"], {
|
||||
await program.parseAsync(["channels", "logout", "--account", "work"], {
|
||||
from: "user",
|
||||
});
|
||||
expect(runProviderLogout).toHaveBeenCalledWith(
|
||||
{ provider: undefined, account: "work" },
|
||||
runtime,
|
||||
);
|
||||
});
|
||||
|
||||
it("runs hidden login alias", async () => {
|
||||
const program = buildProgram();
|
||||
await program.parseAsync(["login", "--account", "work"], { from: "user" });
|
||||
expect(runProviderLogin).toHaveBeenCalledWith(
|
||||
{ provider: undefined, account: "work", verbose: false },
|
||||
expect(runChannelLogout).toHaveBeenCalledWith(
|
||||
{ channel: undefined, account: "work" },
|
||||
runtime,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Command } from "commander";
|
||||
import { listChannelPlugins } from "../channels/plugins/index.js";
|
||||
import { DEFAULT_CHAT_CHANNEL } from "../channels/registry.js";
|
||||
import { agentCliCommand } from "../commands/agent-via-gateway.js";
|
||||
import {
|
||||
agentsAddCommand,
|
||||
@@ -30,8 +32,6 @@ import {
|
||||
import { danger, setVerbose } from "../globals.js";
|
||||
import { autoMigrateLegacyState } from "../infra/state-migrations.js";
|
||||
import { registerPluginCliCommands } from "../plugins/cli.js";
|
||||
import { listProviderPlugins } from "../providers/plugins/index.js";
|
||||
import { DEFAULT_CHAT_PROVIDER } from "../providers/registry.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { formatDocsLink } from "../terminal/links.js";
|
||||
import { isRich, theme } from "../terminal/theme.js";
|
||||
@@ -42,6 +42,7 @@ import {
|
||||
hasEmittedCliBanner,
|
||||
} from "./banner.js";
|
||||
import { registerBrowserCli } from "./browser-cli.js";
|
||||
import { registerChannelsCli } from "./channels-cli.js";
|
||||
import { hasExplicitOptions } from "./command-options.js";
|
||||
import { registerCronCli } from "./cron-cli.js";
|
||||
import { registerDaemonCli } from "./daemon-cli.js";
|
||||
@@ -57,8 +58,6 @@ import { registerNodesCli } from "./nodes-cli.js";
|
||||
import { registerPairingCli } from "./pairing-cli.js";
|
||||
import { registerPluginsCli } from "./plugins-cli.js";
|
||||
import { forceFreePort } from "./ports.js";
|
||||
import { runProviderLogin, runProviderLogout } from "./provider-auth.js";
|
||||
import { registerProvidersCli } from "./providers-cli.js";
|
||||
import { registerSandboxCli } from "./sandbox-cli.js";
|
||||
import { registerSkillsCli } from "./skills-cli.js";
|
||||
import { registerTuiCli } from "./tui-cli.js";
|
||||
@@ -73,9 +72,9 @@ function collectOption(value: string, previous: string[] = []): string[] {
|
||||
export function buildProgram() {
|
||||
const program = new Command();
|
||||
const PROGRAM_VERSION = VERSION;
|
||||
const providerOptions = listProviderPlugins().map((plugin) => plugin.id);
|
||||
const messageProviderOptions = providerOptions.join("|");
|
||||
const agentProviderOptions = ["last", ...providerOptions].join("|");
|
||||
const channelOptions = listChannelPlugins().map((plugin) => plugin.id);
|
||||
const messageChannelOptions = channelOptions.join("|");
|
||||
const agentChannelOptions = ["last", ...channelOptions].join("|");
|
||||
|
||||
program
|
||||
.name("clawdbot")
|
||||
@@ -167,7 +166,7 @@ export function buildProgram() {
|
||||
});
|
||||
const examples = [
|
||||
[
|
||||
"clawdbot providers login --verbose",
|
||||
"clawdbot channels login --verbose",
|
||||
"Link personal WhatsApp Web and show QR + connection logs.",
|
||||
],
|
||||
[
|
||||
@@ -189,7 +188,7 @@ export function buildProgram() {
|
||||
"Talk directly to the agent using the Gateway; optionally send the WhatsApp reply.",
|
||||
],
|
||||
[
|
||||
'clawdbot message send --provider telegram --to @mychat --message "Hi"',
|
||||
'clawdbot message send --channel telegram --to @mychat --message "Hi"',
|
||||
"Send via your Telegram bot.",
|
||||
],
|
||||
] as const;
|
||||
@@ -304,7 +303,7 @@ export function buildProgram() {
|
||||
.option("--no-install-daemon", "Skip gateway daemon install")
|
||||
.option("--skip-daemon", "Skip gateway daemon install")
|
||||
.option("--daemon-runtime <runtime>", "Daemon runtime: node|bun")
|
||||
.option("--skip-providers", "Skip provider setup")
|
||||
.option("--skip-channels", "Skip channel setup")
|
||||
.option("--skip-skills", "Skip skills setup")
|
||||
.option("--skip-health", "Skip health check")
|
||||
.option("--skip-ui", "Skip Control UI/TUI prompts")
|
||||
@@ -385,7 +384,7 @@ export function buildProgram() {
|
||||
tailscaleResetOnExit: Boolean(opts.tailscaleResetOnExit),
|
||||
installDaemon,
|
||||
daemonRuntime: opts.daemonRuntime as "node" | "bun" | undefined,
|
||||
skipProviders: Boolean(opts.skipProviders),
|
||||
skipChannels: Boolean(opts.skipChannels),
|
||||
skipSkills: Boolean(opts.skipSkills),
|
||||
skipHealth: Boolean(opts.skipHealth),
|
||||
skipUi: Boolean(opts.skipUi),
|
||||
@@ -404,7 +403,7 @@ export function buildProgram() {
|
||||
.command("configure")
|
||||
.alias("config")
|
||||
.description(
|
||||
"Interactive wizard to update models, providers, skills, and gateway",
|
||||
"Interactive wizard to update models, channels, skills, and gateway",
|
||||
)
|
||||
.option(
|
||||
"--section <name>",
|
||||
@@ -446,7 +445,7 @@ export function buildProgram() {
|
||||
|
||||
program
|
||||
.command("doctor")
|
||||
.description("Health checks + quick fixes for the gateway and providers")
|
||||
.description("Health checks + quick fixes for the gateway and channels")
|
||||
.option(
|
||||
"--no-workspace-suggestions",
|
||||
"Disable workspace memory system suggestions",
|
||||
@@ -559,52 +558,9 @@ export function buildProgram() {
|
||||
}
|
||||
});
|
||||
|
||||
// Deprecated hidden aliases: use `clawdbot providers login/logout`. Remove in a future major.
|
||||
program
|
||||
.command("login", { hidden: true })
|
||||
.description("Link your personal WhatsApp via QR (web provider)")
|
||||
.option("--verbose", "Verbose connection logs", false)
|
||||
.option("--provider <provider>", "Provider alias (default: whatsapp)")
|
||||
.option("--account <id>", "WhatsApp account id (accountId)")
|
||||
.action(async (opts) => {
|
||||
try {
|
||||
await runProviderLogin(
|
||||
{
|
||||
provider: opts.provider as string | undefined,
|
||||
account: opts.account as string | undefined,
|
||||
verbose: Boolean(opts.verbose),
|
||||
},
|
||||
defaultRuntime,
|
||||
);
|
||||
} catch (err) {
|
||||
defaultRuntime.error(danger(`Web login failed: ${String(err)}`));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
program
|
||||
.command("logout", { hidden: true })
|
||||
.description("Log out of WhatsApp Web (keeps config)")
|
||||
.option("--provider <provider>", "Provider alias (default: whatsapp)")
|
||||
.option("--account <id>", "WhatsApp account id (accountId)")
|
||||
.action(async (opts) => {
|
||||
try {
|
||||
await runProviderLogout(
|
||||
{
|
||||
provider: opts.provider as string | undefined,
|
||||
account: opts.account as string | undefined,
|
||||
},
|
||||
defaultRuntime,
|
||||
);
|
||||
} catch (err) {
|
||||
defaultRuntime.error(danger(`Logout failed: ${String(err)}`));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
const message = program
|
||||
.command("message")
|
||||
.description("Send messages and provider actions")
|
||||
.description("Send messages and channel actions")
|
||||
.addHelpText(
|
||||
"after",
|
||||
() =>
|
||||
@@ -612,8 +568,8 @@ export function buildProgram() {
|
||||
Examples:
|
||||
clawdbot message send --to +15555550123 --message "Hi"
|
||||
clawdbot message send --to +15555550123 --message "Hi" --media photo.jpg
|
||||
clawdbot message poll --provider discord --to channel:123 --poll-question "Snack?" --poll-option Pizza --poll-option Sushi
|
||||
clawdbot message react --provider discord --to 123 --message-id 456 --emoji "✅"
|
||||
clawdbot message poll --channel discord --to channel:123 --poll-question "Snack?" --poll-option Pizza --poll-option Sushi
|
||||
clawdbot message react --channel discord --to 123 --message-id 456 --emoji "✅"
|
||||
|
||||
${theme.muted("Docs:")} ${formatDocsLink(
|
||||
"/cli/message",
|
||||
@@ -626,8 +582,8 @@ ${theme.muted("Docs:")} ${formatDocsLink(
|
||||
|
||||
const withMessageBase = (command: Command) =>
|
||||
command
|
||||
.option("--provider <provider>", `Provider: ${messageProviderOptions}`)
|
||||
.option("--account <id>", "Provider account id (accountId)")
|
||||
.option("--channel <channel>", `Channel: ${messageChannelOptions}`)
|
||||
.option("--account <id>", "Channel account id (accountId)")
|
||||
.option("--json", "Output result as JSON", false)
|
||||
.option("--dry-run", "Print payload and skip sending", false)
|
||||
.option("--verbose", "Verbose logging", false);
|
||||
@@ -1097,17 +1053,17 @@ ${theme.muted("Docs:")} ${formatDocsLink(
|
||||
)
|
||||
.option("--verbose <on|off>", "Persist agent verbose level for the session")
|
||||
.option(
|
||||
"--provider <provider>",
|
||||
`Delivery provider: ${agentProviderOptions} (default: ${DEFAULT_CHAT_PROVIDER})`,
|
||||
"--channel <channel>",
|
||||
`Delivery channel: ${agentChannelOptions} (default: ${DEFAULT_CHAT_CHANNEL})`,
|
||||
)
|
||||
.option(
|
||||
"--local",
|
||||
"Run the embedded agent locally (requires provider API keys in your shell)",
|
||||
"Run the embedded agent locally (requires model provider API keys in your shell)",
|
||||
false,
|
||||
)
|
||||
.option(
|
||||
"--deliver",
|
||||
"Send the agent's reply back to the selected provider (requires --to)",
|
||||
"Send the agent's reply back to the selected channel (requires --to)",
|
||||
false,
|
||||
)
|
||||
.option("--json", "Output result as JSON", false)
|
||||
@@ -1172,8 +1128,8 @@ ${theme.muted("Docs:")} ${formatDocsLink(
|
||||
.option("--model <id>", "Model id for this agent")
|
||||
.option("--agent-dir <dir>", "Agent state directory for this agent")
|
||||
.option(
|
||||
"--bind <provider[:accountId]>",
|
||||
"Route provider binding (repeatable)",
|
||||
"--bind <channel[:accountId]>",
|
||||
"Route channel binding (repeatable)",
|
||||
collectOption,
|
||||
[],
|
||||
)
|
||||
@@ -1253,20 +1209,20 @@ ${theme.muted("Docs:")} ${formatDocsLink(
|
||||
registerHooksCli(program);
|
||||
registerPairingCli(program);
|
||||
registerPluginsCli(program);
|
||||
registerProvidersCli(program);
|
||||
registerChannelsCli(program);
|
||||
registerSkillsCli(program);
|
||||
registerUpdateCli(program);
|
||||
registerPluginCliCommands(program, loadConfig());
|
||||
|
||||
program
|
||||
.command("status")
|
||||
.description("Show provider health and recent session recipients")
|
||||
.description("Show channel health and recent session recipients")
|
||||
.option("--json", "Output JSON instead of text", false)
|
||||
.option("--all", "Full diagnosis (read-only, pasteable)", false)
|
||||
.option("--usage", "Show provider usage/quota snapshots", false)
|
||||
.option("--usage", "Show model provider usage/quota snapshots", false)
|
||||
.option(
|
||||
"--deep",
|
||||
"Probe providers (WhatsApp Web + Telegram + Discord + Slack + Signal)",
|
||||
"Probe channels (WhatsApp Web + Telegram + Discord + Slack + Signal)",
|
||||
false,
|
||||
)
|
||||
.option("--timeout <ms>", "Probe timeout in milliseconds", "10000")
|
||||
@@ -1279,10 +1235,10 @@ Examples:
|
||||
clawdbot status # show linked account + session store summary
|
||||
clawdbot status --all # full diagnosis (read-only)
|
||||
clawdbot status --json # machine-readable output
|
||||
clawdbot status --usage # show provider usage/quota snapshots
|
||||
clawdbot status --deep # run provider probes (WA + Telegram + Discord + Slack + Signal)
|
||||
clawdbot status --usage # show model provider usage/quota snapshots
|
||||
clawdbot status --deep # run channel probes (WA + Telegram + Discord + Slack + Signal)
|
||||
clawdbot status --deep --timeout 5000 # tighten probe timeout
|
||||
clawdbot providers status # gateway provider runtime + probes`,
|
||||
clawdbot channels status # gateway channel runtime + probes`,
|
||||
)
|
||||
.action(async (opts) => {
|
||||
const verbose = Boolean(opts.verbose || opts.debug);
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
import { loadConfig } from "../config/config.js";
|
||||
import { setVerbose } from "../globals.js";
|
||||
import { resolveProviderDefaultAccountId } from "../providers/plugins/helpers.js";
|
||||
import {
|
||||
getProviderPlugin,
|
||||
normalizeProviderId,
|
||||
} from "../providers/plugins/index.js";
|
||||
import { DEFAULT_CHAT_PROVIDER } from "../providers/registry.js";
|
||||
import { defaultRuntime, type RuntimeEnv } from "../runtime.js";
|
||||
|
||||
type ProviderAuthOptions = {
|
||||
provider?: string;
|
||||
account?: string;
|
||||
verbose?: boolean;
|
||||
};
|
||||
|
||||
export async function runProviderLogin(
|
||||
opts: ProviderAuthOptions,
|
||||
runtime: RuntimeEnv = defaultRuntime,
|
||||
) {
|
||||
const providerInput = opts.provider ?? DEFAULT_CHAT_PROVIDER;
|
||||
const providerId = normalizeProviderId(providerInput);
|
||||
if (!providerId) {
|
||||
throw new Error(`Unsupported provider: ${providerInput}`);
|
||||
}
|
||||
const plugin = getProviderPlugin(providerId);
|
||||
if (!plugin?.auth?.login) {
|
||||
throw new Error(`Provider ${providerId} does not support login`);
|
||||
}
|
||||
// Auth-only flow: do not mutate provider config here.
|
||||
setVerbose(Boolean(opts.verbose));
|
||||
const cfg = loadConfig();
|
||||
const accountId =
|
||||
opts.account?.trim() || resolveProviderDefaultAccountId({ plugin, cfg });
|
||||
await plugin.auth.login({
|
||||
cfg,
|
||||
accountId,
|
||||
runtime,
|
||||
verbose: Boolean(opts.verbose),
|
||||
providerInput,
|
||||
});
|
||||
}
|
||||
|
||||
export async function runProviderLogout(
|
||||
opts: ProviderAuthOptions,
|
||||
runtime: RuntimeEnv = defaultRuntime,
|
||||
) {
|
||||
const providerInput = opts.provider ?? DEFAULT_CHAT_PROVIDER;
|
||||
const providerId = normalizeProviderId(providerInput);
|
||||
if (!providerId) {
|
||||
throw new Error(`Unsupported provider: ${providerInput}`);
|
||||
}
|
||||
const plugin = getProviderPlugin(providerId);
|
||||
if (!plugin?.gateway?.logoutAccount) {
|
||||
throw new Error(`Provider ${providerId} does not support logout`);
|
||||
}
|
||||
// Auth-only flow: resolve account + clear session state only.
|
||||
const cfg = loadConfig();
|
||||
const accountId =
|
||||
opts.account?.trim() || resolveProviderDefaultAccountId({ plugin, cfg });
|
||||
const account = plugin.config.resolveAccount(cfg, accountId);
|
||||
await plugin.gateway.logoutAccount({
|
||||
cfg,
|
||||
accountId,
|
||||
account,
|
||||
runtime,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user