fix: merge plugin manifest types

This commit is contained in:
Peter Steinberger
2026-01-20 11:15:14 +00:00
parent 660f87278c
commit 636a8e3181
3 changed files with 105 additions and 12 deletions

View File

@@ -2,7 +2,7 @@ import path from "node:path";
import { discoverClawdbotPlugins } from "../../plugins/discovery.js"; import { discoverClawdbotPlugins } from "../../plugins/discovery.js";
import type { PluginOrigin } from "../../plugins/types.js"; import type { PluginOrigin } from "../../plugins/types.js";
import type { ClawdbotManifest } from "../../plugins/manifest.js"; import type { ClawdbotPackageManifest } from "../../plugins/manifest.js";
import type { ChannelMeta } from "./types.js"; import type { ChannelMeta } from "./types.js";
export type ChannelPluginCatalogEntry = { export type ChannelPluginCatalogEntry = {
@@ -27,7 +27,7 @@ const ORIGIN_PRIORITY: Record<PluginOrigin, number> = {
}; };
function toChannelMeta(params: { function toChannelMeta(params: {
channel: NonNullable<ClawdbotManifest["channel"]>; channel: NonNullable<ClawdbotPackageManifest["channel"]>;
id: string; id: string;
}): ChannelMeta | null { }): ChannelMeta | null {
const label = params.channel.label?.trim(); const label = params.channel.label?.trim();
@@ -70,7 +70,7 @@ function toChannelMeta(params: {
} }
function resolveInstallInfo(params: { function resolveInstallInfo(params: {
manifest: ClawdbotManifest; manifest: ClawdbotPackageManifest;
packageName?: string; packageName?: string;
packageDir?: string; packageDir?: string;
workspaceDir?: string; workspaceDir?: string;
@@ -93,7 +93,7 @@ function buildCatalogEntry(candidate: {
packageName?: string; packageName?: string;
packageDir?: string; packageDir?: string;
workspaceDir?: string; workspaceDir?: string;
packageClawdbot?: ClawdbotManifest; packageClawdbot?: ClawdbotPackageManifest;
}): ChannelPluginCatalogEntry | null { }): ChannelPluginCatalogEntry | null {
const manifest = candidate.packageClawdbot; const manifest = candidate.packageClawdbot;
if (!manifest?.channel) return null; if (!manifest?.channel) return null;

View File

@@ -3,7 +3,7 @@ import path from "node:path";
import { resolveConfigDir, resolveUserPath } from "../utils.js"; import { resolveConfigDir, resolveUserPath } from "../utils.js";
import { resolveBundledPluginsDir } from "./bundled-dir.js"; import { resolveBundledPluginsDir } from "./bundled-dir.js";
import type { ClawdbotManifest, PackageManifest } from "./manifest.js"; import type { ClawdbotPackageManifest, PackageManifest } from "./manifest.js";
import type { PluginDiagnostic, PluginOrigin } from "./types.js"; import type { PluginDiagnostic, PluginOrigin } from "./types.js";
const EXTENSION_EXTS = new Set([".ts", ".js", ".mts", ".cts", ".mjs", ".cjs"]); const EXTENSION_EXTS = new Set([".ts", ".js", ".mts", ".cts", ".mjs", ".cjs"]);
@@ -18,7 +18,7 @@ export type PluginCandidate = {
packageVersion?: string; packageVersion?: string;
packageDescription?: string; packageDescription?: string;
packageDir?: string; packageDir?: string;
packageClawdbot?: ClawdbotManifest; packageClawdbot?: ClawdbotPackageManifest;
}; };
export type PluginDiscoveryResult = { export type PluginDiscoveryResult = {

View File

@@ -1,4 +1,97 @@
export type PluginManifestChannel = { import fs from "node:fs";
import path from "node:path";
import type { PluginConfigUiHint, PluginKind } from "./types.js";
export const PLUGIN_MANIFEST_FILENAME = "clawdbot.plugin.json";
export type PluginManifest = {
id: string;
configSchema: Record<string, unknown>;
kind?: PluginKind;
channels?: string[];
providers?: string[];
name?: string;
description?: string;
version?: string;
uiHints?: Record<string, PluginConfigUiHint>;
};
export type PluginManifestLoadResult =
| { ok: true; manifest: PluginManifest; manifestPath: string }
| { ok: false; error: string; manifestPath: string };
function normalizeStringList(value: unknown): string[] {
if (!Array.isArray(value)) return [];
return value.map((entry) => (typeof entry === "string" ? entry.trim() : "")).filter(Boolean);
}
function isRecord(value: unknown): value is Record<string, unknown> {
return Boolean(value && typeof value === "object" && !Array.isArray(value));
}
export function resolvePluginManifestPath(rootDir: string): string {
return path.join(rootDir, PLUGIN_MANIFEST_FILENAME);
}
export function loadPluginManifest(rootDir: string): PluginManifestLoadResult {
const manifestPath = resolvePluginManifestPath(rootDir);
if (!fs.existsSync(manifestPath)) {
return { ok: false, error: `plugin manifest not found: ${manifestPath}`, manifestPath };
}
let raw: unknown;
try {
raw = JSON.parse(fs.readFileSync(manifestPath, "utf-8")) as unknown;
} catch (err) {
return {
ok: false,
error: `failed to parse plugin manifest: ${String(err)}`,
manifestPath,
};
}
if (!isRecord(raw)) {
return { ok: false, error: "plugin manifest must be an object", manifestPath };
}
const id = typeof raw.id === "string" ? raw.id.trim() : "";
if (!id) {
return { ok: false, error: "plugin manifest requires id", manifestPath };
}
const configSchema = isRecord(raw.configSchema) ? raw.configSchema : null;
if (!configSchema) {
return { ok: false, error: "plugin manifest requires configSchema", manifestPath };
}
const kind = typeof raw.kind === "string" ? (raw.kind as PluginKind) : undefined;
const name = typeof raw.name === "string" ? raw.name.trim() : undefined;
const description = typeof raw.description === "string" ? raw.description.trim() : undefined;
const version = typeof raw.version === "string" ? raw.version.trim() : undefined;
const channels = normalizeStringList(raw.channels);
const providers = normalizeStringList(raw.providers);
let uiHints: Record<string, PluginConfigUiHint> | undefined;
if (isRecord(raw.uiHints)) {
uiHints = raw.uiHints as Record<string, PluginConfigUiHint>;
}
return {
ok: true,
manifest: {
id,
configSchema,
kind,
channels,
providers,
name,
description,
version,
uiHints,
},
manifestPath,
};
}
// package.json "clawdbot" metadata (used for onboarding/catalog)
export type PluginPackageChannel = {
id?: string; id?: string;
label?: string; label?: string;
selectionLabel?: string; selectionLabel?: string;
@@ -16,21 +109,21 @@ export type PluginManifestChannel = {
preferSessionLookupForAnnounceTarget?: boolean; preferSessionLookupForAnnounceTarget?: boolean;
}; };
export type PluginManifestInstall = { export type PluginPackageInstall = {
npmSpec?: string; npmSpec?: string;
localPath?: string; localPath?: string;
defaultChoice?: "npm" | "local"; defaultChoice?: "npm" | "local";
}; };
export type ClawdbotManifest = { export type ClawdbotPackageManifest = {
extensions?: string[]; extensions?: string[];
channel?: PluginManifestChannel; channel?: PluginPackageChannel;
install?: PluginManifestInstall; install?: PluginPackageInstall;
}; };
export type PackageManifest = { export type PackageManifest = {
name?: string; name?: string;
version?: string; version?: string;
description?: string; description?: string;
clawdbot?: ClawdbotManifest; clawdbot?: ClawdbotPackageManifest;
}; };