Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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"]
}
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
21 changes: 15 additions & 6 deletions .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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"
}
}
5 changes: 5 additions & 0 deletions assets/pascal-mark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions plugin-evals/README.md
Original file line number Diff line number Diff line change
@@ -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.
File renamed without changes.
42 changes: 42 additions & 0 deletions plugin.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
}
93 changes: 90 additions & 3 deletions scripts/validate-skills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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'))

Expand All @@ -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<string, unknown> | undefined
const openAiExtension = extensions?.['com.openai'] as Record<string, unknown> | undefined
const portableInterface = openAiExtension?.interface as Record<string, unknown> | undefined
const codexInterface = codexPlugin.interface as Record<string, unknown> | 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
Expand Down Expand Up @@ -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}.`,
)
4 changes: 3 additions & 1 deletion skills/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
8 changes: 7 additions & 1 deletion skills/VALIDATION.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
8 changes: 0 additions & 8 deletions skills/evals/README.md

This file was deleted.

Loading