feat: add Nostr channel plugin and onboarding install defaults

Co-authored-by: joelklabo <joelklabo@users.noreply.github.com>
This commit is contained in:
Peter Steinberger
2026-01-20 20:14:44 +00:00
parent 8686b3b951
commit 7b6cbf5869
46 changed files with 7789 additions and 9 deletions

View File

@@ -102,6 +102,52 @@ describe("ensureOnboardingPluginInstalled", () => {
expect(result.cfg.plugins?.entries?.zalo?.enabled).toBe(true);
});
it("defaults to local on dev channel when local path exists", async () => {
const runtime = makeRuntime();
const select = vi.fn(async () => "skip") as WizardPrompter["select"];
const prompter = makePrompter({ select });
const cfg: ClawdbotConfig = { update: { channel: "dev" } };
vi.mocked(fs.existsSync).mockImplementation((value) => {
const raw = String(value);
return (
raw.endsWith(`${path.sep}.git`) || raw.endsWith(`${path.sep}extensions${path.sep}zalo`)
);
});
await ensureOnboardingPluginInstalled({
cfg,
entry: baseEntry,
prompter,
runtime,
});
const firstCall = select.mock.calls[0]?.[0];
expect(firstCall?.initialValue).toBe("local");
});
it("defaults to npm on beta channel even when local path exists", async () => {
const runtime = makeRuntime();
const select = vi.fn(async () => "skip") as WizardPrompter["select"];
const prompter = makePrompter({ select });
const cfg: ClawdbotConfig = { update: { channel: "beta" } };
vi.mocked(fs.existsSync).mockImplementation((value) => {
const raw = String(value);
return (
raw.endsWith(`${path.sep}.git`) || raw.endsWith(`${path.sep}extensions${path.sep}zalo`)
);
});
await ensureOnboardingPluginInstalled({
cfg,
entry: baseEntry,
prompter,
runtime,
});
const firstCall = select.mock.calls[0]?.[0];
expect(firstCall?.initialValue).toBe("npm");
});
it("falls back to local path after npm install failure", async () => {
const runtime = makeRuntime();
const note = vi.fn(async () => {});

View File

@@ -67,9 +67,10 @@ function addPluginLoadPath(cfg: ClawdbotConfig, pluginPath: string): ClawdbotCon
async function promptInstallChoice(params: {
entry: ChannelPluginCatalogEntry;
localPath?: string | null;
defaultChoice: InstallChoice;
prompter: WizardPrompter;
}): Promise<InstallChoice> {
const { entry, localPath, prompter } = params;
const { entry, localPath, prompter, defaultChoice } = params;
const localOptions: Array<{ value: InstallChoice; label: string; hint?: string }> = localPath
? [
{
@@ -84,7 +85,8 @@ async function promptInstallChoice(params: {
...localOptions,
{ value: "skip", label: "Skip for now" },
];
const initialValue: InstallChoice = localPath ? "local" : "npm";
const initialValue: InstallChoice =
defaultChoice === "local" && !localPath ? "npm" : defaultChoice;
return await prompter.select<InstallChoice>({
message: `Install ${entry.meta.label} plugin?`,
options,
@@ -92,6 +94,25 @@ async function promptInstallChoice(params: {
});
}
function resolveInstallDefaultChoice(params: {
cfg: ClawdbotConfig;
entry: ChannelPluginCatalogEntry;
localPath?: string | null;
}): InstallChoice {
const { cfg, entry, localPath } = params;
const updateChannel = cfg.update?.channel;
if (updateChannel === "dev") {
return localPath ? "local" : "npm";
}
if (updateChannel === "stable" || updateChannel === "beta") {
return "npm";
}
const entryDefault = entry.install.defaultChoice;
if (entryDefault === "local") return localPath ? "local" : "npm";
if (entryDefault === "npm") return "npm";
return localPath ? "local" : "npm";
}
export async function ensureOnboardingPluginInstalled(params: {
cfg: ClawdbotConfig;
entry: ChannelPluginCatalogEntry;
@@ -103,9 +124,15 @@ export async function ensureOnboardingPluginInstalled(params: {
let next = params.cfg;
const allowLocal = hasGitWorkspace(workspaceDir);
const localPath = resolveLocalPath(entry, workspaceDir, allowLocal);
const defaultChoice = resolveInstallDefaultChoice({
cfg: next,
entry,
localPath,
});
const choice = await promptInstallChoice({
entry,
localPath,
defaultChoice,
prompter,
});