diff --git a/docs/cli/index.md b/docs/cli/index.md index d9f9361bb..94bdfe012 100644 --- a/docs/cli/index.md +++ b/docs/cli/index.md @@ -19,6 +19,7 @@ If you change the CLI code, update this doc. ## Output styling - 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. - Long-running commands show a progress indicator (OSC 9;4 when supported). @@ -50,6 +51,10 @@ clawdbot [--dev] [--profile ] status add remove + skills + list + info + check send poll agent @@ -237,6 +242,21 @@ clawdbot providers status --probe clawdbot status --deep ``` +### `skills` +List and inspect available skills plus readiness info. + +Subcommands: +- `skills list`: list skills (default when no subcommand). +- `skills info `: 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` Approve DM pairing requests across providers. diff --git a/src/commands/providers/add-mutators.ts b/src/commands/providers/add-mutators.ts new file mode 100644 index 000000000..e7f946b58 --- /dev/null +++ b/src/commands/providers/add-mutators.ts @@ -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)[provider] as + | { accounts?: Record } + | 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)[provider] as + | { name?: string; accounts?: Record> } + | undefined; + const baseName = base?.name?.trim(); + if (!baseName) return cfg; + const accounts: Record> = { + ...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)[key]; + const safeBase = + typeof baseConfig === "object" && baseConfig + ? (baseConfig as Record) + : {}; + return { + ...params.cfg, + [key]: { + ...safeBase, + name: trimmed, + }, + } as ClawdbotConfig; + } + const base = (params.cfg as Record)[key] as + | { name?: string; accounts?: Record> } + | undefined; + const baseAccounts: Record< + string, + Record + > = 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; +} diff --git a/src/commands/providers/add.ts b/src/commands/providers/add.ts index 66f9f94b5..416b52b75 100644 --- a/src/commands/providers/add.ts +++ b/src/commands/providers/add.ts @@ -1,8 +1,5 @@ -import { type ClawdbotConfig, writeConfigFile } from "../../config/config.js"; -import { - type ChatProviderId, - normalizeChatProviderId, -} from "../../providers/registry.js"; +import { writeConfigFile } from "../../config/config.js"; +import { normalizeChatProviderId } from "../../providers/registry.js"; import { DEFAULT_ACCOUNT_ID, normalizeAccountId, @@ -11,14 +8,16 @@ import { defaultRuntime, type RuntimeEnv } from "../../runtime.js"; import { createClackPrompter } from "../../wizard/clack-prompter.js"; import { setupProviders } from "../onboard-providers.js"; import type { ProviderChoice } from "../onboard-types.js"; +import { + applyAccountName, + applyProviderAccountConfig, +} from "./add-mutators.js"; import { providerLabel, requireValidConfig, shouldUseWizard, } from "./shared.js"; -type ChatProvider = ChatProviderId; - export type ProvidersAddOptions = { provider?: string; account?: string; @@ -39,342 +38,6 @@ export type ProvidersAddOptions = { useEnv?: boolean; }; -function providerHasAccounts(cfg: ClawdbotConfig, provider: ChatProvider) { - if (provider === "whatsapp") return true; - const base = (cfg as Record)[provider] as - | { accounts?: Record } - | 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)[provider] as - | { name?: string; accounts?: Record> } - | undefined; - const baseName = base?.name?.trim(); - if (!baseName) return cfg; - const accounts: Record> = { - ...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)[key]; - const safeBase = - typeof baseConfig === "object" && baseConfig - ? (baseConfig as Record) - : {}; - return { - ...params.cfg, - [key]: { - ...safeBase, - name: trimmed, - }, - } as ClawdbotConfig; - } - const base = (params.cfg as Record)[key] as - | { name?: string; accounts?: Record> } - | undefined; - const baseAccounts: Record< - string, - Record - > = 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( opts: ProvidersAddOptions, runtime: RuntimeEnv = defaultRuntime, diff --git a/src/commands/providers/list.ts b/src/commands/providers/list.ts index bc8797c18..5f1c41cd3 100644 --- a/src/commands/providers/list.ts +++ b/src/commands/providers/list.ts @@ -40,11 +40,7 @@ import { resolveWhatsAppAuthDir, } from "../../web/accounts.js"; import { webAuthExists } from "../../web/session.js"; -import { - formatAccountLabel, - providerLabel, - requireValidConfig, -} from "./shared.js"; +import { formatProviderAccountLabel, requireValidConfig } from "./shared.js"; export type ProvidersListOptions = { json?: boolean; @@ -116,12 +112,14 @@ export async function providersListCommand( > = { telegram: async (accountId) => { const account = resolveTelegramAccount({ cfg, accountId }); - return `- ${theme.accent(providerLabel("telegram"))} ${theme.heading( - formatAccountLabel({ - accountId, - name: account.name, - }), - )}: ${formatConfigured(Boolean(account.token))}, ${formatTokenSource( + const label = formatProviderAccountLabel({ + provider: "telegram", + accountId, + name: account.name, + providerStyle: theme.accent, + accountStyle: theme.heading, + }); + return `- ${label}: ${formatConfigured(Boolean(account.token))}, ${formatTokenSource( account.tokenSource, )}, ${formatEnabled(account.enabled)}`; }, @@ -129,12 +127,14 @@ export async function providersListCommand( const { authDir } = resolveWhatsAppAuthDir({ cfg, accountId }); const linked = await webAuthExists(authDir); const name = cfg.whatsapp?.accounts?.[accountId]?.name; - return `- ${theme.accent(providerLabel("whatsapp"))} ${theme.heading( - formatAccountLabel({ - accountId, - name, - }), - )}: ${formatLinked(linked)}, ${formatEnabled( + const label = formatProviderAccountLabel({ + provider: "whatsapp", + accountId, + name, + providerStyle: theme.accent, + accountStyle: theme.heading, + }); + return `- ${label}: ${formatLinked(linked)}, ${formatEnabled( cfg.whatsapp?.accounts?.[accountId]?.enabled ?? cfg.web?.enabled ?? true, @@ -142,24 +142,28 @@ export async function providersListCommand( }, discord: async (accountId) => { const account = resolveDiscordAccount({ cfg, accountId }); - return `- ${theme.accent(providerLabel("discord"))} ${theme.heading( - formatAccountLabel({ - accountId, - name: account.name, - }), - )}: ${formatConfigured(Boolean(account.token))}, ${formatTokenSource( + const label = formatProviderAccountLabel({ + provider: "discord", + accountId, + name: account.name, + providerStyle: theme.accent, + accountStyle: theme.heading, + }); + return `- ${label}: ${formatConfigured(Boolean(account.token))}, ${formatTokenSource( account.tokenSource, )}, ${formatEnabled(account.enabled)}`; }, slack: async (accountId) => { const account = resolveSlackAccount({ cfg, accountId }); const configured = Boolean(account.botToken && account.appToken); - return `- ${theme.accent(providerLabel("slack"))} ${theme.heading( - formatAccountLabel({ - accountId, - name: account.name, - }), - )}: ${formatConfigured(configured)}, ${formatSource( + const label = formatProviderAccountLabel({ + provider: "slack", + accountId, + name: account.name, + providerStyle: theme.accent, + accountStyle: theme.heading, + }); + return `- ${label}: ${formatConfigured(configured)}, ${formatSource( "bot", account.botTokenSource, )}, ${formatSource("app", account.appTokenSource)}, ${formatEnabled( @@ -168,23 +172,27 @@ export async function providersListCommand( }, signal: async (accountId) => { const account = resolveSignalAccount({ cfg, accountId }); - return `- ${theme.accent(providerLabel("signal"))} ${theme.heading( - formatAccountLabel({ - accountId, - name: account.name, - }), - )}: ${formatConfigured(account.configured)}, base=${theme.muted( + const label = formatProviderAccountLabel({ + provider: "signal", + accountId, + name: account.name, + providerStyle: theme.accent, + accountStyle: theme.heading, + }); + return `- ${label}: ${formatConfigured(account.configured)}, base=${theme.muted( account.baseUrl, )}, ${formatEnabled(account.enabled)}`; }, imessage: async (accountId) => { const account = resolveIMessageAccount({ cfg, accountId }); - return `- ${theme.accent(providerLabel("imessage"))} ${theme.heading( - formatAccountLabel({ - accountId, - name: account.name, - }), - )}: ${formatEnabled(account.enabled)}`; + const label = formatProviderAccountLabel({ + provider: "imessage", + accountId, + name: account.name, + providerStyle: theme.accent, + accountStyle: theme.heading, + }); + return `- ${label}: ${formatEnabled(account.enabled)}`; }, }; diff --git a/src/commands/providers/shared.ts b/src/commands/providers/shared.ts index ab9a589a6..66fce862a 100644 --- a/src/commands/providers/shared.ts +++ b/src/commands/providers/shared.ts @@ -42,6 +42,27 @@ export function formatAccountLabel(params: { export const providerLabel = (provider: ChatProvider) => 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 }) { return params?.hasFlags === false; } diff --git a/src/commands/providers/status.ts b/src/commands/providers/status.ts index 1b76d8bf4..5b51025f3 100644 --- a/src/commands/providers/status.ts +++ b/src/commands/providers/status.ts @@ -4,7 +4,7 @@ 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 { type ChatProvider, formatAccountLabel } from "./shared.js"; +import { type ChatProvider, formatProviderAccountLabel } from "./shared.js"; export type ProvidersStatusOptions = { json?: boolean; @@ -18,7 +18,7 @@ export function formatGatewayProvidersStatusLines( const lines: string[] = []; lines.push(theme.success("Gateway reachable.")); const accountLines = ( - label: string, + provider: ChatProvider, accounts: Array>, ) => accounts.map((account) => { @@ -42,10 +42,11 @@ export function formatGatewayProvidersStatusLines( const accountId = typeof account.accountId === "string" ? account.accountId : "default"; const name = typeof account.name === "string" ? account.name.trim() : ""; - const labelText = `${label} ${formatAccountLabel({ + const labelText = formatProviderAccountLabel({ + provider, accountId, name: name || undefined, - })}`; + }); return `- ${labelText}: ${bits.join(", ")}`; }); @@ -75,7 +76,7 @@ export function formatGatewayProvidersStatusLines( for (const meta of listChatProviders()) { const accounts = accountPayloads[meta.id]; if (accounts && accounts.length > 0) { - lines.push(...accountLines(meta.label, accounts)); + lines.push(...accountLines(meta.id, accounts)); } }