wp-typia 0.22.1 → 0.22.3

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.
@@ -4,6 +4,7 @@ import {
4
4
  REQUIRED_WORKSPACE_ABILITY_COMPATIBILITY,
5
5
  assertExternalLayerCompositionOptions,
6
6
  copyInterpolatedDirectory,
7
+ createScaffoldCompatibilityConfig,
7
8
  getDefaultAnswers,
8
9
  listInterpolatedDirectoryOutputs,
9
10
  normalizeOptionalCliString,
@@ -20,7 +21,7 @@ import {
20
21
  scaffoldProject,
21
22
  syncPersistenceRestArtifacts,
22
23
  updatePluginHeaderCompatibility
23
- } from "./cli-886tjd8m.js";
24
+ } from "./cli-tbd9x8b6.js";
24
25
  import {
25
26
  DEFAULT_WORDPRESS_ABILITIES_VERSION,
26
27
  DEFAULT_WORDPRESS_CORE_ABILITIES_VERSION,
@@ -33,7 +34,7 @@ import {
33
34
  } from "./cli-1sm60g1z.js";
34
35
  import {
35
36
  snapshotProjectVersion
36
- } from "./cli-yzmkz95r.js";
37
+ } from "./cli-prc42zqd.js";
37
38
  import {
38
39
  ensureMigrationDirectories,
39
40
  parseMigrationConfig,
@@ -57,6 +58,8 @@ import {
57
58
  assertAdminViewDoesNotExist,
58
59
  assertAiFeatureDoesNotExist,
59
60
  assertBindingSourceDoesNotExist,
61
+ assertBlockStyleDoesNotExist,
62
+ assertBlockTransformDoesNotExist,
60
63
  assertEditorPluginDoesNotExist,
61
64
  assertPatternDoesNotExist,
62
65
  assertRestResourceDoesNotExist,
@@ -94,7 +97,7 @@ import {
94
97
  toPascalCase,
95
98
  toSnakeCase,
96
99
  toTitleCase
97
- } from "./cli-5md428hf.js";
100
+ } from "./cli-x0h03qqe.js";
98
101
  import {
99
102
  createManagedTempRoot
100
103
  } from "./cli-t73q5aqz.js";
@@ -2376,8 +2379,10 @@ registerBlockBindingsSource( {
2376
2379
  `;
2377
2380
  }
2378
2381
  function resolveBindingTarget(options, namespace) {
2379
- const hasBlock = options.blockName !== undefined && options.blockName.trim().length > 0;
2380
- const hasAttribute = options.attributeName !== undefined && options.attributeName.trim().length > 0;
2382
+ const blockName = normalizeOptionalCliString(options.blockName);
2383
+ const attributeName = normalizeOptionalCliString(options.attributeName);
2384
+ const hasBlock = blockName !== undefined;
2385
+ const hasAttribute = attributeName !== undefined;
2381
2386
  if (!hasBlock && !hasAttribute) {
2382
2387
  return;
2383
2388
  }
@@ -2385,8 +2390,8 @@ function resolveBindingTarget(options, namespace) {
2385
2390
  throw new Error("`wp-typia add binding-source` requires --block and --attribute to be provided together.");
2386
2391
  }
2387
2392
  return {
2388
- attributeName: assertValidBindingAttributeName(options.attributeName ?? ""),
2389
- blockSlug: resolveBindingTargetBlockSlug(options.blockName ?? "", namespace)
2393
+ attributeName: assertValidBindingAttributeName(attributeName ?? ""),
2394
+ blockSlug: resolveBindingTargetBlockSlug(blockName ?? "", namespace)
2390
2395
  };
2391
2396
  }
2392
2397
  function formatBindingAttributeTypeMember(attributeName) {
@@ -4366,12 +4371,14 @@ async function runAddRestResourceCommand({
4366
4371
  throw error;
4367
4372
  }
4368
4373
  }
4369
- // ../wp-typia-project-tools/src/runtime/cli-add-workspace-ability.ts
4374
+ // ../wp-typia-project-tools/src/runtime/cli-add-workspace-ability-scaffold.ts
4370
4375
  var import_semver = __toESM(require_semver(), 1);
4371
4376
  import fs5 from "fs";
4372
4377
  import { promises as fsp6 } from "fs";
4373
4378
  import path10 from "path";
4374
4379
  import { syncTypeSchemas as syncTypeSchemas2 } from "@wp-typia/block-runtime/metadata-core";
4380
+
4381
+ // ../wp-typia-project-tools/src/runtime/cli-add-workspace-ability-types.ts
4375
4382
  var ABILITY_SERVER_GLOB = "/inc/abilities/*.php";
4376
4383
  var ABILITY_EDITOR_SCRIPT = "build/abilities/index.js";
4377
4384
  var ABILITY_EDITOR_ASSET = "build/abilities/index.asset.php";
@@ -4379,17 +4386,8 @@ var ABILITY_REGISTRY_END_MARKER = "// wp-typia add ability entries end";
4379
4386
  var ABILITY_REGISTRY_START_MARKER = "// wp-typia add ability entries start";
4380
4387
  var WP_ABILITIES_SCRIPT_MODULE_ID = "@wordpress/abilities";
4381
4388
  var WP_CORE_ABILITIES_SCRIPT_MODULE_ID = "@wordpress/core-abilities";
4382
- function resolveManagedDependencyVersion(existingVersion, requiredVersion) {
4383
- if (!existingVersion) {
4384
- return requiredVersion;
4385
- }
4386
- const existingMinimum = import_semver.default.minVersion(existingVersion);
4387
- const requiredMinimum = import_semver.default.minVersion(requiredVersion);
4388
- if (!existingMinimum || !requiredMinimum) {
4389
- return requiredVersion;
4390
- }
4391
- return import_semver.default.gte(existingMinimum, requiredMinimum) ? existingVersion : requiredVersion;
4392
- }
4389
+
4390
+ // ../wp-typia-project-tools/src/runtime/cli-add-workspace-ability-templates.ts
4393
4391
  function toAbilityCategorySlug(workspaceNamespace) {
4394
4392
  const normalizedNamespace = workspaceNamespace.replace(/[^a-z0-9-]+/gu, "-").replace(/-{2,}/gu, "-").replace(/^-|-$/gu, "");
4395
4393
  return `${normalizedNamespace || "workspace"}-workflows`;
@@ -4854,6 +4852,19 @@ function buildAbilityRegistrySource(abilitySlugs) {
4854
4852
  `).concat(`
4855
4853
  `);
4856
4854
  }
4855
+
4856
+ // ../wp-typia-project-tools/src/runtime/cli-add-workspace-ability-scaffold.ts
4857
+ function resolveManagedDependencyVersion(existingVersion, requiredVersion) {
4858
+ if (!existingVersion) {
4859
+ return requiredVersion;
4860
+ }
4861
+ const existingMinimum = import_semver.default.minVersion(existingVersion);
4862
+ const requiredMinimum = import_semver.default.minVersion(requiredVersion);
4863
+ if (!existingMinimum || !requiredMinimum) {
4864
+ return requiredVersion;
4865
+ }
4866
+ return import_semver.default.gte(existingMinimum, requiredMinimum) ? existingVersion : requiredVersion;
4867
+ }
4857
4868
  function resolveAbilityRegistryPath(projectDir) {
4858
4869
  const abilitiesDir = path10.join(projectDir, "src", "abilities");
4859
4870
  return [path10.join(abilitiesDir, "index.ts"), path10.join(abilitiesDir, "index.js")].find((candidatePath) => fs5.existsSync(candidatePath)) ?? path10.join(abilitiesDir, "index.ts");
@@ -5065,21 +5076,29 @@ async function ensureAbilityBuildScriptAnchors(workspace) {
5065
5076
  }
5066
5077
  const sharedEntriesPattern = /(for\s*\(\s*const\s+relativePath\s+of\s+\[)([\s\S]*?)(\]\s*\)\s*\{)/u;
5067
5078
  const match = nextSource.match(sharedEntriesPattern);
5068
- if (!match || !match[2].includes("src/bindings/index.ts") || !match[2].includes("src/editor-plugins/index.ts")) {
5079
+ if (!match || !/['"]src\/bindings\/index\.(?:ts|js)['"]/u.test(match[2]) || !/['"]src\/editor-plugins\/index\.(?:tsx|ts|js)['"]/u.test(match[2])) {
5069
5080
  throw new Error([
5070
5081
  `ensureAbilityBuildScriptAnchors could not patch ${path10.basename(buildScriptPath)}.`,
5071
5082
  "Missing the expected shared editor entries array in scripts/build-workspace.mjs.",
5072
5083
  "Restore the generated template or wire abilities/index manually before retrying."
5073
5084
  ].join(" "));
5074
5085
  }
5075
- nextSource = nextSource.replace(sharedEntriesPattern, `$1
5076
- 'src/bindings/index.ts',
5077
- 'src/bindings/index.js',
5078
- 'src/editor-plugins/index.ts',
5079
- 'src/editor-plugins/index.js',
5080
- 'src/abilities/index.ts',
5081
- 'src/abilities/index.js',
5082
- $3`);
5086
+ nextSource = nextSource.replace(sharedEntriesPattern, (fullMatch, sharedEntries) => {
5087
+ const missingAbilityEntries = [
5088
+ "'src/abilities/index.ts'",
5089
+ "'src/abilities/index.js'"
5090
+ ].filter((entry) => !sharedEntries.includes(entry));
5091
+ if (missingAbilityEntries.length === 0) {
5092
+ return fullMatch;
5093
+ }
5094
+ const itemIndent = sharedEntries.match(/\n([ \t]*)['"]/u)?.[1] ?? "\t\t";
5095
+ const trimmedEntries = sharedEntries.replace(/\s*$/u, "");
5096
+ const trailingWhitespace = sharedEntries.slice(trimmedEntries.length);
5097
+ const separator = trimmedEntries.trimEnd().endsWith(",") ? "" : ",";
5098
+ const nextEntries = `${trimmedEntries}${separator}` + missingAbilityEntries.map((entry) => `
5099
+ ${itemIndent}${entry},`).join("") + trailingWhitespace;
5100
+ return fullMatch.replace(sharedEntries, nextEntries);
5101
+ });
5083
5102
  return nextSource;
5084
5103
  });
5085
5104
  }
@@ -5108,8 +5127,7 @@ async function ensureAbilityWebpackAnchors(workspace) {
5108
5127
  entries.push( [ entryName, entryPath ] );
5109
5128
  break;
5110
5129
  }
5111
- }
5112
- $2`);
5130
+ }$2`);
5113
5131
  }
5114
5132
  const sharedEntriesPattern = /for\s*\(\s*const\s+\[\s*entryName\s*,\s*candidates\s*\]\s+of\s+\[([\s\S]*?)\]\s*\)\s*\{/u;
5115
5133
  const match = source.match(sharedEntriesPattern);
@@ -5120,31 +5138,33 @@ $2`);
5120
5138
  "Restore the generated template or wire abilities/index manually before retrying."
5121
5139
  ].join(" "));
5122
5140
  }
5123
- return source.replace(sharedEntriesPattern, `for ( const [ entryName, candidates ] of [
5124
- [
5125
- 'bindings/index',
5126
- [ 'src/bindings/index.ts', 'src/bindings/index.js' ],
5127
- ],
5128
- [
5129
- 'editor-plugins/index',
5130
- [ 'src/editor-plugins/index.ts', 'src/editor-plugins/index.js' ],
5131
- ],
5132
- [
5133
- 'abilities/index',
5134
- [ 'src/abilities/index.ts', 'src/abilities/index.js' ],
5135
- ],
5136
- ] ) {`);
5141
+ return source.replace(sharedEntriesPattern, (fullMatch, sharedEntries) => {
5142
+ if (/['"]abilities\/index['"]/u.test(sharedEntries)) {
5143
+ return fullMatch;
5144
+ }
5145
+ const tupleIndent = sharedEntries.match(/\n([ \t]*)\[/u)?.[1] ?? "\t\t";
5146
+ const nestedIndent = `${tupleIndent} `;
5147
+ const trimmedEntries = sharedEntries.replace(/\s*$/u, "");
5148
+ const trailingWhitespace = sharedEntries.slice(trimmedEntries.length);
5149
+ const separator = trimmedEntries.trimEnd().endsWith(",") ? "" : ",";
5150
+ const abilityTuple = [
5151
+ `${tupleIndent}[`,
5152
+ `${nestedIndent}'abilities/index',`,
5153
+ `${nestedIndent}[ 'src/abilities/index.ts', 'src/abilities/index.js' ],`,
5154
+ `${tupleIndent}],`
5155
+ ].join(`
5156
+ `);
5157
+ const nextEntries = `${trimmedEntries}${separator}
5158
+ ${abilityTuple}` + trailingWhitespace;
5159
+ return fullMatch.replace(sharedEntries, nextEntries);
5160
+ });
5137
5161
  });
5138
5162
  }
5139
- async function runAddAbilityCommand({
5140
- abilityName,
5141
- cwd = process.cwd()
5163
+ async function scaffoldAbilityWorkspace({
5164
+ abilitySlug,
5165
+ compatibilityPolicy,
5166
+ workspace
5142
5167
  }) {
5143
- const workspace = resolveWorkspaceProject(cwd);
5144
- const abilitySlug = assertValidGeneratedSlug("Ability name", normalizeBlockSlug(abilityName), "wp-typia add ability <name>");
5145
- const inventory = readWorkspaceInventory(workspace.projectDir);
5146
- assertAbilityDoesNotExist(workspace.projectDir, abilitySlug, inventory);
5147
- const compatibilityPolicy = resolveScaffoldCompatibilityPolicy(REQUIRED_WORKSPACE_ABILITY_COMPATIBILITY);
5148
5168
  const blockConfigPath = path10.join(workspace.projectDir, "scripts", "block-config.ts");
5149
5169
  const bootstrapPath = getWorkspaceBootstrapPath(workspace);
5150
5170
  const buildScriptPath = path10.join(workspace.projectDir, "scripts", "build-workspace.mjs");
@@ -5203,18 +5223,37 @@ async function runAddAbilityCommand({
5203
5223
  });
5204
5224
  await writeAbilityRegistry(workspace.projectDir, abilitySlug);
5205
5225
  await appendWorkspaceInventoryEntries(workspace.projectDir, {
5206
- abilityEntries: [buildAbilityConfigEntry(abilitySlug, compatibilityPolicy)]
5226
+ abilityEntries: [
5227
+ buildAbilityConfigEntry(abilitySlug, compatibilityPolicy)
5228
+ ]
5207
5229
  });
5208
- return {
5209
- abilitySlug,
5210
- projectDir: workspace.projectDir
5211
- };
5212
5230
  } catch (error) {
5213
5231
  await rollbackWorkspaceMutation(mutationSnapshot);
5214
5232
  throw error;
5215
5233
  }
5216
5234
  }
5217
- // ../wp-typia-project-tools/src/runtime/cli-add-workspace-ai.ts
5235
+
5236
+ // ../wp-typia-project-tools/src/runtime/cli-add-workspace-ability.ts
5237
+ async function runAddAbilityCommand({
5238
+ abilityName,
5239
+ cwd = process.cwd()
5240
+ }) {
5241
+ const workspace = resolveWorkspaceProject(cwd);
5242
+ const abilitySlug = assertValidGeneratedSlug("Ability name", normalizeBlockSlug(abilityName), "wp-typia add ability <name>");
5243
+ const inventory = readWorkspaceInventory(workspace.projectDir);
5244
+ assertAbilityDoesNotExist(workspace.projectDir, abilitySlug, inventory);
5245
+ const compatibilityPolicy = resolveScaffoldCompatibilityPolicy(REQUIRED_WORKSPACE_ABILITY_COMPATIBILITY);
5246
+ await scaffoldAbilityWorkspace({
5247
+ abilitySlug,
5248
+ compatibilityPolicy,
5249
+ workspace
5250
+ });
5251
+ return {
5252
+ abilitySlug,
5253
+ projectDir: workspace.projectDir
5254
+ };
5255
+ }
5256
+ // ../wp-typia-project-tools/src/runtime/cli-add-workspace-ai-scaffold.ts
5218
5257
  import { promises as fsp8 } from "fs";
5219
5258
  import path13 from "path";
5220
5259
 
@@ -5435,6 +5474,45 @@ export interface ${pascalCase}AiFeatureResponse {
5435
5474
  result: ${pascalCase}AiFeatureResult;
5436
5475
  telemetry: ${pascalCase}AiFeatureTelemetry;
5437
5476
  }
5477
+
5478
+ export type ${pascalCase}AiFeatureSupportProbeMode = 'request-time';
5479
+
5480
+ export type ${pascalCase}AiFeatureUnavailableErrorCode =
5481
+ 'ai_client_unavailable';
5482
+
5483
+ export type ${pascalCase}AiFeatureUnavailableReasonCode =
5484
+ | 'missing-wordpress-ai-client'
5485
+ | 'request-time-support-probe';
5486
+
5487
+ export interface ${pascalCase}AiFeatureSupportReason {
5488
+ code: ${pascalCase}AiFeatureUnavailableReasonCode;
5489
+ label: string & tags.MinLength< 1 > & tags.MaxLength< 160 >;
5490
+ message: string & tags.MinLength< 1 > & tags.MaxLength< 4000 >;
5491
+ }
5492
+
5493
+ export interface ${pascalCase}AiFeatureSupportMetadata {
5494
+ featureLabel: string & tags.MinLength< 1 > & tags.MaxLength< 160 >;
5495
+ featureSlug: string & tags.MinLength< 1 > & tags.MaxLength< 160 >;
5496
+ compatibility: {
5497
+ hardMinimums: {
5498
+ php?: string;
5499
+ wordpress?: string;
5500
+ };
5501
+ mode: 'baseline' | 'optional' | 'required';
5502
+ optionalFeatureIds: string[];
5503
+ optionalFeatures: string[];
5504
+ requiredFeatureIds: string[];
5505
+ requiredFeatures: string[];
5506
+ runtimeGates: string[];
5507
+ };
5508
+ supportProbe: {
5509
+ endpointMethod: 'POST';
5510
+ endpointPath: string & tags.MinLength< 1 > & tags.MaxLength< 200 >;
5511
+ mode: ${pascalCase}AiFeatureSupportProbeMode;
5512
+ unavailableErrorCode: ${pascalCase}AiFeatureUnavailableErrorCode;
5513
+ };
5514
+ unavailableReasons: ${pascalCase}AiFeatureSupportReason[];
5515
+ }
5438
5516
  `;
5439
5517
  }
5440
5518
  function buildAiFeatureValidatorsSource(aiFeatureSlug) {
@@ -5470,6 +5548,8 @@ export const apiValidators = {
5470
5548
  }
5471
5549
  function buildAiFeatureApiSource(aiFeatureSlug) {
5472
5550
  const pascalCase = toPascalCase(aiFeatureSlug);
5551
+ const compatibility = createScaffoldCompatibilityConfig(resolveScaffoldCompatibilityPolicy(OPTIONAL_WORDPRESS_AI_CLIENT_COMPATIBILITY));
5552
+ const title = toTitleCase(aiFeatureSlug);
5473
5553
  return `import {
5474
5554
  callEndpoint,
5475
5555
  resolveRestRouteUrl,
@@ -5477,6 +5557,7 @@ function buildAiFeatureApiSource(aiFeatureSlug) {
5477
5557
 
5478
5558
  import type {
5479
5559
  ${pascalCase}AiFeatureRequest,
5560
+ ${pascalCase}AiFeatureSupportMetadata,
5480
5561
  } from './api-types';
5481
5562
  import {
5482
5563
  run${pascalCase}AiFeatureEndpoint,
@@ -5503,6 +5584,14 @@ function resolveRestNonce( fallback?: string ): string | undefined {
5503
5584
  : undefined;
5504
5585
  }
5505
5586
 
5587
+ function isPlainObject( value: unknown ): value is Record< string, unknown > {
5588
+ return (
5589
+ !! value &&
5590
+ typeof value === 'object' &&
5591
+ ! Array.isArray( value )
5592
+ );
5593
+ }
5594
+
5506
5595
  export const aiFeatureRunEndpoint = {
5507
5596
  ...run${pascalCase}AiFeatureEndpoint,
5508
5597
  buildRequestOptions: () => {
@@ -5518,6 +5607,63 @@ export const aiFeatureRunEndpoint = {
5518
5607
  },
5519
5608
  };
5520
5609
 
5610
+ export const aiFeatureSupportMetadata = {
5611
+ compatibility: ${JSON.stringify(compatibility, null, "\t")},
5612
+ featureLabel: ${quoteTsString(title)},
5613
+ featureSlug: ${quoteTsString(aiFeatureSlug)},
5614
+ supportProbe: {
5615
+ endpointMethod: 'POST',
5616
+ endpointPath: aiFeatureRunEndpoint.path,
5617
+ mode: 'request-time',
5618
+ unavailableErrorCode: 'ai_client_unavailable',
5619
+ },
5620
+ unavailableReasons: [
5621
+ {
5622
+ code: 'missing-wordpress-ai-client',
5623
+ label: 'WordPress AI Client unavailable',
5624
+ message:
5625
+ 'This AI feature stays disabled until the WordPress AI Client is available on the site.',
5626
+ },
5627
+ {
5628
+ code: 'request-time-support-probe',
5629
+ label: 'Support is checked at request time',
5630
+ message:
5631
+ 'Support is verified when the feature runs, so editor and admin UIs should degrade gracefully when the site rejects the request.',
5632
+ },
5633
+ ],
5634
+ } satisfies ${pascalCase}AiFeatureSupportMetadata;
5635
+
5636
+ export function getAiFeatureSupportHintLines() {
5637
+ return aiFeatureSupportMetadata.unavailableReasons.map(
5638
+ ( reason ) => reason.message
5639
+ );
5640
+ }
5641
+
5642
+ export function isAiFeatureSupportUnavailableError( error: unknown ) {
5643
+ if ( ! isPlainObject( error ) ) {
5644
+ return false;
5645
+ }
5646
+
5647
+ const data = isPlainObject( error.data ) ? error.data : undefined;
5648
+ return (
5649
+ error.code === aiFeatureSupportMetadata.supportProbe.unavailableErrorCode ||
5650
+ data?.status === 501
5651
+ );
5652
+ }
5653
+
5654
+ export function resolveAiFeatureUnavailableMessage( error: unknown ) {
5655
+ if (
5656
+ isPlainObject( error ) &&
5657
+ typeof error.message === 'string' &&
5658
+ error.message.length > 0
5659
+ ) {
5660
+ return error.message;
5661
+ }
5662
+
5663
+ return aiFeatureSupportMetadata.unavailableReasons[ 0 ]?.message ??
5664
+ 'This AI feature is currently unavailable.';
5665
+ }
5666
+
5521
5667
  export function runAiFeature( request: ${pascalCase}AiFeatureRequest ) {
5522
5668
  return callEndpoint( aiFeatureRunEndpoint, request );
5523
5669
  }
@@ -5536,6 +5682,10 @@ import type {
5536
5682
  } from './api-types';
5537
5683
  import {
5538
5684
  aiFeatureRunEndpoint,
5685
+ aiFeatureSupportMetadata,
5686
+ getAiFeatureSupportHintLines,
5687
+ isAiFeatureSupportUnavailableError,
5688
+ resolveAiFeatureUnavailableMessage,
5539
5689
  } from './api';
5540
5690
 
5541
5691
  export type UseRun${pascalCase}AiFeatureMutationOptions =
@@ -5550,6 +5700,13 @@ export function useRun${pascalCase}AiFeatureMutation(
5550
5700
  ) {
5551
5701
  return useEndpointMutation( aiFeatureRunEndpoint, options );
5552
5702
  }
5703
+
5704
+ export {
5705
+ aiFeatureSupportMetadata,
5706
+ getAiFeatureSupportHintLines,
5707
+ isAiFeatureSupportUnavailableError,
5708
+ resolveAiFeatureUnavailableMessage,
5709
+ };
5553
5710
  `;
5554
5711
  }
5555
5712
  function buildAiFeatureSyncScriptSource() {
@@ -5953,7 +6110,7 @@ async function ensureAiFeatureSyncRestAnchors(workspace) {
5953
6110
  });
5954
6111
  }
5955
6112
 
5956
- // ../wp-typia-project-tools/src/runtime/cli-add-workspace-ai.ts
6113
+ // ../wp-typia-project-tools/src/runtime/cli-add-workspace-ai-templates.ts
5957
6114
  function buildAiFeaturePhpSource(aiFeatureSlug, namespace, phpPrefix, textDomain) {
5958
6115
  const aiFeatureTitle = toTitleCase(aiFeatureSlug);
5959
6116
  const aiFeaturePhpId = aiFeatureSlug.replace(/-/g, "_");
@@ -5962,18 +6119,40 @@ function buildAiFeaturePhpSource(aiFeatureSlug, namespace, phpPrefix, textDomain
5962
6119
  const normalizeSchemaFunctionName = `${phpPrefix}_${aiFeaturePhpId}_sanitize_ai_feature_schema`;
5963
6120
  const validatePayloadFunctionName = `${phpPrefix}_${aiFeaturePhpId}_validate_ai_feature_payload`;
5964
6121
  const canManageFunctionName = `${phpPrefix}_${aiFeaturePhpId}_can_manage_ai_feature`;
6122
+ const normalizePromptPayloadFunctionName = `${phpPrefix}_${aiFeaturePhpId}_normalize_ai_feature_prompt_payload`;
5965
6123
  const buildPromptFunctionName = `${phpPrefix}_${aiFeaturePhpId}_build_ai_feature_prompt`;
6124
+ const resolvePromptOptionsFunctionName = `${phpPrefix}_${aiFeaturePhpId}_resolve_ai_feature_prompt_options`;
5966
6125
  const normalizeProviderTypeFunctionName = `${phpPrefix}_${aiFeaturePhpId}_normalize_provider_type`;
5967
6126
  const buildTelemetryFunctionName = `${phpPrefix}_${aiFeaturePhpId}_build_ai_feature_telemetry`;
6127
+ const resolveUnavailableMessageFunctionName = `${phpPrefix}_${aiFeaturePhpId}_resolve_ai_feature_unavailable_message`;
5968
6128
  const isSupportedFunctionName = `${phpPrefix}_${aiFeaturePhpId}_is_ai_feature_supported`;
5969
6129
  const adminNoticeFunctionName = `${phpPrefix}_${aiFeaturePhpId}_ai_feature_admin_notice`;
5970
6130
  const handlerFunctionName = `${phpPrefix}_${aiFeaturePhpId}_handle_run_ai_feature`;
5971
6131
  const registerRoutesFunctionName = `${phpPrefix}_${aiFeaturePhpId}_register_ai_feature_routes`;
6132
+ const permissionFilterHook = `${phpPrefix}_${aiFeaturePhpId}_ai_feature_permission`;
6133
+ const promptPayloadFilterHook = `${phpPrefix}_${aiFeaturePhpId}_ai_feature_prompt_payload`;
6134
+ const promptFilterHook = `${phpPrefix}_${aiFeaturePhpId}_ai_feature_prompt`;
6135
+ const promptOptionsFilterHook = `${phpPrefix}_${aiFeaturePhpId}_ai_feature_prompt_options`;
6136
+ const adminNoticeMessageFilterHook = `${phpPrefix}_${aiFeaturePhpId}_ai_feature_admin_notice_message`;
6137
+ const unavailableMessageFilterHook = `${phpPrefix}_${aiFeaturePhpId}_ai_feature_unavailable_message`;
6138
+ const telemetryFilterHook = `${phpPrefix}_${aiFeaturePhpId}_ai_feature_telemetry`;
5972
6139
  return `<?php
5973
6140
  if ( ! defined( 'ABSPATH' ) ) {
5974
6141
  return;
5975
6142
  }
5976
6143
 
6144
+ /*
6145
+ * Customization hooks for the ${aiFeatureTitle} AI feature:
6146
+ *
6147
+ * - ${quotePhpString(permissionFilterHook)} filters the default current_user_can( 'edit_posts' ) capability check.
6148
+ * - ${quotePhpString(promptPayloadFilterHook)} filters the validated request payload array before prompt serialization.
6149
+ * - ${quotePhpString(promptFilterHook)} filters the final prompt string after payload normalization.
6150
+ * - ${quotePhpString(promptOptionsFilterHook)} filters prompt options with \`temperature\` and \`modelPreference\` keys.
6151
+ * - ${quotePhpString(adminNoticeMessageFilterHook)} filters the wp-admin notice shown when AI support is unavailable.
6152
+ * - ${quotePhpString(unavailableMessageFilterHook)} filters REST-facing unavailable messages by reason code.
6153
+ * - ${quotePhpString(telemetryFilterHook)} filters the response telemetry array before schema validation. Return a schema-compatible array.
6154
+ */
6155
+
5977
6156
  if ( ! function_exists( '${loadSchemaFunctionName}' ) ) {
5978
6157
  function ${loadSchemaFunctionName}( $schema_name ) {
5979
6158
  $project_root = dirname( __DIR__, 2 );
@@ -6040,17 +6219,111 @@ if ( ! function_exists( '${validatePayloadFunctionName}' ) ) {
6040
6219
  }
6041
6220
 
6042
6221
  if ( ! function_exists( '${canManageFunctionName}' ) ) {
6043
- function ${canManageFunctionName}() {
6044
- return current_user_can( 'edit_posts' );
6222
+ function ${canManageFunctionName}( WP_REST_Request $request = null ) {
6223
+ $permission = apply_filters(
6224
+ ${quotePhpString(permissionFilterHook)},
6225
+ current_user_can( 'edit_posts' ),
6226
+ $request
6227
+ );
6228
+ if ( is_wp_error( $permission ) ) {
6229
+ return $permission;
6230
+ }
6231
+ return (bool) $permission;
6232
+ }
6233
+ }
6234
+
6235
+ if ( ! function_exists( '${normalizePromptPayloadFunctionName}' ) ) {
6236
+ function ${normalizePromptPayloadFunctionName}( array $payload ) {
6237
+ $normalized_payload = apply_filters(
6238
+ ${quotePhpString(promptPayloadFilterHook)},
6239
+ $payload
6240
+ );
6241
+ return is_array( $normalized_payload ) ? $normalized_payload : $payload;
6045
6242
  }
6046
6243
  }
6047
6244
 
6048
6245
  if ( ! function_exists( '${buildPromptFunctionName}' ) ) {
6049
6246
  function ${buildPromptFunctionName}( array $payload ) {
6050
- return sprintf(
6247
+ $normalized_payload = ${normalizePromptPayloadFunctionName}( $payload );
6248
+ $prompt = sprintf(
6051
6249
  'You are helping with the %1$s AI workflow. Read the JSON request payload and return JSON that matches the provided schema. Request payload: %2$s',
6052
6250
  ${quotePhpString(aiFeatureTitle)},
6053
- wp_json_encode( $payload )
6251
+ wp_json_encode( $normalized_payload )
6252
+ );
6253
+ $filtered_prompt = apply_filters(
6254
+ ${quotePhpString(promptFilterHook)},
6255
+ $prompt,
6256
+ $normalized_payload,
6257
+ $payload
6258
+ );
6259
+ return is_string( $filtered_prompt ) && '' !== $filtered_prompt ? $filtered_prompt : $prompt;
6260
+ }
6261
+ }
6262
+
6263
+ if ( ! function_exists( '${resolvePromptOptionsFunctionName}' ) ) {
6264
+ function ${resolvePromptOptionsFunctionName}( array $payload = array() ) {
6265
+ $options = apply_filters(
6266
+ ${quotePhpString(promptOptionsFilterHook)},
6267
+ array(
6268
+ 'modelPreference' => array(),
6269
+ 'temperature' => 0.2,
6270
+ ),
6271
+ $payload
6272
+ );
6273
+ if ( ! is_array( $options ) ) {
6274
+ $options = array();
6275
+ }
6276
+
6277
+ $temperature = 0.2;
6278
+ if ( array_key_exists( 'temperature', $options ) ) {
6279
+ if ( null === $options['temperature'] ) {
6280
+ $temperature = null;
6281
+ } elseif ( is_numeric( $options['temperature'] ) ) {
6282
+ $temperature = (float) $options['temperature'];
6283
+ }
6284
+ }
6285
+
6286
+ $model_preferences = array();
6287
+ if ( isset( $options['modelPreference'] ) ) {
6288
+ $raw_model_preferences = $options['modelPreference'];
6289
+ if ( is_string( $raw_model_preferences ) && '' !== $raw_model_preferences ) {
6290
+ $model_preferences = array( $raw_model_preferences );
6291
+ } elseif ( is_array( $raw_model_preferences ) ) {
6292
+ $model_preferences = array_values(
6293
+ array_filter(
6294
+ array_map(
6295
+ static function ( $candidate ) {
6296
+ if ( is_string( $candidate ) && '' !== $candidate ) {
6297
+ return $candidate;
6298
+ }
6299
+ if ( ! is_array( $candidate ) ) {
6300
+ return null;
6301
+ }
6302
+
6303
+ $normalized = array_values(
6304
+ array_filter(
6305
+ $candidate,
6306
+ static function ( $value ) {
6307
+ return is_string( $value ) && '' !== $value;
6308
+ }
6309
+ )
6310
+ );
6311
+
6312
+ return count( $normalized ) > 0 ? $normalized : null;
6313
+ },
6314
+ $raw_model_preferences
6315
+ ),
6316
+ static function ( $candidate ) {
6317
+ return null !== $candidate;
6318
+ }
6319
+ )
6320
+ );
6321
+ }
6322
+ }
6323
+
6324
+ return array(
6325
+ 'modelPreference' => $model_preferences,
6326
+ 'temperature' => $temperature,
6054
6327
  );
6055
6328
  }
6056
6329
  }
@@ -6066,7 +6339,7 @@ if ( ! function_exists( '${normalizeProviderTypeFunctionName}' ) ) {
6066
6339
  }
6067
6340
 
6068
6341
  if ( ! function_exists( '${buildTelemetryFunctionName}' ) ) {
6069
- function ${buildTelemetryFunctionName}( $result ) {
6342
+ function ${buildTelemetryFunctionName}( $result, array $payload = array(), array $normalized_result = array() ) {
6070
6343
  if (
6071
6344
  ! is_object( $result ) ||
6072
6345
  ! method_exists( $result, 'getId' ) ||
@@ -6126,47 +6399,95 @@ if ( ! function_exists( '${buildTelemetryFunctionName}' ) ) {
6126
6399
  }
6127
6400
  }
6128
6401
 
6129
- return $telemetry;
6402
+ $filtered_telemetry = apply_filters(
6403
+ ${quotePhpString(telemetryFilterHook)},
6404
+ $telemetry,
6405
+ $result,
6406
+ $payload,
6407
+ $normalized_result
6408
+ );
6409
+ return is_array( $filtered_telemetry ) ? $filtered_telemetry : $telemetry;
6410
+ }
6411
+ }
6412
+
6413
+ if ( ! function_exists( '${resolveUnavailableMessageFunctionName}' ) ) {
6414
+ function ${resolveUnavailableMessageFunctionName}( $message, $reason, array $context = array() ) {
6415
+ $filtered_message = apply_filters(
6416
+ ${quotePhpString(unavailableMessageFilterHook)},
6417
+ $message,
6418
+ $reason,
6419
+ $context
6420
+ );
6421
+ return is_string( $filtered_message ) && '' !== $filtered_message ? $filtered_message : $message;
6130
6422
  }
6131
6423
  }
6132
6424
 
6133
6425
  if ( ! function_exists( '${isSupportedFunctionName}' ) ) {
6134
- function ${isSupportedFunctionName}() {
6426
+ function ${isSupportedFunctionName}( array $payload = array(), $cache_result = true ) {
6135
6427
  static $is_supported = null;
6136
- if ( null !== $is_supported ) {
6428
+ $use_cache = $cache_result && count( $payload ) === 0;
6429
+ if ( $use_cache && null !== $is_supported ) {
6137
6430
  return $is_supported;
6138
6431
  }
6139
6432
 
6140
6433
  if ( ! function_exists( 'wp_ai_client_prompt' ) ) {
6141
- $is_supported = false;
6142
- return $is_supported;
6434
+ if ( $use_cache ) {
6435
+ $is_supported = false;
6436
+ }
6437
+ return false;
6143
6438
  }
6144
6439
 
6145
6440
  $schema = ${loadAiSchemaFunctionName}();
6146
6441
  if ( ! is_array( $schema ) ) {
6147
- $is_supported = false;
6148
- return $is_supported;
6442
+ if ( $use_cache ) {
6443
+ $is_supported = false;
6444
+ }
6445
+ return false;
6149
6446
  }
6150
6447
 
6151
6448
  $prompt = wp_ai_client_prompt( 'AI feature support probe.' );
6152
6449
  if ( ! is_object( $prompt ) || ! method_exists( $prompt, 'as_json_response' ) ) {
6153
- $is_supported = false;
6154
- return $is_supported;
6450
+ if ( $use_cache ) {
6451
+ $is_supported = false;
6452
+ }
6453
+ return false;
6454
+ }
6455
+ $prompt_options = ${resolvePromptOptionsFunctionName}( $payload );
6456
+ if (
6457
+ array_key_exists( 'temperature', $prompt_options ) &&
6458
+ null !== $prompt_options['temperature'] &&
6459
+ method_exists( $prompt, 'using_temperature' )
6460
+ ) {
6461
+ $prompt = $prompt->using_temperature( $prompt_options['temperature'] );
6462
+ }
6463
+ if (
6464
+ ! empty( $prompt_options['modelPreference'] ) &&
6465
+ method_exists( $prompt, 'using_model_preference' )
6466
+ ) {
6467
+ $prompt = $prompt->using_model_preference( ...$prompt_options['modelPreference'] );
6155
6468
  }
6156
6469
 
6157
6470
  $structured_prompt = $prompt->as_json_response( $schema );
6158
6471
  if ( ! is_object( $structured_prompt ) ) {
6159
- $is_supported = false;
6160
- return $is_supported;
6472
+ if ( $use_cache ) {
6473
+ $is_supported = false;
6474
+ }
6475
+ return false;
6161
6476
  }
6162
6477
 
6163
6478
  if ( method_exists( $structured_prompt, 'is_supported_for_text_generation' ) ) {
6164
- $is_supported = (bool) $structured_prompt->is_supported_for_text_generation();
6165
- return $is_supported;
6479
+ $supported = (bool) $structured_prompt->is_supported_for_text_generation();
6480
+ if ( $use_cache ) {
6481
+ $is_supported = $supported;
6482
+ }
6483
+ return $supported;
6166
6484
  }
6167
6485
 
6168
- $is_supported = method_exists( $structured_prompt, 'generate_text_result' );
6169
- return $is_supported;
6486
+ $supported = method_exists( $structured_prompt, 'generate_text_result' );
6487
+ if ( $use_cache ) {
6488
+ $is_supported = $supported;
6489
+ }
6490
+ return $supported;
6170
6491
  }
6171
6492
  }
6172
6493
 
@@ -6181,6 +6502,18 @@ if ( ! function_exists( '${adminNoticeFunctionName}' ) ) {
6181
6502
  __( 'The %s AI feature is optional and remains disabled until the WordPress AI Client is available with structured text generation support for the generated schema.', ${quotePhpString(textDomain)} ),
6182
6503
  ${quotePhpString(aiFeatureTitle)}
6183
6504
  );
6505
+ $filtered_message = apply_filters(
6506
+ ${quotePhpString(adminNoticeMessageFilterHook)},
6507
+ $message,
6508
+ array(
6509
+ 'featureSlug' => ${quotePhpString(aiFeatureSlug)},
6510
+ 'featureTitle' => ${quotePhpString(aiFeatureTitle)},
6511
+ 'namespace' => ${quotePhpString(namespace)},
6512
+ )
6513
+ );
6514
+ if ( is_string( $filtered_message ) && '' !== $filtered_message ) {
6515
+ $message = $filtered_message;
6516
+ }
6184
6517
  printf( '<div class="notice notice-warning"><p>%s</p></div>', esc_html( $message ) );
6185
6518
  }
6186
6519
  }
@@ -6192,10 +6525,16 @@ if ( ! function_exists( '${handlerFunctionName}' ) ) {
6192
6525
  return $payload;
6193
6526
  }
6194
6527
 
6195
- if ( ! ${isSupportedFunctionName}() ) {
6528
+ if ( ! ${isSupportedFunctionName}( $payload, false ) ) {
6196
6529
  return new WP_Error(
6197
6530
  'ai_client_unavailable',
6198
- 'The WordPress AI Client is unavailable or does not support this feature endpoint.',
6531
+ ${resolveUnavailableMessageFunctionName}(
6532
+ 'The WordPress AI Client is unavailable or does not support this feature endpoint.',
6533
+ 'support_probe_failed',
6534
+ array(
6535
+ 'featureSlug' => ${quotePhpString(aiFeatureSlug)},
6536
+ )
6537
+ ),
6199
6538
  array( 'status' => 501 )
6200
6539
  );
6201
6540
  }
@@ -6209,22 +6548,45 @@ if ( ! function_exists( '${handlerFunctionName}' ) ) {
6209
6548
  );
6210
6549
  }
6211
6550
 
6551
+ $prompt_options = ${resolvePromptOptionsFunctionName}( $payload );
6212
6552
  $prompt = wp_ai_client_prompt( ${buildPromptFunctionName}( $payload ) );
6213
6553
  if ( ! is_object( $prompt ) ) {
6214
6554
  return new WP_Error(
6215
6555
  'ai_client_unavailable',
6216
- 'The WordPress AI Client prompt builder is unavailable on this site.',
6556
+ ${resolveUnavailableMessageFunctionName}(
6557
+ 'The WordPress AI Client prompt builder is unavailable on this site.',
6558
+ 'prompt_builder_missing',
6559
+ array(
6560
+ 'featureSlug' => ${quotePhpString(aiFeatureSlug)},
6561
+ )
6562
+ ),
6217
6563
  array( 'status' => 501 )
6218
6564
  );
6219
6565
  }
6220
6566
 
6221
- if ( method_exists( $prompt, 'using_temperature' ) ) {
6222
- $prompt = $prompt->using_temperature( 0.2 );
6567
+ if (
6568
+ array_key_exists( 'temperature', $prompt_options ) &&
6569
+ null !== $prompt_options['temperature'] &&
6570
+ method_exists( $prompt, 'using_temperature' )
6571
+ ) {
6572
+ $prompt = $prompt->using_temperature( $prompt_options['temperature'] );
6573
+ }
6574
+ if (
6575
+ ! empty( $prompt_options['modelPreference'] ) &&
6576
+ method_exists( $prompt, 'using_model_preference' )
6577
+ ) {
6578
+ $prompt = $prompt->using_model_preference( ...$prompt_options['modelPreference'] );
6223
6579
  }
6224
6580
  if ( ! method_exists( $prompt, 'as_json_response' ) ) {
6225
6581
  return new WP_Error(
6226
6582
  'ai_client_unavailable',
6227
- 'The current WordPress AI Client does not expose as_json_response().',
6583
+ ${resolveUnavailableMessageFunctionName}(
6584
+ 'The current WordPress AI Client does not expose as_json_response().',
6585
+ 'as_json_response_missing',
6586
+ array(
6587
+ 'featureSlug' => ${quotePhpString(aiFeatureSlug)},
6588
+ )
6589
+ ),
6228
6590
  array( 'status' => 501 )
6229
6591
  );
6230
6592
  }
@@ -6233,7 +6595,13 @@ if ( ! function_exists( '${handlerFunctionName}' ) ) {
6233
6595
  if ( ! is_object( $structured_prompt ) ) {
6234
6596
  return new WP_Error(
6235
6597
  'ai_client_unavailable',
6236
- 'The current WordPress AI Client could not prepare a structured-output prompt.',
6598
+ ${resolveUnavailableMessageFunctionName}(
6599
+ 'The current WordPress AI Client could not prepare a structured-output prompt.',
6600
+ 'structured_prompt_missing',
6601
+ array(
6602
+ 'featureSlug' => ${quotePhpString(aiFeatureSlug)},
6603
+ )
6604
+ ),
6237
6605
  array( 'status' => 501 )
6238
6606
  );
6239
6607
  }
@@ -6244,14 +6612,26 @@ if ( ! function_exists( '${handlerFunctionName}' ) ) {
6244
6612
  ) {
6245
6613
  return new WP_Error(
6246
6614
  'ai_client_unavailable',
6247
- 'The current WordPress AI Client provider or model does not support this structured-output feature.',
6615
+ ${resolveUnavailableMessageFunctionName}(
6616
+ 'The current WordPress AI Client provider or model does not support this structured-output feature.',
6617
+ 'text_generation_unsupported',
6618
+ array(
6619
+ 'featureSlug' => ${quotePhpString(aiFeatureSlug)},
6620
+ )
6621
+ ),
6248
6622
  array( 'status' => 501 )
6249
6623
  );
6250
6624
  }
6251
6625
  if ( ! method_exists( $structured_prompt, 'generate_text_result' ) ) {
6252
6626
  return new WP_Error(
6253
6627
  'ai_client_unavailable',
6254
- 'The current WordPress AI Client does not expose generate_text_result() after as_json_response().',
6628
+ ${resolveUnavailableMessageFunctionName}(
6629
+ 'The current WordPress AI Client does not expose generate_text_result() after as_json_response().',
6630
+ 'generate_text_result_missing',
6631
+ array(
6632
+ 'featureSlug' => ${quotePhpString(aiFeatureSlug)},
6633
+ )
6634
+ ),
6255
6635
  array( 'status' => 501 )
6256
6636
  );
6257
6637
  }
@@ -6286,7 +6666,7 @@ if ( ! function_exists( '${handlerFunctionName}' ) ) {
6286
6666
  );
6287
6667
  }
6288
6668
 
6289
- $telemetry = ${buildTelemetryFunctionName}( $result );
6669
+ $telemetry = ${buildTelemetryFunctionName}( $result, $payload, $normalized_result );
6290
6670
  if ( is_wp_error( $telemetry ) ) {
6291
6671
  return $telemetry;
6292
6672
  }
@@ -6331,17 +6711,14 @@ add_action( 'admin_notices', '${adminNoticeFunctionName}' );
6331
6711
  add_action( 'rest_api_init', '${registerRoutesFunctionName}' );
6332
6712
  `;
6333
6713
  }
6334
- async function runAddAiFeatureCommand({
6335
- aiFeatureName,
6336
- cwd = process.cwd(),
6337
- namespace
6714
+
6715
+ // ../wp-typia-project-tools/src/runtime/cli-add-workspace-ai-scaffold.ts
6716
+ async function scaffoldAiFeatureWorkspace({
6717
+ aiFeatureSlug,
6718
+ compatibilityPolicy,
6719
+ namespace,
6720
+ workspace
6338
6721
  }) {
6339
- const workspace = resolveWorkspaceProject(cwd);
6340
- const aiFeatureSlug = assertValidGeneratedSlug("AI feature name", normalizeBlockSlug(aiFeatureName), "wp-typia add ai-feature <name> [--namespace <vendor/v1>]");
6341
- const resolvedNamespace = resolveRestResourceNamespace(workspace.workspace.namespace, namespace);
6342
- const compatibilityPolicy = resolveScaffoldCompatibilityPolicy(OPTIONAL_WORDPRESS_AI_CLIENT_COMPATIBILITY);
6343
- const inventory = readWorkspaceInventory(workspace.projectDir);
6344
- assertAiFeatureDoesNotExist(workspace.projectDir, aiFeatureSlug, inventory);
6345
6722
  const blockConfigPath = path13.join(workspace.projectDir, "scripts", "block-config.ts");
6346
6723
  const bootstrapPath = getWorkspaceBootstrapPath(workspace);
6347
6724
  const packageJsonPath = path13.join(workspace.projectDir, "package.json");
@@ -6379,7 +6756,7 @@ async function runAddAiFeatureCommand({
6379
6756
  await fsp8.writeFile(validatorsFilePath, buildAiFeatureValidatorsSource(aiFeatureSlug), "utf8");
6380
6757
  await fsp8.writeFile(apiFilePath, buildAiFeatureApiSource(aiFeatureSlug), "utf8");
6381
6758
  await fsp8.writeFile(dataFilePath, buildAiFeatureDataSource(aiFeatureSlug), "utf8");
6382
- await fsp8.writeFile(phpFilePath, buildAiFeaturePhpSource(aiFeatureSlug, resolvedNamespace, workspace.workspace.phpPrefix, workspace.workspace.textDomain), "utf8");
6759
+ await fsp8.writeFile(phpFilePath, buildAiFeaturePhpSource(aiFeatureSlug, namespace, workspace.workspace.phpPrefix, workspace.workspace.textDomain), "utf8");
6383
6760
  const pascalCase = toPascalCase(aiFeatureSlug);
6384
6761
  await syncAiFeatureRestArtifacts({
6385
6762
  clientFile: `src/ai-features/${aiFeatureSlug}/api-client.ts`,
@@ -6388,7 +6765,7 @@ async function runAddAiFeatureCommand({
6388
6765
  typesFile: `src/ai-features/${aiFeatureSlug}/api-types.ts`,
6389
6766
  validatorsFile: `src/ai-features/${aiFeatureSlug}/api-validators.ts`,
6390
6767
  variables: {
6391
- namespace: resolvedNamespace,
6768
+ namespace,
6392
6769
  pascalCase,
6393
6770
  slugKebabCase: aiFeatureSlug,
6394
6771
  title: toTitleCase(aiFeatureSlug)
@@ -6401,14 +6778,11 @@ async function runAddAiFeatureCommand({
6401
6778
  });
6402
6779
  await appendWorkspaceInventoryEntries(workspace.projectDir, {
6403
6780
  aiFeatureEntries: [
6404
- buildAiFeatureConfigEntry(aiFeatureSlug, resolvedNamespace)
6781
+ buildAiFeatureConfigEntry(aiFeatureSlug, namespace)
6405
6782
  ],
6406
6783
  transformSource: ensureBlockConfigCanAddRestManifests
6407
6784
  });
6408
6785
  return {
6409
- aiFeatureSlug,
6410
- namespace: resolvedNamespace,
6411
- projectDir: workspace.projectDir,
6412
6786
  warnings: packageScriptChanges.addedProjectToolsDependency ? [
6413
6787
  "Added `@wp-typia/project-tools` to devDependencies for `sync-ai`. If this workspace was already installed, rerun your package manager install command before the first `wp-typia sync ai`."
6414
6788
  ] : []
@@ -6419,6 +6793,32 @@ async function runAddAiFeatureCommand({
6419
6793
  }
6420
6794
  }
6421
6795
 
6796
+ // ../wp-typia-project-tools/src/runtime/cli-add-workspace-ai.ts
6797
+ async function runAddAiFeatureCommand({
6798
+ aiFeatureName,
6799
+ cwd = process.cwd(),
6800
+ namespace
6801
+ }) {
6802
+ const workspace = resolveWorkspaceProject(cwd);
6803
+ const aiFeatureSlug = assertValidGeneratedSlug("AI feature name", normalizeBlockSlug(aiFeatureName), "wp-typia add ai-feature <name> [--namespace <vendor/v1>]");
6804
+ const resolvedNamespace = resolveRestResourceNamespace(workspace.workspace.namespace, namespace);
6805
+ const compatibilityPolicy = resolveScaffoldCompatibilityPolicy(OPTIONAL_WORDPRESS_AI_CLIENT_COMPATIBILITY);
6806
+ const inventory = readWorkspaceInventory(workspace.projectDir);
6807
+ assertAiFeatureDoesNotExist(workspace.projectDir, aiFeatureSlug, inventory);
6808
+ const scaffoldResult = await scaffoldAiFeatureWorkspace({
6809
+ aiFeatureSlug,
6810
+ compatibilityPolicy,
6811
+ namespace: resolvedNamespace,
6812
+ workspace
6813
+ });
6814
+ return {
6815
+ aiFeatureSlug,
6816
+ namespace: resolvedNamespace,
6817
+ projectDir: workspace.projectDir,
6818
+ warnings: scaffoldResult.warnings
6819
+ };
6820
+ }
6821
+
6422
6822
  // ../wp-typia-project-tools/src/runtime/cli-add-workspace.ts
6423
6823
  var VARIATIONS_IMPORT_LINE = "import { registerWorkspaceVariations } from './variations';";
6424
6824
  var VARIATIONS_IMPORT_PATTERN = /^\s*import\s*\{\s*registerWorkspaceVariations\s*\}\s*from\s*["']\.\/variations["']\s*;?\s*$/mu;
@@ -6900,24 +7300,6 @@ async function writeBlockTransformRegistry(projectDir, blockSlug, transformSlug)
6900
7300
  const nextTransformSlugs = Array.from(new Set([...existingTransformSlugs, transformSlug])).sort();
6901
7301
  await fsp9.writeFile(transformsIndexPath, buildBlockTransformIndexSource(nextTransformSlugs), "utf8");
6902
7302
  }
6903
- function assertBlockStyleDoesNotExist(projectDir, blockSlug, styleSlug, inventory) {
6904
- const stylePath = path14.join(projectDir, "src", "blocks", blockSlug, "styles", `${styleSlug}.ts`);
6905
- if (fs6.existsSync(stylePath)) {
6906
- throw new Error(`A block style already exists at ${path14.relative(projectDir, stylePath)}. Choose a different name.`);
6907
- }
6908
- if (inventory.blockStyles.some((entry) => entry.block === blockSlug && entry.slug === styleSlug)) {
6909
- throw new Error(`A block style inventory entry already exists for ${blockSlug}/${styleSlug}. Choose a different name.`);
6910
- }
6911
- }
6912
- function assertBlockTransformDoesNotExist(projectDir, blockSlug, transformSlug, inventory) {
6913
- const transformPath = path14.join(projectDir, "src", "blocks", blockSlug, "transforms", `${transformSlug}.ts`);
6914
- if (fs6.existsSync(transformPath)) {
6915
- throw new Error(`A block transform already exists at ${path14.relative(projectDir, transformPath)}. Choose a different name.`);
6916
- }
6917
- if (inventory.blockTransforms.some((entry) => entry.block === blockSlug && entry.slug === transformSlug)) {
6918
- throw new Error(`A block transform inventory entry already exists for ${blockSlug}/${transformSlug}. Choose a different name.`);
6919
- }
6920
- }
6921
7303
  function assertFullBlockName(blockName, flagName) {
6922
7304
  const trimmed = blockName.trim();
6923
7305
  if (!trimmed) {
@@ -7165,4 +7547,4 @@ export {
7165
7547
  ADD_BLOCK_TEMPLATE_IDS
7166
7548
  };
7167
7549
 
7168
- //# debugId=DE205F0F5620636264756E2164756E21
7550
+ //# debugId=272EBA8B7C96349F64756E2164756E21