Skip to content

Commit 0a2e679

Browse files
committed
feat: add aiToolsExpanded and installAiTools message handlers to HomescreenViewProvider
1 parent cc301c8 commit 0a2e679

1 file changed

Lines changed: 154 additions & 3 deletions

File tree

src/webview/homescreenView.ts

Lines changed: 154 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,20 @@ import * as vscode from "vscode";
77
import { CloudinaryTreeDataProvider } from "../tree/treeDataProvider";
88
import { createWebviewDocument, getScriptUri } from "./webviewUtils";
99
import { escapeHtml } from "./utils/helpers";
10+
import {
11+
SkillInfo,
12+
MCP_SERVERS,
13+
detectEditor,
14+
getMcpFilePath,
15+
fetchSkillList,
16+
fetchSkillContent,
17+
readInstalledSkillDirNames,
18+
readConfiguredMcpServerKeys,
19+
installForClaudeCode,
20+
installForCursor,
21+
installForCopilot,
22+
installMcpServers,
23+
} from "../aiToolsService";
1024

1125
export class HomescreenViewProvider implements vscode.WebviewViewProvider {
1226
public static readonly viewType = "cloudinaryHomescreen";
@@ -17,6 +31,7 @@ export class HomescreenViewProvider implements vscode.WebviewViewProvider {
1731
) {}
1832

1933
private _webviewView: vscode.WebviewView | undefined;
34+
private _cachedSkills: SkillInfo[] | undefined;
2035

2136
resolveWebviewView(
2237
webviewView: vscode.WebviewView,
@@ -49,7 +64,7 @@ export class HomescreenViewProvider implements vscode.WebviewViewProvider {
4964
});
5065

5166
webviewView.webview.onDidReceiveMessage(
52-
(message: { command: string }) => {
67+
(message: { command: string; skills?: string[]; ideTarget?: string; mcpServers?: string[] }) => {
5368
switch (message.command) {
5469
case "openGlobalConfig":
5570
vscode.commands.executeCommand("cloudinary.openGlobalConfig");
@@ -63,8 +78,15 @@ export class HomescreenViewProvider implements vscode.WebviewViewProvider {
6378
case "openWelcomeScreen":
6479
vscode.commands.executeCommand("cloudinary.openWelcomeScreen");
6580
break;
66-
case "configureAiTools":
67-
vscode.commands.executeCommand("cloudinary.configureAiTools");
81+
case "aiToolsExpanded":
82+
this._handleAiToolsExpanded();
83+
break;
84+
case "installAiTools":
85+
this._handleInstallAiTools(
86+
message.skills ?? [],
87+
message.ideTarget ?? "Claude Code",
88+
message.mcpServers ?? []
89+
);
6890
break;
6991
}
7092
}
@@ -752,4 +774,133 @@ export class HomescreenViewProvider implements vscode.WebviewViewProvider {
752774
</div>
753775
`;
754776
}
777+
778+
private async _handleAiToolsExpanded(): Promise<void> {
779+
const view = this._webviewView;
780+
if (!view) { return; }
781+
782+
const workspaceFolders = vscode.workspace.workspaceFolders;
783+
if (!workspaceFolders || workspaceFolders.length === 0) {
784+
view.webview.postMessage({
785+
command: "aiToolsData",
786+
error: "Please open a workspace folder first.",
787+
});
788+
return;
789+
}
790+
const rootUri = workspaceFolders[0].uri;
791+
792+
try {
793+
// Fetch skills once; cache for subsequent opens
794+
if (!this._cachedSkills) {
795+
this._cachedSkills = await fetchSkillList();
796+
}
797+
const skills = this._cachedSkills;
798+
799+
const ideLabels: string[] = ["Claude Code", "Cursor", "VS Code (Copilot)"];
800+
801+
// Pre-compute installed status for all 3 IDEs
802+
const installedByIde: Record<string, string[]> = {};
803+
await Promise.all(
804+
ideLabels.map(async (label) => {
805+
const installedSet = await readInstalledSkillDirNames(rootUri, label, skills);
806+
installedByIde[label] = [...installedSet];
807+
})
808+
);
809+
810+
// MCP servers — use detected editor for the config file path
811+
const editor = detectEditor();
812+
const mcpFilePath = getMcpFilePath(editor);
813+
const rootKey = editor === "vscode" ? "servers" : "mcpServers";
814+
const configuredMcpSet = await readConfiguredMcpServerKeys(rootUri, mcpFilePath, rootKey);
815+
816+
const detectedIde =
817+
editor === "cursor" ? "Cursor" :
818+
editor === "vscode" ? "VS Code (Copilot)" :
819+
"Claude Code";
820+
821+
view.webview.postMessage({
822+
command: "aiToolsData",
823+
skills: skills.map((s) => ({ name: s.name, description: s.description, dirName: s.dirName })),
824+
installedByIde,
825+
mcpServers: MCP_SERVERS.map((s) => ({ key: s.key, label: s.label, description: s.description })),
826+
configuredMcpKeys: [...configuredMcpSet],
827+
detectedIde,
828+
});
829+
} catch (err: any) {
830+
view.webview.postMessage({
831+
command: "aiToolsData",
832+
error: err.message ?? String(err),
833+
});
834+
}
835+
}
836+
837+
private async _handleInstallAiTools(
838+
skills: string[],
839+
ideTarget: string,
840+
mcpServers: string[]
841+
): Promise<void> {
842+
const view = this._webviewView;
843+
if (!view) { return; }
844+
845+
const workspaceFolders = vscode.workspace.workspaceFolders;
846+
if (!workspaceFolders || workspaceFolders.length === 0) {
847+
view.webview.postMessage({ command: "aiToolsResult", errors: ["No workspace folder open."] });
848+
return;
849+
}
850+
const rootUri = workspaceFolders[0].uri;
851+
const errors: string[] = [];
852+
853+
// Install skills
854+
const cachedSkills = this._cachedSkills ?? [];
855+
for (const dirName of skills) {
856+
const skillInfo = cachedSkills.find((s) => s.dirName === dirName);
857+
if (!skillInfo) { continue; }
858+
859+
let content: string;
860+
try {
861+
content = await fetchSkillContent(dirName);
862+
} catch (err: any) {
863+
errors.push(`${dirName}: ${err.message}`);
864+
view.webview.postMessage({ command: "aiToolsProgress", item: dirName, status: "error" });
865+
continue;
866+
}
867+
868+
const createdFiles: string[] = [];
869+
try {
870+
if (ideTarget === "Claude Code") {
871+
await installForClaudeCode(rootUri, dirName, content, createdFiles, errors);
872+
} else if (ideTarget === "Cursor") {
873+
await installForCursor(rootUri, dirName, content, createdFiles);
874+
} else {
875+
await installForCopilot(rootUri, skillInfo.name, content, createdFiles);
876+
}
877+
view.webview.postMessage({ command: "aiToolsProgress", item: dirName, status: "done" });
878+
} catch (err: any) {
879+
errors.push(`${dirName}: ${err.message}`);
880+
view.webview.postMessage({ command: "aiToolsProgress", item: dirName, status: "error" });
881+
}
882+
}
883+
884+
// Install MCP servers
885+
if (mcpServers.length > 0) {
886+
const editor = detectEditor();
887+
const createdFiles: string[] = [];
888+
try {
889+
await installMcpServers(rootUri, editor, mcpServers, createdFiles);
890+
for (const key of mcpServers) {
891+
view.webview.postMessage({ command: "aiToolsProgress", item: key, status: "done" });
892+
}
893+
} catch (err: any) {
894+
errors.push(`MCP: ${err.message}`);
895+
for (const key of mcpServers) {
896+
view.webview.postMessage({ command: "aiToolsProgress", item: key, status: "error" });
897+
}
898+
}
899+
}
900+
901+
// Invalidate cached skills so next open re-reads disk
902+
this._cachedSkills = undefined;
903+
904+
view.webview.postMessage({ command: "aiToolsResult", errors });
905+
}
755906
}

0 commit comments

Comments
 (0)