refactor: centralize provider helpers
This commit is contained in:
@@ -19,6 +19,7 @@ If you change the CLI code, update this doc.
|
|||||||
## Output styling
|
## Output styling
|
||||||
|
|
||||||
- ANSI colors and progress indicators only render in TTY sessions.
|
- ANSI colors and progress indicators only render in TTY sessions.
|
||||||
|
- OSC-8 hyperlinks render as clickable links in supported terminals; otherwise we fall back to plain URLs.
|
||||||
- `--json` (and `--plain` where supported) disables styling for clean output.
|
- `--json` (and `--plain` where supported) disables styling for clean output.
|
||||||
- Long-running commands show a progress indicator (OSC 9;4 when supported).
|
- Long-running commands show a progress indicator (OSC 9;4 when supported).
|
||||||
|
|
||||||
@@ -50,6 +51,10 @@ clawdbot [--dev] [--profile <name>] <command>
|
|||||||
status
|
status
|
||||||
add
|
add
|
||||||
remove
|
remove
|
||||||
|
skills
|
||||||
|
list
|
||||||
|
info
|
||||||
|
check
|
||||||
send
|
send
|
||||||
poll
|
poll
|
||||||
agent
|
agent
|
||||||
@@ -237,6 +242,21 @@ clawdbot providers status --probe
|
|||||||
clawdbot status --deep
|
clawdbot status --deep
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `skills`
|
||||||
|
List and inspect available skills plus readiness info.
|
||||||
|
|
||||||
|
Subcommands:
|
||||||
|
- `skills list`: list skills (default when no subcommand).
|
||||||
|
- `skills info <name>`: show details for one skill.
|
||||||
|
- `skills check`: summary of ready vs missing requirements.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
- `--eligible`: show only ready skills.
|
||||||
|
- `--json`: output JSON (no styling).
|
||||||
|
- `-v`, `--verbose`: include missing requirements detail.
|
||||||
|
|
||||||
|
Tip: use `npx clawdhub` to search, install, and sync skills.
|
||||||
|
|
||||||
### `pairing`
|
### `pairing`
|
||||||
Approve DM pairing requests across providers.
|
Approve DM pairing requests across providers.
|
||||||
|
|
||||||
|
|||||||
344
src/commands/providers/add-mutators.ts
Normal file
344
src/commands/providers/add-mutators.ts
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
import type { ClawdbotConfig } from "../../config/config.js";
|
||||||
|
import type { ChatProviderId } from "../../providers/registry.js";
|
||||||
|
import {
|
||||||
|
DEFAULT_ACCOUNT_ID,
|
||||||
|
normalizeAccountId,
|
||||||
|
} from "../../routing/session-key.js";
|
||||||
|
|
||||||
|
type ChatProvider = ChatProviderId;
|
||||||
|
|
||||||
|
function providerHasAccounts(cfg: ClawdbotConfig, provider: ChatProvider) {
|
||||||
|
if (provider === "whatsapp") return true;
|
||||||
|
const base = (cfg as Record<string, unknown>)[provider] as
|
||||||
|
| { accounts?: Record<string, unknown> }
|
||||||
|
| undefined;
|
||||||
|
return Boolean(base?.accounts && Object.keys(base.accounts).length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldStoreNameInAccounts(
|
||||||
|
cfg: ClawdbotConfig,
|
||||||
|
provider: ChatProvider,
|
||||||
|
accountId: string,
|
||||||
|
): boolean {
|
||||||
|
if (provider === "whatsapp") return true;
|
||||||
|
if (accountId !== DEFAULT_ACCOUNT_ID) return true;
|
||||||
|
return providerHasAccounts(cfg, provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
function migrateBaseNameToDefaultAccount(
|
||||||
|
cfg: ClawdbotConfig,
|
||||||
|
provider: ChatProvider,
|
||||||
|
): ClawdbotConfig {
|
||||||
|
if (provider === "whatsapp") return cfg;
|
||||||
|
const base = (cfg as Record<string, unknown>)[provider] as
|
||||||
|
| { name?: string; accounts?: Record<string, Record<string, unknown>> }
|
||||||
|
| undefined;
|
||||||
|
const baseName = base?.name?.trim();
|
||||||
|
if (!baseName) return cfg;
|
||||||
|
const accounts: Record<string, Record<string, unknown>> = {
|
||||||
|
...base?.accounts,
|
||||||
|
};
|
||||||
|
const defaultAccount = accounts[DEFAULT_ACCOUNT_ID] ?? {};
|
||||||
|
if (!defaultAccount.name) {
|
||||||
|
accounts[DEFAULT_ACCOUNT_ID] = { ...defaultAccount, name: baseName };
|
||||||
|
}
|
||||||
|
const { name: _ignored, ...rest } = base ?? {};
|
||||||
|
return {
|
||||||
|
...cfg,
|
||||||
|
[provider]: {
|
||||||
|
...rest,
|
||||||
|
accounts,
|
||||||
|
},
|
||||||
|
} as ClawdbotConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyAccountName(params: {
|
||||||
|
cfg: ClawdbotConfig;
|
||||||
|
provider: ChatProvider;
|
||||||
|
accountId: string;
|
||||||
|
name?: string;
|
||||||
|
}): ClawdbotConfig {
|
||||||
|
const trimmed = params.name?.trim();
|
||||||
|
if (!trimmed) return params.cfg;
|
||||||
|
const accountId = normalizeAccountId(params.accountId);
|
||||||
|
if (params.provider === "whatsapp") {
|
||||||
|
return {
|
||||||
|
...params.cfg,
|
||||||
|
whatsapp: {
|
||||||
|
...params.cfg.whatsapp,
|
||||||
|
accounts: {
|
||||||
|
...params.cfg.whatsapp?.accounts,
|
||||||
|
[accountId]: {
|
||||||
|
...params.cfg.whatsapp?.accounts?.[accountId],
|
||||||
|
name: trimmed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const key = params.provider;
|
||||||
|
const useAccounts = shouldStoreNameInAccounts(params.cfg, key, accountId);
|
||||||
|
if (!useAccounts && accountId === DEFAULT_ACCOUNT_ID) {
|
||||||
|
const baseConfig = (params.cfg as Record<string, unknown>)[key];
|
||||||
|
const safeBase =
|
||||||
|
typeof baseConfig === "object" && baseConfig
|
||||||
|
? (baseConfig as Record<string, unknown>)
|
||||||
|
: {};
|
||||||
|
return {
|
||||||
|
...params.cfg,
|
||||||
|
[key]: {
|
||||||
|
...safeBase,
|
||||||
|
name: trimmed,
|
||||||
|
},
|
||||||
|
} as ClawdbotConfig;
|
||||||
|
}
|
||||||
|
const base = (params.cfg as Record<string, unknown>)[key] as
|
||||||
|
| { name?: string; accounts?: Record<string, Record<string, unknown>> }
|
||||||
|
| undefined;
|
||||||
|
const baseAccounts: Record<
|
||||||
|
string,
|
||||||
|
Record<string, unknown>
|
||||||
|
> = base?.accounts ?? {};
|
||||||
|
const existingAccount = baseAccounts[accountId] ?? {};
|
||||||
|
const baseWithoutName =
|
||||||
|
accountId === DEFAULT_ACCOUNT_ID
|
||||||
|
? (({ name: _ignored, ...rest }) => rest)(base ?? {})
|
||||||
|
: (base ?? {});
|
||||||
|
return {
|
||||||
|
...params.cfg,
|
||||||
|
[key]: {
|
||||||
|
...baseWithoutName,
|
||||||
|
accounts: {
|
||||||
|
...baseAccounts,
|
||||||
|
[accountId]: {
|
||||||
|
...existingAccount,
|
||||||
|
name: trimmed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as ClawdbotConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyProviderAccountConfig(params: {
|
||||||
|
cfg: ClawdbotConfig;
|
||||||
|
provider: ChatProvider;
|
||||||
|
accountId: string;
|
||||||
|
name?: string;
|
||||||
|
token?: string;
|
||||||
|
tokenFile?: string;
|
||||||
|
botToken?: string;
|
||||||
|
appToken?: string;
|
||||||
|
signalNumber?: string;
|
||||||
|
cliPath?: string;
|
||||||
|
dbPath?: string;
|
||||||
|
service?: "imessage" | "sms" | "auto";
|
||||||
|
region?: string;
|
||||||
|
authDir?: string;
|
||||||
|
httpUrl?: string;
|
||||||
|
httpHost?: string;
|
||||||
|
httpPort?: string;
|
||||||
|
useEnv?: boolean;
|
||||||
|
}): ClawdbotConfig {
|
||||||
|
const accountId = normalizeAccountId(params.accountId);
|
||||||
|
const name = params.name?.trim() || undefined;
|
||||||
|
const namedConfig = applyAccountName({
|
||||||
|
cfg: params.cfg,
|
||||||
|
provider: params.provider,
|
||||||
|
accountId,
|
||||||
|
name,
|
||||||
|
});
|
||||||
|
const next =
|
||||||
|
accountId !== DEFAULT_ACCOUNT_ID
|
||||||
|
? migrateBaseNameToDefaultAccount(namedConfig, params.provider)
|
||||||
|
: namedConfig;
|
||||||
|
|
||||||
|
if (params.provider === "whatsapp") {
|
||||||
|
const entry = {
|
||||||
|
...next.whatsapp?.accounts?.[accountId],
|
||||||
|
...(params.authDir ? { authDir: params.authDir } : {}),
|
||||||
|
enabled: true,
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
...next,
|
||||||
|
whatsapp: {
|
||||||
|
...next.whatsapp,
|
||||||
|
accounts: {
|
||||||
|
...next.whatsapp?.accounts,
|
||||||
|
[accountId]: entry,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.provider === "telegram") {
|
||||||
|
if (accountId === DEFAULT_ACCOUNT_ID) {
|
||||||
|
return {
|
||||||
|
...next,
|
||||||
|
telegram: {
|
||||||
|
...next.telegram,
|
||||||
|
enabled: true,
|
||||||
|
...(params.useEnv
|
||||||
|
? {}
|
||||||
|
: params.tokenFile
|
||||||
|
? { tokenFile: params.tokenFile }
|
||||||
|
: params.token
|
||||||
|
? { botToken: params.token }
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...next,
|
||||||
|
telegram: {
|
||||||
|
...next.telegram,
|
||||||
|
enabled: true,
|
||||||
|
accounts: {
|
||||||
|
...next.telegram?.accounts,
|
||||||
|
[accountId]: {
|
||||||
|
...next.telegram?.accounts?.[accountId],
|
||||||
|
enabled: true,
|
||||||
|
...(params.tokenFile
|
||||||
|
? { tokenFile: params.tokenFile }
|
||||||
|
: params.token
|
||||||
|
? { botToken: params.token }
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.provider === "discord") {
|
||||||
|
if (accountId === DEFAULT_ACCOUNT_ID) {
|
||||||
|
return {
|
||||||
|
...next,
|
||||||
|
discord: {
|
||||||
|
...next.discord,
|
||||||
|
enabled: true,
|
||||||
|
...(params.useEnv ? {} : params.token ? { token: params.token } : {}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...next,
|
||||||
|
discord: {
|
||||||
|
...next.discord,
|
||||||
|
enabled: true,
|
||||||
|
accounts: {
|
||||||
|
...next.discord?.accounts,
|
||||||
|
[accountId]: {
|
||||||
|
...next.discord?.accounts?.[accountId],
|
||||||
|
enabled: true,
|
||||||
|
...(params.token ? { token: params.token } : {}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.provider === "slack") {
|
||||||
|
if (accountId === DEFAULT_ACCOUNT_ID) {
|
||||||
|
return {
|
||||||
|
...next,
|
||||||
|
slack: {
|
||||||
|
...next.slack,
|
||||||
|
enabled: true,
|
||||||
|
...(params.useEnv
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
|
...(params.botToken ? { botToken: params.botToken } : {}),
|
||||||
|
...(params.appToken ? { appToken: params.appToken } : {}),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...next,
|
||||||
|
slack: {
|
||||||
|
...next.slack,
|
||||||
|
enabled: true,
|
||||||
|
accounts: {
|
||||||
|
...next.slack?.accounts,
|
||||||
|
[accountId]: {
|
||||||
|
...next.slack?.accounts?.[accountId],
|
||||||
|
enabled: true,
|
||||||
|
...(params.botToken ? { botToken: params.botToken } : {}),
|
||||||
|
...(params.appToken ? { appToken: params.appToken } : {}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.provider === "signal") {
|
||||||
|
if (accountId === DEFAULT_ACCOUNT_ID) {
|
||||||
|
return {
|
||||||
|
...next,
|
||||||
|
signal: {
|
||||||
|
...next.signal,
|
||||||
|
enabled: true,
|
||||||
|
...(params.signalNumber ? { account: params.signalNumber } : {}),
|
||||||
|
...(params.cliPath ? { cliPath: params.cliPath } : {}),
|
||||||
|
...(params.httpUrl ? { httpUrl: params.httpUrl } : {}),
|
||||||
|
...(params.httpHost ? { httpHost: params.httpHost } : {}),
|
||||||
|
...(params.httpPort ? { httpPort: Number(params.httpPort) } : {}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...next,
|
||||||
|
signal: {
|
||||||
|
...next.signal,
|
||||||
|
enabled: true,
|
||||||
|
accounts: {
|
||||||
|
...next.signal?.accounts,
|
||||||
|
[accountId]: {
|
||||||
|
...next.signal?.accounts?.[accountId],
|
||||||
|
enabled: true,
|
||||||
|
...(params.signalNumber ? { account: params.signalNumber } : {}),
|
||||||
|
...(params.cliPath ? { cliPath: params.cliPath } : {}),
|
||||||
|
...(params.httpUrl ? { httpUrl: params.httpUrl } : {}),
|
||||||
|
...(params.httpHost ? { httpHost: params.httpHost } : {}),
|
||||||
|
...(params.httpPort ? { httpPort: Number(params.httpPort) } : {}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.provider === "imessage") {
|
||||||
|
if (accountId === DEFAULT_ACCOUNT_ID) {
|
||||||
|
return {
|
||||||
|
...next,
|
||||||
|
imessage: {
|
||||||
|
...next.imessage,
|
||||||
|
enabled: true,
|
||||||
|
...(params.cliPath ? { cliPath: params.cliPath } : {}),
|
||||||
|
...(params.dbPath ? { dbPath: params.dbPath } : {}),
|
||||||
|
...(params.service ? { service: params.service } : {}),
|
||||||
|
...(params.region ? { region: params.region } : {}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...next,
|
||||||
|
imessage: {
|
||||||
|
...next.imessage,
|
||||||
|
enabled: true,
|
||||||
|
accounts: {
|
||||||
|
...next.imessage?.accounts,
|
||||||
|
[accountId]: {
|
||||||
|
...next.imessage?.accounts?.[accountId],
|
||||||
|
enabled: true,
|
||||||
|
...(params.cliPath ? { cliPath: params.cliPath } : {}),
|
||||||
|
...(params.dbPath ? { dbPath: params.dbPath } : {}),
|
||||||
|
...(params.service ? { service: params.service } : {}),
|
||||||
|
...(params.region ? { region: params.region } : {}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return next;
|
||||||
|
}
|
||||||
@@ -1,8 +1,5 @@
|
|||||||
import { type ClawdbotConfig, writeConfigFile } from "../../config/config.js";
|
import { writeConfigFile } from "../../config/config.js";
|
||||||
import {
|
import { normalizeChatProviderId } from "../../providers/registry.js";
|
||||||
type ChatProviderId,
|
|
||||||
normalizeChatProviderId,
|
|
||||||
} from "../../providers/registry.js";
|
|
||||||
import {
|
import {
|
||||||
DEFAULT_ACCOUNT_ID,
|
DEFAULT_ACCOUNT_ID,
|
||||||
normalizeAccountId,
|
normalizeAccountId,
|
||||||
@@ -11,14 +8,16 @@ import { defaultRuntime, type RuntimeEnv } from "../../runtime.js";
|
|||||||
import { createClackPrompter } from "../../wizard/clack-prompter.js";
|
import { createClackPrompter } from "../../wizard/clack-prompter.js";
|
||||||
import { setupProviders } from "../onboard-providers.js";
|
import { setupProviders } from "../onboard-providers.js";
|
||||||
import type { ProviderChoice } from "../onboard-types.js";
|
import type { ProviderChoice } from "../onboard-types.js";
|
||||||
|
import {
|
||||||
|
applyAccountName,
|
||||||
|
applyProviderAccountConfig,
|
||||||
|
} from "./add-mutators.js";
|
||||||
import {
|
import {
|
||||||
providerLabel,
|
providerLabel,
|
||||||
requireValidConfig,
|
requireValidConfig,
|
||||||
shouldUseWizard,
|
shouldUseWizard,
|
||||||
} from "./shared.js";
|
} from "./shared.js";
|
||||||
|
|
||||||
type ChatProvider = ChatProviderId;
|
|
||||||
|
|
||||||
export type ProvidersAddOptions = {
|
export type ProvidersAddOptions = {
|
||||||
provider?: string;
|
provider?: string;
|
||||||
account?: string;
|
account?: string;
|
||||||
@@ -39,342 +38,6 @@ export type ProvidersAddOptions = {
|
|||||||
useEnv?: boolean;
|
useEnv?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
function providerHasAccounts(cfg: ClawdbotConfig, provider: ChatProvider) {
|
|
||||||
if (provider === "whatsapp") return true;
|
|
||||||
const base = (cfg as Record<string, unknown>)[provider] as
|
|
||||||
| { accounts?: Record<string, unknown> }
|
|
||||||
| undefined;
|
|
||||||
return Boolean(base?.accounts && Object.keys(base.accounts).length > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function shouldStoreNameInAccounts(
|
|
||||||
cfg: ClawdbotConfig,
|
|
||||||
provider: ChatProvider,
|
|
||||||
accountId: string,
|
|
||||||
): boolean {
|
|
||||||
if (provider === "whatsapp") return true;
|
|
||||||
if (accountId !== DEFAULT_ACCOUNT_ID) return true;
|
|
||||||
return providerHasAccounts(cfg, provider);
|
|
||||||
}
|
|
||||||
|
|
||||||
function migrateBaseNameToDefaultAccount(
|
|
||||||
cfg: ClawdbotConfig,
|
|
||||||
provider: ChatProvider,
|
|
||||||
): ClawdbotConfig {
|
|
||||||
if (provider === "whatsapp") return cfg;
|
|
||||||
const base = (cfg as Record<string, unknown>)[provider] as
|
|
||||||
| { name?: string; accounts?: Record<string, Record<string, unknown>> }
|
|
||||||
| undefined;
|
|
||||||
const baseName = base?.name?.trim();
|
|
||||||
if (!baseName) return cfg;
|
|
||||||
const accounts: Record<string, Record<string, unknown>> = {
|
|
||||||
...base?.accounts,
|
|
||||||
};
|
|
||||||
const defaultAccount = accounts[DEFAULT_ACCOUNT_ID] ?? {};
|
|
||||||
if (!defaultAccount.name) {
|
|
||||||
accounts[DEFAULT_ACCOUNT_ID] = { ...defaultAccount, name: baseName };
|
|
||||||
}
|
|
||||||
const { name: _ignored, ...rest } = base ?? {};
|
|
||||||
return {
|
|
||||||
...cfg,
|
|
||||||
[provider]: {
|
|
||||||
...rest,
|
|
||||||
accounts,
|
|
||||||
},
|
|
||||||
} as ClawdbotConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyAccountName(params: {
|
|
||||||
cfg: ClawdbotConfig;
|
|
||||||
provider: ChatProvider;
|
|
||||||
accountId: string;
|
|
||||||
name?: string;
|
|
||||||
}): ClawdbotConfig {
|
|
||||||
const trimmed = params.name?.trim();
|
|
||||||
if (!trimmed) return params.cfg;
|
|
||||||
const accountId = normalizeAccountId(params.accountId);
|
|
||||||
if (params.provider === "whatsapp") {
|
|
||||||
return {
|
|
||||||
...params.cfg,
|
|
||||||
whatsapp: {
|
|
||||||
...params.cfg.whatsapp,
|
|
||||||
accounts: {
|
|
||||||
...params.cfg.whatsapp?.accounts,
|
|
||||||
[accountId]: {
|
|
||||||
...params.cfg.whatsapp?.accounts?.[accountId],
|
|
||||||
name: trimmed,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const key = params.provider;
|
|
||||||
const useAccounts = shouldStoreNameInAccounts(params.cfg, key, accountId);
|
|
||||||
if (!useAccounts && accountId === DEFAULT_ACCOUNT_ID) {
|
|
||||||
const baseConfig = (params.cfg as Record<string, unknown>)[key];
|
|
||||||
const safeBase =
|
|
||||||
typeof baseConfig === "object" && baseConfig
|
|
||||||
? (baseConfig as Record<string, unknown>)
|
|
||||||
: {};
|
|
||||||
return {
|
|
||||||
...params.cfg,
|
|
||||||
[key]: {
|
|
||||||
...safeBase,
|
|
||||||
name: trimmed,
|
|
||||||
},
|
|
||||||
} as ClawdbotConfig;
|
|
||||||
}
|
|
||||||
const base = (params.cfg as Record<string, unknown>)[key] as
|
|
||||||
| { name?: string; accounts?: Record<string, Record<string, unknown>> }
|
|
||||||
| undefined;
|
|
||||||
const baseAccounts: Record<
|
|
||||||
string,
|
|
||||||
Record<string, unknown>
|
|
||||||
> = base?.accounts ?? {};
|
|
||||||
const existingAccount = baseAccounts[accountId] ?? {};
|
|
||||||
const baseWithoutName =
|
|
||||||
accountId === DEFAULT_ACCOUNT_ID
|
|
||||||
? (({ name: _ignored, ...rest }) => rest)(base ?? {})
|
|
||||||
: (base ?? {});
|
|
||||||
return {
|
|
||||||
...params.cfg,
|
|
||||||
[key]: {
|
|
||||||
...baseWithoutName,
|
|
||||||
accounts: {
|
|
||||||
...baseAccounts,
|
|
||||||
[accountId]: {
|
|
||||||
...existingAccount,
|
|
||||||
name: trimmed,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as ClawdbotConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyProviderAccountConfig(params: {
|
|
||||||
cfg: ClawdbotConfig;
|
|
||||||
provider: ChatProvider;
|
|
||||||
accountId: string;
|
|
||||||
name?: string;
|
|
||||||
token?: string;
|
|
||||||
tokenFile?: string;
|
|
||||||
botToken?: string;
|
|
||||||
appToken?: string;
|
|
||||||
signalNumber?: string;
|
|
||||||
cliPath?: string;
|
|
||||||
dbPath?: string;
|
|
||||||
service?: "imessage" | "sms" | "auto";
|
|
||||||
region?: string;
|
|
||||||
authDir?: string;
|
|
||||||
httpUrl?: string;
|
|
||||||
httpHost?: string;
|
|
||||||
httpPort?: string;
|
|
||||||
useEnv?: boolean;
|
|
||||||
}): ClawdbotConfig {
|
|
||||||
const accountId = normalizeAccountId(params.accountId);
|
|
||||||
const name = params.name?.trim() || undefined;
|
|
||||||
const namedConfig = applyAccountName({
|
|
||||||
cfg: params.cfg,
|
|
||||||
provider: params.provider,
|
|
||||||
accountId,
|
|
||||||
name,
|
|
||||||
});
|
|
||||||
const next =
|
|
||||||
accountId !== DEFAULT_ACCOUNT_ID
|
|
||||||
? migrateBaseNameToDefaultAccount(namedConfig, params.provider)
|
|
||||||
: namedConfig;
|
|
||||||
|
|
||||||
if (params.provider === "whatsapp") {
|
|
||||||
const entry = {
|
|
||||||
...next.whatsapp?.accounts?.[accountId],
|
|
||||||
...(params.authDir ? { authDir: params.authDir } : {}),
|
|
||||||
enabled: true,
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
...next,
|
|
||||||
whatsapp: {
|
|
||||||
...next.whatsapp,
|
|
||||||
accounts: {
|
|
||||||
...next.whatsapp?.accounts,
|
|
||||||
[accountId]: entry,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.provider === "telegram") {
|
|
||||||
if (accountId === DEFAULT_ACCOUNT_ID) {
|
|
||||||
return {
|
|
||||||
...next,
|
|
||||||
telegram: {
|
|
||||||
...next.telegram,
|
|
||||||
enabled: true,
|
|
||||||
...(params.useEnv
|
|
||||||
? {}
|
|
||||||
: params.tokenFile
|
|
||||||
? { tokenFile: params.tokenFile }
|
|
||||||
: params.token
|
|
||||||
? { botToken: params.token }
|
|
||||||
: {}),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...next,
|
|
||||||
telegram: {
|
|
||||||
...next.telegram,
|
|
||||||
enabled: true,
|
|
||||||
accounts: {
|
|
||||||
...next.telegram?.accounts,
|
|
||||||
[accountId]: {
|
|
||||||
...next.telegram?.accounts?.[accountId],
|
|
||||||
enabled: true,
|
|
||||||
...(params.tokenFile
|
|
||||||
? { tokenFile: params.tokenFile }
|
|
||||||
: params.token
|
|
||||||
? { botToken: params.token }
|
|
||||||
: {}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.provider === "discord") {
|
|
||||||
if (accountId === DEFAULT_ACCOUNT_ID) {
|
|
||||||
return {
|
|
||||||
...next,
|
|
||||||
discord: {
|
|
||||||
...next.discord,
|
|
||||||
enabled: true,
|
|
||||||
...(params.useEnv ? {} : params.token ? { token: params.token } : {}),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...next,
|
|
||||||
discord: {
|
|
||||||
...next.discord,
|
|
||||||
enabled: true,
|
|
||||||
accounts: {
|
|
||||||
...next.discord?.accounts,
|
|
||||||
[accountId]: {
|
|
||||||
...next.discord?.accounts?.[accountId],
|
|
||||||
enabled: true,
|
|
||||||
...(params.token ? { token: params.token } : {}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.provider === "slack") {
|
|
||||||
if (accountId === DEFAULT_ACCOUNT_ID) {
|
|
||||||
return {
|
|
||||||
...next,
|
|
||||||
slack: {
|
|
||||||
...next.slack,
|
|
||||||
enabled: true,
|
|
||||||
...(params.useEnv
|
|
||||||
? {}
|
|
||||||
: {
|
|
||||||
...(params.botToken ? { botToken: params.botToken } : {}),
|
|
||||||
...(params.appToken ? { appToken: params.appToken } : {}),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...next,
|
|
||||||
slack: {
|
|
||||||
...next.slack,
|
|
||||||
enabled: true,
|
|
||||||
accounts: {
|
|
||||||
...next.slack?.accounts,
|
|
||||||
[accountId]: {
|
|
||||||
...next.slack?.accounts?.[accountId],
|
|
||||||
enabled: true,
|
|
||||||
...(params.botToken ? { botToken: params.botToken } : {}),
|
|
||||||
...(params.appToken ? { appToken: params.appToken } : {}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.provider === "signal") {
|
|
||||||
if (accountId === DEFAULT_ACCOUNT_ID) {
|
|
||||||
return {
|
|
||||||
...next,
|
|
||||||
signal: {
|
|
||||||
...next.signal,
|
|
||||||
enabled: true,
|
|
||||||
...(params.signalNumber ? { account: params.signalNumber } : {}),
|
|
||||||
...(params.cliPath ? { cliPath: params.cliPath } : {}),
|
|
||||||
...(params.httpUrl ? { httpUrl: params.httpUrl } : {}),
|
|
||||||
...(params.httpHost ? { httpHost: params.httpHost } : {}),
|
|
||||||
...(params.httpPort ? { httpPort: Number(params.httpPort) } : {}),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...next,
|
|
||||||
signal: {
|
|
||||||
...next.signal,
|
|
||||||
enabled: true,
|
|
||||||
accounts: {
|
|
||||||
...next.signal?.accounts,
|
|
||||||
[accountId]: {
|
|
||||||
...next.signal?.accounts?.[accountId],
|
|
||||||
enabled: true,
|
|
||||||
...(params.signalNumber ? { account: params.signalNumber } : {}),
|
|
||||||
...(params.cliPath ? { cliPath: params.cliPath } : {}),
|
|
||||||
...(params.httpUrl ? { httpUrl: params.httpUrl } : {}),
|
|
||||||
...(params.httpHost ? { httpHost: params.httpHost } : {}),
|
|
||||||
...(params.httpPort ? { httpPort: Number(params.httpPort) } : {}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.provider === "imessage") {
|
|
||||||
if (accountId === DEFAULT_ACCOUNT_ID) {
|
|
||||||
return {
|
|
||||||
...next,
|
|
||||||
imessage: {
|
|
||||||
...next.imessage,
|
|
||||||
enabled: true,
|
|
||||||
...(params.cliPath ? { cliPath: params.cliPath } : {}),
|
|
||||||
...(params.dbPath ? { dbPath: params.dbPath } : {}),
|
|
||||||
...(params.service ? { service: params.service } : {}),
|
|
||||||
...(params.region ? { region: params.region } : {}),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...next,
|
|
||||||
imessage: {
|
|
||||||
...next.imessage,
|
|
||||||
enabled: true,
|
|
||||||
accounts: {
|
|
||||||
...next.imessage?.accounts,
|
|
||||||
[accountId]: {
|
|
||||||
...next.imessage?.accounts?.[accountId],
|
|
||||||
enabled: true,
|
|
||||||
...(params.cliPath ? { cliPath: params.cliPath } : {}),
|
|
||||||
...(params.dbPath ? { dbPath: params.dbPath } : {}),
|
|
||||||
...(params.service ? { service: params.service } : {}),
|
|
||||||
...(params.region ? { region: params.region } : {}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return next;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function providersAddCommand(
|
export async function providersAddCommand(
|
||||||
opts: ProvidersAddOptions,
|
opts: ProvidersAddOptions,
|
||||||
runtime: RuntimeEnv = defaultRuntime,
|
runtime: RuntimeEnv = defaultRuntime,
|
||||||
|
|||||||
@@ -40,11 +40,7 @@ import {
|
|||||||
resolveWhatsAppAuthDir,
|
resolveWhatsAppAuthDir,
|
||||||
} from "../../web/accounts.js";
|
} from "../../web/accounts.js";
|
||||||
import { webAuthExists } from "../../web/session.js";
|
import { webAuthExists } from "../../web/session.js";
|
||||||
import {
|
import { formatProviderAccountLabel, requireValidConfig } from "./shared.js";
|
||||||
formatAccountLabel,
|
|
||||||
providerLabel,
|
|
||||||
requireValidConfig,
|
|
||||||
} from "./shared.js";
|
|
||||||
|
|
||||||
export type ProvidersListOptions = {
|
export type ProvidersListOptions = {
|
||||||
json?: boolean;
|
json?: boolean;
|
||||||
@@ -116,12 +112,14 @@ export async function providersListCommand(
|
|||||||
> = {
|
> = {
|
||||||
telegram: async (accountId) => {
|
telegram: async (accountId) => {
|
||||||
const account = resolveTelegramAccount({ cfg, accountId });
|
const account = resolveTelegramAccount({ cfg, accountId });
|
||||||
return `- ${theme.accent(providerLabel("telegram"))} ${theme.heading(
|
const label = formatProviderAccountLabel({
|
||||||
formatAccountLabel({
|
provider: "telegram",
|
||||||
accountId,
|
accountId,
|
||||||
name: account.name,
|
name: account.name,
|
||||||
}),
|
providerStyle: theme.accent,
|
||||||
)}: ${formatConfigured(Boolean(account.token))}, ${formatTokenSource(
|
accountStyle: theme.heading,
|
||||||
|
});
|
||||||
|
return `- ${label}: ${formatConfigured(Boolean(account.token))}, ${formatTokenSource(
|
||||||
account.tokenSource,
|
account.tokenSource,
|
||||||
)}, ${formatEnabled(account.enabled)}`;
|
)}, ${formatEnabled(account.enabled)}`;
|
||||||
},
|
},
|
||||||
@@ -129,12 +127,14 @@ export async function providersListCommand(
|
|||||||
const { authDir } = resolveWhatsAppAuthDir({ cfg, accountId });
|
const { authDir } = resolveWhatsAppAuthDir({ cfg, accountId });
|
||||||
const linked = await webAuthExists(authDir);
|
const linked = await webAuthExists(authDir);
|
||||||
const name = cfg.whatsapp?.accounts?.[accountId]?.name;
|
const name = cfg.whatsapp?.accounts?.[accountId]?.name;
|
||||||
return `- ${theme.accent(providerLabel("whatsapp"))} ${theme.heading(
|
const label = formatProviderAccountLabel({
|
||||||
formatAccountLabel({
|
provider: "whatsapp",
|
||||||
accountId,
|
accountId,
|
||||||
name,
|
name,
|
||||||
}),
|
providerStyle: theme.accent,
|
||||||
)}: ${formatLinked(linked)}, ${formatEnabled(
|
accountStyle: theme.heading,
|
||||||
|
});
|
||||||
|
return `- ${label}: ${formatLinked(linked)}, ${formatEnabled(
|
||||||
cfg.whatsapp?.accounts?.[accountId]?.enabled ??
|
cfg.whatsapp?.accounts?.[accountId]?.enabled ??
|
||||||
cfg.web?.enabled ??
|
cfg.web?.enabled ??
|
||||||
true,
|
true,
|
||||||
@@ -142,24 +142,28 @@ export async function providersListCommand(
|
|||||||
},
|
},
|
||||||
discord: async (accountId) => {
|
discord: async (accountId) => {
|
||||||
const account = resolveDiscordAccount({ cfg, accountId });
|
const account = resolveDiscordAccount({ cfg, accountId });
|
||||||
return `- ${theme.accent(providerLabel("discord"))} ${theme.heading(
|
const label = formatProviderAccountLabel({
|
||||||
formatAccountLabel({
|
provider: "discord",
|
||||||
accountId,
|
accountId,
|
||||||
name: account.name,
|
name: account.name,
|
||||||
}),
|
providerStyle: theme.accent,
|
||||||
)}: ${formatConfigured(Boolean(account.token))}, ${formatTokenSource(
|
accountStyle: theme.heading,
|
||||||
|
});
|
||||||
|
return `- ${label}: ${formatConfigured(Boolean(account.token))}, ${formatTokenSource(
|
||||||
account.tokenSource,
|
account.tokenSource,
|
||||||
)}, ${formatEnabled(account.enabled)}`;
|
)}, ${formatEnabled(account.enabled)}`;
|
||||||
},
|
},
|
||||||
slack: async (accountId) => {
|
slack: async (accountId) => {
|
||||||
const account = resolveSlackAccount({ cfg, accountId });
|
const account = resolveSlackAccount({ cfg, accountId });
|
||||||
const configured = Boolean(account.botToken && account.appToken);
|
const configured = Boolean(account.botToken && account.appToken);
|
||||||
return `- ${theme.accent(providerLabel("slack"))} ${theme.heading(
|
const label = formatProviderAccountLabel({
|
||||||
formatAccountLabel({
|
provider: "slack",
|
||||||
accountId,
|
accountId,
|
||||||
name: account.name,
|
name: account.name,
|
||||||
}),
|
providerStyle: theme.accent,
|
||||||
)}: ${formatConfigured(configured)}, ${formatSource(
|
accountStyle: theme.heading,
|
||||||
|
});
|
||||||
|
return `- ${label}: ${formatConfigured(configured)}, ${formatSource(
|
||||||
"bot",
|
"bot",
|
||||||
account.botTokenSource,
|
account.botTokenSource,
|
||||||
)}, ${formatSource("app", account.appTokenSource)}, ${formatEnabled(
|
)}, ${formatSource("app", account.appTokenSource)}, ${formatEnabled(
|
||||||
@@ -168,23 +172,27 @@ export async function providersListCommand(
|
|||||||
},
|
},
|
||||||
signal: async (accountId) => {
|
signal: async (accountId) => {
|
||||||
const account = resolveSignalAccount({ cfg, accountId });
|
const account = resolveSignalAccount({ cfg, accountId });
|
||||||
return `- ${theme.accent(providerLabel("signal"))} ${theme.heading(
|
const label = formatProviderAccountLabel({
|
||||||
formatAccountLabel({
|
provider: "signal",
|
||||||
accountId,
|
accountId,
|
||||||
name: account.name,
|
name: account.name,
|
||||||
}),
|
providerStyle: theme.accent,
|
||||||
)}: ${formatConfigured(account.configured)}, base=${theme.muted(
|
accountStyle: theme.heading,
|
||||||
|
});
|
||||||
|
return `- ${label}: ${formatConfigured(account.configured)}, base=${theme.muted(
|
||||||
account.baseUrl,
|
account.baseUrl,
|
||||||
)}, ${formatEnabled(account.enabled)}`;
|
)}, ${formatEnabled(account.enabled)}`;
|
||||||
},
|
},
|
||||||
imessage: async (accountId) => {
|
imessage: async (accountId) => {
|
||||||
const account = resolveIMessageAccount({ cfg, accountId });
|
const account = resolveIMessageAccount({ cfg, accountId });
|
||||||
return `- ${theme.accent(providerLabel("imessage"))} ${theme.heading(
|
const label = formatProviderAccountLabel({
|
||||||
formatAccountLabel({
|
provider: "imessage",
|
||||||
accountId,
|
accountId,
|
||||||
name: account.name,
|
name: account.name,
|
||||||
}),
|
providerStyle: theme.accent,
|
||||||
)}: ${formatEnabled(account.enabled)}`;
|
accountStyle: theme.heading,
|
||||||
|
});
|
||||||
|
return `- ${label}: ${formatEnabled(account.enabled)}`;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,27 @@ export function formatAccountLabel(params: {
|
|||||||
export const providerLabel = (provider: ChatProvider) =>
|
export const providerLabel = (provider: ChatProvider) =>
|
||||||
getChatProviderMeta(provider).label;
|
getChatProviderMeta(provider).label;
|
||||||
|
|
||||||
|
export function formatProviderAccountLabel(params: {
|
||||||
|
provider: ChatProvider;
|
||||||
|
accountId: string;
|
||||||
|
name?: string;
|
||||||
|
providerStyle?: (value: string) => string;
|
||||||
|
accountStyle?: (value: string) => string;
|
||||||
|
}): string {
|
||||||
|
const providerText = providerLabel(params.provider);
|
||||||
|
const accountText = formatAccountLabel({
|
||||||
|
accountId: params.accountId,
|
||||||
|
name: params.name,
|
||||||
|
});
|
||||||
|
const styledProvider = params.providerStyle
|
||||||
|
? params.providerStyle(providerText)
|
||||||
|
: providerText;
|
||||||
|
const styledAccount = params.accountStyle
|
||||||
|
? params.accountStyle(accountText)
|
||||||
|
: accountText;
|
||||||
|
return `${styledProvider} ${styledAccount}`;
|
||||||
|
}
|
||||||
|
|
||||||
export function shouldUseWizard(params?: { hasFlags?: boolean }) {
|
export function shouldUseWizard(params?: { hasFlags?: boolean }) {
|
||||||
return params?.hasFlags === false;
|
return params?.hasFlags === false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { listChatProviders } from "../../providers/registry.js";
|
|||||||
import { defaultRuntime, type RuntimeEnv } from "../../runtime.js";
|
import { defaultRuntime, type RuntimeEnv } from "../../runtime.js";
|
||||||
import { formatDocsLink } from "../../terminal/links.js";
|
import { formatDocsLink } from "../../terminal/links.js";
|
||||||
import { theme } from "../../terminal/theme.js";
|
import { theme } from "../../terminal/theme.js";
|
||||||
import { type ChatProvider, formatAccountLabel } from "./shared.js";
|
import { type ChatProvider, formatProviderAccountLabel } from "./shared.js";
|
||||||
|
|
||||||
export type ProvidersStatusOptions = {
|
export type ProvidersStatusOptions = {
|
||||||
json?: boolean;
|
json?: boolean;
|
||||||
@@ -18,7 +18,7 @@ export function formatGatewayProvidersStatusLines(
|
|||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
lines.push(theme.success("Gateway reachable."));
|
lines.push(theme.success("Gateway reachable."));
|
||||||
const accountLines = (
|
const accountLines = (
|
||||||
label: string,
|
provider: ChatProvider,
|
||||||
accounts: Array<Record<string, unknown>>,
|
accounts: Array<Record<string, unknown>>,
|
||||||
) =>
|
) =>
|
||||||
accounts.map((account) => {
|
accounts.map((account) => {
|
||||||
@@ -42,10 +42,11 @@ export function formatGatewayProvidersStatusLines(
|
|||||||
const accountId =
|
const accountId =
|
||||||
typeof account.accountId === "string" ? account.accountId : "default";
|
typeof account.accountId === "string" ? account.accountId : "default";
|
||||||
const name = typeof account.name === "string" ? account.name.trim() : "";
|
const name = typeof account.name === "string" ? account.name.trim() : "";
|
||||||
const labelText = `${label} ${formatAccountLabel({
|
const labelText = formatProviderAccountLabel({
|
||||||
|
provider,
|
||||||
accountId,
|
accountId,
|
||||||
name: name || undefined,
|
name: name || undefined,
|
||||||
})}`;
|
});
|
||||||
return `- ${labelText}: ${bits.join(", ")}`;
|
return `- ${labelText}: ${bits.join(", ")}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -75,7 +76,7 @@ export function formatGatewayProvidersStatusLines(
|
|||||||
for (const meta of listChatProviders()) {
|
for (const meta of listChatProviders()) {
|
||||||
const accounts = accountPayloads[meta.id];
|
const accounts = accountPayloads[meta.id];
|
||||||
if (accounts && accounts.length > 0) {
|
if (accounts && accounts.length > 0) {
|
||||||
lines.push(...accountLines(meta.label, accounts));
|
lines.push(...accountLines(meta.id, accounts));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user