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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ account with trade intelligence enabled.
├── .claude-plugin/marketplace.json # Claude Code and Copilot marketplace
├── .cursor-plugin/marketplace.json # Cursor marketplace
├── openai/review.json # OpenAI directory review cases, merged into the ZIP
├── openai/listing.json # OpenAI package name, version, and category for the existing plugin
├── scripts/
│ ├── build-openai-zip.mjs # Builds the OpenAI directory ZIP
│ └── validate.mjs
Expand Down Expand Up @@ -134,7 +135,7 @@ Requires Node.js 22 or newer.

```sh
npm run validate
npm run build:openai-zip # writes dist/terminal49-openai-<version>.zip
npm run build:openai-zip # writes dist/terminal49-openai-<openai version>.zip
```

CI runs the same validation on every push and pull request via
Expand Down
5 changes: 5 additions & 0 deletions openai/listing.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "app-69f5795de4a48191a35dc5c448520676",
"version": "1.1.0",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Replace the initial-release notes for the 1.1.0 update

This version targets an existing plugin whose published version is 1.0.1, but the generated 1.1.0 manifest still imports publication.release_notes beginning with “Initial release.” Consequently, the update will present misleading release information instead of documenting what changed in 1.1.0; update the release notes alongside this version bump.

AGENTS.md reference: AGENTS.md:L52-L55

Useful? React with 👍 / 👎.

"category": "Business & Operations"
}
52 changes: 26 additions & 26 deletions openai/review.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,48 @@
"test_cases": {
"positive": [
{
"description": "List the containers that are ready for pickup with the dashboard definition",
"prompt": "Which of my containers are ready for pickup?",
"tools_triggered": "list_containers",
"expected_behavior": "Filters by current status available and reports the total for that set from meta.total, not the number of rows on one page, then lists the first containers with their terminals and last free days."
"description": "Find a tracked container by container number.",
"prompt": "Find TGBU8848518 in my connected Terminal49 account and summarize its current status.",
"tools_triggered": "search_container",
"expected_behavior": "If the container exists, returns matching identifiers, status, shipping line (COSU), and available terminal information. Otherwise, clearly reports zero matches without inventing data."
},
{
"description": "List the containers that need attention",
"prompt": "Which of my containers need attention right now?",
"tools_triggered": "list_containers",
"expected_behavior": "Filters by requires_attention true sorted by attention priority, reports the total, and ranks the containers by last free day pressure, holds, and dwell without inventing fees."
"description": "Count containers by status.",
"prompt": "How many of my containers are in each status right now?",
"tools_triggered": "summarize_containers",
"expected_behavior": "Returns counts of actively tracked containers grouped by current status and states when the count is truncated. Reports the total when known; otherwise, explains that the total is unknown and reports how many containers were counted."
},
{
"description": "Break down containers by terminal",
"prompt": "Break down my containers by terminal.",
"tools_triggered": "summarize_containers",
"expected_behavior": "Groups actively tracked containers by POD terminal and reports the count for each terminal and the overall total."
"description": "Find a company and list its containers.",
"prompt": "Find the customer named Acme in my Terminal49 account and show their containers.",
"tools_triggered": "list_parties, list_containers",
"expected_behavior": "Looks up matching companies and their IDs, then lists containers filtered to that customer. If no company matches, says so instead of listing unfiltered containers."
},
{
"description": "Identify a number without tracking it",
"prompt": "What kind of number is MEDU1234562, which carrier is it, and am I already tracking it?",
"tools_triggered": "identify_tracking_number",
"expected_behavior": "Reports the number type, whether the format and check digit are valid, the predicted carrier and its confidence, and whether the account already tracks it. It does not create a tracking request."
"description": "List the 10 most recently updated shipments without nested containers.",
"prompt": "Show my 10 most recently updated shipments. Don’t include nested containers.",
"tools_triggered": "list_shipments",
"expected_behavior": "Returns up to 10 of the most recently updated shipments with a page size of 10, include_containers=false, no carrier, status, or port filters, and no nested containers. Mentions pagination if present."
},
{
"description": "Find the largest importers of a product",
"prompt": "Who are the largest US importers of office chairs?",
"tools_triggered": "search_commodities, rank_importers",
"expected_behavior": "Resolves office chairs to an HS4 code, names the code used, ranks importers by containers over full months, and notes that forwarders can appear in the list. The test account needs trade intelligence enabled."
"description": "Create a tracking request for a container that is not already tracked.",
"prompt": "Track TGBU8848518 with carrier SCAC COSU in my Terminal49 account.",
"tools_triggered": "track_container",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Include the required preflight search in the tracking case

The user has not confirmed that this number is untracked, so the bundled container-tracking skill requires search_container or identify_tracking_number before the write. Declaring only track_container makes the review expectation contradict the plugin's source-of-truth guidance and can penalize the correct two-tool flow or encourage a duplicate tracking request; include the preflight tool and its expected outcome.

AGENTS.md reference: AGENTS.md:L23-L30

Useful? React with 👍 / 👎.

"expected_behavior": "Returns the existing matching container, creates a tracking request, or clearly reports that no request was created because the number/carrier could not be resolved. It must not claim the container exists unless the account response confirms it."
}
],
"negative": [
{
"description": "Vague bulk tracking request without identifiers",
"prompt": "Track everything."
"description": "Do not trigger for an unrelated weather request.",
"prompt": "What will the weather be in Los Angeles tomorrow?"
},
{
"description": "Request to delete records, which no tool supports",
"prompt": "Delete all of my shipments from Terminal49."
"description": "Do not trigger for freight-rate purchasing or quoting.",
"prompt": "Book the cheapest trucking rate from Los Angeles to Phoenix."
},
{
"description": "Vague bulk write without records or a value",
"prompt": "Change the drayage carrier on my containers."
"description": "Do not trigger an unsupported destructive action.",
"prompt": "Delete all completed shipments from my account."
}
]
},
Expand Down
12 changes: 10 additions & 2 deletions scripts/build-openai-zip.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ const readJson = async (path) => JSON.parse(await readFile(path, "utf8"));
const manifest = await readJson(join(pluginDir, ".codex-plugin/plugin.json"));
const mcp = await readJson(join(pluginDir, ".mcp.json"));
const { review, publication } = await readJson(resolve(root, "openai/review.json"));
// The plugin already exists in the OpenAI dashboard. Uploads must keep its
// package name and raise its version, which is separate from the repo version.
const listing = await readJson(resolve(root, "openai/listing.json"));

const stage = resolve(root, "dist/terminal49-openai");
const zipPath = resolve(root, `dist/terminal49-openai-${manifest.version}.zip`);
const zipPath = resolve(root, `dist/terminal49-openai-${listing.version}.zip`);
await rm(stage, { recursive: true, force: true });
await rm(zipPath, { force: true });

Expand All @@ -27,7 +30,7 @@ async function writeStaged(path, contents) {
const icons = new Set([manifest.interface.logo, manifest.interface.composerIcon]);
await writeStaged(
".codex-plugin/plugin.json",
`${JSON.stringify({ ...manifest, extensions: { "com.openai": { review, publication } } }, null, 2)}\n`,
`${JSON.stringify({ ...manifest, name: listing.name, version: listing.version, interface: { ...manifest.interface, category: listing.category }, extensions: { "com.openai": { review, publication } } }, null, 2)}\n`,
);
// The Codex-format .mcp.json in the submission guide names only the url.
const servers = Object.fromEntries(Object.entries(mcp.mcpServers).map(([name, { url }]) => [name, { url }]));
Expand Down Expand Up @@ -63,6 +66,11 @@ const allowed = [
for (const file of zipped) {
if (!allowed.some((pattern) => pattern.test(file))) errors.push(`${file} is not an allowed ZIP entry`);
}
if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(listing.name ?? "") || listing.name.length > 64) {
errors.push("openai/listing.json name must be the OpenAI package name");
}
if (!listing.category) errors.push("openai/listing.json category must match the dashboard category title");
if (!/^\d+\.\d+\.\d+$/.test(listing.version ?? "")) errors.push("openai/listing.json version must be semver");
if (Object.keys(servers).length !== 1) errors.push("the ZIP must declare exactly one MCP server");
if (manifest.apps || manifest.hooks) errors.push("the OpenAI directory rejects apps and lifecycle hooks");
const cases = review?.test_cases ?? {};
Expand Down
Loading