openyida 2026.9.11-3 → 2026.9.11
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 +4 -6
- package/bin/yida.js +0 -1
- package/lib/ai/ai.js +0 -2
- package/lib/app/update-app.js +2 -17
- package/lib/asset/ai-image.js +65 -37
- package/lib/asset/asset-cmd.js +110 -151
- package/lib/asset/asset-resolve.js +225 -343
- package/lib/asset/asset-status.js +31 -20
- package/lib/asset/url-verify.js +130 -90
- package/lib/core/agent-capabilities.js +0 -11
- package/lib/core/command-manifest.js +10 -24
- package/lib/core/locales/en.js +2 -6
- package/lib/core/locales/zh.js +2 -6
- package/lib/design-plan/materialize.js +9 -7
- package/lib/design-plan/normalize.js +1 -11
- package/lib/process/services/process-compiler.js +30 -4
- package/lib/process/services/process-view-verifier.js +0 -31
- package/package.json +1 -1
- package/scripts/postinstall.js +1 -1
- package/yida-skills/SKILL.md +2 -3
- package/yida-skills/skills/yida-app/SKILL.md +1 -1
- package/yida-skills/skills/yida-app/workflow/step-2-design.md +1 -11
- package/yida-skills/skills/yida-app/workflow/step-3-create-or-reuse-app.md +1 -21
- package/yida-skills/skills/yida-app/workflow/step-7-page-code.md +0 -4
- package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +27 -3
- package/yida-skills/skills/yida-create-app/SKILL.md +4 -4
- package/yida-skills/skills/yida-create-process/SKILL.md +0 -2
- package/yida-skills/skills/yida-custom-page/references/assets-guide.md +12 -9
- package/yida-skills/skills/yida-design/SKILL.md +0 -1
- package/yida-skills/skills/yida-design/references/ask-human-interaction-contract.md +4 -4
- package/yida-skills/skills/yida-design/references/asset-workflow.md +53 -14
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/build-plan-compact-schema.md +1 -1
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/build-plan-schema.md +4 -21
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/scripts/render_build_plan.py +2 -1
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/scripts/validate_design_themes.py +1 -1
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/README.md +1 -1
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/airy-media-grid.md +1 -1
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/airy-modular-clarity.md +1 -1
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/airy-structured-clarity.md +1 -1
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/ambient-halo-layered.md +1 -1
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/dark-focus-layered.md +1 -1
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/dark-luminous-modular.md +1 -1
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/deep-stage-duotone.md +1 -1
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/hairline-runway-clarity.md +1 -1
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/high-contrast-modular.md +1 -1
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/interlocked-vivid-modules.md +1 -1
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/matrix-grid-focus.md +1 -1
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/media-rail-inspector.md +1 -1
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/mist-layered-signal.md +1 -1
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/modular-rail-signal.md +1 -1
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/mono-grid-signal.md +2 -2
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/ribbon-ledger-lift.md +1 -1
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/segmented-meter-clarity.md +1 -1
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/split-rail-precision.md +1 -1
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/status-framed-media-grid.md +1 -1
- package/yida-skills/skills/yida-design/workflow/output-design.md +0 -4
- package/yida-skills/skills/yida-design/workflow/step-4-wireframe-interaction.md +1 -1
- package/yida-skills/skills/yida-design/workflow/step-5-visual-states.md +1 -2
- package/yida-skills/skills/yida-nav-shell/references/nav-shell-patterns.md +1 -1
- package/yida-skills/skills/yida-prd/workflow/output-prd.md +2 -2
- package/yida-skills/skills/yida-process-rule/SKILL.md +1 -6
- package/yida-skills/skills/yida-requirement-analysis/SKILL.md +1 -1
- package/yida-skills/skills/yida-requirement-analysis/workflow/prepare-brief.md +13 -22
- package/yida-skills/skills-index.json +1 -18
- package/lib/asset/asset-plan.js +0 -105
- package/lib/asset/attachment-upload.js +0 -29
- package/lib/asset/host-capabilities.js +0 -91
- package/lib/asset/image-metadata.js +0 -104
- package/lib/process/services/process-actions.js +0 -145
- package/yida-skills/skills/yida-image-assets/SKILL.md +0 -91
- package/yida-skills/skills/yida-image-assets/references/manifest-contract.md +0 -87
- package/yida-skills/skills/yida-image-assets/references/source-policy.md +0 -19
- package/yida-skills/skills/yida-process-rule/references/approval-actions.md +0 -48
package/README.md
CHANGED
|
@@ -210,8 +210,6 @@ openyida data create form APP_XXX FORM_XXX --expect-form-name 客户 --expect-fo
|
|
|
210
210
|
openyida get-permission APP_XXX FORM_XXX
|
|
211
211
|
```
|
|
212
212
|
|
|
213
|
-
Append and forward permissions are configured in the process definition through `nodes[].actions.normalActions/appendActions`. Both `create-process` and `configure-process` compile these settings into matching designer and runtime properties. See [approval action configuration](yida-skills/skills/yida-process-rule/references/approval-actions.md) for the JSON example, defaults, and verification limits.
|
|
214
|
-
|
|
215
213
|
`configure-process` 的流程 JSON 中,审批人可配置为发起人、指定成员、指定角色、部门主管或直属主管,例如:
|
|
216
214
|
|
|
217
215
|
```json
|
|
@@ -456,8 +454,8 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
|
|
|
456
454
|
|
|
457
455
|
| Command | Description |
|
|
458
456
|
|---------|-------------|
|
|
459
|
-
| `openyida configure-process <appType> <formUuid> <definition> [processCode] [--replace]` | Configure and publish process rules
|
|
460
|
-
| `openyida create-process <appType> ... [--replace]` | Create process form (all-in-one)
|
|
457
|
+
| `openyida configure-process <appType> <formUuid> <definition> [processCode] [--replace]` | Configure and publish process rules |
|
|
458
|
+
| `openyida create-process <appType> ... [--replace]` | Create process form (all-in-one) |
|
|
461
459
|
| `openyida ai-form-setting <get\|fields\|models\|enable\|disable\|save> <appType> ...` | Manage process form AI approval prompts |
|
|
462
460
|
| `openyida process preview <appType> ...` | Preview process instance (visual flowchart) |
|
|
463
461
|
|
|
@@ -531,7 +529,7 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
|
|
|
531
529
|
| `openyida batch <file>\|--commands "cmd1 ; cmd2" [--stop-on-error] [--json]` | Run OpenYida commands in batch |
|
|
532
530
|
| `openyida flash-to-prd --file <path> --name "<project>"` | Convert flash notes or meeting notes to a PRD prompt |
|
|
533
531
|
| `openyida ai <text\|image> [options]` | Call Yida AI text and image recognition APIs |
|
|
534
|
-
| `openyida asset <status\|resolve\|
|
|
532
|
+
| `openyida asset <status\|resolve\|generate> [options]` | Detect asset capability / resolve materials |
|
|
535
533
|
| `openyida cdn-config [options]` | Configure CDN / OSS upload |
|
|
536
534
|
| `openyida cdn-upload <image-path>` | Upload image to CDN |
|
|
537
535
|
| `openyida cdn-refresh [options]` | Refresh CDN cache |
|
|
@@ -562,7 +560,7 @@ The runner rejects public generic echo services such as httpbin and example.com.
|
|
|
562
560
|
|
|
563
561
|
The opt-in `node scripts/e2e-real/connector/action-update-runner.js` regression uses the owner-confirmed login-free `www.aliwork.com` fixture and a single owned NONE-auth connector containing a target action plus one preservation sentinel. Enable it with `OPENYIDA_E2E=1 OPENYIDA_E2E_CONNECTOR_ACTION_UPDATE=1`. It verifies isolated `currentPage`, `pageSize`, `userLanguage`, `searchFieldJson`, and dynamic `_stamp` edits, restores each field and the final baseline, and persists only response structure, data count, and SHA-256. It never stores response row values or auth/profile/corp identifiers, never retries unknown writes, and intentionally leaves the owned connector as a `cleanup_blocked` residual because no proven delete API exists.
|
|
564
562
|
|
|
565
|
-
`openyida asset resolve --
|
|
563
|
+
`openyida asset resolve --hero <path-or-url> --product <path-or-url> --require-hero --upload-assets --json` is the preferred preflight for homepage visuals. It handles local files and external image references in one flow, uploads or mirrors them when CDN is configured, and returns `materialStatus: final|draft|none` so agents do not claim an unfinished visual page is final.
|
|
566
564
|
|
|
567
565
|
#### Environment and Localization
|
|
568
566
|
|
package/bin/yida.js
CHANGED
|
@@ -496,7 +496,6 @@ const MANIFEST_HELP_PATHS = Object.freeze({
|
|
|
496
496
|
data: ['data'],
|
|
497
497
|
report: ['report'],
|
|
498
498
|
'create-process': ['create-process'],
|
|
499
|
-
'configure-process': ['configure-process'],
|
|
500
499
|
'create-report': ['create-report'],
|
|
501
500
|
'append-chart': ['append-chart'],
|
|
502
501
|
'save-share-config': ['save-share-config'],
|
package/lib/ai/ai.js
CHANGED
|
@@ -31,7 +31,6 @@ const IMAGE_MIME_TYPES = {
|
|
|
31
31
|
'.gif': 'image/gif',
|
|
32
32
|
'.webp': 'image/webp',
|
|
33
33
|
'.bmp': 'image/bmp',
|
|
34
|
-
'.svg': 'image/svg+xml',
|
|
35
34
|
};
|
|
36
35
|
|
|
37
36
|
function printHelp() {
|
|
@@ -501,7 +500,6 @@ module.exports = {
|
|
|
501
500
|
getAuthRef,
|
|
502
501
|
callTextFromAI,
|
|
503
502
|
uploadImageForAI,
|
|
504
|
-
inferAppType,
|
|
505
503
|
invokeImageRecognition,
|
|
506
504
|
normalizeImageResult,
|
|
507
505
|
parseArgs,
|
package/lib/app/update-app.js
CHANGED
|
@@ -221,16 +221,6 @@ function normalizeLayoutDirection(layoutDirection) {
|
|
|
221
221
|
return normalized;
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
-
/** 与 yc-utils 的应用配置归一化一致;不应用详情页 Shell 的 top_fold 覆盖。 */
|
|
225
|
-
function normalizeStoredLayoutDirection(layoutDirection, navType) {
|
|
226
|
-
if (SUPPORTED_LAYOUT_DIRECTIONS.includes(layoutDirection)) {return layoutDirection;}
|
|
227
|
-
if (layoutDirection === 'hoz') {return navType === 'top_side' ? 'l_shape' : 'top';}
|
|
228
|
-
if (layoutDirection === 'ver') {return 'side';}
|
|
229
|
-
if (navType === 'top_fold') {return 'top';}
|
|
230
|
-
if (navType === 'top_side') {return 'l_shape';}
|
|
231
|
-
return 'side';
|
|
232
|
-
}
|
|
233
|
-
|
|
234
224
|
function pickAppField(currentApp, fieldName, configName) {
|
|
235
225
|
if (currentApp[fieldName] !== undefined && currentApp[fieldName] !== null) {
|
|
236
226
|
return currentApp[fieldName];
|
|
@@ -306,8 +296,6 @@ function buildUpdateAppPostData(params, currentApp = {}, authRef) {
|
|
|
306
296
|
const currentCustomThemeStyle = pickAppField(currentApp, 'customThemeStyle', 'CUSTOM_THEME_STYLE');
|
|
307
297
|
const addWaterMark = pickAppField(currentApp, 'addWaterMark', 'ADDWATERMARK');
|
|
308
298
|
const sentryMode = pickAppField(currentApp, 'sentryMode', 'SENTRY_MODE');
|
|
309
|
-
const currentNavType = currentApp.navType || (currentApp.config && currentApp.config.NAVTYPE);
|
|
310
|
-
const currentLayoutDirection = currentApp.layoutDirection || (currentApp.config && currentApp.config.LAY_OUT_DIRECTION);
|
|
311
299
|
if (params.colour === 'custom' && !params.themeColor && !currentThemeColor) {
|
|
312
300
|
throw new Error(t('update_app.custom_theme_color_required'));
|
|
313
301
|
}
|
|
@@ -331,10 +319,9 @@ function buildUpdateAppPostData(params, currentApp = {}, authRef) {
|
|
|
331
319
|
mode: currentApp.mode || (currentApp.config && currentApp.config.APPMODE) || 'normal',
|
|
332
320
|
type: currentApp.type || (currentApp.config && currentApp.config.APPTYPE) || 'single',
|
|
333
321
|
navTheme: params.navTheme || currentApp.navTheme || (currentApp.config && currentApp.config.NAV_THEME) || 'light',
|
|
322
|
+
navType: currentApp.navType || (currentApp.config && currentApp.config.NAVTYPE) || 'top_side',
|
|
334
323
|
navLayout: currentApp.navLayout || (currentApp.config && currentApp.config.NAVLAYOUT) || 'auto',
|
|
335
|
-
layoutDirection: params.layoutDirection
|
|
336
|
-
? normalizeLayoutDirection(params.layoutDirection)
|
|
337
|
-
: normalizeStoredLayoutDirection(currentLayoutDirection, currentNavType),
|
|
324
|
+
layoutDirection: params.layoutDirection || currentApp.layoutDirection || (currentApp.config && currentApp.config.LAY_OUT_DIRECTION) || 'side',
|
|
338
325
|
homepageLogo: currentApp.homepageLogo || (currentApp.config && currentApp.config.HOMEPAGELOGO) || '',
|
|
339
326
|
logoSource: params.logoSource || currentApp.logoSource || (currentApp.config && currentApp.config.LOGO_SOURCE) || 'appIcon',
|
|
340
327
|
logoLink: currentApp.logoLink || (currentApp.config && currentApp.config.LOGOLINK) || '',
|
|
@@ -359,8 +346,6 @@ function buildUpdateAppPostData(params, currentApp = {}, authRef) {
|
|
|
359
346
|
pageFooter: currentApp.pageFooter || '',
|
|
360
347
|
};
|
|
361
348
|
|
|
362
|
-
// ThemeNavSetting 保存现有 navType;新布局由 layoutDirection 控制,不反写或补造旧字段。
|
|
363
|
-
if (currentNavType) {postDataObj.navType = currentNavType;}
|
|
364
349
|
if (addWaterMark !== undefined && addWaterMark !== null) {
|
|
365
350
|
postDataObj.addWaterMark = addWaterMark;
|
|
366
351
|
}
|
package/lib/asset/ai-image.js
CHANGED
|
@@ -1,60 +1,89 @@
|
|
|
1
1
|
|
|
2
|
+
/**
|
|
3
|
+
* ai-image.js - 图片素材来源能力检测(诚实降级 + 引导本地 agent)
|
|
4
|
+
*
|
|
5
|
+
* P3 素材工具化的关键澄清:openyida 本身没有内置「文生图」API,
|
|
6
|
+
* `openyida ai` 只支持文生文与识图,不能凭空生成图片 URL。
|
|
7
|
+
*
|
|
8
|
+
* 因此本模块不假装能生成图片,而是:
|
|
9
|
+
* 1. 诚实地检测「是否存在内置图片生成能力」(目前恒为 false);
|
|
10
|
+
* 2. 把「生成/寻找素材」这件事交回给正在驱动本 CLI 的智能体(Claude/Codex 等),
|
|
11
|
+
* 由 skill 文档引导它:能自己生成就生成,否则去免费素材库搜真实图片;
|
|
12
|
+
* 3. 提供推荐的免费/可商用素材库清单与使用纪律,供 skill / agent 参考。
|
|
13
|
+
*
|
|
14
|
+
* 无论走哪条路,候选素材都交给 asset resolve 统一解析;有 CDN 时再转存,
|
|
15
|
+
* 绝不把编造的 URL 直接写进页面。
|
|
16
|
+
*
|
|
17
|
+
* 用法:
|
|
18
|
+
* const { detectImageGenerator, getFreeStockLibraries, getMaterialSourcingGuidance } = require('./ai-image');
|
|
19
|
+
* const g = detectImageGenerator(); // { available:false, reason, delegateToAgent:true }
|
|
20
|
+
*/
|
|
21
|
+
|
|
2
22
|
'use strict';
|
|
3
23
|
|
|
24
|
+
/**
|
|
25
|
+
* 推荐的免费 / 可商用图片素材库
|
|
26
|
+
* 说明:这些站点提供免费可商用(多为 CC0 / 自有免费授权)的高质量图片,
|
|
27
|
+
* 适合官网 Hero、产品图、场景配图。agent 应优先从这里取真实图片 URL,
|
|
28
|
+
* 取到后交给 `openyida asset resolve` 统一处理。
|
|
29
|
+
*/
|
|
4
30
|
const FREE_STOCK_LIBRARIES = [
|
|
5
31
|
{
|
|
6
32
|
name: 'Unsplash',
|
|
7
33
|
site: 'https://unsplash.com',
|
|
8
|
-
license: '
|
|
9
|
-
bestFor: 'Hero
|
|
10
|
-
directImageHint:
|
|
11
|
-
|
|
34
|
+
license: 'Unsplash License(免费可商用,无需署名)',
|
|
35
|
+
bestFor: '高质量摄影级 Hero 大图、生活方式/场景图',
|
|
36
|
+
directImageHint:
|
|
37
|
+
'images.unsplash.com/photo-... 形式的直链可直接用于 <img>;页面链接需先取得图片直链。',
|
|
12
38
|
},
|
|
13
39
|
{
|
|
14
40
|
name: 'Pexels',
|
|
15
41
|
site: 'https://www.pexels.com',
|
|
16
|
-
license: '
|
|
17
|
-
bestFor: '
|
|
42
|
+
license: 'Pexels License(免费可商用,无需署名)',
|
|
43
|
+
bestFor: '产品/办公/团队/自然等主题摄影图与短视频',
|
|
18
44
|
directImageHint: 'images.pexels.com/photos/... 形式为图片直链。',
|
|
19
|
-
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'Pixabay',
|
|
48
|
+
site: 'https://pixabay.com',
|
|
49
|
+
license: 'Pixabay Content License(免费可商用)',
|
|
50
|
+
bestFor: '插画、矢量、照片综合库',
|
|
51
|
+
directImageHint: 'cdn.pixabay.com/photo/... 形式为图片直链。',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'Undraw',
|
|
55
|
+
site: 'https://undraw.co/illustrations',
|
|
56
|
+
license: 'unDraw open-source license(免费可商用,可改色)',
|
|
57
|
+
bestFor: '扁平风品牌插画、空状态/引导插画(SVG,可随品牌色变量着色)',
|
|
58
|
+
directImageHint: '导出 SVG 或使用官方 CDN SVG 直链。',
|
|
20
59
|
},
|
|
21
60
|
];
|
|
22
61
|
|
|
23
62
|
/**
|
|
24
63
|
* 检测是否存在「内置图片生成能力」。
|
|
25
64
|
*
|
|
26
|
-
* openyida
|
|
27
|
-
* 由
|
|
65
|
+
* 目前 openyida 无内置文生图连接器,恒返回 available:false,并显式声明
|
|
66
|
+
* 由 agent 接管素材来源(避免任何模块据此伪造图片 URL)。
|
|
28
67
|
*
|
|
29
68
|
* @param {object} [options]
|
|
30
|
-
* @
|
|
69
|
+
* @param {boolean} [options.hasImageGenConnector] 预留:未来若配置了文生图连接器可置 true
|
|
70
|
+
* @returns {{ available: boolean, reason: string, delegateToAgent: boolean }}
|
|
31
71
|
*/
|
|
32
72
|
function detectImageGenerator(options = {}) {
|
|
33
|
-
|
|
34
|
-
if (capability && capability.status === 'available') {
|
|
73
|
+
if (options.hasImageGenConnector) {
|
|
35
74
|
return {
|
|
36
|
-
status: 'available',
|
|
37
75
|
available: true,
|
|
38
|
-
reason: '
|
|
39
|
-
delegateToAgent: true,
|
|
40
|
-
source: capability.source || 'host_capability',
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
if (capability && capability.status === 'unavailable') {
|
|
44
|
-
return {
|
|
45
|
-
status: 'unavailable',
|
|
46
|
-
available: false,
|
|
47
|
-
reason: '当前宿主不支持图片生成。',
|
|
76
|
+
reason: '检测到已配置的文生图连接器,可由连接器生成图片后再转存 CDN。',
|
|
48
77
|
delegateToAgent: false,
|
|
49
|
-
source: capability.source,
|
|
50
78
|
};
|
|
51
79
|
}
|
|
52
80
|
return {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
81
|
+
available: false,
|
|
82
|
+
reason:
|
|
83
|
+
'openyida 无内置文生图能力(`openyida ai` 仅支持文生文/识图)。' +
|
|
84
|
+
'图片素材应由智能体自行生成,或到免费素材库(Unsplash/Pexels 等)检索真实图片,' +
|
|
85
|
+
'再交给 `openyida asset resolve` 解析、(有 CDN 时)转存 CDN 后回填 spec。',
|
|
56
86
|
delegateToAgent: true,
|
|
57
|
-
source: (capability && capability.source) || 'host_tool_inventory_required',
|
|
58
87
|
};
|
|
59
88
|
}
|
|
60
89
|
|
|
@@ -73,18 +102,17 @@ function getFreeStockLibraries() {
|
|
|
73
102
|
function getMaterialSourcingGuidance() {
|
|
74
103
|
return {
|
|
75
104
|
steps: [
|
|
76
|
-
'1.
|
|
77
|
-
'2.
|
|
78
|
-
'3.
|
|
79
|
-
'4.
|
|
80
|
-
'5.
|
|
105
|
+
'1. 判断页面是否需要真实图片(官网 Hero / 产品图 / 场景图通常需要)。',
|
|
106
|
+
'2. 优先:若智能体自身具备图片生成能力,按品牌调性生成图片并保存到本地。',
|
|
107
|
+
'3. 备选:到免费可商用素材库(见 libraries)检索与主题匹配的真实图片,拿到图片直链。',
|
|
108
|
+
'4. 解析:把本地文件或外链交给 `openyida asset resolve` 统一处理并回填 spec.assets;已配置 CDN 时转存为稳定 URL。',
|
|
109
|
+
'5. 兜底:若既无生成能力又找不到可用素材,交付「标注素材缺口」的低保真草稿,绝不编造图片 URL 或声称已上传。',
|
|
81
110
|
],
|
|
82
111
|
libraries: getFreeStockLibraries(),
|
|
83
112
|
rules: [
|
|
84
|
-
'
|
|
85
|
-
'
|
|
86
|
-
'
|
|
87
|
-
'来源不明时使用中性占位或宿主生成图。',
|
|
113
|
+
'绝不编造图片 URL;素材经 `openyida asset resolve` 统一处理后再写进页面。',
|
|
114
|
+
'无 CDN 时不得声称「已上传」或「已是最终版」。',
|
|
115
|
+
'素材授权需为免费可商用;如不确定授权,改用文字排版/数据图示/插画(如 unDraw)替代。',
|
|
88
116
|
],
|
|
89
117
|
};
|
|
90
118
|
}
|
package/lib/asset/asset-cmd.js
CHANGED
|
@@ -1,104 +1,82 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* asset-cmd.js - `openyida asset` 素材能力命令
|
|
4
|
+
*
|
|
5
|
+
* 素材工具的统一入口,把「检测素材能力 / 解析回填 / 素材来源引导」
|
|
6
|
+
* 暴露为可脚本化命令,供本地智能体在生成官网/落地页前调用,
|
|
7
|
+
* 从而做到:无 CDN 不谎称已上传、无可用素材不交付「最终版」。
|
|
8
|
+
*
|
|
9
|
+
* 子命令:
|
|
10
|
+
* status 检测当前素材能力(CDN / 生成 / 推荐策略)
|
|
11
|
+
* resolve --hero <..> [--product <..>] [--upload-assets] 解析并回填一组素材,输出 materialStatus/缺口
|
|
12
|
+
* generate 输出素材来源引导(免费素材库清单 + 纪律)
|
|
13
|
+
*
|
|
14
|
+
* 所有子命令均支持 --json 便于智能体解析。
|
|
15
|
+
*/
|
|
16
|
+
|
|
1
17
|
'use strict';
|
|
2
18
|
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { readDesignAssetStrategy } = require('./asset-plan');
|
|
6
19
|
const { getAssetStatus } = require('./asset-status');
|
|
7
|
-
const { resolveAssets
|
|
8
|
-
const {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
20
|
+
const { resolveAssets } = require('./asset-resolve');
|
|
21
|
+
const {
|
|
22
|
+
detectImageGenerator,
|
|
23
|
+
getMaterialSourcingGuidance,
|
|
24
|
+
} = require('./ai-image');
|
|
25
|
+
const { throwCommandError, throwStatus } = require('../core/command-errors');
|
|
12
26
|
|
|
13
27
|
function printHelp() {
|
|
14
28
|
console.log(`
|
|
15
29
|
用法:
|
|
16
30
|
openyida asset status [--offline] [--json]
|
|
17
|
-
openyida asset resolve
|
|
18
|
-
openyida asset
|
|
31
|
+
openyida asset resolve --hero <路径或URL> [--product <路径或URL> ...] [--require-hero] [--upload-assets] [--offline] [--json]
|
|
32
|
+
openyida asset generate [--json]
|
|
19
33
|
|
|
20
34
|
子命令:
|
|
21
|
-
status
|
|
22
|
-
resolve
|
|
35
|
+
status 检测当前素材能力:是否配置 CDN、是否可生成、推荐素材策略
|
|
36
|
+
resolve 把本地/外链素材解析为稳定 URL 并回填,输出 materialStatus(final|draft|none) 与缺口
|
|
37
|
+
generate 输出素材来源引导(免费可商用素材库清单 + 使用纪律,供智能体检索真实图片)
|
|
23
38
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
sources 输出 Unsplash/Pexels 使用规则
|
|
39
|
+
说明:
|
|
40
|
+
openyida 无内置文生图能力;图片应由智能体自行生成或到免费素材库检索,
|
|
41
|
+
再通过 resolve 解析,并在配置 CDN 时转存后回填 spec。
|
|
28
42
|
`);
|
|
29
43
|
}
|
|
30
44
|
|
|
31
|
-
function parseSlot(value) {
|
|
32
|
-
const index = String(value || '').indexOf('=');
|
|
33
|
-
if (index <= 0 || index === String(value).length - 1) {return null;}
|
|
34
|
-
const slotId = String(value).slice(0, index).trim();
|
|
35
|
-
const input = String(value).slice(index + 1).trim();
|
|
36
|
-
if (!slotId || !input) {return null;}
|
|
37
|
-
return {
|
|
38
|
-
slotId,
|
|
39
|
-
input,
|
|
40
|
-
required: true,
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
45
|
function parseArgs(args) {
|
|
45
46
|
const parsed = {
|
|
46
47
|
subCommand: args[0],
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
design: '',
|
|
48
|
+
hero: '',
|
|
49
|
+
products: [],
|
|
50
|
+
requireHero: false,
|
|
51
51
|
offline: false,
|
|
52
52
|
uploadAssets: false,
|
|
53
|
-
appType: '',
|
|
54
|
-
source: 'search',
|
|
55
53
|
timeout: 0,
|
|
56
54
|
json: false,
|
|
57
55
|
help: false,
|
|
58
|
-
errors: [],
|
|
59
|
-
usedOptions: [],
|
|
60
56
|
};
|
|
57
|
+
|
|
61
58
|
for (let i = 1; i < args.length; i++) {
|
|
62
59
|
const arg = args[i];
|
|
63
60
|
if (arg === '--help' || arg === '-h') {
|
|
64
61
|
parsed.help = true;
|
|
65
62
|
} else if (arg === '--json') {
|
|
66
|
-
parsed.usedOptions.push('--json');
|
|
67
63
|
parsed.json = true;
|
|
68
64
|
} else if (arg === '--offline') {
|
|
69
|
-
parsed.usedOptions.push('--offline');
|
|
70
65
|
parsed.offline = true;
|
|
71
|
-
} else if (arg === '--
|
|
72
|
-
parsed.
|
|
73
|
-
|
|
74
|
-
if (!parsed.appType || parsed.appType.startsWith('--')) {parsed.errors.push('ASSET_APP_TYPE_REQUIRED');}
|
|
75
|
-
} else if (arg === '--upload-assets') {
|
|
76
|
-
parsed.usedOptions.push('--upload-assets');
|
|
66
|
+
} else if (arg === '--require-hero') {
|
|
67
|
+
parsed.requireHero = true;
|
|
68
|
+
} else if (arg === '--upload-assets' || arg === '--mirror-external') {
|
|
77
69
|
parsed.uploadAssets = true;
|
|
78
|
-
} else if (arg === '--
|
|
79
|
-
parsed.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
parsed.
|
|
83
|
-
parsed.input = args[++i] || '';
|
|
84
|
-
} else if (arg === '--design') {
|
|
85
|
-
parsed.usedOptions.push('--design');
|
|
86
|
-
parsed.design = args[++i] || '';
|
|
87
|
-
if (!parsed.design || parsed.design.startsWith('--')) {parsed.errors.push('ASSET_DESIGN_REQUIRED');}
|
|
88
|
-
} else if (arg === '--manifest') {
|
|
89
|
-
parsed.usedOptions.push('--manifest');
|
|
90
|
-
parsed.manifest = args[++i] || '';
|
|
91
|
-
} else if (arg === '--slot') {
|
|
92
|
-
parsed.usedOptions.push('--slot');
|
|
93
|
-
const slot = parseSlot(args[++i]);
|
|
94
|
-
if (slot) {parsed.slots.push(slot);} else {parsed.errors.push('--slot 格式应为 <slotId>=<路径或URL>');}
|
|
70
|
+
} else if (arg === '--hero') {
|
|
71
|
+
parsed.hero = args[++i] || '';
|
|
72
|
+
} else if (arg === '--product') {
|
|
73
|
+
const v = args[++i];
|
|
74
|
+
if (v) {parsed.products.push(v);}
|
|
95
75
|
} else if (arg === '--timeout') {
|
|
96
|
-
parsed.usedOptions.push('--timeout');
|
|
97
76
|
parsed.timeout = parseInt(args[++i], 10) || 0;
|
|
98
|
-
} else {
|
|
99
|
-
parsed.errors.push(`未知参数:${arg}`);
|
|
100
77
|
}
|
|
101
78
|
}
|
|
79
|
+
|
|
102
80
|
return parsed;
|
|
103
81
|
}
|
|
104
82
|
|
|
@@ -109,88 +87,74 @@ function runStatus(options) {
|
|
|
109
87
|
return;
|
|
110
88
|
}
|
|
111
89
|
console.log('素材能力检测');
|
|
112
|
-
console.log('
|
|
113
|
-
console.log('
|
|
114
|
-
console.log('
|
|
115
|
-
console.log('
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
function readDraft(filePath) {
|
|
120
|
-
let draft;
|
|
121
|
-
try {
|
|
122
|
-
draft = JSON.parse(fs.readFileSync(path.resolve(filePath), 'utf8'));
|
|
123
|
-
} catch (error) {
|
|
124
|
-
throwCommandError(`无法读取素材草稿:${error.message}`, { code: 'ASSET_INPUT_INVALID' });
|
|
90
|
+
console.log(' CDN 已配置 : ' + (status.cdnConfigured ? '是' : '否'));
|
|
91
|
+
console.log(' 可上传转存 : ' + (status.canUpload ? '是' : '否'));
|
|
92
|
+
console.log(' 可 AI 生成 : ' + (status.canGenerate ? '是' : '否(由智能体生成/检索素材)'));
|
|
93
|
+
console.log(' 推荐素材策略 : ' + status.recommendedStrategy);
|
|
94
|
+
console.log(' CDN 配置文件 : ' + status.cdn.configPath);
|
|
95
|
+
if (status.cdn.missing.length) {
|
|
96
|
+
console.log(' 缺失 CDN 字段 : ' + status.cdn.missing.join(', '));
|
|
125
97
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
return draft;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function writeManifest(filePath, manifest) {
|
|
133
|
-
const target = path.resolve(filePath);
|
|
134
|
-
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
135
|
-
fs.writeFileSync(target, JSON.stringify(manifest, null, 2) + '\n', 'utf8');
|
|
136
|
-
return target;
|
|
98
|
+
console.log('原因/建议:');
|
|
99
|
+
status.reasons.forEach((r) => console.log(' - ' + r));
|
|
137
100
|
}
|
|
138
101
|
|
|
139
102
|
async function runResolve(options) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
throwUsage('--input 与 --slot 不能同时使用', RESOLVE_USAGE, { code: 'ASSET_INPUT_CONFLICT' });
|
|
145
|
-
}
|
|
146
|
-
if (!ASSET_SOURCES.includes(options.source)) {
|
|
147
|
-
throwUsage('--source 仅支持 search、user 或 generated', RESOLVE_USAGE, { code: 'ASSET_INVALID_SOURCE' });
|
|
148
|
-
}
|
|
149
|
-
const draft = options.input ? readDraft(options.input) : {};
|
|
150
|
-
const assetStrategy = options.design ? readDesignAssetStrategy(path.resolve(options.design)) : draft.assetStrategy;
|
|
151
|
-
const assets = [
|
|
152
|
-
...(draft.assets || []),
|
|
153
|
-
...options.slots.map(slot => ({ ...slot, source: options.source })),
|
|
154
|
-
];
|
|
155
|
-
if (assets.length === 0 && !options.input) {
|
|
156
|
-
throwUsage('至少提供一个 --slot 或 --input', RESOLVE_USAGE, { code: 'ASSET_INPUT_REQUIRED' });
|
|
157
|
-
}
|
|
158
|
-
|
|
103
|
+
const assets = {
|
|
104
|
+
heroImage: options.hero,
|
|
105
|
+
productImages: options.products,
|
|
106
|
+
};
|
|
159
107
|
const result = await resolveAssets(assets, {
|
|
160
|
-
|
|
108
|
+
requireHero: options.requireHero,
|
|
161
109
|
online: !options.offline,
|
|
162
|
-
|
|
163
|
-
assetSource: options.source,
|
|
110
|
+
mirrorExternal: !!options.uploadAssets,
|
|
164
111
|
timeout: options.timeout || undefined,
|
|
165
112
|
});
|
|
166
|
-
|
|
167
|
-
|
|
113
|
+
if (options.json) {
|
|
114
|
+
console.log(JSON.stringify(result, null, 2));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
console.log('素材解析结果');
|
|
118
|
+
console.log(' materialStatus : ' + result.materialStatus +
|
|
119
|
+
(result.materialStatus === 'final' ? '(可交付最终版)'
|
|
120
|
+
: result.materialStatus === 'draft' ? '(仅可交付标注缺口的草稿)'
|
|
121
|
+
: '(无可用素材,只能低保真草稿)'));
|
|
122
|
+
console.log(' strategy : ' + result.strategy);
|
|
123
|
+
console.log(' Hero : ' + (result.assets.heroImage || '(无)'));
|
|
124
|
+
console.log(' 产品图 : ' + (result.assets.productImages.length
|
|
125
|
+
? result.assets.productImages.join(', ') : '(无)'));
|
|
126
|
+
if (result.materialGaps.length) {
|
|
127
|
+
console.log('素材缺口:');
|
|
128
|
+
result.materialGaps.forEach((g) => console.log(' - ' + g));
|
|
129
|
+
}
|
|
130
|
+
// 非 final 时以非零退出码提示调用方(生成链路可据此拦截「最终版」)
|
|
131
|
+
if (result.materialStatus !== 'final') {
|
|
168
132
|
throwStatus('素材未达到 final 状态', 2, {
|
|
169
133
|
code: 'ASSET_MATERIAL_NOT_FINAL',
|
|
170
|
-
details:
|
|
134
|
+
details: result,
|
|
171
135
|
});
|
|
172
136
|
}
|
|
173
|
-
if (options.json) {
|
|
174
|
-
console.log(JSON.stringify({ ...result, manifestPath }, null, 2));
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
console.log('素材状态: ' + result.materialStatus);
|
|
178
|
-
console.log('素材数量: ' + result.assets.length);
|
|
179
|
-
if (manifestPath) {console.log('素材清单: ' + manifestPath);}
|
|
180
137
|
}
|
|
181
138
|
|
|
182
|
-
function
|
|
183
|
-
const
|
|
184
|
-
const
|
|
185
|
-
generator: detectImageGenerator({ hostCapabilities: status.hostCapabilities }),
|
|
186
|
-
guidance: getMaterialSourcingGuidance(),
|
|
187
|
-
};
|
|
139
|
+
function runGenerate(options) {
|
|
140
|
+
const generator = detectImageGenerator();
|
|
141
|
+
const guidance = getMaterialSourcingGuidance();
|
|
188
142
|
if (options.json) {
|
|
189
|
-
console.log(JSON.stringify(
|
|
143
|
+
console.log(JSON.stringify({ generator, guidance }, null, 2));
|
|
190
144
|
return;
|
|
191
145
|
}
|
|
192
|
-
console.log('
|
|
193
|
-
|
|
146
|
+
console.log('图片生成能力: ' + (generator.available ? '可用' : '不可用'));
|
|
147
|
+
console.log(' ' + generator.reason);
|
|
148
|
+
console.log('\n素材来源步骤:');
|
|
149
|
+
guidance.steps.forEach((s) => console.log(' ' + s));
|
|
150
|
+
console.log('\n免费可商用素材库:');
|
|
151
|
+
guidance.libraries.forEach((lib) => {
|
|
152
|
+
console.log(' · ' + lib.name + ' (' + lib.site + ')');
|
|
153
|
+
console.log(' 授权: ' + lib.license);
|
|
154
|
+
console.log(' 适合: ' + lib.bestFor);
|
|
155
|
+
});
|
|
156
|
+
console.log('\n纪律:');
|
|
157
|
+
guidance.rules.forEach((r) => console.log(' - ' + r));
|
|
194
158
|
}
|
|
195
159
|
|
|
196
160
|
async function run(args) {
|
|
@@ -199,27 +163,22 @@ async function run(args) {
|
|
|
199
163
|
printHelp();
|
|
200
164
|
return;
|
|
201
165
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
166
|
+
switch (options.subCommand) {
|
|
167
|
+
case 'status':
|
|
168
|
+
runStatus(options);
|
|
169
|
+
break;
|
|
170
|
+
case 'resolve':
|
|
171
|
+
await runResolve(options);
|
|
172
|
+
break;
|
|
173
|
+
case 'generate':
|
|
174
|
+
runGenerate(options);
|
|
175
|
+
break;
|
|
176
|
+
default:
|
|
177
|
+
printHelp();
|
|
178
|
+
throwCommandError('未知子命令: ' + options.subCommand, {
|
|
179
|
+
code: 'ASSET_UNKNOWN_SUBCOMMAND',
|
|
180
|
+
});
|
|
212
181
|
}
|
|
213
|
-
if (options.subCommand === 'status') {runStatus(options); return;}
|
|
214
|
-
if (options.subCommand === 'resolve') {await runResolve(options); return;}
|
|
215
|
-
if (options.subCommand === 'sources') {runSources(options); return;}
|
|
216
|
-
throwCommandError(`未知子命令:${options.subCommand}`, { code: 'ASSET_UNKNOWN_SUBCOMMAND' });
|
|
217
182
|
}
|
|
218
183
|
|
|
219
|
-
module.exports = {
|
|
220
|
-
run,
|
|
221
|
-
parseArgs,
|
|
222
|
-
parseSlot,
|
|
223
|
-
readDraft,
|
|
224
|
-
writeManifest,
|
|
225
|
-
};
|
|
184
|
+
module.exports = { run, parseArgs };
|