File size: 4,064 Bytes
ac68ae4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import * as p from "@clack/prompts";
import { askConfirm, askText, isPromptBack, pickOne, PROMPT_BACK } from "../lib/prompts.js";
import type { PluginChoice, PluginSource } from "../types.js";
import { listInstalledPlugins, searchPlugins } from "../lib/plugins.js";

interface AskPluginsOptions {
  allowBack?: boolean;
  backHint?: string;
  initial?: PluginChoice[];
  /** When set, skip the interactive plugin search UI. */
  prechosen?: PluginChoice[];
  skip?: boolean;
}

function pluginLabel(plugin: {
  name: string;
  version: string;
  source: PluginSource;
  description: string;
}): string {
  const sourceTag = plugin.source === "registry" ? "registry" : "local";
  return `${plugin.name} (${sourceTag})`;
}

export async function askPlugins(
  opts: AskPluginsOptions = {},
): Promise<PluginChoice[] | typeof PROMPT_BACK> {
  if (opts.skip || opts.prechosen !== undefined) {
    return opts.prechosen ?? [];
  }

  const selections = new Map<string, PluginChoice>();
  for (const plugin of opts.initial ?? []) {
    selections.set(`${plugin.source}:${plugin.name}:${plugin.path ?? ""}`, plugin);
  }

  while (true) {
    const query = await askText({
      message: "Search plugin by name (or leave empty to continue)",
      placeholder: "chatbot",
    });
    if (!query.trim()) {
      if (opts.allowBack && selections.size === 0) {
        const shouldGoBack = await pickOne<"continue" | "back">({
          message: "No plugins selected. Continue or go back?",
          options: [
            { value: "continue", label: "Continue" },
            { value: "back", label: "Back", hint: opts.backHint },
          ],
          initialValue: "continue",
        });
        if (shouldGoBack === "back") {
          return PROMPT_BACK;
        }
      }
      return [...selections.values()];
    }

    const results = await searchPlugins(query);
    if (results.length === 0) {
      p.log.warn(`No plugin found for "${query}".`);
    } else {
      const choice = await pickOne<string>({
        message: "Select a plugin to add",
        options: results.map((plugin, index) => ({
          value: String(index),
          label: pluginLabel(plugin),
          hint: plugin.description || `${plugin.version}`,
        })),
        allowBack: true,
        backHint: opts.backHint,
      });
      if (isPromptBack(choice)) {
        if (opts.allowBack) {
          return PROMPT_BACK;
        }
        continue;
      }
      const selected = results[Number(choice)];
      if (!selected) {
        p.log.warn("Invalid plugin selection.");
        continue;
      }
      const confirmed = await askConfirm({
        message: `Add ${selected.name} from ${selected.source}?`,
        initialValue: true,
      });
      if (confirmed) {
        const key = `${selected.source}:${selected.name}:${selected.path ?? ""}`;
        selections.set(key, {
          name: selected.name,
          source: selected.source,
          version: selected.version,
          path: selected.path,
        });
        p.log.success(`Added ${selected.name}.`);
      }
    }

    const addMore = await askConfirm({
      message: "Add another plugin?",
      initialValue: true,
    });
    if (!addMore) {
      return [...selections.values()];
    }
  }
}

export async function askPluginUninstall(
  opts: { allowBack?: boolean; backHint?: string } = {},
): Promise<string | typeof PROMPT_BACK | null> {
  const installed = await listInstalledPlugins();
  if (installed.length === 0) {
    p.log.info("No installed plugins found.");
    return null;
  }
  const options = installed.map((plugin) => ({
    value: plugin.name,
    label: plugin.name,
    hint: plugin.version,
  }));
  const choice = opts.allowBack
    ? await pickOne<string>({
        message: "Select plugin to uninstall",
        options,
        allowBack: true,
        backHint: opts.backHint,
      })
    : await pickOne<string>({
        message: "Select plugin to uninstall",
        options,
      });
  if (isPromptBack(choice)) {
    return PROMPT_BACK;
  }
  return choice;
}