openyida 2026.7.21 → 2026.7.23-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/yida.js +29 -2
- package/lib/app/canvas-compile.js +44 -12
- package/lib/app/create-form/definition-reader.js +22 -2
- package/lib/app/create-form.js +378 -43
- package/lib/app/generate-page.js +42 -1
- package/lib/app/publish.js +27 -0
- package/lib/app/services/form-compiler.js +66 -4
- package/lib/core/agent-capabilities.js +258 -0
- package/lib/core/command-manifest.js +236 -0
- package/lib/core/locales/en.js +11 -0
- package/lib/core/locales/zh.js +11 -0
- package/package.json +1 -1
- package/scripts/postinstall.js +4 -0
- package/scripts/validate-command-manifest.js +72 -0
- package/yida-skills/SKILL.md +12 -5
- package/yida-skills/references/setup-and-env.md +12 -10
- package/yida-skills/skills/yida-app/SKILL.md +24 -3
- package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +9 -3
- package/yida-skills/skills/yida-canvas-custom-page/references/dependencies-and-cdn.md +5 -1
- package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +2 -0
- package/yida-skills/skills/yida-canvas-data-binding/SKILL.md +1 -0
- package/yida-skills/skills/yida-create-form-page/SKILL.md +10 -0
- package/yida-skills/skills/yida-custom-page/SKILL.md +3 -0
- package/yida-skills/skills/yida-get-schema/SKILL.md +8 -2
- 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
|
@@ -509,6 +509,9 @@ const SUPPORTED_FIELD_TYPES = Object.freeze([
|
|
|
509
509
|
]);
|
|
510
510
|
|
|
511
511
|
function normalizeFieldType(fieldType) {
|
|
512
|
+
if (fieldType === undefined || fieldType === null || fieldType === '') {
|
|
513
|
+
return '';
|
|
514
|
+
}
|
|
512
515
|
return FIELD_TYPE_ALIAS[fieldType] || fieldType;
|
|
513
516
|
}
|
|
514
517
|
|
|
@@ -520,6 +523,57 @@ function isOptionFieldType(fieldType) {
|
|
|
520
523
|
return OPTION_FIELD_TYPES.indexOf(normalizeFieldType(fieldType)) !== -1;
|
|
521
524
|
}
|
|
522
525
|
|
|
526
|
+
function readFieldDefinitionType(field) {
|
|
527
|
+
if (!field || typeof field !== 'object' || Array.isArray(field)) {
|
|
528
|
+
return '';
|
|
529
|
+
}
|
|
530
|
+
if (field.type !== undefined && field.type !== null && field.type !== '') {
|
|
531
|
+
return String(field.type).trim();
|
|
532
|
+
}
|
|
533
|
+
if (field.componentType !== undefined && field.componentType !== null && field.componentType !== '') {
|
|
534
|
+
return String(field.componentType).trim();
|
|
535
|
+
}
|
|
536
|
+
if (field.componentName !== undefined && field.componentName !== null && field.componentName !== '') {
|
|
537
|
+
return String(field.componentName).trim();
|
|
538
|
+
}
|
|
539
|
+
return '';
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function assertSupportedFieldDefinition(field, details) {
|
|
543
|
+
if (!field || typeof field !== 'object' || Array.isArray(field)) {
|
|
544
|
+
throw createFormCompilerError(
|
|
545
|
+
'字段定义必须是对象',
|
|
546
|
+
'FORM_COMPILER_INVALID_DEFINITION',
|
|
547
|
+
details
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
const rawType = readFieldDefinitionType(field);
|
|
551
|
+
const componentName = normalizeFieldType(rawType);
|
|
552
|
+
if (!componentName) {
|
|
553
|
+
throw createFormCompilerError(
|
|
554
|
+
'字段定义缺少 type/componentType/componentName',
|
|
555
|
+
'FORM_COMPILER_FIELD_TYPE_MISSING',
|
|
556
|
+
Object.assign({
|
|
557
|
+
label: field.label,
|
|
558
|
+
title: field.title,
|
|
559
|
+
}, details)
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
if (!isSupportedFieldType(componentName)) {
|
|
563
|
+
throw createFormCompilerError(
|
|
564
|
+
'不支持的字段类型: ' + componentName,
|
|
565
|
+
'FORM_COMPILER_UNSUPPORTED_FIELD_TYPE',
|
|
566
|
+
Object.assign({
|
|
567
|
+
label: field.label,
|
|
568
|
+
title: field.title,
|
|
569
|
+
type: rawType,
|
|
570
|
+
componentName,
|
|
571
|
+
}, details)
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
return componentName;
|
|
575
|
+
}
|
|
576
|
+
|
|
523
577
|
const COMPONENT_ALIAS_META = Symbol('openyida.componentAlias');
|
|
524
578
|
|
|
525
579
|
function normalizeComponentAlias(field) {
|
|
@@ -544,7 +598,9 @@ function normalizeComponentAlias(field) {
|
|
|
544
598
|
|
|
545
599
|
function buildFieldComponent(field, options) {
|
|
546
600
|
const compilerOptions = createCompilerOptions(options);
|
|
547
|
-
const componentName =
|
|
601
|
+
const componentName = assertSupportedFieldDefinition(field, {
|
|
602
|
+
semanticPath: compilerOptions.semanticPath,
|
|
603
|
+
});
|
|
548
604
|
const semanticKey = normalizeSemanticKey(field);
|
|
549
605
|
if (semanticKey && compilerOptions.requireParentSemanticPath && !compilerOptions.semanticPath) {
|
|
550
606
|
throw createFormCompilerError(
|
|
@@ -1139,10 +1195,16 @@ function buildFieldComponent(field, options) {
|
|
|
1139
1195
|
function collectComponentNames(fields) {
|
|
1140
1196
|
const names = new Set(['Page', 'RootHeader', 'RootContent', 'RootFooter', 'FooterYida', 'FormContainer']);
|
|
1141
1197
|
fields.forEach(function (field) {
|
|
1142
|
-
|
|
1143
|
-
if (
|
|
1198
|
+
const componentName = normalizeFieldType(readFieldDefinitionType(field));
|
|
1199
|
+
if (componentName) {
|
|
1200
|
+
names.add(componentName);
|
|
1201
|
+
}
|
|
1202
|
+
if (componentName === 'TableField' && field.children) {
|
|
1144
1203
|
field.children.forEach(function (child) {
|
|
1145
|
-
|
|
1204
|
+
const childComponentName = normalizeFieldType(readFieldDefinitionType(child));
|
|
1205
|
+
if (childComponentName) {
|
|
1206
|
+
names.add(childComponentName);
|
|
1207
|
+
}
|
|
1146
1208
|
});
|
|
1147
1209
|
}
|
|
1148
1210
|
});
|
|
@@ -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,
|