@easytwin/devkit 0.1.0 → 0.1.1
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 -5
- package/dist/bin.js +293 -20
- package/dist/bin.js.map +1 -1
- package/dist/index.d.ts +126 -10
- package/dist/index.js +444 -17
- package/dist/index.js.map +1 -1
- package/dist/runtime-types/index.d.ts +8243 -0
- package/package.json +20 -5
- package/scene.example.json +2663 -0
- package/skills/easytwin-bootstrap/SKILL.md +7 -2
- package/skills/easytwin-core/references/engine.md +3 -3
- package/skills/easytwin-develop/SKILL.md +6 -2
- package/skills/easytwin-render/SKILL.md +20 -19
- package/skills/easytwin-render/references/intro.md +5 -6
- package/skills/easytwin-render/references/scene-and-assets.md +4 -3
- package/skills/easytwin-scene/SKILL.md +33 -5
- package/skills/easytwin-upload/SKILL.md +4 -0
package/dist/index.js
CHANGED
|
@@ -2,7 +2,14 @@
|
|
|
2
2
|
import { promises as fs } from "fs";
|
|
3
3
|
import path from "path";
|
|
4
4
|
var CONFIG_FILE_NAME = "easytwin.config.json";
|
|
5
|
-
var DEFAULT_BASE_URL = "
|
|
5
|
+
var DEFAULT_BASE_URL = "http://saas-twin.k8s.dtstack.cn/";
|
|
6
|
+
var TEST_BASE_URL = "http://saas-twin-test.k8s.dtstack.cn/";
|
|
7
|
+
var DEFAULT_OSS_URL = "https://dt-easyv-test.oss-cn-hangzhou.aliyuncs.com/";
|
|
8
|
+
var MOCK_APP_ID = "test";
|
|
9
|
+
var MOCK_APP_SECRET = "test";
|
|
10
|
+
function isMockCredentials(appId, appSecret) {
|
|
11
|
+
return appId === MOCK_APP_ID && appSecret === MOCK_APP_SECRET;
|
|
12
|
+
}
|
|
6
13
|
var ConfigError = class extends Error {
|
|
7
14
|
constructor(message) {
|
|
8
15
|
super(message);
|
|
@@ -35,8 +42,16 @@ function validateConfigShape(value) {
|
|
|
35
42
|
if (v.baseUrl !== void 0 && (typeof v.baseUrl !== "string" || v.baseUrl.length === 0)) {
|
|
36
43
|
throw new ConfigError("baseUrl \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");
|
|
37
44
|
}
|
|
45
|
+
if (v.ossUrl !== void 0 && (typeof v.ossUrl !== "string" || v.ossUrl.length === 0)) {
|
|
46
|
+
throw new ConfigError("ossUrl \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");
|
|
47
|
+
}
|
|
48
|
+
if (v.env !== void 0 && v.env !== "prod" && v.env !== "test") {
|
|
49
|
+
throw new ConfigError("env \u5FC5\u987B\u662F prod \u6216 test");
|
|
50
|
+
}
|
|
38
51
|
const config = { appId: v.appId, appSecret: v.appSecret };
|
|
52
|
+
if (v.env === "prod" || v.env === "test") config.env = v.env;
|
|
39
53
|
if (typeof v.baseUrl === "string") config.baseUrl = v.baseUrl;
|
|
54
|
+
if (typeof v.ossUrl === "string") config.ossUrl = v.ossUrl;
|
|
40
55
|
return config;
|
|
41
56
|
}
|
|
42
57
|
async function readConfigFile(cwd) {
|
|
@@ -52,11 +67,13 @@ async function readConfigFile(cwd) {
|
|
|
52
67
|
function resolveConfig(file, env = process.env) {
|
|
53
68
|
const appId = env.EASYTWIN_APP_ID ?? file.appId;
|
|
54
69
|
const appSecret = env.EASYTWIN_APP_SECRET ?? file.appSecret;
|
|
55
|
-
const
|
|
70
|
+
const easyEnv = env.EASYTWIN_ENV === "test" || env.EASYTWIN_ENV === "prod" ? env.EASYTWIN_ENV : file.env ?? "prod";
|
|
71
|
+
const baseUrl = env.EASYTWIN_BASE_URL ?? file.baseUrl ?? (easyEnv === "test" ? TEST_BASE_URL : DEFAULT_BASE_URL);
|
|
72
|
+
const ossUrl = env.EASYTWIN_OSS_URL ?? file.ossUrl ?? DEFAULT_OSS_URL;
|
|
56
73
|
if (!appId) throw new ConfigError("\u7F3A\u5C11 appId:\u914D\u7F6E\u6587\u4EF6\u4E2D\u672A\u63D0\u4F9B,\u4E14\u672A\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF EASYTWIN_APP_ID");
|
|
57
74
|
if (!appSecret) throw new ConfigError("\u7F3A\u5C11 appSecret:\u914D\u7F6E\u6587\u4EF6\u4E2D\u672A\u63D0\u4F9B,\u4E14\u672A\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF EASYTWIN_APP_SECRET");
|
|
58
75
|
const source = env.EASYTWIN_APP_ID !== void 0 || env.EASYTWIN_APP_SECRET !== void 0 ? "env" : "file";
|
|
59
|
-
return { appId, appSecret, baseUrl, source };
|
|
76
|
+
return { appId, appSecret, baseUrl, ossUrl, mock: isMockCredentials(appId, appSecret), source };
|
|
60
77
|
}
|
|
61
78
|
async function loadConfig(cwd, env = process.env) {
|
|
62
79
|
return resolveConfig(await readConfigFile(cwd), env);
|
|
@@ -64,7 +81,9 @@ async function loadConfig(cwd, env = process.env) {
|
|
|
64
81
|
async function writeConfigFile(cwd, config) {
|
|
65
82
|
const file = configFilePath(cwd);
|
|
66
83
|
const body = { appId: config.appId, appSecret: config.appSecret };
|
|
84
|
+
if (config.env) body.env = config.env;
|
|
67
85
|
if (config.baseUrl) body.baseUrl = config.baseUrl;
|
|
86
|
+
if (config.ossUrl) body.ossUrl = config.ossUrl;
|
|
68
87
|
await fs.mkdir(cwd, { recursive: true });
|
|
69
88
|
await fs.writeFile(file, JSON.stringify(body, null, 2) + "\n", "utf8");
|
|
70
89
|
return file;
|
|
@@ -133,10 +152,13 @@ function parseResponseBody(buffer) {
|
|
|
133
152
|
}
|
|
134
153
|
var EasyTwinClient = class {
|
|
135
154
|
baseUrl;
|
|
155
|
+
/** 本地测试模式:true 时 scene/upload 走本地 mock,不发网络请求(见 config.ts)。 */
|
|
156
|
+
mock;
|
|
136
157
|
appId;
|
|
137
158
|
appSecret;
|
|
138
159
|
constructor(config) {
|
|
139
160
|
this.baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
161
|
+
this.mock = config.mock;
|
|
140
162
|
this.appId = config.appId;
|
|
141
163
|
this.appSecret = config.appSecret;
|
|
142
164
|
}
|
|
@@ -148,8 +170,8 @@ var EasyTwinClient = class {
|
|
|
148
170
|
};
|
|
149
171
|
}
|
|
150
172
|
/** JSON 请求(原生 fetch)。 */
|
|
151
|
-
async request(
|
|
152
|
-
const url = `${this.baseUrl}${
|
|
173
|
+
async request(path6, options = {}) {
|
|
174
|
+
const url = `${this.baseUrl}${path6}`;
|
|
153
175
|
const headers = { ...this.authHeaders(), ...options.headers };
|
|
154
176
|
if (options.body !== void 0 && options.body !== null && !("Content-Type" in headers)) {
|
|
155
177
|
headers["Content-Type"] = "application/json";
|
|
@@ -171,8 +193,8 @@ var EasyTwinClient = class {
|
|
|
171
193
|
* multipart 流式上传(手写 http 请求以支持逐字节进度回调)。
|
|
172
194
|
* 这是 fetch 上传的替代路径:原生 fetch 无法上报上传进度。
|
|
173
195
|
*/
|
|
174
|
-
async upload(
|
|
175
|
-
const url = new URL(`${this.baseUrl}${
|
|
196
|
+
async upload(path6, options) {
|
|
197
|
+
const url = new URL(`${this.baseUrl}${path6}`);
|
|
176
198
|
const mod = url.protocol === "https:" ? https : http;
|
|
177
199
|
const headers = {
|
|
178
200
|
...this.authHeaders(),
|
|
@@ -214,6 +236,7 @@ var EasyTwinClient = class {
|
|
|
214
236
|
// src/scene.ts
|
|
215
237
|
import { promises as fs2 } from "fs";
|
|
216
238
|
import path2 from "path";
|
|
239
|
+
import { fileURLToPath } from "url";
|
|
217
240
|
function normalizeSceneList(data) {
|
|
218
241
|
const list = Array.isArray(data) ? data : data?.list;
|
|
219
242
|
if (!Array.isArray(list)) throw new Error("scene list \u54CD\u5E94\u7ED3\u6784\u65E0\u6CD5\u8BC6\u522B");
|
|
@@ -226,11 +249,50 @@ function normalizeSceneDetail(data) {
|
|
|
226
249
|
const it = data ?? {};
|
|
227
250
|
return { id: String(it.id ?? ""), name: String(it.name ?? ""), payload: data };
|
|
228
251
|
}
|
|
229
|
-
|
|
252
|
+
var EXAMPLE_SCENE_FILE = "scene.example.json";
|
|
253
|
+
var MOCK_SCENE_NAME = "\u672C\u5730\u793A\u4F8B\u573A\u666F";
|
|
254
|
+
function resolveExampleScenePath() {
|
|
255
|
+
if (typeof import.meta.url !== "string" || import.meta.url.length === 0) {
|
|
256
|
+
throw new Error(`\u65E0\u6CD5\u5B9A\u4F4D ${EXAMPLE_SCENE_FILE}:\u63D2\u4EF6(bundle/CJS)\u573A\u666F\u5FC5\u987B\u663E\u5F0F\u4F20\u5165 exampleFile`);
|
|
257
|
+
}
|
|
258
|
+
return path2.resolve(path2.dirname(fileURLToPath(import.meta.url)), "..", EXAMPLE_SCENE_FILE);
|
|
259
|
+
}
|
|
260
|
+
async function loadExampleScene(exampleFile) {
|
|
261
|
+
const file = exampleFile ?? resolveExampleScenePath();
|
|
262
|
+
let raw;
|
|
263
|
+
try {
|
|
264
|
+
raw = await fs2.readFile(file, "utf8");
|
|
265
|
+
} catch {
|
|
266
|
+
throw new Error(`\u65E0\u6CD5\u8BFB\u53D6\u672C\u5730\u793A\u4F8B\u573A\u666F ${file}(\u672C\u5730\u6D4B\u8BD5\u6A21\u5F0F\u9700\u8981 devkit \u5305\u5185\u7684 ${EXAMPLE_SCENE_FILE})`);
|
|
267
|
+
}
|
|
268
|
+
try {
|
|
269
|
+
return JSON.parse(raw);
|
|
270
|
+
} catch {
|
|
271
|
+
throw new Error(`\u672C\u5730\u793A\u4F8B\u573A\u666F\u4E0D\u662F\u5408\u6CD5 JSON:${file}`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
function deriveExampleSceneId(payload) {
|
|
275
|
+
const root = payload ?? {};
|
|
276
|
+
const id = root?.objs?.map((o) => o.sceneId).find((s) => typeof s === "string" && s.length > 0);
|
|
277
|
+
return id ?? "local";
|
|
278
|
+
}
|
|
279
|
+
async function exampleSceneSummary(exampleFile) {
|
|
280
|
+
return { id: deriveExampleSceneId(await loadExampleScene(exampleFile)), name: MOCK_SCENE_NAME };
|
|
281
|
+
}
|
|
282
|
+
async function listScenes(client, options = {}) {
|
|
283
|
+
if (client.mock) return [await exampleSceneSummary(options.exampleFile)];
|
|
230
284
|
const data = await client.request(ENDPOINTS.scenes, { method: "GET" });
|
|
231
285
|
return normalizeSceneList(data);
|
|
232
286
|
}
|
|
233
|
-
async function pullScene(client, id) {
|
|
287
|
+
async function pullScene(client, id, options = {}) {
|
|
288
|
+
if (client.mock) {
|
|
289
|
+
const payload = await loadExampleScene(options.exampleFile);
|
|
290
|
+
const sceneId = deriveExampleSceneId(payload);
|
|
291
|
+
if (sceneId !== id) {
|
|
292
|
+
throw new Error(`\u672C\u5730\u6D4B\u8BD5\u6A21\u5F0F:\u793A\u4F8B\u573A\u666F id \u4E3A ${sceneId},\u6536\u5230 ${id}(\u672C\u5730\u4EC5\u63D0\u4F9B ${EXAMPLE_SCENE_FILE} \u8FD9\u4E00\u4E2A\u573A\u666F)`);
|
|
293
|
+
}
|
|
294
|
+
return { id: sceneId, name: MOCK_SCENE_NAME, payload };
|
|
295
|
+
}
|
|
234
296
|
const data = await client.request(ENDPOINTS.scene(id), { method: "GET" });
|
|
235
297
|
return normalizeSceneDetail(data);
|
|
236
298
|
}
|
|
@@ -239,6 +301,56 @@ async function saveSceneJson(scene, out) {
|
|
|
239
301
|
await fs2.writeFile(out, JSON.stringify(scene.payload ?? scene, null, 2) + "\n", "utf8");
|
|
240
302
|
return out;
|
|
241
303
|
}
|
|
304
|
+
function parseSceneStructure(scene) {
|
|
305
|
+
let data = scene;
|
|
306
|
+
if (typeof data === "string") {
|
|
307
|
+
try {
|
|
308
|
+
data = JSON.parse(data);
|
|
309
|
+
} catch {
|
|
310
|
+
return [];
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
const root = data ?? {};
|
|
314
|
+
const objs = Array.isArray(root.objs) ? root.objs : [];
|
|
315
|
+
const hierarchy = Array.isArray(root.hierarchy) ? root.hierarchy : [];
|
|
316
|
+
const items = objs.length > 0 ? objs : hierarchy;
|
|
317
|
+
const typeById = /* @__PURE__ */ new Map();
|
|
318
|
+
for (const o of objs) {
|
|
319
|
+
if (typeof o.id === "string" && typeof o.type === "string") typeById.set(o.id, o.type);
|
|
320
|
+
}
|
|
321
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
322
|
+
const order = [];
|
|
323
|
+
for (const item of items) {
|
|
324
|
+
const id = typeof item.id === "string" ? item.id : "";
|
|
325
|
+
if (id.length === 0 || nodes.has(id)) continue;
|
|
326
|
+
const name = typeof item.name === "string" && item.name.length > 0 ? item.name : id;
|
|
327
|
+
nodes.set(id, { id, name, type: typeById.get(id), children: [] });
|
|
328
|
+
order.push(id);
|
|
329
|
+
}
|
|
330
|
+
const childrenOf = /* @__PURE__ */ new Map();
|
|
331
|
+
for (const item of items) {
|
|
332
|
+
const id = typeof item.id === "string" ? item.id : "";
|
|
333
|
+
if (id.length === 0 || !nodes.has(id)) continue;
|
|
334
|
+
const parentId = typeof item.parentObjId === "string" && item.parentObjId.length > 0 ? item.parentObjId : "";
|
|
335
|
+
if (parentId.length === 0 || parentId === id || !nodes.has(parentId)) continue;
|
|
336
|
+
const kids = childrenOf.get(parentId) ?? [];
|
|
337
|
+
kids.push(id);
|
|
338
|
+
childrenOf.set(parentId, kids);
|
|
339
|
+
}
|
|
340
|
+
const isChild = /* @__PURE__ */ new Set();
|
|
341
|
+
for (const kids of childrenOf.values()) for (const k of kids) isChild.add(k);
|
|
342
|
+
const build2 = (id, seen) => {
|
|
343
|
+
const node = nodes.get(id);
|
|
344
|
+
const children = [];
|
|
345
|
+
if (!seen.has(id)) {
|
|
346
|
+
seen.add(id);
|
|
347
|
+
for (const kid of childrenOf.get(id) ?? []) children.push(build2(kid, seen));
|
|
348
|
+
seen.delete(id);
|
|
349
|
+
}
|
|
350
|
+
return { ...node, children };
|
|
351
|
+
};
|
|
352
|
+
return order.filter((id) => !isChild.has(id)).map((id) => build2(id, /* @__PURE__ */ new Set()));
|
|
353
|
+
}
|
|
242
354
|
|
|
243
355
|
// src/upload.ts
|
|
244
356
|
import { promises as fs3 } from "fs";
|
|
@@ -292,7 +404,16 @@ Content-Type: application/octet-stream\r
|
|
|
292
404
|
`, "utf8"));
|
|
293
405
|
return Buffer.concat(parts);
|
|
294
406
|
}
|
|
407
|
+
async function mockUpload(dir, options) {
|
|
408
|
+
const ignore = options.ignore ?? (() => false);
|
|
409
|
+
const files = await collectFiles(dir, ignore);
|
|
410
|
+
const total = files.reduce((sum, f) => sum + f.size, 0);
|
|
411
|
+
options.onProgress?.({ phase: "collect", current: total, total });
|
|
412
|
+
options.onProgress?.({ phase: "upload", current: total, total });
|
|
413
|
+
return { fileCount: files.length, byteCount: total, mock: true };
|
|
414
|
+
}
|
|
295
415
|
async function uploadDirectory(client, dir, options = {}) {
|
|
416
|
+
if (client.mock) return mockUpload(dir, options);
|
|
296
417
|
const ignore = options.ignore ?? (() => false);
|
|
297
418
|
const files = await collectFiles(dir, ignore);
|
|
298
419
|
const total = files.reduce((sum, f) => sum + f.size, 0);
|
|
@@ -315,9 +436,9 @@ async function uploadDirectory(client, dir, options = {}) {
|
|
|
315
436
|
}
|
|
316
437
|
|
|
317
438
|
// src/skills.ts
|
|
318
|
-
import { promises as fs4 } from "fs";
|
|
439
|
+
import { existsSync, promises as fs4 } from "fs";
|
|
319
440
|
import path4 from "path";
|
|
320
|
-
import { fileURLToPath } from "url";
|
|
441
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
321
442
|
var SKILL_NAMES = [
|
|
322
443
|
"easytwin-render",
|
|
323
444
|
"easytwin-core",
|
|
@@ -329,8 +450,48 @@ var SKILL_NAMES = [
|
|
|
329
450
|
var CODEX_MARKER_BEGIN = "<!-- EASYTWIN-SKILLS:BEGIN -->";
|
|
330
451
|
var CODEX_MARKER_END = "<!-- EASYTWIN-SKILLS:END -->";
|
|
331
452
|
var META_FILE_NAME = ".easytwin-meta.json";
|
|
453
|
+
var EASYTWIN_TYPES_DIR = ".easytwin/types";
|
|
454
|
+
var MINIMAL_TSCONFIG = `${JSON.stringify(
|
|
455
|
+
{
|
|
456
|
+
compilerOptions: {
|
|
457
|
+
target: "ES2022",
|
|
458
|
+
module: "ESNext",
|
|
459
|
+
moduleResolution: "bundler",
|
|
460
|
+
strict: true,
|
|
461
|
+
skipLibCheck: true,
|
|
462
|
+
noEmit: true,
|
|
463
|
+
paths: {
|
|
464
|
+
"@easytwin/runtime": [".easytwin/types"]
|
|
465
|
+
}
|
|
466
|
+
},
|
|
467
|
+
include: ["src/**/*.ts"]
|
|
468
|
+
},
|
|
469
|
+
null,
|
|
470
|
+
2
|
|
471
|
+
)}
|
|
472
|
+
`;
|
|
473
|
+
var TSCONFIG_PATHS_HINT = `\u5DF2\u6709 tsconfig.json,\u672A\u6539\u52A8\u3002\u8BF7\u5728 compilerOptions \u4E2D\u52A0\u5165:
|
|
474
|
+
"skipLibCheck": true,
|
|
475
|
+
"paths": {
|
|
476
|
+
"@easytwin/runtime": [".easytwin/types"]
|
|
477
|
+
}`;
|
|
478
|
+
var RUN_CONTEXT_DECL = `
|
|
479
|
+
/** \u9884\u89C8\u9875 Run \u6309\u94AE\u6CE8\u5165\u7684\u8FD0\u884C\u4E0A\u4E0B\u6587(\u89C1 D12)\u3002 */
|
|
480
|
+
export interface EasyTwinRunContext {
|
|
481
|
+
engine: RuntimeEngine;
|
|
482
|
+
runtime: typeof import("@easytwin/runtime");
|
|
483
|
+
sceneJson: SceneJson;
|
|
484
|
+
}
|
|
485
|
+
`;
|
|
486
|
+
function buildRuntimeTypesContent(sourceDts) {
|
|
487
|
+
const trimmed = sourceDts.replace(/\s+$/, "");
|
|
488
|
+
if (trimmed.includes("export interface EasyTwinRunContext")) return `${trimmed}
|
|
489
|
+
`;
|
|
490
|
+
return `${trimmed}
|
|
491
|
+
${RUN_CONTEXT_DECL}`;
|
|
492
|
+
}
|
|
332
493
|
function normalizeTargets(target = "all") {
|
|
333
|
-
if (target === "all") return ["cursor", "claude", "codex"];
|
|
494
|
+
if (target === "all") return ["cursor", "claude", "codex", "qoder"];
|
|
334
495
|
if (Array.isArray(target)) return [...new Set(target)];
|
|
335
496
|
return [target];
|
|
336
497
|
}
|
|
@@ -338,9 +499,20 @@ function resolveSkillsSourceDir() {
|
|
|
338
499
|
if (typeof import.meta.url !== "string" || import.meta.url.length === 0) {
|
|
339
500
|
throw new Error("\u65E0\u6CD5\u5B9A\u4F4D skills \u6E90\u76EE\u5F55:\u63D2\u4EF6(bundle/CJS)\u573A\u666F\u5FC5\u987B\u663E\u5F0F\u4F20\u5165 sourceDir");
|
|
340
501
|
}
|
|
341
|
-
const here = path4.dirname(
|
|
502
|
+
const here = path4.dirname(fileURLToPath2(import.meta.url));
|
|
342
503
|
return path4.resolve(here, "..", "skills");
|
|
343
504
|
}
|
|
505
|
+
function resolveRuntimeTypesSourceFile() {
|
|
506
|
+
if (typeof import.meta.url !== "string" || import.meta.url.length === 0) {
|
|
507
|
+
throw new Error("\u65E0\u6CD5\u5B9A\u4F4D runtime \u7C7B\u578B:\u63D2\u4EF6(bundle/CJS)\u573A\u666F\u5FC5\u987B\u663E\u5F0F\u4F20\u5165 typesSourceFile");
|
|
508
|
+
}
|
|
509
|
+
const here = path4.dirname(fileURLToPath2(import.meta.url));
|
|
510
|
+
const fromDist = path4.join(here, "runtime-types", "index.d.ts");
|
|
511
|
+
const fromSrc = path4.resolve(here, "lib", "index.d.ts");
|
|
512
|
+
if (existsSync(fromDist)) return fromDist;
|
|
513
|
+
if (existsSync(fromSrc)) return fromSrc;
|
|
514
|
+
throw new Error(`\u65E0\u6CD5\u5B9A\u4F4D runtime \u7C7B\u578B\u58F0\u660E,\u5DF2\u5C1D\u8BD5:${fromDist}, ${fromSrc}`);
|
|
515
|
+
}
|
|
344
516
|
async function readJson(file) {
|
|
345
517
|
return JSON.parse(await fs4.readFile(file, "utf8"));
|
|
346
518
|
}
|
|
@@ -465,6 +637,38 @@ async function syncToCodex(sourceRoot, cwd, version) {
|
|
|
465
637
|
}
|
|
466
638
|
return { target: "codex", entries: [], codex: { name: "AGENTS.md", action } };
|
|
467
639
|
}
|
|
640
|
+
async function syncRuntimeTypes(cwd, typesSourceFile) {
|
|
641
|
+
const destDir = path4.join(cwd, ".easytwin", "types");
|
|
642
|
+
const destFile = path4.join(destDir, "index.d.ts");
|
|
643
|
+
const content = buildRuntimeTypesContent(await fs4.readFile(typesSourceFile, "utf8"));
|
|
644
|
+
let exists = true;
|
|
645
|
+
let current = "";
|
|
646
|
+
try {
|
|
647
|
+
current = await fs4.readFile(destFile, "utf8");
|
|
648
|
+
} catch {
|
|
649
|
+
exists = false;
|
|
650
|
+
}
|
|
651
|
+
const action = actionFor(exists, current === content);
|
|
652
|
+
if (action !== "unchanged") {
|
|
653
|
+
await fs4.mkdir(destDir, { recursive: true });
|
|
654
|
+
await fs4.writeFile(destFile, content, "utf8");
|
|
655
|
+
}
|
|
656
|
+
const tsconfigPath = path4.join(cwd, "tsconfig.json");
|
|
657
|
+
let tsconfig;
|
|
658
|
+
let pathsHint;
|
|
659
|
+
try {
|
|
660
|
+
const existing = await fs4.readFile(tsconfigPath, "utf8");
|
|
661
|
+
if (existing === MINIMAL_TSCONFIG) tsconfig = "unchanged";
|
|
662
|
+
else {
|
|
663
|
+
tsconfig = "manual-paths";
|
|
664
|
+
pathsHint = TSCONFIG_PATHS_HINT;
|
|
665
|
+
}
|
|
666
|
+
} catch {
|
|
667
|
+
await fs4.writeFile(tsconfigPath, MINIMAL_TSCONFIG, "utf8");
|
|
668
|
+
tsconfig = "created";
|
|
669
|
+
}
|
|
670
|
+
return { action, tsconfig, pathsHint };
|
|
671
|
+
}
|
|
468
672
|
async function syncSkills(options) {
|
|
469
673
|
const targets = normalizeTargets(options.targets ?? "all");
|
|
470
674
|
const sourceRoot = options.sourceDir ?? resolveSkillsSourceDir();
|
|
@@ -473,16 +677,24 @@ async function syncSkills(options) {
|
|
|
473
677
|
for (const target of targets) {
|
|
474
678
|
if (target === "cursor") summaries.push(await syncToDir("cursor", sourceRoot, path4.join(options.cwd, ".cursor", "skills"), version));
|
|
475
679
|
else if (target === "claude") summaries.push(await syncToDir("claude", sourceRoot, path4.join(options.cwd, ".claude", "skills"), version));
|
|
680
|
+
else if (target === "qoder") summaries.push(await syncToDir("qoder", sourceRoot, path4.join(options.cwd, ".qoder", "skills"), version));
|
|
476
681
|
else summaries.push(await syncToCodex(sourceRoot, options.cwd, version));
|
|
477
682
|
}
|
|
478
|
-
|
|
683
|
+
const types = await syncRuntimeTypes(options.cwd, options.typesSourceFile ?? resolveRuntimeTypesSourceFile());
|
|
684
|
+
return { summaries, types };
|
|
479
685
|
}
|
|
480
686
|
async function detectSkillsStatus(cwd, sourceDir) {
|
|
481
687
|
const sourceRoot = sourceDir ?? resolveSkillsSourceDir();
|
|
482
688
|
const version = await readDevkitVersion(sourceRoot);
|
|
483
689
|
const targets = [];
|
|
484
|
-
|
|
485
|
-
|
|
690
|
+
const dirTargets = ["cursor", "claude", "qoder"];
|
|
691
|
+
const DIR_ROOTS = {
|
|
692
|
+
cursor: ".cursor/skills",
|
|
693
|
+
claude: ".claude/skills",
|
|
694
|
+
qoder: ".qoder/skills"
|
|
695
|
+
};
|
|
696
|
+
for (const target of dirTargets) {
|
|
697
|
+
const root = path4.join(cwd, DIR_ROOTS[target]);
|
|
486
698
|
const missing = [];
|
|
487
699
|
for (const name of SKILL_NAMES) {
|
|
488
700
|
if (!await fs4.stat(path4.join(root, name)).then(() => true).catch(() => false)) missing.push(name);
|
|
@@ -507,43 +719,258 @@ async function detectSkillsStatus(cwd, sourceDir) {
|
|
|
507
719
|
}
|
|
508
720
|
const hasCodex = agentsContent.includes(CODEX_MARKER_BEGIN) && agentsContent.includes(CODEX_MARKER_END) && agentsContent.includes(`v${version}`);
|
|
509
721
|
targets.push({ target: "codex", synced: hasCodex, reason: hasCodex ? void 0 : "AGENTS.md \u7F3A\u5C11\u540C\u6B65\u6807\u8BB0\u6BB5" });
|
|
510
|
-
|
|
722
|
+
let typesSynced = false;
|
|
723
|
+
try {
|
|
724
|
+
const dts = await fs4.readFile(path4.join(cwd, ".easytwin", "types", "index.d.ts"), "utf8");
|
|
725
|
+
typesSynced = dts.includes("export interface EasyTwinRunContext");
|
|
726
|
+
} catch {
|
|
727
|
+
typesSynced = false;
|
|
728
|
+
}
|
|
729
|
+
return { cwd, targets, typesSynced };
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// src/bundle.ts
|
|
733
|
+
import * as esbuild from "esbuild-wasm";
|
|
734
|
+
import { promises as fs5 } from "fs";
|
|
735
|
+
import path5 from "path";
|
|
736
|
+
var USER_ENTRY = "src/main.ts";
|
|
737
|
+
var DEFAULT_BUNDLE_OUT = "dist/main.js";
|
|
738
|
+
var RUNTIME_MODULE = "@easytwin/runtime";
|
|
739
|
+
var BundleError = class extends Error {
|
|
740
|
+
constructor(message) {
|
|
741
|
+
super(message);
|
|
742
|
+
this.name = "BundleError";
|
|
743
|
+
}
|
|
744
|
+
};
|
|
745
|
+
function isRelativeOrAbsolute(spec) {
|
|
746
|
+
return spec.startsWith("./") || spec.startsWith("../") || spec.startsWith("/") || path5.isAbsolute(spec);
|
|
747
|
+
}
|
|
748
|
+
function whitelistPlugin() {
|
|
749
|
+
return {
|
|
750
|
+
name: "easytwin-whitelist",
|
|
751
|
+
setup(build2) {
|
|
752
|
+
build2.onResolve({ filter: /.*/ }, (args) => {
|
|
753
|
+
if (args.kind === "entry-point") return void 0;
|
|
754
|
+
if (isRelativeOrAbsolute(args.path)) return void 0;
|
|
755
|
+
if (args.path === RUNTIME_MODULE) return { path: args.path, external: true };
|
|
756
|
+
return {
|
|
757
|
+
errors: [
|
|
758
|
+
{
|
|
759
|
+
text: `\u68C0\u6D4B\u5230\u5916\u90E8\u4F9D\u8D56 ${args.path},\u7528\u6237\u4EE3\u7801\u53EA\u80FD\u4F9D\u8D56 @easytwin/runtime\u3002\u4E0D\u8981 npm install,\u7C7B\u578B\u7531 easytwin skills sync \u5206\u53D1,\u8FD0\u884C\u8D70\u9884\u89C8\u9875 Run \u6216 easytwin bundle\u3002`
|
|
760
|
+
}
|
|
761
|
+
]
|
|
762
|
+
};
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
function formatEsbuildMessages(messages) {
|
|
768
|
+
return messages.map((m) => {
|
|
769
|
+
const loc = m.location ? `${m.location.file}:${m.location.line}:${m.location.column}: ` : "";
|
|
770
|
+
return `${loc}${m.text}`;
|
|
771
|
+
}).join("\n");
|
|
772
|
+
}
|
|
773
|
+
async function bundleUserCode(options) {
|
|
774
|
+
const cwd = path5.resolve(options.cwd);
|
|
775
|
+
const entry = path5.join(cwd, USER_ENTRY);
|
|
776
|
+
try {
|
|
777
|
+
await fs5.access(entry);
|
|
778
|
+
} catch {
|
|
779
|
+
throw new BundleError(
|
|
780
|
+
`\u672A\u627E\u5230\u5165\u53E3\u6587\u4EF6 ${entry}\u3002\u8BF7\u5728\u5DE5\u4F5C\u533A\u521B\u5EFA src/main.ts,\u5E76 \`export default async function main(ctx)\`\u3002`
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
let result;
|
|
784
|
+
try {
|
|
785
|
+
result = await esbuild.build({
|
|
786
|
+
absWorkingDir: cwd,
|
|
787
|
+
entryPoints: [entry],
|
|
788
|
+
bundle: true,
|
|
789
|
+
write: false,
|
|
790
|
+
format: "esm",
|
|
791
|
+
platform: "browser",
|
|
792
|
+
target: "es2022",
|
|
793
|
+
sourcemap: "inline",
|
|
794
|
+
logLevel: "silent",
|
|
795
|
+
plugins: [whitelistPlugin()]
|
|
796
|
+
});
|
|
797
|
+
} catch (err) {
|
|
798
|
+
const errors = err.errors;
|
|
799
|
+
if (Array.isArray(errors) && errors.length > 0) {
|
|
800
|
+
throw new BundleError(formatEsbuildMessages(errors));
|
|
801
|
+
}
|
|
802
|
+
throw err instanceof Error ? new BundleError(err.message) : err;
|
|
803
|
+
}
|
|
804
|
+
if (result.errors.length > 0) {
|
|
805
|
+
throw new BundleError(formatEsbuildMessages(result.errors));
|
|
806
|
+
}
|
|
807
|
+
const file = result.outputFiles?.[0];
|
|
808
|
+
if (!file) throw new BundleError("\u6253\u5305\u672A\u4EA7\u51FA\u6587\u4EF6");
|
|
809
|
+
const code = file.text;
|
|
810
|
+
const warnings = result.warnings.map((w) => formatEsbuildMessages([w]));
|
|
811
|
+
if (options.outFile) {
|
|
812
|
+
const outFile = path5.isAbsolute(options.outFile) ? options.outFile : path5.join(cwd, options.outFile);
|
|
813
|
+
await fs5.mkdir(path5.dirname(outFile), { recursive: true });
|
|
814
|
+
await fs5.writeFile(outFile, code, "utf8");
|
|
815
|
+
}
|
|
816
|
+
return { code, warnings };
|
|
817
|
+
}
|
|
818
|
+
async function typesMissingHint(cwd) {
|
|
819
|
+
try {
|
|
820
|
+
await fs5.access(path5.join(cwd, ".easytwin", "types", "index.d.ts"));
|
|
821
|
+
return void 0;
|
|
822
|
+
} catch {
|
|
823
|
+
return "\u672A\u627E\u5230 .easytwin/types,\u8FD0\u884C `easytwin skills sync` \u4EE5\u5206\u53D1 @easytwin/runtime \u7C7B\u578B\u58F0\u660E\u3002";
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
var SOURCEMAP_RE = /sourceMappingURL=data:application\/json(?:;charset=[^;]+)?;base64,([A-Za-z0-9+/]+=*)/;
|
|
827
|
+
var VLQ_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
828
|
+
function decodeVLQValues(str) {
|
|
829
|
+
const values = [];
|
|
830
|
+
let i = 0;
|
|
831
|
+
while (i < str.length) {
|
|
832
|
+
let result = 0;
|
|
833
|
+
let shift = 0;
|
|
834
|
+
let continuation = true;
|
|
835
|
+
while (continuation) {
|
|
836
|
+
if (i >= str.length) return values;
|
|
837
|
+
const digit = VLQ_CHARS.indexOf(str[i++] ?? "");
|
|
838
|
+
if (digit < 0) return values;
|
|
839
|
+
continuation = (digit & 32) !== 0;
|
|
840
|
+
result += (digit & 31) << shift;
|
|
841
|
+
shift += 5;
|
|
842
|
+
}
|
|
843
|
+
values.push(result & 1 ? -(result >> 1) : result >> 1);
|
|
844
|
+
}
|
|
845
|
+
return values;
|
|
846
|
+
}
|
|
847
|
+
function decodeMappings(map) {
|
|
848
|
+
const sources = map.sources ?? [];
|
|
849
|
+
const lines = (map.mappings ?? "").split(";");
|
|
850
|
+
let sourceIndex = 0;
|
|
851
|
+
let originalLine = 0;
|
|
852
|
+
let originalColumn = 0;
|
|
853
|
+
const decoded = [];
|
|
854
|
+
for (const line of lines) {
|
|
855
|
+
let generatedColumn = 0;
|
|
856
|
+
const segs = [];
|
|
857
|
+
if (line) {
|
|
858
|
+
for (const raw of line.split(",")) {
|
|
859
|
+
if (!raw) continue;
|
|
860
|
+
const nums = decodeVLQValues(raw);
|
|
861
|
+
if (nums[0] === void 0) continue;
|
|
862
|
+
generatedColumn += nums[0];
|
|
863
|
+
if (nums.length >= 4) {
|
|
864
|
+
sourceIndex += nums[1] ?? 0;
|
|
865
|
+
originalLine += nums[2] ?? 0;
|
|
866
|
+
originalColumn += nums[3] ?? 0;
|
|
867
|
+
segs.push({
|
|
868
|
+
generatedColumn,
|
|
869
|
+
source: sources[sourceIndex] ?? USER_ENTRY,
|
|
870
|
+
originalLine,
|
|
871
|
+
originalColumn
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
decoded.push(segs);
|
|
877
|
+
}
|
|
878
|
+
return decoded;
|
|
879
|
+
}
|
|
880
|
+
function originalPositionFor(map, line, column) {
|
|
881
|
+
const decoded = decodeMappings(map);
|
|
882
|
+
for (let i = line - 1; i >= 0; i--) {
|
|
883
|
+
const segs = decoded[i];
|
|
884
|
+
if (!segs || segs.length === 0) continue;
|
|
885
|
+
const col = i === line - 1 ? column - 1 : Number.POSITIVE_INFINITY;
|
|
886
|
+
let best = segs[0];
|
|
887
|
+
for (const seg of segs) {
|
|
888
|
+
if (seg.generatedColumn <= col) best = seg;
|
|
889
|
+
else break;
|
|
890
|
+
}
|
|
891
|
+
if (!best) continue;
|
|
892
|
+
return { source: best.source, line: best.originalLine + 1, column: best.originalColumn };
|
|
893
|
+
}
|
|
894
|
+
return void 0;
|
|
895
|
+
}
|
|
896
|
+
function extractInlineSourceMap(code) {
|
|
897
|
+
const m = code.match(SOURCEMAP_RE);
|
|
898
|
+
if (!m?.[1]) return void 0;
|
|
899
|
+
try {
|
|
900
|
+
return JSON.parse(Buffer.from(m[1], "base64").toString("utf8"));
|
|
901
|
+
} catch {
|
|
902
|
+
return void 0;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
function remapErrorStack(stack, bundledCode) {
|
|
906
|
+
const map = extractInlineSourceMap(bundledCode);
|
|
907
|
+
if (!map?.mappings) return stack;
|
|
908
|
+
return stack.replace(/(blob:[^\s)]+?):(\d+):(\d+)/g, (full, _url, line, col) => {
|
|
909
|
+
const orig = originalPositionFor(map, Number(line), Number(col));
|
|
910
|
+
if (!orig) return full;
|
|
911
|
+
return `${orig.source}:${orig.line}:${orig.column}`;
|
|
912
|
+
});
|
|
511
913
|
}
|
|
512
914
|
export {
|
|
513
915
|
APP_ID_HEADER,
|
|
514
916
|
AUTH_HEADER,
|
|
515
917
|
BEARER_PREFIX,
|
|
918
|
+
BundleError,
|
|
516
919
|
CODEX_MARKER_BEGIN,
|
|
517
920
|
CODEX_MARKER_END,
|
|
518
921
|
CONFIG_FILE_NAME,
|
|
519
922
|
ConfigError,
|
|
520
923
|
DEFAULT_BASE_URL,
|
|
924
|
+
DEFAULT_BUNDLE_OUT,
|
|
925
|
+
DEFAULT_OSS_URL,
|
|
926
|
+
EASYTWIN_TYPES_DIR,
|
|
521
927
|
ENDPOINTS,
|
|
928
|
+
EXAMPLE_SCENE_FILE,
|
|
522
929
|
EasyTwinApiError,
|
|
523
930
|
EasyTwinClient,
|
|
524
931
|
GITIGNORE_ENTRY,
|
|
525
932
|
GITIGNORE_FILE_NAME,
|
|
526
933
|
META_FILE_NAME,
|
|
934
|
+
MINIMAL_TSCONFIG,
|
|
935
|
+
MOCK_APP_ID,
|
|
936
|
+
MOCK_APP_SECRET,
|
|
937
|
+
MOCK_SCENE_NAME,
|
|
938
|
+
RUNTIME_MODULE,
|
|
527
939
|
SKILL_NAMES,
|
|
940
|
+
TEST_BASE_URL,
|
|
941
|
+
TSCONFIG_PATHS_HINT,
|
|
942
|
+
USER_ENTRY,
|
|
528
943
|
appendGitignore,
|
|
529
944
|
buildMultipartBody,
|
|
945
|
+
buildRuntimeTypesContent,
|
|
946
|
+
bundleUserCode,
|
|
530
947
|
collectFiles,
|
|
531
948
|
configFilePath,
|
|
949
|
+
deriveExampleSceneId,
|
|
532
950
|
detectSkillsStatus,
|
|
951
|
+
exampleSceneSummary,
|
|
952
|
+
extractInlineSourceMap,
|
|
533
953
|
initConfig,
|
|
954
|
+
isMockCredentials,
|
|
534
955
|
listScenes,
|
|
535
956
|
loadConfig,
|
|
957
|
+
loadExampleScene,
|
|
536
958
|
normalizeSceneDetail,
|
|
537
959
|
normalizeSceneList,
|
|
538
960
|
normalizeTargets,
|
|
539
961
|
parseConfig,
|
|
962
|
+
parseSceneStructure,
|
|
540
963
|
pullScene,
|
|
541
964
|
readConfigFile,
|
|
542
965
|
readDevkitVersion,
|
|
966
|
+
remapErrorStack,
|
|
543
967
|
resolveConfig,
|
|
968
|
+
resolveExampleScenePath,
|
|
969
|
+
resolveRuntimeTypesSourceFile,
|
|
544
970
|
resolveSkillsSourceDir,
|
|
545
971
|
saveSceneJson,
|
|
546
972
|
syncSkills,
|
|
973
|
+
typesMissingHint,
|
|
547
974
|
uploadDirectory,
|
|
548
975
|
validateConfigShape,
|
|
549
976
|
writeConfigFile
|