@easytwin/devkit 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +72 -0
- package/dist/bin.js +586 -0
- package/dist/bin.js.map +1 -0
- package/dist/index.d.ts +181 -0
- package/dist/index.js +551 -0
- package/dist/index.js.map +1 -0
- package/package.json +45 -0
- package/skills/easytwin-bootstrap/SKILL.md +26 -0
- package/skills/easytwin-core/SKILL.md +25 -0
- package/skills/easytwin-core/references/base-classes.md +32 -0
- package/skills/easytwin-core/references/camera.md +33 -0
- package/skills/easytwin-core/references/engine.md +88 -0
- package/skills/easytwin-core/references/physics.md +30 -0
- package/skills/easytwin-core/references/virtual-components.md +169 -0
- package/skills/easytwin-develop/SKILL.md +25 -0
- package/skills/easytwin-render/SKILL.md +42 -0
- package/skills/easytwin-render/references/intro.md +81 -0
- package/skills/easytwin-render/references/lifecycle-events.md +79 -0
- package/skills/easytwin-render/references/rendering-apis.md +116 -0
- package/skills/easytwin-render/references/scene-and-assets.md +94 -0
- package/skills/easytwin-scene/SKILL.md +26 -0
- package/skills/easytwin-upload/SKILL.md +21 -0
package/dist/bin.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/bin.ts","../src/config.ts","../src/client.ts","../src/scene.ts","../src/upload.ts","../src/skills.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { realpathSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { createInterface } from \"node:readline/promises\";\nimport { stdin, stdout } from \"node:process\";\nimport { pathToFileURL } from \"node:url\";\nimport path from \"node:path\";\nimport { CONFIG_FILE_NAME, DEFAULT_BASE_URL, initConfig, loadConfig, type EasyTwinConfig } from \"./config\";\nimport { EasyTwinClient } from \"./client\";\nimport { listScenes, pullScene, saveSceneJson } from \"./scene\";\nimport { uploadDirectory } from \"./upload\";\nimport { syncSkills, type SyncTargetArg } from \"./skills\";\n\nasync function readVersion(): Promise<string> {\n try {\n const pkg = JSON.parse(await readFile(new URL(\"../package.json\", import.meta.url), \"utf8\")) as { version: string };\n return pkg.version;\n } catch {\n return \"0.0.0\";\n }\n}\n\nasync function prompt(question: string): Promise<string> {\n const rl = createInterface({ input: stdin, output: stdout });\n try {\n return (await rl.question(question)).trim();\n } finally {\n rl.close();\n }\n}\n\nexport interface InitFlags {\n appId?: string;\n appSecret?: string;\n baseUrl?: string;\n}\n\nexport async function runInit(cwd: string, flags: InitFlags): Promise<void> {\n const appId = flags.appId ?? (await prompt(\"App ID: \"));\n const appSecret = flags.appSecret ?? (await prompt(\"App Secret: \"));\n const baseUrlInput = flags.baseUrl ?? (await prompt(`Base URL(缺省 ${DEFAULT_BASE_URL}): `));\n const input: EasyTwinConfig = { appId, appSecret };\n if (baseUrlInput.trim().length > 0) input.baseUrl = baseUrlInput.trim();\n const result = await initConfig(input, cwd);\n console.log(`已生成 ${result.configFile}`);\n console.log(\n result.gitignore.added\n ? `已把 ${CONFIG_FILE_NAME} 追加进 .gitignore(防止密钥入库)`\n : `.gitignore 已包含 ${CONFIG_FILE_NAME}`\n );\n}\n\nexport function buildProgram(cwd: string, version = \"0.0.0\"): Command {\n const program = new Command();\n program.name(\"easytwin\").description(\"EasyTwin DevKit 命令行工具\").version(version, \"-V, --version\");\n\n program\n .command(\"init\")\n .description(\"交互式生成 easytwin.config.json,并追加进 .gitignore\")\n .option(\"--app-id <id>\", \"App ID(跳过交互)\")\n .option(\"--app-secret <secret>\", \"App Secret(跳过交互)\")\n .option(\"--base-url <url>\", \"Base URL(跳过交互)\")\n .action((opts: InitFlags) => runInit(cwd, opts));\n\n const scene = program.command(\"scene\").description(\"场景管理\");\n scene\n .command(\"list\")\n .description(\"列出凭据可见的场景\")\n .action(async () => {\n const config = await loadConfig(cwd);\n const client = new EasyTwinClient(config);\n const scenes = await listScenes(client);\n if (scenes.length === 0) {\n console.log(\"(无场景)\");\n return;\n }\n for (const s of scenes) console.log(`${s.id}\\t${s.name}`);\n });\n scene\n .command(\"pull <id>\")\n .description(\"拉取场景 JSON 到本地\")\n .option(\"-o, --out <path>\", \"输出文件路径(缺省 ./<id>.scene.json)\")\n .action(async (id: string, opts: { out?: string }) => {\n const config = await loadConfig(cwd);\n const client = new EasyTwinClient(config);\n const scene = await pullScene(client, id);\n const out = opts.out ?? path.join(cwd, `${id}.scene.json`);\n const file = await saveSceneJson(scene, out);\n console.log(`已保存到 ${file}`);\n });\n\n program\n .command(\"upload <dir>\")\n .description(\"全量覆盖上传目录(不可逆)\")\n .action(async (dir: string) => {\n const config = await loadConfig(cwd);\n const client = new EasyTwinClient(config);\n await uploadDirectory(client, dir, {\n onProgress: (p) => {\n if (p.phase === \"upload\") {\n process.stderr.write(`\\r已上传 ${p.current}/${p.total} bytes${p.file ? ` (${p.file})` : \"\"}`);\n }\n },\n });\n process.stderr.write(\"\\n\");\n });\n\n const skills = program.command(\"skills\").description(\"skills 同步到用户项目\");\n skills\n .command(\"sync\")\n .description(\"同步 skills 到用户项目(cursor/claude/codex),缺省 all\")\n .option(\"--target <target>\", \"cursor|claude|codex|all\", \"all\")\n .action(async (opts: { target: string }) => {\n const summaries = await syncSkills({ cwd, targets: opts.target as SyncTargetArg });\n for (const s of summaries) {\n if (s.codex) {\n console.log(`[codex] AGENTS.md: ${s.codex.action}`);\n continue;\n }\n for (const e of s.entries) console.log(`[${s.target}] ${e.name}: ${e.action}`);\n }\n });\n\n return program;\n}\n\nexport async function main(argv: string[], cwd = process.cwd()): Promise<void> {\n const version = await readVersion();\n const program = buildProgram(cwd, version);\n await program.parseAsync(argv, { from: \"user\" });\n}\n\n// realpath 处理经符号链接调用(如 pnpm 链接的 bin)时 argv[1] 与 import.meta.url 不一致的问题\nconst isMain =\n process.argv[1] !== undefined && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href;\nif (isMain) {\n main(process.argv.slice(2)).catch((err: unknown) => {\n console.error(err instanceof Error ? err.message : String(err));\n process.exitCode = 1;\n });\n}","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","URL","path","fs","path","fs","path","fs","path","fs","path","fs","path","scene","path"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/** 配置文件名(项目根)。 */
|
|
2
|
+
declare const CONFIG_FILE_NAME = "easytwin.config.json";
|
|
3
|
+
/**
|
|
4
|
+
* 官方缺省域名。
|
|
5
|
+
* TODO(外部输入): 域名值待定。该常量是全仓唯一收口点,只允许写在这里。
|
|
6
|
+
*/
|
|
7
|
+
declare const DEFAULT_BASE_URL = "https://api.easytwin.example.com";
|
|
8
|
+
/** 磁盘上的配置结构(4.3 配置契约)。 */
|
|
9
|
+
interface EasyTwinConfig {
|
|
10
|
+
appId: string;
|
|
11
|
+
appSecret: string;
|
|
12
|
+
baseUrl?: string;
|
|
13
|
+
}
|
|
14
|
+
/** 解析 + 环境变量覆盖后的最终配置。 */
|
|
15
|
+
interface ResolvedConfig {
|
|
16
|
+
appId: string;
|
|
17
|
+
appSecret: string;
|
|
18
|
+
baseUrl: string;
|
|
19
|
+
source: "file" | "env";
|
|
20
|
+
}
|
|
21
|
+
declare class ConfigError extends Error {
|
|
22
|
+
constructor(message: string);
|
|
23
|
+
}
|
|
24
|
+
declare function configFilePath(cwd: string): string;
|
|
25
|
+
declare function parseConfig(raw: string): EasyTwinConfig;
|
|
26
|
+
declare function validateConfigShape(value: unknown): EasyTwinConfig;
|
|
27
|
+
declare function readConfigFile(cwd: string): Promise<EasyTwinConfig>;
|
|
28
|
+
/** 环境变量优先于文件(CI 与不落盘场景)。 */
|
|
29
|
+
declare function resolveConfig(file: EasyTwinConfig, env?: NodeJS.ProcessEnv): ResolvedConfig;
|
|
30
|
+
declare function loadConfig(cwd: string, env?: NodeJS.ProcessEnv): Promise<ResolvedConfig>;
|
|
31
|
+
declare function writeConfigFile(cwd: string, config: EasyTwinConfig): Promise<string>;
|
|
32
|
+
declare const GITIGNORE_FILE_NAME = ".gitignore";
|
|
33
|
+
declare const GITIGNORE_ENTRY = "easytwin.config.json";
|
|
34
|
+
interface GitignoreResult {
|
|
35
|
+
created: boolean;
|
|
36
|
+
added: boolean;
|
|
37
|
+
}
|
|
38
|
+
/** 把配置文件名追加进 .gitignore(无则创建),幂等:已存在条目时不重复追加。 */
|
|
39
|
+
declare function appendGitignore(cwd: string): Promise<GitignoreResult>;
|
|
40
|
+
interface InitResult {
|
|
41
|
+
configFile: string;
|
|
42
|
+
gitignore: GitignoreResult;
|
|
43
|
+
}
|
|
44
|
+
/** init 的 lib 实现:生成配置文件 + 追加 gitignore(与插件配置向导共用同一实现)。 */
|
|
45
|
+
declare function initConfig(input: EasyTwinConfig, cwd: string): Promise<InitResult>;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* TODO(swagger): 认证头名称、endpoint 路径、请求/响应结构一律以内部 swagger 为准。
|
|
49
|
+
* 以下常量为占位约定;swagger 到位后在 client.ts / scene.ts / upload.ts 集中修订,不扩散到其它文件。
|
|
50
|
+
*/
|
|
51
|
+
declare const AUTH_HEADER = "Authorization";
|
|
52
|
+
declare const BEARER_PREFIX = "Bearer";
|
|
53
|
+
/** TODO(swagger): 应用标识头名待定(上传目标空间由 App ID 决定)。 */
|
|
54
|
+
declare const APP_ID_HEADER = "X-Easytwin-App-Id";
|
|
55
|
+
declare const ENDPOINTS: {
|
|
56
|
+
readonly scenes: "/api/scenes";
|
|
57
|
+
readonly scene: (id: string) => string;
|
|
58
|
+
readonly upload: "/api/upload";
|
|
59
|
+
};
|
|
60
|
+
declare class EasyTwinApiError extends Error {
|
|
61
|
+
readonly status: number;
|
|
62
|
+
readonly body: unknown;
|
|
63
|
+
constructor(status: number, message: string, body?: unknown);
|
|
64
|
+
}
|
|
65
|
+
interface RequestOptions {
|
|
66
|
+
method?: "GET" | "POST" | "PUT" | "DELETE";
|
|
67
|
+
headers?: Record<string, string>;
|
|
68
|
+
body?: string | null;
|
|
69
|
+
signal?: AbortSignal;
|
|
70
|
+
}
|
|
71
|
+
declare class EasyTwinClient {
|
|
72
|
+
readonly baseUrl: string;
|
|
73
|
+
private readonly appId;
|
|
74
|
+
private readonly appSecret;
|
|
75
|
+
constructor(config: ResolvedConfig);
|
|
76
|
+
/** 全仓唯一认证头注入点。 */
|
|
77
|
+
private authHeaders;
|
|
78
|
+
/** JSON 请求(原生 fetch)。 */
|
|
79
|
+
request(path: string, options?: RequestOptions): Promise<unknown>;
|
|
80
|
+
/**
|
|
81
|
+
* multipart 流式上传(手写 http 请求以支持逐字节进度回调)。
|
|
82
|
+
* 这是 fetch 上传的替代路径:原生 fetch 无法上报上传进度。
|
|
83
|
+
*/
|
|
84
|
+
upload(path: string, options: {
|
|
85
|
+
boundary: string;
|
|
86
|
+
body: Buffer;
|
|
87
|
+
onBytes?: (bytes: number) => void;
|
|
88
|
+
}): Promise<unknown>;
|
|
89
|
+
private handleStatus;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface SceneSummary {
|
|
93
|
+
id: string;
|
|
94
|
+
name: string;
|
|
95
|
+
}
|
|
96
|
+
interface SceneDetail {
|
|
97
|
+
id: string;
|
|
98
|
+
name: string;
|
|
99
|
+
/** 服务端返回的场景 JSON 原始内容。 */
|
|
100
|
+
payload: unknown;
|
|
101
|
+
}
|
|
102
|
+
declare function normalizeSceneList(data: unknown): SceneSummary[];
|
|
103
|
+
declare function normalizeSceneDetail(data: unknown): SceneDetail;
|
|
104
|
+
declare function listScenes(client: EasyTwinClient): Promise<SceneSummary[]>;
|
|
105
|
+
declare function pullScene(client: EasyTwinClient, id: string): Promise<SceneDetail>;
|
|
106
|
+
declare function saveSceneJson(scene: SceneDetail, out: string): Promise<string>;
|
|
107
|
+
|
|
108
|
+
interface UploadProgress {
|
|
109
|
+
phase: "collect" | "upload";
|
|
110
|
+
/** collect 阶段为已统计字节;upload 阶段为已发送字节。 */
|
|
111
|
+
current: number;
|
|
112
|
+
total: number;
|
|
113
|
+
/** upload 阶段当前文件相对路径。 */
|
|
114
|
+
file?: string;
|
|
115
|
+
}
|
|
116
|
+
interface UploadOptions {
|
|
117
|
+
onProgress?: (progress: UploadProgress) => void;
|
|
118
|
+
/** 自定义忽略规则,默认只跳过 .git。 */
|
|
119
|
+
ignore?: (relPath: string) => boolean;
|
|
120
|
+
}
|
|
121
|
+
interface UploadResult {
|
|
122
|
+
fileCount: number;
|
|
123
|
+
byteCount: number;
|
|
124
|
+
}
|
|
125
|
+
interface CollectedFile {
|
|
126
|
+
relPath: string;
|
|
127
|
+
absPath: string;
|
|
128
|
+
size: number;
|
|
129
|
+
}
|
|
130
|
+
/** 递归收集目录下全部文件(默认跳过 .git),按相对路径升序排列,保证顺序稳定。 */
|
|
131
|
+
declare function collectFiles(dir: string, ignore?: (relPath: string) => boolean): Promise<CollectedFile[]>;
|
|
132
|
+
declare function buildMultipartBody(boundary: string, fields: Record<string, string>, file: {
|
|
133
|
+
name: string;
|
|
134
|
+
content: Buffer;
|
|
135
|
+
}): Buffer;
|
|
136
|
+
/** 全量覆盖上传目录:逐文件 multipart 上传,带进度回调(供插件进度条)。 */
|
|
137
|
+
declare function uploadDirectory(client: EasyTwinClient, dir: string, options?: UploadOptions): Promise<UploadResult>;
|
|
138
|
+
|
|
139
|
+
type SyncTarget = "cursor" | "claude" | "codex";
|
|
140
|
+
type SyncTargetArg = SyncTarget | "all";
|
|
141
|
+
/** 六件套清单(5.1),顺序即输出顺序。 */
|
|
142
|
+
declare const SKILL_NAMES: readonly ["easytwin-render", "easytwin-core", "easytwin-develop", "easytwin-bootstrap", "easytwin-scene", "easytwin-upload"];
|
|
143
|
+
type SyncAction = "created" | "updated" | "unchanged";
|
|
144
|
+
interface SyncEntrySummary {
|
|
145
|
+
name: string;
|
|
146
|
+
action: SyncAction;
|
|
147
|
+
}
|
|
148
|
+
interface SyncSummary {
|
|
149
|
+
target: SyncTarget;
|
|
150
|
+
entries: SyncEntrySummary[];
|
|
151
|
+
codex?: SyncEntrySummary;
|
|
152
|
+
}
|
|
153
|
+
interface SyncOptions {
|
|
154
|
+
cwd: string;
|
|
155
|
+
targets?: SyncTargetArg | SyncTarget[];
|
|
156
|
+
version?: string;
|
|
157
|
+
/** skills 源目录;缺省按 devkit 包内 skills/ 解析(CLI)。插件 bundle 场景显式传入插件内目录。 */
|
|
158
|
+
sourceDir?: string;
|
|
159
|
+
}
|
|
160
|
+
declare const CODEX_MARKER_BEGIN = "<!-- EASYTWIN-SKILLS:BEGIN -->";
|
|
161
|
+
declare const CODEX_MARKER_END = "<!-- EASYTWIN-SKILLS:END -->";
|
|
162
|
+
declare const META_FILE_NAME = ".easytwin-meta.json";
|
|
163
|
+
declare function normalizeTargets(target?: SyncTargetArg | SyncTarget[]): SyncTarget[];
|
|
164
|
+
/** skills 源目录默认解析:CLI 下为 devkit 包内 skills/。插件 bundle 到 CJS 后 import.meta.url 为空,
|
|
165
|
+
* 必须通过 SyncOptions.sourceDir / detectSkillsStatus 的 sourceDir 显式传入(见 esbuild.config.mjs 注释)。 */
|
|
166
|
+
declare function resolveSkillsSourceDir(): string;
|
|
167
|
+
declare function readDevkitVersion(skillsDir?: string): Promise<string>;
|
|
168
|
+
declare function syncSkills(options: SyncOptions): Promise<SyncSummary[]>;
|
|
169
|
+
interface SkillsTargetStatus {
|
|
170
|
+
target: SyncTarget;
|
|
171
|
+
synced: boolean;
|
|
172
|
+
reason?: string;
|
|
173
|
+
}
|
|
174
|
+
interface SkillsStatus {
|
|
175
|
+
cwd: string;
|
|
176
|
+
targets: SkillsTargetStatus[];
|
|
177
|
+
}
|
|
178
|
+
/** 供插件"缺失/过期时提示一次"使用:检测各目标当前同步状态。 */
|
|
179
|
+
declare function detectSkillsStatus(cwd: string, sourceDir?: string): Promise<SkillsStatus>;
|
|
180
|
+
|
|
181
|
+
export { APP_ID_HEADER, AUTH_HEADER, BEARER_PREFIX, CODEX_MARKER_BEGIN, CODEX_MARKER_END, CONFIG_FILE_NAME, type CollectedFile, ConfigError, DEFAULT_BASE_URL, ENDPOINTS, EasyTwinApiError, EasyTwinClient, type EasyTwinConfig, GITIGNORE_ENTRY, GITIGNORE_FILE_NAME, type GitignoreResult, type InitResult, META_FILE_NAME, type RequestOptions, type ResolvedConfig, SKILL_NAMES, type SceneDetail, type SceneSummary, type SkillsStatus, type SkillsTargetStatus, type SyncAction, type SyncEntrySummary, type SyncOptions, type SyncSummary, type SyncTarget, type SyncTargetArg, type UploadOptions, type UploadProgress, type UploadResult, appendGitignore, buildMultipartBody, collectFiles, configFilePath, detectSkillsStatus, initConfig, listScenes, loadConfig, normalizeSceneDetail, normalizeSceneList, normalizeTargets, parseConfig, pullScene, readConfigFile, readDevkitVersion, resolveConfig, resolveSkillsSourceDir, saveSceneJson, syncSkills, uploadDirectory, validateConfigShape, writeConfigFile };
|