claude-world-studio 1.0.0
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/.env.example +30 -0
- package/.mcp.json +51 -0
- package/README.md +224 -0
- package/client/App.tsx +446 -0
- package/client/components/ChatWindow.tsx +790 -0
- package/client/components/FileExplorer.tsx +218 -0
- package/client/components/FilePreviewModal.tsx +179 -0
- package/client/components/PublishDialog.tsx +307 -0
- package/client/components/SettingsPage.tsx +452 -0
- package/client/components/Sidebar.tsx +198 -0
- package/client/components/ToolUseBlock.tsx +140 -0
- package/client/index.html +12 -0
- package/client/index.tsx +10 -0
- package/client/styles/globals.css +48 -0
- package/demo/01-welcome.png +0 -0
- package/demo/02-pipeline-cards.png +0 -0
- package/demo/03-custom-topic-fill.png +0 -0
- package/demo/04-topic-typed.png +0 -0
- package/demo/05-loading-state.png +0 -0
- package/demo/06-tool-calls.png +0 -0
- package/demo/07-history-rich.png +0 -0
- package/demo/09-en-cards.png +0 -0
- package/demo/10-ja-cards.png +0 -0
- package/demo/capture-remaining.mjs +73 -0
- package/demo/capture.mjs +110 -0
- package/demo/demo-walkthrough-2.webm +0 -0
- package/demo/demo-walkthrough.webm +0 -0
- package/package.json +48 -0
- package/postcss.config.js +6 -0
- package/scripts/threads_api.py +536 -0
- package/server/ai-client.ts +356 -0
- package/server/db.ts +299 -0
- package/server/mcp-config.ts +85 -0
- package/server/routes/accounts.ts +88 -0
- package/server/routes/files.ts +175 -0
- package/server/routes/publish.ts +77 -0
- package/server/routes/sessions.ts +59 -0
- package/server/routes/settings.ts +220 -0
- package/server/server.ts +261 -0
- package/server/services/social-publisher.ts +74 -0
- package/server/services/studio-mcp.ts +107 -0
- package/server/session.ts +167 -0
- package/server/types.ts +86 -0
- package/tailwind.config.js +8 -0
- package/tsconfig.json +16 -0
- package/vite.config.ts +19 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
|
|
3
|
+
interface ToolUseBlockProps {
|
|
4
|
+
toolName: string;
|
|
5
|
+
toolInput: Record<string, unknown>;
|
|
6
|
+
toolId: string;
|
|
7
|
+
onPreviewFile?: (absolutePath: string) => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Color mapping for MCP server tools
|
|
11
|
+
function getToolColor(name: string): string {
|
|
12
|
+
if (name.startsWith("mcp__trend-pulse") || name.startsWith("mcp__trend_pulse")) {
|
|
13
|
+
return "border-emerald-300 bg-emerald-50";
|
|
14
|
+
}
|
|
15
|
+
if (name.startsWith("mcp__cf-browser") || name.startsWith("mcp__cf_browser")) {
|
|
16
|
+
return "border-blue-300 bg-blue-50";
|
|
17
|
+
}
|
|
18
|
+
if (name.startsWith("mcp__notebooklm")) {
|
|
19
|
+
return "border-purple-300 bg-purple-50";
|
|
20
|
+
}
|
|
21
|
+
return "border-gray-200 bg-gray-50";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getToolBadge(name: string): { label: string; color: string } | null {
|
|
25
|
+
if (name.startsWith("mcp__trend-pulse") || name.startsWith("mcp__trend_pulse")) {
|
|
26
|
+
return { label: "trend-pulse", color: "bg-emerald-100 text-emerald-700" };
|
|
27
|
+
}
|
|
28
|
+
if (name.startsWith("mcp__cf-browser") || name.startsWith("mcp__cf_browser")) {
|
|
29
|
+
return { label: "cf-browser", color: "bg-blue-100 text-blue-700" };
|
|
30
|
+
}
|
|
31
|
+
if (name.startsWith("mcp__notebooklm")) {
|
|
32
|
+
return { label: "notebooklm", color: "bg-purple-100 text-purple-700" };
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getToolDisplayName(name: string): string {
|
|
38
|
+
return name
|
|
39
|
+
.replace(/^mcp__(trend[-_]pulse|cf[-_]browser|notebooklm)__/, "")
|
|
40
|
+
.replace(/_/g, " ");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function str(val: unknown): string {
|
|
44
|
+
return typeof val === "string" ? val : "";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getToolSummary(name: string, input: Record<string, unknown>): string {
|
|
48
|
+
switch (name) {
|
|
49
|
+
case "Read":
|
|
50
|
+
case "Write":
|
|
51
|
+
case "Edit":
|
|
52
|
+
return str(input.file_path);
|
|
53
|
+
case "Bash": {
|
|
54
|
+
const cmd = str(input.command);
|
|
55
|
+
return cmd.length > 80 ? cmd.slice(0, 80) + "..." : cmd;
|
|
56
|
+
}
|
|
57
|
+
case "Grep":
|
|
58
|
+
return `"${str(input.pattern)}" in ${str(input.path) || "."}`;
|
|
59
|
+
case "Glob":
|
|
60
|
+
return str(input.pattern);
|
|
61
|
+
case "WebSearch":
|
|
62
|
+
return str(input.query);
|
|
63
|
+
case "WebFetch":
|
|
64
|
+
return str(input.url);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (input.topic) return str(input.topic);
|
|
68
|
+
if (input.query) return str(input.query);
|
|
69
|
+
if (input.url) return str(input.url);
|
|
70
|
+
if (input.keyword) return str(input.keyword);
|
|
71
|
+
if (input.sources) return `sources: ${str(input.sources)}`;
|
|
72
|
+
|
|
73
|
+
return JSON.stringify(input).slice(0, 60);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const PREVIEWABLE_EXTS = [
|
|
77
|
+
"png", "jpg", "jpeg", "gif", "webp", "svg",
|
|
78
|
+
"pdf", "md", "txt", "json", "ts", "tsx", "js", "jsx", "py", "html", "css",
|
|
79
|
+
"mp3", "wav", "m4a", "mp4", "webm",
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
function isPreviewable(filePath: string): boolean {
|
|
83
|
+
const ext = filePath.split(".").pop()?.toLowerCase() || "";
|
|
84
|
+
return PREVIEWABLE_EXTS.includes(ext);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function ToolUseBlock({ toolName, toolInput, toolId, onPreviewFile }: ToolUseBlockProps) {
|
|
88
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
89
|
+
const colorClass = getToolColor(toolName);
|
|
90
|
+
const badge = getToolBadge(toolName);
|
|
91
|
+
|
|
92
|
+
const filePath = toolInput.file_path as string | undefined;
|
|
93
|
+
const canPreview = onPreviewFile && filePath && isPreviewable(filePath) &&
|
|
94
|
+
(toolName === "Write" || toolName === "Read" || toolName === "Edit");
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<div className={`my-2 border rounded ${colorClass}`}>
|
|
98
|
+
<div className="p-2 flex items-center justify-between">
|
|
99
|
+
{/* Collapse toggle — clickable label area */}
|
|
100
|
+
<div
|
|
101
|
+
role="button"
|
|
102
|
+
tabIndex={0}
|
|
103
|
+
onClick={() => setIsExpanded(!isExpanded)}
|
|
104
|
+
onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); setIsExpanded(!isExpanded); } }}
|
|
105
|
+
className="flex items-center gap-2 min-w-0 cursor-pointer hover:opacity-80 flex-1"
|
|
106
|
+
>
|
|
107
|
+
<span className="text-xs text-gray-400 shrink-0">{isExpanded ? "▼" : "▶"}</span>
|
|
108
|
+
{badge && (
|
|
109
|
+
<span className={`text-[10px] font-medium px-1.5 py-0.5 rounded ${badge.color}`}>
|
|
110
|
+
{badge.label}
|
|
111
|
+
</span>
|
|
112
|
+
)}
|
|
113
|
+
<span className="text-xs font-semibold text-gray-600 uppercase shrink-0">
|
|
114
|
+
{getToolDisplayName(toolName)}
|
|
115
|
+
</span>
|
|
116
|
+
<span className="text-xs text-gray-500 truncate">
|
|
117
|
+
{getToolSummary(toolName, toolInput)}
|
|
118
|
+
</span>
|
|
119
|
+
</div>
|
|
120
|
+
{/* Preview button — separate from toggle, no nesting */}
|
|
121
|
+
{canPreview && (
|
|
122
|
+
<button
|
|
123
|
+
type="button"
|
|
124
|
+
onClick={() => onPreviewFile!(filePath!)}
|
|
125
|
+
className="text-[10px] px-2 py-0.5 rounded bg-blue-100 text-blue-600 hover:bg-blue-200 transition-colors shrink-0 ml-2"
|
|
126
|
+
>
|
|
127
|
+
Preview
|
|
128
|
+
</button>
|
|
129
|
+
)}
|
|
130
|
+
</div>
|
|
131
|
+
{isExpanded && (
|
|
132
|
+
<div className="p-2 border-t border-gray-200/50">
|
|
133
|
+
<pre className="text-xs bg-white p-2 rounded overflow-x-auto max-h-64 overflow-y-auto">
|
|
134
|
+
{JSON.stringify(toolInput, null, 2)}
|
|
135
|
+
</pre>
|
|
136
|
+
</div>
|
|
137
|
+
)}
|
|
138
|
+
</div>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Claude World Studio</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/index.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
package/client/index.tsx
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { createRoot } from "react-dom/client";
|
|
3
|
+
import App from "./App";
|
|
4
|
+
import "./styles/globals.css";
|
|
5
|
+
|
|
6
|
+
const container = document.getElementById("root");
|
|
7
|
+
if (container) {
|
|
8
|
+
const root = createRoot(container);
|
|
9
|
+
root.render(<App />);
|
|
10
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
@tailwind base;
|
|
2
|
+
@tailwind components;
|
|
3
|
+
@tailwind utilities;
|
|
4
|
+
|
|
5
|
+
/* Custom scrollbar */
|
|
6
|
+
::-webkit-scrollbar {
|
|
7
|
+
width: 6px;
|
|
8
|
+
}
|
|
9
|
+
::-webkit-scrollbar-track {
|
|
10
|
+
background: transparent;
|
|
11
|
+
}
|
|
12
|
+
::-webkit-scrollbar-thumb {
|
|
13
|
+
background: #4b5563;
|
|
14
|
+
border-radius: 3px;
|
|
15
|
+
}
|
|
16
|
+
::-webkit-scrollbar-thumb:hover {
|
|
17
|
+
background: #6b7280;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* Pulse dot animation */
|
|
21
|
+
@keyframes pulse-dot {
|
|
22
|
+
0%, 100% { opacity: 1; }
|
|
23
|
+
50% { opacity: 0.3; }
|
|
24
|
+
}
|
|
25
|
+
.animate-pulse-dot {
|
|
26
|
+
animation: pulse-dot 1.5s ease-in-out infinite;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* Typing indicator */
|
|
30
|
+
@keyframes typing {
|
|
31
|
+
0% { opacity: 0.3; }
|
|
32
|
+
20% { opacity: 1; }
|
|
33
|
+
100% { opacity: 0.3; }
|
|
34
|
+
}
|
|
35
|
+
.typing-dot:nth-child(1) { animation: typing 1.4s infinite 0s; }
|
|
36
|
+
.typing-dot:nth-child(2) { animation: typing 1.4s infinite 0.2s; }
|
|
37
|
+
.typing-dot:nth-child(3) { animation: typing 1.4s infinite 0.4s; }
|
|
38
|
+
|
|
39
|
+
/* Workflow step connector */
|
|
40
|
+
.workflow-step + .workflow-step::before {
|
|
41
|
+
content: '';
|
|
42
|
+
position: absolute;
|
|
43
|
+
left: 50%;
|
|
44
|
+
top: -12px;
|
|
45
|
+
width: 2px;
|
|
46
|
+
height: 12px;
|
|
47
|
+
background: #d1d5db;
|
|
48
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { chromium } from "playwright";
|
|
2
|
+
import { setTimeout } from "timers/promises";
|
|
3
|
+
|
|
4
|
+
const BASE = "http://localhost:5173";
|
|
5
|
+
const OUT = new URL(".", import.meta.url).pathname;
|
|
6
|
+
|
|
7
|
+
(async () => {
|
|
8
|
+
const browser = await chromium.launch({ headless: true });
|
|
9
|
+
const context = await browser.newContext({
|
|
10
|
+
viewport: { width: 1440, height: 900 },
|
|
11
|
+
recordVideo: { dir: OUT, size: { width: 1440, height: 900 } },
|
|
12
|
+
});
|
|
13
|
+
const page = await context.newPage();
|
|
14
|
+
|
|
15
|
+
// Wait for server
|
|
16
|
+
for (let i = 0; i < 30; i++) {
|
|
17
|
+
try { await page.goto(BASE, { timeout: 3000 }); break; }
|
|
18
|
+
catch { console.log(`Waiting... (${i + 1})`); await setTimeout(2000); }
|
|
19
|
+
}
|
|
20
|
+
await setTimeout(2000);
|
|
21
|
+
|
|
22
|
+
// Click existing session with rich content (Claude Code 4.6)
|
|
23
|
+
const session46 = page.locator('[class*="cursor-pointer"]').filter({ hasText: "Claude Code 4.6" }).first();
|
|
24
|
+
if (await session46.count() > 0) {
|
|
25
|
+
await session46.click();
|
|
26
|
+
await setTimeout(2000);
|
|
27
|
+
await page.screenshot({ path: `${OUT}07-history-rich.png`, fullPage: true });
|
|
28
|
+
console.log("✓ 07-history-rich.png");
|
|
29
|
+
|
|
30
|
+
// Open file explorer
|
|
31
|
+
const filesBtn = page.locator('button').filter({ hasText: /^檔案$|^Files$/ }).first();
|
|
32
|
+
await filesBtn.click();
|
|
33
|
+
await setTimeout(2000);
|
|
34
|
+
await page.screenshot({ path: `${OUT}08-file-explorer.png` });
|
|
35
|
+
console.log("✓ 08-file-explorer.png");
|
|
36
|
+
await filesBtn.click();
|
|
37
|
+
await setTimeout(500);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Switch to EN
|
|
41
|
+
await page.click('button:has-text("EN")');
|
|
42
|
+
await setTimeout(1000);
|
|
43
|
+
|
|
44
|
+
// New session in EN
|
|
45
|
+
await page.click('button:has-text("New Session")');
|
|
46
|
+
await setTimeout(1500);
|
|
47
|
+
await page.screenshot({ path: `${OUT}09-en-cards.png` });
|
|
48
|
+
console.log("✓ 09-en-cards.png");
|
|
49
|
+
|
|
50
|
+
// Switch to JA
|
|
51
|
+
await page.click('button:has-text("JA")');
|
|
52
|
+
await setTimeout(500);
|
|
53
|
+
await page.click('button:has-text("New Session")');
|
|
54
|
+
await setTimeout(1500);
|
|
55
|
+
await page.screenshot({ path: `${OUT}10-ja-cards.png` });
|
|
56
|
+
console.log("✓ 10-ja-cards.png");
|
|
57
|
+
|
|
58
|
+
// Back to TW, show settings
|
|
59
|
+
await page.click('button:has-text("TW")');
|
|
60
|
+
await setTimeout(500);
|
|
61
|
+
const settingsBtn = page.locator('button:has-text("Settings")');
|
|
62
|
+
if (await settingsBtn.count() > 0) {
|
|
63
|
+
await settingsBtn.click();
|
|
64
|
+
await setTimeout(1000);
|
|
65
|
+
await page.screenshot({ path: `${OUT}11-settings.png` });
|
|
66
|
+
console.log("✓ 11-settings.png");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
await page.close();
|
|
70
|
+
await context.close();
|
|
71
|
+
await browser.close();
|
|
72
|
+
console.log("\n✅ Remaining screenshots captured");
|
|
73
|
+
})();
|
package/demo/capture.mjs
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { chromium } from "playwright";
|
|
2
|
+
import { setTimeout } from "timers/promises";
|
|
3
|
+
|
|
4
|
+
const BASE = "http://localhost:5173";
|
|
5
|
+
const OUT = new URL(".", import.meta.url).pathname;
|
|
6
|
+
|
|
7
|
+
(async () => {
|
|
8
|
+
const browser = await chromium.launch({ headless: true });
|
|
9
|
+
const context = await browser.newContext({
|
|
10
|
+
viewport: { width: 1440, height: 900 },
|
|
11
|
+
recordVideo: { dir: OUT, size: { width: 1440, height: 900 } },
|
|
12
|
+
});
|
|
13
|
+
const page = await context.newPage();
|
|
14
|
+
|
|
15
|
+
// Wait for Vite to be ready (retry)
|
|
16
|
+
for (let i = 0; i < 20; i++) {
|
|
17
|
+
try {
|
|
18
|
+
await page.goto(BASE, { timeout: 5000 });
|
|
19
|
+
break;
|
|
20
|
+
} catch {
|
|
21
|
+
console.log(`Waiting for server... (${i + 1})`);
|
|
22
|
+
await setTimeout(2000);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
// 1. Welcome screen
|
|
26
|
+
await setTimeout(2000);
|
|
27
|
+
await page.screenshot({ path: `${OUT}01-welcome.png` });
|
|
28
|
+
console.log("✓ 01-welcome.png");
|
|
29
|
+
|
|
30
|
+
// 2. Create new session → Empty chat with pipeline cards
|
|
31
|
+
await page.click('button:has-text("開始新 Session")');
|
|
32
|
+
await setTimeout(1500);
|
|
33
|
+
await page.screenshot({ path: `${OUT}02-pipeline-cards.png` });
|
|
34
|
+
console.log("✓ 02-pipeline-cards.png");
|
|
35
|
+
|
|
36
|
+
// 3. Click 指定主題 → input fill + hint
|
|
37
|
+
await page.click('button:has-text("指定主題發文")');
|
|
38
|
+
await setTimeout(1000);
|
|
39
|
+
await page.screenshot({ path: `${OUT}03-custom-topic-fill.png` });
|
|
40
|
+
console.log("✓ 03-custom-topic-fill.png");
|
|
41
|
+
|
|
42
|
+
// 4. Type topic and send
|
|
43
|
+
const textarea = page.locator("textarea");
|
|
44
|
+
await textarea.fill("AI Agent 自動化工作流 2026 最新趨勢");
|
|
45
|
+
await setTimeout(500);
|
|
46
|
+
await page.screenshot({ path: `${OUT}04-topic-typed.png` });
|
|
47
|
+
console.log("✓ 04-topic-typed.png");
|
|
48
|
+
|
|
49
|
+
await textarea.press("Enter");
|
|
50
|
+
await setTimeout(3000);
|
|
51
|
+
await page.screenshot({ path: `${OUT}05-loading-state.png` });
|
|
52
|
+
console.log("✓ 05-loading-state.png");
|
|
53
|
+
|
|
54
|
+
// 5. Wait for response with tool calls
|
|
55
|
+
await setTimeout(15000);
|
|
56
|
+
await page.screenshot({ path: `${OUT}06-tool-calls.png` });
|
|
57
|
+
console.log("✓ 06-tool-calls.png");
|
|
58
|
+
|
|
59
|
+
// 6. Wait more for full response
|
|
60
|
+
await setTimeout(30000);
|
|
61
|
+
await page.screenshot({ path: `${OUT}07-response.png`, fullPage: true });
|
|
62
|
+
console.log("✓ 07-response.png");
|
|
63
|
+
|
|
64
|
+
// 7. Open file explorer
|
|
65
|
+
const filesBtn = page.locator('button:has-text("Files"), button:has-text("檔案")').first();
|
|
66
|
+
await filesBtn.click();
|
|
67
|
+
await setTimeout(2000);
|
|
68
|
+
await page.screenshot({ path: `${OUT}08-file-explorer.png` });
|
|
69
|
+
console.log("✓ 08-file-explorer.png");
|
|
70
|
+
await filesBtn.click(); // close
|
|
71
|
+
|
|
72
|
+
// 8. Switch language to EN
|
|
73
|
+
await page.click('button:has-text("EN")');
|
|
74
|
+
await setTimeout(1500);
|
|
75
|
+
await page.screenshot({ path: `${OUT}09-english-mode.png` });
|
|
76
|
+
console.log("✓ 09-english-mode.png");
|
|
77
|
+
|
|
78
|
+
// 9. Create new session in EN to show EN cards
|
|
79
|
+
await page.click('button:has-text("New Session")');
|
|
80
|
+
await setTimeout(1500);
|
|
81
|
+
await page.screenshot({ path: `${OUT}10-en-pipeline-cards.png` });
|
|
82
|
+
console.log("✓ 10-en-pipeline-cards.png");
|
|
83
|
+
|
|
84
|
+
// 10. Switch to JA
|
|
85
|
+
await page.click('button:has-text("JA")');
|
|
86
|
+
await setTimeout(500);
|
|
87
|
+
await page.click('button:has-text("New Session")');
|
|
88
|
+
await setTimeout(1500);
|
|
89
|
+
await page.screenshot({ path: `${OUT}11-ja-pipeline-cards.png` });
|
|
90
|
+
console.log("✓ 11-ja-pipeline-cards.png");
|
|
91
|
+
|
|
92
|
+
// Switch back to TW
|
|
93
|
+
await page.click('button:has-text("TW")');
|
|
94
|
+
await setTimeout(500);
|
|
95
|
+
|
|
96
|
+
// 12. Click existing session with history to show rich content
|
|
97
|
+
const sessionItem = page.locator('[class*="cursor-pointer"]').filter({ hasText: "Claude Code 4.6" }).first();
|
|
98
|
+
if (await sessionItem.count() > 0) {
|
|
99
|
+
await sessionItem.click();
|
|
100
|
+
await setTimeout(2000);
|
|
101
|
+
await page.screenshot({ path: `${OUT}12-history-rich-content.png`, fullPage: true });
|
|
102
|
+
console.log("✓ 12-history-rich-content.png");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Done
|
|
106
|
+
await page.close();
|
|
107
|
+
await context.close();
|
|
108
|
+
await browser.close();
|
|
109
|
+
console.log("\n✅ All screenshots captured in demo/");
|
|
110
|
+
})();
|
|
Binary file
|
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-world-studio",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Claude World Studio - Trend Discovery to Content Publishing Pipeline",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "concurrently \"npm run dev:server\" \"npm run dev:client\"",
|
|
8
|
+
"dev:server": "tsx watch server/server.ts",
|
|
9
|
+
"dev:client": "vite --port 5173",
|
|
10
|
+
"start": "tsx server/server.ts",
|
|
11
|
+
"build": "vite build"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@anthropic-ai/claude-agent-sdk": "^0.1.28",
|
|
15
|
+
"better-sqlite3": "^11.7.0",
|
|
16
|
+
"cors": "^2.8.5",
|
|
17
|
+
"dotenv": "^16.4.5",
|
|
18
|
+
"express": "^4.21.0",
|
|
19
|
+
"playwright": "^1.58.2",
|
|
20
|
+
"react": "^18.3.1",
|
|
21
|
+
"react-dom": "^18.3.1",
|
|
22
|
+
"react-markdown": "^10.1.0",
|
|
23
|
+
"react-use-websocket": "^4.13.0",
|
|
24
|
+
"rehype-sanitize": "^6.0.0",
|
|
25
|
+
"uuid": "^10.0.0",
|
|
26
|
+
"ws": "^8.18.0",
|
|
27
|
+
"zod": "^4.3.6"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@tailwindcss/typography": "^0.5.19",
|
|
31
|
+
"@types/better-sqlite3": "^7.6.12",
|
|
32
|
+
"@types/cors": "^2.8.17",
|
|
33
|
+
"@types/express": "^4.17.21",
|
|
34
|
+
"@types/node": "^22.0.0",
|
|
35
|
+
"@types/react": "^18.3.0",
|
|
36
|
+
"@types/react-dom": "^18.3.0",
|
|
37
|
+
"@types/uuid": "^10.0.0",
|
|
38
|
+
"@types/ws": "^8.5.12",
|
|
39
|
+
"@vitejs/plugin-react": "^4.3.0",
|
|
40
|
+
"autoprefixer": "^10.4.20",
|
|
41
|
+
"concurrently": "^9.0.0",
|
|
42
|
+
"postcss": "^8.4.47",
|
|
43
|
+
"tailwindcss": "^3.4.14",
|
|
44
|
+
"tsx": "^4.19.0",
|
|
45
|
+
"typescript": "^5.5.0",
|
|
46
|
+
"vite": "^5.4.0"
|
|
47
|
+
}
|
|
48
|
+
}
|