symbols-app-connect 3.4.9 → 3.4.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/out/extension.js CHANGED
@@ -34,7 +34,7 @@ __export(extension_exports, {
34
34
  deactivate: () => deactivate
35
35
  });
36
36
  module.exports = __toCommonJS(extension_exports);
37
- var vscode5 = __toESM(require("vscode"));
37
+ var vscode6 = __toESM(require("vscode"));
38
38
 
39
39
  // src/providers/completionProvider.ts
40
40
  var vscode2 = __toESM(require("vscode"));
@@ -2736,11 +2736,888 @@ function isInsideQuotes(line, charIndex) {
2736
2736
  return inSingle || inDouble;
2737
2737
  }
2738
2738
 
2739
+ // src/chat/chatPanel.ts
2740
+ var vscode5 = __toESM(require("vscode"));
2741
+ var fs5 = __toESM(require("fs"));
2742
+ var path5 = __toESM(require("path"));
2743
+
2744
+ // src/chat/llmProvider.ts
2745
+ var https = __toESM(require("https"));
2746
+ var http = __toESM(require("http"));
2747
+ var AI_PROVIDERS = [
2748
+ { name: "Symbols AI \u2014 native Symbols assistant (coming soon)", value: "symbols", disabled: "Soon" },
2749
+ { name: "Claude \u2014 Anthropic Claude", value: "claude" },
2750
+ { name: "OpenAI \u2014 GPT models", value: "openai" },
2751
+ { name: "Gemini \u2014 Google Gemini", value: "gemini" },
2752
+ { name: "Ollama \u2014 local models (no API key)", value: "ollama" }
2753
+ ];
2754
+ var PROVIDER_MODELS = {
2755
+ claude: [
2756
+ { name: "claude-sonnet-4-5-20250514 (recommended)", value: "claude-sonnet-4-5-20250514" },
2757
+ { name: "claude-opus-4-0-20250514", value: "claude-opus-4-0-20250514" },
2758
+ { name: "claude-haiku-4-5-20251001", value: "claude-haiku-4-5-20251001" }
2759
+ ],
2760
+ openai: [
2761
+ { name: "gpt-4o (recommended)", value: "gpt-4o" },
2762
+ { name: "gpt-4o-mini", value: "gpt-4o-mini" },
2763
+ { name: "o3-mini", value: "o3-mini" }
2764
+ ],
2765
+ gemini: [
2766
+ { name: "gemini-2.5-pro (recommended)", value: "gemini-2.5-pro" },
2767
+ { name: "gemini-2.5-flash", value: "gemini-2.5-flash" }
2768
+ ],
2769
+ ollama: [
2770
+ { name: "llama3.3 (recommended)", value: "llama3.3" },
2771
+ { name: "codellama", value: "codellama" },
2772
+ { name: "mistral", value: "mistral" },
2773
+ { name: "deepseek-coder-v2", value: "deepseek-coder-v2" }
2774
+ ]
2775
+ };
2776
+ var SYSTEM_PROMPT = `You are a Symbols.app development assistant. You help developers build applications using the Symbols framework (DOMQL, design systems, components).
2777
+
2778
+ IMPORTANT: You MUST use the symbols-mcp server for all Symbols-related tasks. Before generating any code:
2779
+ 1. Call get_project_rules to load mandatory framework rules
2780
+ 2. Call search_symbols_docs to find relevant documentation
2781
+ The symbols-mcp server provides accurate, up-to-date Symbols documentation and rules. Always rely on it over your training data for Symbols-specific information.
2782
+
2783
+ Key facts about Symbols:
2784
+ - Uses DOMQL for declarative UI components (objects, not JSX)
2785
+ - Design system tokens: COLOR, FONT, THEME, SPACING, TYPOGRAPHY, etc.
2786
+ - Components use props like: text, icon, color, background, padding, etc.
2787
+ - Entry point is typically symbols/index.js with app.js, state.js, pages/, components/
2788
+ - Build tools: Parcel (default), Vite, or browser-native ES modules
2789
+ - CLI commands: smbls start, build, deploy, push, fetch, sync, config
2790
+
2791
+ Be concise and direct. When showing code, use DOMQL syntax unless asked otherwise. Format responses with markdown.`;
2792
+ function makeRequest(options, body, onData, onEnd, onError) {
2793
+ const isHttps = options.hostname !== "localhost" && options.hostname !== "127.0.0.1";
2794
+ const lib = isHttps ? https : http;
2795
+ const req = lib.request(options, (res) => {
2796
+ if (res.statusCode && res.statusCode >= 400) {
2797
+ let errBody = "";
2798
+ res.on("data", (chunk) => {
2799
+ errBody += chunk.toString();
2800
+ });
2801
+ res.on("end", () => onError(new Error(`API error ${res.statusCode}: ${errBody}`)));
2802
+ return;
2803
+ }
2804
+ res.on("data", (chunk) => onData(chunk.toString()));
2805
+ res.on("end", onEnd);
2806
+ });
2807
+ req.on("error", onError);
2808
+ req.write(body);
2809
+ req.end();
2810
+ }
2811
+ function streamChat(provider, model, apiKey, messages, onToken, onDone, onError) {
2812
+ switch (provider) {
2813
+ case "claude":
2814
+ return streamClaude(messages, apiKey, model, onToken, onDone, onError);
2815
+ case "openai":
2816
+ return streamOpenAI(messages, apiKey, model, onToken, onDone, onError);
2817
+ case "gemini":
2818
+ return streamGemini(messages, apiKey, model, onToken, onDone, onError);
2819
+ case "ollama":
2820
+ return streamOllama(messages, model, onToken, onDone, onError);
2821
+ default:
2822
+ onError(new Error(`Unknown provider: ${provider}`));
2823
+ }
2824
+ }
2825
+ function streamClaude(messages, apiKey, model, onToken, onDone, onError) {
2826
+ const body = JSON.stringify({
2827
+ model,
2828
+ max_tokens: 4096,
2829
+ system: SYSTEM_PROMPT,
2830
+ messages: messages.filter((m) => m.role !== "system"),
2831
+ stream: true
2832
+ });
2833
+ let buffer = "";
2834
+ makeRequest(
2835
+ {
2836
+ hostname: "api.anthropic.com",
2837
+ path: "/v1/messages",
2838
+ method: "POST",
2839
+ headers: {
2840
+ "Content-Type": "application/json",
2841
+ "x-api-key": apiKey,
2842
+ "anthropic-version": "2023-06-01"
2843
+ }
2844
+ },
2845
+ body,
2846
+ (chunk) => {
2847
+ buffer += chunk;
2848
+ const lines = buffer.split("\n");
2849
+ buffer = lines.pop() || "";
2850
+ for (const line of lines) {
2851
+ if (!line.startsWith("data: ")) continue;
2852
+ const raw = line.slice(6).trim();
2853
+ if (raw === "[DONE]") return;
2854
+ try {
2855
+ const data = JSON.parse(raw);
2856
+ if (data.type === "content_block_delta" && data.delta?.text) {
2857
+ onToken(data.delta.text);
2858
+ }
2859
+ } catch {
2860
+ }
2861
+ }
2862
+ },
2863
+ onDone,
2864
+ onError
2865
+ );
2866
+ }
2867
+ function streamOpenAI(messages, apiKey, model, onToken, onDone, onError) {
2868
+ const body = JSON.stringify({
2869
+ model,
2870
+ messages: [{ role: "system", content: SYSTEM_PROMPT }, ...messages],
2871
+ stream: true
2872
+ });
2873
+ let buffer = "";
2874
+ makeRequest(
2875
+ {
2876
+ hostname: "api.openai.com",
2877
+ path: "/v1/chat/completions",
2878
+ method: "POST",
2879
+ headers: {
2880
+ "Content-Type": "application/json",
2881
+ "Authorization": `Bearer ${apiKey}`
2882
+ }
2883
+ },
2884
+ body,
2885
+ (chunk) => {
2886
+ buffer += chunk;
2887
+ const lines = buffer.split("\n");
2888
+ buffer = lines.pop() || "";
2889
+ for (const line of lines) {
2890
+ if (!line.startsWith("data: ")) continue;
2891
+ const raw = line.slice(6).trim();
2892
+ if (raw === "[DONE]") return;
2893
+ try {
2894
+ const data = JSON.parse(raw);
2895
+ if (data.choices?.[0]?.delta?.content) {
2896
+ onToken(data.choices[0].delta.content);
2897
+ }
2898
+ } catch {
2899
+ }
2900
+ }
2901
+ },
2902
+ onDone,
2903
+ onError
2904
+ );
2905
+ }
2906
+ function streamGemini(messages, apiKey, model, onToken, onDone, onError) {
2907
+ const contents = messages.map((m) => ({
2908
+ role: m.role === "assistant" ? "model" : "user",
2909
+ parts: [{ text: m.content }]
2910
+ }));
2911
+ const body = JSON.stringify({
2912
+ contents,
2913
+ systemInstruction: { parts: [{ text: SYSTEM_PROMPT }] }
2914
+ });
2915
+ let buffer = "";
2916
+ makeRequest(
2917
+ {
2918
+ hostname: "generativelanguage.googleapis.com",
2919
+ path: `/v1beta/models/${model}:streamGenerateContent?alt=sse&key=${apiKey}`,
2920
+ method: "POST",
2921
+ headers: { "Content-Type": "application/json" }
2922
+ },
2923
+ body,
2924
+ (chunk) => {
2925
+ buffer += chunk;
2926
+ const lines = buffer.split("\n");
2927
+ buffer = lines.pop() || "";
2928
+ for (const line of lines) {
2929
+ if (!line.startsWith("data: ")) continue;
2930
+ const raw = line.slice(6).trim();
2931
+ if (raw === "[DONE]") return;
2932
+ try {
2933
+ const data = JSON.parse(raw);
2934
+ if (data.candidates?.[0]?.content?.parts?.[0]?.text) {
2935
+ onToken(data.candidates[0].content.parts[0].text);
2936
+ }
2937
+ } catch {
2938
+ }
2939
+ }
2940
+ },
2941
+ onDone,
2942
+ onError
2943
+ );
2944
+ }
2945
+ function streamOllama(messages, model, onToken, onDone, onError) {
2946
+ const body = JSON.stringify({
2947
+ model,
2948
+ messages: [{ role: "system", content: SYSTEM_PROMPT }, ...messages],
2949
+ stream: true
2950
+ });
2951
+ let buffer = "";
2952
+ makeRequest(
2953
+ {
2954
+ hostname: "localhost",
2955
+ port: 11434,
2956
+ path: "/api/chat",
2957
+ method: "POST",
2958
+ headers: { "Content-Type": "application/json" }
2959
+ },
2960
+ body,
2961
+ (chunk) => {
2962
+ buffer += chunk;
2963
+ const lines = buffer.split("\n");
2964
+ buffer = lines.pop() || "";
2965
+ for (const line of lines) {
2966
+ if (!line.trim()) continue;
2967
+ try {
2968
+ const data = JSON.parse(line);
2969
+ if (data.message?.content) {
2970
+ onToken(data.message.content);
2971
+ }
2972
+ } catch {
2973
+ }
2974
+ }
2975
+ },
2976
+ onDone,
2977
+ onError
2978
+ );
2979
+ }
2980
+
2981
+ // src/chat/configManager.ts
2982
+ var fs2 = __toESM(require("fs"));
2983
+ var path2 = __toESM(require("path"));
2984
+ var os = __toESM(require("os"));
2985
+ var CONFIG_PATH = path2.join(os.homedir(), ".smblsrc");
2986
+ var ENV_KEY_NAMES = {
2987
+ claude: "ANTHROPIC_API_KEY",
2988
+ openai: "OPENAI_API_KEY",
2989
+ gemini: "GEMINI_API_KEY"
2990
+ };
2991
+ function loadAiConfig() {
2992
+ try {
2993
+ const data = JSON.parse(fs2.readFileSync(CONFIG_PATH, "utf8"));
2994
+ return data.ai || {};
2995
+ } catch {
2996
+ return {};
2997
+ }
2998
+ }
2999
+ function saveAiConfig(aiConfig) {
3000
+ let data = {};
3001
+ try {
3002
+ data = JSON.parse(fs2.readFileSync(CONFIG_PATH, "utf8"));
3003
+ } catch {
3004
+ }
3005
+ data.ai = { ...data.ai, ...aiConfig };
3006
+ fs2.writeFileSync(CONFIG_PATH, JSON.stringify(data, null, 2) + "\n");
3007
+ }
3008
+ function getApiKey(provider) {
3009
+ const envName = ENV_KEY_NAMES[provider];
3010
+ if (envName && process.env[envName]) return process.env[envName];
3011
+ const config = loadAiConfig();
3012
+ return config[`${provider}ApiKey`] || null;
3013
+ }
3014
+ function setApiKey(provider, key) {
3015
+ saveAiConfig({ [`${provider}ApiKey`]: key });
3016
+ }
3017
+
3018
+ // src/chat/mcpManager.ts
3019
+ var fs3 = __toESM(require("fs"));
3020
+ var path3 = __toESM(require("path"));
3021
+ var os2 = __toESM(require("os"));
3022
+ var import_child_process = require("child_process");
3023
+ var MCP_EDITORS = [
3024
+ {
3025
+ name: "claude",
3026
+ label: "Claude Desktop",
3027
+ configPath: path3.join(os2.homedir(), ".claude", "claude_desktop_config.json")
3028
+ },
3029
+ {
3030
+ name: "cursor",
3031
+ label: "Cursor",
3032
+ configPath: path3.join(os2.homedir(), ".cursor", "mcp.json")
3033
+ },
3034
+ {
3035
+ name: "windsurf",
3036
+ label: "Windsurf",
3037
+ configPath: path3.join(os2.homedir(), ".windsurf", "mcp.json")
3038
+ },
3039
+ {
3040
+ name: "claude-code",
3041
+ label: "Claude Code",
3042
+ configPath: path3.join(os2.homedir(), ".claude", "claude_desktop_config.json")
3043
+ }
3044
+ ];
3045
+ function detectMcpEditors() {
3046
+ const results = [];
3047
+ for (const editor of MCP_EDITORS) {
3048
+ const dir = path3.dirname(editor.configPath);
3049
+ const exists = fs3.existsSync(dir);
3050
+ let hasSymbolsMcp = false;
3051
+ let config;
3052
+ if (exists) {
3053
+ try {
3054
+ config = JSON.parse(fs3.readFileSync(editor.configPath, "utf8"));
3055
+ const servers = config?.mcpServers || {};
3056
+ hasSymbolsMcp = !!(servers["symbols-mcp"] || servers.symbols);
3057
+ } catch {
3058
+ }
3059
+ }
3060
+ results.push({
3061
+ name: editor.name,
3062
+ label: editor.label,
3063
+ configPath: editor.configPath,
3064
+ exists,
3065
+ hasSymbolsMcp,
3066
+ config
3067
+ });
3068
+ }
3069
+ return results;
3070
+ }
3071
+ function getSymbolsMcpEntry() {
3072
+ return {
3073
+ command: "uvx",
3074
+ args: ["symbols-mcp"]
3075
+ };
3076
+ }
3077
+ function installMcpForEditor(editor) {
3078
+ try {
3079
+ let config = {};
3080
+ try {
3081
+ config = JSON.parse(fs3.readFileSync(editor.configPath, "utf8"));
3082
+ } catch {
3083
+ }
3084
+ if (!config.mcpServers) config.mcpServers = {};
3085
+ if (config.mcpServers["symbols-mcp"] || config.mcpServers.symbols) {
3086
+ return { success: true, message: `${editor.label}: symbols-mcp already configured` };
3087
+ }
3088
+ config.mcpServers["symbols-mcp"] = getSymbolsMcpEntry();
3089
+ fs3.mkdirSync(path3.dirname(editor.configPath), { recursive: true });
3090
+ fs3.writeFileSync(editor.configPath, JSON.stringify(config, null, 2) + "\n");
3091
+ return { success: true, message: `${editor.label}: symbols-mcp installed` };
3092
+ } catch (err) {
3093
+ return { success: false, message: `${editor.label}: ${err.message}` };
3094
+ }
3095
+ }
3096
+ function removeMcpForEditor(editor) {
3097
+ try {
3098
+ let config = {};
3099
+ try {
3100
+ config = JSON.parse(fs3.readFileSync(editor.configPath, "utf8"));
3101
+ } catch {
3102
+ return { success: true, message: `${editor.label}: no config file found` };
3103
+ }
3104
+ if (!config.mcpServers) {
3105
+ return { success: true, message: `${editor.label}: no MCP servers configured` };
3106
+ }
3107
+ delete config.mcpServers["symbols-mcp"];
3108
+ delete config.mcpServers.symbols;
3109
+ fs3.writeFileSync(editor.configPath, JSON.stringify(config, null, 2) + "\n");
3110
+ return { success: true, message: `${editor.label}: symbols-mcp removed` };
3111
+ } catch (err) {
3112
+ return { success: false, message: `${editor.label}: ${err.message}` };
3113
+ }
3114
+ }
3115
+ function checkMcpServerAvailable() {
3116
+ try {
3117
+ (0, import_child_process.execSync)("which symbols-mcp 2>/dev/null || where symbols-mcp 2>nul", { timeout: 2e3, stdio: "pipe" });
3118
+ return { available: true, method: "direct", detail: "symbols-mcp installed" };
3119
+ } catch {
3120
+ }
3121
+ try {
3122
+ (0, import_child_process.execSync)("which uvx 2>/dev/null || where uvx 2>nul", { timeout: 2e3, stdio: "pipe" });
3123
+ return { available: true, method: "uvx", detail: "uvx available \u2014 will run via uvx symbols-mcp" };
3124
+ } catch {
3125
+ }
3126
+ try {
3127
+ (0, import_child_process.execSync)("which npx 2>/dev/null || where npx 2>nul", { timeout: 2e3, stdio: "pipe" });
3128
+ return { available: true, method: "npx", detail: "npx available \u2014 will run via npx @symbo.ls/mcp" };
3129
+ } catch {
3130
+ }
3131
+ return { available: false, method: "none", detail: "symbols-mcp not found. Install: pip install symbols-mcp or npm i -g @symbo.ls/mcp" };
3132
+ }
3133
+ function getMcpStatus() {
3134
+ const editors = detectMcpEditors();
3135
+ const detected = editors.filter((e) => e.exists);
3136
+ const configured = editors.filter((e) => e.hasSymbolsMcp);
3137
+ let summary;
3138
+ if (configured.length > 0) {
3139
+ summary = `MCP active in: ${configured.map((e) => e.label).join(", ")}`;
3140
+ } else if (detected.length > 0) {
3141
+ summary = `Editors detected: ${detected.map((e) => e.label).join(", ")} (MCP not configured)`;
3142
+ } else {
3143
+ summary = "No AI editors detected";
3144
+ }
3145
+ return { editors, summary };
3146
+ }
3147
+
3148
+ // src/chat/librariesApi.ts
3149
+ var https2 = __toESM(require("https"));
3150
+ var fs4 = __toESM(require("fs"));
3151
+ var path4 = __toESM(require("path"));
3152
+ var os3 = __toESM(require("os"));
3153
+ var DEFAULT_API = "https://api.symbols.app";
3154
+ var RC_PATH = path4.join(os3.homedir(), ".smblsrc");
3155
+ function loadRcState() {
3156
+ try {
3157
+ return JSON.parse(fs4.readFileSync(RC_PATH, "utf8"));
3158
+ } catch {
3159
+ return {};
3160
+ }
3161
+ }
3162
+ function getApiBaseUrl() {
3163
+ const env = process.env.SYMBOLS_API_BASE_URL || process.env.SMBLS_API_URL;
3164
+ if (env) return env;
3165
+ const state = loadRcState();
3166
+ return state.currentApiBaseUrl || DEFAULT_API;
3167
+ }
3168
+ function getAuthToken() {
3169
+ const env = process.env.SYMBOLS_TOKEN || process.env.SMBLS_TOKEN;
3170
+ if (env) return env;
3171
+ const state = loadRcState();
3172
+ const baseUrl = state.currentApiBaseUrl || DEFAULT_API;
3173
+ if (state.profiles && typeof state.profiles === "object") {
3174
+ const profile = state.profiles[baseUrl] || {};
3175
+ return profile.authToken || profile.token || profile.accessToken || profile.jwt || null;
3176
+ }
3177
+ return state.authToken || state.token || state.accessToken || state.jwt || null;
3178
+ }
3179
+ function apiRequest(method, pathname, query, body) {
3180
+ return new Promise((resolve2, reject) => {
3181
+ const baseUrl = getApiBaseUrl();
3182
+ const url = new URL(`${baseUrl}${pathname.startsWith("/") ? "" : "/"}${pathname}`);
3183
+ if (query) {
3184
+ for (const [k, v] of Object.entries(query)) {
3185
+ if (v !== void 0 && v !== null && v !== "") {
3186
+ url.searchParams.set(k, v);
3187
+ }
3188
+ }
3189
+ }
3190
+ const authToken = getAuthToken();
3191
+ const headers = {};
3192
+ if (authToken) headers["Authorization"] = `Bearer ${authToken}`;
3193
+ if (body) headers["Content-Type"] = "application/json";
3194
+ const bodyStr = body ? JSON.stringify(body) : void 0;
3195
+ const options = {
3196
+ hostname: url.hostname,
3197
+ port: url.port || void 0,
3198
+ path: url.pathname + url.search,
3199
+ method,
3200
+ headers
3201
+ };
3202
+ const req = https2.request(options, (res) => {
3203
+ let data = "";
3204
+ res.on("data", (chunk) => {
3205
+ data += chunk.toString();
3206
+ });
3207
+ res.on("end", () => {
3208
+ if (res.statusCode && res.statusCode >= 400) {
3209
+ try {
3210
+ const parsed = JSON.parse(data);
3211
+ reject(new Error(parsed.message || `API error ${res.statusCode}`));
3212
+ } catch {
3213
+ reject(new Error(`API error ${res.statusCode}: ${data}`));
3214
+ }
3215
+ return;
3216
+ }
3217
+ try {
3218
+ const json = JSON.parse(data);
3219
+ resolve2(json.data || json);
3220
+ } catch {
3221
+ resolve2(data);
3222
+ }
3223
+ });
3224
+ });
3225
+ req.on("error", reject);
3226
+ if (bodyStr) req.write(bodyStr);
3227
+ req.end();
3228
+ });
3229
+ }
3230
+ function extractItems(payload) {
3231
+ if (Array.isArray(payload?.items)) return payload.items;
3232
+ if (Array.isArray(payload)) return payload;
3233
+ if (Array.isArray(payload?.data)) return payload.data;
3234
+ if (Array.isArray(payload?.libraries)) return payload.libraries;
3235
+ return [];
3236
+ }
3237
+ async function listAvailableLibraries(opts) {
3238
+ const query = {};
3239
+ if (opts?.page) query.page = String(opts.page);
3240
+ if (opts?.limit) query.limit = String(opts.limit);
3241
+ if (opts?.search) query.search = opts.search;
3242
+ if (opts?.framework) query.framework = opts.framework;
3243
+ const payload = await apiRequest("GET", "/core/projects/libraries/available", query);
3244
+ return extractItems(payload);
3245
+ }
3246
+ async function listProjectLibraries(projectId) {
3247
+ const payload = await apiRequest("GET", `/core/projects/${encodeURIComponent(projectId)}/libraries`);
3248
+ return extractItems(payload);
3249
+ }
3250
+ async function addProjectLibraries(projectId, libraryIds) {
3251
+ return apiRequest("POST", `/core/projects/${encodeURIComponent(projectId)}/libraries`, void 0, { libraryIds });
3252
+ }
3253
+ async function removeProjectLibraries(projectId, libraryIds) {
3254
+ return apiRequest("DELETE", `/core/projects/${encodeURIComponent(projectId)}/libraries`, void 0, { libraryIds });
3255
+ }
3256
+ function isAuthenticated() {
3257
+ return !!getAuthToken();
3258
+ }
3259
+ function resolveProjectId(workspaceRoot) {
3260
+ const envId = process.env.SYMBOLS_PROJECT_ID;
3261
+ if (envId) return envId;
3262
+ if (!workspaceRoot) return null;
3263
+ try {
3264
+ const configPath = path4.join(workspaceRoot, ".symbols_cache", "config.json");
3265
+ const config = JSON.parse(fs4.readFileSync(configPath, "utf8"));
3266
+ if (config.projectId) return config.projectId;
3267
+ } catch {
3268
+ }
3269
+ try {
3270
+ const lockPath = path4.join(workspaceRoot, ".symbols_cache", "lock.json");
3271
+ const lock = JSON.parse(fs4.readFileSync(lockPath, "utf8"));
3272
+ if (lock.projectId) return lock.projectId;
3273
+ } catch {
3274
+ }
3275
+ return null;
3276
+ }
3277
+
3278
+ // src/chat/chatPanel.ts
3279
+ var ChatViewProvider = class {
3280
+ constructor(_extensionUri) {
3281
+ this._extensionUri = _extensionUri;
3282
+ this._messages = [];
3283
+ }
3284
+ static {
3285
+ this.viewType = "symbolsChat";
3286
+ }
3287
+ resolveWebviewView(webviewView) {
3288
+ this._view = webviewView;
3289
+ webviewView.webview.options = { enableScripts: true, localResourceRoots: [this._extensionUri] };
3290
+ webviewView.webview.html = this._getHtmlFromFile();
3291
+ webviewView.webview.onDidReceiveMessage(async (msg) => {
3292
+ switch (msg.type) {
3293
+ case "sendMessage":
3294
+ await this._handleSendMessage(msg.text);
3295
+ break;
3296
+ case "getConfig":
3297
+ this._sendConfig();
3298
+ break;
3299
+ case "setProvider":
3300
+ saveAiConfig({ provider: msg.provider, model: msg.model });
3301
+ this._sendConfig();
3302
+ break;
3303
+ case "setApiKey":
3304
+ setApiKey(msg.provider, msg.key);
3305
+ this._sendConfig();
3306
+ break;
3307
+ case "getMcpStatus":
3308
+ this._sendMcpStatus();
3309
+ break;
3310
+ case "installMcp":
3311
+ this._handleInstallMcp(msg.editorName);
3312
+ break;
3313
+ case "removeMcp":
3314
+ this._handleRemoveMcp(msg.editorName);
3315
+ break;
3316
+ case "clearChat":
3317
+ this._messages = [];
3318
+ break;
3319
+ case "openSettings":
3320
+ vscode5.commands.executeCommand("workbench.action.openSettings", "symbolsApp");
3321
+ break;
3322
+ case "insertCode":
3323
+ this._insertCode(msg.code);
3324
+ break;
3325
+ case "listAvailableLibs":
3326
+ this._listAvailableLibs(msg.search, msg.page);
3327
+ break;
3328
+ case "listProjectLibs":
3329
+ this._listProjectLibs();
3330
+ break;
3331
+ case "addLib":
3332
+ this._addLib(msg.libraryId);
3333
+ break;
3334
+ case "removeLib":
3335
+ this._removeLib(msg.libraryId);
3336
+ break;
3337
+ case "getProjectConfig":
3338
+ this._sendProjectConfig();
3339
+ break;
3340
+ case "saveProjectConfig":
3341
+ this._saveProjectConfig(msg.config);
3342
+ break;
3343
+ }
3344
+ });
3345
+ setTimeout(() => {
3346
+ this._sendConfig();
3347
+ this._sendMcpStatus();
3348
+ this._sendAuthStatus();
3349
+ }, 150);
3350
+ }
3351
+ _getHtmlFromFile() {
3352
+ const candidates = [
3353
+ path5.join(__dirname, "webview.html"),
3354
+ path5.join(this._extensionUri.fsPath, "out", "webview.html"),
3355
+ path5.join(this._extensionUri.fsPath, "src", "chat", "webview.html")
3356
+ ];
3357
+ for (const filePath of candidates) {
3358
+ if (fs5.existsSync(filePath)) {
3359
+ try {
3360
+ return fs5.readFileSync(filePath, "utf8");
3361
+ } catch {
3362
+ }
3363
+ }
3364
+ }
3365
+ return `<!DOCTYPE html><html><body><p style="color:red;padding:20px">Failed to load webview HTML. Looked in: ${candidates.join(", ")}</p></body></html>`;
3366
+ }
3367
+ _post(msg) {
3368
+ this._view?.webview.postMessage(msg);
3369
+ }
3370
+ _sendConfig() {
3371
+ const config = loadAiConfig();
3372
+ const providers = AI_PROVIDERS.filter((p) => !p.disabled);
3373
+ const models = config.provider ? PROVIDER_MODELS[config.provider] || [] : [];
3374
+ const hasKey = config.provider ? config.provider === "ollama" || !!getApiKey(config.provider) : false;
3375
+ this._post({
3376
+ type: "config",
3377
+ provider: config.provider || "",
3378
+ model: config.model || "",
3379
+ providers,
3380
+ models,
3381
+ hasApiKey: hasKey,
3382
+ needsSetup: !config.provider || !hasKey && config.provider !== "ollama"
3383
+ });
3384
+ }
3385
+ _sendAuthStatus() {
3386
+ this._post({
3387
+ type: "authStatus",
3388
+ authenticated: isAuthenticated(),
3389
+ hasProject: !!this._getProjectId()
3390
+ });
3391
+ }
3392
+ _sendMcpStatus() {
3393
+ const status = getMcpStatus();
3394
+ let server;
3395
+ try {
3396
+ server = checkMcpServerAvailable();
3397
+ } catch {
3398
+ server = { available: false, method: "none", detail: "Could not check availability" };
3399
+ }
3400
+ this._post({ type: "mcpStatus", editors: status.editors, summary: status.summary, server });
3401
+ }
3402
+ _handleInstallMcp(name) {
3403
+ const ed = getMcpStatus().editors.find((e) => e.name === name);
3404
+ if (ed) {
3405
+ const r = installMcpForEditor(ed);
3406
+ vscode5.window.showInformationMessage(r.message);
3407
+ this._sendMcpStatus();
3408
+ }
3409
+ }
3410
+ _handleRemoveMcp(name) {
3411
+ const ed = getMcpStatus().editors.find((e) => e.name === name);
3412
+ if (ed) {
3413
+ const r = removeMcpForEditor(ed);
3414
+ vscode5.window.showInformationMessage(r.message);
3415
+ this._sendMcpStatus();
3416
+ }
3417
+ }
3418
+ async _listAvailableLibs(search, page) {
3419
+ try {
3420
+ const libs = await listAvailableLibraries({ search, page: page || 1, limit: 20 });
3421
+ this._post({ type: "availableLibs", libs, search: search || "" });
3422
+ } catch (err) {
3423
+ this._post({ type: "libsError", text: err.message });
3424
+ }
3425
+ }
3426
+ async _listProjectLibs() {
3427
+ const pid = this._getProjectId();
3428
+ if (!pid) {
3429
+ this._post({ type: "libsError", text: "No project linked. Run smbls project link first." });
3430
+ return;
3431
+ }
3432
+ try {
3433
+ const libs = await listProjectLibraries(pid);
3434
+ this._post({ type: "projectLibs", libs });
3435
+ } catch (err) {
3436
+ this._post({ type: "libsError", text: err.message });
3437
+ }
3438
+ }
3439
+ async _addLib(id) {
3440
+ const pid = this._getProjectId();
3441
+ if (!pid) {
3442
+ this._post({ type: "libsError", text: "No project linked." });
3443
+ return;
3444
+ }
3445
+ try {
3446
+ await addProjectLibraries(pid, [id]);
3447
+ vscode5.window.showInformationMessage("Shared library added");
3448
+ this._listProjectLibs();
3449
+ } catch (err) {
3450
+ this._post({ type: "libsError", text: err.message });
3451
+ }
3452
+ }
3453
+ async _removeLib(id) {
3454
+ const pid = this._getProjectId();
3455
+ if (!pid) {
3456
+ this._post({ type: "libsError", text: "No project linked." });
3457
+ return;
3458
+ }
3459
+ try {
3460
+ await removeProjectLibraries(pid, [id]);
3461
+ vscode5.window.showInformationMessage("Shared library removed");
3462
+ this._listProjectLibs();
3463
+ } catch (err) {
3464
+ this._post({ type: "libsError", text: err.message });
3465
+ }
3466
+ }
3467
+ _getProjectId() {
3468
+ const root = vscode5.workspace.workspaceFolders?.[0]?.uri.fsPath;
3469
+ return resolveProjectId(root);
3470
+ }
3471
+ _sendProjectConfig() {
3472
+ const root = vscode5.workspace.workspaceFolders?.[0]?.uri.fsPath;
3473
+ if (!root) {
3474
+ this._post({ type: "projectConfig", found: false, config: {} });
3475
+ return;
3476
+ }
3477
+ const cfgPath = path5.join(root, "symbols.json");
3478
+ try {
3479
+ if (fs5.existsSync(cfgPath)) {
3480
+ const config = JSON.parse(fs5.readFileSync(cfgPath, "utf8"));
3481
+ this._post({ type: "projectConfig", found: true, config });
3482
+ } else {
3483
+ this._post({ type: "projectConfig", found: false, config: {} });
3484
+ }
3485
+ } catch {
3486
+ this._post({ type: "projectConfig", found: false, config: {} });
3487
+ }
3488
+ }
3489
+ _saveProjectConfig(config) {
3490
+ const root = vscode5.workspace.workspaceFolders?.[0]?.uri.fsPath;
3491
+ if (!root) {
3492
+ this._post({ type: "projectConfigError", text: "No workspace folder open" });
3493
+ return;
3494
+ }
3495
+ const cfgPath = path5.join(root, "symbols.json");
3496
+ try {
3497
+ let existing = {};
3498
+ if (fs5.existsSync(cfgPath)) {
3499
+ existing = JSON.parse(fs5.readFileSync(cfgPath, "utf8"));
3500
+ }
3501
+ const merged = { ...existing, ...config };
3502
+ for (const key of Object.keys(merged)) {
3503
+ if (merged[key] === "") delete merged[key];
3504
+ }
3505
+ fs5.writeFileSync(cfgPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
3506
+ this._post({ type: "projectConfigSaved" });
3507
+ } catch (err) {
3508
+ this._post({ type: "projectConfigError", text: err.message });
3509
+ }
3510
+ }
3511
+ async _handleSendMessage(text) {
3512
+ const trimmed = text.trim();
3513
+ if (trimmed.startsWith("/")) {
3514
+ const parts = trimmed.split(/\s+/);
3515
+ const cmd = parts[0].toLowerCase();
3516
+ const args = parts.slice(1);
3517
+ if (cmd === "/libraries" || cmd === "/libs") {
3518
+ const sub = args[0]?.toLowerCase();
3519
+ if (sub === "available" || sub === "search") {
3520
+ this._post({ type: "switchTab", tab: "libraries" });
3521
+ await this._listAvailableLibs(args.slice(1).join(" ") || void 0);
3522
+ } else if (sub === "add" && args[1]) {
3523
+ await this._addLib(args[1]);
3524
+ } else if (sub === "remove" && args[1]) {
3525
+ await this._removeLib(args[1]);
3526
+ } else {
3527
+ this._post({ type: "switchTab", tab: "libraries" });
3528
+ this._listProjectLibs();
3529
+ this._listAvailableLibs();
3530
+ }
3531
+ return;
3532
+ }
3533
+ if (cmd === "/mcp") {
3534
+ this._post({ type: "switchTab", tab: "mcp" });
3535
+ this._sendMcpStatus();
3536
+ return;
3537
+ }
3538
+ if (cmd === "/project") {
3539
+ this._post({ type: "switchTab", tab: "project" });
3540
+ this._sendProjectConfig();
3541
+ return;
3542
+ }
3543
+ if (cmd === "/clear") {
3544
+ this._messages = [];
3545
+ this._post({ type: "chatCleared" });
3546
+ return;
3547
+ }
3548
+ if (cmd === "/config" || cmd === "/settings") {
3549
+ this._post({ type: "switchTab", tab: "settings" });
3550
+ return;
3551
+ }
3552
+ if (cmd === "/help") {
3553
+ this._post({ type: "systemMessage", text: "**Commands:** /libraries, /mcp, /project, /config, /clear, /help" });
3554
+ return;
3555
+ }
3556
+ }
3557
+ const config = loadAiConfig();
3558
+ if (!config.provider || !config.model) {
3559
+ this._post({ type: "error", text: "Configure your AI provider in the Settings tab first." });
3560
+ return;
3561
+ }
3562
+ const apiKey = config.provider === "ollama" ? "" : getApiKey(config.provider);
3563
+ if (config.provider !== "ollama" && !apiKey) {
3564
+ this._post({ type: "error", text: `No API key for ${config.provider}. Set it in Settings tab.` });
3565
+ return;
3566
+ }
3567
+ let content = text;
3568
+ if (this._messages.length === 0) {
3569
+ const ctx = this._getWorkspaceContext();
3570
+ if (ctx) content = `[Context: ${ctx}]
3571
+
3572
+ ${text}`;
3573
+ }
3574
+ this._messages.push({ role: "user", content });
3575
+ this._post({ type: "streamStart" });
3576
+ let response = "";
3577
+ streamChat(
3578
+ config.provider,
3579
+ config.model,
3580
+ apiKey || "",
3581
+ this._messages,
3582
+ (token) => {
3583
+ response += token;
3584
+ this._post({ type: "streamToken", text: token });
3585
+ },
3586
+ () => {
3587
+ this._messages.push({ role: "assistant", content: response });
3588
+ this._post({ type: "streamEnd" });
3589
+ },
3590
+ (err) => {
3591
+ this._messages.pop();
3592
+ this._post({ type: "error", text: err.message });
3593
+ this._post({ type: "streamEnd" });
3594
+ }
3595
+ );
3596
+ }
3597
+ _getWorkspaceContext() {
3598
+ const root = vscode5.workspace.workspaceFolders?.[0]?.uri.fsPath;
3599
+ if (!root) return "";
3600
+ try {
3601
+ const cfgPath = path5.join(root, "symbols.json");
3602
+ if (fs5.existsSync(cfgPath)) {
3603
+ const c = JSON.parse(fs5.readFileSync(cfgPath, "utf8"));
3604
+ return `Project: ${c.key || "unnamed"}, bundler: ${c.bundler || "parcel"}, dir: ${c.dir || "./symbols"}`;
3605
+ }
3606
+ } catch {
3607
+ }
3608
+ return `Workspace: ${path5.basename(root)}`;
3609
+ }
3610
+ _insertCode(code) {
3611
+ const editor = vscode5.window.activeTextEditor;
3612
+ if (editor) editor.edit((b) => b.insert(editor.selection.active, code));
3613
+ }
3614
+ };
3615
+
2739
3616
  // src/extension.ts
2740
3617
  var LANGUAGES = ["javascript", "typescript", "javascriptreact", "typescriptreact"];
2741
3618
  var output;
2742
3619
  function activate(context) {
2743
- output = vscode5.window.createOutputChannel("Symbols.app");
3620
+ output = vscode6.window.createOutputChannel("Symbols.app");
2744
3621
  output.appendLine("Symbols.app extension activating...");
2745
3622
  const completionProvider = new DomqlCompletionProvider();
2746
3623
  const hoverProvider = new DomqlHoverProvider();
@@ -2748,7 +3625,7 @@ function activate(context) {
2748
3625
  for (const lang of LANGUAGES) {
2749
3626
  const selector = { language: lang, scheme: "file" };
2750
3627
  context.subscriptions.push(
2751
- vscode5.languages.registerCompletionItemProvider(
3628
+ vscode6.languages.registerCompletionItemProvider(
2752
3629
  selector,
2753
3630
  completionProvider,
2754
3631
  ".",
@@ -2761,33 +3638,33 @@ function activate(context) {
2761
3638
  )
2762
3639
  );
2763
3640
  context.subscriptions.push(
2764
- vscode5.languages.registerHoverProvider(selector, hoverProvider)
3641
+ vscode6.languages.registerHoverProvider(selector, hoverProvider)
2765
3642
  );
2766
3643
  context.subscriptions.push(
2767
- vscode5.languages.registerDefinitionProvider(selector, definitionProvider)
3644
+ vscode6.languages.registerDefinitionProvider(selector, definitionProvider)
2768
3645
  );
2769
3646
  }
2770
- const watcher = vscode5.workspace.createFileSystemWatcher("**/*.{js,ts,jsx,tsx}");
3647
+ const watcher = vscode6.workspace.createFileSystemWatcher("**/*.{js,ts,jsx,tsx}");
2771
3648
  watcher.onDidChange(() => invalidateCache());
2772
3649
  watcher.onDidCreate(() => invalidateCache());
2773
3650
  watcher.onDidDelete(() => invalidateCache());
2774
3651
  context.subscriptions.push(watcher);
2775
3652
  context.subscriptions.push(
2776
- vscode5.commands.registerCommand("symbolsApp.toggle", () => {
2777
- const config = vscode5.workspace.getConfiguration("symbolsApp");
3653
+ vscode6.commands.registerCommand("symbolsApp.toggle", () => {
3654
+ const config = vscode6.workspace.getConfiguration("symbolsApp");
2778
3655
  const current = config.get("enable", true);
2779
- config.update("enable", !current, vscode5.ConfigurationTarget.Global);
2780
- vscode5.window.showInformationMessage(
3656
+ config.update("enable", !current, vscode6.ConfigurationTarget.Global);
3657
+ vscode6.window.showInformationMessage(
2781
3658
  `Symbols.app ${!current ? "enabled" : "disabled"}`
2782
3659
  );
2783
3660
  })
2784
3661
  );
2785
3662
  context.subscriptions.push(
2786
- vscode5.commands.registerCommand("symbolsApp.diagnose", () => {
3663
+ vscode6.commands.registerCommand("symbolsApp.diagnose", () => {
2787
3664
  output.show();
2788
3665
  output.appendLine("--- Diagnostics ---");
2789
- output.appendLine(`Workspace folders: ${vscode5.workspace.workspaceFolders?.map((f) => f.uri.fsPath).join(", ")}`);
2790
- const editor = vscode5.window.activeTextEditor;
3666
+ output.appendLine(`Workspace folders: ${vscode6.workspace.workspaceFolders?.map((f) => f.uri.fsPath).join(", ")}`);
3667
+ const editor = vscode6.window.activeTextEditor;
2791
3668
  if (editor) {
2792
3669
  output.appendLine(`Active file: ${editor.document.uri.fsPath}`);
2793
3670
  output.appendLine(`Language: ${editor.document.languageId}`);
@@ -2799,11 +3676,20 @@ function activate(context) {
2799
3676
  output.appendLine(`Has component export: ${/export\s+(?:const|let|var)\s+[A-Z][a-zA-Z0-9]+\s*=\s*\{/.test(text)}`);
2800
3677
  }
2801
3678
  output.appendLine("--- End ---");
2802
- vscode5.window.showInformationMessage("Symbols.app: Check Output panel (Symbols.app channel)");
3679
+ vscode6.window.showInformationMessage("Symbols.app: Check Output panel (Symbols.app channel)");
3680
+ })
3681
+ );
3682
+ const chatProvider = new ChatViewProvider(context.extensionUri);
3683
+ context.subscriptions.push(
3684
+ vscode6.window.registerWebviewViewProvider(ChatViewProvider.viewType, chatProvider)
3685
+ );
3686
+ context.subscriptions.push(
3687
+ vscode6.commands.registerCommand("symbolsApp.openChat", () => {
3688
+ vscode6.commands.executeCommand("symbolsChat.focus");
2803
3689
  })
2804
3690
  );
2805
3691
  output.appendLine("Symbols.app extension activated successfully");
2806
- vscode5.window.showInformationMessage("Symbols.app Connect active");
3692
+ vscode6.window.showInformationMessage("Symbols.app Connect active");
2807
3693
  }
2808
3694
  function deactivate() {
2809
3695
  }