openyida 2026.4.2-beta.0 → 2026.4.2-beta.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -31,6 +31,15 @@ npm install -g openyida
31
31
 
32
32
  **Zero config, works out of the box.** After installation, just chat in Claude Code / OpenCode / Aone Copilot:
33
33
 
34
+ ### Wukong Installation
35
+
36
+ Wukong uses manual skill package installation instead of npm:
37
+
38
+ 1. Download the latest skill package (`.zip`) from [GitHub Releases](https://github.com/openyida/openyida/releases)
39
+ 2. Open Wukong → **Skill Center** → **Upload Skill**, then select the downloaded package
40
+
41
+
42
+
34
43
  ```
35
44
  Build me an IPD system on Yida to manage the full chip production workflow
36
45
  Help me set up a CRM
package/lib/auth/login.js CHANGED
@@ -1,6 +1,11 @@
1
1
  /**
2
2
  * login.js - 宜搭登录态管理(Playwright 扫码登录)
3
3
  *
4
+ * 登录策略(按优先级):
5
+ * 1. 本地 Cookie 缓存(最快)
6
+ * 2. 本地 Chrome(channel: 'chrome')
7
+ * 3. Playwright 内置 Chromium(兜底)
8
+ *
4
9
  * 导出函数:
5
10
  * ensureLogin() - 确保有效登录态(优先缓存,否则扫码)
6
11
  * checkLoginOnly() - 仅检查登录态,不触发登录
@@ -228,7 +233,14 @@ const { chromium } = playwright;
228
233
  const { URL } = require('url');
229
234
 
230
235
  (async () => {
231
- const browser = await chromium.launch({ headless: false });
236
+ // 优先使用本地已安装的 Chrome,避免下载 Playwright 内置 Chromium
237
+ let browser;
238
+ try {
239
+ browser = await chromium.launch({ channel: 'chrome', headless: false });
240
+ } catch {
241
+ // 本地未安装 Chrome,降级为 Playwright 内置 Chromium
242
+ browser = await chromium.launch({ headless: false });
243
+ }
232
244
  const context = await browser.newContext();
233
245
  const page = await context.newPage();
234
246
  await page.goto(${JSON.stringify(loginUrl)}, { timeout: 120000 });
package/lib/core/copy.js CHANGED
@@ -194,7 +194,7 @@ function resolveDestBaseFromEnv(activeToolName, activeProjectRoot, envResults) {
194
194
  if (isWukong) {
195
195
  return activeProjectRoot
196
196
  ? path.dirname(activeProjectRoot)
197
- : path.join(os.homedir(), '.real', 'workspace');
197
+ : path.join(process.env.AGENT_WORK_ROOT || path.join(os.homedir(), '.real', 'workspace'));
198
198
  }
199
199
 
200
200
  if (activeToolName) {
@@ -274,13 +274,24 @@ function run() {
274
274
  const results = [];
275
275
 
276
276
  if (shouldCopyProject) {
277
- const count = copyItem(
278
- 'project/',
279
- packageProjectDir,
280
- path.join(destBase, 'project'),
281
- isForce
282
- );
283
- results.push({ label: 'project/', dest: path.join(destBase, 'project'), count, type: 'copy' });
277
+ // 检查 destBase 是否为空目录:
278
+ // - 空目录(如悟空新工作区)→ 直接把 project/ 内容铺进 destBase,不创建 project/ 这层
279
+ // - 非空目录(已有其他文件)→ 复制整个 project/ 目录(含目录本身)
280
+ const destBaseEntries = fs.existsSync(destBase)
281
+ ? fs.readdirSync(destBase).filter((name) => name !== '.DS_Store')
282
+ : [];
283
+ const isDestBaseEmpty = destBaseEntries.length === 0;
284
+
285
+ const projectDestDir = isDestBaseEmpty
286
+ ? destBase
287
+ : path.join(destBase, 'project');
288
+
289
+ if (isDestBaseEmpty) {
290
+ console.log(t('copy.dest_empty_flatten'));
291
+ }
292
+
293
+ const count = copyItem('project/', packageProjectDir, projectDestDir, isForce);
294
+ results.push({ label: 'project/', dest: projectDestDir, count, type: 'copy' });
284
295
  }
285
296
 
286
297
  if (shouldLinkSkills) {
@@ -212,7 +212,7 @@ class EnvironmentChecker {
212
212
  severity: Severity.ERROR,
213
213
  message: 'Playwright 未安装',
214
214
  fixType: FixType.COMMAND,
215
- fixCommand: 'npm install playwright && npx playwright install chromium',
215
+ fixCommand: 'npm install playwright',
216
216
  };
217
217
  }
218
218
  }
@@ -426,7 +426,12 @@ class ProjectInitChecker {
426
426
  }
427
427
 
428
428
  async check() {
429
- const projectDir = path.join(this.projectRoot, 'project');
429
+ // projectRoot 本身就是 project 目录(AI 工具将工作区根识别为 project/ 子目录时),
430
+ // 则直接检测 projectRoot/config.json,避免拼出 project/project 的错误路径。
431
+ const rootBasename = path.basename(this.projectRoot);
432
+ const projectDir = rootBasename === 'project'
433
+ ? this.projectRoot
434
+ : path.join(this.projectRoot, 'project');
430
435
  const configPath = path.join(projectDir, 'config.json');
431
436
  const passed = fs.existsSync(projectDir) && fs.existsSync(configPath);
432
437
 
@@ -434,10 +439,10 @@ class ProjectInitChecker {
434
439
  id: 'project-init',
435
440
  label: passed ? 'project/ 工作目录已初始化' : 'project/ 工作目录检测',
436
441
  passed,
437
- severity: passed ? Severity.INFO : Severity.WARNING,
438
- message: passed ? null : 'project/ 目录未初始化,将自动执行 openyida copy',
439
- fixType: passed ? null : FixType.COMMAND,
440
- fixCommand: passed ? null : 'openyida copy',
442
+ severity: passed ? Severity.INFO : Severity.ERROR,
443
+ message: passed ? null : 'project/ 目录未初始化,将自动初始化',
444
+ fixType: passed ? null : FixType.AUTO,
445
+ fixAction: passed ? null : 'init-project',
441
446
  }];
442
447
  }
443
448
  }
@@ -657,7 +662,7 @@ class FixEngine {
657
662
 
658
663
  for (const issue of issues) {
659
664
  if (issue.fixType === FixType.AUTO) {
660
- const result = this.applyAutoFix(issue);
665
+ const result = await this.applyAutoFix(issue);
661
666
  this.fixResults.push(result);
662
667
  } else if (issue.fixType === FixType.COMMAND) {
663
668
  this.fixResults.push({
@@ -680,9 +685,9 @@ class FixEngine {
680
685
  /**
681
686
  * 执行自动修复动作。
682
687
  * @param {object} issue
683
- * @returns {object}
688
+ * @returns {Promise<object>}
684
689
  */
685
- applyAutoFix(issue) {
690
+ async applyAutoFix(issue) {
686
691
  switch (issue.fixAction) {
687
692
  case 'create-config': {
688
693
  const configPath = path.join(this.projectRoot, 'config.json');
@@ -710,6 +715,24 @@ class FixEngine {
710
715
  };
711
716
  }
712
717
 
718
+ case 'init-project': {
719
+ try {
720
+ const copy = require('./copy');
721
+ await copy([]);
722
+ return {
723
+ id: issue.id,
724
+ fixed: true,
725
+ message: '已自动初始化 project/ 工作目录',
726
+ };
727
+ } catch (error) {
728
+ return {
729
+ id: issue.id,
730
+ fixed: false,
731
+ message: `project/ 初始化失败,请手动运行:openyida copy(${error.message})`,
732
+ };
733
+ }
734
+ }
735
+
713
736
  default:
714
737
  return {
715
738
  id: issue.id,
package/lib/core/env.js CHANGED
@@ -55,7 +55,7 @@ function detectEnvironment() {
55
55
  const isActive = activeTool && activeTool.dirName === dirName;
56
56
  // path.join 在 Windows 上自动使用反斜杠,兼容所有平台
57
57
  const workspaceRoot = isWukong
58
- ? path.join(home, '.real', 'workspace', 'project')
58
+ ? path.join(process.env.AGENT_WORK_ROOT || path.join(home, '.real', 'workspace'), 'project')
59
59
  : cwdProject;
60
60
  const hasProject = fs.existsSync(workspaceRoot);
61
61
 
@@ -203,6 +203,7 @@ module.exports = {
203
203
  title: ' openyida copy - تهيئة دليل عمل Yida',
204
204
  package_root: '\n📦 جذر الحزمة: {0}',
205
205
  dest_root: '🤖 الدليل الهدف: {0}',
206
+ dest_empty_flatten: '📂 الدليل الهدف فارغ، سيتم نسخ محتويات project/ مباشرةً (بدون إنشاء مجلد project/ فرعي)',
206
207
  force_mode: '⚠️ وضع --force: سيتم مسح الدليل الهدف قبل النسخ',
207
208
  no_package: '\n❌ لم يتم العثور على دليل حزمة openyida',
208
209
  no_package_hint1: ' تأكد من تثبيت openyida بشكل عام:',
@@ -203,6 +203,7 @@ module.exports = {
203
203
  title: ' openyida copy - Yida-Arbeitsverzeichnis initialisieren',
204
204
  package_root: '\n📦 Paketstamm: {0}',
205
205
  dest_root: '🤖 Zielverzeichnis: {0}',
206
+ dest_empty_flatten: '📂 Zielverzeichnis ist leer, project/-Inhalte werden direkt hineinkopiert (kein project/-Unterverzeichnis)',
206
207
  force_mode: '⚠️ --force-Modus: Zielverzeichnis wird vor dem Kopieren geleert',
207
208
  no_package: '\n❌ openyida-Paketverzeichnis nicht gefunden',
208
209
  no_package_hint1: ' Stellen Sie sicher, dass openyida global installiert ist:',
@@ -575,6 +575,7 @@ Examples:
575
575
  package_root: '\n📦 Package root: {0}',
576
576
  dest_base: '🤖 Target root: {0}',
577
577
  dest_root: '🤖 Target root: {0}',
578
+ dest_empty_flatten: '📂 Target directory is empty, flattening project/ contents directly into it (no project/ subdirectory)',
578
579
  force_mode: '⚠️ --force mode: target directory will be cleared before copying',
579
580
  no_package: '\n❌ openyida package directory not found',
580
581
  no_package_hint1: ' Please ensure openyida is installed globally:',
@@ -203,6 +203,7 @@ module.exports = {
203
203
  title: ' openyida copy - Inicializar directorio de trabajo Yida',
204
204
  package_root: '\n📦 Raíz del paquete: {0}',
205
205
  dest_root: '🤖 Directorio destino: {0}',
206
+ dest_empty_flatten: '📂 El directorio destino está vacío, los contenidos de project/ se copiarán directamente (sin crear subdirectorio project/)',
206
207
  force_mode: '⚠️ Modo --force: el directorio destino será vaciado antes de copiar',
207
208
  no_package: '\n❌ Directorio del paquete openyida no encontrado',
208
209
  no_package_hint1: ' Asegúrese de que openyida esté instalado globalmente:',
@@ -203,6 +203,7 @@ module.exports = {
203
203
  title: ' openyida copy - Initialisation du répertoire de travail Yida',
204
204
  package_root: '\n📦 Racine du package : {0}',
205
205
  dest_root: '🤖 Répertoire cible : {0}',
206
+ dest_empty_flatten: '📂 Le répertoire cible est vide, les contenus de project/ seront copiés directement (sans créer de sous-répertoire project/)',
206
207
  force_mode: '⚠️ Mode --force : le répertoire cible sera vidé avant la copie',
207
208
  no_package: '\n❌ Répertoire du package openyida introuvable',
208
209
  no_package_hint1: " Vérifiez qu'openyida est installé globalement :",
@@ -203,6 +203,7 @@ module.exports = {
203
203
  title: ' openyida copy - Yida कार्य निर्देशिका प्रारंभ करें',
204
204
  package_root: '\n📦 पैकेज रूट: {0}',
205
205
  dest_root: '🤖 लक्ष्य निर्देशिका: {0}',
206
+ dest_empty_flatten: '📂 लक्ष्य निर्देशिका खाली है, project/ की सामग्री सीधे कॉपी की जाएगी (project/ उपनिर्देशिका नहीं बनेगी)',
206
207
  force_mode: '⚠️ --force मोड: कॉपी करने से पहले लक्ष्य निर्देशिका साफ की जाएगी',
207
208
  no_package: '\n❌ openyida पैकेज निर्देशिका नहीं मिली',
208
209
  no_package_hint1: ' सुनिश्चित करें कि openyida वैश्विक रूप से स्थापित है:',
@@ -560,6 +560,7 @@ openyida - Yida CLI ツール
560
560
  package_root: '\n📦 パッケージルート: {0}',
561
561
  dest_base: '🤖 ターゲットルート: {0}',
562
562
  dest_root: '🤖 ターゲットルート: {0}',
563
+ dest_empty_flatten: '📂 ターゲットディレクトリが空のため、project/ の内容を直接コピーします(project/ サブディレクトリは作成しません)',
563
564
  force_mode: '⚠️ --force モード:ターゲットディレクトリをクリアしてからコピーします',
564
565
  no_package: '\n❌ openyida パッケージディレクトリが見つかりません',
565
566
  no_package_hint1: ' openyida がグローバルにインストールされていることを確認してください:',
@@ -206,6 +206,7 @@ module.exports = {
206
206
  title: ' openyida copy - Yida 작업 디렉토리 초기화',
207
207
  package_root: '\n📦 패키지 루트: {0}',
208
208
  dest_root: '🤖 대상 루트: {0}',
209
+ dest_empty_flatten: '📂 대상 디렉토리가 비어 있어 project/ 내용을 직접 복사합니다 (project/ 하위 디렉토리 생성 안 함)',
209
210
  force_mode: '⚠️ --force 모드: 대상 디렉토리를 초기화 후 복사합니다',
210
211
  no_package: '\n❌ openyida 패키지 디렉토리를 찾을 수 없습니다',
211
212
  no_package_hint1: ' openyida가 전역으로 설치되어 있는지 확인하세요:',
@@ -203,6 +203,7 @@ module.exports = {
203
203
  title: ' openyida copy - Inicializar diretório de trabalho Yida',
204
204
  package_root: '\n📦 Raiz do pacote: {0}',
205
205
  dest_root: '🤖 Diretório destino: {0}',
206
+ dest_empty_flatten: '📂 O diretório destino está vazio, os conteúdos de project/ serão copiados diretamente (sem criar subdiretório project/)',
206
207
  force_mode: '⚠️ Modo --force: o diretório destino será esvaziado antes de copiar',
207
208
  no_package: '\n❌ Diretório do pacote openyida não encontrado',
208
209
  no_package_hint1: ' Certifique-se de que o openyida está instalado globalmente:',
@@ -203,6 +203,7 @@ module.exports = {
203
203
  title: ' openyida copy - Khởi tạo thư mục làm việc Yida',
204
204
  package_root: '\n📦 Thư mục gốc gói: {0}',
205
205
  dest_root: '🤖 Thư mục đích: {0}',
206
+ dest_empty_flatten: '📂 Thư mục đích trống, nội dung project/ sẽ được sao chép trực tiếp (không tạo thư mục con project/)',
206
207
  force_mode: '⚠️ Chế độ --force: thư mục đích sẽ được xóa trước khi sao chép',
207
208
  no_package: '\n❌ Không tìm thấy thư mục gói openyida',
208
209
  no_package_hint1: ' Hãy đảm bảo openyida đã được cài đặt toàn cục:',
@@ -469,6 +469,7 @@ openyida - 宜搭命令列工具
469
469
  package_root: '\n📦 套件根目錄:{0}',
470
470
  dest_base: '🤖 目標根目錄:{0}',
471
471
  dest_root: '🤖 目標根目錄:{0}',
472
+ dest_empty_flatten: '📂 目標目錄為空,將 project/ 內容直接鋪入(不建立 project/ 子目錄)',
472
473
  force_mode: '⚠️ --force 模式:目標目錄將被清空後重新複製',
473
474
  no_package: '\n❌ 未找到 openyida 安裝套件目錄',
474
475
  no_package_hint1: ' 請確認 openyida 已正確全域安裝:',
@@ -531,6 +531,7 @@ openyida - 宜搭命令行工具
531
531
  package_root: '\n📦 包根目录: {0}',
532
532
  dest_base: '🤖 目标根目录: {0}',
533
533
  dest_root: '🤖 目标根目录: {0}',
534
+ dest_empty_flatten: '📂 目标目录为空,将 project/ 内容直接铺入(不创建 project/ 子目录)',
534
535
  force_mode: '⚠️ --force 模式:目标目录将被清空后重新复制',
535
536
  no_package: '\n❌ 未找到 openyida 安装包目录',
536
537
  no_package_hint1: ' 请确认 openyida 已正确全局安装:',
package/lib/core/utils.js CHANGED
@@ -87,7 +87,8 @@ function detectActiveTool() {
87
87
  tool: 'wukong',
88
88
  displayName: '悟空(Wukong)',
89
89
  dirName: '.real',
90
- workspaceRoot: path.join(home, '.real', 'workspace', 'project'),
90
+ // 直接使用 AGENT_WORK_ROOT,悟空工作区路径是动态的(含 uuid),不能硬编码
91
+ workspaceRoot: path.join(env.AGENT_WORK_ROOT, 'project'),
91
92
  };
92
93
  }
93
94
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openyida",
3
- "version": "2026.04.02-beta.0",
3
+ "version": "2026.04.02-beta.12",
4
4
  "description": "OpenYida CLI - 宜搭低代码 AI 开发工具(安装即用,零配置)",
5
5
  "bin": {
6
6
  "openyida": "./bin/yida.js",
@@ -132,47 +132,10 @@ safeExec(() => {
132
132
  }
133
133
  });
134
134
 
135
- // ── 2. Wukong integration ─────────────────────────────────────────────
135
+ // ── 2. Wukong ────────────────────────────────────────────────────────
136
+ // 悟空通过手动上传技能,无需 postinstall 安装。
136
137
 
137
- safeExec(() => {
138
- if (fs.existsSync(path.join(HOME_DIR, '.real'))) {
139
- installSkills(path.join(HOME_DIR, '.real'));
140
- }
141
- });
142
-
143
- // ── 3. Chromium 按需安装 ─────────────────────────────────────────────
144
-
145
- safeExec(() => {
146
- const { execSync } = require('child_process');
147
-
148
- let chromiumPath = null;
149
- try {
150
- // 通过 playwright 内置 API 获取 Chromium 可执行文件路径
151
- const { chromium } = require('playwright');
152
- chromiumPath = chromium.executablePath();
153
- } catch {
154
- // playwright 未安装或 API 不可用,跳过
155
- return;
156
- }
157
-
158
- if (chromiumPath && fs.existsSync(chromiumPath)) {
159
- // Chromium 已存在,无需重复安装
160
- return;
161
- }
162
-
163
- console.log('\n 🌐 正在安装 Chromium 浏览器(首次安装需要下载,请稍候)...');
164
- try {
165
- execSync('npx playwright install chromium', {
166
- stdio: 'inherit',
167
- timeout: 300_000, // 5 分钟超时
168
- });
169
- console.log(' ✅ Chromium 安装完成!\n');
170
- } catch {
171
- console.warn(' ⚠️ Chromium 安装失败,可手动执行:npx playwright install chromium\n');
172
- }
173
- });
174
-
175
- // ── 4. 首次安装欢迎引导 ──────────────────────────────────────────────
138
+ // ── 3. 首次安装欢迎引导 ──────────────────────────────────────────────
176
139
 
177
140
  safeExec(() => {
178
141
  const FIRST_INSTALL_FLAG = path.join(HOME_DIR, '.openyida', 'installed');
@@ -4,7 +4,7 @@ description: >
4
4
  宜搭低代码平台 AI 开发入口。一句话生成完整应用:创建应用、表单设计、自定义页面、流程配置、数据管理。
5
5
  当用户提到"宜搭"、"yida"、"低代码"、"创建应用"、"创建表单"、"发布页面"、"搭建"、"系统"、"应用"时触发。
6
6
  metadata:
7
- version: 2026.04.02-beta.0
7
+ version: 2026.04.02-beta.12
8
8
  ---
9
9
 
10
10
  # 宜搭 AI 应用开发指南
@@ -301,6 +301,43 @@ this.forceUpdate();
301
301
 
302
302
  > **生成代码时的自检清单**:检查 `renderJsx` 中所有 `onClick`、`onChange`、`onSubmit` 等事件属性,确保每一个都是 `(e) => { this.xxx(e) }` 形式,不存在任何 `onClick={this.xxx}` 的写法。
303
303
 
304
+ // ❌ 错误③:在 .map(function(){}) 普通函数回调中使用箭头函数事件处理器,this 已在 function 回调里丢失,箭头函数捕获的 this 是 undefined!
305
+ export function renderJsx() {
306
+ return (
307
+ <div>
308
+ {quickBtns.map(function(btn, idx) {
309
+ return (
310
+ <button
311
+ key={idx}
312
+ onClick={(e) => { this.goToForm(btn.form); }} // ❌ this 是 undefined,运行时报错
313
+ >
314
+ {btn.label}
315
+ </button>
316
+ );
317
+ })}
318
+ </div>
319
+ );
320
+ }
321
+
322
+ // ✅ 正确:.map() 回调必须使用箭头函数,确保 this 正确捕获
323
+ export function renderJsx() {
324
+ return (
325
+ <div>
326
+ {quickBtns.map((btn, idx) => (
327
+ <button
328
+ key={idx}
329
+ onClick={(e) => { this.goToForm(btn.form); }} // ✅ 箭头函数回调 + 箭头函数事件处理器,this 正确
330
+ >
331
+ {btn.label}
332
+ </button>
333
+ ))}
334
+ </div>
335
+ );
336
+ }
337
+ ```
338
+
339
+ > **补充自检项**:检查 `renderJsx` 中所有 `.map()`、`.filter()`、`.forEach()` 等数组方法的回调,确保全部使用**箭头函数**形式 `(item) => ...`,不存在任何 `.map(function(item) {...})` 的写法,否则回调内部的 `this` 会丢失。
340
+
304
341
  3. **输入法组合输入处理**:使用 `_isComposing` 标记配合 `compositionstart` / `compositionend` 事件,正确处理中文输入法的组合输入状态,避免输入过程中触发提交
305
342
  4. **定时器清理**:在 `didUnmount` 中必须清理所有通过 `setInterval` / `setTimeout` 创建的定时器,防止内存泄漏
306
343
  5. **错误处理**:所有 API 调用(`this.utils.yida.*`、`fetch`)必须使用 `.catch()` 处理异常,并通过 `this.utils.toast({ title: message, type: 'error' })` 向用户展示错误提示
@@ -29,8 +29,6 @@ description: 宜搭登录态管理。扫码登录,Cookie 持久化到 .cache/c
29
29
 
30
30
  ## 命令
31
31
 
32
- ### 标准环境
33
-
34
32
  ```bash
35
33
  openyida login
36
34
  ```