@seastudio/sdk 3.0.5 → 3.2.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/README.md +8 -0
- package/dist/{chunk-E33WZ4UI.cjs → chunk-B4SSXXGO.cjs} +4 -4
- package/dist/{chunk-IBRD7E5X.js → chunk-JECBSAOI.js} +1 -1
- package/dist/{chunk-7I3A4U6V.cjs → chunk-UISZZGNC.cjs} +15 -1
- package/dist/{chunk-4QK6VMIM.js → chunk-ZP2QGNBN.js} +15 -1
- package/dist/index.cjs +24 -24
- package/dist/index.js +2 -2
- package/dist/mcp/index.cjs +24 -24
- package/dist/mcp/index.js +2 -2
- package/dist/mcp/seastudio/index.cjs +24 -24
- package/dist/mcp/seastudio/index.d.cts +49 -1
- package/dist/mcp/seastudio/index.d.ts +49 -1
- package/dist/mcp/seastudio/index.js +1 -1
- package/package.json +5 -20
- package/bin/seastudio.js +0 -3
- package/dist/develop-tool/cli/index.cjs +0 -1210
- package/dist/develop-tool/cli/index.d.cts +0 -1
- package/dist/develop-tool/cli/index.d.ts +0 -1
- package/dist/develop-tool/cli/index.js +0 -1199
- package/src/develop-tool/templates/plugin/README.md.tmpl +0 -36
- package/src/develop-tool/templates/plugin/frontend/index.html.tmpl +0 -12
- package/src/develop-tool/templates/plugin/frontend/package.json.tmpl +0 -30
- package/src/develop-tool/templates/plugin/frontend/postcss.config.js +0 -6
- package/src/develop-tool/templates/plugin/frontend/src/App.css +0 -43
- package/src/develop-tool/templates/plugin/frontend/src/App.tsx.tmpl +0 -43
- package/src/develop-tool/templates/plugin/frontend/src/main.tsx +0 -13
- package/src/develop-tool/templates/plugin/frontend/src/mcp-entry.ts.tmpl +0 -12
- package/src/develop-tool/templates/plugin/frontend/src/vite-env.d.ts +0 -1
- package/src/develop-tool/templates/plugin/frontend/tsconfig.json +0 -20
- package/src/develop-tool/templates/plugin/frontend/vite.config.ts +0 -12
- package/src/develop-tool/templates/plugin/seastudio.config.json.tmpl +0 -14
|
@@ -1,1199 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { Command } from 'commander';
|
|
3
|
-
import inquirer from 'inquirer';
|
|
4
|
-
import { writeFile, mkdir, rm, readFile, readdir, mkdtemp, stat, lstat, readlink, unlink, copyFile, chmod } from 'fs/promises';
|
|
5
|
-
import { dirname, resolve, join, basename } from 'path';
|
|
6
|
-
import { existsSync, createWriteStream, mkdirSync, createReadStream } from 'fs';
|
|
7
|
-
import { fileURLToPath } from 'url';
|
|
8
|
-
import chalk from 'chalk';
|
|
9
|
-
import ora2 from 'ora';
|
|
10
|
-
import { spawnSync, spawn } from 'child_process';
|
|
11
|
-
import { tmpdir } from 'os';
|
|
12
|
-
import https from 'https';
|
|
13
|
-
import { pipeline } from 'stream/promises';
|
|
14
|
-
import archiver from 'archiver';
|
|
15
|
-
import { x } from 'tar';
|
|
16
|
-
import { Extract } from 'unzipper';
|
|
17
|
-
|
|
18
|
-
var __filename$1 = fileURLToPath(import.meta.url);
|
|
19
|
-
var __dirname$1 = dirname(__filename$1);
|
|
20
|
-
function validatePluginId(input) {
|
|
21
|
-
if (!input.trim()) {
|
|
22
|
-
return "\u63D2\u4EF6 ID \u4E0D\u80FD\u4E3A\u7A7A";
|
|
23
|
-
}
|
|
24
|
-
if (input.startsWith("seastudio.")) {
|
|
25
|
-
return '\u63D2\u4EF6 ID \u4E0D\u80FD\u4EE5 "seastudio." \u5F00\u5934\uFF08\u4FDD\u7559\u524D\u7F00\uFF09';
|
|
26
|
-
}
|
|
27
|
-
if (!/^[a-z0-9-]+\.[a-z0-9-]+$/.test(input)) {
|
|
28
|
-
return '\u63D2\u4EF6 ID \u683C\u5F0F\u5E94\u4E3A "namespace.name"\uFF0C\u4F8B\u5982 "mycompany.my-plugin"';
|
|
29
|
-
}
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
32
|
-
function validateProtocolHost(input) {
|
|
33
|
-
if (!input.trim()) {
|
|
34
|
-
return "Protocol Host \u4E0D\u80FD\u4E3A\u7A7A";
|
|
35
|
-
}
|
|
36
|
-
if (!/^[a-z0-9-]+$/.test(input)) {
|
|
37
|
-
return "Protocol Host \u53EA\u80FD\u5305\u542B\u5C0F\u5199\u5B57\u6BCD\u3001\u6570\u5B57\u548C\u8FDE\u5B57\u7B26";
|
|
38
|
-
}
|
|
39
|
-
return true;
|
|
40
|
-
}
|
|
41
|
-
async function getTemplatesDir() {
|
|
42
|
-
const possiblePaths = [
|
|
43
|
-
// 开发时:相对于 src 目录
|
|
44
|
-
resolve(__dirname$1, "../../templates"),
|
|
45
|
-
// 打包后:相对于 dist 目录
|
|
46
|
-
resolve(__dirname$1, "../../../src/develop-tool/templates"),
|
|
47
|
-
// npm 安装后:相对于 node_modules
|
|
48
|
-
resolve(__dirname$1, "../../../../src/develop-tool/templates")
|
|
49
|
-
];
|
|
50
|
-
for (const p of possiblePaths) {
|
|
51
|
-
if (existsSync(p)) {
|
|
52
|
-
return p;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
throw new Error("\u65E0\u6CD5\u627E\u5230\u6A21\u677F\u76EE\u5F55");
|
|
56
|
-
}
|
|
57
|
-
async function copyDirectory(src, dest, replacements) {
|
|
58
|
-
await mkdir(dest, { recursive: true });
|
|
59
|
-
const entries = await readdir(src, { withFileTypes: true });
|
|
60
|
-
for (const entry of entries) {
|
|
61
|
-
const srcPath = join(src, entry.name);
|
|
62
|
-
let destName = entry.name;
|
|
63
|
-
if (destName.endsWith(".tmpl")) {
|
|
64
|
-
destName = destName.slice(0, -5);
|
|
65
|
-
}
|
|
66
|
-
const destPath = join(dest, destName);
|
|
67
|
-
if (entry.isDirectory()) {
|
|
68
|
-
await copyDirectory(srcPath, destPath, replacements);
|
|
69
|
-
} else {
|
|
70
|
-
let content = await readFile(srcPath, "utf-8");
|
|
71
|
-
for (const [key, value] of Object.entries(replacements)) {
|
|
72
|
-
content = content.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value);
|
|
73
|
-
}
|
|
74
|
-
await writeFile(destPath, content, "utf-8");
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
var createCommand = new Command("create").description("\u521B\u5EFA\u65B0\u7684 SeaStudio \u63D2\u4EF6\u6216 Agent \u9879\u76EE").argument("[directory]", "\u9879\u76EE\u76EE\u5F55\u540D\u79F0").option("--type <type>", "\u9879\u76EE\u7C7B\u578B (plugin | agent)").action(async (directory, options) => {
|
|
79
|
-
console.log(chalk.cyan("\n\u{1F680} SeaStudio \u521B\u5EFA\u5411\u5BFC\n"));
|
|
80
|
-
const answers = await inquirer.prompt([
|
|
81
|
-
{
|
|
82
|
-
type: "list",
|
|
83
|
-
name: "projectType",
|
|
84
|
-
message: "\u9879\u76EE\u7C7B\u578B:",
|
|
85
|
-
choices: [
|
|
86
|
-
{ name: "Plugin", value: "plugin" },
|
|
87
|
-
{ name: "Agent", value: "agent" }
|
|
88
|
-
],
|
|
89
|
-
default: options?.type === "agent" ? "agent" : "plugin",
|
|
90
|
-
when: () => options?.type !== "plugin" && options?.type !== "agent"
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
type: "input",
|
|
94
|
-
name: "pluginId",
|
|
95
|
-
message: (current) => current.projectType === "agent" ? "Agent ID (\u4F8B\u5982 mycompany.my-agent):" : "\u63D2\u4EF6 ID (\u4F8B\u5982 mycompany.awesome-tool):",
|
|
96
|
-
validate: validatePluginId
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
type: "input",
|
|
100
|
-
name: "pluginName",
|
|
101
|
-
message: (current) => current.projectType === "agent" ? "Agent \u540D\u79F0:" : "\u63D2\u4EF6\u540D\u79F0:",
|
|
102
|
-
validate: (input) => input.trim() ? true : "\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A"
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
type: "input",
|
|
106
|
-
name: "protocolHost",
|
|
107
|
-
message: "Protocol Host (\u7528\u4E8E URL\uFF0C\u4F8B\u5982 awesome-tool):",
|
|
108
|
-
default: (answers2) => {
|
|
109
|
-
const parts = answers2.pluginId?.split(".");
|
|
110
|
-
return parts?.[parts.length - 1] || "";
|
|
111
|
-
},
|
|
112
|
-
validate: validateProtocolHost
|
|
113
|
-
},
|
|
114
|
-
{
|
|
115
|
-
type: "list",
|
|
116
|
-
name: "category",
|
|
117
|
-
message: "\u63D2\u4EF6\u5206\u7C7B:",
|
|
118
|
-
choices: [
|
|
119
|
-
{ name: "B - \u6807\u7B7E\u9875\u533A\u57DF\uFF08\u63A8\u8350\uFF09", value: "B" },
|
|
120
|
-
{ name: "A - \u56FA\u5B9A\u533A\u57DF", value: "A" }
|
|
121
|
-
],
|
|
122
|
-
default: "B",
|
|
123
|
-
when: (current) => (options?.type || current.projectType) !== "agent"
|
|
124
|
-
},
|
|
125
|
-
{
|
|
126
|
-
type: "list",
|
|
127
|
-
name: "backendMode",
|
|
128
|
-
message: "Agent \u540E\u7AEF\u6A21\u5F0F:",
|
|
129
|
-
choices: [
|
|
130
|
-
{ name: "external - \u8FDE\u63A5\u5916\u90E8\u540E\u7AEF", value: "external" },
|
|
131
|
-
{ name: "hosted - Host \u6258\u7BA1\u672C\u5730\u540E\u7AEF", value: "hosted" }
|
|
132
|
-
],
|
|
133
|
-
default: "external",
|
|
134
|
-
when: (current) => (options?.type || current.projectType) === "agent"
|
|
135
|
-
},
|
|
136
|
-
{
|
|
137
|
-
type: "input",
|
|
138
|
-
name: "backendUrl",
|
|
139
|
-
message: "\u9ED8\u8BA4\u540E\u7AEF\u5730\u5740:",
|
|
140
|
-
default: "http://localhost:3000",
|
|
141
|
-
when: (current) => current.backendMode === "external"
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
type: "list",
|
|
145
|
-
name: "backendRuntime",
|
|
146
|
-
message: "Host \u6258\u7BA1\u540E\u7AEF\u8FD0\u884C\u65F6:",
|
|
147
|
-
choices: [
|
|
148
|
-
{ name: "Python", value: "python" },
|
|
149
|
-
{ name: "Node.js", value: "node" }
|
|
150
|
-
],
|
|
151
|
-
default: "python",
|
|
152
|
-
when: (current) => current.backendMode === "hosted"
|
|
153
|
-
},
|
|
154
|
-
{
|
|
155
|
-
type: "input",
|
|
156
|
-
name: "backendEntry",
|
|
157
|
-
message: "Host \u6258\u7BA1\u540E\u7AEF\u5165\u53E3\u6587\u4EF6:",
|
|
158
|
-
default: (current) => current.backendRuntime === "node" ? "server.js" : "run.py",
|
|
159
|
-
when: (current) => current.backendMode === "hosted"
|
|
160
|
-
},
|
|
161
|
-
{
|
|
162
|
-
type: "number",
|
|
163
|
-
name: "backendPort",
|
|
164
|
-
message: "Host \u6258\u7BA1\u540E\u7AEF\u9ED8\u8BA4\u7AEF\u53E3:",
|
|
165
|
-
default: 9201,
|
|
166
|
-
when: (current) => current.backendMode === "hosted"
|
|
167
|
-
}
|
|
168
|
-
]);
|
|
169
|
-
const projectType = options?.type === "plugin" || options?.type === "agent" ? options.type : answers.projectType;
|
|
170
|
-
const projectDir = directory || answers.protocolHost;
|
|
171
|
-
const fullPath = resolve(process.cwd(), projectDir);
|
|
172
|
-
if (existsSync(fullPath)) {
|
|
173
|
-
console.log(chalk.red(`
|
|
174
|
-
\u274C \u76EE\u5F55 "${projectDir}" \u5DF2\u5B58\u5728
|
|
175
|
-
`));
|
|
176
|
-
process.exit(1);
|
|
177
|
-
}
|
|
178
|
-
const spinner = ora2(`\u521B\u5EFA${projectType === "agent" ? " Agent" : "\u63D2\u4EF6"}\u9879\u76EE...`).start();
|
|
179
|
-
try {
|
|
180
|
-
const templatesDir = await getTemplatesDir();
|
|
181
|
-
const pluginTemplateDir = join(templatesDir, "plugin");
|
|
182
|
-
if (!existsSync(pluginTemplateDir)) {
|
|
183
|
-
throw new Error(`\u6A21\u677F\u76EE\u5F55\u4E0D\u5B58\u5728: ${pluginTemplateDir}`);
|
|
184
|
-
}
|
|
185
|
-
const replacements = {
|
|
186
|
-
pluginId: answers.pluginId,
|
|
187
|
-
pluginName: answers.pluginName,
|
|
188
|
-
protocolHost: answers.protocolHost,
|
|
189
|
-
category: answers.category || "B"
|
|
190
|
-
};
|
|
191
|
-
await copyDirectory(pluginTemplateDir, fullPath, replacements);
|
|
192
|
-
if (projectType === "agent") {
|
|
193
|
-
const backendMode = answers.backendMode || "external";
|
|
194
|
-
const agentConfig = backendMode === "external" ? {
|
|
195
|
-
type: "agent",
|
|
196
|
-
id: answers.pluginId,
|
|
197
|
-
name: answers.pluginName,
|
|
198
|
-
version: "1.0.0",
|
|
199
|
-
protocolHost: answers.protocolHost,
|
|
200
|
-
frontendDir: "frontend",
|
|
201
|
-
backend: {
|
|
202
|
-
mode: "external",
|
|
203
|
-
url: answers.backendUrl || "http://localhost:3000",
|
|
204
|
-
healthEndpoint: "/agent/info"
|
|
205
|
-
}
|
|
206
|
-
} : {
|
|
207
|
-
type: "agent",
|
|
208
|
-
id: answers.pluginId,
|
|
209
|
-
name: answers.pluginName,
|
|
210
|
-
version: "1.0.0",
|
|
211
|
-
protocolHost: answers.protocolHost,
|
|
212
|
-
frontendDir: "frontend",
|
|
213
|
-
backendDir: "backend",
|
|
214
|
-
backend: {
|
|
215
|
-
mode: "hosted",
|
|
216
|
-
runtime: answers.backendRuntime || "python",
|
|
217
|
-
entry: answers.backendEntry || "run.py",
|
|
218
|
-
port: answers.backendPort || 9201,
|
|
219
|
-
healthEndpoint: "/agent/info",
|
|
220
|
-
autoStart: true
|
|
221
|
-
}
|
|
222
|
-
};
|
|
223
|
-
const agentReadme = `# ${answers.pluginName}
|
|
224
|
-
|
|
225
|
-
SeaStudio \u7B2C\u4E09\u65B9 Agent
|
|
226
|
-
|
|
227
|
-
## \u5F00\u53D1
|
|
228
|
-
|
|
229
|
-
\`\`\`bash
|
|
230
|
-
cd frontend
|
|
231
|
-
npm install
|
|
232
|
-
npm run dev
|
|
233
|
-
\`\`\`
|
|
234
|
-
|
|
235
|
-
## \u6784\u5EFA
|
|
236
|
-
|
|
237
|
-
\`\`\`bash
|
|
238
|
-
cd frontend
|
|
239
|
-
npm run build
|
|
240
|
-
\`\`\`
|
|
241
|
-
|
|
242
|
-
## \u6253\u5305
|
|
243
|
-
|
|
244
|
-
\`\`\`bash
|
|
245
|
-
seastudio pack
|
|
246
|
-
\`\`\`
|
|
247
|
-
|
|
248
|
-
\u6253\u5305\u540E\u4F1A\u751F\u6210 \`${answers.protocolHost}.seaagent\` \u6587\u4EF6\u3002
|
|
249
|
-
|
|
250
|
-
## \u8FD0\u884C\u6A21\u5F0F
|
|
251
|
-
|
|
252
|
-
- backend.mode: \`${backendMode}\`
|
|
253
|
-
- protocolHost: \`${answers.protocolHost}\`
|
|
254
|
-
|
|
255
|
-
## \u5B89\u88C5
|
|
256
|
-
|
|
257
|
-
1. \u6253\u5F00 SeaStudio
|
|
258
|
-
2. \u8BBE\u7F6E \u2192 Agent \u7BA1\u7406 \u2192 \u5BFC\u5165 Agent
|
|
259
|
-
3. \u9009\u62E9 \`.seaagent\` \u6587\u4EF6
|
|
260
|
-
|
|
261
|
-
## \u8BB8\u53EF\u8BC1
|
|
262
|
-
|
|
263
|
-
MIT
|
|
264
|
-
`;
|
|
265
|
-
const agentApp = `import { useEffect, useMemo, useState } from 'react';
|
|
266
|
-
|
|
267
|
-
interface InitState {
|
|
268
|
-
agentId: string;
|
|
269
|
-
backendUrl: string;
|
|
270
|
-
backendMode: 'external' | 'hosted';
|
|
271
|
-
healthEndpoint: string;
|
|
272
|
-
theme: 'light' | 'dark';
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
function App() {
|
|
276
|
-
const [initState, setInitState] = useState<InitState | null>(null);
|
|
277
|
-
|
|
278
|
-
useEffect(() => {
|
|
279
|
-
const readyPayload = {
|
|
280
|
-
type: 'mcp',
|
|
281
|
-
payload: {
|
|
282
|
-
jsonrpc: '2.0',
|
|
283
|
-
method: 'agent:ready',
|
|
284
|
-
params: {},
|
|
285
|
-
},
|
|
286
|
-
};
|
|
287
|
-
window.parent.postMessage(readyPayload, '*');
|
|
288
|
-
|
|
289
|
-
const handleMessage = (event: MessageEvent) => {
|
|
290
|
-
const data = event.data as {
|
|
291
|
-
type?: string;
|
|
292
|
-
payload?: { method?: string; params?: Record<string, unknown> };
|
|
293
|
-
};
|
|
294
|
-
if (data?.type !== 'mcp' || data?.payload?.method !== 'seastudio:init') {
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
const params = data.payload.params || {};
|
|
298
|
-
setInitState({
|
|
299
|
-
agentId: String(params.agentId || ''),
|
|
300
|
-
backendUrl: String(params.backendUrl || ''),
|
|
301
|
-
backendMode: (params.backendMode === 'hosted' ? 'hosted' : 'external'),
|
|
302
|
-
healthEndpoint: String(params.healthEndpoint || '/agent/info'),
|
|
303
|
-
theme: params.theme === 'light' ? 'light' : 'dark',
|
|
304
|
-
});
|
|
305
|
-
};
|
|
306
|
-
|
|
307
|
-
window.addEventListener('message', handleMessage);
|
|
308
|
-
return () => window.removeEventListener('message', handleMessage);
|
|
309
|
-
}, []);
|
|
310
|
-
|
|
311
|
-
const statusText = useMemo(() => {
|
|
312
|
-
if (!initState?.backendUrl) {
|
|
313
|
-
return '\u7B49\u5F85 Host \u6CE8\u5165\u540E\u7AEF\u914D\u7F6E';
|
|
314
|
-
}
|
|
315
|
-
return initState.backendUrl;
|
|
316
|
-
}, [initState]);
|
|
317
|
-
|
|
318
|
-
return (
|
|
319
|
-
<div className="min-h-screen bg-background text-foreground p-6">
|
|
320
|
-
<h1 className="text-2xl font-bold mb-4">${answers.pluginName}</h1>
|
|
321
|
-
<div className="space-y-4">
|
|
322
|
-
<div className="p-4 rounded-lg bg-muted/50 border border-border">
|
|
323
|
-
<h2 className="text-sm font-medium text-muted-foreground mb-2">\u8FD0\u884C\u4FE1\u606F</h2>
|
|
324
|
-
<p className="text-sm">Agent ID: {initState?.agentId || '${answers.pluginId}'}</p>
|
|
325
|
-
<p className="text-sm">\u540E\u7AEF\u6A21\u5F0F: {initState?.backendMode || '${backendMode}'}</p>
|
|
326
|
-
<p className="text-sm break-all">\u540E\u7AEF\u5730\u5740: {statusText}</p>
|
|
327
|
-
<p className="text-sm">\u5065\u5EB7\u68C0\u67E5: {initState?.healthEndpoint || '/agent/info'}</p>
|
|
328
|
-
</div>
|
|
329
|
-
<div className="p-4 rounded-lg bg-muted/50 border border-border">
|
|
330
|
-
<h2 className="text-sm font-medium text-muted-foreground mb-2">\u5F00\u59CB\u5F00\u53D1</h2>
|
|
331
|
-
<p className="text-sm text-muted-foreground">
|
|
332
|
-
\u7F16\u8F91\u6B64\u6587\u4EF6 (frontend/src/App.tsx) \u6765\u6784\u5EFA\u4F60\u7684 Agent \u524D\u7AEF\u3002
|
|
333
|
-
</p>
|
|
334
|
-
</div>
|
|
335
|
-
</div>
|
|
336
|
-
</div>
|
|
337
|
-
);
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
export default App;
|
|
341
|
-
`;
|
|
342
|
-
await writeFile(join(fullPath, "seastudio.config.json"), `${JSON.stringify(agentConfig, null, 2)}
|
|
343
|
-
`, "utf-8");
|
|
344
|
-
await writeFile(join(fullPath, "README.md"), agentReadme, "utf-8");
|
|
345
|
-
await writeFile(join(fullPath, "frontend", "src", "App.tsx"), agentApp, "utf-8");
|
|
346
|
-
if (backendMode === "hosted") {
|
|
347
|
-
await mkdir(join(fullPath, "backend"), { recursive: true });
|
|
348
|
-
if ((answers.backendRuntime || "python") === "python") {
|
|
349
|
-
const pythonEntry = `from fastapi import FastAPI
|
|
350
|
-
import uvicorn
|
|
351
|
-
import os
|
|
352
|
-
|
|
353
|
-
app = FastAPI()
|
|
354
|
-
|
|
355
|
-
@app.get("/health")
|
|
356
|
-
def health():
|
|
357
|
-
return {"success": True}
|
|
358
|
-
|
|
359
|
-
@app.get("/agent/info")
|
|
360
|
-
def info():
|
|
361
|
-
return {
|
|
362
|
-
"id": "${answers.pluginId}",
|
|
363
|
-
"name": "${answers.pluginName}",
|
|
364
|
-
"description": "Hosted Agent backend"
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
if __name__ == "__main__":
|
|
368
|
-
port = int(os.getenv("PORT", "${answers.backendPort || 9201}"))
|
|
369
|
-
uvicorn.run(app, host="127.0.0.1", port=port)
|
|
370
|
-
`;
|
|
371
|
-
await writeFile(join(fullPath, "backend", answers.backendEntry || "run.py"), pythonEntry, "utf-8");
|
|
372
|
-
} else {
|
|
373
|
-
const nodeEntry = `import express from 'express';
|
|
374
|
-
|
|
375
|
-
const app = express();
|
|
376
|
-
const port = Number(process.env.PORT || ${answers.backendPort || 9201});
|
|
377
|
-
|
|
378
|
-
app.get('/health', (_req, res) => {
|
|
379
|
-
res.json({ success: true });
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
app.get('/agent/info', (_req, res) => {
|
|
383
|
-
res.json({
|
|
384
|
-
id: '${answers.pluginId}',
|
|
385
|
-
name: '${answers.pluginName}',
|
|
386
|
-
description: 'Hosted Agent backend',
|
|
387
|
-
});
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
app.listen(port, '127.0.0.1', () => {
|
|
391
|
-
console.log(\`Agent backend listening on \${port}\`);
|
|
392
|
-
});
|
|
393
|
-
`;
|
|
394
|
-
const backendPackage = {
|
|
395
|
-
name: `${answers.pluginId}-backend`,
|
|
396
|
-
private: true,
|
|
397
|
-
type: "module",
|
|
398
|
-
dependencies: {
|
|
399
|
-
express: "^4.21.2"
|
|
400
|
-
}
|
|
401
|
-
};
|
|
402
|
-
await writeFile(join(fullPath, "backend", answers.backendEntry || "server.js"), nodeEntry, "utf-8");
|
|
403
|
-
await writeFile(join(fullPath, "backend", "package.json"), `${JSON.stringify(backendPackage, null, 2)}
|
|
404
|
-
`, "utf-8");
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
spinner.succeed(`${projectType === "agent" ? "Agent" : "\u63D2\u4EF6"}\u9879\u76EE\u521B\u5EFA\u6210\u529F`);
|
|
409
|
-
console.log(chalk.green(`
|
|
410
|
-
\u2705 ${projectType === "agent" ? "Agent" : "\u63D2\u4EF6"}\u9879\u76EE\u5DF2\u521B\u5EFA\u5728 ${chalk.bold(fullPath)}
|
|
411
|
-
`));
|
|
412
|
-
console.log(chalk.cyan("\u4E0B\u4E00\u6B65\u64CD\u4F5C:\n"));
|
|
413
|
-
console.log(` ${chalk.yellow("cd")} ${projectDir}/frontend`);
|
|
414
|
-
console.log(` ${chalk.yellow("npm install")}`);
|
|
415
|
-
console.log(` ${chalk.yellow("npm run dev")}
|
|
416
|
-
`);
|
|
417
|
-
console.log(chalk.cyan("\u6253\u5305\u53D1\u5E03:\n"));
|
|
418
|
-
console.log(` ${chalk.yellow("npm run build")}`);
|
|
419
|
-
console.log(` ${chalk.yellow("cd ..")}`);
|
|
420
|
-
console.log(` ${chalk.yellow("seastudio pack")}
|
|
421
|
-
`);
|
|
422
|
-
} catch (error) {
|
|
423
|
-
spinner.fail("\u521B\u5EFA\u5931\u8D25");
|
|
424
|
-
console.error(chalk.red(`
|
|
425
|
-
\u274C ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}
|
|
426
|
-
`));
|
|
427
|
-
process.exit(1);
|
|
428
|
-
}
|
|
429
|
-
});
|
|
430
|
-
function normalizeTools(tools) {
|
|
431
|
-
return tools.map((tool) => JSON.parse(JSON.stringify({
|
|
432
|
-
name: tool.name,
|
|
433
|
-
description: tool.description,
|
|
434
|
-
inputSchema: tool.inputSchema,
|
|
435
|
-
...tool.outputSchema ? { outputSchema: tool.outputSchema } : {},
|
|
436
|
-
...tool.annotations ? { annotations: tool.annotations } : {}
|
|
437
|
-
})));
|
|
438
|
-
}
|
|
439
|
-
function loadManifestFromEntry(entryPath) {
|
|
440
|
-
const readerScript = `
|
|
441
|
-
import { pathToFileURL } from 'node:url';
|
|
442
|
-
const entryPath = process.argv[1];
|
|
443
|
-
const mod = await import(pathToFileURL(entryPath).href);
|
|
444
|
-
const manifest = mod.pluginMcpManifest ?? (Array.isArray(mod.mcpTools)
|
|
445
|
-
? { schemaVersion: 1, tools: mod.mcpTools }
|
|
446
|
-
: null);
|
|
447
|
-
if (!manifest) {
|
|
448
|
-
throw new Error('frontend/src/mcp-entry.ts \u5FC5\u987B\u5BFC\u51FA pluginMcpManifest \u6216 mcpTools');
|
|
449
|
-
}
|
|
450
|
-
process.stdout.write(JSON.stringify(manifest));
|
|
451
|
-
`;
|
|
452
|
-
const result = spawnSync(process.execPath, [
|
|
453
|
-
"--experimental-strip-types",
|
|
454
|
-
"--experimental-specifier-resolution=node",
|
|
455
|
-
"--input-type=module",
|
|
456
|
-
"-e",
|
|
457
|
-
readerScript,
|
|
458
|
-
entryPath
|
|
459
|
-
], {
|
|
460
|
-
encoding: "utf-8"
|
|
461
|
-
});
|
|
462
|
-
if (result.status !== 0) {
|
|
463
|
-
throw new Error((result.stderr || result.stdout || "\u8BFB\u53D6 frontend/src/mcp-entry.ts \u5931\u8D25").trim());
|
|
464
|
-
}
|
|
465
|
-
const parsed = JSON.parse(result.stdout || "{}");
|
|
466
|
-
return {
|
|
467
|
-
schemaVersion: 1,
|
|
468
|
-
tools: normalizeTools(parsed.tools ?? [])
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
async function generatePluginMcpManifest(projectDir) {
|
|
472
|
-
const frontendDir = existsSync(join(projectDir, "src")) ? projectDir : join(projectDir, "frontend");
|
|
473
|
-
const entryPath = join(frontendDir, "src", "mcp-entry.ts");
|
|
474
|
-
const outputPath = join(frontendDir, "dist", "mcp-manifest.json");
|
|
475
|
-
if (!existsSync(entryPath)) {
|
|
476
|
-
throw new Error(`\u672A\u627E\u5230\u6807\u51C6 MCP \u5165\u53E3\u6587\u4EF6: ${entryPath}`);
|
|
477
|
-
}
|
|
478
|
-
const manifest = loadManifestFromEntry(entryPath);
|
|
479
|
-
await mkdir(dirname(outputPath), { recursive: true });
|
|
480
|
-
await writeFile(outputPath, `${JSON.stringify(manifest, null, 2)}
|
|
481
|
-
`, "utf-8");
|
|
482
|
-
return {
|
|
483
|
-
outputPath,
|
|
484
|
-
toolCount: manifest.tools.length
|
|
485
|
-
};
|
|
486
|
-
}
|
|
487
|
-
var generateMcpManifestCommand = new Command("generate-mcp-manifest").description("\u4ECE frontend/src/mcp-entry.ts \u751F\u6210 dist/mcp-manifest.json").option("-d, --dir <directory>", "\u63D2\u4EF6\u9879\u76EE\u76EE\u5F55", ".").action(async (options) => {
|
|
488
|
-
try {
|
|
489
|
-
const projectDir = resolve(process.cwd(), options.dir || ".");
|
|
490
|
-
const result = await generatePluginMcpManifest(projectDir);
|
|
491
|
-
console.log(`Generated ${result.outputPath} (${result.toolCount} tools)`);
|
|
492
|
-
} catch (error) {
|
|
493
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
494
|
-
process.exit(1);
|
|
495
|
-
}
|
|
496
|
-
});
|
|
497
|
-
var PYTHON_STANDALONE_RELEASE_API = "https://api.github.com/repos/astral-sh/python-build-standalone/releases/latest";
|
|
498
|
-
var TARGET_PYTHON_MINOR = Number(process.env.SEASTUDIO_AGENT_PYTHON_MINOR) || 12;
|
|
499
|
-
var MAX_PYTHON_MINOR = 13;
|
|
500
|
-
var PYTHON_RUNTIME_DIRNAME = ".python-runtime";
|
|
501
|
-
var PYTHON_VENDOR_DIRNAME = ".seastudio-python-vendor";
|
|
502
|
-
var PYTHON_RUNTIME_MANIFEST = ".seastudio-python-runtime-manifest.json";
|
|
503
|
-
var PYTHON_VENDOR_MANIFEST = ".seastudio-python-vendor-manifest.json";
|
|
504
|
-
var PYTHON_CACHE_DIR = join(tmpdir(), "seastudio-python-runtime-cache");
|
|
505
|
-
var NETWORK_RETRY_COUNT = 3;
|
|
506
|
-
var DOWNLOAD_PROGRESS_LOG_INTERVAL_MS = 5e3;
|
|
507
|
-
function logPackStep(message) {
|
|
508
|
-
console.log(chalk.cyan(`[pack] ${message}`));
|
|
509
|
-
}
|
|
510
|
-
function formatBytes(bytes) {
|
|
511
|
-
if (!Number.isFinite(bytes) || bytes <= 0) {
|
|
512
|
-
return "0 B";
|
|
513
|
-
}
|
|
514
|
-
if (bytes < 1024) return `${bytes.toFixed(0)} B`;
|
|
515
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
516
|
-
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
517
|
-
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
518
|
-
}
|
|
519
|
-
function formatPercent(value) {
|
|
520
|
-
if (!Number.isFinite(value)) {
|
|
521
|
-
return "0.0%";
|
|
522
|
-
}
|
|
523
|
-
return `${value.toFixed(1)}%`;
|
|
524
|
-
}
|
|
525
|
-
function formatDuration(ms) {
|
|
526
|
-
const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
|
|
527
|
-
const minutes = Math.floor(totalSeconds / 60);
|
|
528
|
-
const seconds = totalSeconds % 60;
|
|
529
|
-
if (minutes <= 0) {
|
|
530
|
-
return `${seconds}s`;
|
|
531
|
-
}
|
|
532
|
-
return `${minutes}m ${seconds}s`;
|
|
533
|
-
}
|
|
534
|
-
function updateSpinnerText(spinner, text) {
|
|
535
|
-
if (!spinner?.isSpinning) {
|
|
536
|
-
return;
|
|
537
|
-
}
|
|
538
|
-
spinner.text = text;
|
|
539
|
-
}
|
|
540
|
-
async function streamCommandWithProgress(params) {
|
|
541
|
-
const { command, args, cwd, spinner, label } = params;
|
|
542
|
-
if (spinner?.isSpinning) {
|
|
543
|
-
spinner.stop();
|
|
544
|
-
}
|
|
545
|
-
logPackStep(`${label}\uFF08\u5B9E\u65F6\u8F93\u51FA\u5F00\u59CB\uFF09`);
|
|
546
|
-
return await new Promise((resolvePromise, reject) => {
|
|
547
|
-
const child = spawn(command, args, {
|
|
548
|
-
cwd,
|
|
549
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
550
|
-
env: {
|
|
551
|
-
...process.env,
|
|
552
|
-
PIP_PROGRESS_BAR: "on"
|
|
553
|
-
}
|
|
554
|
-
});
|
|
555
|
-
let stdout = "";
|
|
556
|
-
let stderr = "";
|
|
557
|
-
child.stdout.on("data", (chunk) => {
|
|
558
|
-
const text = chunk.toString();
|
|
559
|
-
stdout += text;
|
|
560
|
-
process.stdout.write(text);
|
|
561
|
-
});
|
|
562
|
-
child.stderr.on("data", (chunk) => {
|
|
563
|
-
const text = chunk.toString();
|
|
564
|
-
stderr += text;
|
|
565
|
-
process.stderr.write(text);
|
|
566
|
-
});
|
|
567
|
-
child.on("error", reject);
|
|
568
|
-
child.on("close", (code) => {
|
|
569
|
-
spinner?.start(`${label}\u5B8C\u6210`);
|
|
570
|
-
resolvePromise({
|
|
571
|
-
stdout,
|
|
572
|
-
stderr,
|
|
573
|
-
code: code ?? 0
|
|
574
|
-
});
|
|
575
|
-
});
|
|
576
|
-
});
|
|
577
|
-
}
|
|
578
|
-
async function validateProjectConfig(configPath) {
|
|
579
|
-
if (!existsSync(configPath)) {
|
|
580
|
-
throw new Error("\u672A\u627E\u5230 seastudio.config.json \u6587\u4EF6");
|
|
581
|
-
}
|
|
582
|
-
const content = await readFile(configPath, "utf-8");
|
|
583
|
-
const config = JSON.parse(content);
|
|
584
|
-
if (config.type !== "plugin" && config.type !== "agent") {
|
|
585
|
-
throw new Error(`\u914D\u7F6E\u6587\u4EF6 type \u5FC5\u987B\u4E3A "plugin" \u6216 "agent"\uFF0C\u5F53\u524D\u4E3A "${config.type}"`);
|
|
586
|
-
}
|
|
587
|
-
if (!config.id) {
|
|
588
|
-
throw new Error("\u914D\u7F6E\u6587\u4EF6\u7F3A\u5C11 id \u5B57\u6BB5");
|
|
589
|
-
}
|
|
590
|
-
if (!config.name) {
|
|
591
|
-
throw new Error("\u914D\u7F6E\u6587\u4EF6\u7F3A\u5C11 name \u5B57\u6BB5");
|
|
592
|
-
}
|
|
593
|
-
if (!config.protocolHost) {
|
|
594
|
-
throw new Error("\u914D\u7F6E\u6587\u4EF6\u7F3A\u5C11 protocolHost \u5B57\u6BB5");
|
|
595
|
-
}
|
|
596
|
-
if (config.type === "plugin") {
|
|
597
|
-
if (!config.tab?.id || !config.tab?.title) {
|
|
598
|
-
throw new Error("\u914D\u7F6E\u6587\u4EF6\u7F3A\u5C11 tab \u914D\u7F6E");
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
return config;
|
|
602
|
-
}
|
|
603
|
-
async function getFileSize(filePath) {
|
|
604
|
-
const stats = await stat(filePath);
|
|
605
|
-
const bytes = stats.size;
|
|
606
|
-
if (bytes < 1024) return `${bytes} B`;
|
|
607
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
608
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
609
|
-
}
|
|
610
|
-
function resolvePythonStandaloneTarget(platform, arch) {
|
|
611
|
-
if (platform === "darwin") {
|
|
612
|
-
if (arch === "arm64") return "aarch64-apple-darwin";
|
|
613
|
-
if (arch === "x64") return "x86_64-apple-darwin";
|
|
614
|
-
}
|
|
615
|
-
if (platform === "win32") {
|
|
616
|
-
if (arch === "x64") return "x86_64-pc-windows-msvc";
|
|
617
|
-
if (arch === "ia32") return "i686-pc-windows-msvc";
|
|
618
|
-
}
|
|
619
|
-
if (platform === "linux") {
|
|
620
|
-
if (arch === "x64") return "x86_64-unknown-linux-gnu";
|
|
621
|
-
if (arch === "arm64") return "aarch64-unknown-linux-gnu";
|
|
622
|
-
}
|
|
623
|
-
return null;
|
|
624
|
-
}
|
|
625
|
-
function pickPythonRuntimeAsset(assets, target) {
|
|
626
|
-
const suffixes = [
|
|
627
|
-
`-${target}-install_only_stripped.tar.gz`,
|
|
628
|
-
`-${target}-install_only.tar.gz`
|
|
629
|
-
];
|
|
630
|
-
const matches = assets.filter((asset) => {
|
|
631
|
-
const name = asset?.name || "";
|
|
632
|
-
if (!name.startsWith("cpython-") || !suffixes.some((suffix) => name.endsWith(suffix))) {
|
|
633
|
-
return false;
|
|
634
|
-
}
|
|
635
|
-
const versionMatch = name.match(/cpython-(\d+)\.(\d+)\.(\d+)/);
|
|
636
|
-
if (!versionMatch) {
|
|
637
|
-
return false;
|
|
638
|
-
}
|
|
639
|
-
const major = Number(versionMatch[1]);
|
|
640
|
-
const minor = Number(versionMatch[2]);
|
|
641
|
-
return major === 3 && minor >= TARGET_PYTHON_MINOR && minor <= MAX_PYTHON_MINOR;
|
|
642
|
-
});
|
|
643
|
-
if (matches.length === 0) {
|
|
644
|
-
return null;
|
|
645
|
-
}
|
|
646
|
-
matches.sort((a, b) => {
|
|
647
|
-
const aMatch = (a.name || "").match(/cpython-(\d+)\.(\d+)\.(\d+)/);
|
|
648
|
-
const bMatch = (b.name || "").match(/cpython-(\d+)\.(\d+)\.(\d+)/);
|
|
649
|
-
if (!aMatch || !bMatch) {
|
|
650
|
-
return 0;
|
|
651
|
-
}
|
|
652
|
-
const aMinor = Number(aMatch[2]);
|
|
653
|
-
const bMinor = Number(bMatch[2]);
|
|
654
|
-
if (aMinor !== bMinor) {
|
|
655
|
-
return aMinor - bMinor;
|
|
656
|
-
}
|
|
657
|
-
const aPatch = Number(aMatch[3]);
|
|
658
|
-
const bPatch = Number(bMatch[3]);
|
|
659
|
-
return bPatch - aPatch;
|
|
660
|
-
});
|
|
661
|
-
return matches[0];
|
|
662
|
-
}
|
|
663
|
-
async function fetchText(url, headers) {
|
|
664
|
-
return new Promise((resolve4, reject) => {
|
|
665
|
-
https.get(url, { headers }, (res) => {
|
|
666
|
-
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
667
|
-
res.resume();
|
|
668
|
-
fetchText(res.headers.location, headers).then(resolve4).catch(reject);
|
|
669
|
-
return;
|
|
670
|
-
}
|
|
671
|
-
if (res.statusCode && res.statusCode >= 400) {
|
|
672
|
-
reject(new Error(`Request failed: ${res.statusCode}`));
|
|
673
|
-
res.resume();
|
|
674
|
-
return;
|
|
675
|
-
}
|
|
676
|
-
const chunks = [];
|
|
677
|
-
res.on("data", (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
|
|
678
|
-
res.on("end", () => resolve4(Buffer.concat(chunks).toString("utf8")));
|
|
679
|
-
res.on("error", reject);
|
|
680
|
-
}).on("error", reject);
|
|
681
|
-
});
|
|
682
|
-
}
|
|
683
|
-
async function sleep(ms) {
|
|
684
|
-
await new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
685
|
-
}
|
|
686
|
-
async function withRetries(label, task) {
|
|
687
|
-
let lastError;
|
|
688
|
-
for (let attempt = 1; attempt <= NETWORK_RETRY_COUNT; attempt += 1) {
|
|
689
|
-
try {
|
|
690
|
-
if (attempt > 1) {
|
|
691
|
-
logPackStep(`${label}\uFF1A\u7B2C ${attempt} \u6B21\u5C1D\u8BD5`);
|
|
692
|
-
}
|
|
693
|
-
return await task();
|
|
694
|
-
} catch (error) {
|
|
695
|
-
lastError = error;
|
|
696
|
-
if (attempt >= NETWORK_RETRY_COUNT) {
|
|
697
|
-
break;
|
|
698
|
-
}
|
|
699
|
-
await sleep(1e3 * attempt);
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
throw new Error(`${label} failed after ${NETWORK_RETRY_COUNT} attempts: ${lastError instanceof Error ? lastError.message : String(lastError)}`);
|
|
703
|
-
}
|
|
704
|
-
async function fetchJson(url) {
|
|
705
|
-
const text = await withRetries("Fetch python runtime release metadata", () => fetchText(url, {
|
|
706
|
-
"User-Agent": "seastudio-sdk-pack",
|
|
707
|
-
"Accept": "application/vnd.github+json"
|
|
708
|
-
}));
|
|
709
|
-
return JSON.parse(text);
|
|
710
|
-
}
|
|
711
|
-
async function downloadToFile(url, dest, options = {}, redirectCount = 0) {
|
|
712
|
-
if (redirectCount > 5) {
|
|
713
|
-
throw new Error(`Too many redirects: ${url}`);
|
|
714
|
-
}
|
|
715
|
-
await new Promise((resolvePromise, reject) => {
|
|
716
|
-
https.get(url, { headers: { "User-Agent": "seastudio-sdk-pack" } }, (res) => {
|
|
717
|
-
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
718
|
-
res.resume();
|
|
719
|
-
downloadToFile(res.headers.location, dest, options, redirectCount + 1).then(resolvePromise).catch(reject);
|
|
720
|
-
return;
|
|
721
|
-
}
|
|
722
|
-
if (res.statusCode && res.statusCode >= 400) {
|
|
723
|
-
reject(new Error(`Download failed: ${res.statusCode}`));
|
|
724
|
-
res.resume();
|
|
725
|
-
return;
|
|
726
|
-
}
|
|
727
|
-
const totalBytes = Number(res.headers["content-length"] || 0);
|
|
728
|
-
let downloadedBytes = 0;
|
|
729
|
-
const startedAt = Date.now();
|
|
730
|
-
let lastUpdateAt = 0;
|
|
731
|
-
let lastLoggedAt = 0;
|
|
732
|
-
const label = options.label || "\u4E0B\u8F7D\u4E2D";
|
|
733
|
-
res.on("data", (chunk) => {
|
|
734
|
-
downloadedBytes += Buffer.isBuffer(chunk) ? chunk.length : Buffer.byteLength(String(chunk));
|
|
735
|
-
const now = Date.now();
|
|
736
|
-
const elapsedMs = Math.max(1, now - startedAt);
|
|
737
|
-
const speed = downloadedBytes / (elapsedMs / 1e3);
|
|
738
|
-
const progressText = totalBytes > 0 ? `${label} ${formatPercent(downloadedBytes / totalBytes * 100)} (${formatBytes(downloadedBytes)}/${formatBytes(totalBytes)}, ${formatBytes(speed)}/s)` : `${label} ${formatBytes(downloadedBytes)} (${formatBytes(speed)}/s)`;
|
|
739
|
-
if (now - lastUpdateAt >= 200) {
|
|
740
|
-
lastUpdateAt = now;
|
|
741
|
-
updateSpinnerText(options.spinner, progressText);
|
|
742
|
-
}
|
|
743
|
-
if (now - lastLoggedAt >= DOWNLOAD_PROGRESS_LOG_INTERVAL_MS) {
|
|
744
|
-
lastLoggedAt = now;
|
|
745
|
-
logPackStep(progressText);
|
|
746
|
-
}
|
|
747
|
-
});
|
|
748
|
-
const fileStream = createWriteStream(dest);
|
|
749
|
-
pipeline(res, fileStream).then(() => {
|
|
750
|
-
const elapsedMs = Math.max(1, Date.now() - startedAt);
|
|
751
|
-
const speed = downloadedBytes / (elapsedMs / 1e3);
|
|
752
|
-
const finalText = totalBytes > 0 ? `${label}\u5B8C\u6210 ${formatPercent(100)} (${formatBytes(downloadedBytes)}/${formatBytes(totalBytes)}, ${formatBytes(speed)}/s, ${formatDuration(elapsedMs)})` : `${label}\u5B8C\u6210 ${formatBytes(downloadedBytes)} (${formatBytes(speed)}/s, ${formatDuration(elapsedMs)})`;
|
|
753
|
-
updateSpinnerText(options.spinner, finalText);
|
|
754
|
-
logPackStep(finalText);
|
|
755
|
-
resolvePromise();
|
|
756
|
-
}).catch(reject);
|
|
757
|
-
}).on("error", reject);
|
|
758
|
-
});
|
|
759
|
-
}
|
|
760
|
-
async function extractTarWithProgress(params) {
|
|
761
|
-
let entryCount = 0;
|
|
762
|
-
let fileCount = 0;
|
|
763
|
-
let totalEntryBytes = 0;
|
|
764
|
-
let lastUpdateAt = 0;
|
|
765
|
-
const startedAt = Date.now();
|
|
766
|
-
await x({
|
|
767
|
-
file: params.file,
|
|
768
|
-
cwd: params.cwd,
|
|
769
|
-
gzip: true,
|
|
770
|
-
onReadEntry: (entry) => {
|
|
771
|
-
entryCount += 1;
|
|
772
|
-
if (entry.type !== "Directory") {
|
|
773
|
-
fileCount += 1;
|
|
774
|
-
}
|
|
775
|
-
totalEntryBytes += Number(entry.size || 0);
|
|
776
|
-
const now = Date.now();
|
|
777
|
-
if (now - lastUpdateAt < 200) {
|
|
778
|
-
return;
|
|
779
|
-
}
|
|
780
|
-
lastUpdateAt = now;
|
|
781
|
-
updateSpinnerText(
|
|
782
|
-
params.spinner,
|
|
783
|
-
`${params.label} \u5DF2\u5904\u7406 ${entryCount} \u9879 / ${fileCount} \u6587\u4EF6 (${formatBytes(totalEntryBytes)})`
|
|
784
|
-
);
|
|
785
|
-
}
|
|
786
|
-
});
|
|
787
|
-
updateSpinnerText(
|
|
788
|
-
params.spinner,
|
|
789
|
-
`${params.label}\u5B8C\u6210\uFF0C\u5171 ${entryCount} \u9879 / ${fileCount} \u6587\u4EF6 (${formatBytes(totalEntryBytes)}, ${formatDuration(Date.now() - startedAt)})`
|
|
790
|
-
);
|
|
791
|
-
}
|
|
792
|
-
function isCorruptedArchiveError(error) {
|
|
793
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
794
|
-
return /unexpected end of file|invalid distance|incorrect data check|zlib/i.test(message);
|
|
795
|
-
}
|
|
796
|
-
async function materializeSymlinks(rootDir, currentDir = rootDir) {
|
|
797
|
-
const entries = await readdir(currentDir, { withFileTypes: true });
|
|
798
|
-
for (const entry of entries) {
|
|
799
|
-
const fullPath = join(currentDir, entry.name);
|
|
800
|
-
if (entry.isDirectory()) {
|
|
801
|
-
await materializeSymlinks(rootDir, fullPath);
|
|
802
|
-
continue;
|
|
803
|
-
}
|
|
804
|
-
const stats = await lstat(fullPath);
|
|
805
|
-
if (!stats.isSymbolicLink()) {
|
|
806
|
-
continue;
|
|
807
|
-
}
|
|
808
|
-
const currentTarget = await readlink(fullPath);
|
|
809
|
-
const resolvedTarget = currentTarget.startsWith("/") ? currentTarget : resolve(dirname(fullPath), currentTarget);
|
|
810
|
-
if (!resolvedTarget.startsWith(rootDir) || !existsSync(resolvedTarget)) {
|
|
811
|
-
continue;
|
|
812
|
-
}
|
|
813
|
-
const targetStats = await stat(resolvedTarget);
|
|
814
|
-
await unlink(fullPath);
|
|
815
|
-
await copyFile(resolvedTarget, fullPath);
|
|
816
|
-
await chmod(fullPath, targetStats.mode);
|
|
817
|
-
logPackStep(`\u5B9E\u4F53\u5316\u8F6F\u94FE\u63A5 ${fullPath} -> ${resolvedTarget}`);
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
async function buildPythonRuntimeDirectory(spinner) {
|
|
821
|
-
const target = resolvePythonStandaloneTarget(process.platform, process.arch);
|
|
822
|
-
if (!target) {
|
|
823
|
-
throw new Error(`\u5F53\u524D\u5E73\u53F0\u6682\u4E0D\u652F\u6301\u6253\u5305 Python runtime: ${process.platform} ${process.arch}`);
|
|
824
|
-
}
|
|
825
|
-
logPackStep(`\u51C6\u5907 Python runtime\uFF0C\u76EE\u6807\u5E73\u53F0 ${target}`);
|
|
826
|
-
updateSpinnerText(spinner, "\u51C6\u5907 Agent \u81EA\u5E26 Python runtime... \u8BFB\u53D6 runtime \u5143\u6570\u636E");
|
|
827
|
-
logPackStep("\u62C9\u53D6 python-build-standalone \u6700\u65B0 release \u4FE1\u606F");
|
|
828
|
-
const release = await fetchJson(
|
|
829
|
-
PYTHON_STANDALONE_RELEASE_API
|
|
830
|
-
);
|
|
831
|
-
const asset = pickPythonRuntimeAsset(release.assets || [], target);
|
|
832
|
-
if (!asset?.name || !asset.browser_download_url) {
|
|
833
|
-
throw new Error(
|
|
834
|
-
`\u672A\u627E\u5230\u9002\u914D ${target} \u7684 Python runtime\uFF08\u8981\u6C42 minor >= ${TARGET_PYTHON_MINOR}\uFF09`
|
|
835
|
-
);
|
|
836
|
-
}
|
|
837
|
-
await mkdir(PYTHON_CACHE_DIR, { recursive: true });
|
|
838
|
-
const archivePath = join(PYTHON_CACHE_DIR, asset.name);
|
|
839
|
-
if (!existsSync(archivePath)) {
|
|
840
|
-
logPackStep(`\u4E0B\u8F7D Python runtime \u538B\u7F29\u5305 ${asset.name}`);
|
|
841
|
-
await withRetries("Download python runtime archive", () => downloadToFile(
|
|
842
|
-
asset.browser_download_url,
|
|
843
|
-
archivePath,
|
|
844
|
-
{
|
|
845
|
-
spinner,
|
|
846
|
-
label: `\u4E0B\u8F7D Python runtime ${asset.name}`
|
|
847
|
-
}
|
|
848
|
-
));
|
|
849
|
-
} else {
|
|
850
|
-
logPackStep(`\u590D\u7528\u672C\u5730 Python runtime \u7F13\u5B58 ${asset.name}`);
|
|
851
|
-
updateSpinnerText(spinner, `\u51C6\u5907 Agent \u81EA\u5E26 Python runtime... \u590D\u7528\u7F13\u5B58 ${asset.name}`);
|
|
852
|
-
}
|
|
853
|
-
const tempRoot = await mkdtemp(join(tmpdir(), "seastudio-python-runtime-"));
|
|
854
|
-
try {
|
|
855
|
-
logPackStep(`\u89E3\u538B Python runtime \u538B\u7F29\u5305 ${asset.name}`);
|
|
856
|
-
updateSpinnerText(spinner, `\u89E3\u538B Python runtime ${asset.name}...`);
|
|
857
|
-
await extractTarWithProgress({
|
|
858
|
-
file: archivePath,
|
|
859
|
-
cwd: tempRoot,
|
|
860
|
-
spinner,
|
|
861
|
-
label: `\u89E3\u538B Python runtime ${asset.name}`
|
|
862
|
-
});
|
|
863
|
-
} catch (error) {
|
|
864
|
-
if (!isCorruptedArchiveError(error)) {
|
|
865
|
-
await rm(tempRoot, { recursive: true, force: true }).catch(() => {
|
|
866
|
-
});
|
|
867
|
-
throw error;
|
|
868
|
-
}
|
|
869
|
-
logPackStep(`\u68C0\u6D4B\u5230\u635F\u574F\u7F13\u5B58\uFF0C\u5220\u9664\u5E76\u91CD\u65B0\u4E0B\u8F7D ${asset.name}`);
|
|
870
|
-
updateSpinnerText(spinner, `\u68C0\u6D4B\u5230\u7F13\u5B58\u635F\u574F\uFF0C\u91CD\u65B0\u4E0B\u8F7D ${asset.name}...`);
|
|
871
|
-
await rm(archivePath, { force: true }).catch(() => {
|
|
872
|
-
});
|
|
873
|
-
await withRetries("Re-download corrupted python runtime archive", () => downloadToFile(
|
|
874
|
-
asset.browser_download_url,
|
|
875
|
-
archivePath,
|
|
876
|
-
{
|
|
877
|
-
spinner,
|
|
878
|
-
label: `\u91CD\u65B0\u4E0B\u8F7D Python runtime ${asset.name}`
|
|
879
|
-
}
|
|
880
|
-
));
|
|
881
|
-
await rm(tempRoot, { recursive: true, force: true }).catch(() => {
|
|
882
|
-
});
|
|
883
|
-
const retryTempRoot = await mkdtemp(join(tmpdir(), "seastudio-python-runtime-"));
|
|
884
|
-
try {
|
|
885
|
-
logPackStep(`\u91CD\u65B0\u89E3\u538B Python runtime \u538B\u7F29\u5305 ${asset.name}`);
|
|
886
|
-
updateSpinnerText(spinner, `\u91CD\u65B0\u89E3\u538B Python runtime ${asset.name}...`);
|
|
887
|
-
await extractTarWithProgress({
|
|
888
|
-
file: archivePath,
|
|
889
|
-
cwd: retryTempRoot,
|
|
890
|
-
spinner,
|
|
891
|
-
label: `\u91CD\u65B0\u89E3\u538B Python runtime ${asset.name}`
|
|
892
|
-
});
|
|
893
|
-
return finalizePythonRuntimeDirectory(retryTempRoot, asset.name, target);
|
|
894
|
-
} catch (retryError) {
|
|
895
|
-
await rm(retryTempRoot, { recursive: true, force: true }).catch(() => {
|
|
896
|
-
});
|
|
897
|
-
throw retryError;
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
return finalizePythonRuntimeDirectory(tempRoot, asset.name, target);
|
|
901
|
-
}
|
|
902
|
-
async function finalizePythonRuntimeDirectory(tempRoot, assetName, target) {
|
|
903
|
-
const extractedRoot = join(tempRoot, "python");
|
|
904
|
-
if (!existsSync(extractedRoot)) {
|
|
905
|
-
await rm(tempRoot, { recursive: true, force: true }).catch(() => {
|
|
906
|
-
});
|
|
907
|
-
throw new Error(`Python runtime \u538B\u7F29\u5305\u5E03\u5C40\u4E0D\u7B26\u5408\u9884\u671F: ${assetName}`);
|
|
908
|
-
}
|
|
909
|
-
const pythonCommand = process.platform === "win32" ? join(extractedRoot, "python.exe") : join(extractedRoot, "bin", "python3");
|
|
910
|
-
if (!existsSync(pythonCommand)) {
|
|
911
|
-
await rm(tempRoot, { recursive: true, force: true }).catch(() => {
|
|
912
|
-
});
|
|
913
|
-
throw new Error(`Python runtime \u7F3A\u5C11\u89E3\u91CA\u5668: ${pythonCommand}`);
|
|
914
|
-
}
|
|
915
|
-
logPackStep(`\u63A2\u6D4B Python runtime \u7248\u672C ${pythonCommand}`);
|
|
916
|
-
const versionResult = spawnSync(pythonCommand, ["--version"], { encoding: "utf-8" });
|
|
917
|
-
if (versionResult.status !== 0) {
|
|
918
|
-
await rm(tempRoot, { recursive: true, force: true }).catch(() => {
|
|
919
|
-
});
|
|
920
|
-
throw new Error(
|
|
921
|
-
`Python runtime \u7248\u672C\u63A2\u6D4B\u5931\u8D25: ${versionResult.stderr?.trim() || versionResult.stdout?.trim() || "unknown error"}`
|
|
922
|
-
);
|
|
923
|
-
}
|
|
924
|
-
const manifestPath = join(tempRoot, PYTHON_RUNTIME_MANIFEST);
|
|
925
|
-
const pythonVersion = `${versionResult.stdout || versionResult.stderr}`.trim();
|
|
926
|
-
logPackStep(`Python runtime \u5C31\u7EEA\uFF1A${pythonVersion}`);
|
|
927
|
-
await materializeSymlinks(extractedRoot);
|
|
928
|
-
await writeFile(manifestPath, `${JSON.stringify({
|
|
929
|
-
pythonVersion,
|
|
930
|
-
target,
|
|
931
|
-
platform: process.platform,
|
|
932
|
-
arch: process.arch,
|
|
933
|
-
assetName,
|
|
934
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
935
|
-
}, null, 2)}
|
|
936
|
-
`, "utf-8");
|
|
937
|
-
return {
|
|
938
|
-
runtimeDir: extractedRoot,
|
|
939
|
-
manifestPath,
|
|
940
|
-
pythonCommand,
|
|
941
|
-
pythonVersion
|
|
942
|
-
};
|
|
943
|
-
}
|
|
944
|
-
async function buildPythonVendorDirectory(projectDir, backendDirName, pythonCommand, pythonVersion, spinner) {
|
|
945
|
-
const backendDir = join(projectDir, backendDirName);
|
|
946
|
-
const requirementsPath = join(backendDir, "requirements.txt");
|
|
947
|
-
if (!existsSync(requirementsPath)) {
|
|
948
|
-
logPackStep("\u672A\u627E\u5230 requirements.txt\uFF0C\u8DF3\u8FC7 Python vendor \u4F9D\u8D56");
|
|
949
|
-
return null;
|
|
950
|
-
}
|
|
951
|
-
const tempRoot = await mkdtemp(join(tmpdir(), "seastudio-python-vendor-"));
|
|
952
|
-
const vendorDir = join(tempRoot, PYTHON_VENDOR_DIRNAME);
|
|
953
|
-
const manifestPath = join(tempRoot, PYTHON_VENDOR_MANIFEST);
|
|
954
|
-
logPackStep(`\u5B89\u88C5 Python vendor \u4F9D\u8D56\uFF0C\u4F7F\u7528 ${pythonVersion}`);
|
|
955
|
-
updateSpinnerText(spinner, `\u5B89\u88C5 Python vendor \u4F9D\u8D56... \u4F7F\u7528 ${pythonVersion}`);
|
|
956
|
-
const installResult = await streamCommandWithProgress({
|
|
957
|
-
command: pythonCommand,
|
|
958
|
-
args: ["-m", "pip", "install", "-r", requirementsPath, "--target", vendorDir, "--upgrade", "--progress-bar", "on"],
|
|
959
|
-
cwd: backendDir,
|
|
960
|
-
spinner,
|
|
961
|
-
label: "\u5B89\u88C5 Python vendor \u4F9D\u8D56"
|
|
962
|
-
});
|
|
963
|
-
if (installResult.code !== 0) {
|
|
964
|
-
await rm(tempRoot, { recursive: true, force: true }).catch(() => {
|
|
965
|
-
});
|
|
966
|
-
throw new Error(
|
|
967
|
-
`Python \u4F9D\u8D56 vendor \u5931\u8D25: ${installResult.stderr.trim() || installResult.stdout.trim() || "unknown error"}`
|
|
968
|
-
);
|
|
969
|
-
}
|
|
970
|
-
logPackStep("Python vendor \u4F9D\u8D56\u5B89\u88C5\u5B8C\u6210");
|
|
971
|
-
updateSpinnerText(spinner, "Python vendor \u4F9D\u8D56\u5B89\u88C5\u5B8C\u6210\uFF0C\u5199\u5165 manifest...");
|
|
972
|
-
const versionResult = spawnSync(pythonCommand, ["--version"], { encoding: "utf-8" });
|
|
973
|
-
const manifest = {
|
|
974
|
-
pythonCommand,
|
|
975
|
-
pythonVersion: `${versionResult.stdout || versionResult.stderr}`.trim() || pythonVersion,
|
|
976
|
-
platform: process.platform,
|
|
977
|
-
arch: process.arch,
|
|
978
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
979
|
-
requirementsFile: "requirements.txt"
|
|
980
|
-
};
|
|
981
|
-
await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
982
|
-
`, "utf-8");
|
|
983
|
-
return {
|
|
984
|
-
vendorDir,
|
|
985
|
-
manifestPath
|
|
986
|
-
};
|
|
987
|
-
}
|
|
988
|
-
var packCommand = new Command("pack").description("\u6253\u5305\u63D2\u4EF6\u6216 Agent").option("-d, --dir <directory>", "\u9879\u76EE\u76EE\u5F55", ".").option("-o, --output <file>", "\u8F93\u51FA\u6587\u4EF6\u540D").action(async (options) => {
|
|
989
|
-
const projectDir = resolve(process.cwd(), options.dir);
|
|
990
|
-
const configPath = join(projectDir, "seastudio.config.json");
|
|
991
|
-
const frontendDistDir = join(projectDir, "frontend", "dist");
|
|
992
|
-
let pythonRuntimeArtifacts = null;
|
|
993
|
-
let pythonVendorArtifacts = null;
|
|
994
|
-
console.log(chalk.cyan("\n\u{1F4E6} SeaStudio \u6253\u5305\n"));
|
|
995
|
-
const spinnerConfig = ora2("\u8BFB\u53D6 seastudio.config.json...").start();
|
|
996
|
-
let config;
|
|
997
|
-
try {
|
|
998
|
-
config = await validateProjectConfig(configPath);
|
|
999
|
-
spinnerConfig.succeed(`${config.type === "agent" ? "Agent" : "\u63D2\u4EF6"}: ${chalk.bold(config.name)} (${config.id})`);
|
|
1000
|
-
} catch (error) {
|
|
1001
|
-
spinnerConfig.fail("\u914D\u7F6E\u9A8C\u8BC1\u5931\u8D25");
|
|
1002
|
-
console.error(chalk.red(`
|
|
1003
|
-
\u274C ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}
|
|
1004
|
-
`));
|
|
1005
|
-
process.exit(1);
|
|
1006
|
-
}
|
|
1007
|
-
const isRemotePlugin = config.type === "plugin" && config.uiType === "remote";
|
|
1008
|
-
if (!isRemotePlugin) {
|
|
1009
|
-
const spinnerDist = ora2("\u68C0\u67E5 frontend build...").start();
|
|
1010
|
-
const distIndexPath = join(frontendDistDir, "index.html");
|
|
1011
|
-
if (!existsSync(distIndexPath)) {
|
|
1012
|
-
spinnerDist.fail("\u672A\u627E\u5230 frontend/dist/index.html");
|
|
1013
|
-
console.log(chalk.yellow("\n\u63D0\u793A: \u8BF7\u5148\u6267\u884C frontend build\n"));
|
|
1014
|
-
console.log(` ${chalk.yellow("cd")} frontend`);
|
|
1015
|
-
console.log(` ${chalk.yellow("npm run build")}
|
|
1016
|
-
`);
|
|
1017
|
-
process.exit(1);
|
|
1018
|
-
}
|
|
1019
|
-
if (config.type === "plugin") {
|
|
1020
|
-
const generated = await generatePluginMcpManifest(projectDir);
|
|
1021
|
-
logPackStep(`\u751F\u6210 MCP manifest\uFF1A${generated.outputPath} (${generated.toolCount} tools)`);
|
|
1022
|
-
}
|
|
1023
|
-
spinnerDist.succeed("frontend/dist/index.html \u5DF2\u627E\u5230");
|
|
1024
|
-
} else {
|
|
1025
|
-
const spinnerDist = ora2("\u8FDC\u7A0B\u63D2\u4EF6\uFF0C\u8DF3\u8FC7 frontend build \u68C0\u67E5").start();
|
|
1026
|
-
if (!config.remoteUrl) {
|
|
1027
|
-
spinnerDist.fail("\u8FDC\u7A0B\u63D2\u4EF6\u7F3A\u5C11 remoteUrl");
|
|
1028
|
-
process.exit(1);
|
|
1029
|
-
}
|
|
1030
|
-
spinnerDist.succeed(`\u8FDC\u7A0B\u63D2\u4EF6: ${config.remoteUrl}`);
|
|
1031
|
-
}
|
|
1032
|
-
const outputFileName = options.output || `${config.protocolHost}.${config.type === "agent" ? "seaagent" : "seaplugin"}`;
|
|
1033
|
-
const outputPath = resolve(projectDir, outputFileName);
|
|
1034
|
-
const spinnerPack = ora2(`\u521B\u5EFA${config.type === "agent" ? " Agent" : "\u63D2\u4EF6"}\u5305...`).start();
|
|
1035
|
-
try {
|
|
1036
|
-
const output = createWriteStream(outputPath);
|
|
1037
|
-
const archive = archiver("zip", { zlib: { level: 9 } });
|
|
1038
|
-
const archivePromise = new Promise((resolvePromise, reject) => {
|
|
1039
|
-
output.on("close", () => resolvePromise());
|
|
1040
|
-
archive.on("error", (err) => reject(err));
|
|
1041
|
-
});
|
|
1042
|
-
let lastArchiveUpdateAt = 0;
|
|
1043
|
-
archive.on("progress", (event) => {
|
|
1044
|
-
const now = Date.now();
|
|
1045
|
-
if (now - lastArchiveUpdateAt < 200) {
|
|
1046
|
-
return;
|
|
1047
|
-
}
|
|
1048
|
-
lastArchiveUpdateAt = now;
|
|
1049
|
-
const totalBytes = Number(event.fs.totalBytes || 0);
|
|
1050
|
-
const processedBytes = Number(event.fs.processedBytes || 0);
|
|
1051
|
-
const totalEntries = Number(event.entries.total || 0);
|
|
1052
|
-
const processedEntries = Number(event.entries.processed || 0);
|
|
1053
|
-
const percent = totalBytes > 0 ? formatPercent(processedBytes / totalBytes * 100) : "...";
|
|
1054
|
-
spinnerPack.text = `\u5199\u5165\u538B\u7F29\u5305\u4E2D ${percent} (${formatBytes(processedBytes)}/${formatBytes(totalBytes || processedBytes)}, ${processedEntries}/${totalEntries || processedEntries} \u9879)`;
|
|
1055
|
-
});
|
|
1056
|
-
archive.pipe(output);
|
|
1057
|
-
archive.file(configPath, { name: "seastudio.config.json" });
|
|
1058
|
-
if (!isRemotePlugin) {
|
|
1059
|
-
archive.directory(frontendDistDir, "dist");
|
|
1060
|
-
}
|
|
1061
|
-
const iconPath = join(projectDir, "icon.png");
|
|
1062
|
-
if (existsSync(iconPath)) {
|
|
1063
|
-
archive.file(iconPath, { name: "icon.png" });
|
|
1064
|
-
}
|
|
1065
|
-
if (config.type === "agent" && config.backend?.mode === "hosted") {
|
|
1066
|
-
const backendDir = join(projectDir, config.backendDir || "backend");
|
|
1067
|
-
if (existsSync(backendDir)) {
|
|
1068
|
-
if (config.backend.runtime === "python") {
|
|
1069
|
-
spinnerPack.text = "\u51C6\u5907 Agent \u81EA\u5E26 Python runtime...";
|
|
1070
|
-
pythonRuntimeArtifacts = await buildPythonRuntimeDirectory(spinnerPack);
|
|
1071
|
-
spinnerPack.text = "\u6253\u5305 Python vendored \u4F9D\u8D56...";
|
|
1072
|
-
pythonVendorArtifacts = await buildPythonVendorDirectory(
|
|
1073
|
-
projectDir,
|
|
1074
|
-
config.backendDir || "backend",
|
|
1075
|
-
pythonRuntimeArtifacts.pythonCommand,
|
|
1076
|
-
pythonRuntimeArtifacts.pythonVersion,
|
|
1077
|
-
spinnerPack
|
|
1078
|
-
);
|
|
1079
|
-
}
|
|
1080
|
-
logPackStep(`\u5199\u5165 backend \u76EE\u5F55\u5230\u538B\u7F29\u5305\uFF1A${config.backendDir || "backend"}`);
|
|
1081
|
-
archive.directory(backendDir, config.backendDir || "backend");
|
|
1082
|
-
if (pythonRuntimeArtifacts) {
|
|
1083
|
-
logPackStep(`\u5199\u5165 Python runtime \u5230\u538B\u7F29\u5305\uFF1A${config.backendDir || "backend"}/${PYTHON_RUNTIME_DIRNAME}`);
|
|
1084
|
-
archive.directory(
|
|
1085
|
-
pythonRuntimeArtifacts.runtimeDir,
|
|
1086
|
-
`${config.backendDir || "backend"}/${PYTHON_RUNTIME_DIRNAME}`
|
|
1087
|
-
);
|
|
1088
|
-
archive.file(
|
|
1089
|
-
pythonRuntimeArtifacts.manifestPath,
|
|
1090
|
-
{ name: `${config.backendDir || "backend"}/${PYTHON_RUNTIME_MANIFEST}` }
|
|
1091
|
-
);
|
|
1092
|
-
}
|
|
1093
|
-
if (pythonVendorArtifacts) {
|
|
1094
|
-
logPackStep(`\u5199\u5165 Python vendor \u4F9D\u8D56\u5230\u538B\u7F29\u5305\uFF1A${config.backendDir || "backend"}/${PYTHON_VENDOR_DIRNAME}`);
|
|
1095
|
-
archive.directory(
|
|
1096
|
-
pythonVendorArtifacts.vendorDir,
|
|
1097
|
-
`${config.backendDir || "backend"}/${PYTHON_VENDOR_DIRNAME}`
|
|
1098
|
-
);
|
|
1099
|
-
archive.file(
|
|
1100
|
-
pythonVendorArtifacts.manifestPath,
|
|
1101
|
-
{ name: `${config.backendDir || "backend"}/${PYTHON_VENDOR_MANIFEST}` }
|
|
1102
|
-
);
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
spinnerPack.text = `\u538B\u7F29 ${config.type === "agent" ? "Agent" : "\u63D2\u4EF6"} \u6587\u4EF6\u4E2D...`;
|
|
1107
|
-
await archive.finalize();
|
|
1108
|
-
await archivePromise;
|
|
1109
|
-
const fileSize = await getFileSize(outputPath);
|
|
1110
|
-
spinnerPack.succeed(`${config.type === "agent" ? "Agent" : "\u63D2\u4EF6"}\u5305\u521B\u5EFA\u5B8C\u6210`);
|
|
1111
|
-
console.log(chalk.green(`
|
|
1112
|
-
\u2705 \u5DF2\u521B\u5EFA ${chalk.bold(outputFileName)} (${fileSize})
|
|
1113
|
-
`));
|
|
1114
|
-
console.log(chalk.cyan("\u5B89\u88C5\u65B9\u5F0F:\n"));
|
|
1115
|
-
console.log(" 1. \u6253\u5F00 SeaStudio");
|
|
1116
|
-
console.log(` 2. \u8BBE\u7F6E \u2192 ${config.type === "agent" ? "Agent \u7BA1\u7406" : "\u63D2\u4EF6\u7BA1\u7406"} \u2192 ${config.type === "agent" ? "\u5BFC\u5165 Agent" : "\u6DFB\u52A0\u63D2\u4EF6"}`);
|
|
1117
|
-
console.log(` 3. \u9009\u62E9 ${chalk.bold(outputFileName)} \u6587\u4EF6
|
|
1118
|
-
`);
|
|
1119
|
-
} catch (error) {
|
|
1120
|
-
spinnerPack.fail("\u6253\u5305\u5931\u8D25");
|
|
1121
|
-
console.error(chalk.red(`
|
|
1122
|
-
\u274C ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}
|
|
1123
|
-
`));
|
|
1124
|
-
process.exit(1);
|
|
1125
|
-
} finally {
|
|
1126
|
-
if (pythonRuntimeArtifacts) {
|
|
1127
|
-
await rm(join(pythonRuntimeArtifacts.runtimeDir, ".."), { recursive: true, force: true }).catch(() => {
|
|
1128
|
-
});
|
|
1129
|
-
}
|
|
1130
|
-
if (pythonVendorArtifacts) {
|
|
1131
|
-
await rm(join(pythonVendorArtifacts.vendorDir, ".."), { recursive: true, force: true }).catch(() => {
|
|
1132
|
-
});
|
|
1133
|
-
}
|
|
1134
|
-
}
|
|
1135
|
-
});
|
|
1136
|
-
var installCommand = new Command("install").description("\u5B89\u88C5 .seaplugin \u6216 .seaagent \u6587\u4EF6\u5230\u6307\u5B9A\u76EE\u5F55").argument("<archive>", ".seaplugin \u6216 .seaagent \u6587\u4EF6\u8DEF\u5F84").option("--plugins-dir <dir>", "\u63D2\u4EF6\u5B89\u88C5\u76EE\u5F55").option("--agents-dir <dir>", "Agent \u5B89\u88C5\u76EE\u5F55").option("--force", "\u5F3A\u5236\u8986\u76D6\u5DF2\u5B58\u5728\u7684\u9879\u76EE", false).action(async (archivePath, options) => {
|
|
1137
|
-
try {
|
|
1138
|
-
if (!existsSync(archivePath)) {
|
|
1139
|
-
console.error(`\u274C \u6587\u4EF6\u4E0D\u5B58\u5728: ${archivePath}`);
|
|
1140
|
-
process.exit(1);
|
|
1141
|
-
}
|
|
1142
|
-
const isPlugin = archivePath.endsWith(".seaplugin");
|
|
1143
|
-
const isAgent = archivePath.endsWith(".seaagent");
|
|
1144
|
-
if (!isPlugin && !isAgent) {
|
|
1145
|
-
console.error("\u274C \u6587\u4EF6\u5FC5\u987B\u662F .seaplugin \u6216 .seaagent \u683C\u5F0F");
|
|
1146
|
-
process.exit(1);
|
|
1147
|
-
}
|
|
1148
|
-
const targetRoot = isPlugin ? options.pluginsDir : options.agentsDir;
|
|
1149
|
-
if (!targetRoot) {
|
|
1150
|
-
console.error(`\u274C \u5FC5\u987B\u6307\u5B9A --${isPlugin ? "plugins-dir" : "agents-dir"} \u53C2\u6570`);
|
|
1151
|
-
process.exit(1);
|
|
1152
|
-
}
|
|
1153
|
-
const tempDir = join(targetRoot, ".temp-install-" + Date.now());
|
|
1154
|
-
mkdirSync(tempDir, { recursive: true });
|
|
1155
|
-
console.log(`\u{1F4E6} \u89E3\u538B\u5B89\u88C5\u5305: ${basename(archivePath)}`);
|
|
1156
|
-
await new Promise((resolve4, reject) => {
|
|
1157
|
-
createReadStream(archivePath).pipe(Extract({ path: tempDir })).on("close", resolve4).on("error", reject);
|
|
1158
|
-
});
|
|
1159
|
-
const configPath = join(tempDir, "seastudio.config.json");
|
|
1160
|
-
if (!existsSync(configPath)) {
|
|
1161
|
-
await rm(tempDir, { recursive: true, force: true });
|
|
1162
|
-
console.error("\u274C \u65E0\u6548\u7684\u5B89\u88C5\u5305: \u7F3A\u5C11 seastudio.config.json");
|
|
1163
|
-
process.exit(1);
|
|
1164
|
-
}
|
|
1165
|
-
const configContent = await readFile(configPath, "utf-8");
|
|
1166
|
-
const config = JSON.parse(configContent);
|
|
1167
|
-
if (!config.id || !config.protocolHost) {
|
|
1168
|
-
await rm(tempDir, { recursive: true, force: true });
|
|
1169
|
-
console.error("\u274C \u65E0\u6548\u7684\u914D\u7F6E: \u7F3A\u5C11 id \u6216 protocolHost");
|
|
1170
|
-
process.exit(1);
|
|
1171
|
-
}
|
|
1172
|
-
const targetDir = join(targetRoot, config.protocolHost);
|
|
1173
|
-
if (existsSync(targetDir)) {
|
|
1174
|
-
if (!options.force) {
|
|
1175
|
-
await rm(tempDir, { recursive: true, force: true });
|
|
1176
|
-
console.log(`\u26A0\uFE0F \u5DF2\u5B58\u5728: ${config.protocolHost}\uFF0C\u4F7F\u7528 --force \u8986\u76D6`);
|
|
1177
|
-
process.exit(0);
|
|
1178
|
-
}
|
|
1179
|
-
console.log(`\u{1F504} \u8986\u76D6\u5DF2\u5B58\u5728\u7684\u9879\u76EE: ${config.protocolHost}`);
|
|
1180
|
-
await rm(targetDir, { recursive: true, force: true });
|
|
1181
|
-
}
|
|
1182
|
-
const { rename } = await import('fs/promises');
|
|
1183
|
-
await rename(tempDir, targetDir);
|
|
1184
|
-
console.log(`\u2705 ${isPlugin ? "\u63D2\u4EF6" : "Agent"}\u5B89\u88C5\u6210\u529F: ${config.name} (${config.protocolHost})`);
|
|
1185
|
-
console.log(` \u4F4D\u7F6E: ${targetDir}`);
|
|
1186
|
-
} catch (error) {
|
|
1187
|
-
console.error("\u274C \u5B89\u88C5\u5931\u8D25:", error instanceof Error ? error.message : error);
|
|
1188
|
-
process.exit(1);
|
|
1189
|
-
}
|
|
1190
|
-
});
|
|
1191
|
-
|
|
1192
|
-
// src/develop-tool/cli/index.ts
|
|
1193
|
-
var program = new Command();
|
|
1194
|
-
program.name("seastudio").description("SeaStudio Plugin Development CLI").version("1.0.0");
|
|
1195
|
-
program.addCommand(createCommand);
|
|
1196
|
-
program.addCommand(generateMcpManifestCommand);
|
|
1197
|
-
program.addCommand(packCommand);
|
|
1198
|
-
program.addCommand(installCommand);
|
|
1199
|
-
program.parse();
|