refactor: share ClickClack setup verification

This commit is contained in:
Shakker
2026-07-15 14:37:12 +01:00
committed by Shakker
parent a748634aa9
commit 3e352d6727
2 changed files with 115 additions and 40 deletions
+19 -40
View File
@@ -1,5 +1,4 @@
// ClickClack plugin module implements guided setup behavior.
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
import {
createStandardChannelSetupStatus,
createSetupTranslator,
@@ -10,31 +9,17 @@ import {
type ChannelSetupWizard,
} from "openclaw/plugin-sdk/setup";
import { listClickClackAccountIds, resolveClickClackAccount } from "./accounts.js";
import { createClickClackClient } from "./http-client.js";
import { resolveWorkspaceId } from "./resolve.js";
import {
applyClickClackCredentialConfig,
applyClickClackSetupConfigPatch,
normalizeClickClackBaseUrl,
} from "./setup-core.js";
import { checkClickClackSetupConnection } from "./setup-verify.js";
import type { CoreConfig, ResolvedClickClackAccount } from "./types.js";
const t = createSetupTranslator();
const channel = "clickclack" as const;
function isHttpStatus(error: unknown, status: number): boolean {
return (
typeof error === "object" &&
error !== null &&
"status" in error &&
(error as { status?: unknown }).status === status
);
}
function isWorkspaceNotFound(error: unknown): boolean {
return error instanceof Error && error.message.startsWith("ClickClack workspace not found:");
}
function hasConfiguredClickClackCredential(account: ResolvedClickClackAccount): boolean {
return (
hasConfiguredSecretInput(account.config.token) || Boolean(account.config.tokenFile?.trim())
@@ -166,39 +151,33 @@ export const clickClackSetupWizard: ChannelSetupWizard = {
},
],
finalize: async ({ cfg, accountId, credentialValues, prompter }) => {
const account = resolveClickClackAccount({
const result = await checkClickClackSetupConnection({
cfg: cfg as CoreConfig,
accountId,
token: credentialValues.token,
});
try {
const client = createClickClackClient({
baseUrl: account.baseUrl,
token: credentialValues.token || account.token,
});
const me = await client.me();
const workspaceId = await resolveWorkspaceId(client, account.workspace);
const workspaces = await client.workspaces();
const workspace = workspaces.find((candidate) => candidate.id === workspaceId);
if (!workspace) {
throw new Error(`ClickClack workspace not found: ${account.workspace}`);
}
if (result.status === "connected") {
await prompter.note(
t("wizard.clickclack.connected", {
handle: me.handle,
workspace: workspace.name,
handle: result.handle,
workspace: result.workspaceName,
}),
t("wizard.clickclack.connectionTitle"),
);
} catch (error) {
const message = isHttpStatus(error, 401)
? t("wizard.clickclack.invalidToken")
: isWorkspaceNotFound(error)
? t("wizard.clickclack.workspaceNotFound", { workspace: account.workspace })
: t("wizard.clickclack.connectionFailed", {
error: formatErrorMessage(error),
});
await prompter.note(message, t("wizard.clickclack.validationWarningTitle"));
return;
}
if (result.status === "skipped-env-token" || result.status === "skipped-unconfigured") {
return;
}
const message =
result.status === "invalid-token"
? t("wizard.clickclack.invalidToken")
: result.status === "workspace-not-found"
? t("wizard.clickclack.workspaceNotFound", { workspace: result.workspace })
: t("wizard.clickclack.connectionFailed", {
error: result.error,
});
await prompter.note(message, t("wizard.clickclack.validationWarningTitle"));
},
disable: (cfg) => setSetupChannelEnabled(cfg, channel, false),
};
+96
View File
@@ -0,0 +1,96 @@
// ClickClack plugin module implements shared setup connection verification.
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
import { hasConfiguredSecretInput } from "openclaw/plugin-sdk/setup";
import { resolveClickClackAccount } from "./accounts.js";
import { createClickClackClient } from "./http-client.js";
import { resolveWorkspaceId } from "./resolve.js";
import type { CoreConfig, ResolvedClickClackAccount } from "./types.js";
export type ClickClackSetupConnectionResult =
| { status: "connected"; handle: string; workspaceName: string }
| { status: "invalid-token" }
| { status: "workspace-not-found"; workspace: string }
| { status: "failed"; error: string }
| { status: "skipped-env-token" }
| { status: "skipped-unconfigured" };
function isHttpStatus(error: unknown, status: number): boolean {
return (
typeof error === "object" &&
error !== null &&
"status" in error &&
(error as { status?: unknown }).status === status
);
}
function isWorkspaceNotFound(error: unknown): boolean {
return error instanceof Error && error.message.startsWith("ClickClack workspace not found:");
}
function usesUnavailableImplicitEnvToken(
account: ResolvedClickClackAccount,
tokenOverride: string,
): boolean {
return (
account.accountId === DEFAULT_ACCOUNT_ID &&
Boolean(account.baseUrl && account.workspace) &&
!tokenOverride &&
!account.token &&
!hasConfiguredSecretInput(account.config.token) &&
!account.config.tokenFile?.trim()
);
}
export async function checkClickClackSetupConnection(params: {
cfg: CoreConfig;
accountId?: string;
token?: string;
}): Promise<ClickClackSetupConnectionResult> {
let workspaceInput = "";
try {
const account = resolveClickClackAccount({
cfg: params.cfg,
accountId: params.accountId,
});
workspaceInput = account.workspace;
const token = params.token?.trim() || account.token;
if (usesUnavailableImplicitEnvToken(account, token)) {
return { status: "skipped-env-token" };
}
if (!account.baseUrl || !account.workspace || !token) {
return { status: "skipped-unconfigured" };
}
const client = createClickClackClient({
baseUrl: account.baseUrl,
token,
});
const me = await client.me();
const workspaceId = await resolveWorkspaceId(client, account.workspace);
const workspaces = await client.workspaces();
const workspace = workspaces.find((candidate) => candidate.id === workspaceId);
if (!workspace) {
throw new Error(`ClickClack workspace not found: ${account.workspace}`);
}
return {
status: "connected",
handle: me.handle,
workspaceName: workspace.name,
};
} catch (error) {
if (isHttpStatus(error, 401)) {
return { status: "invalid-token" };
}
if (isWorkspaceNotFound(error)) {
return {
status: "workspace-not-found",
workspace: workspaceInput,
};
}
return {
status: "failed",
error: formatErrorMessage(error),
};
}
}