Localize marketplace metadata and group Three.js skills
Constraint: Chinese users need localized plugin and skill descriptions while third-party sources must remain syncable from upstream. Rejected: Editing third-party generated files by hand | Sync would overwrite them and make localization drift from config. Rejected: One plugin per Three.js skill | Users should install one domain package while Codex can still select individual skills inside it. Confidence: high Scope-risk: moderate Directive: Keep plugin IDs and skill names in English kebab-case; put user-facing Chinese text in manifest fields, SKILL.md descriptions, or external-source overrides. Tested: Codex manual confirms duplicate skills are not merged; powershell sync_external_plugins.ps1 with proxy; python scripts\\validate_marketplace.py; validate_plugin.py for all plugins; codex plugin list local marketplace; codex plugin add/remove eapil-threejs. Not-tested: Codex Desktop visual refresh after pulling the updated remote marketplace.
This commit is contained in:
@@ -69,7 +69,8 @@ function Copy-PathIfExists {
|
||||
|
||||
function Read-JsonFile {
|
||||
param([string]$Path)
|
||||
return Get-Content -Raw -LiteralPath $Path | ConvertFrom-Json
|
||||
$resolvedPath = (Resolve-Path -LiteralPath $Path).Path
|
||||
return [System.IO.File]::ReadAllText($resolvedPath, [System.Text.Encoding]::UTF8) | ConvertFrom-Json
|
||||
}
|
||||
|
||||
function Write-JsonFile {
|
||||
@@ -79,17 +80,30 @@ function Write-JsonFile {
|
||||
[System.IO.File]::WriteAllText($Path, "$json`n", $utf8NoBom)
|
||||
}
|
||||
|
||||
function Write-Utf8NoBomText {
|
||||
param([string]$Path, [string]$Text)
|
||||
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
|
||||
[System.IO.File]::WriteAllText($Path, $Text, $utf8NoBom)
|
||||
}
|
||||
|
||||
function ConvertTo-Hashtable {
|
||||
param([object]$InputObject)
|
||||
if ($null -eq $InputObject) {
|
||||
return $null
|
||||
}
|
||||
if ($InputObject -is [System.Collections.IDictionary]) {
|
||||
$hash = [ordered]@{}
|
||||
foreach ($key in $InputObject.Keys) {
|
||||
$hash[$key] = ConvertTo-Hashtable $InputObject[$key]
|
||||
}
|
||||
return $hash
|
||||
}
|
||||
if ($InputObject -is [System.Collections.IEnumerable] -and -not ($InputObject -is [string]) -and -not ($InputObject -is [System.Management.Automation.PSCustomObject])) {
|
||||
$items = @()
|
||||
foreach ($item in $InputObject) {
|
||||
$items += ConvertTo-Hashtable $item
|
||||
}
|
||||
return $items
|
||||
return ,$items
|
||||
}
|
||||
if ($InputObject -is [System.Management.Automation.PSCustomObject]) {
|
||||
$hash = [ordered]@{}
|
||||
@@ -103,7 +117,7 @@ function ConvertTo-Hashtable {
|
||||
|
||||
function Get-SkillFrontmatter {
|
||||
param([string]$SkillFile)
|
||||
$lines = Get-Content -LiteralPath $SkillFile
|
||||
$lines = [System.IO.File]::ReadAllLines((Resolve-Path -LiteralPath $SkillFile).Path, [System.Text.Encoding]::UTF8)
|
||||
if ($lines.Count -lt 3 -or $lines[0] -ne "---") {
|
||||
return @{}
|
||||
}
|
||||
@@ -121,6 +135,108 @@ function Get-SkillFrontmatter {
|
||||
return $frontmatter
|
||||
}
|
||||
|
||||
function Merge-Hashtable {
|
||||
param(
|
||||
[object]$Base,
|
||||
[object]$Override
|
||||
)
|
||||
$baseHash = ConvertTo-Hashtable $Base
|
||||
$overrideHash = ConvertTo-Hashtable $Override
|
||||
foreach ($key in $overrideHash.Keys) {
|
||||
if ($baseHash.Contains($key) -and $baseHash[$key] -is [System.Collections.IDictionary] -and $overrideHash[$key] -is [System.Collections.IDictionary]) {
|
||||
$baseHash[$key] = Merge-Hashtable -Base $baseHash[$key] -Override $overrideHash[$key]
|
||||
} else {
|
||||
$baseHash[$key] = $overrideHash[$key]
|
||||
}
|
||||
}
|
||||
return $baseHash
|
||||
}
|
||||
|
||||
function Get-SourceCategory {
|
||||
param([object]$Source)
|
||||
if ($Source.PSObject.Properties.Name -contains "marketplace" -and $Source.marketplace -and $Source.marketplace.category) {
|
||||
return $Source.marketplace.category
|
||||
}
|
||||
return $Source.category
|
||||
}
|
||||
|
||||
function ConvertTo-YamlDoubleQuoted {
|
||||
param([string]$Value)
|
||||
$escaped = $Value.Replace("\", "\\").Replace('"', '\"')
|
||||
return "`"$escaped`""
|
||||
}
|
||||
|
||||
function Set-SkillFrontmatterValue {
|
||||
param(
|
||||
[string]$SkillFile,
|
||||
[string]$Key,
|
||||
[string]$Value
|
||||
)
|
||||
$lines = @([System.IO.File]::ReadAllLines((Resolve-Path -LiteralPath $SkillFile).Path, [System.Text.Encoding]::UTF8))
|
||||
if ($lines.Count -lt 3 -or $lines[0] -ne "---") {
|
||||
throw "Cannot update frontmatter in $SkillFile"
|
||||
}
|
||||
|
||||
$endIndex = -1
|
||||
for ($i = 1; $i -lt $lines.Count; $i++) {
|
||||
if ($lines[$i] -eq "---") {
|
||||
$endIndex = $i
|
||||
break
|
||||
}
|
||||
}
|
||||
if ($endIndex -lt 0) {
|
||||
throw "Cannot find frontmatter end in $SkillFile"
|
||||
}
|
||||
|
||||
$replacement = "$Key`: $(ConvertTo-YamlDoubleQuoted $Value)"
|
||||
$updated = $false
|
||||
for ($i = 1; $i -lt $endIndex; $i++) {
|
||||
if ($lines[$i] -match "^\s*$([regex]::Escape($Key))\s*:") {
|
||||
$lines[$i] = $replacement
|
||||
$updated = $true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $updated) {
|
||||
$before = @()
|
||||
if ($endIndex -gt 1) {
|
||||
$before = $lines[0..($endIndex - 1)]
|
||||
} else {
|
||||
$before = @($lines[0])
|
||||
}
|
||||
$after = $lines[$endIndex..($lines.Count - 1)]
|
||||
$lines = @($before + $replacement + $after)
|
||||
}
|
||||
|
||||
Write-Utf8NoBomText -Path $SkillFile -Text (($lines -join "`n") + "`n")
|
||||
}
|
||||
|
||||
function Apply-LocalOverrides {
|
||||
param(
|
||||
[string]$PluginDir,
|
||||
[object]$Source
|
||||
)
|
||||
|
||||
if ($Source.PSObject.Properties.Name -contains "manifestOverrides" -and $Source.manifestOverrides) {
|
||||
$manifestPath = Join-Path $PluginDir ".codex-plugin\plugin.json"
|
||||
$manifest = ConvertTo-Hashtable (Read-JsonFile $manifestPath)
|
||||
$manifest = Merge-Hashtable -Base $manifest -Override $Source.manifestOverrides
|
||||
Write-JsonFile -Path $manifestPath -Value $manifest
|
||||
}
|
||||
|
||||
if ($Source.PSObject.Properties.Name -contains "skillDescriptions" -and $Source.skillDescriptions) {
|
||||
$skillsRoot = Join-Path $PluginDir "skills"
|
||||
foreach ($property in $Source.skillDescriptions.PSObject.Properties) {
|
||||
$skillFile = Join-Path (Join-Path $skillsRoot $property.Name) "SKILL.md"
|
||||
if (-not (Test-Path -LiteralPath $skillFile)) {
|
||||
throw "skillDescriptions references missing skill: $skillFile"
|
||||
}
|
||||
Set-SkillFrontmatterValue -SkillFile $skillFile -Key "description" -Value $property.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Get-Clone {
|
||||
param(
|
||||
[object]$Source,
|
||||
@@ -201,6 +317,7 @@ function Sync-CodexPlugin {
|
||||
$dst = Join-Path $pluginDir $include
|
||||
Copy-PathIfExists -Source $src -Destination $dst -Within $RepoRoot
|
||||
}
|
||||
Apply-LocalOverrides -PluginDir $pluginDir -Source $Source
|
||||
Write-Provenance -PluginDir $pluginDir -Source $Source -Commit $Commit -SyncedAt $SyncedAt -SourcePath $relativeSourcePath
|
||||
}
|
||||
|
||||
@@ -301,7 +418,7 @@ function Sync-ClaudeSkill {
|
||||
shortDescription = $description
|
||||
longDescription = $description
|
||||
developerName = (New-AuthorObject $metadata.author).name
|
||||
category = $Source.category
|
||||
category = (Get-SourceCategory $Source)
|
||||
capabilities = @("Read", "Write")
|
||||
defaultPrompt = @("Use $displayName for this task.")
|
||||
websiteURL = $homepage
|
||||
@@ -312,6 +429,7 @@ function Sync-ClaudeSkill {
|
||||
}
|
||||
}
|
||||
Write-JsonFile -Path (Join-Path $pluginDir ".codex-plugin\plugin.json") -Value $manifest
|
||||
Apply-LocalOverrides -PluginDir $pluginDir -Source $Source
|
||||
Write-Provenance -PluginDir $pluginDir -Source $Source -Commit $Commit -SyncedAt $SyncedAt -SourcePath $Source.skillPath
|
||||
}
|
||||
|
||||
@@ -340,7 +458,7 @@ function Update-Marketplace {
|
||||
installation = "AVAILABLE"
|
||||
authentication = "ON_INSTALL"
|
||||
}
|
||||
category = $source.category
|
||||
category = (Get-SourceCategory $source)
|
||||
}
|
||||
}
|
||||
$marketplace.plugins = $plugins
|
||||
|
||||
Reference in New Issue
Block a user