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.
@@ -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 = normalizeFieldType(field.type);
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
- names.add(field.type);
1143
- if (field.type === 'TableField' && field.children) {
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
- names.add(child.type);
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,