diff --git a/README.md b/README.md index 25880a8..4acb961 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ - **Git Explorer** - View, stage and commit your changes. You can also switch branches - **Session Management** - Resume conversations, manage multiple sessions, and track history - **TaskMaster AI Integration** *(Optional)* - Advanced project management with AI-powered task planning, PRD parsing, and workflow automation -- **Model Compatibility** - Works with Claude Sonnet 4.5, Opus 4.5, GPT-5.2, and Gemini. +- **Model Compatibility** - Works with Claude, GPT, and Gemini model families (see [`shared/modelConstants.js`](shared/modelConstants.js) for the full list of supported models) ## Quick Start diff --git a/plugins/starter b/plugins/starter new file mode 160000 index 0000000..bfa6332 --- /dev/null +++ b/plugins/starter @@ -0,0 +1 @@ +Subproject commit bfa63328103ca330a012bc083e4f934adbc2086e diff --git a/server/utils/plugin-loader.js b/server/utils/plugin-loader.js index bbcdfd2..b95b69e 100644 --- a/server/utils/plugin-loader.js +++ b/server/utils/plugin-loader.js @@ -89,6 +89,23 @@ export function scanPlugins() { continue; } + // Try to read git remote URL + let repoUrl = null; + try { + const gitConfigPath = path.join(pluginsDir, entry.name, '.git', 'config'); + if (fs.existsSync(gitConfigPath)) { + const gitConfig = fs.readFileSync(gitConfigPath, 'utf-8'); + const match = gitConfig.match(/url\s*=\s*(.+)/); + if (match) { + repoUrl = match[1].trim().replace(/\.git$/, ''); + // Convert SSH URLs to HTTPS + if (repoUrl.startsWith('git@')) { + repoUrl = repoUrl.replace(/^git@([^:]+):/, 'https://$1/'); + } + } + } + } catch { /* ignore */ } + plugins.push({ name: manifest.name, displayName: manifest.displayName, @@ -103,6 +120,7 @@ export function scanPlugins() { permissions: manifest.permissions || [], enabled: config[manifest.name]?.enabled !== false, // enabled by default dirName: entry.name, + repoUrl, }); } catch (err) { console.warn(`[Plugins] Failed to read manifest for ${entry.name}:`, err.message); @@ -137,6 +155,13 @@ export function resolvePluginAssetPath(name, assetPath) { export function installPluginFromGit(url) { return new Promise((resolve, reject) => { + if (typeof url !== 'string' || !url.trim()) { + return reject(new Error('Invalid URL: must be a non-empty string')); + } + if (url.startsWith('-')) { + return reject(new Error('Invalid URL: must not start with "-"')); + } + // Extract repo name from URL for directory name const urlClean = url.replace(/\.git$/, '').replace(/\/$/, ''); const repoName = urlClean.split('/').pop(); @@ -174,7 +199,7 @@ export function installPluginFromGit(url) { resolve(manifest); }; - const gitProcess = spawn('git', ['clone', '--depth', '1', url, tempDir], { + const gitProcess = spawn('git', ['clone', '--depth', '1', '--', url, tempDir], { stdio: ['ignore', 'pipe', 'pipe'], }); @@ -249,7 +274,7 @@ export function updatePluginFromGit(name) { } // Only fast-forward to avoid silent divergence - const gitProcess = spawn('git', ['pull', '--ff-only'], { + const gitProcess = spawn('git', ['pull', '--ff-only', '--'], { cwd: pluginDir, stdio: ['ignore', 'pipe', 'pipe'], }); diff --git a/src/components/plugins/PluginSettingsTab.tsx b/src/components/plugins/PluginSettingsTab.tsx index a918a87..132c9f7 100644 --- a/src/components/plugins/PluginSettingsTab.tsx +++ b/src/components/plugins/PluginSettingsTab.tsx @@ -1,5 +1,5 @@ import { useState } from 'react'; -import { Trash2, RefreshCw, GitBranch, Loader2, ServerCrash, ChevronRight, ShieldAlert, ExternalLink, BookOpen, Download, BarChart3 } from 'lucide-react'; +import { Trash2, RefreshCw, GitBranch, Loader2, ServerCrash, ShieldAlert, ExternalLink, BookOpen, Download, BarChart3 } from 'lucide-react'; import { usePlugins } from '../../contexts/PluginsContext'; import PluginIcon from './PluginIcon'; import type { Plugin } from '../../contexts/PluginsContext'; @@ -76,7 +76,7 @@ function PluginCard({ return (
-+
{plugin.description}
)} - {plugin.author && ( -- {plugin.author} -
- )} ++
File counts, lines of code, file-type breakdown, and recent activity for your project.
+ +No plugins installed
-- Install from git or drop a folder in the plugins directory -
-
- Extend the interface with custom plugins. Install from{' '}
-
- git
- {' '}
- or drop a folder in{' '}
-
- ~/.claude-code-ui/plugins/
-
-
+ Extend the interface with custom plugins. Install from{' '}
+
+ git
+ {' '}
+ or drop a folder in{' '}
+
+ ~/.claude-code-ui/plugins/
+
+
{installError}
+{installError}
)} -+
No plugins installed
+ ) : plugins.length === 0 ? ( +No plugins installed
+ ) : ( +