diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json
index 995075af0a..691cdded4a 100644
--- a/.claude-plugin/marketplace.json
+++ b/.claude-plugin/marketplace.json
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-marketplace.json",
"name": "pascal",
- "version": "0.1.4",
+ "version": "0.1.5",
"description": "Public Pascal workflows for MCP-capable agents.",
"owner": {
"name": "Pascal"
@@ -11,7 +11,7 @@
"name": "pascal-agent-skills",
"source": "./",
"description": "Create and inspect editable Pascal scenes and run bounded furniture footprint assessments with blocker-aware next actions.",
- "version": "0.1.4",
+ "version": "0.1.5",
"category": "productivity",
"skills": ["./skills/pascal-3d", "./skills/furniture-fit"]
}
diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json
index 610f9b9171..e5cb3d04d2 100644
--- a/.claude-plugin/plugin.json
+++ b/.claude-plugin/plugin.json
@@ -2,7 +2,7 @@
"$schema": "https://json.schemastore.org/claude-code-plugin.json",
"name": "pascal-agent-skills",
"displayName": "Pascal agent skills",
- "version": "0.1.4",
+ "version": "0.1.5",
"description": "Create, inspect, validate, and assess furniture layouts with bounded next actions in Pascal through MCP.",
"author": {
"name": "Pascal"
diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json
index b992339f84..ce111bdf35 100644
--- a/.codex-plugin/plugin.json
+++ b/.codex-plugin/plugin.json
@@ -1,9 +1,10 @@
{
"name": "pascal-agent-skills",
- "version": "0.1.4",
+ "version": "0.1.5",
"description": "Create, inspect, validate, and assess furniture layouts with bounded next actions in Pascal through MCP.",
"author": {
"name": "Pascal",
+ "email": "support@pascal.app",
"url": "https://pascal.app"
},
"homepage": "https://editor.pascal.app/docs/developers/mcp",
@@ -13,17 +14,25 @@
"skills": "./skills/",
"interface": {
"displayName": "Pascal agent skills",
- "shortDescription": "Build and inspect Pascal scenes and assess furniture footprints.",
+ "shortDescription": "Build 3D scenes and check fit",
"longDescription": "Use Pascal's MCP tools to work with editable building scenes, validate and save results, and produce bounded furniture footprint reports with explicit evidence, limitations, and blocker-aware next actions.",
"developerName": "Pascal",
"category": "Developer Tools",
- "capabilities": ["Interactive", "Read", "Write"],
- "websiteURL": "https://pascal.app",
+ "capabilities": [
+ "Build and edit 3D scenes",
+ "Validate spatial layouts",
+ "Assess furniture footprints"
+ ],
+ "websiteURL": "https://editor.pascal.app/docs/developers/mcp",
+ "supportURL": "https://github.com/pascalorg/editor/issues",
+ "privacyPolicyURL": "https://editor.pascal.app/privacy",
+ "termsOfServiceURL": "https://editor.pascal.app/terms",
"defaultPrompt": [
"Use Pascal to inspect this building project, make the requested bounded edit, validate it, save it, and return the editor URL.",
- "Check whether this furniture footprint fits in the measured Pascal room, including rotations, collisions, door access, and unsupported checks."
+ "Check whether this furniture footprint fits in a measured Pascal room, including rotations, collisions, and door access."
],
"brandColor": "#171717",
- "screenshots": []
+ "composerIcon": "./assets/pascal-mark.svg",
+ "logo": "./assets/pascal-mark.svg"
}
}
diff --git a/assets/pascal-mark.svg b/assets/pascal-mark.svg
new file mode 100644
index 0000000000..176994e5a4
--- /dev/null
+++ b/assets/pascal-mark.svg
@@ -0,0 +1,5 @@
+
diff --git a/plugin-evals/README.md b/plugin-evals/README.md
new file mode 100644
index 0000000000..45ca43b5c1
--- /dev/null
+++ b/plugin-evals/README.md
@@ -0,0 +1,8 @@
+# Publishing evaluation fixtures
+
+`publishing-cases.json` is the minimum cross-skill release suite. It contains at least five positive cases and three negative or refusal-boundary cases. Each standalone skill also bundles:
+
+- `evals/evals.json` for task behavior;
+- `evals/trigger-evals.json` for description routing, with at least five positive and three negative queries.
+
+The shared suite lives outside `skills/` so an OpenAI skills-only upload sees only valid immediate skill directories there. These are fixtures for model evaluation. Their presence does not mean a native host has passed them; record those results separately when the release candidate is tested.
diff --git a/skills/evals/publishing-cases.json b/plugin-evals/publishing-cases.json
similarity index 100%
rename from skills/evals/publishing-cases.json
rename to plugin-evals/publishing-cases.json
diff --git a/plugin.json b/plugin.json
new file mode 100644
index 0000000000..34611a4f95
--- /dev/null
+++ b/plugin.json
@@ -0,0 +1,42 @@
+{
+ "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
+ "name": "pascal-agent-skills",
+ "version": "0.1.5",
+ "description": "Create, inspect, validate, and assess furniture layouts with bounded next actions in Pascal through MCP.",
+ "author": {
+ "name": "Pascal",
+ "email": "support@pascal.app",
+ "url": "https://pascal.app"
+ },
+ "homepage": "https://editor.pascal.app/docs/developers/mcp",
+ "repository": "https://github.com/pascalorg/editor",
+ "license": "MIT",
+ "keywords": ["pascal", "3d", "architecture", "mcp", "furniture", "spatial"],
+ "extensions": {
+ "com.openai": {
+ "interface": {
+ "displayName": "Pascal agent skills",
+ "shortDescription": "Build 3D scenes and check fit",
+ "longDescription": "Use Pascal's MCP tools to work with editable building scenes, validate and save results, and produce bounded furniture footprint reports with explicit evidence, limitations, and blocker-aware next actions.",
+ "developerName": "Pascal",
+ "category": "Developer Tools",
+ "capabilities": [
+ "Build and edit 3D scenes",
+ "Validate spatial layouts",
+ "Assess furniture footprints"
+ ],
+ "websiteURL": "https://editor.pascal.app/docs/developers/mcp",
+ "supportURL": "https://github.com/pascalorg/editor/issues",
+ "privacyPolicyURL": "https://editor.pascal.app/privacy",
+ "termsOfServiceURL": "https://editor.pascal.app/terms",
+ "defaultPrompt": [
+ "Use Pascal to inspect this building project, make the requested bounded edit, validate it, save it, and return the editor URL.",
+ "Check whether this furniture footprint fits in a measured Pascal room, including rotations, collisions, and door access."
+ ],
+ "brandColor": "#171717",
+ "composerIcon": "./assets/pascal-mark.svg",
+ "logo": "./assets/pascal-mark.svg"
+ }
+ }
+ }
+}
diff --git a/scripts/validate-skills.ts b/scripts/validate-skills.ts
index 896aaebc87..8ebe305c9d 100644
--- a/scripts/validate-skills.ts
+++ b/scripts/validate-skills.ts
@@ -5,7 +5,15 @@ import { fileURLToPath } from 'node:url'
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..')
const skillNames = ['pascal-3d', 'furniture-fit'] as const
const skillVersions = { 'pascal-3d': '0.1.0', 'furniture-fit': '0.1.3' } as const
-const pluginVersion = '0.1.4'
+const pluginVersion = '0.1.5'
+const portablePluginSchema = 'https://agent-plugins.org/schemas/1.0.0/plugin.schema.json'
+const openAiListingLimits = {
+ displayName: 30,
+ shortDescription: 30,
+ longDescription: 4000,
+ developerName: 80,
+} as const
+const openAiDefaultPromptLimit = 128
const furnitureNextActionKinds = [
'request_measurement',
'check_alternate_pose',
@@ -81,6 +89,12 @@ function walk(path: string): string[] {
return files
}
+for (const entry of readdirSync(join(root, 'skills'), { withFileTypes: true })) {
+ if (entry.isDirectory() && !skillNames.includes(entry.name as (typeof skillNames)[number])) {
+ fail(`OpenAI skills directory contains an unexpected non-skill directory: ${entry.name}`)
+ }
+}
+
for (const skillName of skillNames) {
const skillRoot = join(root, 'skills', skillName)
const skillFile = join(skillRoot, 'SKILL.md')
@@ -421,7 +435,7 @@ for (const [semanticCase, requirement] of requiredSemanticCases) {
}
}
-const publishingFile = join(root, 'skills', 'evals', 'publishing-cases.json')
+const publishingFile = join(root, 'plugin-evals', 'publishing-cases.json')
const publishing = parseJson(publishingFile) as {
cases?: Array<{
id?: unknown
@@ -458,6 +472,7 @@ if (negativePublishingCases < 3) fail('Publishing suite needs at least 3 negativ
const claudePlugin = parseJson(join(root, '.claude-plugin', 'plugin.json'))
const claudeMarketplace = parseJson(join(root, '.claude-plugin', 'marketplace.json'))
+const portablePlugin = parseJson(join(root, 'plugin.json'))
const codexPlugin = parseJson(join(root, '.codex-plugin', 'plugin.json'))
const codexMarketplace = parseJson(join(root, '.agents', 'plugins', 'marketplace.json'))
@@ -469,6 +484,78 @@ for (const [label, manifest] of [
if (manifest.version !== pluginVersion) fail(`${label}: version must be ${pluginVersion}`)
}
+if (portablePlugin.$schema !== portablePluginSchema) {
+ fail(`Portable plugin must declare ${portablePluginSchema}`)
+}
+if (portablePlugin.name !== codexPlugin.name) fail('Portable and Codex plugin names must match')
+if (portablePlugin.version !== pluginVersion) {
+ fail(`Portable plugin version must be ${pluginVersion}`)
+}
+if (portablePlugin.description !== codexPlugin.description) {
+ fail('Portable and Codex plugin descriptions must match')
+}
+const extensions = portablePlugin.extensions as Record | undefined
+const openAiExtension = extensions?.['com.openai'] as Record | undefined
+const portableInterface = openAiExtension?.interface as Record | undefined
+const codexInterface = codexPlugin.interface as Record | undefined
+if (!portableInterface) fail('Portable plugin must declare extensions.com.openai.interface')
+if (JSON.stringify(portableInterface) !== JSON.stringify(codexInterface)) {
+ fail('Portable and Codex OpenAI interfaces must match')
+}
+
+for (const [field, limit] of Object.entries(openAiListingLimits)) {
+ const value = portableInterface?.[field]
+ const mustBeSingleLine = field !== 'longDescription'
+ if (
+ typeof value !== 'string' ||
+ !value ||
+ (mustBeSingleLine && value.includes('\n')) ||
+ value.length > limit
+ ) {
+ fail(
+ `OpenAI ${field} must be non-empty${mustBeSingleLine ? ', single-line,' : ''} and at most ${limit} characters`,
+ )
+ }
+}
+const defaultPrompts = portableInterface?.defaultPrompt
+if (!Array.isArray(defaultPrompts) || defaultPrompts.length === 0 || defaultPrompts.length > 3) {
+ fail('OpenAI defaultPrompt must contain between 1 and 3 prompts')
+} else {
+ for (const prompt of defaultPrompts) {
+ if (
+ typeof prompt !== 'string' ||
+ !prompt ||
+ prompt.includes('\n') ||
+ prompt.length > openAiDefaultPromptLimit ||
+ prompt.includes('@')
+ ) {
+ fail(
+ `OpenAI default prompts must be non-empty single lines of at most ${openAiDefaultPromptLimit} characters without @mentions`,
+ )
+ }
+ }
+}
+for (const field of ['websiteURL', 'supportURL', 'privacyPolicyURL', 'termsOfServiceURL']) {
+ const value = portableInterface?.[field]
+ if (typeof value !== 'string' || !value.startsWith('https://')) {
+ fail(`OpenAI ${field} must be an HTTPS URL`)
+ }
+}
+for (const field of ['composerIcon', 'logo']) {
+ const value = portableInterface?.[field]
+ if (typeof value !== 'string' || !value.startsWith('./')) {
+ fail(`OpenAI ${field} must be a plugin-relative path starting with ./`)
+ continue
+ }
+ const asset = resolve(root, value)
+ if (!asset.startsWith(`${root}/`) || !existsSync(asset)) {
+ fail(`OpenAI ${field} must reference an existing file inside the plugin`)
+ }
+}
+if ('screenshots' in (portableInterface ?? {})) {
+ fail('Skills-only OpenAI plugin must not declare screenshots')
+}
+
if (codexPlugin.skills !== './skills/') fail('Codex plugin must point to canonical ./skills/')
if (codexMarketplace.name !== 'pascal') fail('Codex marketplace name must be pascal')
const codexEntries = codexMarketplace.plugins
@@ -525,5 +612,5 @@ if (failures.length > 0) {
}
console.log(
- `Validated ${skillNames.length} skills (${skillNames.map((name) => `${name}@${skillVersions[name]}`).join(', ')}) and both plugin manifests at ${pluginVersion}.`,
+ `Validated ${skillNames.length} skills (${skillNames.map((name) => `${name}@${skillVersions[name]}`).join(', ')}) and portable, Codex, and Claude plugin manifests at ${pluginVersion}.`,
)
diff --git a/skills/README.md b/skills/README.md
index 1ac7ce35ca..379a554697 100644
--- a/skills/README.md
+++ b/skills/README.md
@@ -44,6 +44,8 @@ codex plugin add pascal-agent-skills@pascal
The plugin installs the instructions from the canonical `skills/` directory. Connect Pascal MCP separately by following the setup reference included in either skill. Installation alone never creates an account, uploads a project, or authorizes paid work.
+The root [`plugin.json`](../plugin.json) is the portable Agent Plugins manifest used for OpenAI submission. The repository keeps `.codex-plugin/plugin.json` as a compatibility fallback and validates that both expose the same OpenAI listing metadata. Public-directory submission, review, and publication are separate external steps; a Git marketplace install does not make the plugin publicly listed in ChatGPT or Codex.
+
The npm `beta` CLI remains on the older runtime contract. For the read-only `check_collisions.candidate` capability used by the current furniture workflow, follow the checksum-verified [GitHub preview instructions](pascal-3d/references/setup.md#candidate-enabled-github-preview). The preview archive is published on GitHub, not npm.
Use one active agent client per local CLI service. Its standalone HTTP runtime shares active scene state; the hosted endpoint uses a separate session-isolated bridge.
@@ -66,4 +68,4 @@ bun scripts/validate-skills.ts
claude plugin validate . --strict
```
-The repository validator checks frontmatter, bundled links, task and trigger fixtures, semantic furniture next-action decision cases, the publishing suite, manifest consistency, and accidental private-path or credential leakage.
+The repository validator checks frontmatter, bundled links, task and trigger fixtures, semantic furniture next-action decision cases, the publishing suite, portable and compatibility manifest consistency, OpenAI public-directory metadata limits, bundled branding assets, and accidental private-path or credential leakage.
diff --git a/skills/VALIDATION.md b/skills/VALIDATION.md
index d44c84db04..9a211149a5 100644
--- a/skills/VALIDATION.md
+++ b/skills/VALIDATION.md
@@ -1,6 +1,12 @@
# Skill package validation
-Release package source: **0.1.4**. `pascal-3d` skill metadata version: **0.1.0**. `furniture-fit` skill metadata version: **0.1.3**. Recorded September 9, 2026.
+Candidate package source: **0.1.5**. Latest released package source: **0.1.4**. `pascal-3d` skill metadata version: **0.1.0**. `furniture-fit` skill metadata version: **0.1.3**. Recorded September 9, 2026.
+
+## Bundle 0.1.5 OpenAI submission readiness candidate
+
+This candidate adds the portable root Agent Plugins manifest while retaining the Codex compatibility manifest and Claude marketplace package. Its OpenAI listing metadata fits the final public-directory limits: display and short-description copy are at most 30 characters, starter prompts are at most 128 characters, and the package supplies a square bundled logo asset plus public website, support, privacy, and terms URLs. The repository validator keeps the portable and compatibility OpenAI interfaces identical and rejects screenshots for this skills-only package. Shared publishing fixtures moved out of `skills/`, leaving only valid immediate skill directories for OpenAI archive ingestion.
+
+These source checks do not submit or publish the plugin. OpenAI Platform access, a verified matching developer or business identity, portal review, country selection, policy attestations, and the developer's separate publish action remain external requirements. The existing publishing suite provides more than the required five positive and three negative cases, but the submitter must enter reviewer-ready cases and release notes in the portal and resolve any automated skill-scan findings.
## Bundle 0.1.4 release source
diff --git a/skills/evals/README.md b/skills/evals/README.md
deleted file mode 100644
index 1eaec5ef4c..0000000000
--- a/skills/evals/README.md
+++ /dev/null
@@ -1,8 +0,0 @@
-# Publishing evaluation fixtures
-
-`publishing-cases.json` is the minimum cross-skill release suite. It contains five positive cases and three negative or refusal-boundary cases. Each standalone skill also bundles:
-
-- `evals/evals.json` for task behavior;
-- `evals/trigger-evals.json` for description routing, with at least five positive and three negative queries.
-
-These are fixtures for model evaluation. Their presence does not mean a native host has passed them; record those results separately when the release candidate is tested.