openyida 2026.7.21 → 2026.7.22
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/bin/yida.js +29 -2
- package/lib/app/canvas-compile.js +44 -12
- package/lib/app/publish.js +27 -0
- package/lib/core/agent-capabilities.js +258 -0
- package/lib/core/command-manifest.js +236 -0
- package/lib/core/locales/en.js +9 -0
- package/lib/core/locales/zh.js +9 -0
- package/package.json +1 -1
- package/scripts/validate-command-manifest.js +72 -0
- package/yida-skills/SKILL.md +6 -4
- package/yida-skills/references/setup-and-env.md +12 -10
- package/yida-skills/skills/yida-app/SKILL.md +13 -0
- package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +3 -0
- package/yida-skills/skills/yida-canvas-custom-page/references/dependencies-and-cdn.md +5 -1
- package/yida-skills/skills/yida-canvas-data-binding/SKILL.md +1 -0
- package/yida-skills/skills/yida-custom-page/SKILL.md +3 -0
- package/yida-skills/skills/yida-login/SKILL.md +9 -7
- package/yida-skills/skills/yida-publish-page/SKILL.md +3 -1
- package/yida-skills/skills-index.json +1 -1
package/bin/yida.js
CHANGED
|
@@ -14,7 +14,7 @@ const { version: currentVersion } = require('../package.json');
|
|
|
14
14
|
const { t } = require('../lib/core/i18n');
|
|
15
15
|
const { warn } = require('../lib/core/chalk');
|
|
16
16
|
const { CliError, isCliError, toErrorPayload } = require('../lib/core/cli-error');
|
|
17
|
-
const { COMMAND_GROUPS, buildCommandManifest } = require('../lib/core/command-manifest');
|
|
17
|
+
const { COMMAND_GROUPS, buildCommandManifest, findCommandSuggestion } = require('../lib/core/command-manifest');
|
|
18
18
|
|
|
19
19
|
const command = process.argv[2];
|
|
20
20
|
const args = process.argv.slice(3);
|
|
@@ -477,6 +477,33 @@ function throwCliUsage(...lines) {
|
|
|
477
477
|
});
|
|
478
478
|
}
|
|
479
479
|
|
|
480
|
+
function formatSuggestionMessage(suggestion) {
|
|
481
|
+
if (!suggestion) {
|
|
482
|
+
return '';
|
|
483
|
+
}
|
|
484
|
+
if (suggestion.message_key) {
|
|
485
|
+
return t(suggestion.message_key, ...(suggestion.message_args || []));
|
|
486
|
+
}
|
|
487
|
+
return '';
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function throwUnknownCommand(commandName, commandArgs = []) {
|
|
491
|
+
const suggestion = findCommandSuggestion([commandName, ...commandArgs]);
|
|
492
|
+
const lines = [t('cli.unknown_command', commandName)];
|
|
493
|
+
if (suggestion) {
|
|
494
|
+
lines.push(t('cli.command_suggestion', suggestion.suggested_usage));
|
|
495
|
+
const suggestionMessage = formatSuggestionMessage(suggestion);
|
|
496
|
+
if (suggestionMessage) {
|
|
497
|
+
lines.push(suggestionMessage);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
lines.push(t('cli.run_help'));
|
|
501
|
+
throw new CliError(lines.filter(Boolean).join('\n'), {
|
|
502
|
+
code: 'INVALID_ARGUMENTS',
|
|
503
|
+
details: suggestion ? { suggestion } : undefined,
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
|
|
480
507
|
function hasHelpFlag(cliArgs = []) {
|
|
481
508
|
return cliArgs.includes('--help') || cliArgs.includes('-h');
|
|
482
509
|
}
|
|
@@ -1147,7 +1174,7 @@ async function main() {
|
|
|
1147
1174
|
}
|
|
1148
1175
|
|
|
1149
1176
|
default: {
|
|
1150
|
-
|
|
1177
|
+
throwUnknownCommand(command, args);
|
|
1151
1178
|
}
|
|
1152
1179
|
}
|
|
1153
1180
|
}
|
|
@@ -35,8 +35,9 @@ const Babel = require('@babel/standalone');
|
|
|
35
35
|
* 逐条镜像自 @ali/vc-deep-yida 的
|
|
36
36
|
* src/components/yida-code-canvas/dependencies.ts → getModuleAliasMap()
|
|
37
37
|
* 只保留运行时真正用到的 windowAlias(资源 URL 由画布运行时按别名注入,
|
|
38
|
-
* 本地编译不关心 CDN
|
|
39
|
-
*
|
|
38
|
+
* 本地编译不关心 CDN 地址)。默认拒绝未收录依赖;如果宜搭物料依赖表已经
|
|
39
|
+
* 先于 CLI 升级,可临时设置 OPENYIDA_CANVAS_ALLOW_UNSUPPORTED_IMPORTS=1
|
|
40
|
+
* 退回旧的 window["pkg"] 映射,避免白名单漂移阻断发布。
|
|
40
41
|
* @type {Record<string, string>}
|
|
41
42
|
*/
|
|
42
43
|
const MODULE_ALIAS_MAP = {
|
|
@@ -59,6 +60,19 @@ const IMPORT_SIDE_EFFECT_PATTERN = /import\s+['"]([^'"]+)['"]/g;
|
|
|
59
60
|
const REQUIRE_PATTERN = /require\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
60
61
|
const DYNAMIC_IMPORT_PATTERN = /import\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
61
62
|
|
|
63
|
+
function isTruthy(value) {
|
|
64
|
+
return ['1', 'true', 'yes', 'on'].includes(String(value || '').trim().toLowerCase());
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function shouldAllowUnsupportedBareImports(options = {}, env = process.env) {
|
|
68
|
+
return options.allowUnsupportedBareImports === true ||
|
|
69
|
+
isTruthy(env.OPENYIDA_CANVAS_ALLOW_UNSUPPORTED_IMPORTS);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function packageTempName(pkg) {
|
|
73
|
+
return String(pkg || 'module').replace(/[^A-Za-z0-9_$]+/g, '_') || 'module';
|
|
74
|
+
}
|
|
75
|
+
|
|
62
76
|
function stripJsComments(code) {
|
|
63
77
|
return String(code || '')
|
|
64
78
|
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
@@ -113,16 +127,27 @@ function resolveWindowAlias(pkg) {
|
|
|
113
127
|
* export const x = ... → const x = ...(去掉 export 关键字)
|
|
114
128
|
* @param {{ types: import('@babel/types') }} babel
|
|
115
129
|
*/
|
|
116
|
-
function esmToWindowPlugin({ types: t }) {
|
|
117
|
-
|
|
118
|
-
|
|
130
|
+
function esmToWindowPlugin({ types: t }, options = {}) {
|
|
131
|
+
const allowUnsupportedBareImports = options.allowUnsupportedBareImports === true;
|
|
132
|
+
|
|
133
|
+
function moduleExpr(pkg, alias) {
|
|
119
134
|
if (alias) {
|
|
120
135
|
return t.memberExpression(t.identifier('window'), t.identifier(alias));
|
|
121
136
|
}
|
|
122
|
-
// 未收录包:退化为 window["pkg"],运行时若未注入会自然报错(与线上一致)。
|
|
123
137
|
return t.memberExpression(t.identifier('window'), t.stringLiteral(pkg), true);
|
|
124
138
|
}
|
|
125
139
|
|
|
140
|
+
function buildUnsupportedBareImportError(path, pkg) {
|
|
141
|
+
return path.buildCodeFrameError(
|
|
142
|
+
`Code Canvas 不支持从裸包 "${pkg}" 导入绑定。`
|
|
143
|
+
+ '只允许 MODULE_ALIAS_MAP 白名单依赖;'
|
|
144
|
+
+ '宜搭平台运行态全局对象请显式使用 window.* 访问'
|
|
145
|
+
+ '(例如 window.Deep、window.DeepYida、window.YidaNativeComponents),不要从包中 import。'
|
|
146
|
+
+ '若已确认宜搭物料运行态已注入该包且 CLI 白名单滞后,可临时设置 '
|
|
147
|
+
+ 'OPENYIDA_CANVAS_ALLOW_UNSUPPORTED_IMPORTS=1 退回 legacy window["pkg"] 映射。'
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
126
151
|
return {
|
|
127
152
|
name: 'yida-esm-to-window',
|
|
128
153
|
visitor: {
|
|
@@ -140,10 +165,14 @@ function esmToWindowPlugin({ types: t }) {
|
|
|
140
165
|
path.remove();
|
|
141
166
|
return;
|
|
142
167
|
}
|
|
168
|
+
const alias = resolveWindowAlias(pkg);
|
|
169
|
+
if (!alias && !allowUnsupportedBareImports) {
|
|
170
|
+
throw buildUnsupportedBareImportError(path, pkg);
|
|
171
|
+
}
|
|
143
172
|
|
|
144
173
|
const decls = [];
|
|
145
|
-
const tmp = path.scope.generateUidIdentifier(
|
|
146
|
-
decls.push(t.variableDeclarator(t.cloneNode(tmp), moduleExpr(pkg)));
|
|
174
|
+
const tmp = path.scope.generateUidIdentifier(alias || packageTempName(pkg));
|
|
175
|
+
decls.push(t.variableDeclarator(t.cloneNode(tmp), moduleExpr(pkg, alias)));
|
|
147
176
|
|
|
148
177
|
const namedProps = [];
|
|
149
178
|
for (const spec of specifiers) {
|
|
@@ -230,7 +259,7 @@ function esmToWindowPlugin({ types: t }) {
|
|
|
230
259
|
* @param {string} source 原始 React/JSX/TSX 源码
|
|
231
260
|
* @returns {{ runtimeCode: string, importedModules: string }}
|
|
232
261
|
*/
|
|
233
|
-
function compileCanvasLocal(source) {
|
|
262
|
+
function compileCanvasLocal(source, options = {}) {
|
|
234
263
|
const importedModules = extractImportedModules(source);
|
|
235
264
|
|
|
236
265
|
// 第一步:剥类型 + 转 JSX(classic runtime,产出 React.createElement,
|
|
@@ -265,7 +294,9 @@ function compileCanvasLocal(source) {
|
|
|
265
294
|
// 第二步:把 import/export 改写成 window 别名 + YidaComp。
|
|
266
295
|
const stage2 = Babel.transform(intermediate, {
|
|
267
296
|
filename: 'canvas.js',
|
|
268
|
-
plugins: [esmToWindowPlugin
|
|
297
|
+
plugins: [[esmToWindowPlugin, {
|
|
298
|
+
allowUnsupportedBareImports: shouldAllowUnsupportedBareImports(options),
|
|
299
|
+
}]],
|
|
269
300
|
sourceType: 'module',
|
|
270
301
|
compact: false,
|
|
271
302
|
babelrc: false,
|
|
@@ -286,14 +317,14 @@ function compileCanvasLocal(source) {
|
|
|
286
317
|
* @param {object} [options] 兼容占位,未使用
|
|
287
318
|
* @returns {Promise<{ runtimeCode: string, importedModules: string }>}
|
|
288
319
|
*/
|
|
289
|
-
function compileCanvas(source, options = {}) {
|
|
320
|
+
function compileCanvas(source, options = {}) {
|
|
290
321
|
return new Promise((resolve, reject) => {
|
|
291
322
|
if (typeof source !== 'string' || source.trim() === '') {
|
|
292
323
|
reject(new Error('canvas 编译源码为空'));
|
|
293
324
|
return;
|
|
294
325
|
}
|
|
295
326
|
try {
|
|
296
|
-
resolve(compileCanvasLocal(source));
|
|
327
|
+
resolve(compileCanvasLocal(source, options));
|
|
297
328
|
} catch (compileError) {
|
|
298
329
|
const detail = compileError && compileError.message ? compileError.message : String(compileError);
|
|
299
330
|
reject(new Error(`Code Canvas 本地编译失败: ${detail}`));
|
|
@@ -306,5 +337,6 @@ module.exports = {
|
|
|
306
337
|
compileCanvasLocal,
|
|
307
338
|
extractImportedModules,
|
|
308
339
|
resolveWindowAlias,
|
|
340
|
+
shouldAllowUnsupportedBareImports,
|
|
309
341
|
MODULE_ALIAS_MAP,
|
|
310
342
|
};
|
package/lib/app/publish.js
CHANGED
|
@@ -126,6 +126,28 @@ function parseArgs(argv = process.argv.slice(2)) {
|
|
|
126
126
|
};
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
function normalizeSourcePathForHint(sourceFile) {
|
|
130
|
+
return String(sourceFile || '')
|
|
131
|
+
.replace(/\\/g, '/')
|
|
132
|
+
.replace(/^\.\//, '');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function buildMissingSourceHints(sourceFile, cwd = process.cwd()) {
|
|
136
|
+
const normalized = normalizeSourcePathForHint(sourceFile);
|
|
137
|
+
const candidates = [];
|
|
138
|
+
|
|
139
|
+
if (normalized.startsWith('project/pages/src/')) {
|
|
140
|
+
candidates.push(normalized.slice('project/'.length));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (normalized.startsWith('pages/src/')) {
|
|
144
|
+
candidates.push(`project/${normalized}`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return Array.from(new Set(candidates))
|
|
148
|
+
.filter(candidate => fs.existsSync(path.resolve(cwd, candidate)));
|
|
149
|
+
}
|
|
150
|
+
|
|
129
151
|
// ── 2. 读取并构建 Schema ────────────────────────────
|
|
130
152
|
|
|
131
153
|
async function fetchExistingSchemaContent(appType, formUuid, authRef) {
|
|
@@ -618,6 +640,10 @@ async function main(argv) {
|
|
|
618
640
|
let sourcePath = path.resolve(sourceFile);
|
|
619
641
|
if (!fs.existsSync(sourcePath)) {
|
|
620
642
|
error(t('publish.source_not_found', sourcePath));
|
|
643
|
+
buildMissingSourceHints(sourceFile).forEach((candidate) => {
|
|
644
|
+
hint(t('publish.source_path_hint', candidate));
|
|
645
|
+
});
|
|
646
|
+
process.exit(1);
|
|
621
647
|
}
|
|
622
648
|
|
|
623
649
|
// 路由:默认 native;.canvas.jsx / .canvas.tsx 源文件自动走 Code Canvas 链路,
|
|
@@ -799,6 +825,7 @@ if (require.main === module) {
|
|
|
799
825
|
module.exports = main;
|
|
800
826
|
module.exports.parseArgs = parseArgs;
|
|
801
827
|
module.exports.normalizePublishArgs = normalizePublishArgs;
|
|
828
|
+
module.exports.buildMissingSourceHints = buildMissingSourceHints;
|
|
802
829
|
module.exports.findDuplicateSourceMismatches = findDuplicateSourceMismatches;
|
|
803
830
|
module.exports.sendHealthCheckRequest = sendHealthCheckRequest;
|
|
804
831
|
module.exports.normalizeFormType = normalizeFormType;
|
|
@@ -24,6 +24,247 @@ function compactLogin(login) {
|
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
function isTruthyEnv(value) {
|
|
28
|
+
return ['1', 'true', 'yes', 'on'].includes(String(value || '').trim().toLowerCase());
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function hasEnvTokenCredential(env = process.env) {
|
|
32
|
+
return !!(
|
|
33
|
+
String(env.OPENYIDA_ACCESS_TOKEN || '').trim() ||
|
|
34
|
+
String(env.OPENYIDA_REFRESH_TOKEN || '').trim()
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const BUILDER_CORE_COMMAND_IDS = Object.freeze([
|
|
39
|
+
'agent-capabilities',
|
|
40
|
+
'commands',
|
|
41
|
+
'app-list',
|
|
42
|
+
'list-forms',
|
|
43
|
+
'get-schema',
|
|
44
|
+
'create-app',
|
|
45
|
+
'create-form.create',
|
|
46
|
+
'create-page',
|
|
47
|
+
'publish',
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
function commandBriefs(manifest, commandIds) {
|
|
51
|
+
const byId = new Map((manifest.commands || []).map(entry => [entry.id, entry]));
|
|
52
|
+
return commandIds
|
|
53
|
+
.map(id => byId.get(id))
|
|
54
|
+
.filter(Boolean)
|
|
55
|
+
.map(entry => ({
|
|
56
|
+
id: entry.id,
|
|
57
|
+
usage: entry.usage,
|
|
58
|
+
requires_login: entry.requires_login,
|
|
59
|
+
output: entry.output,
|
|
60
|
+
permission_mode: entry.permission && entry.permission.mode,
|
|
61
|
+
side_effect_kind: entry.side_effect && entry.side_effect.kind,
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function forbiddenAliasBriefs(manifest) {
|
|
66
|
+
return (manifest.forbidden_aliases || []).map(entry => ({
|
|
67
|
+
pattern: entry.pattern,
|
|
68
|
+
matcher: entry.matcher,
|
|
69
|
+
suggested_command_id: entry.suggested_command_id,
|
|
70
|
+
suggested_usage: entry.suggested_usage,
|
|
71
|
+
alternative_command_ids: entry.alternative_command_ids || [],
|
|
72
|
+
alternative_usages: entry.alternative_usages || [],
|
|
73
|
+
message_key: entry.message_key,
|
|
74
|
+
message_args: entry.message_args || [],
|
|
75
|
+
message: entry.message,
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function buildAuthFastPath(login, env = process.env) {
|
|
80
|
+
const hostInjectedTokenMode = isTruthyEnv(env.YIDA_AUTH_ENABLED);
|
|
81
|
+
const envTokenPresent = hasEnvTokenCredential(env);
|
|
82
|
+
const authSource = login.auth_source || (hostInjectedTokenMode || envTokenPresent ? 'env' : 'token_session');
|
|
83
|
+
const hostTokenEnvDetected = hostInjectedTokenMode || envTokenPresent || authSource === 'env';
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
mode: login.auth_mode || 'token',
|
|
87
|
+
source: authSource,
|
|
88
|
+
can_auto_use: login.can_auto_use === true,
|
|
89
|
+
host_injected_token_mode: hostInjectedTokenMode,
|
|
90
|
+
host_token_env_detected: hostTokenEnvDetected,
|
|
91
|
+
env_token_present: envTokenPresent,
|
|
92
|
+
interactive_login_allowed: !hostTokenEnvDetected,
|
|
93
|
+
browser_session_auth_allowed: false,
|
|
94
|
+
auth_runtime: 'token_oauth_session',
|
|
95
|
+
cookie_auth_supported: false,
|
|
96
|
+
cookie_check_required: false,
|
|
97
|
+
playwright_cookie_check_required: false,
|
|
98
|
+
qr_login_required: false,
|
|
99
|
+
prohibited_legacy_checks: [
|
|
100
|
+
'browser_cookie',
|
|
101
|
+
'playwright_cookie',
|
|
102
|
+
'qr_login',
|
|
103
|
+
'cookie_cache',
|
|
104
|
+
],
|
|
105
|
+
missing_token_action: hostInjectedTokenMode
|
|
106
|
+
? 'STOP_AND_REQUEST_HOST_TOKEN'
|
|
107
|
+
: 'RUN_OPENYIDA_LOGIN_IF_USER_APPROVES',
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function buildBuilderFastPath(login, projectRoot, manifest, env = process.env) {
|
|
112
|
+
const auth = buildAuthFastPath(login, env);
|
|
113
|
+
const canTrustSummaryPreflight = auth.can_auto_use === true || auth.host_injected_token_mode === true;
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
schema_version: 1,
|
|
117
|
+
preflight: {
|
|
118
|
+
recommended_command: 'openyida agent-capabilities --summary-json',
|
|
119
|
+
run_once: true,
|
|
120
|
+
additional_env_check_default: false,
|
|
121
|
+
additional_login_check_default: false,
|
|
122
|
+
trust_summary_json_as_builder_preflight: true,
|
|
123
|
+
full_capabilities_command: 'openyida agent-capabilities --json',
|
|
124
|
+
full_capabilities_only_when: 'Need full command_manifest.commands, forbidden_aliases, or detailed environment diagnostics.',
|
|
125
|
+
},
|
|
126
|
+
auth,
|
|
127
|
+
environment_check_simplification: {
|
|
128
|
+
contract_role: 'machine_readable_fast_path_for_builder_runtime_not_yida_agent_business_logic',
|
|
129
|
+
minimal_probe_commands: [
|
|
130
|
+
'which openyida',
|
|
131
|
+
'openyida agent-capabilities --summary-json',
|
|
132
|
+
],
|
|
133
|
+
can_skip_default_exploration_when_summary_ok: canTrustSummaryPreflight,
|
|
134
|
+
skip_default_command_patterns: [
|
|
135
|
+
'openyida --help',
|
|
136
|
+
'openyida <command> --help',
|
|
137
|
+
'openyida env --json',
|
|
138
|
+
'openyida login --check-only --json',
|
|
139
|
+
'browser login',
|
|
140
|
+
'qr login',
|
|
141
|
+
'Playwright cookie inspection',
|
|
142
|
+
'cookie cache inspection',
|
|
143
|
+
'openyida app-list',
|
|
144
|
+
],
|
|
145
|
+
skip_help_discovery_default: true,
|
|
146
|
+
skip_env_noise_default: true,
|
|
147
|
+
skip_login_check_only_default: canTrustSummaryPreflight,
|
|
148
|
+
skip_browser_login_default: auth.host_token_env_detected,
|
|
149
|
+
skip_cookie_or_playwright_checks_default: true,
|
|
150
|
+
default_app_list_policy: 'skip_when_bound_app_type_unique',
|
|
151
|
+
stop_when_host_token_missing: auth.host_injected_token_mode && !auth.env_token_present,
|
|
152
|
+
},
|
|
153
|
+
command_contract: {
|
|
154
|
+
command_prefix: manifest.command_prefix,
|
|
155
|
+
supported_command_count: manifest.commands.length,
|
|
156
|
+
supported_command_ids: manifest.commands.map(entry => entry.id),
|
|
157
|
+
canonical_builder_commands: commandBriefs(manifest, BUILDER_CORE_COMMAND_IDS),
|
|
158
|
+
forbidden_aliases_available_in: 'openyida commands --json',
|
|
159
|
+
forbidden_alias_count: manifest.forbidden_aliases.length,
|
|
160
|
+
forbidden_aliases: forbiddenAliasBriefs(manifest),
|
|
161
|
+
unknown_command_policy: 'deny_with_manifest_suggestion_before_asking_user',
|
|
162
|
+
},
|
|
163
|
+
bound_context: {
|
|
164
|
+
existing_app_type_policy: 'do_not_call_app_list_by_default',
|
|
165
|
+
skip_app_list_when: [
|
|
166
|
+
'appType is already provided by the user',
|
|
167
|
+
'a bound app context is already available',
|
|
168
|
+
],
|
|
169
|
+
call_app_list_only_when: [
|
|
170
|
+
'name_search',
|
|
171
|
+
'target_conflict',
|
|
172
|
+
'failure_diagnosis',
|
|
173
|
+
],
|
|
174
|
+
},
|
|
175
|
+
resource_context_resolution: {
|
|
176
|
+
contract_role: 'structured_runtime_hint_not_skill_stage_rewrite',
|
|
177
|
+
if_bound_app_type_unique: {
|
|
178
|
+
action: 'reuse_bound_app_type',
|
|
179
|
+
command: null,
|
|
180
|
+
skip_command_ids: ['app-list'],
|
|
181
|
+
},
|
|
182
|
+
app_name_search: {
|
|
183
|
+
command_id: 'app-list',
|
|
184
|
+
usage: 'openyida app-list [--size N]',
|
|
185
|
+
},
|
|
186
|
+
app_forms_or_pages_lookup: {
|
|
187
|
+
command_id: 'list-forms',
|
|
188
|
+
usage: 'openyida list-forms <appType> [--keyword <text>]',
|
|
189
|
+
},
|
|
190
|
+
schema_or_field_lookup: {
|
|
191
|
+
command_id: 'get-schema',
|
|
192
|
+
usages: [
|
|
193
|
+
'openyida get-schema <appType> <formUuid> --summary-json',
|
|
194
|
+
'openyida get-schema <appType> --all --summary-json --keyword <text> --output-dir .cache/openyida/<task>/schemas',
|
|
195
|
+
],
|
|
196
|
+
},
|
|
197
|
+
preflight_context: {
|
|
198
|
+
command_id: 'agent-capabilities',
|
|
199
|
+
usage: 'openyida agent-capabilities --summary-json',
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
paths: {
|
|
203
|
+
workdir: projectRoot,
|
|
204
|
+
page_source_cli_path_policy: 'If builder Bash cwd is the project/ directory, pass page source paths as pages/src/<file>.',
|
|
205
|
+
page_source_examples: [
|
|
206
|
+
'pages/src/home.canvas.jsx',
|
|
207
|
+
'pages/src/dashboard.oyd.jsx',
|
|
208
|
+
],
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function compactBuilderFastPath(builderFastPath) {
|
|
214
|
+
const environment = builderFastPath.environment_check_simplification;
|
|
215
|
+
const commandContract = builderFastPath.command_contract;
|
|
216
|
+
const resourceContext = builderFastPath.resource_context_resolution;
|
|
217
|
+
const paths = builderFastPath.paths;
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
schema_version: builderFastPath.schema_version,
|
|
221
|
+
preflight: builderFastPath.preflight,
|
|
222
|
+
auth: {
|
|
223
|
+
mode: builderFastPath.auth.mode,
|
|
224
|
+
source: builderFastPath.auth.source,
|
|
225
|
+
can_auto_use: builderFastPath.auth.can_auto_use,
|
|
226
|
+
host_injected_token_mode: builderFastPath.auth.host_injected_token_mode,
|
|
227
|
+
host_token_env_detected: builderFastPath.auth.host_token_env_detected,
|
|
228
|
+
env_token_present: builderFastPath.auth.env_token_present,
|
|
229
|
+
interactive_login_allowed: builderFastPath.auth.interactive_login_allowed,
|
|
230
|
+
browser_session_auth_allowed: builderFastPath.auth.browser_session_auth_allowed,
|
|
231
|
+
auth_runtime: builderFastPath.auth.auth_runtime,
|
|
232
|
+
cookie_auth_supported: builderFastPath.auth.cookie_auth_supported,
|
|
233
|
+
cookie_check_required: builderFastPath.auth.cookie_check_required,
|
|
234
|
+
playwright_cookie_check_required: builderFastPath.auth.playwright_cookie_check_required,
|
|
235
|
+
qr_login_required: builderFastPath.auth.qr_login_required,
|
|
236
|
+
missing_token_action: builderFastPath.auth.missing_token_action,
|
|
237
|
+
},
|
|
238
|
+
environment_check_simplification: {
|
|
239
|
+
minimal_probe_commands: environment.minimal_probe_commands,
|
|
240
|
+
can_skip_default_exploration_when_summary_ok: environment.can_skip_default_exploration_when_summary_ok,
|
|
241
|
+
skip_login_check_only_default: environment.skip_login_check_only_default,
|
|
242
|
+
skip_browser_login_default: environment.skip_browser_login_default,
|
|
243
|
+
skip_cookie_or_playwright_checks_default: environment.skip_cookie_or_playwright_checks_default,
|
|
244
|
+
stop_when_host_token_missing: environment.stop_when_host_token_missing,
|
|
245
|
+
default_app_list_policy: environment.default_app_list_policy,
|
|
246
|
+
},
|
|
247
|
+
command_contract: {
|
|
248
|
+
command_prefix: commandContract.command_prefix,
|
|
249
|
+
supported_command_count: commandContract.supported_command_count,
|
|
250
|
+
canonical_builder_command_ids: commandContract.canonical_builder_commands.map(entry => entry.id),
|
|
251
|
+
forbidden_aliases_available_in: commandContract.forbidden_aliases_available_in,
|
|
252
|
+
forbidden_alias_count: commandContract.forbidden_alias_count,
|
|
253
|
+
unknown_command_policy: commandContract.unknown_command_policy,
|
|
254
|
+
},
|
|
255
|
+
bound_context: {
|
|
256
|
+
existing_app_type_policy: builderFastPath.bound_context.existing_app_type_policy,
|
|
257
|
+
},
|
|
258
|
+
resource_context_resolution: {
|
|
259
|
+
if_bound_app_type_unique: resourceContext.if_bound_app_type_unique,
|
|
260
|
+
full_contract_in: 'openyida agent-capabilities --json',
|
|
261
|
+
},
|
|
262
|
+
paths: {
|
|
263
|
+
page_source_cli_path_policy: paths.page_source_cli_path_policy,
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
27
268
|
function canonicalize(value) {
|
|
28
269
|
if (Array.isArray(value)) {
|
|
29
270
|
return value.map(canonicalize);
|
|
@@ -47,10 +288,16 @@ function buildCommandManifestDigest(manifest) {
|
|
|
47
288
|
summary: {
|
|
48
289
|
command_count: manifest.summary.command_count,
|
|
49
290
|
group_count: manifest.summary.group_count,
|
|
291
|
+
forbidden_alias_count: manifest.summary.forbidden_alias_count,
|
|
50
292
|
side_effect_counts: manifest.summary.side_effect_counts,
|
|
51
293
|
permission_mode_counts: manifest.summary.permission_mode_counts,
|
|
52
294
|
core_workflows: manifest.summary.core_workflows,
|
|
53
295
|
},
|
|
296
|
+
forbidden_aliases: (manifest.forbidden_aliases || []).map(entry => ({
|
|
297
|
+
pattern: entry.pattern,
|
|
298
|
+
suggested_command_id: entry.suggested_command_id,
|
|
299
|
+
suggested_usage: entry.suggested_usage,
|
|
300
|
+
})),
|
|
54
301
|
commands: manifest.commands.map(entry => ({
|
|
55
302
|
id: entry.id,
|
|
56
303
|
usage: entry.usage,
|
|
@@ -73,6 +320,9 @@ function buildAgentCapabilitiesSummary() {
|
|
|
73
320
|
const manifest = buildCommandManifest({ t, version });
|
|
74
321
|
const projectRoot = envSnapshot.active.projectRoot;
|
|
75
322
|
const loginStatus = getAuthStatus({ projectRoot, includeSecrets: false });
|
|
323
|
+
const builderFastPath = compactBuilderFastPath(
|
|
324
|
+
buildBuilderFastPath(loginStatus, projectRoot, manifest)
|
|
325
|
+
);
|
|
76
326
|
|
|
77
327
|
return {
|
|
78
328
|
schema_version: 1,
|
|
@@ -87,6 +337,7 @@ function buildAgentCapabilitiesSummary() {
|
|
|
87
337
|
command_manifest_digest_algorithm: 'sha256',
|
|
88
338
|
command_count: manifest.summary.command_count,
|
|
89
339
|
full_capabilities_command: 'openyida agent-capabilities --json',
|
|
340
|
+
builder_fast_path: builderFastPath,
|
|
90
341
|
};
|
|
91
342
|
}
|
|
92
343
|
|
|
@@ -95,6 +346,7 @@ function buildAgentCapabilities() {
|
|
|
95
346
|
const manifest = buildCommandManifest({ t, version });
|
|
96
347
|
const projectRoot = envSnapshot.active.projectRoot;
|
|
97
348
|
const loginStatus = redactLogin(getAuthStatus({ projectRoot, includeSecrets: false }));
|
|
349
|
+
const builderFastPath = buildBuilderFastPath(loginStatus, projectRoot, manifest);
|
|
98
350
|
|
|
99
351
|
return {
|
|
100
352
|
schema_version: 1,
|
|
@@ -116,6 +368,7 @@ function buildAgentCapabilities() {
|
|
|
116
368
|
openyida_task_cache_dir: path.join(projectRoot, '.cache', 'openyida'),
|
|
117
369
|
default_full_app_workflow: manifest.summary.core_workflows.full_app_fast_build,
|
|
118
370
|
},
|
|
371
|
+
builder_fast_path: builderFastPath,
|
|
119
372
|
skills: {
|
|
120
373
|
index_file: 'skills-index.json',
|
|
121
374
|
entry: 'openyida',
|
|
@@ -134,6 +387,8 @@ function buildAgentCapabilities() {
|
|
|
134
387
|
ask_command_ids: manifest.summary.ask_command_ids,
|
|
135
388
|
deny_command_ids: manifest.summary.deny_command_ids,
|
|
136
389
|
core_workflows: manifest.summary.core_workflows,
|
|
390
|
+
forbidden_alias_count: manifest.summary.forbidden_alias_count,
|
|
391
|
+
forbidden_alias_patterns: manifest.summary.forbidden_alias_patterns,
|
|
137
392
|
},
|
|
138
393
|
sideEffects: {
|
|
139
394
|
read_only_preflight: [
|
|
@@ -153,6 +408,8 @@ function buildAgentCapabilities() {
|
|
|
153
408
|
groups: manifest.groups,
|
|
154
409
|
side_effect_schema: manifest.side_effect_schema,
|
|
155
410
|
permission_schema: manifest.permission_schema,
|
|
411
|
+
forbidden_alias_schema: manifest.forbidden_alias_schema,
|
|
412
|
+
forbidden_aliases: manifest.forbidden_aliases,
|
|
156
413
|
summary: manifest.summary,
|
|
157
414
|
commands: manifest.commands,
|
|
158
415
|
},
|
|
@@ -173,6 +430,7 @@ async function run(args = []) {
|
|
|
173
430
|
module.exports = {
|
|
174
431
|
buildAgentCapabilities,
|
|
175
432
|
buildAgentCapabilitiesSummary,
|
|
433
|
+
compactBuilderFastPath,
|
|
176
434
|
buildCommandManifestDigest,
|
|
177
435
|
run,
|
|
178
436
|
};
|
|
@@ -77,6 +77,26 @@ const PERMISSION_SCHEMA = Object.freeze({
|
|
|
77
77
|
},
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
+
const FORBIDDEN_ALIAS_SCHEMA = Object.freeze({
|
|
81
|
+
version: 1,
|
|
82
|
+
fields: {
|
|
83
|
+
pattern: 'Human-readable forbidden argv pattern. Match against arguments after openyida/yida.',
|
|
84
|
+
matcher: 'Structured matcher for agents that need deterministic deny decisions.',
|
|
85
|
+
suggested_command_id: 'Canonical command id to run instead.',
|
|
86
|
+
suggested_usage: 'Concrete canonical OpenYida usage to show or execute after parameter correction.',
|
|
87
|
+
alternative_command_ids: 'Optional extra command ids when the alias is ambiguous.',
|
|
88
|
+
alternative_usages: 'Optional concrete usages for alternative command ids.',
|
|
89
|
+
message_key: 'i18n key for the explanation suitable for a CLI hint or agent denial reason.',
|
|
90
|
+
message_args: 'Arguments for message_key interpolation.',
|
|
91
|
+
message: 'Localized explanation generated from message_key when a translator is available.',
|
|
92
|
+
},
|
|
93
|
+
matcher_types: {
|
|
94
|
+
argv_prefix: 'Deny when the invocation argv begins with matcher.tokens.',
|
|
95
|
+
command_has_option: 'Deny when matcher.command is the command root and matcher.option appears anywhere in argv.',
|
|
96
|
+
},
|
|
97
|
+
agent_policy: 'Deny forbidden aliases before asking the user; return suggested_command_id and suggested_usage as the repair hint.',
|
|
98
|
+
});
|
|
99
|
+
|
|
80
100
|
function sideEffect(kind, overrides = {}) {
|
|
81
101
|
if (!SIDE_EFFECT_BASES[kind]) {
|
|
82
102
|
throw new Error('Unknown side effect kind: ' + kind);
|
|
@@ -142,6 +162,46 @@ function clonePermissionSchema() {
|
|
|
142
162
|
return JSON.parse(JSON.stringify(PERMISSION_SCHEMA));
|
|
143
163
|
}
|
|
144
164
|
|
|
165
|
+
function cloneForbiddenAliasSchema() {
|
|
166
|
+
return JSON.parse(JSON.stringify(FORBIDDEN_ALIAS_SCHEMA));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function cloneMatcher(matcher) {
|
|
170
|
+
if (!matcher || typeof matcher !== 'object') {
|
|
171
|
+
return matcher;
|
|
172
|
+
}
|
|
173
|
+
const cloned = { ...matcher };
|
|
174
|
+
if (Array.isArray(matcher.tokens)) {
|
|
175
|
+
cloned.tokens = [...matcher.tokens];
|
|
176
|
+
}
|
|
177
|
+
return cloned;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function cloneForbiddenAlias(entry) {
|
|
181
|
+
const cloned = {
|
|
182
|
+
...entry,
|
|
183
|
+
matcher: cloneMatcher(entry.matcher),
|
|
184
|
+
};
|
|
185
|
+
if (Array.isArray(entry.alternative_command_ids)) {
|
|
186
|
+
cloned.alternative_command_ids = [...entry.alternative_command_ids];
|
|
187
|
+
}
|
|
188
|
+
if (Array.isArray(entry.alternative_usages)) {
|
|
189
|
+
cloned.alternative_usages = [...entry.alternative_usages];
|
|
190
|
+
}
|
|
191
|
+
if (Array.isArray(entry.message_args)) {
|
|
192
|
+
cloned.message_args = [...entry.message_args];
|
|
193
|
+
}
|
|
194
|
+
return cloned;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function localizeForbiddenAlias(entry, translate) {
|
|
198
|
+
const localized = cloneForbiddenAlias(entry);
|
|
199
|
+
if (localized.message_key) {
|
|
200
|
+
localized.message = translate(localized.message_key, ...(localized.message_args || []));
|
|
201
|
+
}
|
|
202
|
+
return localized;
|
|
203
|
+
}
|
|
204
|
+
|
|
145
205
|
function normalizePermission(metadata, sideEffectMetadata) {
|
|
146
206
|
const normalized = { ...metadata };
|
|
147
207
|
if (sideEffectMetadata && sideEffectMetadata.kind === 'mixed') {
|
|
@@ -185,6 +245,114 @@ function listCommandPermissionIds() {
|
|
|
185
245
|
return [...COMMAND_PERMISSIONS.keys()].sort();
|
|
186
246
|
}
|
|
187
247
|
|
|
248
|
+
function normalizeInvocationArgv(argv = []) {
|
|
249
|
+
const normalized = (Array.isArray(argv) ? argv : [])
|
|
250
|
+
.map(value => String(value || '').trim())
|
|
251
|
+
.filter(Boolean);
|
|
252
|
+
if (normalized[0] === 'openyida' || normalized[0] === 'yida') {
|
|
253
|
+
return normalized.slice(1);
|
|
254
|
+
}
|
|
255
|
+
return normalized;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function matcherMatchesArgv(argv, matcher) {
|
|
259
|
+
const normalized = normalizeInvocationArgv(argv);
|
|
260
|
+
if (!matcher || typeof matcher !== 'object' || normalized.length === 0) {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (matcher.type === 'argv_prefix') {
|
|
265
|
+
const tokens = Array.isArray(matcher.tokens) ? matcher.tokens : [];
|
|
266
|
+
return tokens.length > 0 && tokens.every((token, index) => normalized[index] === token);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (matcher.type === 'command_has_option') {
|
|
270
|
+
return normalized[0] === matcher.command && normalized.includes(matcher.option);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function findForbiddenAliasSuggestion(argv = []) {
|
|
277
|
+
const match = FORBIDDEN_ALIASES.find(entry => matcherMatchesArgv(argv, entry.matcher));
|
|
278
|
+
return match ? cloneForbiddenAlias(match) : null;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function levenshteinDistance(left, right) {
|
|
282
|
+
const a = String(left || '');
|
|
283
|
+
const b = String(right || '');
|
|
284
|
+
const dp = Array.from({ length: a.length + 1 }, () => Array(b.length + 1).fill(0));
|
|
285
|
+
for (let i = 0; i <= a.length; i++) {
|
|
286
|
+
dp[i][0] = i;
|
|
287
|
+
}
|
|
288
|
+
for (let j = 0; j <= b.length; j++) {
|
|
289
|
+
dp[0][j] = j;
|
|
290
|
+
}
|
|
291
|
+
for (let i = 1; i <= a.length; i++) {
|
|
292
|
+
for (let j = 1; j <= b.length; j++) {
|
|
293
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
294
|
+
dp[i][j] = Math.min(
|
|
295
|
+
dp[i - 1][j] + 1,
|
|
296
|
+
dp[i][j - 1] + 1,
|
|
297
|
+
dp[i - 1][j - 1] + cost
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return dp[a.length][b.length];
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function findNearestCommandSuggestion(argv = []) {
|
|
305
|
+
const normalized = normalizeInvocationArgv(argv);
|
|
306
|
+
const root = normalized[0];
|
|
307
|
+
if (!root) {
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const candidates = new Map();
|
|
312
|
+
for (const entry of flattenCommandManifest()) {
|
|
313
|
+
if (!entry.hidden && entry.path && entry.path[0] && !candidates.has(entry.path[0])) {
|
|
314
|
+
candidates.set(entry.path[0], entry);
|
|
315
|
+
}
|
|
316
|
+
for (const alias of entry.aliases || []) {
|
|
317
|
+
const aliasRoot = String(alias || '').trim().split(/\s+/)[0];
|
|
318
|
+
if (aliasRoot && !candidates.has(aliasRoot)) {
|
|
319
|
+
candidates.set(aliasRoot, entry);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (candidates.has(root)) {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
let best = null;
|
|
329
|
+
for (const [candidate, entry] of candidates) {
|
|
330
|
+
const distance = levenshteinDistance(root, candidate);
|
|
331
|
+
if (!best || distance < best.distance) {
|
|
332
|
+
best = { candidate, entry, distance };
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const threshold = Math.max(2, Math.floor(root.length / 3));
|
|
337
|
+
if (!best || best.distance > threshold) {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return {
|
|
342
|
+
id: `nearest.${root}`,
|
|
343
|
+
pattern: root,
|
|
344
|
+
matcher: { type: 'argv_prefix', tokens: [root] },
|
|
345
|
+
suggested_command_id: best.entry.id,
|
|
346
|
+
suggested_usage: `openyida ${best.entry.usage}`,
|
|
347
|
+
message_key: 'cli.nearest_command_suggestion',
|
|
348
|
+
message_args: [root, best.candidate],
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function findCommandSuggestion(argv = []) {
|
|
353
|
+
return findForbiddenAliasSuggestion(argv) || findNearestCommandSuggestion(argv);
|
|
354
|
+
}
|
|
355
|
+
|
|
188
356
|
const COMMAND_SIDE_EFFECTS = new Map([
|
|
189
357
|
...sideEffectEntries([
|
|
190
358
|
'agent-capabilities',
|
|
@@ -723,6 +891,68 @@ const COMMAND_PERMISSIONS = new Map([
|
|
|
723
891
|
})),
|
|
724
892
|
]);
|
|
725
893
|
|
|
894
|
+
const FORBIDDEN_ALIASES = Object.freeze([
|
|
895
|
+
Object.freeze({
|
|
896
|
+
id: 'forbidden-alias.list-apps',
|
|
897
|
+
pattern: 'list-apps',
|
|
898
|
+
matcher: { type: 'argv_prefix', tokens: ['list-apps'] },
|
|
899
|
+
suggested_command_id: 'app-list',
|
|
900
|
+
suggested_usage: 'openyida app-list [--size N]',
|
|
901
|
+
message_key: 'cli.forbidden_alias_list_apps',
|
|
902
|
+
message_args: ['list-apps', 'app-list'],
|
|
903
|
+
}),
|
|
904
|
+
Object.freeze({
|
|
905
|
+
id: 'forbidden-alias.get-app',
|
|
906
|
+
pattern: 'get-app',
|
|
907
|
+
matcher: { type: 'argv_prefix', tokens: ['get-app'] },
|
|
908
|
+
suggested_command_id: 'app-list',
|
|
909
|
+
suggested_usage: 'openyida app-list [--size N]',
|
|
910
|
+
alternative_command_ids: ['get-schema', 'agent-capabilities'],
|
|
911
|
+
alternative_usages: [
|
|
912
|
+
'openyida get-schema <appType> <formUuid|--all> [--summary-json|--field-map-json]',
|
|
913
|
+
'openyida agent-capabilities --summary-json',
|
|
914
|
+
],
|
|
915
|
+
message_key: 'cli.forbidden_alias_get_app',
|
|
916
|
+
message_args: ['get-app', 'app-list', 'get-schema', 'agent-capabilities'],
|
|
917
|
+
}),
|
|
918
|
+
Object.freeze({
|
|
919
|
+
id: 'forbidden-alias.create-app-json',
|
|
920
|
+
pattern: 'create-app --json',
|
|
921
|
+
matcher: { type: 'command_has_option', command: 'create-app', option: '--json' },
|
|
922
|
+
suggested_command_id: 'create-app',
|
|
923
|
+
suggested_usage: 'openyida create-app "<name>"|--name <name> [options] [--locale zh_CN|en_US|ja_JP] [--open|--no-open]',
|
|
924
|
+
message_key: 'cli.forbidden_alias_create_app_json',
|
|
925
|
+
message_args: ['create-app --json', 'create-app'],
|
|
926
|
+
}),
|
|
927
|
+
Object.freeze({
|
|
928
|
+
id: 'forbidden-alias.create-page-app-type-option',
|
|
929
|
+
pattern: 'create-page --app-type',
|
|
930
|
+
matcher: { type: 'command_has_option', command: 'create-page', option: '--app-type' },
|
|
931
|
+
suggested_command_id: 'create-page',
|
|
932
|
+
suggested_usage: 'openyida create-page <appType> "<name>" [--mode dashboard] [--locale zh_CN|en_US|ja_JP] [--open|--no-open]',
|
|
933
|
+
message_key: 'cli.forbidden_alias_create_page_app_type_option',
|
|
934
|
+
message_args: ['create-page', '--app-type'],
|
|
935
|
+
}),
|
|
936
|
+
Object.freeze({
|
|
937
|
+
id: 'forbidden-alias.get-schema-app-type-option',
|
|
938
|
+
pattern: 'get-schema --app-type',
|
|
939
|
+
matcher: { type: 'command_has_option', command: 'get-schema', option: '--app-type' },
|
|
940
|
+
suggested_command_id: 'get-schema',
|
|
941
|
+
suggested_usage: 'openyida get-schema <appType> <formUuid|--all> [--summary-json|--field-map-json]',
|
|
942
|
+
message_key: 'cli.forbidden_alias_get_schema_app_type_option',
|
|
943
|
+
message_args: ['get-schema', '--app-type'],
|
|
944
|
+
}),
|
|
945
|
+
Object.freeze({
|
|
946
|
+
id: 'forbidden-alias.get-schema-form-uuid-option',
|
|
947
|
+
pattern: 'get-schema --form-uuid',
|
|
948
|
+
matcher: { type: 'command_has_option', command: 'get-schema', option: '--form-uuid' },
|
|
949
|
+
suggested_command_id: 'get-schema',
|
|
950
|
+
suggested_usage: 'openyida get-schema <appType> <formUuid|--all> [--summary-json|--field-map-json]',
|
|
951
|
+
message_key: 'cli.forbidden_alias_get_schema_form_uuid_option',
|
|
952
|
+
message_args: ['get-schema', '--form-uuid'],
|
|
953
|
+
}),
|
|
954
|
+
]);
|
|
955
|
+
|
|
726
956
|
function command(id, path, usage, descriptionKey, options = {}) {
|
|
727
957
|
const commandSideEffect = COMMAND_SIDE_EFFECTS.get(id);
|
|
728
958
|
if (!commandSideEffect) {
|
|
@@ -1069,6 +1299,8 @@ function summarizeLocalizedCommands(commands) {
|
|
|
1069
1299
|
return {
|
|
1070
1300
|
command_count: commands.length,
|
|
1071
1301
|
group_count: COMMAND_GROUPS.length,
|
|
1302
|
+
forbidden_alias_count: FORBIDDEN_ALIASES.length,
|
|
1303
|
+
forbidden_alias_patterns: FORBIDDEN_ALIASES.map(entry => entry.pattern),
|
|
1072
1304
|
side_effect_counts: sideEffectCounts,
|
|
1073
1305
|
ids_by_side_effect: idsBySideEffect,
|
|
1074
1306
|
read_only_command_ids: readOnlyCommandIds,
|
|
@@ -1138,6 +1370,8 @@ function buildCommandManifest(options = {}) {
|
|
|
1138
1370
|
})),
|
|
1139
1371
|
side_effect_schema: cloneSideEffectSchema(),
|
|
1140
1372
|
permission_schema: clonePermissionSchema(),
|
|
1373
|
+
forbidden_alias_schema: cloneForbiddenAliasSchema(),
|
|
1374
|
+
forbidden_aliases: FORBIDDEN_ALIASES.map(entry => localizeForbiddenAlias(entry, translate)),
|
|
1141
1375
|
summary: summarizeLocalizedCommands(localizedCommands),
|
|
1142
1376
|
commands: localizedCommands,
|
|
1143
1377
|
};
|
|
@@ -1146,6 +1380,8 @@ function buildCommandManifest(options = {}) {
|
|
|
1146
1380
|
module.exports = {
|
|
1147
1381
|
COMMAND_GROUPS,
|
|
1148
1382
|
buildCommandManifest,
|
|
1383
|
+
findCommandSuggestion,
|
|
1384
|
+
findForbiddenAliasSuggestion,
|
|
1149
1385
|
flattenCommandManifest,
|
|
1150
1386
|
listCommandPermissionIds,
|
|
1151
1387
|
listCommandSideEffectIds,
|
package/lib/core/locales/en.js
CHANGED
|
@@ -239,6 +239,14 @@ Examples:
|
|
|
239
239
|
openyida export-conversation --list List available conversations
|
|
240
240
|
`,
|
|
241
241
|
unknown_command: 'Unknown command: {0}',
|
|
242
|
+
command_suggestion: 'Suggested command: {0}',
|
|
243
|
+
forbidden_alias_list_apps: '`{0}` is not an OpenYida command; use `{1}` for application discovery.',
|
|
244
|
+
forbidden_alias_get_app: '`{0}` is ambiguous; use `{1}` for app name search, `{2}` for form/schema lookup, or `{3}` for bound context preflight.',
|
|
245
|
+
forbidden_alias_create_app_json: '`{0}` is not a separate command contract; use canonical `{1}` output.',
|
|
246
|
+
forbidden_alias_create_page_app_type_option: '`{0}` takes appType as the first positional argument, not `{1}`.',
|
|
247
|
+
forbidden_alias_get_schema_app_type_option: '`{0}` takes appType as the first positional argument, not `{1}`.',
|
|
248
|
+
forbidden_alias_get_schema_form_uuid_option: '`{0}` takes formUuid as the second positional argument, not `{1}`.',
|
|
249
|
+
nearest_command_suggestion: 'Unknown OpenYida command root "{0}". Did you mean "{1}"?',
|
|
242
250
|
run_help: 'Run openyida --help for usage',
|
|
243
251
|
integration_help: 'Usage: openyida integration <create|list|enable|disable|check|diagnose> ...',
|
|
244
252
|
integration_unknown: 'Unknown integration subcommand: {0}',
|
|
@@ -1201,6 +1209,7 @@ Examples:
|
|
|
1201
1209
|
exception: '\n❌ Publish error: {0}',
|
|
1202
1210
|
error: '\n❌ Publish error: {0}',
|
|
1203
1211
|
source_not_found: '❌ Source file not found: {0}',
|
|
1212
|
+
source_path_hint: '💡 Try this source file path: {0}',
|
|
1204
1213
|
usage: 'Usage: openyida publish <sourceFile> <appType> <formUuid> [--health-check] [--canvas]',
|
|
1205
1214
|
example: 'Example: openyida publish pages/src/xxx.js APP_XXX FORM-XXX --health-check',
|
|
1206
1215
|
},
|
package/lib/core/locales/zh.js
CHANGED
|
@@ -240,6 +240,14 @@ openyida - 宜搭命令行工具
|
|
|
240
240
|
openyida export-conversation --list 列出可用对话
|
|
241
241
|
`,
|
|
242
242
|
unknown_command: '未知命令: {0}',
|
|
243
|
+
command_suggestion: '建议命令: {0}',
|
|
244
|
+
forbidden_alias_list_apps: '`{0}` 不是 OpenYida 命令;请使用 `{1}` 查询应用。',
|
|
245
|
+
forbidden_alias_get_app: '`{0}` 含义不明确;按应用名称搜索请用 `{1}`,查询表单/字段结构请用 `{2}`,读取绑定上下文请用 `{3}`。',
|
|
246
|
+
forbidden_alias_create_app_json: '`{0}` 不是独立命令契约;请使用规范的 `{1}` 输出。',
|
|
247
|
+
forbidden_alias_create_page_app_type_option: '`{0}` 使用第一个位置参数传入 appType,不使用 `{1}`。',
|
|
248
|
+
forbidden_alias_get_schema_app_type_option: '`{0}` 使用第一个位置参数传入 appType,不使用 `{1}`。',
|
|
249
|
+
forbidden_alias_get_schema_form_uuid_option: '`{0}` 使用第二个位置参数传入 formUuid,不使用 `{1}`。',
|
|
250
|
+
nearest_command_suggestion: '未知 OpenYida 命令根「{0}」。你是不是想用「{1}」?',
|
|
243
251
|
run_help: '运行 openyida --help 查看帮助',
|
|
244
252
|
integration_help: '用法: openyida integration <create|list|enable|disable|check|diagnose> ...',
|
|
245
253
|
integration_unknown: '未知的 integration 子命令: {0}',
|
|
@@ -1195,6 +1203,7 @@ openyida - 宜搭命令行工具
|
|
|
1195
1203
|
exception: '\n❌ 发布异常: {0}',
|
|
1196
1204
|
error: '\n❌ 发布异常: {0}',
|
|
1197
1205
|
source_not_found: '❌ 源文件不存在:{0}',
|
|
1206
|
+
source_path_hint: '💡 可尝试使用源文件路径:{0}',
|
|
1198
1207
|
usage: '用法: openyida publish <源文件路径> <appType> <formUuid> [--health-check] [--canvas]',
|
|
1199
1208
|
example: '示例:openyida publish pages/src/xxx.js APP_XXX FORM-XXX --health-check',
|
|
1200
1209
|
},
|
package/package.json
CHANGED
|
@@ -260,6 +260,77 @@ function validatePermissions(commands) {
|
|
|
260
260
|
}
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
+
function validateForbiddenAliases() {
|
|
264
|
+
const manifest = require('../lib/core/command-manifest').buildCommandManifest();
|
|
265
|
+
const commandIds = new Set(flattenCommandManifest().map(entry => entry.id));
|
|
266
|
+
const allowedMatcherTypes = new Set([
|
|
267
|
+
'argv_prefix',
|
|
268
|
+
'command_has_option',
|
|
269
|
+
]);
|
|
270
|
+
const seenPatterns = new Set();
|
|
271
|
+
|
|
272
|
+
if (!manifest.forbidden_alias_schema || manifest.forbidden_alias_schema.version !== 1) {
|
|
273
|
+
errors.push('Command manifest is missing forbidden_alias_schema.version 1');
|
|
274
|
+
}
|
|
275
|
+
if (!Array.isArray(manifest.forbidden_aliases)) {
|
|
276
|
+
errors.push('Command manifest forbidden_aliases must be an array');
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
for (const entry of manifest.forbidden_aliases) {
|
|
281
|
+
if (!entry || typeof entry !== 'object') {
|
|
282
|
+
errors.push('Forbidden alias entries must be objects');
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
if (typeof entry.id !== 'string' || !entry.id.trim()) {
|
|
286
|
+
errors.push('Forbidden alias entry is missing id');
|
|
287
|
+
}
|
|
288
|
+
if (typeof entry.pattern !== 'string' || !entry.pattern.trim()) {
|
|
289
|
+
errors.push(`Forbidden alias "${entry.id || '(missing id)'}" is missing pattern`);
|
|
290
|
+
} else if (seenPatterns.has(entry.pattern)) {
|
|
291
|
+
errors.push(`Duplicate forbidden alias pattern: ${entry.pattern}`);
|
|
292
|
+
} else {
|
|
293
|
+
seenPatterns.add(entry.pattern);
|
|
294
|
+
}
|
|
295
|
+
if (!entry.matcher || typeof entry.matcher !== 'object' || Array.isArray(entry.matcher)) {
|
|
296
|
+
errors.push(`Forbidden alias "${entry.id}" matcher must be an object`);
|
|
297
|
+
} else if (!allowedMatcherTypes.has(entry.matcher.type)) {
|
|
298
|
+
errors.push(`Forbidden alias "${entry.id}" has invalid matcher type "${entry.matcher.type}"`);
|
|
299
|
+
} else if (entry.matcher.type === 'argv_prefix') {
|
|
300
|
+
if (!Array.isArray(entry.matcher.tokens) || entry.matcher.tokens.length === 0) {
|
|
301
|
+
errors.push(`Forbidden alias "${entry.id}" argv_prefix matcher.tokens must be a non-empty array`);
|
|
302
|
+
}
|
|
303
|
+
} else if (entry.matcher.type === 'command_has_option') {
|
|
304
|
+
if (typeof entry.matcher.command !== 'string' || !entry.matcher.command.trim()) {
|
|
305
|
+
errors.push(`Forbidden alias "${entry.id}" command_has_option matcher.command must be a non-empty string`);
|
|
306
|
+
}
|
|
307
|
+
if (typeof entry.matcher.option !== 'string' || !entry.matcher.option.startsWith('--')) {
|
|
308
|
+
errors.push(`Forbidden alias "${entry.id}" command_has_option matcher.option must be a --option string`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (!commandIds.has(entry.suggested_command_id)) {
|
|
312
|
+
errors.push(`Forbidden alias "${entry.id}" references unknown suggested_command_id "${entry.suggested_command_id}"`);
|
|
313
|
+
}
|
|
314
|
+
if (typeof entry.suggested_usage !== 'string' || !entry.suggested_usage.startsWith('openyida ')) {
|
|
315
|
+
errors.push(`Forbidden alias "${entry.id}" suggested_usage must start with "openyida "`);
|
|
316
|
+
}
|
|
317
|
+
if (typeof entry.message_key !== 'string' || !entry.message_key.trim()) {
|
|
318
|
+
errors.push(`Forbidden alias "${entry.id}" message_key must be a non-empty string`);
|
|
319
|
+
}
|
|
320
|
+
if (entry.message_args !== undefined && !Array.isArray(entry.message_args)) {
|
|
321
|
+
errors.push(`Forbidden alias "${entry.id}" message_args must be an array when present`);
|
|
322
|
+
}
|
|
323
|
+
if (entry.message !== undefined && (typeof entry.message !== 'string' || !entry.message.trim())) {
|
|
324
|
+
errors.push(`Forbidden alias "${entry.id}" message must be a non-empty string when present`);
|
|
325
|
+
}
|
|
326
|
+
for (const commandId of entry.alternative_command_ids || []) {
|
|
327
|
+
if (!commandIds.has(commandId)) {
|
|
328
|
+
errors.push(`Forbidden alias "${entry.id}" references unknown alternative_command_id "${commandId}"`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
263
334
|
function collectPatternCovers(patterns) {
|
|
264
335
|
if (!Array.isArray(patterns)) {
|
|
265
336
|
return [];
|
|
@@ -316,6 +387,7 @@ function run() {
|
|
|
316
387
|
validateReadmeCoverage(commands);
|
|
317
388
|
validateSideEffects(commands);
|
|
318
389
|
validatePermissions(commands);
|
|
390
|
+
validateForbiddenAliases();
|
|
319
391
|
|
|
320
392
|
if (errors.length > 0) {
|
|
321
393
|
console.error('Command manifest validation failed:');
|
package/yida-skills/SKILL.md
CHANGED
|
@@ -8,7 +8,7 @@ description: >
|
|
|
8
8
|
|
|
9
9
|
# 宜搭 AI 应用开发指南
|
|
10
10
|
|
|
11
|
-
通过有 AI Coding 能力的智能体(悟空/Claude/Open Code 等)+ 宜搭低代码平台,实现一句话搭建或修改完整应用。所有操作通过 **`openyida`** CLI
|
|
11
|
+
通过有 AI Coding 能力的智能体(悟空/Claude/Open Code 等)+ 宜搭低代码平台,实现一句话搭建或修改完整应用。所有操作通过 **`openyida`** CLI 统一执行。登录态分流必须以 `openyida agent-capabilities --summary-json` 或 `openyida login --check-only --json` 返回的 auth snapshot 为准;只有 snapshot 明确返回 `login.auth_source=env` 或 `failure_reason=env_token_missing` 时,才按宿主注入 token 模式处理。其他未登录 token 场景走默认 OAuth token 登录,不要根据 agent 名称、宿主类型或手写环境判断自行分流;禁止读取 `.cache/cookies*.json`。
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
@@ -38,8 +38,9 @@ description: >
|
|
|
38
38
|
|---------|------|
|
|
39
39
|
| 命令跑不了(`command not found`) | openyida 未安装 → `npm install -g openyida` |
|
|
40
40
|
| Node/npm 版本不达标 | 先升级 Node(≥16)再装/升级 openyida |
|
|
41
|
-
| `login.auth_mode=token`
|
|
42
|
-
| `login.
|
|
41
|
+
| `login.auth_mode=token` 且 `status=ok` / `can_auto_use=true` | 继续执行业务命令 |
|
|
42
|
+
| snapshot 返回 `login.auth_source=env` / `failure_reason=env_token_missing` | STOP;宿主必须注入 `OPENYIDA_ACCESS_TOKEN` 或 `OPENYIDA_REFRESH_TOKEN`;禁止触发 OAuth;禁止读 `.cache/cookies*.json` |
|
|
43
|
+
| `login.auth_mode=token` 且未登录,且 snapshot 未返回 env 注入模式 | `openyida login`(指定入口带 URL 或 flag),完成后再 `openyida login --check-only --json` 验证 |
|
|
43
44
|
| `workdir_exists` / `active.projectRootExists` 为 false | 无工作目录 → `openyida copy` 初始化 |
|
|
44
45
|
|
|
45
46
|
**👉 环境异常、登录失败、悟空降级、OAuth token 登录异常等特殊分支 → [references/setup-and-env.md](references/setup-and-env.md)。正常 `agent-capabilities` 通过时不要默认读取该 reference。**
|
|
@@ -221,10 +222,11 @@ schema-managed create/update 必须等待用户对当前 `planId` 显式批准
|
|
|
221
222
|
### 致命规则(FATAL,违反即失败/报错)
|
|
222
223
|
|
|
223
224
|
1. **技能加载唯一入口**:执行任何子技能前,支持 `use_skill` 的宿主必须调用 `use_skill("<技能名>", "<本阶段目的>")` 加载对应技能;不要用 `Read` / `read_file` / `cat` 读取 SKILL.md 路径,不凭记忆猜参数格式。
|
|
224
|
-
2. **corpId 一致性检查**:创建或发布页面前对比 prd/resource context 与当前 auth
|
|
225
|
+
2. **corpId 一致性检查**:创建或发布页面前对比 prd/resource context 与当前 auth snapshot(本地 OAuth token session 或 snapshot 明确返回的宿主注入 env token)中的 corpId,不一致必须询问用户(重新登录到目标组织,或确认在当前组织继续操作已解析资源/缺失资源)。
|
|
225
226
|
3. **发布前本地校验**:普通自定义页面 `.oyd.jsx` / `.jsx` 发布前跑 `openyida check-page` + `openyida compile`;Code Canvas `.canvas.jsx` 不跑这两个普通自定义页面检查,改由 `openyida publish` 的 Canvas 编译阶段或 `compileCanvasLocal` 快检校验;JSON 配置写盘后先解析校验,再调用平台命令。
|
|
226
227
|
4. **页面源码修改必须发布闭环**:只要本轮 Write/Edit/Create 了页面源码 `project/pages/src/*.{canvas.jsx,canvas.tsx,oyd.jsx,jsx,tsx}`(含完整搭建、补齐、已有页面 update path、单点优化),final 前必须看到成功的 `openyida publish <source> <appType> <displayPageFormUuid>` 命令结果;本地文件编辑、diff、本地校验或编译只证明源码可发布,不等于远端页面已更新。若没有 publish 成功证据,final 只能说“源码已修改,尚未发布”,禁止说“页面已更新 / 已重新发布 / 已上线”。
|
|
227
228
|
5. **命令输入文件禁止 shell 写入**:当 OpenYida 命令需要 JSON/YAML/CSV/config/script 文件参数时,先使用当前 agent 运行时提供的结构化文件写入工具(如 create_file / Write / file edit tool)创建文件,再把路径传给命令;禁止用 shell heredoc、`cat`/`echo`/`printf`/`tee` 加输出重定向,或把命令 stdout 重定向成业务文件。
|
|
229
|
+
6. **读文件少用 Bash 噪声**:读取或定位 workspace 文件优先用宿主的 Read / Glob / Grep;OpenYida CLI 已返回成功 JSON、URL 或 `formUuid/appType` 时,不要再用 Bash `cat`/`ls` 做无意义复核。
|
|
228
230
|
|
|
229
231
|
### 重要规则(IMPORTANT,影响质量/性能/可维护性)
|
|
230
232
|
|
|
@@ -15,8 +15,10 @@ openyida login --check-only --json
|
|
|
15
15
|
|
|
16
16
|
## Auth Mode
|
|
17
17
|
|
|
18
|
-
-
|
|
19
|
-
-
|
|
18
|
+
- Do not infer auth mode from agent name, host product, workspace path, or a guessed environment variable.
|
|
19
|
+
- First use `openyida agent-capabilities --summary-json`; fallback to `openyida login --check-only --json` only when the compact snapshot is unavailable or insufficient.
|
|
20
|
+
- If the snapshot reports `login.auth_source=env` or `failure_reason=env_token_missing`, treat it as host-injected token mode; the host must provide token env such as `OPENYIDA_ACCESS_TOKEN` or `OPENYIDA_REFRESH_TOKEN`.
|
|
21
|
+
- Otherwise, `login.auth_mode=token` uses the default OAuth token session flow.
|
|
20
22
|
- NEVER infer auth from `.cache/cookies*.json`.
|
|
21
23
|
|
|
22
24
|
## Decision Table
|
|
@@ -26,13 +28,13 @@ openyida login --check-only --json
|
|
|
26
28
|
| command not found | install/update `openyida`; do not create resources |
|
|
27
29
|
| `workdir_exists=false` or `active.projectRootExists=false` | run `openyida copy`; do not create resources before workspace exists |
|
|
28
30
|
| `auth_mode=token`, `status=ok` or `can_auto_use=true` | continue |
|
|
29
|
-
| `
|
|
30
|
-
| `auth_mode=token`, not logged in,
|
|
31
|
-
| `auth_mode=token`, access token expired | run `openyida auth refresh`; if still failed and
|
|
31
|
+
| snapshot reports `auth_source=env` / `failure_reason=env_token_missing` | Treat as host-injected token mode; if token is missing, STOP and ask host to inject `OPENYIDA_ACCESS_TOKEN` or `OPENYIDA_REFRESH_TOKEN`; do not run OAuth |
|
|
32
|
+
| `auth_mode=token`, not logged in, and snapshot does not report env injection | run `openyida login`; verify with `openyida login --check-only --json` |
|
|
33
|
+
| `auth_mode=token`, access token expired | run `openyida auth refresh`; if still failed and snapshot does not report env injection, run `openyida login` |
|
|
32
34
|
|
|
33
35
|
## Token Mode Commands
|
|
34
36
|
|
|
35
|
-
Use OAuth login only when
|
|
37
|
+
Use OAuth login only when the auth snapshot does not report env injection.
|
|
36
38
|
|
|
37
39
|
```bash
|
|
38
40
|
openyida login
|
|
@@ -54,7 +56,7 @@ Overseas / international / global / Japan / Global YiDA => add `--intl` or equiv
|
|
|
54
56
|
|
|
55
57
|
## Host-Injected Token Mode Commands
|
|
56
58
|
|
|
57
|
-
Use only
|
|
59
|
+
Use only after the auth snapshot reports `auth_source=env` or `failure_reason=env_token_missing`.
|
|
58
60
|
|
|
59
61
|
```bash
|
|
60
62
|
openyida agent-capabilities --summary-json
|
|
@@ -75,11 +77,11 @@ Allowed result:
|
|
|
75
77
|
}
|
|
76
78
|
```
|
|
77
79
|
|
|
78
|
-
If the host did not inject token env,
|
|
80
|
+
If the host did not inject token env, the snapshot includes `failure_reason=env_token_missing`; stop the task and go back to the host. Do not launch OAuth from this mode.
|
|
79
81
|
|
|
80
82
|
## NEVER
|
|
81
83
|
|
|
82
|
-
- Never run `openyida login`
|
|
84
|
+
- Never run `openyida login` after the snapshot reports host-injected token mode.
|
|
83
85
|
- Never read `.cache/cookies*.json` as yida-agent auth.
|
|
84
86
|
- Never ask the user to export browser Cookie.
|
|
85
87
|
- Never print Cookie, CSRF, `access_token`, or `refresh_token`.
|
|
@@ -87,5 +89,5 @@ If the host did not inject token env, failure result includes `failure_reason=en
|
|
|
87
89
|
## Wukong / Codex
|
|
88
90
|
|
|
89
91
|
- Same auth mode rules as above.
|
|
90
|
-
- Do not special-case Wukong or
|
|
92
|
+
- Do not special-case Wukong, Codex, yida-agent, or any host identity into an auth branch; follow the OpenYida auth snapshot.
|
|
91
93
|
- Do not create app/page/form/publish until auth snapshot is usable.
|
|
@@ -30,6 +30,14 @@ description: 宜搭完整应用开发编排技能。对普通 OpenYida 应用做
|
|
|
30
30
|
- 已有流程表单或 `processCode` 时,流程诉求走 `yida-process-rule`;只有没有表单/流程且用户要新建审批表单时才进入 `yida-create-process`。
|
|
31
31
|
- 多个同优先级候选、当前轮显式资源冲突或目标不明时才问用户;不要因为 cache 和历史里同时存在资源就默认打断。
|
|
32
32
|
|
|
33
|
+
### 阶段 0 命令选择(不要猜命令)
|
|
34
|
+
|
|
35
|
+
- 已有显式 `appType`、应用 URL 或 agent bound `appType` 且能唯一解析时,直接复用该 app;不要调用 `app-list` 做存在性确认。
|
|
36
|
+
- 只有用户只给应用名称、存在多个候选、resource context 冲突,或需要诊断目标 app 访问失败时,才运行 `openyida app-list [--size N]`。
|
|
37
|
+
- 已知 `appType` 后,查询该应用下表单/页面用 `openyida list-forms <appType> [--keyword <text>]`;选择页面发布目标时只用 `formType=display`。
|
|
38
|
+
- 查询表单/页面 Schema、字段 ID 或批量字段摘要用 `openyida get-schema <appType> <formUuid|--all> ...`。
|
|
39
|
+
- 阶段 0 禁止编造 `list-apps` / `get-app`;也不要把 `--app-type` / `--form-uuid` 当成 `list-forms` 或 `get-schema` 的参数。按目的在 `app-list`、`list-forms`、`get-schema` 三者中选择。
|
|
40
|
+
|
|
33
41
|
该阶段只决定普通 OpenYida resource context;schema-managed 路径仍以 schema CLI 的 validate/plan/apply 结果为准。schema-managed create/update 必须停在当前 `planId`,等待用户显式批准后才可执行 `apply`;`nextAction`、错误恢复或本技能判断都不能授予 `mixed/write`。Phase 1 中 report、automation、page config、delete、pull 不从 Manifest fallback 到本技能的 legacy workflow。
|
|
34
42
|
|
|
35
43
|
## 阶段 1:resolve app name / rename placeholder app
|
|
@@ -59,6 +67,11 @@ description: 宜搭完整应用开发编排技能。对普通 OpenYida 应用做
|
|
|
59
67
|
|
|
60
68
|
遵循根入口的只读预检结果。若当前会话还没做预检,先按根入口执行一次只读校验;只有登录态可用后,才执行会创建、修改或发布宜搭资源的命令。不要在每个阶段重复跑 env/help/login 探测。
|
|
61
69
|
|
|
70
|
+
## 路径与文件读取口径
|
|
71
|
+
|
|
72
|
+
- 页面源码路径按当前 Bash cwd 选择:从仓库根执行时用 `project/pages/src/...`;如果 cwd 已是 `<workspace>/project`,用 `pages/src/...`,不要传 `project/pages/src/...` 导致 `project/project`。
|
|
73
|
+
- 读取 PRD、字段 JSON、页面源码或 schema 文件时优先用宿主 Read / Glob / Grep;OpenYida CLI 成功输出已经是操作证据,不要再 Bash `cat`/`ls` 复核。
|
|
74
|
+
|
|
62
75
|
## 标准执行流
|
|
63
76
|
|
|
64
77
|
```text
|
|
@@ -22,6 +22,7 @@ Code Canvas 是宜搭的代码画布自定义页面链路:以 `YidaCodeCanvas`
|
|
|
22
22
|
## 运行时事实
|
|
23
23
|
|
|
24
24
|
- Canvas 源码写成 `.canvas.jsx` / `.canvas.tsx`,`openyida publish` 会自动走 Canvas 链路。
|
|
25
|
+
- 页面源码路径按 Bash cwd 选择:从仓库根执行命令时用 `project/pages/src/...`;如果 cwd 已是 `<workspace>/project`,用 `pages/src/...`,不要写成 `project/pages/src/...`。
|
|
25
26
|
- `runtimeCode` 在宿主页真实 `window` 中执行,入口必须返回 `YidaComp` / `YidaComp.default` / 组件函数。
|
|
26
27
|
- Canvas 组件没有普通页面实例上下文;数据读写通过 fetch、开放 API、连接器代理或显式 props 数据桥完成。
|
|
27
28
|
- 第三方依赖走白名单;React、antd、ahooks、d3、recharts、Radix、framer-motion 等可按规则 import。
|
|
@@ -154,6 +155,8 @@ npx jest tests/canvas-compile.test.js tests/generate-page.test.js --runInBand
|
|
|
154
155
|
|
|
155
156
|
## 开发流程
|
|
156
157
|
|
|
158
|
+
下面命令以仓库根为视角;如果当前 cwd 已经是 `<workspace>/project`,把 `project/pages/src/...` 改成 `pages/src/...`。读取生成文件、Schema 或校验产物时优先用宿主 Read / Glob / Grep,不要在 CLI 成功后 Bash `cat`/`ls` 复核。
|
|
159
|
+
|
|
157
160
|
```bash
|
|
158
161
|
# 1. 只读检查环境和登录态;真实创建资源前必须通过
|
|
159
162
|
openyida env --json
|
|
@@ -21,7 +21,11 @@
|
|
|
21
21
|
| framer-motion | `FramerMotion` | `${cdn}/.../framerMotion.js` |
|
|
22
22
|
| yida-plugin-markdown | `YidaMarkdown` | moduleFederation 0.0.4 |
|
|
23
23
|
|
|
24
|
-
新增依赖必须同时满足:① 编译能把 import 抽进 `importedModules` 并映射到 windowAlias(见 `canvas-compile.js` 的 `MODULE_ALIAS_MAP`);② 上表或平台运行时能把依赖加载到 window;③ `runtimeCode` 引用的变量名与 windowAlias 一致;④ CSS 资源可加载,否则组件可能渲染但样式/弹层异常。白名单外的包(yida-utils、`@ali/deep`、原生字段组件等)不能 `import
|
|
24
|
+
新增依赖必须同时满足:① 编译能把 import 抽进 `importedModules` 并映射到 windowAlias(见 `canvas-compile.js` 的 `MODULE_ALIAS_MAP`);② 上表或平台运行时能把依赖加载到 window;③ `runtimeCode` 引用的变量名与 windowAlias 一致;④ CSS 资源可加载,否则组件可能渲染但样式/弹层异常。白名单外的包(yida-utils、`@ali/deep`、原生字段组件等)不能 `import`;带绑定的非白名单裸包 import 会在本地编译阶段硬失败。宜搭平台运行态全局对象必须显式使用 `window.Deep`、`window.DeepYida`、`window.YidaNativeComponents` 等 `window.*` 访问,不要从包中导入。
|
|
25
|
+
|
|
26
|
+
如果宜搭物料依赖表已经先于 OpenYida CLI 升级,且你已经确认运行时确实会注入某个新裸包,可以临时设置 `OPENYIDA_CANVAS_ALLOW_UNSUPPORTED_IMPORTS=1` 退回 legacy `window["pkg"]` 映射发布;这只是白名单漂移逃生舱,不应用来绕过 `useDataBinding` 这类不存在的 hook 或未验证依赖。
|
|
27
|
+
|
|
28
|
+
Canvas 没有官方 `useDataBinding` hook,不得从任何包 `import { useDataBinding }`。真实表单数据绑定使用页面内本地 `useYidaData(binding)`、`DataBridge` 与同源 `fetch` 实现。
|
|
25
29
|
|
|
26
30
|
编译位置:OpenYida CLI **本地用 Babel** 把源码转译为 `runtimeCode` + `importedModules`(`import`→`window.<别名>`、`export default`→`YidaComp`、依赖名正则抽取),不调用任何在线编译服务,因此不依赖登录态、不经过风控。别名映射逐条镜像自 `dependencies.ts` 的 `getModuleAliasMap()`;运行时消费契约见 `factory.tsx`(`new Function` 执行 `runtimeCode` 取 `YidaComp`)。
|
|
27
31
|
|
|
@@ -16,6 +16,7 @@ Code Canvas 运行时是标准 React 组件环境,组件没有普通宜搭自
|
|
|
16
16
|
- `YidaCodeCanvas` 物料只透传 `code / runtimeCode / importedModules / pageType`。
|
|
17
17
|
- 组件内没有 `this` 上下文,也没有 `dataSourceMap`。
|
|
18
18
|
- `this.utils.yida.*`、`didMount()`、`_customState` 等普通页面契约不可用。
|
|
19
|
+
- Canvas 没有官方 `useDataBinding` hook,不得从任何包 `import { useDataBinding }`;真实表单数据绑定用页面内本地 `useYidaData(binding)`、`DataBridge` 和同源 `fetch` 实现。
|
|
19
20
|
- Cookie 由浏览器同源请求自动携带,前端代码不能硬编码 Cookie、appSecret、accessKey 或外部密钥。
|
|
20
21
|
- 调宜搭同源端点时,请求必须带 `credentials: 'include'`。
|
|
21
22
|
- CSRF 优先从 `window.g_config._csrf_token` 或 `window.g_config.csrfToken` 读取;内部端点常同时需要 `_csrf_token` 参数和 `global_csrf_token` 请求头。
|
|
@@ -16,6 +16,7 @@ description: 宜搭普通自定义页面 JSX / Jsx 组件开发规范(React 16
|
|
|
16
16
|
- 完整应用 `fast_build` 如果已有 bound app/page,主页面源码直接落到该页面;只在缺少主入口 display page 且用户意图允许新增时创建页面容器。
|
|
17
17
|
- 用户只说“优化这个页面 URL / 修改现有页面 / 重新发布”时,本技能与 `yida-publish-page` 配合即可完成,不创建 app/page。
|
|
18
18
|
- 如果用户给的是普通表单 `formUuid`,页面源码只能把它作为数据源或入口链接使用;不能把数据表单 ID 当作发布目标。
|
|
19
|
+
- 页面源码路径按 Bash cwd 选择:从仓库根执行命令时用 `project/pages/src/...`;如果 cwd 已是 `<workspace>/project`,用 `pages/src/...`,不要写成 `project/pages/src/...`。
|
|
19
20
|
|
|
20
21
|
## 核心规则
|
|
21
22
|
|
|
@@ -85,6 +86,8 @@ description: 宜搭普通自定义页面 JSX / Jsx 组件开发规范(React 16
|
|
|
85
86
|
|
|
86
87
|
以开发「员工信息查询页」为例,完整流程如下:
|
|
87
88
|
|
|
89
|
+
下面命令以仓库根为视角;如果当前 cwd 已经是 `<workspace>/project`,把 `project/pages/src/...` 改成 `pages/src/...`。读取生成文件和 Schema 时优先用宿主 Read / Glob / Grep,不要在 CLI 成功后 Bash `cat`/`ls` 复核。
|
|
90
|
+
|
|
88
91
|
1. 获取表单 Schema,确认字段 ID:
|
|
89
92
|
|
|
90
93
|
```bash
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: yida-login
|
|
3
|
-
description:
|
|
3
|
+
description: 宜搭登录态管理。以 OpenYida auth snapshot 为准;默认 OAuth token,snapshot 返回 env 注入状态时使用宿主 token。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# yida-login
|
|
7
7
|
|
|
8
8
|
## Mode
|
|
9
9
|
|
|
10
|
-
-
|
|
11
|
-
-
|
|
10
|
+
- Do not infer auth mode from agent name, host product, workspace path, or a guessed environment variable.
|
|
11
|
+
- First read `openyida agent-capabilities --summary-json`; fallback to `openyida login --check-only --json` only when needed.
|
|
12
|
+
- If the snapshot reports `login.auth_source=env` or `failure_reason=env_token_missing`, treat it as host-injected token mode. The only credential sources are host-injected token env such as `OPENYIDA_ACCESS_TOKEN` and `OPENYIDA_REFRESH_TOKEN`.
|
|
13
|
+
- Otherwise, `auth_mode=token` uses the default OAuth token session flow.
|
|
12
14
|
- NEVER infer auth from `.cache/cookies*.json`.
|
|
13
15
|
|
|
14
16
|
## Preflight
|
|
@@ -31,12 +33,12 @@ openyida login --check-only --json
|
|
|
31
33
|
| Observed status | Action |
|
|
32
34
|
|---|---|
|
|
33
35
|
| `auth_mode=token`, `status=ok` or `can_auto_use=true` | Continue business command |
|
|
34
|
-
| `failure_reason=env_token_missing` |
|
|
35
|
-
| `auth_mode=token`, not logged in,
|
|
36
|
+
| `auth_source=env` / `failure_reason=env_token_missing` | Treat as host-injected token mode; if token is missing, STOP and ask host to inject `OPENYIDA_ACCESS_TOKEN` or `OPENYIDA_REFRESH_TOKEN`; do not OAuth |
|
|
37
|
+
| `auth_mode=token`, not logged in, and snapshot does not report env injection | Run `openyida login`, then verify with `openyida login --check-only --json` |
|
|
36
38
|
|
|
37
39
|
## Token Mode Commands
|
|
38
40
|
|
|
39
|
-
Use OAuth login only when
|
|
41
|
+
Use OAuth login only when the auth snapshot does not report env injection.
|
|
40
42
|
|
|
41
43
|
```bash
|
|
42
44
|
openyida login
|
|
@@ -58,7 +60,7 @@ Overseas / international / global / Japan / Global YiDA => add `--intl` or equiv
|
|
|
58
60
|
|
|
59
61
|
## Host-Injected Token Mode Commands
|
|
60
62
|
|
|
61
|
-
Use only
|
|
63
|
+
Use only after the auth snapshot reports `auth_source=env` or `failure_reason=env_token_missing`.
|
|
62
64
|
|
|
63
65
|
```bash
|
|
64
66
|
openyida agent-capabilities --summary-json
|
|
@@ -63,6 +63,8 @@ description: 自定义页面 JSX 编译发布技能;schema-managed 页面由
|
|
|
63
63
|
openyida publish <源文件路径> <appType> <formUuid> [--compat] [--canvas] [--health-check] [--force]
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
+
路径口径:从仓库根执行时,源文件用 `project/pages/src/...`;如果 Bash cwd 已经是 `<workspace>/project`,源文件用 `pages/src/...`,不要传 `project/pages/src/...` 导致查找 `project/project/pages/src/...`。发布失败提示源文件不存在时,先按该规则切换路径,不要自动发布另一份文件。
|
|
67
|
+
|
|
66
68
|
> 本技能覆盖两条并列发布链路:**普通自定义页面 JSX/Jsx 组件链路**(`.oyd.jsx` / `.jsx`,Babel + UglifyJS + `Jsx` 组件 Schema)和 **Code Canvas 链路**(`.canvas.jsx`,本地 Babel 编译 + `YidaCodeCanvas` Schema)。从零写普通自定义页面见 `yida-custom-page`;从零写或迁移 Canvas 页见 `yida-canvas-custom-page` / `yida-canvas-upgrade`。
|
|
67
69
|
> 注意:`openyida check-page` / `openyida compile` 当前是普通自定义页面检查器,不适合作为 `.canvas.jsx` 的预检;Canvas 预检以 `publish` 的「编译 Code Canvas 源码」阶段为准。
|
|
68
70
|
|
|
@@ -139,7 +141,7 @@ body { background-color: #f2f3f5; }
|
|
|
139
141
|
|
|
140
142
|
## 注意事项
|
|
141
143
|
|
|
142
|
-
- 发布目标地址由当前环境配置和 auth
|
|
144
|
+
- 发布目标地址由当前环境配置和 auth snapshot(本地 OAuth token session 或 snapshot 明确返回的宿主注入 env token)中的 `base_url` 决定
|
|
143
145
|
- 碰到组织 corpId 不匹配时,询问用户是重新登录到目标组织,还是确认在当前组织继续发布到已解析页面;不要通过新建应用规避不匹配
|
|
144
146
|
- **编写源码前必须先加载对应页面子技能**:普通自定义页面 JSX/Jsx 组件链路调用 `use_skill("yida-custom-page", "编写宜搭普通自定义页面 JSX")`;Code Canvas 链路调用 `use_skill("yida-canvas-custom-page", "编写 Code Canvas 自定义页面")`。旧式 `renderJsx` 写法不要使用 Hooks,现代普通自定义页面 authoring 写法必须走 `.oyd.jsx` 兼容编译;Canvas 源码写成 `.canvas.jsx`
|
|
145
147
|
|
|
@@ -713,7 +713,7 @@
|
|
|
713
713
|
"name": "yida-login",
|
|
714
714
|
"path": "skills/yida-login/SKILL.md",
|
|
715
715
|
"display_name": "宜搭登录态管理",
|
|
716
|
-
"description": "
|
|
716
|
+
"description": "宜搭登录态管理。以 OpenYida auth snapshot 为准;默认 OAuth token,snapshot 返回 env 注入状态时使用宿主 token。",
|
|
717
717
|
"category": "yida-skills/context",
|
|
718
718
|
"tags": [
|
|
719
719
|
"登录",
|