mirror of https://github.com/openclaw/openclaw.git
refactor(tlon): share setup wizard base
This commit is contained in:
parent
d20363bcc9
commit
dd85ff4da7
|
|
@ -1,7 +1,11 @@
|
|||
import type { ChannelPlugin, OpenClawConfig } from "openclaw/plugin-sdk/tlon";
|
||||
import { tlonChannelConfigSchema } from "./config-schema.js";
|
||||
import { tlonSetupAdapter } from "./setup-core.js";
|
||||
import { applyTlonSetupConfig } from "./setup-core.js";
|
||||
import {
|
||||
applyTlonSetupConfig,
|
||||
createTlonSetupWizardBase,
|
||||
resolveTlonSetupConfigured,
|
||||
tlonSetupAdapter,
|
||||
} from "./setup-core.js";
|
||||
import { formatTargetHint, normalizeShip, parseTlonTarget } from "./targets.js";
|
||||
import { resolveTlonAccount, listTlonAccountIds } from "./types.js";
|
||||
import { validateUrbitBaseUrl } from "./urbit/base-url.js";
|
||||
|
|
@ -15,91 +19,21 @@ async function loadTlonChannelRuntime() {
|
|||
return tlonChannelRuntimePromise;
|
||||
}
|
||||
|
||||
const tlonSetupWizardProxy = {
|
||||
channel: "tlon",
|
||||
status: {
|
||||
configuredLabel: "configured",
|
||||
unconfiguredLabel: "needs setup",
|
||||
configuredHint: "configured",
|
||||
unconfiguredHint: "urbit messenger",
|
||||
configuredScore: 1,
|
||||
unconfiguredScore: 4,
|
||||
resolveConfigured: async ({ cfg }) =>
|
||||
await (await loadTlonChannelRuntime()).tlonSetupWizard.status.resolveConfigured({ cfg }),
|
||||
resolveStatusLines: async ({ cfg, configured }) =>
|
||||
(await (
|
||||
await loadTlonChannelRuntime()
|
||||
).tlonSetupWizard.status.resolveStatusLines?.({
|
||||
cfg,
|
||||
configured,
|
||||
})) ?? [],
|
||||
},
|
||||
introNote: {
|
||||
title: "Tlon setup",
|
||||
lines: [
|
||||
"You need your Urbit ship URL and login code.",
|
||||
"Example URL: https://your-ship-host",
|
||||
"Example ship: ~sampel-palnet",
|
||||
"If your ship URL is on a private network (LAN/localhost), you must explicitly allow it during setup.",
|
||||
"Docs: https://docs.openclaw.ai/channels/tlon",
|
||||
],
|
||||
},
|
||||
credentials: [],
|
||||
textInputs: [
|
||||
{
|
||||
inputKey: "ship",
|
||||
message: "Ship name",
|
||||
placeholder: "~sampel-palnet",
|
||||
currentValue: ({ cfg, accountId }) => resolveTlonAccount(cfg, accountId).ship ?? undefined,
|
||||
validate: ({ value }) => (String(value ?? "").trim() ? undefined : "Required"),
|
||||
normalizeValue: ({ value }) => normalizeShip(String(value).trim()),
|
||||
applySet: async ({ cfg, accountId, value }) =>
|
||||
applyTlonSetupConfig({
|
||||
cfg,
|
||||
accountId,
|
||||
input: { ship: value },
|
||||
}),
|
||||
},
|
||||
{
|
||||
inputKey: "url",
|
||||
message: "Ship URL",
|
||||
placeholder: "https://your-ship-host",
|
||||
currentValue: ({ cfg, accountId }) => resolveTlonAccount(cfg, accountId).url ?? undefined,
|
||||
validate: ({ value }) => {
|
||||
const next = validateUrbitBaseUrl(String(value ?? ""));
|
||||
if (!next.ok) {
|
||||
return next.error;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
normalizeValue: ({ value }) => String(value).trim(),
|
||||
applySet: async ({ cfg, accountId, value }) =>
|
||||
applyTlonSetupConfig({
|
||||
cfg,
|
||||
accountId,
|
||||
input: { url: value },
|
||||
}),
|
||||
},
|
||||
{
|
||||
inputKey: "code",
|
||||
message: "Login code",
|
||||
placeholder: "lidlut-tabwed-pillex-ridrup",
|
||||
currentValue: ({ cfg, accountId }) => resolveTlonAccount(cfg, accountId).code ?? undefined,
|
||||
validate: ({ value }) => (String(value ?? "").trim() ? undefined : "Required"),
|
||||
normalizeValue: ({ value }) => String(value).trim(),
|
||||
applySet: async ({ cfg, accountId, value }) =>
|
||||
applyTlonSetupConfig({
|
||||
cfg,
|
||||
accountId,
|
||||
input: { code: value },
|
||||
}),
|
||||
},
|
||||
],
|
||||
const tlonSetupWizardProxy = createTlonSetupWizardBase({
|
||||
resolveConfigured: async ({ cfg }) =>
|
||||
await (await loadTlonChannelRuntime()).tlonSetupWizard.status.resolveConfigured({ cfg }),
|
||||
resolveStatusLines: async ({ cfg, configured }) =>
|
||||
(await (
|
||||
await loadTlonChannelRuntime()
|
||||
).tlonSetupWizard.status.resolveStatusLines?.({
|
||||
cfg,
|
||||
configured,
|
||||
})) ?? [],
|
||||
finalize: async (params) =>
|
||||
await (
|
||||
await loadTlonChannelRuntime()
|
||||
).tlonSetupWizard.finalize!(params),
|
||||
} satisfies NonNullable<ChannelPlugin["setupWizard"]>;
|
||||
}) satisfies NonNullable<ChannelPlugin["setupWizard"]>;
|
||||
|
||||
export const tlonPlugin: ChannelPlugin = {
|
||||
id: TLON_CHANNEL_ID,
|
||||
|
|
|
|||
|
|
@ -1,14 +1,19 @@
|
|||
import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
formatDocsLink,
|
||||
normalizeAccountId,
|
||||
patchScopedAccountConfig,
|
||||
prepareScopedSetupConfig,
|
||||
type ChannelSetupAdapter,
|
||||
type ChannelSetupInput,
|
||||
type ChannelSetupWizard,
|
||||
type OpenClawConfig,
|
||||
type WizardPrompter,
|
||||
} from "openclaw/plugin-sdk/setup";
|
||||
import { buildTlonAccountFields } from "./account-fields.js";
|
||||
import { resolveTlonAccount } from "./types.js";
|
||||
import { normalizeShip } from "./targets.js";
|
||||
import { listTlonAccountIds, resolveTlonAccount, type TlonResolvedAccount } from "./types.js";
|
||||
import { validateUrbitBaseUrl } from "./urbit/base-url.js";
|
||||
|
||||
const channel = "tlon" as const;
|
||||
|
||||
|
|
@ -23,6 +28,115 @@ export type TlonSetupInput = ChannelSetupInput & {
|
|||
ownerShip?: string;
|
||||
};
|
||||
|
||||
function isConfigured(account: TlonResolvedAccount): boolean {
|
||||
return Boolean(account.ship && account.url && account.code);
|
||||
}
|
||||
|
||||
type TlonSetupWizardBaseParams = {
|
||||
resolveConfigured: (params: { cfg: OpenClawConfig }) => boolean | Promise<boolean>;
|
||||
resolveStatusLines?: (params: {
|
||||
cfg: OpenClawConfig;
|
||||
configured: boolean;
|
||||
}) => string[] | Promise<string[]>;
|
||||
finalize: (params: {
|
||||
cfg: OpenClawConfig;
|
||||
accountId: string;
|
||||
prompter: WizardPrompter;
|
||||
options?: Record<string, unknown>;
|
||||
}) => Promise<{ cfg: OpenClawConfig }>;
|
||||
};
|
||||
|
||||
export function createTlonSetupWizardBase(params: TlonSetupWizardBaseParams): ChannelSetupWizard {
|
||||
return {
|
||||
channel,
|
||||
status: {
|
||||
configuredLabel: "configured",
|
||||
unconfiguredLabel: "needs setup",
|
||||
configuredHint: "configured",
|
||||
unconfiguredHint: "urbit messenger",
|
||||
configuredScore: 1,
|
||||
unconfiguredScore: 4,
|
||||
resolveConfigured: ({ cfg }) => params.resolveConfigured({ cfg }),
|
||||
resolveStatusLines: ({ cfg, configured }) =>
|
||||
params.resolveStatusLines?.({ cfg, configured }) ?? [],
|
||||
},
|
||||
introNote: {
|
||||
title: "Tlon setup",
|
||||
lines: [
|
||||
"You need your Urbit ship URL and login code.",
|
||||
"Example URL: https://your-ship-host",
|
||||
"Example ship: ~sampel-palnet",
|
||||
"If your ship URL is on a private network (LAN/localhost), you must explicitly allow it during setup.",
|
||||
`Docs: ${formatDocsLink("/channels/tlon", "channels/tlon")}`,
|
||||
],
|
||||
},
|
||||
credentials: [],
|
||||
textInputs: [
|
||||
{
|
||||
inputKey: "ship",
|
||||
message: "Ship name",
|
||||
placeholder: "~sampel-palnet",
|
||||
currentValue: ({ cfg, accountId }) => resolveTlonAccount(cfg, accountId).ship ?? undefined,
|
||||
validate: ({ value }) => (String(value ?? "").trim() ? undefined : "Required"),
|
||||
normalizeValue: ({ value }) => normalizeShip(String(value).trim()),
|
||||
applySet: async ({ cfg, accountId, value }) =>
|
||||
applyTlonSetupConfig({
|
||||
cfg,
|
||||
accountId,
|
||||
input: { ship: value },
|
||||
}),
|
||||
},
|
||||
{
|
||||
inputKey: "url",
|
||||
message: "Ship URL",
|
||||
placeholder: "https://your-ship-host",
|
||||
currentValue: ({ cfg, accountId }) => resolveTlonAccount(cfg, accountId).url ?? undefined,
|
||||
validate: ({ value }) => {
|
||||
const next = validateUrbitBaseUrl(String(value ?? ""));
|
||||
if (!next.ok) {
|
||||
return next.error;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
normalizeValue: ({ value }) => String(value).trim(),
|
||||
applySet: async ({ cfg, accountId, value }) =>
|
||||
applyTlonSetupConfig({
|
||||
cfg,
|
||||
accountId,
|
||||
input: { url: value },
|
||||
}),
|
||||
},
|
||||
{
|
||||
inputKey: "code",
|
||||
message: "Login code",
|
||||
placeholder: "lidlut-tabwed-pillex-ridrup",
|
||||
currentValue: ({ cfg, accountId }) => resolveTlonAccount(cfg, accountId).code ?? undefined,
|
||||
validate: ({ value }) => (String(value ?? "").trim() ? undefined : "Required"),
|
||||
normalizeValue: ({ value }) => String(value).trim(),
|
||||
applySet: async ({ cfg, accountId, value }) =>
|
||||
applyTlonSetupConfig({
|
||||
cfg,
|
||||
accountId,
|
||||
input: { code: value },
|
||||
}),
|
||||
},
|
||||
],
|
||||
finalize: params.finalize,
|
||||
};
|
||||
}
|
||||
|
||||
export async function resolveTlonSetupConfigured(cfg: OpenClawConfig): Promise<boolean> {
|
||||
const accountIds = listTlonAccountIds(cfg);
|
||||
return accountIds.length > 0
|
||||
? accountIds.some((accountId) => isConfigured(resolveTlonAccount(cfg, accountId)))
|
||||
: isConfigured(resolveTlonAccount(cfg, DEFAULT_ACCOUNT_ID));
|
||||
}
|
||||
|
||||
export async function resolveTlonSetupStatusLines(cfg: OpenClawConfig): Promise<string[]> {
|
||||
const configured = await resolveTlonSetupConfigured(cfg);
|
||||
return [`Tlon: ${configured ? "configured" : "needs setup"}`];
|
||||
}
|
||||
|
||||
export function applyTlonSetupConfig(params: {
|
||||
cfg: OpenClawConfig;
|
||||
accountId: string;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/setup";
|
||||
import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
formatDocsLink,
|
||||
type ChannelSetupWizard,
|
||||
} from "openclaw/plugin-sdk/setup";
|
||||
import { applyTlonSetupConfig, type TlonSetupInput, tlonSetupAdapter } from "./setup-core.js";
|
||||
applyTlonSetupConfig,
|
||||
createTlonSetupWizardBase,
|
||||
resolveTlonSetupConfigured,
|
||||
resolveTlonSetupStatusLines,
|
||||
type TlonSetupInput,
|
||||
tlonSetupAdapter,
|
||||
} from "./setup-core.js";
|
||||
import { normalizeShip } from "./targets.js";
|
||||
import { listTlonAccountIds, resolveTlonAccount, type TlonResolvedAccount } from "./types.js";
|
||||
import { isBlockedUrbitHostname, validateUrbitBaseUrl } from "./urbit/base-url.js";
|
||||
|
|
@ -23,91 +26,9 @@ function parseList(value: string): string[] {
|
|||
|
||||
export { tlonSetupAdapter } from "./setup-core.js";
|
||||
|
||||
export const tlonSetupWizard: ChannelSetupWizard = {
|
||||
channel,
|
||||
status: {
|
||||
configuredLabel: "configured",
|
||||
unconfiguredLabel: "needs setup",
|
||||
configuredHint: "configured",
|
||||
unconfiguredHint: "urbit messenger",
|
||||
configuredScore: 1,
|
||||
unconfiguredScore: 4,
|
||||
resolveConfigured: ({ cfg }) => {
|
||||
const accountIds = listTlonAccountIds(cfg);
|
||||
return accountIds.length > 0
|
||||
? accountIds.some((accountId) => isConfigured(resolveTlonAccount(cfg, accountId)))
|
||||
: isConfigured(resolveTlonAccount(cfg, DEFAULT_ACCOUNT_ID));
|
||||
},
|
||||
resolveStatusLines: ({ cfg }) => {
|
||||
const accountIds = listTlonAccountIds(cfg);
|
||||
const configured =
|
||||
accountIds.length > 0
|
||||
? accountIds.some((accountId) => isConfigured(resolveTlonAccount(cfg, accountId)))
|
||||
: isConfigured(resolveTlonAccount(cfg, DEFAULT_ACCOUNT_ID));
|
||||
return [`Tlon: ${configured ? "configured" : "needs setup"}`];
|
||||
},
|
||||
},
|
||||
introNote: {
|
||||
title: "Tlon setup",
|
||||
lines: [
|
||||
"You need your Urbit ship URL and login code.",
|
||||
"Example URL: https://your-ship-host",
|
||||
"Example ship: ~sampel-palnet",
|
||||
"If your ship URL is on a private network (LAN/localhost), you must explicitly allow it during setup.",
|
||||
`Docs: ${formatDocsLink("/channels/tlon", "channels/tlon")}`,
|
||||
],
|
||||
},
|
||||
credentials: [],
|
||||
textInputs: [
|
||||
{
|
||||
inputKey: "ship",
|
||||
message: "Ship name",
|
||||
placeholder: "~sampel-palnet",
|
||||
currentValue: ({ cfg, accountId }) => resolveTlonAccount(cfg, accountId).ship ?? undefined,
|
||||
validate: ({ value }) => (String(value ?? "").trim() ? undefined : "Required"),
|
||||
normalizeValue: ({ value }) => normalizeShip(String(value).trim()),
|
||||
applySet: async ({ cfg, accountId, value }) =>
|
||||
applyTlonSetupConfig({
|
||||
cfg,
|
||||
accountId,
|
||||
input: { ship: value },
|
||||
}),
|
||||
},
|
||||
{
|
||||
inputKey: "url",
|
||||
message: "Ship URL",
|
||||
placeholder: "https://your-ship-host",
|
||||
currentValue: ({ cfg, accountId }) => resolveTlonAccount(cfg, accountId).url ?? undefined,
|
||||
validate: ({ value }) => {
|
||||
const next = validateUrbitBaseUrl(String(value ?? ""));
|
||||
if (!next.ok) {
|
||||
return next.error;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
normalizeValue: ({ value }) => String(value).trim(),
|
||||
applySet: async ({ cfg, accountId, value }) =>
|
||||
applyTlonSetupConfig({
|
||||
cfg,
|
||||
accountId,
|
||||
input: { url: value },
|
||||
}),
|
||||
},
|
||||
{
|
||||
inputKey: "code",
|
||||
message: "Login code",
|
||||
placeholder: "lidlut-tabwed-pillex-ridrup",
|
||||
currentValue: ({ cfg, accountId }) => resolveTlonAccount(cfg, accountId).code ?? undefined,
|
||||
validate: ({ value }) => (String(value ?? "").trim() ? undefined : "Required"),
|
||||
normalizeValue: ({ value }) => String(value).trim(),
|
||||
applySet: async ({ cfg, accountId, value }) =>
|
||||
applyTlonSetupConfig({
|
||||
cfg,
|
||||
accountId,
|
||||
input: { code: value },
|
||||
}),
|
||||
},
|
||||
],
|
||||
export const tlonSetupWizard = createTlonSetupWizardBase({
|
||||
resolveConfigured: async ({ cfg }) => await resolveTlonSetupConfigured(cfg),
|
||||
resolveStatusLines: async ({ cfg }) => await resolveTlonSetupStatusLines(cfg),
|
||||
finalize: async ({ cfg, accountId, prompter }) => {
|
||||
let next = cfg;
|
||||
const resolved = resolveTlonAccount(next, accountId);
|
||||
|
|
@ -183,4 +104,4 @@ export const tlonSetupWizard: ChannelSetupWizard = {
|
|||
|
||||
return { cfg: next };
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue