@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/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/config.ts","../src/client.ts","../src/scene.ts","../src/upload.ts","../src/skills.ts"],"sourcesContent":["import { promises as fs } from \"node:fs\";\nimport path from \"node:path\";\n\n/** 配置文件名(项目根)。 */\nexport const CONFIG_FILE_NAME = \"easytwin.config.json\";\n\n/**\n * 官方缺省域名。\n * TODO(外部输入): 域名值待定。该常量是全仓唯一收口点,只允许写在这里。\n */\nexport const DEFAULT_BASE_URL = \"https://api.easytwin.example.com\";\n\n/** 磁盘上的配置结构(4.3 配置契约)。 */\nexport interface EasyTwinConfig {\n appId: string;\n appSecret: string;\n baseUrl?: string;\n}\n\n/** 解析 + 环境变量覆盖后的最终配置。 */\nexport interface ResolvedConfig {\n appId: string;\n appSecret: string;\n baseUrl: string;\n source: \"file\" | \"env\";\n}\n\nexport class ConfigError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ConfigError\";\n }\n}\n\nexport function configFilePath(cwd: string): string {\n return path.join(cwd, CONFIG_FILE_NAME);\n}\n\nexport function parseConfig(raw: string): EasyTwinConfig {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new ConfigError(`配置文件不是合法 JSON:${CONFIG_FILE_NAME}`);\n }\n return validateConfigShape(parsed);\n}\n\nexport function validateConfigShape(value: unknown): EasyTwinConfig {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new ConfigError(\"配置必须是 JSON 对象\");\n }\n const v = value as Record<string, unknown>;\n if (typeof v.appId !== \"string\" || v.appId.length === 0) {\n throw new ConfigError(\"配置缺少必填字段 appId\");\n }\n if (typeof v.appSecret !== \"string\" || v.appSecret.length === 0) {\n throw new ConfigError(\"配置缺少必填字段 appSecret\");\n }\n if (v.baseUrl !== undefined && (typeof v.baseUrl !== \"string\" || v.baseUrl.length === 0)) {\n throw new ConfigError(\"baseUrl 必须是非空字符串\");\n }\n const config: EasyTwinConfig = { appId: v.appId, appSecret: v.appSecret };\n if (typeof v.baseUrl === \"string\") config.baseUrl = v.baseUrl;\n return config;\n}\n\nexport async function readConfigFile(cwd: string): Promise<EasyTwinConfig> {\n const file = configFilePath(cwd);\n let raw: string;\n try {\n raw = await fs.readFile(file, \"utf8\");\n } catch {\n throw new ConfigError(`未找到配置文件 ${file},请先运行 \\`easytwin init\\``);\n }\n return parseConfig(raw);\n}\n\n/** 环境变量优先于文件(CI 与不落盘场景)。 */\nexport function resolveConfig(file: EasyTwinConfig, env: NodeJS.ProcessEnv = process.env): ResolvedConfig {\n const appId = env.EASYTWIN_APP_ID ?? file.appId;\n const appSecret = env.EASYTWIN_APP_SECRET ?? file.appSecret;\n const baseUrl = env.EASYTWIN_BASE_URL ?? file.baseUrl ?? DEFAULT_BASE_URL;\n if (!appId) throw new ConfigError(\"缺少 appId:配置文件中未提供,且未设置环境变量 EASYTWIN_APP_ID\");\n if (!appSecret) throw new ConfigError(\"缺少 appSecret:配置文件中未提供,且未设置环境变量 EASYTWIN_APP_SECRET\");\n const source: ResolvedConfig[\"source\"] =\n env.EASYTWIN_APP_ID !== undefined || env.EASYTWIN_APP_SECRET !== undefined ? \"env\" : \"file\";\n return { appId, appSecret, baseUrl, source };\n}\n\nexport async function loadConfig(cwd: string, env: NodeJS.ProcessEnv = process.env): Promise<ResolvedConfig> {\n return resolveConfig(await readConfigFile(cwd), env);\n}\n\nexport async function writeConfigFile(cwd: string, config: EasyTwinConfig): Promise<string> {\n const file = configFilePath(cwd);\n const body: EasyTwinConfig = { appId: config.appId, appSecret: config.appSecret };\n if (config.baseUrl) body.baseUrl = config.baseUrl;\n await fs.mkdir(cwd, { recursive: true });\n await fs.writeFile(file, JSON.stringify(body, null, 2) + \"\\n\", \"utf8\");\n return file;\n}\n\nexport const GITIGNORE_FILE_NAME = \".gitignore\";\nexport const GITIGNORE_ENTRY = \"easytwin.config.json\";\n\nexport interface GitignoreResult {\n created: boolean;\n added: boolean;\n}\n\n/** 把配置文件名追加进 .gitignore(无则创建),幂等:已存在条目时不重复追加。 */\nexport async function appendGitignore(cwd: string): Promise<GitignoreResult> {\n const file = path.join(cwd, GITIGNORE_FILE_NAME);\n let content = \"\";\n let created = false;\n try {\n content = await fs.readFile(file, \"utf8\");\n } catch {\n created = true;\n }\n const lines = content.split(/\\r?\\n/);\n if (lines.includes(GITIGNORE_ENTRY)) return { created, added: false };\n const prefix = content.length > 0 && !content.endsWith(\"\\n\") ? \"\\n\" : \"\";\n await fs.writeFile(file, content + prefix + GITIGNORE_ENTRY + \"\\n\", \"utf8\");\n return { created, added: true };\n}\n\nexport interface InitResult {\n configFile: string;\n gitignore: GitignoreResult;\n}\n\n/** init 的 lib 实现:生成配置文件 + 追加 gitignore(与插件配置向导共用同一实现)。 */\nexport async function initConfig(input: EasyTwinConfig, cwd: string): Promise<InitResult> {\n const validated = validateConfigShape(input);\n const configFile = await writeConfigFile(cwd, validated);\n const gitignore = await appendGitignore(cwd);\n return { configFile, gitignore };\n}","import http from \"node:http\";\nimport https from \"node:https\";\nimport { URL } from \"node:url\";\nimport type { ResolvedConfig } from \"./config\";\n\n/**\n * TODO(swagger): 认证头名称、endpoint 路径、请求/响应结构一律以内部 swagger 为准。\n * 以下常量为占位约定;swagger 到位后在 client.ts / scene.ts / upload.ts 集中修订,不扩散到其它文件。\n */\nexport const AUTH_HEADER = \"Authorization\";\nexport const BEARER_PREFIX = \"Bearer\";\n/** TODO(swagger): 应用标识头名待定(上传目标空间由 App ID 决定)。 */\nexport const APP_ID_HEADER = \"X-Easytwin-App-Id\";\n\nexport const ENDPOINTS = {\n scenes: \"/api/scenes\",\n scene: (id: string) => `/api/scenes/${encodeURIComponent(id)}`,\n upload: \"/api/upload\",\n} as const;\n\nexport class EasyTwinApiError extends Error {\n readonly status: number;\n readonly body: unknown;\n constructor(status: number, message: string, body?: unknown) {\n super(message);\n this.name = \"EasyTwinApiError\";\n this.status = status;\n this.body = body;\n }\n}\n\nexport interface RequestOptions {\n method?: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\";\n headers?: Record<string, string>;\n body?: string | null;\n signal?: AbortSignal;\n}\n\ninterface RawResponse {\n status: number;\n body: Buffer;\n}\n\nfunction messageFromBody(body: unknown): string | undefined {\n if (typeof body === \"object\" && body !== null) {\n const m = (body as Record<string, unknown>).message;\n if (typeof m === \"string\") return m;\n }\n return undefined;\n}\n\nfunction parseResponseBody(buffer: Buffer): unknown {\n const text = buffer.toString(\"utf8\");\n if (text.length === 0) return undefined;\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n}\n\nexport class EasyTwinClient {\n readonly baseUrl: string;\n private readonly appId: string;\n private readonly appSecret: string;\n\n constructor(config: ResolvedConfig) {\n this.baseUrl = config.baseUrl.replace(/\\/+$/, \"\");\n this.appId = config.appId;\n this.appSecret = config.appSecret;\n }\n\n /** 全仓唯一认证头注入点。 */\n private authHeaders(): Record<string, string> {\n return {\n [AUTH_HEADER]: `${BEARER_PREFIX} ${this.appSecret}`,\n [APP_ID_HEADER]: this.appId,\n };\n }\n\n /** JSON 请求(原生 fetch)。 */\n async request(path: string, options: RequestOptions = {}): Promise<unknown> {\n const url = `${this.baseUrl}${path}`;\n const headers: Record<string, string> = { ...this.authHeaders(), ...options.headers };\n if (options.body !== undefined && options.body !== null && !(\"Content-Type\" in headers)) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n let res: Response;\n try {\n res = await fetch(url, {\n method: options.method ?? \"GET\",\n headers,\n body: options.body,\n signal: options.signal,\n });\n } catch (err) {\n throw new EasyTwinApiError(0, `请求失败:${err instanceof Error ? err.message : String(err)}`);\n }\n return this.handleStatus(res.status, Buffer.from(await res.arrayBuffer()));\n }\n\n /**\n * multipart 流式上传(手写 http 请求以支持逐字节进度回调)。\n * 这是 fetch 上传的替代路径:原生 fetch 无法上报上传进度。\n */\n async upload(\n path: string,\n options: { boundary: string; body: Buffer; onBytes?: (bytes: number) => void }\n ): Promise<unknown> {\n const url = new URL(`${this.baseUrl}${path}`);\n const mod = url.protocol === \"https:\" ? https : http;\n const headers: Record<string, string> = {\n ...this.authHeaders(),\n \"Content-Type\": `multipart/form-data; boundary=${options.boundary}`,\n \"Content-Length\": String(options.body.length),\n };\n const raw = await new Promise<RawResponse>((resolve, reject) => {\n const req = mod.request(url, { method: \"POST\", headers }, (res) => {\n const chunks: Buffer[] = [];\n res.on(\"data\", (c: Buffer) => chunks.push(c));\n res.on(\"end\", () => resolve({ status: res.statusCode ?? 0, body: Buffer.concat(chunks) }));\n });\n req.on(\"error\", reject);\n const CHUNK = 64 * 1024;\n let offset = 0;\n const sendNext = (): void => {\n if (offset >= options.body.length) {\n req.end();\n return;\n }\n const chunk = options.body.subarray(offset, offset + CHUNK);\n offset += chunk.length;\n req.write(chunk, () => {\n options.onBytes?.(chunk.length);\n sendNext();\n });\n };\n sendNext();\n });\n return this.handleStatus(raw.status, raw.body);\n }\n\n private handleStatus(status: number, body: Buffer): unknown {\n if (status >= 200 && status < 300) return parseResponseBody(body);\n const parsed = parseResponseBody(body);\n throw new EasyTwinApiError(status, messageFromBody(parsed) ?? `HTTP ${status}`, parsed);\n }\n}","import { promises as fs } from \"node:fs\";\nimport path from \"node:path\";\nimport { ENDPOINTS, type EasyTwinClient } from \"./client\";\n\n// TODO(swagger): 场景列表/详情响应结构待定,当前类型为占位,以内部 swagger 为准。\nexport interface SceneSummary {\n id: string;\n name: string;\n}\n\nexport interface SceneDetail {\n id: string;\n name: string;\n /** 服务端返回的场景 JSON 原始内容。 */\n payload: unknown;\n}\n\nexport function normalizeSceneList(data: unknown): SceneSummary[] {\n // TODO(swagger): 兼容 \"数组\" 与 \"{ list: [...] }\" 两种占位形态,swagger 到位后收敛为一种。\n const list = Array.isArray(data) ? data : (data as { list?: unknown } | null)?.list;\n if (!Array.isArray(list)) throw new Error(\"scene list 响应结构无法识别\");\n return list.map((item) => {\n const it = (item ?? {}) as Record<string, unknown>;\n return { id: String(it.id ?? \"\"), name: String(it.name ?? it.id ?? \"\") };\n });\n}\n\nexport function normalizeSceneDetail(data: unknown): SceneDetail {\n const it = (data ?? {}) as Record<string, unknown>;\n return { id: String(it.id ?? \"\"), name: String(it.name ?? \"\"), payload: data };\n}\n\nexport async function listScenes(client: EasyTwinClient): Promise<SceneSummary[]> {\n const data = await client.request(ENDPOINTS.scenes, { method: \"GET\" });\n return normalizeSceneList(data);\n}\n\nexport async function pullScene(client: EasyTwinClient, id: string): Promise<SceneDetail> {\n const data = await client.request(ENDPOINTS.scene(id), { method: \"GET\" });\n return normalizeSceneDetail(data);\n}\n\nexport async function saveSceneJson(scene: SceneDetail, out: string): Promise<string> {\n await fs.mkdir(path.dirname(out), { recursive: true });\n await fs.writeFile(out, JSON.stringify(scene.payload ?? scene, null, 2) + \"\\n\", \"utf8\");\n return out;\n}","import { promises as fs } from \"node:fs\";\nimport path from \"node:path\";\nimport { ENDPOINTS, type EasyTwinClient } from \"./client\";\n\nexport interface UploadProgress {\n phase: \"collect\" | \"upload\";\n /** collect 阶段为已统计字节;upload 阶段为已发送字节。 */\n current: number;\n total: number;\n /** upload 阶段当前文件相对路径。 */\n file?: string;\n}\n\nexport interface UploadOptions {\n onProgress?: (progress: UploadProgress) => void;\n /** 自定义忽略规则,默认只跳过 .git。 */\n ignore?: (relPath: string) => boolean;\n}\n\nexport interface UploadResult {\n fileCount: number;\n byteCount: number;\n}\n\nconst DEFAULT_IGNORED_DIRS = new Set([\".git\"]);\n\nexport interface CollectedFile {\n relPath: string;\n absPath: string;\n size: number;\n}\n\n/** 递归收集目录下全部文件(默认跳过 .git),按相对路径升序排列,保证顺序稳定。 */\nexport async function collectFiles(dir: string, ignore: (relPath: string) => boolean = () => false): Promise<CollectedFile[]> {\n const files: CollectedFile[] = [];\n async function walk(current: string, rel: string): Promise<void> {\n const entries = await fs.readdir(current, { withFileTypes: true });\n for (const entry of entries) {\n const relPath = path.posix.join(rel, entry.name);\n if (ignore(relPath)) continue;\n if (entry.isDirectory()) {\n if (DEFAULT_IGNORED_DIRS.has(entry.name)) continue;\n await walk(path.join(current, entry.name), relPath);\n } else if (entry.isFile()) {\n const absPath = path.join(current, entry.name);\n const stat = await fs.stat(absPath);\n files.push({ relPath, absPath, size: stat.size });\n }\n }\n }\n await walk(dir, \"\");\n files.sort((a, b) => a.relPath.localeCompare(b.relPath));\n return files;\n}\n\nexport function buildMultipartBody(\n boundary: string,\n fields: Record<string, string>,\n file: { name: string; content: Buffer }\n): Buffer {\n const parts: Buffer[] = [];\n for (const [key, value] of Object.entries(fields)) {\n parts.push(\n Buffer.from(`--${boundary}\\r\\nContent-Disposition: form-data; name=\"${key}\"\\r\\n\\r\\n${value}\\r\\n`, \"utf8\")\n );\n }\n parts.push(\n Buffer.from(\n `--${boundary}\\r\\nContent-Disposition: form-data; name=\"file\"; filename=\"${file.name}\"\\r\\nContent-Type: application/octet-stream\\r\\n\\r\\n`,\n \"utf8\"\n )\n );\n parts.push(file.content);\n parts.push(Buffer.from(`\\r\\n--${boundary}--\\r\\n`, \"utf8\"));\n return Buffer.concat(parts);\n}\n\n/** 全量覆盖上传目录:逐文件 multipart 上传,带进度回调(供插件进度条)。 */\nexport async function uploadDirectory(client: EasyTwinClient, dir: string, options: UploadOptions = {}): Promise<UploadResult> {\n const ignore = options.ignore ?? (() => false);\n const files = await collectFiles(dir, ignore);\n const total = files.reduce((sum, f) => sum + f.size, 0);\n options.onProgress?.({ phase: \"collect\", current: total, total });\n\n // 进度以原始文件字节计(multipart 头开销不计入),逐文件上报,供进度条/进度输出使用。\n let sent = 0;\n for (const file of files) {\n const content = await fs.readFile(file.absPath);\n const boundary = `----easytwin${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`;\n const body = buildMultipartBody(\n boundary,\n // TODO(swagger): multipart 字段名/结构待定,当前为占位约定。\n { path: file.relPath },\n { name: path.basename(file.relPath), content }\n );\n await client.upload(ENDPOINTS.upload, { boundary, body });\n sent += file.size;\n options.onProgress?.({ phase: \"upload\", current: sent, total, file: file.relPath });\n }\n return { fileCount: files.length, byteCount: sent };\n}","import { promises as fs } from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nexport type SyncTarget = \"cursor\" | \"claude\" | \"codex\";\nexport type SyncTargetArg = SyncTarget | \"all\";\n\n/** 六件套清单(5.1),顺序即输出顺序。 */\nexport const SKILL_NAMES = [\n \"easytwin-render\",\n \"easytwin-core\",\n \"easytwin-develop\",\n \"easytwin-bootstrap\",\n \"easytwin-scene\",\n \"easytwin-upload\",\n] as const;\n\nexport type SyncAction = \"created\" | \"updated\" | \"unchanged\";\n\nexport interface SyncEntrySummary {\n name: string;\n action: SyncAction;\n}\n\nexport interface SyncSummary {\n target: SyncTarget;\n entries: SyncEntrySummary[];\n codex?: SyncEntrySummary;\n}\n\nexport interface SyncOptions {\n cwd: string;\n targets?: SyncTargetArg | SyncTarget[];\n version?: string;\n /** skills 源目录;缺省按 devkit 包内 skills/ 解析(CLI)。插件 bundle 场景显式传入插件内目录。 */\n sourceDir?: string;\n}\n\nexport const CODEX_MARKER_BEGIN = \"<!-- EASYTWIN-SKILLS:BEGIN -->\";\nexport const CODEX_MARKER_END = \"<!-- EASYTWIN-SKILLS:END -->\";\nexport const META_FILE_NAME = \".easytwin-meta.json\";\n\nexport function normalizeTargets(target: SyncTargetArg | SyncTarget[] = \"all\"): SyncTarget[] {\n if (target === \"all\") return [\"cursor\", \"claude\", \"codex\"];\n if (Array.isArray(target)) return [...new Set(target)];\n return [target];\n}\n\n/** skills 源目录默认解析:CLI 下为 devkit 包内 skills/。插件 bundle 到 CJS 后 import.meta.url 为空,\n * 必须通过 SyncOptions.sourceDir / detectSkillsStatus 的 sourceDir 显式传入(见 esbuild.config.mjs 注释)。 */\nexport function resolveSkillsSourceDir(): string {\n if (typeof import.meta.url !== \"string\" || import.meta.url.length === 0) {\n throw new Error(\"无法定位 skills 源目录:插件(bundle/CJS)场景必须显式传入 sourceDir\");\n }\n const here = path.dirname(fileURLToPath(import.meta.url));\n return path.resolve(here, \"..\", \"skills\");\n}\n\nasync function readJson<T>(file: string): Promise<T> {\n return JSON.parse(await fs.readFile(file, \"utf8\")) as T;\n}\n\nexport async function readDevkitVersion(skillsDir = resolveSkillsSourceDir()): Promise<string> {\n const marker = path.join(skillsDir, \".easytwin-source.json\");\n try {\n const meta = await readJson<{ version: string }>(marker);\n if (typeof meta.version === \"string\") return meta.version;\n } catch {\n // 无构建期标记,退回读包内 package.json\n }\n try {\n const pkg = await readJson<{ version: string }>(path.resolve(skillsDir, \"..\", \"package.json\"));\n return pkg.version;\n } catch {\n return \"0.0.0\";\n }\n}\n\nasync function copyDir(src: string, dest: string): Promise<void> {\n await fs.mkdir(dest, { recursive: true });\n const entries = await fs.readdir(src, { withFileTypes: true });\n for (const entry of entries) {\n const s = path.join(src, entry.name);\n const d = path.join(dest, entry.name);\n if (entry.isDirectory()) await copyDir(s, d);\n else if (entry.isFile()) await fs.copyFile(s, d);\n }\n}\n\n/** 比较源目录的每个文件与目标是否逐字节一致(忽略目标里额外的文件,如版本标记)。 */\nasync function dirMatches(src: string, dest: string): Promise<boolean> {\n let sourceEntries: string[];\n try {\n sourceEntries = await fs.readdir(src);\n } catch {\n return false;\n }\n for (const name of sourceEntries) {\n const s = path.join(src, name);\n const d = path.join(dest, name);\n const sStat = await fs.stat(s);\n if (sStat.isDirectory()) {\n if (!(await dirMatches(s, d))) return false;\n } else {\n let dStat;\n try {\n dStat = await fs.stat(d);\n } catch {\n return false;\n }\n if (!sStat.isFile() || !dStat.isFile() || sStat.size !== dStat.size) return false;\n if (!(await fs.readFile(s)).equals(await fs.readFile(d))) return false;\n }\n }\n return true;\n}\n\nfunction actionFor(exists: boolean, matches: boolean): SyncAction {\n if (!exists) return \"created\";\n return matches ? \"unchanged\" : \"updated\";\n}\n\nasync function writeMeta(destDir: string, version: string): Promise<void> {\n const meta = { name: \"@easytwin/devkit\", version };\n await fs.writeFile(path.join(destDir, META_FILE_NAME), JSON.stringify(meta, null, 2) + \"\\n\", \"utf8\");\n}\n\n/** cursor / claude:复制技能目录到用户项目,按内容覆盖,写入版本标记。 */\nasync function syncToDir(target: SyncTarget, sourceRoot: string, targetRoot: string, version: string): Promise<SyncSummary> {\n const entries: SyncEntrySummary[] = [];\n for (const name of SKILL_NAMES) {\n const src = path.join(sourceRoot, name);\n const dest = path.join(targetRoot, name);\n const exists = await fs\n .stat(dest)\n .then(() => true)\n .catch(() => false);\n const matches = await dirMatches(src, dest);\n const action = actionFor(exists, matches);\n if (action !== \"unchanged\") {\n await fs.rm(dest, { recursive: true, force: true });\n await copyDir(src, dest);\n }\n entries.push({ name, action });\n }\n await writeMeta(targetRoot, version);\n return { target, entries };\n}\n\n/** codex:在用户项目 AGENTS.md 注入带标记的引用段(整段替换,幂等)。 */\nfunction buildCodexSegment(version: string): string {\n return [\n CODEX_MARKER_BEGIN,\n \"<!-- 本段由 `easytwin skills sync` 管理,可整段替换;手动修改会被下次同步覆盖。 -->\",\n `EasyTwin 开发技能(由 @easytwin/devkit v${version} 同步):`,\n \"\",\n \"- `easytwin-develop`:工作流总纲,开始 EasyTwin 开发前必读。\",\n \"- `easytwin-bootstrap`:初始化应用级凭证(easytwin.config.json)。\",\n \"- `easytwin-scene`:列出场景、拉取场景 JSON。\",\n \"- `easytwin-render`:用 twin runtime 开发渲染功能(主力)。\",\n \"- `easytwin-core`:引擎内核 API(RuntimeEngine/基类/Time/相机/物理)。\",\n \"- `easytwin-upload`:全量上传开发产物目录(覆盖不可逆)。\",\n \"\",\n \"详细用法见各技能;重新同步运行 `easytwin skills sync --target codex`。\",\n CODEX_MARKER_END,\n ].join(\"\\n\");\n}\n\nasync function syncToCodex(sourceRoot: string, cwd: string, version: string): Promise<SyncSummary> {\n const agentsFile = path.join(cwd, \"AGENTS.md\");\n const segment = buildCodexSegment(version);\n let content = \"\";\n let exists = true;\n try {\n content = await fs.readFile(agentsFile, \"utf8\");\n } catch {\n exists = false;\n }\n const start = content.indexOf(CODEX_MARKER_BEGIN);\n const end = content.indexOf(CODEX_MARKER_END);\n let action: SyncAction;\n let next: string;\n if (start === -1 || end === -1 || end < start) {\n action = exists ? \"updated\" : \"created\";\n next = content.length > 0 && !content.endsWith(\"\\n\") ? content + \"\\n\\n\" : content + (content.length > 0 ? \"\\n\" : \"\");\n next += segment + \"\\n\";\n } else {\n const current = content.slice(start, end + CODEX_MARKER_END.length);\n action = current === segment ? \"unchanged\" : \"updated\";\n next = content.slice(0, start) + segment + content.slice(end + CODEX_MARKER_END.length);\n }\n if (action !== \"unchanged\") {\n await fs.mkdir(cwd, { recursive: true });\n await fs.writeFile(agentsFile, next, \"utf8\");\n }\n return { target: \"codex\", entries: [], codex: { name: \"AGENTS.md\", action } };\n}\n\nexport async function syncSkills(options: SyncOptions): Promise<SyncSummary[]> {\n const targets = normalizeTargets(options.targets ?? \"all\");\n const sourceRoot = options.sourceDir ?? resolveSkillsSourceDir();\n const version = options.version ?? (await readDevkitVersion(sourceRoot));\n const summaries: SyncSummary[] = [];\n for (const target of targets) {\n if (target === \"cursor\") summaries.push(await syncToDir(\"cursor\", sourceRoot, path.join(options.cwd, \".cursor\", \"skills\"), version));\n else if (target === \"claude\") summaries.push(await syncToDir(\"claude\", sourceRoot, path.join(options.cwd, \".claude\", \"skills\"), version));\n else summaries.push(await syncToCodex(sourceRoot, options.cwd, version));\n }\n return summaries;\n}\n\nexport interface SkillsTargetStatus {\n target: SyncTarget;\n synced: boolean;\n reason?: string;\n}\n\nexport interface SkillsStatus {\n cwd: string;\n targets: SkillsTargetStatus[];\n}\n\n/** 供插件\"缺失/过期时提示一次\"使用:检测各目标当前同步状态。 */\nexport async function detectSkillsStatus(cwd: string, sourceDir?: string): Promise<SkillsStatus> {\n const sourceRoot = sourceDir ?? resolveSkillsSourceDir();\n const version = await readDevkitVersion(sourceRoot);\n const targets: SkillsTargetStatus[] = [];\n\n for (const target of [\"cursor\", \"claude\"] as const) {\n const root = path.join(cwd, target === \"cursor\" ? \".cursor/skills\" : \".claude/skills\");\n const missing = [];\n for (const name of SKILL_NAMES) {\n if (!(await fs.stat(path.join(root, name)).then(() => true).catch(() => false))) missing.push(name);\n }\n let stale = false;\n try {\n const meta = await readJson<{ version: string }>(path.join(root, META_FILE_NAME));\n stale = meta.version !== version;\n } catch {\n stale = missing.length === 0; // 目录存在但无版本标记\n }\n targets.push({\n target,\n synced: missing.length === 0 && !stale,\n reason: missing.length > 0 ? `缺少 ${missing.join(\", \")}` : stale ? \"版本过期\" : undefined,\n });\n }\n\n let agentsContent = \"\";\n try {\n agentsContent = await fs.readFile(path.join(cwd, \"AGENTS.md\"), \"utf8\");\n } catch {\n // AGENTS.md 不存在视为未同步\n }\n const hasCodex =\n agentsContent.includes(CODEX_MARKER_BEGIN) && agentsContent.includes(CODEX_MARKER_END) && agentsContent.includes(`v${version}`);\n targets.push({ target: \"codex\", synced: hasCodex, reason: hasCodex ? undefined : \"AGENTS.md 缺少同步标记段\" });\n\n return { cwd, targets };\n}\n"],"mappings":"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","names":["path","fs","path","fs","path","fs","path","fs","path","fs","path"]}
1
+ {"version":3,"sources":["../src/config.ts","../src/client.ts","../src/scene.ts","../src/upload.ts","../src/skills.ts","../src/bundle.ts"],"sourcesContent":["import { promises as fs } from \"node:fs\";\nimport path from \"node:path\";\n\n/** 配置文件名(项目根)。 */\nexport const CONFIG_FILE_NAME = \"easytwin.config.json\";\n\n/**\n * 官方缺省域名(正式环境)。该常量是全仓唯一收口点,只允许写在这里。\n */\nexport const DEFAULT_BASE_URL = \"http://saas-twin.k8s.dtstack.cn/\";\n/** 官方测试环境域名。 */\nexport const TEST_BASE_URL = \"http://saas-twin-test.k8s.dtstack.cn/\";\n/**\n * 官方 OSS(twin runtime baseOSSUrl)。系统资产由此拼出:\n * `{ossUrl}/easytwin/system/libs/webp-wasm.wasm`、\n * `{ossUrl}/easytwin/system/libs/draco/`、\n * `{ossUrl}/easytwin/system/components/custom/{type}/{version}/script.js`。\n * 不得用 API baseUrl 代替:场景/上传走 API,系统库与组件脚本走 OSS。\n */\nexport const DEFAULT_OSS_URL = \"https://dt-easyv-test.oss-cn-hangzhou.aliyuncs.com/\";\n\n/** 服务环境,决定缺省 baseUrl。 */\nexport type EasyTwinEnv = \"prod\" | \"test\";\n/** 本地测试模式哨兵凭据:appId 与 appSecret 均为该值时,场景/上传走本地 mock,不发网络请求(见 AGENTS.md 4.4)。 */\nexport const MOCK_APP_ID = \"test\";\nexport const MOCK_APP_SECRET = \"test\";\n\n/** appId/appSecret 是否命中本地测试模式哨兵值。 */\nexport function isMockCredentials(appId: string, appSecret: string): boolean {\n return appId === MOCK_APP_ID && appSecret === MOCK_APP_SECRET;\n}\n\n/** 磁盘上的配置结构(4.3 配置契约)。 */\nexport interface EasyTwinConfig {\n appId: string;\n appSecret: string;\n /** 服务环境,缺省 prod。 */\n env?: EasyTwinEnv;\n baseUrl?: string;\n /** twin runtime 资产根(baseOSSUrl);缺省官方 OSS,可覆盖为 CDN/自建存储。 */\n ossUrl?: string;\n}\n\n/** 解析 + 环境变量覆盖后的最终配置。 */\nexport interface ResolvedConfig {\n appId: string;\n appSecret: string;\n baseUrl: string;\n /** twin runtime baseOSSUrl(系统库 draco/basis/webp、custom script 与相对路径资产);缺省官方 OSS。 */\n ossUrl: string;\n /** 本地测试模式:appId/appSecret 均为 \"test\" 时启用,scene/upload 走本地 mock 不发请求。 */\n mock: boolean;\n source: \"file\" | \"env\";\n}\n\nexport class ConfigError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ConfigError\";\n }\n}\n\nexport function configFilePath(cwd: string): string {\n return path.join(cwd, CONFIG_FILE_NAME);\n}\n\nexport function parseConfig(raw: string): EasyTwinConfig {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new ConfigError(`配置文件不是合法 JSON:${CONFIG_FILE_NAME}`);\n }\n return validateConfigShape(parsed);\n}\n\nexport function validateConfigShape(value: unknown): EasyTwinConfig {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new ConfigError(\"配置必须是 JSON 对象\");\n }\n const v = value as Record<string, unknown>;\n if (typeof v.appId !== \"string\" || v.appId.length === 0) {\n throw new ConfigError(\"配置缺少必填字段 appId\");\n }\n if (typeof v.appSecret !== \"string\" || v.appSecret.length === 0) {\n throw new ConfigError(\"配置缺少必填字段 appSecret\");\n }\n if (v.baseUrl !== undefined && (typeof v.baseUrl !== \"string\" || v.baseUrl.length === 0)) {\n throw new ConfigError(\"baseUrl 必须是非空字符串\");\n }\n if (v.ossUrl !== undefined && (typeof v.ossUrl !== \"string\" || v.ossUrl.length === 0)) {\n throw new ConfigError(\"ossUrl 必须是非空字符串\");\n }\n if (v.env !== undefined && v.env !== \"prod\" && v.env !== \"test\") {\n throw new ConfigError(\"env 必须是 prod 或 test\");\n }\n const config: EasyTwinConfig = { appId: v.appId, appSecret: v.appSecret };\n if (v.env === \"prod\" || v.env === \"test\") config.env = v.env;\n if (typeof v.baseUrl === \"string\") config.baseUrl = v.baseUrl;\n if (typeof v.ossUrl === \"string\") config.ossUrl = v.ossUrl;\n return config;\n}\n\nexport async function readConfigFile(cwd: string): Promise<EasyTwinConfig> {\n const file = configFilePath(cwd);\n let raw: string;\n try {\n raw = await fs.readFile(file, \"utf8\");\n } catch {\n throw new ConfigError(`未找到配置文件 ${file},请先运行 \\`easytwin init\\``);\n }\n return parseConfig(raw);\n}\n\n/** 环境变量优先于文件(CI 与不落盘场景)。 */\nexport function resolveConfig(file: EasyTwinConfig, env: NodeJS.ProcessEnv = process.env): ResolvedConfig {\n const appId = env.EASYTWIN_APP_ID ?? file.appId;\n const appSecret = env.EASYTWIN_APP_SECRET ?? file.appSecret;\n const easyEnv: EasyTwinEnv =\n env.EASYTWIN_ENV === \"test\" || env.EASYTWIN_ENV === \"prod\" ? env.EASYTWIN_ENV : file.env ?? \"prod\";\n const baseUrl = env.EASYTWIN_BASE_URL ?? file.baseUrl ?? (easyEnv === \"test\" ? TEST_BASE_URL : DEFAULT_BASE_URL);\n // 资产根缺省官方 OSS,与 API baseUrl 分离;EASYTWIN_OSS_URL / 文件 ossUrl 始终优先。\n const ossUrl = env.EASYTWIN_OSS_URL ?? file.ossUrl ?? DEFAULT_OSS_URL;\n if (!appId) throw new ConfigError(\"缺少 appId:配置文件中未提供,且未设置环境变量 EASYTWIN_APP_ID\");\n if (!appSecret) throw new ConfigError(\"缺少 appSecret:配置文件中未提供,且未设置环境变量 EASYTWIN_APP_SECRET\");\n const source: ResolvedConfig[\"source\"] =\n env.EASYTWIN_APP_ID !== undefined || env.EASYTWIN_APP_SECRET !== undefined ? \"env\" : \"file\";\n return { appId, appSecret, baseUrl, ossUrl, mock: isMockCredentials(appId, appSecret), source };\n}\n\nexport async function loadConfig(cwd: string, env: NodeJS.ProcessEnv = process.env): Promise<ResolvedConfig> {\n return resolveConfig(await readConfigFile(cwd), env);\n}\n\nexport async function writeConfigFile(cwd: string, config: EasyTwinConfig): Promise<string> {\n const file = configFilePath(cwd);\n const body: EasyTwinConfig = { appId: config.appId, appSecret: config.appSecret };\n if (config.env) body.env = config.env;\n if (config.baseUrl) body.baseUrl = config.baseUrl;\n if (config.ossUrl) body.ossUrl = config.ossUrl;\n await fs.mkdir(cwd, { recursive: true });\n await fs.writeFile(file, JSON.stringify(body, null, 2) + \"\\n\", \"utf8\");\n return file;\n}\n\nexport const GITIGNORE_FILE_NAME = \".gitignore\";\nexport const GITIGNORE_ENTRY = \"easytwin.config.json\";\n\nexport interface GitignoreResult {\n created: boolean;\n added: boolean;\n}\n\n/** 把配置文件名追加进 .gitignore(无则创建),幂等:已存在条目时不重复追加。 */\nexport async function appendGitignore(cwd: string): Promise<GitignoreResult> {\n const file = path.join(cwd, GITIGNORE_FILE_NAME);\n let content = \"\";\n let created = false;\n try {\n content = await fs.readFile(file, \"utf8\");\n } catch {\n created = true;\n }\n const lines = content.split(/\\r?\\n/);\n if (lines.includes(GITIGNORE_ENTRY)) return { created, added: false };\n const prefix = content.length > 0 && !content.endsWith(\"\\n\") ? \"\\n\" : \"\";\n await fs.writeFile(file, content + prefix + GITIGNORE_ENTRY + \"\\n\", \"utf8\");\n return { created, added: true };\n}\n\nexport interface InitResult {\n configFile: string;\n gitignore: GitignoreResult;\n}\n\n/** init 的 lib 实现:生成配置文件 + 追加 gitignore(与插件配置向导共用同一实现)。 */\nexport async function initConfig(input: EasyTwinConfig, cwd: string): Promise<InitResult> {\n const validated = validateConfigShape(input);\n const configFile = await writeConfigFile(cwd, validated);\n const gitignore = await appendGitignore(cwd);\n return { configFile, gitignore };\n}","import http from \"node:http\";\nimport https from \"node:https\";\nimport { URL } from \"node:url\";\nimport type { ResolvedConfig } from \"./config\";\n\n/**\n * TODO(swagger): 认证头名称、endpoint 路径、请求/响应结构一律以内部 swagger 为准。\n * 以下常量为占位约定;swagger 到位后在 client.ts / scene.ts / upload.ts 集中修订,不扩散到其它文件。\n */\nexport const AUTH_HEADER = \"Authorization\";\nexport const BEARER_PREFIX = \"Bearer\";\n/** TODO(swagger): 应用标识头名待定(上传目标空间由 App ID 决定)。 */\nexport const APP_ID_HEADER = \"X-Easytwin-App-Id\";\n\nexport const ENDPOINTS = {\n scenes: \"/api/scenes\",\n scene: (id: string) => `/api/scenes/${encodeURIComponent(id)}`,\n upload: \"/api/upload\",\n} as const;\n\nexport class EasyTwinApiError extends Error {\n readonly status: number;\n readonly body: unknown;\n constructor(status: number, message: string, body?: unknown) {\n super(message);\n this.name = \"EasyTwinApiError\";\n this.status = status;\n this.body = body;\n }\n}\n\nexport interface RequestOptions {\n method?: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\";\n headers?: Record<string, string>;\n body?: string | null;\n signal?: AbortSignal;\n}\n\ninterface RawResponse {\n status: number;\n body: Buffer;\n}\n\nfunction messageFromBody(body: unknown): string | undefined {\n if (typeof body === \"object\" && body !== null) {\n const m = (body as Record<string, unknown>).message;\n if (typeof m === \"string\") return m;\n }\n return undefined;\n}\n\nfunction parseResponseBody(buffer: Buffer): unknown {\n const text = buffer.toString(\"utf8\");\n if (text.length === 0) return undefined;\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n}\n\nexport class EasyTwinClient {\n readonly baseUrl: string;\n /** 本地测试模式:true 时 scene/upload 走本地 mock,不发网络请求(见 config.ts)。 */\n readonly mock: boolean;\n private readonly appId: string;\n private readonly appSecret: string;\n\n constructor(config: ResolvedConfig) {\n this.baseUrl = config.baseUrl.replace(/\\/+$/, \"\");\n this.mock = config.mock;\n this.appId = config.appId;\n this.appSecret = config.appSecret;\n }\n\n /** 全仓唯一认证头注入点。 */\n private authHeaders(): Record<string, string> {\n return {\n [AUTH_HEADER]: `${BEARER_PREFIX} ${this.appSecret}`,\n [APP_ID_HEADER]: this.appId,\n };\n }\n\n /** JSON 请求(原生 fetch)。 */\n async request(path: string, options: RequestOptions = {}): Promise<unknown> {\n const url = `${this.baseUrl}${path}`;\n const headers: Record<string, string> = { ...this.authHeaders(), ...options.headers };\n if (options.body !== undefined && options.body !== null && !(\"Content-Type\" in headers)) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n let res: Response;\n try {\n res = await fetch(url, {\n method: options.method ?? \"GET\",\n headers,\n body: options.body,\n signal: options.signal,\n });\n } catch (err) {\n throw new EasyTwinApiError(0, `请求失败:${err instanceof Error ? err.message : String(err)}`);\n }\n return this.handleStatus(res.status, Buffer.from(await res.arrayBuffer()));\n }\n\n /**\n * multipart 流式上传(手写 http 请求以支持逐字节进度回调)。\n * 这是 fetch 上传的替代路径:原生 fetch 无法上报上传进度。\n */\n async upload(\n path: string,\n options: { boundary: string; body: Buffer; onBytes?: (bytes: number) => void }\n ): Promise<unknown> {\n const url = new URL(`${this.baseUrl}${path}`);\n const mod = url.protocol === \"https:\" ? https : http;\n const headers: Record<string, string> = {\n ...this.authHeaders(),\n \"Content-Type\": `multipart/form-data; boundary=${options.boundary}`,\n \"Content-Length\": String(options.body.length),\n };\n const raw = await new Promise<RawResponse>((resolve, reject) => {\n const req = mod.request(url, { method: \"POST\", headers }, (res) => {\n const chunks: Buffer[] = [];\n res.on(\"data\", (c: Buffer) => chunks.push(c));\n res.on(\"end\", () => resolve({ status: res.statusCode ?? 0, body: Buffer.concat(chunks) }));\n });\n req.on(\"error\", reject);\n const CHUNK = 64 * 1024;\n let offset = 0;\n const sendNext = (): void => {\n if (offset >= options.body.length) {\n req.end();\n return;\n }\n const chunk = options.body.subarray(offset, offset + CHUNK);\n offset += chunk.length;\n req.write(chunk, () => {\n options.onBytes?.(chunk.length);\n sendNext();\n });\n };\n sendNext();\n });\n return this.handleStatus(raw.status, raw.body);\n }\n\n private handleStatus(status: number, body: Buffer): unknown {\n if (status >= 200 && status < 300) return parseResponseBody(body);\n const parsed = parseResponseBody(body);\n throw new EasyTwinApiError(status, messageFromBody(parsed) ?? `HTTP ${status}`, parsed);\n }\n}","import { promises as fs } from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { ENDPOINTS, type EasyTwinClient } from \"./client\";\n\n// TODO(swagger): 场景列表/详情响应结构待定,当前类型为占位,以内部 swagger 为准。\nexport interface SceneSummary {\n id: string;\n name: string;\n}\n\nexport interface SceneDetail {\n id: string;\n name: string;\n /**\n * 服务端返回的场景 JSON 原始内容。\n * 结构以 easytwin-runtime 的 SceneJson 为准(见 skills/easytwin-scene)。\n */\n payload: unknown;\n}\n\nexport function normalizeSceneList(data: unknown): SceneSummary[] {\n // TODO(swagger): 兼容 \"数组\" 与 \"{ list: [...] }\" 两种占位形态,swagger 到位后收敛为一种。\n const list = Array.isArray(data) ? data : (data as { list?: unknown } | null)?.list;\n if (!Array.isArray(list)) throw new Error(\"scene list 响应结构无法识别\");\n return list.map((item) => {\n const it = (item ?? {}) as Record<string, unknown>;\n return { id: String(it.id ?? \"\"), name: String(it.name ?? it.id ?? \"\") };\n });\n}\n\nexport function normalizeSceneDetail(data: unknown): SceneDetail {\n const it = (data ?? {}) as Record<string, unknown>;\n return { id: String(it.id ?? \"\"), name: String(it.name ?? \"\"), payload: data };\n}\n\n/** devkit 包内本地测试场景文件名(4.4 本地测试模式)。 */\nexport const EXAMPLE_SCENE_FILE = \"scene.example.json\";\n/** 本地测试模式下场景列表展示的名字(示例文件本身不含场景级 name)。 */\nexport const MOCK_SCENE_NAME = \"本地示例场景\";\n\n/**\n * 示例场景文件默认路径:devkit 包内 scene.example.json(CLI/源码)。\n * 插件 bundle 到 CJS 后 import.meta.url 为空,必须经 listScenes/pullScene 的 exampleFile 显式传入(与 skills 的 sourceDir 同理)。\n */\nexport function resolveExampleScenePath(): string {\n if (typeof import.meta.url !== \"string\" || import.meta.url.length === 0) {\n throw new Error(`无法定位 ${EXAMPLE_SCENE_FILE}:插件(bundle/CJS)场景必须显式传入 exampleFile`);\n }\n return path.resolve(path.dirname(fileURLToPath(import.meta.url)), \"..\", EXAMPLE_SCENE_FILE);\n}\n\n/** 读取并解析本地示例场景(本地测试模式专用,不发网络请求)。 */\nexport async function loadExampleScene(exampleFile?: string): Promise<unknown> {\n const file = exampleFile ?? resolveExampleScenePath();\n let raw: string;\n try {\n raw = await fs.readFile(file, \"utf8\");\n } catch {\n throw new Error(`无法读取本地示例场景 ${file}(本地测试模式需要 devkit 包内的 ${EXAMPLE_SCENE_FILE})`);\n }\n try {\n return JSON.parse(raw) as unknown;\n } catch {\n throw new Error(`本地示例场景不是合法 JSON:${file}`);\n }\n}\n\n/** 从示例场景提取场景 id:取 objs[].sceneId 首个非空值;无则回退 \"local\"。 */\nexport function deriveExampleSceneId(payload: unknown): string {\n const root = (payload ?? {}) as { objs?: Array<{ sceneId?: unknown }> } | null;\n const id = root?.objs?.map((o) => o.sceneId).find((s): s is string => typeof s === \"string\" && s.length > 0);\n return id ?? \"local\";\n}\n\n/** 本地测试模式下的场景列表(单条)。 */\nexport async function exampleSceneSummary(exampleFile?: string): Promise<SceneSummary> {\n return { id: deriveExampleSceneId(await loadExampleScene(exampleFile)), name: MOCK_SCENE_NAME };\n}\n\nexport async function listScenes(\n client: EasyTwinClient,\n options: { exampleFile?: string } = {}\n): Promise<SceneSummary[]> {\n if (client.mock) return [await exampleSceneSummary(options.exampleFile)];\n const data = await client.request(ENDPOINTS.scenes, { method: \"GET\" });\n return normalizeSceneList(data);\n}\n\nexport async function pullScene(\n client: EasyTwinClient,\n id: string,\n options: { exampleFile?: string } = {}\n): Promise<SceneDetail> {\n if (client.mock) {\n const payload = await loadExampleScene(options.exampleFile);\n const sceneId = deriveExampleSceneId(payload);\n if (sceneId !== id) {\n throw new Error(`本地测试模式:示例场景 id 为 ${sceneId},收到 ${id}(本地仅提供 ${EXAMPLE_SCENE_FILE} 这一个场景)`);\n }\n return { id: sceneId, name: MOCK_SCENE_NAME, payload };\n }\n const data = await client.request(ENDPOINTS.scene(id), { method: \"GET\" });\n return normalizeSceneDetail(data);\n}\n\nexport async function saveSceneJson(scene: SceneDetail, out: string): Promise<string> {\n await fs.mkdir(path.dirname(out), { recursive: true });\n await fs.writeFile(out, JSON.stringify(scene.payload ?? scene, null, 2) + \"\\n\", \"utf8\");\n return out;\n}\n/** 场景对象树节点:由场景 JSON 的 objs/hierarchy 按 parentObjId 组装(侧边栏场景结构树等复用)。 */\nexport interface SceneTreeNode {\n id: string;\n name: string;\n type?: string;\n children: SceneTreeNode[];\n}\n\n/**\n * 解析场景对象层级树:优先读 `objs`(含 type),缺失或为空时回退 `hierarchy`。\n * - `parentObjId` 为空 / 指向不存在的 id / 自引用时,该节点视为根;\n * - 节点顺序保持场景数组顺序;非法输入或无可解析结构时返回空数组。\n * scene 可为已解析对象或 JSON 字符串。\n */\nexport function parseSceneStructure(scene: unknown): SceneTreeNode[] {\n let data: unknown = scene;\n if (typeof data === \"string\") {\n try {\n data = JSON.parse(data);\n } catch {\n return [];\n }\n }\n const root = (data ?? {}) as {\n objs?: Array<Record<string, unknown>>;\n hierarchy?: Array<Record<string, unknown>>;\n };\n const objs = Array.isArray(root.objs) ? root.objs : [];\n const hierarchy = Array.isArray(root.hierarchy) ? root.hierarchy : [];\n const items = objs.length > 0 ? objs : hierarchy;\n\n const typeById = new Map<string, string>();\n for (const o of objs) {\n if (typeof o.id === \"string\" && typeof o.type === \"string\") typeById.set(o.id, o.type);\n }\n\n const nodes = new Map<string, SceneTreeNode>();\n const order: string[] = [];\n for (const item of items) {\n const id = typeof item.id === \"string\" ? item.id : \"\";\n if (id.length === 0 || nodes.has(id)) continue;\n const name = typeof item.name === \"string\" && item.name.length > 0 ? item.name : id;\n nodes.set(id, { id, name, type: typeById.get(id), children: [] });\n order.push(id);\n }\n\n const childrenOf = new Map<string, string[]>();\n for (const item of items) {\n const id = typeof item.id === \"string\" ? item.id : \"\";\n if (id.length === 0 || !nodes.has(id)) continue;\n const parentId = typeof item.parentObjId === \"string\" && item.parentObjId.length > 0 ? item.parentObjId : \"\";\n if (parentId.length === 0 || parentId === id || !nodes.has(parentId)) continue;\n const kids = childrenOf.get(parentId) ?? [];\n kids.push(id);\n childrenOf.set(parentId, kids);\n }\n\n const isChild = new Set<string>();\n for (const kids of childrenOf.values()) for (const k of kids) isChild.add(k);\n\n const build = (id: string, seen: Set<string>): SceneTreeNode => {\n const node = nodes.get(id)!;\n const children: SceneTreeNode[] = [];\n if (!seen.has(id)) {\n seen.add(id);\n for (const kid of childrenOf.get(id) ?? []) children.push(build(kid, seen));\n seen.delete(id);\n }\n return { ...node, children };\n };\n\n return order.filter((id) => !isChild.has(id)).map((id) => build(id, new Set()));\n}\r\n","import { promises as fs } from \"node:fs\";\nimport path from \"node:path\";\nimport { ENDPOINTS, type EasyTwinClient } from \"./client\";\n\nexport interface UploadProgress {\n phase: \"collect\" | \"upload\";\n /** collect 阶段为已统计字节;upload 阶段为已发送字节。 */\n current: number;\n total: number;\n /** upload 阶段当前文件相对路径。 */\n file?: string;\n}\n\nexport interface UploadOptions {\n onProgress?: (progress: UploadProgress) => void;\n /** 自定义忽略规则,默认只跳过 .git。 */\n ignore?: (relPath: string) => boolean;\n}\n\nexport interface UploadResult {\n fileCount: number;\n byteCount: number;\n /** 本地测试模式(dry-run)时置 true:未真正上传。 */\n mock?: boolean;\n}\n\nconst DEFAULT_IGNORED_DIRS = new Set([\".git\"]);\n\nexport interface CollectedFile {\n relPath: string;\n absPath: string;\n size: number;\n}\n\n/** 递归收集目录下全部文件(默认跳过 .git),按相对路径升序排列,保证顺序稳定。 */\nexport async function collectFiles(dir: string, ignore: (relPath: string) => boolean = () => false): Promise<CollectedFile[]> {\n const files: CollectedFile[] = [];\n async function walk(current: string, rel: string): Promise<void> {\n const entries = await fs.readdir(current, { withFileTypes: true });\n for (const entry of entries) {\n const relPath = path.posix.join(rel, entry.name);\n if (ignore(relPath)) continue;\n if (entry.isDirectory()) {\n if (DEFAULT_IGNORED_DIRS.has(entry.name)) continue;\n await walk(path.join(current, entry.name), relPath);\n } else if (entry.isFile()) {\n const absPath = path.join(current, entry.name);\n const stat = await fs.stat(absPath);\n files.push({ relPath, absPath, size: stat.size });\n }\n }\n }\n await walk(dir, \"\");\n files.sort((a, b) => a.relPath.localeCompare(b.relPath));\n return files;\n}\n\nexport function buildMultipartBody(\n boundary: string,\n fields: Record<string, string>,\n file: { name: string; content: Buffer }\n): Buffer {\n const parts: Buffer[] = [];\n for (const [key, value] of Object.entries(fields)) {\n parts.push(\n Buffer.from(`--${boundary}\\r\\nContent-Disposition: form-data; name=\"${key}\"\\r\\n\\r\\n${value}\\r\\n`, \"utf8\")\n );\n }\n parts.push(\n Buffer.from(\n `--${boundary}\\r\\nContent-Disposition: form-data; name=\"file\"; filename=\"${file.name}\"\\r\\nContent-Type: application/octet-stream\\r\\n\\r\\n`,\n \"utf8\"\n )\n );\n parts.push(file.content);\n parts.push(Buffer.from(`\\r\\n--${boundary}--\\r\\n`, \"utf8\"));\n return Buffer.concat(parts);\n}\n\n/** 本地测试模式(dry-run):只收集与统计,不发网络请求。 */\nasync function mockUpload(dir: string, options: UploadOptions): Promise<UploadResult> {\n const ignore = options.ignore ?? (() => false);\n const files = await collectFiles(dir, ignore);\n const total = files.reduce((sum, f) => sum + f.size, 0);\n options.onProgress?.({ phase: \"collect\", current: total, total });\n options.onProgress?.({ phase: \"upload\", current: total, total });\n return { fileCount: files.length, byteCount: total, mock: true };\n}\n\n/** 全量覆盖上传目录:逐文件 multipart 上传,带进度回调(供插件进度条)。本地测试模式(mock)时退化为 dry-run。 */\nexport async function uploadDirectory(client: EasyTwinClient, dir: string, options: UploadOptions = {}): Promise<UploadResult> {\n if (client.mock) return mockUpload(dir, options);\n const ignore = options.ignore ?? (() => false);\n const files = await collectFiles(dir, ignore);\n const total = files.reduce((sum, f) => sum + f.size, 0);\n options.onProgress?.({ phase: \"collect\", current: total, total });\n\n // 进度以原始文件字节计(multipart 头开销不计入),逐文件上报,供进度条/进度输出使用。\n let sent = 0;\n for (const file of files) {\n const content = await fs.readFile(file.absPath);\n const boundary = `----easytwin${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`;\n const body = buildMultipartBody(\n boundary,\n // TODO(swagger): multipart 字段名/结构待定,当前为占位约定。\n { path: file.relPath },\n { name: path.basename(file.relPath), content }\n );\n await client.upload(ENDPOINTS.upload, { boundary, body });\n sent += file.size;\n options.onProgress?.({ phase: \"upload\", current: sent, total, file: file.relPath });\n }\n return { fileCount: files.length, byteCount: sent };\n}","import { existsSync, promises as fs } from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nexport type SyncTarget = \"cursor\" | \"claude\" | \"codex\" | \"qoder\";\nexport type SyncTargetArg = SyncTarget | \"all\";\n\n/** 六件套清单(5.1),顺序即输出顺序。 */\nexport const SKILL_NAMES = [\n \"easytwin-render\",\n \"easytwin-core\",\n \"easytwin-develop\",\n \"easytwin-bootstrap\",\n \"easytwin-scene\",\n \"easytwin-upload\",\n] as const;\n\nexport type SyncAction = \"created\" | \"updated\" | \"unchanged\";\n\nexport interface SyncEntrySummary {\n name: string;\n action: SyncAction;\n}\n\nexport interface SyncSummary {\n target: SyncTarget;\n entries: SyncEntrySummary[];\n codex?: SyncEntrySummary;\n}\n\nexport interface SyncOptions {\n cwd: string;\n targets?: SyncTargetArg | SyncTarget[];\n version?: string;\n /** skills 源目录;缺省按 devkit 包内 skills/ 解析(CLI)。插件 bundle 场景显式传入插件内目录。 */\n sourceDir?: string;\n /** runtime index.d.ts 源文件;缺省按包内 src/lib 或 dist/runtime-types 解析。插件必须显式传入。 */\n typesSourceFile?: string;\n}\n\nexport const CODEX_MARKER_BEGIN = \"<!-- EASYTWIN-SKILLS:BEGIN -->\";\nexport const CODEX_MARKER_END = \"<!-- EASYTWIN-SKILLS:END -->\";\nexport const META_FILE_NAME = \".easytwin-meta.json\";\n\n/** 用户项目内 runtime 类型落地目录(posix)。 */\nexport const EASYTWIN_TYPES_DIR = \".easytwin/types\";\n\nexport const MINIMAL_TSCONFIG = `${JSON.stringify(\n {\n compilerOptions: {\n target: \"ES2022\",\n module: \"ESNext\",\n moduleResolution: \"bundler\",\n strict: true,\n skipLibCheck: true,\n noEmit: true,\n paths: {\n \"@easytwin/runtime\": [\".easytwin/types\"],\n },\n },\n include: [\"src/**/*.ts\"],\n },\n null,\n 2\n)}\\n`;\n\nexport const TSCONFIG_PATHS_HINT = `已有 tsconfig.json,未改动。请在 compilerOptions 中加入:\n \"skipLibCheck\": true,\n \"paths\": {\n \"@easytwin/runtime\": [\".easytwin/types\"]\n }`;\n\nconst RUN_CONTEXT_DECL = `\n/** 预览页 Run 按钮注入的运行上下文(见 D12)。 */\nexport interface EasyTwinRunContext {\n engine: RuntimeEngine;\n runtime: typeof import(\"@easytwin/runtime\");\n sceneJson: SceneJson;\n}\n`;\n\nexport function buildRuntimeTypesContent(sourceDts: string): string {\n const trimmed = sourceDts.replace(/\\s+$/, \"\");\n if (trimmed.includes(\"export interface EasyTwinRunContext\")) return `${trimmed}\\n`;\n return `${trimmed}\\n${RUN_CONTEXT_DECL}`;\n}\n\nexport function normalizeTargets(target: SyncTargetArg | SyncTarget[] = \"all\"): SyncTarget[] {\n if (target === \"all\") return [\"cursor\", \"claude\", \"codex\", \"qoder\"];\n if (Array.isArray(target)) return [...new Set(target)];\n return [target];\n}\n\n/** skills 源目录默认解析:CLI 下为 devkit 包内 skills/。插件 bundle 到 CJS 后 import.meta.url 为空,\n * 必须通过 SyncOptions.sourceDir / detectSkillsStatus 的 sourceDir 显式传入(见 esbuild.config.mjs 注释)。 */\nexport function resolveSkillsSourceDir(): string {\n if (typeof import.meta.url !== \"string\" || import.meta.url.length === 0) {\n throw new Error(\"无法定位 skills 源目录:插件(bundle/CJS)场景必须显式传入 sourceDir\");\n }\n const here = path.dirname(fileURLToPath(import.meta.url));\n return path.resolve(here, \"..\", \"skills\");\n}\n\n/** runtime 类型声明源:开发态 src/lib/index.d.ts,发布态 dist/runtime-types/index.d.ts。 */\nexport function resolveRuntimeTypesSourceFile(): string {\n if (typeof import.meta.url !== \"string\" || import.meta.url.length === 0) {\n throw new Error(\"无法定位 runtime 类型:插件(bundle/CJS)场景必须显式传入 typesSourceFile\");\n }\n const here = path.dirname(fileURLToPath(import.meta.url));\n const fromDist = path.join(here, \"runtime-types\", \"index.d.ts\");\n const fromSrc = path.resolve(here, \"lib\", \"index.d.ts\");\n if (existsSync(fromDist)) return fromDist;\n if (existsSync(fromSrc)) return fromSrc;\n throw new Error(`无法定位 runtime 类型声明,已尝试:${fromDist}, ${fromSrc}`);\n}\n\nasync function readJson<T>(file: string): Promise<T> {\n return JSON.parse(await fs.readFile(file, \"utf8\")) as T;\n}\n\nexport async function readDevkitVersion(skillsDir = resolveSkillsSourceDir()): Promise<string> {\n const marker = path.join(skillsDir, \".easytwin-source.json\");\n try {\n const meta = await readJson<{ version: string }>(marker);\n if (typeof meta.version === \"string\") return meta.version;\n } catch {\n // 无构建期标记,退回读包内 package.json\n }\n try {\n const pkg = await readJson<{ version: string }>(path.resolve(skillsDir, \"..\", \"package.json\"));\n return pkg.version;\n } catch {\n return \"0.0.0\";\n }\n}\n\nasync function copyDir(src: string, dest: string): Promise<void> {\n await fs.mkdir(dest, { recursive: true });\n const entries = await fs.readdir(src, { withFileTypes: true });\n for (const entry of entries) {\n const s = path.join(src, entry.name);\n const d = path.join(dest, entry.name);\n if (entry.isDirectory()) await copyDir(s, d);\n else if (entry.isFile()) await fs.copyFile(s, d);\n }\n}\n\n/** 比较源目录的每个文件与目标是否逐字节一致(忽略目标里额外的文件,如版本标记)。 */\nasync function dirMatches(src: string, dest: string): Promise<boolean> {\n let sourceEntries: string[];\n try {\n sourceEntries = await fs.readdir(src);\n } catch {\n return false;\n }\n for (const name of sourceEntries) {\n const s = path.join(src, name);\n const d = path.join(dest, name);\n const sStat = await fs.stat(s);\n if (sStat.isDirectory()) {\n if (!(await dirMatches(s, d))) return false;\n } else {\n let dStat;\n try {\n dStat = await fs.stat(d);\n } catch {\n return false;\n }\n if (!sStat.isFile() || !dStat.isFile() || sStat.size !== dStat.size) return false;\n if (!(await fs.readFile(s)).equals(await fs.readFile(d))) return false;\n }\n }\n return true;\n}\n\nfunction actionFor(exists: boolean, matches: boolean): SyncAction {\n if (!exists) return \"created\";\n return matches ? \"unchanged\" : \"updated\";\n}\n\nasync function writeMeta(destDir: string, version: string): Promise<void> {\n const meta = { name: \"@easytwin/devkit\", version };\n await fs.writeFile(path.join(destDir, META_FILE_NAME), JSON.stringify(meta, null, 2) + \"\\n\", \"utf8\");\n}\n\n/** cursor / claude / qoder:复制技能目录到用户项目,按内容覆盖,写入版本标记。 */\nasync function syncToDir(target: SyncTarget, sourceRoot: string, targetRoot: string, version: string): Promise<SyncSummary> {\n const entries: SyncEntrySummary[] = [];\n for (const name of SKILL_NAMES) {\n const src = path.join(sourceRoot, name);\n const dest = path.join(targetRoot, name);\n const exists = await fs\n .stat(dest)\n .then(() => true)\n .catch(() => false);\n const matches = await dirMatches(src, dest);\n const action = actionFor(exists, matches);\n if (action !== \"unchanged\") {\n await fs.rm(dest, { recursive: true, force: true });\n await copyDir(src, dest);\n }\n entries.push({ name, action });\n }\n await writeMeta(targetRoot, version);\n return { target, entries };\n}\n\n/** codex:在用户项目 AGENTS.md 注入带标记的引用段(整段替换,幂等)。 */\nfunction buildCodexSegment(version: string): string {\n return [\n CODEX_MARKER_BEGIN,\n \"<!-- 本段由 `easytwin skills sync` 管理,可整段替换;手动修改会被下次同步覆盖。 -->\",\n `EasyTwin 开发技能(由 @easytwin/devkit v${version} 同步):`,\n \"\",\n \"- `easytwin-develop`:工作流总纲,开始 EasyTwin 开发前必读。\",\n \"- `easytwin-bootstrap`:初始化应用级凭证(easytwin.config.json)。\",\n \"- `easytwin-scene`:列出场景、拉取场景 JSON。\",\n \"- `easytwin-render`:用 twin runtime 开发渲染功能(主力)。\",\n \"- `easytwin-core`:引擎内核 API(RuntimeEngine/基类/Time/相机/物理)。\",\n \"- `easytwin-upload`:全量上传开发产物目录(覆盖不可逆)。\",\n \"\",\n \"详细用法见各技能;重新同步运行 `easytwin skills sync --target codex`。\",\n CODEX_MARKER_END,\n ].join(\"\\n\");\n}\n\nasync function syncToCodex(sourceRoot: string, cwd: string, version: string): Promise<SyncSummary> {\n const agentsFile = path.join(cwd, \"AGENTS.md\");\n const segment = buildCodexSegment(version);\n let content = \"\";\n let exists = true;\n try {\n content = await fs.readFile(agentsFile, \"utf8\");\n } catch {\n exists = false;\n }\n const start = content.indexOf(CODEX_MARKER_BEGIN);\n const end = content.indexOf(CODEX_MARKER_END);\n let action: SyncAction;\n let next: string;\n if (start === -1 || end === -1 || end < start) {\n action = exists ? \"updated\" : \"created\";\n next = content.length > 0 && !content.endsWith(\"\\n\") ? content + \"\\n\\n\" : content + (content.length > 0 ? \"\\n\" : \"\");\n next += segment + \"\\n\";\n } else {\n const current = content.slice(start, end + CODEX_MARKER_END.length);\n action = current === segment ? \"unchanged\" : \"updated\";\n next = content.slice(0, start) + segment + content.slice(end + CODEX_MARKER_END.length);\n }\n if (action !== \"unchanged\") {\n await fs.mkdir(cwd, { recursive: true });\n await fs.writeFile(agentsFile, next, \"utf8\");\n }\n return { target: \"codex\", entries: [], codex: { name: \"AGENTS.md\", action } };\n}\n\nexport interface TypesSyncSummary {\n action: SyncAction;\n tsconfig: \"created\" | \"unchanged\" | \"manual-paths\";\n pathsHint?: string;\n}\n\nexport interface SyncSkillsResult {\n summaries: SyncSummary[];\n types: TypesSyncSummary;\n}\n\nasync function syncRuntimeTypes(cwd: string, typesSourceFile: string): Promise<TypesSyncSummary> {\n const destDir = path.join(cwd, \".easytwin\", \"types\");\n const destFile = path.join(destDir, \"index.d.ts\");\n const content = buildRuntimeTypesContent(await fs.readFile(typesSourceFile, \"utf8\"));\n let exists = true;\n let current = \"\";\n try {\n current = await fs.readFile(destFile, \"utf8\");\n } catch {\n exists = false;\n }\n const action = actionFor(exists, current === content);\n if (action !== \"unchanged\") {\n await fs.mkdir(destDir, { recursive: true });\n await fs.writeFile(destFile, content, \"utf8\");\n }\n\n const tsconfigPath = path.join(cwd, \"tsconfig.json\");\n let tsconfig: TypesSyncSummary[\"tsconfig\"];\n let pathsHint: string | undefined;\n try {\n const existing = await fs.readFile(tsconfigPath, \"utf8\");\n if (existing === MINIMAL_TSCONFIG) tsconfig = \"unchanged\";\n else {\n tsconfig = \"manual-paths\";\n pathsHint = TSCONFIG_PATHS_HINT;\n }\n } catch {\n await fs.writeFile(tsconfigPath, MINIMAL_TSCONFIG, \"utf8\");\n tsconfig = \"created\";\n }\n return { action, tsconfig, pathsHint };\n}\n\nexport async function syncSkills(options: SyncOptions): Promise<SyncSkillsResult> {\n const targets = normalizeTargets(options.targets ?? \"all\");\n const sourceRoot = options.sourceDir ?? resolveSkillsSourceDir();\n const version = options.version ?? (await readDevkitVersion(sourceRoot));\n const summaries: SyncSummary[] = [];\n for (const target of targets) {\n if (target === \"cursor\") summaries.push(await syncToDir(\"cursor\", sourceRoot, path.join(options.cwd, \".cursor\", \"skills\"), version));\n else if (target === \"claude\") summaries.push(await syncToDir(\"claude\", sourceRoot, path.join(options.cwd, \".claude\", \"skills\"), version));\n else if (target === \"qoder\") summaries.push(await syncToDir(\"qoder\", sourceRoot, path.join(options.cwd, \".qoder\", \"skills\"), version));\n else summaries.push(await syncToCodex(sourceRoot, options.cwd, version));\n }\n const types = await syncRuntimeTypes(options.cwd, options.typesSourceFile ?? resolveRuntimeTypesSourceFile());\n return { summaries, types };\n}\n\nexport interface SkillsTargetStatus {\n target: SyncTarget;\n synced: boolean;\n reason?: string;\n}\n\nexport interface SkillsStatus {\n cwd: string;\n targets: SkillsTargetStatus[];\n typesSynced: boolean;\n}\n\n/** 供插件\"缺失/过期时提示一次\"使用:检测各目标当前同步状态。 */\nexport async function detectSkillsStatus(cwd: string, sourceDir?: string): Promise<SkillsStatus> {\n const sourceRoot = sourceDir ?? resolveSkillsSourceDir();\n const version = await readDevkitVersion(sourceRoot);\n const targets: SkillsTargetStatus[] = [];\n\n const dirTargets = [\"cursor\", \"claude\", \"qoder\"] as const;\n const DIR_ROOTS: Record<(typeof dirTargets)[number], string> = {\n cursor: \".cursor/skills\",\n claude: \".claude/skills\",\n qoder: \".qoder/skills\",\n };\n\n for (const target of dirTargets) {\n const root = path.join(cwd, DIR_ROOTS[target]);\n const missing = [];\n for (const name of SKILL_NAMES) {\n if (!(await fs.stat(path.join(root, name)).then(() => true).catch(() => false))) missing.push(name);\n }\n let stale = false;\n try {\n const meta = await readJson<{ version: string }>(path.join(root, META_FILE_NAME));\n stale = meta.version !== version;\n } catch {\n stale = missing.length === 0; // 目录存在但无版本标记\n }\n targets.push({\n target,\n synced: missing.length === 0 && !stale,\n reason: missing.length > 0 ? `缺少 ${missing.join(\", \")}` : stale ? \"版本过期\" : undefined,\n });\n }\n\n let agentsContent = \"\";\n try {\n agentsContent = await fs.readFile(path.join(cwd, \"AGENTS.md\"), \"utf8\");\n } catch {\n // AGENTS.md 不存在视为未同步\n }\n const hasCodex =\n agentsContent.includes(CODEX_MARKER_BEGIN) && agentsContent.includes(CODEX_MARKER_END) && agentsContent.includes(`v${version}`);\n targets.push({ target: \"codex\", synced: hasCodex, reason: hasCodex ? undefined : \"AGENTS.md 缺少同步标记段\" });\n\n let typesSynced = false;\n try {\n const dts = await fs.readFile(path.join(cwd, \".easytwin\", \"types\", \"index.d.ts\"), \"utf8\");\n typesSynced = dts.includes(\"export interface EasyTwinRunContext\");\n } catch {\n typesSynced = false;\n }\n\n return { cwd, targets, typesSynced };\n}\n","import * as esbuild from \"esbuild-wasm\";\nimport { promises as fs } from \"node:fs\";\nimport path from \"node:path\";\n\n/** 用户代码固定入口(相对工作区根)。 */\nexport const USER_ENTRY = \"src/main.ts\";\n/** CLI 缺省输出路径(相对工作区根)。 */\nexport const DEFAULT_BUNDLE_OUT = \"dist/main.js\";\n/** 白名单唯一允许的裸模块。 */\nexport const RUNTIME_MODULE = \"@easytwin/runtime\";\n\nexport class BundleError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"BundleError\";\n }\n}\n\nexport interface BundleUserCodeOptions {\n cwd: string;\n /** 缺省不落盘,只返回产物字符串(插件内存编译);CLI 传入绝对/相对路径。 */\n outFile?: string;\n}\n\nexport interface BundleResult {\n code: string;\n warnings: string[];\n}\n\nfunction isRelativeOrAbsolute(spec: string): boolean {\n return spec.startsWith(\"./\") || spec.startsWith(\"../\") || spec.startsWith(\"/\") || path.isAbsolute(spec);\n}\n\nfunction whitelistPlugin(): esbuild.Plugin {\n return {\n name: \"easytwin-whitelist\",\n setup(build) {\n build.onResolve({ filter: /.*/ }, (args) => {\n if (args.kind === \"entry-point\") return undefined;\n if (isRelativeOrAbsolute(args.path)) return undefined;\n if (args.path === RUNTIME_MODULE) return { path: args.path, external: true };\n return {\n errors: [\n {\n text: `检测到外部依赖 ${args.path},用户代码只能依赖 @easytwin/runtime。不要 npm install,类型由 easytwin skills sync 分发,运行走预览页 Run 或 easytwin bundle。`,\n },\n ],\n };\n });\n },\n };\n}\n\nfunction formatEsbuildMessages(messages: readonly esbuild.Message[]): string {\n return messages\n .map((m) => {\n const loc = m.location ? `${m.location.file}:${m.location.line}:${m.location.column}: ` : \"\";\n return `${loc}${m.text}`;\n })\n .join(\"\\n\");\n}\n\n/** 打包工作区 src/main.ts。产物标准 ESM + inline sourcemap;仅允许依赖 @easytwin/runtime。 */\nexport async function bundleUserCode(options: BundleUserCodeOptions): Promise<BundleResult> {\n const cwd = path.resolve(options.cwd);\n const entry = path.join(cwd, USER_ENTRY);\n try {\n await fs.access(entry);\n } catch {\n throw new BundleError(\n `未找到入口文件 ${entry}。请在工作区创建 src/main.ts,并 \\`export default async function main(ctx)\\`。`\n );\n }\n\n let result: esbuild.BuildResult;\n try {\n result = await esbuild.build({\n absWorkingDir: cwd,\n entryPoints: [entry],\n bundle: true,\n write: false,\n format: \"esm\",\n platform: \"browser\",\n target: \"es2022\",\n sourcemap: \"inline\",\n logLevel: \"silent\",\n plugins: [whitelistPlugin()],\n });\n } catch (err) {\n const errors = (err as { errors?: esbuild.Message[] }).errors;\n if (Array.isArray(errors) && errors.length > 0) {\n throw new BundleError(formatEsbuildMessages(errors));\n }\n throw err instanceof Error ? new BundleError(err.message) : err;\n }\n\n if (result.errors.length > 0) {\n throw new BundleError(formatEsbuildMessages(result.errors));\n }\n const file = result.outputFiles?.[0];\n if (!file) throw new BundleError(\"打包未产出文件\");\n const code = file.text;\n const warnings = result.warnings.map((w) => formatEsbuildMessages([w]));\n\n if (options.outFile) {\n const outFile = path.isAbsolute(options.outFile) ? options.outFile : path.join(cwd, options.outFile);\n await fs.mkdir(path.dirname(outFile), { recursive: true });\n await fs.writeFile(outFile, code, \"utf8\");\n }\n\n return { code, warnings };\n}\n\n/** 若工作区尚未同步 runtime 类型,返回提示文案;已同步则返回 undefined。 */\nexport async function typesMissingHint(cwd: string): Promise<string | undefined> {\n try {\n await fs.access(path.join(cwd, \".easytwin\", \"types\", \"index.d.ts\"));\n return undefined;\n } catch {\n return \"未找到 .easytwin/types,运行 `easytwin skills sync` 以分发 @easytwin/runtime 类型声明。\";\n }\n}\n\nconst SOURCEMAP_RE = /sourceMappingURL=data:application\\/json(?:;charset=[^;]+)?;base64,([A-Za-z0-9+/]+=*)/;\n\ninterface SourceMapJson {\n sources?: string[];\n mappings?: string;\n}\n\ninterface OriginalPos {\n source: string;\n line: number;\n column: number;\n}\n\ninterface MappingSeg {\n generatedColumn: number;\n source: string;\n originalLine: number;\n originalColumn: number;\n}\n\nconst VLQ_CHARS = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n\nfunction decodeVLQValues(str: string): number[] {\n const values: number[] = [];\n let i = 0;\n while (i < str.length) {\n let result = 0;\n let shift = 0;\n let continuation = true;\n while (continuation) {\n if (i >= str.length) return values;\n const digit = VLQ_CHARS.indexOf(str[i++] ?? \"\");\n if (digit < 0) return values;\n continuation = (digit & 32) !== 0;\n result += (digit & 31) << shift;\n shift += 5;\n }\n values.push(result & 1 ? -(result >> 1) : result >> 1);\n }\n return values;\n}\n\nfunction decodeMappings(map: SourceMapJson): MappingSeg[][] {\n const sources = map.sources ?? [];\n const lines = (map.mappings ?? \"\").split(\";\");\n let sourceIndex = 0;\n let originalLine = 0;\n let originalColumn = 0;\n const decoded: MappingSeg[][] = [];\n for (const line of lines) {\n let generatedColumn = 0;\n const segs: MappingSeg[] = [];\n if (line) {\n for (const raw of line.split(\",\")) {\n if (!raw) continue;\n const nums = decodeVLQValues(raw);\n if (nums[0] === undefined) continue;\n generatedColumn += nums[0];\n if (nums.length >= 4) {\n sourceIndex += nums[1] ?? 0;\n originalLine += nums[2] ?? 0;\n originalColumn += nums[3] ?? 0;\n segs.push({\n generatedColumn,\n source: sources[sourceIndex] ?? USER_ENTRY,\n originalLine,\n originalColumn,\n });\n }\n }\n }\n decoded.push(segs);\n }\n return decoded;\n}\n\n/** generated line/col 均为 1-based(与 Error.stack 一致)。无精确段时回退到上一行最后一个映射。 */\nfunction originalPositionFor(map: SourceMapJson, line: number, column: number): OriginalPos | undefined {\n const decoded = decodeMappings(map);\n for (let i = line - 1; i >= 0; i--) {\n const segs = decoded[i];\n if (!segs || segs.length === 0) continue;\n const col = i === line - 1 ? column - 1 : Number.POSITIVE_INFINITY;\n let best = segs[0];\n for (const seg of segs) {\n if (seg.generatedColumn <= col) best = seg;\n else break;\n }\n if (!best) continue;\n return { source: best.source, line: best.originalLine + 1, column: best.originalColumn };\n }\n return undefined;\n}\n\nexport function extractInlineSourceMap(code: string): SourceMapJson | undefined {\n const m = code.match(SOURCEMAP_RE);\n if (!m?.[1]) return undefined;\n try {\n return JSON.parse(Buffer.from(m[1], \"base64\").toString(\"utf8\")) as SourceMapJson;\n } catch {\n return undefined;\n }\n}\n\n/**\n * 把 bundled JS 的 stack(blob: 行号)映射回 TS 源码行号。\n * 映射失败时原样返回。\n */\nexport function remapErrorStack(stack: string, bundledCode: string): string {\n const map = extractInlineSourceMap(bundledCode);\n if (!map?.mappings) return stack;\n return stack.replace(/(blob:[^\\s)]+?):(\\d+):(\\d+)/g, (full, _url: string, line: string, col: string) => {\n const orig = originalPositionFor(map, Number(line), Number(col));\n if (!orig) return full;\n return `${orig.source}:${orig.line}:${orig.column}`;\n });\n}\n"],"mappings":"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","names":["path","fs","path","path","fs","build","fs","path","fs","path","fs","path","fileURLToPath","fs","path","build"]}