+
+Scan the QR code to join the WeChat user group for discussions, feedback, and updates.
+
---
## Features
diff --git a/README_CN.md b/README_CN.md
index f2ff22e..11b7a20 100644
--- a/README_CN.md
+++ b/README_CN.md
@@ -9,6 +9,12 @@
[](https://github.com/op7418/CodePilot/releases)
[](LICENSE)
+### 加入用户群
+
+
+
+扫描二维码加入微信用户群,交流使用心得、反馈问题和获取最新动态。
+
---
## 功能特性
diff --git a/docs/wechat-group-qr.png b/docs/wechat-group-qr.png
new file mode 100644
index 0000000..d01343f
Binary files /dev/null and b/docs/wechat-group-qr.png differ
diff --git a/package-lock.json b/package-lock.json
index 1ee6bfb..d7dc950 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "codepilot",
- "version": "0.17.8",
+ "version": "0.18.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "codepilot",
- "version": "0.17.8",
+ "version": "0.18.0",
"dependencies": {
"@ai-sdk/anthropic": "^3.0.47",
"@ai-sdk/google": "^3.0.31",
diff --git a/package.json b/package.json
index 8bc3828..1731999 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "codepilot",
- "version": "0.17.8",
+ "version": "0.18.0",
"private": true,
"author": {
"name": "op7418",
diff --git a/src/app/api/skills/marketplace/install/route.ts b/src/app/api/skills/marketplace/install/route.ts
new file mode 100644
index 0000000..636ec15
--- /dev/null
+++ b/src/app/api/skills/marketplace/install/route.ts
@@ -0,0 +1,76 @@
+import { NextResponse } from "next/server";
+import { spawn } from "child_process";
+
+export async function POST(request: Request) {
+ try {
+ const body = await request.json();
+ const { source, global: isGlobal } = body as { source: string; global?: boolean };
+
+ if (!source || typeof source !== "string") {
+ return NextResponse.json(
+ { error: "source is required" },
+ { status: 400 }
+ );
+ }
+
+ const args = ["skills", "add", source, "-y", "--agent", "claude-code"];
+ if (isGlobal !== false) {
+ args.splice(3, 0, "-g");
+ }
+
+ const child = spawn("npx", args, {
+ env: { ...process.env },
+ shell: true,
+ });
+
+ const encoder = new TextEncoder();
+ const stream = new ReadableStream({
+ start(controller) {
+ const send = (event: string, data: string) => {
+ controller.enqueue(
+ encoder.encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`)
+ );
+ };
+
+ child.stdout?.on("data", (chunk: Buffer) => {
+ send("output", chunk.toString());
+ });
+
+ child.stderr?.on("data", (chunk: Buffer) => {
+ send("output", chunk.toString());
+ });
+
+ child.on("close", (code) => {
+ if (code === 0) {
+ send("done", "Install completed successfully");
+ } else {
+ send("error", `Process exited with code ${code}`);
+ }
+ controller.close();
+ });
+
+ child.on("error", (err) => {
+ send("error", err.message);
+ controller.close();
+ });
+ },
+ cancel() {
+ child.kill();
+ },
+ });
+
+ return new Response(stream, {
+ headers: {
+ "Content-Type": "text/event-stream",
+ "Cache-Control": "no-cache",
+ Connection: "keep-alive",
+ },
+ });
+ } catch (error) {
+ console.error("[marketplace/install] Error:", error);
+ return NextResponse.json(
+ { error: error instanceof Error ? error.message : "Install failed" },
+ { status: 500 }
+ );
+ }
+}
diff --git a/src/app/api/skills/marketplace/readme/route.ts b/src/app/api/skills/marketplace/readme/route.ts
new file mode 100644
index 0000000..342bc92
--- /dev/null
+++ b/src/app/api/skills/marketplace/readme/route.ts
@@ -0,0 +1,95 @@
+import { NextRequest, NextResponse } from "next/server";
+
+// In-memory cache: repo source → Map{t('skills.marketplaceError')}
+{error}
+{t('skills.searchNoResults')}
+{t('skills.marketplaceHint')}
+{t('skills.marketplaceHintDesc')}
+{t('skills.noReadme')}
+{skill.description}
diff --git a/src/components/skills/SkillsManager.tsx b/src/components/skills/SkillsManager.tsx index 9c6462b..b2ad687 100644 --- a/src/components/skills/SkillsManager.tsx +++ b/src/components/skills/SkillsManager.tsx @@ -10,9 +10,13 @@ import { PlusSignIcon, Search01Icon, ZapIcon, Loading02Icon } from "@hugeicons/c import { SkillListItem } from "./SkillListItem"; import { SkillEditor } from "./SkillEditor"; import { CreateSkillDialog } from "./CreateSkillDialog"; +import { MarketplaceBrowser } from "./MarketplaceBrowser"; import { useTranslation } from "@/hooks/useTranslation"; +import { cn } from "@/lib/utils"; import type { SkillItem } from "./SkillListItem"; +type ViewTab = "local" | "marketplace"; + export function SkillsManager() { const { workingDirectory } = usePanel(); const { t } = useTranslation(); @@ -21,6 +25,7 @@ export function SkillsManager() { const [loading, setLoading] = useState(true); const [search, setSearch] = useState(""); const [showCreate, setShowCreate] = useState(false); + const [viewTab, setViewTab] = useState