From af62504532faa35112c7f68438303e0e068c3282 Mon Sep 17 00:00:00 2001 From: Hoa Dang <70422390+hoavdc@users.noreply.github.com> Date: Sun, 11 Oct 2026 19:45:03 +0700 Subject: [PATCH 01/24] feat(work): add scripts/build-work-plugins.mjs --- scripts/build-work-plugins.mjs | 289 +++++++++++++++++++++++++++++++++ 1 file changed, 289 insertions(+) create mode 100644 scripts/build-work-plugins.mjs diff --git a/scripts/build-work-plugins.mjs b/scripts/build-work-plugins.mjs new file mode 100644 index 0000000..57e0c51 --- /dev/null +++ b/scripts/build-work-plugins.mjs @@ -0,0 +1,289 @@ +import { promises as fs } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const groupsFile = path.join(root, "config", "chatgpt-work-groups.json"); +const outRoot = path.join(root, "dist", "work"); +const packageJson = JSON.parse(await fs.readFile(path.join(root, "package.json"), "utf8")); +const groupConfig = JSON.parse(await fs.readFile(groupsFile, "utf8")); +const finder = await fs.readFile(path.join(root, "skill-finder.md"), "utf8"); + +function fail(message) { + throw new Error(message); +} + +function parseSkillFrontmatter(text, skillDirName) { + const match = text.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/); + if (!match) fail(`${skillDirName}/SKILL.md is missing a YAML frontmatter block.`); + const lines = match[1].split(/\r?\n/); + const values = new Map(); + for (const line of lines) { + const field = line.match(/^([a-z][a-z0-9-]*):\s*(.*)$/); + if (field) values.set(field[1], field[2]); + } + const name = unquote(values.get("name") ?? ""); + const description = unquote(values.get("description") ?? ""); + if (!name || !description) fail(`${skillDirName}/SKILL.md needs name and description fields.`); + if (name !== skillDirName) fail(`${skillDirName}/SKILL.md name must match its directory (found ${name}).`); + if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name) || name.length > 64) fail(`${skillDirName} has a non-standard skill name.`); + if (description.length > 1024 || /[<>]/.test(description)) fail(`${skillDirName} description must be <= 1024 characters and contain no angle brackets.`); + if (values.has("metadata")) fail(`${skillDirName} already has a metadata field; merge custom fields before exporting.`); + + const custom = ["version", "category"].filter((key) => values.has(key)); + const allowed = new Set(["name", "description", "license", "compatibility", "allowed-tools", ...custom]); + for (const key of values.keys()) if (!allowed.has(key)) fail(`${skillDirName} has unsupported top-level frontmatter field: ${key}`); + + const output = lines.filter((line) => !/^(version|category):\s*/.test(line)); + if (!values.has("license")) output.push("license: MIT"); + if (!values.has("compatibility")) output.push('compatibility: "ChatGPT Work and Codex plugin surfaces; availability varies by product and workspace."'); + if (custom.length) { + output.push("metadata:"); + for (const key of custom) output.push(` ${key}: ${JSON.stringify(unquote(values.get(key)))}`); + } + return { name, description, text: `---\n${output.join("\n")}\n---\n${text.slice(match[0].length)}` }; +} + +function unquote(value) { + const v = value.trim(); + if (v.length >= 2 && ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'")))) return v.slice(1, -1); + return v; +} + +function getGroupedSkills(markdown) { + const groups = new Map(); + let heading = null; + for (const line of markdown.split(/\r?\n/)) { + const h = line.match(/^## (.+)$/); + if (h) heading = h[1]; + const row = line.match(/^\|\s*`(codexkit-[a-z0-9-]+)`\s*\|/); + if (row && groupConfig[heading]) { + if (!groups.has(heading)) groups.set(heading, []); + groups.get(heading).push(row[1]); + } + } + return groups; +} + +function crc32(buffer) { + let crc = 0xffffffff; + for (const byte of buffer) { + crc ^= byte; + for (let bit = 0; bit < 8; bit++) crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0); + } + return (crc ^ 0xffffffff) >>> 0; +} + +function dosDateTime(date = new Date()) { + const time = (date.getHours() << 11) | (date.getMinutes() << 5) | Math.floor(date.getSeconds() / 2); + const day = Math.max(1, date.getDate()); + const dosDate = ((Math.max(1980, date.getFullYear()) - 1980) << 9) | ((date.getMonth() + 1) << 5) | day; + return { time, date: dosDate }; +} + +function makeZip(entries) { + const localParts = []; + const centralParts = []; + let offset = 0; + const { time, date } = dosDateTime(); + for (const entry of entries) { + const name = Buffer.from(entry.name.replaceAll(path.sep, "/"), "utf8"); + const data = Buffer.isBuffer(entry.data) ? entry.data : Buffer.from(entry.data); + const crc = crc32(data); + const local = Buffer.alloc(30); + local.writeUInt32LE(0x04034b50, 0); + local.writeUInt16LE(20, 4); + local.writeUInt16LE(0x0800, 6); + local.writeUInt16LE(0, 8); + local.writeUInt16LE(time, 10); + local.writeUInt16LE(date, 12); + local.writeUInt32LE(crc, 14); + local.writeUInt32LE(data.length, 18); + local.writeUInt32LE(data.length, 22); + local.writeUInt16LE(name.length, 26); + local.writeUInt16LE(0, 28); + localParts.push(local, name, data); + + const central = Buffer.alloc(46); + central.writeUInt32LE(0x02014b50, 0); + central.writeUInt16LE(20, 4); + central.writeUInt16LE(20, 6); + central.writeUInt16LE(0x0800, 8); + central.writeUInt16LE(0, 10); + central.writeUInt16LE(time, 12); + central.writeUInt16LE(date, 14); + central.writeUInt32LE(crc, 16); + central.writeUInt32LE(data.length, 20); + central.writeUInt32LE(data.length, 24); + central.writeUInt16LE(name.length, 28); + central.writeUInt16LE(0, 30); + central.writeUInt16LE(0, 32); + central.writeUInt16LE(0, 34); + central.writeUInt16LE(0, 36); + central.writeUInt32LE(0, 38); + central.writeUInt32LE(offset, 42); + centralParts.push(central, name); + offset += local.length + name.length + data.length; + } + const centralDirectory = Buffer.concat(centralParts); + const end = Buffer.alloc(22); + end.writeUInt32LE(0x06054b50, 0); + end.writeUInt16LE(0, 4); + end.writeUInt16LE(0, 6); + end.writeUInt16LE(entries.length, 8); + end.writeUInt16LE(entries.length, 10); + end.writeUInt32LE(centralDirectory.length, 12); + end.writeUInt32LE(offset, 16); + end.writeUInt16LE(0, 20); + return Buffer.concat([...localParts, centralDirectory, end]); +} + +async function listFiles(directory) { + const found = []; + for (const entry of await fs.readdir(directory, { withFileTypes: true })) { + const full = path.join(directory, entry.name); + if (entry.isDirectory()) found.push(...await listFiles(full)); + else if (entry.isFile()) found.push(full); + } + return found.sort(); +} + +async function copySkill(skillId, destination) { + const source = path.join(root, "skills", skillId); + const files = await listFiles(source); + if (!files.some((file) => path.basename(file).toLowerCase() === "skill.md")) fail(`No SKILL.md found for ${skillId}.`); + for (const file of files) { + const relative = path.relative(source, file); + const target = path.join(destination, relative); + await fs.mkdir(path.dirname(target), { recursive: true }); + if (path.basename(file).toLowerCase() === "skill.md") { + const parsed = parseSkillFrontmatter(await fs.readFile(file, "utf8"), skillId); + await fs.writeFile(target, parsed.text, "utf8"); + } else { + await fs.copyFile(file, target); + } + } + return files; +} + +function readZipEntryNames(buffer) { + let endOffset = -1; + for (let i = buffer.length - 22; i >= Math.max(0, buffer.length - 65557); i--) { + if (buffer.readUInt32LE(i) === 0x06054b50) { endOffset = i; break; } + } + if (endOffset < 0) fail("Generated ZIP is missing its end record."); + const count = buffer.readUInt16LE(endOffset + 10); + let cursor = buffer.readUInt32LE(endOffset + 16); + const names = []; + for (let i = 0; i < count; i++) { + if (buffer.readUInt32LE(cursor) !== 0x02014b50) fail("Generated ZIP has a malformed central directory."); + const nameLength = buffer.readUInt16LE(cursor + 28); + const extraLength = buffer.readUInt16LE(cursor + 30); + const commentLength = buffer.readUInt16LE(cursor + 32); + names.push(buffer.subarray(cursor + 46, cursor + 46 + nameLength).toString("utf8")); + cursor += 46 + nameLength + extraLength + commentLength; + } + return names; +} + +await fs.rm(outRoot, { recursive: true, force: true }); +await fs.mkdir(outRoot, { recursive: true }); +const sourceDirs = (await fs.readdir(path.join(root, "skills"), { withFileTypes: true })) + .filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort(); +const grouped = getGroupedSkills(finder); +const assigned = [...grouped.values()].flat(); +const duplicates = assigned.filter((id, i) => assigned.indexOf(id) !== i); +if (duplicates.length) fail(`Skills appear in multiple Work groups: ${[...new Set(duplicates)].join(", ")}`); +const missing = sourceDirs.filter((id) => !assigned.includes(id)); +const unknown = assigned.filter((id) => !sourceDirs.includes(id)); +if (missing.length || unknown.length) fail(`Group mismatch. Missing: ${missing.join(", ") || "none"}; unknown: ${unknown.join(", ") || "none"}`); +for (const heading of Object.keys(groupConfig)) if (!(grouped.get(heading)?.length)) fail(`No skills found under ${heading} in skill-finder.md.`); + +const pluginsRoot = path.join(outRoot, "plugins"); +const pluginZipRoot = path.join(outRoot, "plugin-zips"); +const skillZipRoot = path.join(outRoot, "skill-zips"); +await Promise.all([fs.mkdir(pluginsRoot, { recursive: true }), fs.mkdir(pluginZipRoot, { recursive: true }), fs.mkdir(skillZipRoot, { recursive: true })]); +const marketplacePlugins = []; +const pluginSummary = []; + +for (const [heading, config] of Object.entries(groupConfig)) { + const skillIds = grouped.get(heading); + const pluginDir = path.join(pluginsRoot, config.slug); + const skillsRoot = path.join(pluginDir, "skills"); + await fs.mkdir(skillsRoot, { recursive: true }); + const pluginManifest = { + "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", + name: `codexkit-${config.slug}`, + version: packageJson.version, + description: config.description, + author: { name: "Hoa Dang", url: "https://github.com/hoavdc" }, + homepage: "https://codexkit.pages.dev", + repository: "https://github.com/hoavdc/CodexKit", + license: "MIT", + keywords: ["codexkit", ...config.keywords], + extensions: { + "com.openai": { + interface: { + displayName: config.displayName, + shortDescription: config.shortDescription, + longDescription: `${config.description} Includes ${skillIds.length} focused CodexKit skills. This skills-only plugin does not connect to external services.` + } + } + } + }; + await fs.writeFile(path.join(pluginDir, "plugin.json"), `${JSON.stringify(pluginManifest, null, 2)}\n`, "utf8"); + for (const skillId of skillIds) { + const skillDestination = path.join(skillsRoot, skillId); + const files = await copySkill(skillId, skillDestination); + const zipEntries = []; + for (const file of files) { + const relative = path.relative(path.join(root, "skills", skillId), file).split(path.sep).join("/"); + const data = path.basename(file).toLowerCase() === "skill.md" + ? await fs.readFile(path.join(skillDestination, relative)) + : await fs.readFile(file); + zipEntries.push({ name: `${skillId}/${relative}`, data }); + } + const skillZip = makeZip(zipEntries); + const skillZipPath = path.join(skillZipRoot, `${skillId}.zip`); + await fs.writeFile(skillZipPath, skillZip); + const skillNames = readZipEntryNames(skillZip); + if (!skillNames.includes(`${skillId}/SKILL.md`)) fail(`Skill ZIP missing manifest: ${skillId}`); + } + const pluginFiles = await listFiles(pluginDir); + const pluginEntries = []; + for (const file of pluginFiles) { + pluginEntries.push({ name: path.relative(pluginDir, file).split(path.sep).join("/"), data: await fs.readFile(file) }); + } + const pluginZip = makeZip(pluginEntries); + const pluginZipPath = path.join(pluginZipRoot, `${pluginManifest.name}.zip`); + await fs.writeFile(pluginZipPath, pluginZip); + const pluginNames = readZipEntryNames(pluginZip); + if (!pluginNames.includes("plugin.json") || !pluginNames.includes(`skills/${skillIds[0]}/SKILL.md`)) fail(`Plugin ZIP structure invalid: ${config.slug}`); + + marketplacePlugins.push({ + name: pluginManifest.name, + source: { source: "local", path: `./dist/work/plugins/${config.slug}` }, + policy: { installation: "AVAILABLE", authentication: "ON_INSTALL" }, + category: "Productivity" + }); + pluginSummary.push({ name: pluginManifest.name, group: heading, skillCount: skillIds.length, skillIds, zip: `plugin-zips/${pluginManifest.name}.zip` }); +} + +const marketplace = { + name: "codexkit-work", + interface: { displayName: "CodexKit Work" }, + plugins: marketplacePlugins +}; +const marketplacePath = path.join(root, ".agents", "plugins", "marketplace.json"); +await fs.mkdir(path.dirname(marketplacePath), { recursive: true }); +await fs.writeFile(marketplacePath, `${JSON.stringify(marketplace, null, 2)}\n`, "utf8"); +const report = { + sourceVersion: packageJson.version, + pluginCount: pluginSummary.length, + skillCount: sourceDirs.length, + generatedAt: new Date().toISOString(), + plugins: pluginSummary +}; +await fs.writeFile(path.join(outRoot, "build-report.json"), `${JSON.stringify(report, null, 2)}\n`, "utf8"); +console.log(`Built ${pluginSummary.length} plugin packages and ${sourceDirs.length} individual skill ZIPs in dist/work.`); +console.log(`Generated repo marketplace: ${path.relative(root, marketplacePath)}`); From f3205067e996eca25263af0014ac4d674f24993d Mon Sep 17 00:00:00 2001 From: Hoa Dang <70422390+hoavdc@users.noreply.github.com> Date: Sun, 11 Oct 2026 19:45:11 +0700 Subject: [PATCH 02/24] feat(work): add scripts/test-work-plugins.mjs --- scripts/test-work-plugins.mjs | 65 +++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 scripts/test-work-plugins.mjs diff --git a/scripts/test-work-plugins.mjs b/scripts/test-work-plugins.mjs new file mode 100644 index 0000000..db7e66f --- /dev/null +++ b/scripts/test-work-plugins.mjs @@ -0,0 +1,65 @@ +import { promises as fs } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const workRoot = path.join(root, "dist", "work"); +const groups = JSON.parse(await fs.readFile(path.join(root, "tests", "skill-routing-evals.json"), "utf8")); +const marketplace = JSON.parse(await fs.readFile(path.join(root, ".agents", "plugins", "marketplace.json"), "utf8")); +const summary = JSON.parse(await fs.readFile(path.join(workRoot, "build-report.json"), "utf8")); + +function assert(condition, message) { + if (!condition) throw new Error(message); +} + +function zipEntryNames(buffer) { + let end = -1; + for (let i = buffer.length - 22; i >= Math.max(0, buffer.length - 65_557); i -= 1) { + if (buffer.readUInt32LE(i) === 0x06054b50) { end = i; break; } + } + assert(end >= 0, "ZIP end record missing."); + const count = buffer.readUInt16LE(end + 10); + let cursor = buffer.readUInt32LE(end + 16); + const entries = []; + for (let i = 0; i < count; i += 1) { + assert(buffer.readUInt32LE(cursor) === 0x02014b50, "ZIP central directory is malformed."); + const nameLength = buffer.readUInt16LE(cursor + 28); + const extraLength = buffer.readUInt16LE(cursor + 30); + const commentLength = buffer.readUInt16LE(cursor + 32); + entries.push(buffer.subarray(cursor + 46, cursor + 46 + nameLength).toString("utf8")); + cursor += 46 + nameLength + extraLength + commentLength; + } + return entries; +} + +const evalSlugs = groups.groups.map((group) => group.slug); +assert(new Set(evalSlugs).size === evalSlugs.length, "Routing eval group slugs must be unique."); +assert(groups.groups.length === marketplace.plugins.length, "Each plugin group must have routing evals."); +assert(summary.pluginCount === marketplace.plugins.length, "Marketplace and build report plugin counts differ."); +assert(summary.skillCount > 0, "No source skills were exported."); + +for (const group of groups.groups) { + assert(group.positive.length > 0 && group.negative.length > 0, `${group.slug} needs positive and negative prompts.`); + for (const prompt of [...group.positive, ...group.negative]) { + assert(typeof prompt === "string" && prompt.trim().length >= 20, `${group.slug} has an empty or too-short prompt.`); + } + const plugin = marketplace.plugins.find((item) => item.source.path.endsWith(`/${group.slug}`)); + assert(plugin, `Marketplace is missing ${group.slug}.`); + const pluginDir = path.join(root, "dist", "work", "plugins", group.slug); + const manifest = JSON.parse(await fs.readFile(path.join(pluginDir, "plugin.json"), "utf8")); + assert(manifest.name === plugin.name, `Plugin name mismatch for ${group.slug}.`); + assert((await fs.stat(path.join(pluginDir, "skills"))).isDirectory(), `Plugin ${group.slug} has no skills directory.`); + const zipPath = path.join(workRoot, "plugin-zips", `${manifest.name}.zip`); + const names = zipEntryNames(await fs.readFile(zipPath)); + assert(names.includes("plugin.json"), `Plugin ZIP missing plugin.json for ${group.slug}.`); + assert(names.some((name) => name.startsWith("skills/") && name.endsWith("/SKILL.md")), `Plugin ZIP has no skill for ${group.slug}.`); +} + +for (const skillId of summary.plugins.flatMap((entry) => entry.skillIds)) { + const zipPath = path.join(workRoot, "skill-zips", `${skillId}.zip`); + const names = zipEntryNames(await fs.readFile(zipPath)); + assert(names.includes(`${skillId}/SKILL.md`), `Missing single-skill ZIP for ${skillId}.`); +} + +assert(groups.purpose.includes("not"), "Routing evaluation purpose must describe its limitation."); +console.log(`Work plugin tests passed: ${marketplace.plugins.length} plugin groups, ${summary.skillCount} skills, and ${groups.groups.length} routing fixtures.`); From 5cdb58c486df6d3349b48e5ab21f9c98922e08be Mon Sep 17 00:00:00 2001 From: Hoa Dang <70422390+hoavdc@users.noreply.github.com> Date: Sun, 11 Oct 2026 19:45:19 +0700 Subject: [PATCH 03/24] feat(work): add config/chatgpt-work-groups.json --- config/chatgpt-work-groups.json | 100 ++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 config/chatgpt-work-groups.json diff --git a/config/chatgpt-work-groups.json b/config/chatgpt-work-groups.json new file mode 100644 index 0000000..b8eeb59 --- /dev/null +++ b/config/chatgpt-work-groups.json @@ -0,0 +1,100 @@ +{ + "📬 Daily Office & Communication": { + "slug": "daily-office", + "displayName": "CodexKit: Daily Office", + "description": "Draft and organize everyday work communication, meeting follow-ups, briefings, reports, and presentations.", + "shortDescription": "Clear everyday communication and meeting workflows", + "keywords": ["communication", "meetings", "office", "reports"] + }, + "📊 Project Management": { + "slug": "project-management", + "displayName": "CodexKit: Project Management", + "description": "Plan and govern projects, improve backlogs and team flow, manage risks, and coordinate delivery work.", + "shortDescription": "Practical project and portfolio management workflows", + "keywords": ["project-management", "delivery", "agile", "risk"] + }, + "💰 Finance": { + "slug": "finance", + "displayName": "CodexKit: Finance", + "description": "Explain financial performance, coordinate close, assess statements, and build forecasts and scenarios.", + "shortDescription": "Finance analysis, close, and planning workflows", + "keywords": ["finance", "forecasting", "budget", "analysis"] + }, + "⚖️ Legal & Compliance": { + "slug": "legal-compliance", + "displayName": "CodexKit: Legal & Compliance", + "description": "Structure legal intake, review contracts and compliance gaps, and prepare risk-aware legal work products.", + "shortDescription": "Legal intake, contract, and compliance workflows", + "keywords": ["legal", "compliance", "contracts", "risk"] + }, + "👥 HR & Talent": { + "slug": "hr-talent", + "displayName": "CodexKit: HR & Talent", + "description": "Support hiring, onboarding, performance feedback, interviews, and talent review with structured workflows.", + "shortDescription": "Hiring, onboarding, and talent workflows", + "keywords": ["hr", "hiring", "talent", "onboarding"] + }, + "📈 Strategy & Executive": { + "slug": "strategy-executive", + "displayName": "CodexKit: Strategy", + "description": "Turn market, operating, and performance evidence into strategy options, executive briefs, and scorecards.", + "shortDescription": "Strategy, executive briefs, and scorecards", + "keywords": ["strategy", "executive", "leadership", "analysis"] + }, + "🏭 Operations & Supply Chain": { + "slug": "operations-supply-chain", + "displayName": "CodexKit: Operations", + "description": "Improve operating processes, supplier decisions, S&OP, KPI reviews, and continuous improvement.", + "shortDescription": "Operations and supply chain workflows", + "keywords": ["operations", "supply-chain", "process", "procurement"] + }, + "🎯 Marketing & Customer Success": { + "slug": "marketing-customer-success", + "displayName": "CodexKit: Marketing & CX", + "description": "Plan positioning, go-to-market, campaigns, customer journeys, support, and customer success reviews.", + "shortDescription": "Marketing, customer success, and CX workflows", + "keywords": ["marketing", "customer-success", "cx", "sales"] + }, + "📊 Data & Analytics": { + "slug": "data-analytics", + "displayName": "CodexKit: Data & Analytics", + "description": "Design experiments, analyze survey and KPI data, improve data quality, and create reliable SQL queries.", + "shortDescription": "Reliable analysis and data workflows", + "keywords": ["data", "analytics", "sql", "experiments"] + }, + "🔧 Engineering & Code": { + "slug": "engineering-code", + "displayName": "CodexKit: Engineering", + "description": "Understand repositories, review code and APIs, investigate bugs, harden tests, and improve technical delivery.", + "shortDescription": "Engineering, code review, and repo workflows", + "keywords": ["engineering", "code", "testing", "developer-tools"] + }, + "🖥️ IT & Admin": { + "slug": "it-admin", + "displayName": "CodexKit: IT & Admin", + "description": "Compare vendors and plan organizational change for IT and administrative operations.", + "shortDescription": "IT and administrative decision workflows", + "keywords": ["it", "admin", "vendors", "change-management"] + }, + "🎓 Training & Development": { + "slug": "training-development", + "displayName": "CodexKit: Learning & Development", + "description": "Identify workforce learning needs and design structured workshops and development activities.", + "shortDescription": "Training needs and workshop workflows", + "keywords": ["training", "development", "learning", "workshops"] + }, + "🔗 Cross-Functional": { + "slug": "cross-functional", + "displayName": "CodexKit: Cross-Functional", + "description": "Create decision records, business cases, policy documents, and crisis communications across teams.", + "shortDescription": "Decision, policy, and cross-team workflows", + "keywords": ["decisions", "policy", "business-case", "communication"] + }, + "📐 Skill Authoring": { + "slug": "skill-authoring", + "displayName": "CodexKit: Skill Authoring", + "description": "Author and review focused, reusable CodexKit skills with clear triggers, workflows, and verification.", + "shortDescription": "Create and review reusable skills", + "keywords": ["skills", "authoring", "workflows", "codex"] + } +} From 335a73be0bdee2378192628781fd8cb31e7eaf15 Mon Sep 17 00:00:00 2001 From: Hoa Dang <70422390+hoavdc@users.noreply.github.com> Date: Sun, 11 Oct 2026 19:45:27 +0700 Subject: [PATCH 04/24] feat(work): add docs/chatgpt-work.md --- docs/chatgpt-work.md | 49 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 docs/chatgpt-work.md diff --git a/docs/chatgpt-work.md b/docs/chatgpt-work.md new file mode 100644 index 0000000..1d4962d --- /dev/null +++ b/docs/chatgpt-work.md @@ -0,0 +1,49 @@ +# CodexKit for ChatGPT Work and Codex + +CodexKit keeps its source skills in the repository's existing format. The Work export is generated from those source files so the Codex install flow is not changed. Build it after pulling the latest repository changes: + +```bash +npm run work:build +npm run work:test +``` + +The build creates: + +- `dist/work/skill-zips/`: one portable ZIP per skill, with a single top-level skill directory. +- `dist/work/plugin-zips/`: one ZIP per curated domain plugin. +- `dist/work/plugins/`: unpacked plugin packages for local testing. +- `.agents/plugins/marketplace.json`: a local marketplace manifest referencing the generated plugin directories. + +## Choose an install route + +**For one skill:** use the ChatGPT Skills interface and upload the corresponding ZIP from `dist/work/skill-zips/`. This keeps the installation small and focused. If your account does not show Skills, access may depend on plan, workspace settings, or rollout availability. + +**For a domain bundle in Codex:** add the repository as a plugin marketplace using Codex's supported plugin flow, then install only the plugin you need. The generated marketplace references local paths, so it is intended for a checkout on your machine and is not itself a public hosted marketplace. Restart or refresh the client if the newly installed skills do not appear. + +**For teams:** commit the selected unpacked plugin package to a repository or publish it through the product's supported marketplace process. Do not distribute `.env` files, tokens, or other credentials with a skills package. + +The installation and update behavior differs by route: a copied skill folder is editable and must be refreshed from a newer CodexKit release manually; a marketplace plugin may be managed by the host client. Avoid installing the same skills through multiple routes in the same agent, because duplicate copies make it harder to tell which version was invoked. + +## Keep routing predictable + +A skill's frontmatter description is its trigger contract. Keep it concise and concrete: state the task it handles and the useful input or desired output. Avoid vague descriptions that overlap with many unrelated skills. The generated ZIP keeps each skill's original instructions and converts CodexKit's `version` and `category` fields into standard `metadata` entries for portable consumers. + +CodexKit's source files already mark some skills as explicit-only in `agents/openai.yaml`. Preserve that choice for workflows where accidental invocation would be confusing or risky. For other skills, use descriptions that make the intended request easy to recognize. + +Use two invocation patterns deliberately: + +- **User-invoked:** the user asks for a named workflow or types the skill name. These skills can coordinate a multi-step flow or gather missing setup choices. +- **Model-invoked:** the assistant can select the skill when a request clearly matches its scope. These skills should be narrow, reusable procedures that work as one part of a larger task. + +A user-invoked workflow can call reusable model-invoked procedures; keep the call direction one-way to avoid circular skill chains. Prefer a few focused skills that compose through clear handoffs over one skill that tries to own an entire project lifecycle. + +## Evaluate routing and output + +`tests/skill-routing-evals.json` is a lightweight, human-reviewed set of examples for each Work plugin group. It includes positive examples and near-miss prompts that should not route to that group. The checker verifies coverage and schema; it does not run an LLM or claim to measure model quality. When changing descriptions or group membership: + +1. Update the relevant positive and negative prompts. +2. Run `npm run work:test`. +3. In the target client, manually check whether representative prompts select a useful skill and whether near-misses stay out of scope. +4. Record any observed false activations or missed activations in the PR so future edits can compare against them. + +When a workflow has meaningful failure modes, add examples and verification materials to the skill itself. Keep instructions short enough to follow, and use focused reference files for longer domain material. From eb907b53e8a7e245e89cd19b765cf0a0bf7f114d Mon Sep 17 00:00:00 2001 From: Hoa Dang <70422390+hoavdc@users.noreply.github.com> Date: Sun, 11 Oct 2026 19:45:35 +0700 Subject: [PATCH 05/24] feat(work): add tests/skill-routing-evals.json --- tests/skill-routing-evals.json | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 tests/skill-routing-evals.json diff --git a/tests/skill-routing-evals.json b/tests/skill-routing-evals.json new file mode 100644 index 0000000..ce5b8a9 --- /dev/null +++ b/tests/skill-routing-evals.json @@ -0,0 +1,20 @@ +{ + "schemaVersion": 1, + "purpose": "Human-reviewed routing fixtures; schema and coverage are tested, model behavior is not.", + "groups": [ + { "slug": "daily-office", "positive": ["Summarize these meeting notes and assign owners to each follow-up."], "negative": ["Review this pull request for a race condition."] }, + { "slug": "project-management", "positive": ["Build a risk register and delivery plan for a delayed project."], "negative": ["Explain the cash flow variance in this monthly financial statement."] }, + { "slug": "finance", "positive": ["Explain why actual expenses are above budget and suggest corrective actions."], "negative": ["Draft a hiring scorecard for a senior engineer."] }, + { "slug": "legal-compliance", "positive": ["Review this supplier agreement for unusual obligations and renewal risks."], "negative": ["Create a sprint plan from these backlog items."] }, + { "slug": "hr-talent", "positive": ["Prepare structured interview questions for a product manager role."], "negative": ["Write a SQL query to compare monthly churn by cohort."] }, + { "slug": "strategy-executive", "positive": ["Turn these market signals into strategic options for the leadership team."], "negative": ["Summarize these meeting notes and route action items."] }, + { "slug": "operations-supply-chain", "positive": ["Assess these supplier delays and propose a recovery plan for the next S&OP review."], "negative": ["Review the wording of this employment contract."] }, + { "slug": "marketing-customer-success", "positive": ["Create a launch message and customer success plan for this new product."], "negative": ["Calculate CPI and estimate the project cost at completion."] }, + { "slug": "data-analytics", "positive": ["Analyze this experiment data and explain whether the conversion lift is reliable."], "negative": ["Draft a contract clause for late delivery penalties."] }, + { "slug": "engineering-code", "positive": ["Investigate this failing test and propose a minimal code fix."], "negative": ["Prepare a quarterly business review for a customer account."] }, + { "slug": "it-admin", "positive": ["Compare these endpoint management vendors against our IT requirements."], "negative": ["Write a customer email announcing a pricing change."] }, + { "slug": "training-development", "positive": ["Design a workshop to help managers practice giving useful feedback."], "negative": ["Analyze the variance between forecast and actual revenue."] }, + { "slug": "cross-functional", "positive": ["Create a decision record comparing the options and documenting owners and tradeoffs."], "negative": ["Review this pull request for a security bug."] }, + { "slug": "skill-authoring", "positive": ["Help me write a focused reusable skill with a clear trigger and verification steps."], "negative": ["Use the existing meeting summary skill to extract decisions and actions."] } + ] +} From 588b1ad51b677537ae0f2a43d367cafb6c8179e9 Mon Sep 17 00:00:00 2001 From: Hoa Dang <70422390+hoavdc@users.noreply.github.com> Date: Sun, 11 Oct 2026 19:45:59 +0700 Subject: [PATCH 06/24] build: add Work plugin export commands --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index ae15939..d7171e2 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,8 @@ "dev": "npm --prefix web run dev", "build": "npm --prefix web run build", "lint": "npm --prefix web run lint", - "validate": "node scripts/validate-pack.mjs" + "validate": "node scripts/validate-pack.mjs", + "work:build": "node ./scripts/build-work-plugins.mjs", + "work:test": "npm run work:build && node ./scripts/test-work-plugins.mjs" } } From 8ad741ba293fdc87c1859cdbe1cb94852046e886 Mon Sep 17 00:00:00 2001 From: Hoa Dang <70422390+hoavdc@users.noreply.github.com> Date: Sun, 11 Oct 2026 19:46:10 +0700 Subject: [PATCH 07/24] ci: validate Work plugin exports --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1747be0..377c058 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,6 +34,9 @@ jobs: - name: Validate pack structure run: node ./scripts/validate-pack.mjs + - name: Build and test ChatGPT Work plugin exports + run: npm run work:test + - name: Smoke install on Unix if: runner.os != 'Windows' run: | From 33a424b5b482779e8a0142b03b4f25359a2bac2c Mon Sep 17 00:00:00 2001 From: Hoa Dang <70422390+hoavdc@users.noreply.github.com> Date: Sun, 11 Oct 2026 19:46:18 +0700 Subject: [PATCH 08/24] docs: add ChatGPT Work export quick start --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index d44cbf3..ce7e0c1 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,17 @@ If the install completed but the skills do not appear: 2. Check that the skills were copied into `%USERPROFILE%\.agents\skills` on Windows or `$HOME/.agents/skills` on macOS/Linux. 3. In Codex, use `/skills` to verify discovery. +### ChatGPT Work and plugin exports + +CodexKit also builds portable skill ZIPs and curated Work plugin packages from the same source skills. This export leaves the Codex install folders unchanged. + +```bash +npm run work:build +npm run work:test +``` + +See [ChatGPT Work and Codex plugin guide](./docs/chatgpt-work.md) for single-skill upload, local marketplace setup, routing design, and manual activation checks. Product access and install options vary by plan, workspace settings, and client surface. + ### 2. Validate the pack ```bash From 9c06fd9436d47732242de7d4e8d5b82c324dcaf0 Mon Sep 17 00:00:00 2001 From: Hoa Dang <70422390+hoavdc@users.noreply.github.com> Date: Sun, 11 Oct 2026 19:46:32 +0700 Subject: [PATCH 09/24] docs: clarify skill invocation contract in template --- skills/codexkit-skill-template/SKILL.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/skills/codexkit-skill-template/SKILL.md b/skills/codexkit-skill-template/SKILL.md index 17d503e..c1cf512 100644 --- a/skills/codexkit-skill-template/SKILL.md +++ b/skills/codexkit-skill-template/SKILL.md @@ -17,9 +17,9 @@ category: knowledge ## Procedure -### Step 1 — Define the Intent +### Step 1 — Define the Intent and Trigger -Write a clear, one-sentence purpose. Answer: "After running this skill, the user will have ___." +Write a clear, one-sentence purpose. Answer: "After running this skill, the user will have ___." Then decide whether it should be directly invoked by name, automatically selected from its description, or support both. Keep the description concrete enough to distinguish it from neighboring skills. ### Step 2 — Map the Domain @@ -55,6 +55,7 @@ A complete skill folder with: ## Quality Criteria - [ ] Purpose is a single, clear sentence — no compound goals +- [ ] Invocation mode and trigger description are clear; near-miss requests remain out of scope - [ ] Procedure steps each produce a visible deliverable - [ ] Quality Criteria are measurable or concretely observable - [ ] 4C Verification questions are domain-specific, not generic From 7ab072c32d504f4caf83d4e9b47beb96b15f5ed5 Mon Sep 17 00:00:00 2001 From: Hoa Dang <70422390+hoavdc@users.noreply.github.com> Date: Sun, 11 Oct 2026 19:47:11 +0700 Subject: [PATCH 10/24] docs: document Work skill and plugin exports --- web/src/app/docs/installation/page.tsx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/web/src/app/docs/installation/page.tsx b/web/src/app/docs/installation/page.tsx index 1af06b3..b591818 100644 --- a/web/src/app/docs/installation/page.tsx +++ b/web/src/app/docs/installation/page.tsx @@ -104,6 +104,22 @@ node ./scripts/validate-pack.mjs`} +
+

ChatGPT Work and plugin exports

+

+ Build portable single-skill ZIPs and curated domain plugins from the same source skill folders. These exports do not change the existing Codex install. +

+
+          {`npm run work:build
+npm run work:test`}
+        
+ +
+

Updating CodexKit

From 364089fd869051551093b34d61487c9cf4352f8d Mon Sep 17 00:00:00 2001 From: Hoa Dang <70422390+hoavdc@users.noreply.github.com> Date: Sun, 11 Oct 2026 19:47:20 +0700 Subject: [PATCH 11/24] docs: explain explicit and automatic skill routing --- web/src/app/docs/skills/page.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/web/src/app/docs/skills/page.tsx b/web/src/app/docs/skills/page.tsx index 942cea9..3f17a46 100644 --- a/web/src/app/docs/skills/page.tsx +++ b/web/src/app/docs/skills/page.tsx @@ -78,11 +78,14 @@ export default function SkillsPage() {

-

Invocation model

+

Invocation modes and routing

From f5ef113191415cedf1967f1295be70b996bc5140 Mon Sep 17 00:00:00 2001 From: Hoa Dang <70422390+hoavdc@users.noreply.github.com> Date: Sun, 11 Oct 2026 19:48:18 +0700 Subject: [PATCH 12/24] test: verify normalized skill metadata in exports --- scripts/test-work-plugins.mjs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/scripts/test-work-plugins.mjs b/scripts/test-work-plugins.mjs index db7e66f..9ea4e50 100644 --- a/scripts/test-work-plugins.mjs +++ b/scripts/test-work-plugins.mjs @@ -32,6 +32,12 @@ function zipEntryNames(buffer) { return entries; } +function groupForSkill(plugins, skillId) { + const entry = plugins.find((plugin) => plugin.skillIds.includes(skillId)); + assert(entry, `Skill ${skillId} is missing from the build report.`); + return groups.groups.find((group) => group.slug === marketplace.plugins.find((plugin) => plugin.name === entry.name)?.source.path.split("/").at(-1))?.slug; +} + const evalSlugs = groups.groups.map((group) => group.slug); assert(new Set(evalSlugs).size === evalSlugs.length, "Routing eval group slugs must be unique."); assert(groups.groups.length === marketplace.plugins.length, "Each plugin group must have routing evals."); @@ -56,10 +62,14 @@ for (const group of groups.groups) { } for (const skillId of summary.plugins.flatMap((entry) => entry.skillIds)) { + const exportedSkill = await fs.readFile(path.join(root, "dist", "work", "plugins", groupForSkill(summary.plugins, skillId), "skills", skillId, "SKILL.md"), "utf8"); + assert(/^---\\nname:\\s*.+\\ndescription:\\s*.+/m.test(exportedSkill), `Exported skill metadata is malformed for ${skillId}.`); + assert(!/^version:|^category:/m.test(exportedSkill), `CodexKit-only metadata was not normalized for ${skillId}.`); + assert(exportedSkill.includes("metadata:\\n version:") && exportedSkill.includes(" category:"), `Standard metadata block missing for ${skillId}.`); const zipPath = path.join(workRoot, "skill-zips", `${skillId}.zip`); const names = zipEntryNames(await fs.readFile(zipPath)); assert(names.includes(`${skillId}/SKILL.md`), `Missing single-skill ZIP for ${skillId}.`); } -assert(groups.purpose.includes("not"), "Routing evaluation purpose must describe its limitation."); +assert(groups.purpose.includes("model behavior is not"), "Routing evaluation must disclose that it does not measure model behavior."); console.log(`Work plugin tests passed: ${marketplace.plugins.length} plugin groups, ${summary.skillCount} skills, and ${groups.groups.length} routing fixtures.`); From cabf603cfeb53e202cacab6c1631d6577cd11ce9 Mon Sep 17 00:00:00 2001 From: Hoa Dang <70422390+hoavdc@users.noreply.github.com> Date: Sun, 11 Oct 2026 19:49:46 +0700 Subject: [PATCH 13/24] fix: parse skill ids from all finder table columns --- scripts/build-work-plugins.mjs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/scripts/build-work-plugins.mjs b/scripts/build-work-plugins.mjs index 57e0c51..912682d 100644 --- a/scripts/build-work-plugins.mjs +++ b/scripts/build-work-plugins.mjs @@ -56,10 +56,12 @@ function getGroupedSkills(markdown) { for (const line of markdown.split(/\r?\n/)) { const h = line.match(/^## (.+)$/); if (h) heading = h[1]; - const row = line.match(/^\|\s*`(codexkit-[a-z0-9-]+)`\s*\|/); - if (row && groupConfig[heading]) { - if (!groups.has(heading)) groups.set(heading, []); - groups.get(heading).push(row[1]); + if (line.startsWith("|") && groupConfig[heading]) { + const skillIds = [...line.matchAll(/`(codexkit-[a-z0-9-]+)`/g)].map((match) => match[1]); + if (skillIds.length) { + if (!groups.has(heading)) groups.set(heading, []); + groups.get(heading).push(...skillIds); + } } } return groups; From 1775bd77bd30ccbcefc69e05bd2aa9abae220348 Mon Sep 17 00:00:00 2001 From: Hoa Dang <70422390+hoavdc@users.noreply.github.com> Date: Sun, 11 Oct 2026 19:50:58 +0700 Subject: [PATCH 14/24] test: parse exported skill frontmatter reliably --- scripts/test-work-plugins.mjs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/test-work-plugins.mjs b/scripts/test-work-plugins.mjs index 9ea4e50..2e756b3 100644 --- a/scripts/test-work-plugins.mjs +++ b/scripts/test-work-plugins.mjs @@ -62,9 +62,11 @@ for (const group of groups.groups) { } for (const skillId of summary.plugins.flatMap((entry) => entry.skillIds)) { - const exportedSkill = await fs.readFile(path.join(root, "dist", "work", "plugins", groupForSkill(summary.plugins, skillId), "skills", skillId, "SKILL.md"), "utf8"); - assert(/^---\\nname:\\s*.+\\ndescription:\\s*.+/m.test(exportedSkill), `Exported skill metadata is malformed for ${skillId}.`); - assert(!/^version:|^category:/m.test(exportedSkill), `CodexKit-only metadata was not normalized for ${skillId}.`); + const frontmatter = exportedSkill.match(/^---\r?\n([\s\S]*?)\r?\n---/); + assert(Boolean(frontmatter), \`Exported skill frontmatter is malformed for \${skillId}.\`); + assert(/^name:\s*\S+/m.test(frontmatter[1]) && /^description:\s*\S+/m.test(frontmatter[1]), \`Exported skill requires name and description for \${skillId}.\`); + assert(!/^version:|^category:/m.test(frontmatter[1]), \`CodexKit-only metadata was not normalized for \${skillId}.\`); + assert(frontmatter[1].includes("metadata:\n version:") && frontmatter[1].includes(" category:"), \`Standard metadata block missing for \${skillId}.\`); assert(exportedSkill.includes("metadata:\\n version:") && exportedSkill.includes(" category:"), `Standard metadata block missing for ${skillId}.`); const zipPath = path.join(workRoot, "skill-zips", `${skillId}.zip`); const names = zipEntryNames(await fs.readFile(zipPath)); From 4760982977508d4fb965de339997a6e19e836d0c Mon Sep 17 00:00:00 2001 From: Hoa Dang <70422390+hoavdc@users.noreply.github.com> Date: Sun, 11 Oct 2026 19:51:16 +0700 Subject: [PATCH 15/24] test: repair exported metadata assertions --- scripts/test-work-plugins.mjs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/scripts/test-work-plugins.mjs b/scripts/test-work-plugins.mjs index 2e756b3..1f280fa 100644 --- a/scripts/test-work-plugins.mjs +++ b/scripts/test-work-plugins.mjs @@ -62,12 +62,13 @@ for (const group of groups.groups) { } for (const skillId of summary.plugins.flatMap((entry) => entry.skillIds)) { + const pluginGroup = groupForSkill(summary.plugins, skillId); + const exportedSkill = await fs.readFile(path.join(root, "dist", "work", "plugins", pluginGroup, "skills", skillId, "SKILL.md"), "utf8"); const frontmatter = exportedSkill.match(/^---\r?\n([\s\S]*?)\r?\n---/); - assert(Boolean(frontmatter), \`Exported skill frontmatter is malformed for \${skillId}.\`); - assert(/^name:\s*\S+/m.test(frontmatter[1]) && /^description:\s*\S+/m.test(frontmatter[1]), \`Exported skill requires name and description for \${skillId}.\`); - assert(!/^version:|^category:/m.test(frontmatter[1]), \`CodexKit-only metadata was not normalized for \${skillId}.\`); - assert(frontmatter[1].includes("metadata:\n version:") && frontmatter[1].includes(" category:"), \`Standard metadata block missing for \${skillId}.\`); - assert(exportedSkill.includes("metadata:\\n version:") && exportedSkill.includes(" category:"), `Standard metadata block missing for ${skillId}.`); + assert(Boolean(frontmatter), "Exported skill frontmatter is malformed for " + skillId + "."); + assert(/^name:\s*\S+/m.test(frontmatter[1]) && /^description:\s*\S+/m.test(frontmatter[1]), "Exported skill requires name and description for " + skillId + "."); + assert(!/^version:|^category:/m.test(frontmatter[1]), "CodexKit-only metadata was not normalized for " + skillId + "."); + assert(frontmatter[1].includes("metadata:\n version:") && frontmatter[1].includes(" category:"), "Standard metadata block missing for " + skillId + "."); const zipPath = path.join(workRoot, "skill-zips", `${skillId}.zip`); const names = zipEntryNames(await fs.readFile(zipPath)); assert(names.includes(`${skillId}/SKILL.md`), `Missing single-skill ZIP for ${skillId}.`); From c9d5e91bdc6d0ee25b5aa8ce5701396574a9c115 Mon Sep 17 00:00:00 2001 From: Hoa Dang <70422390+hoavdc@users.noreply.github.com> Date: Sun, 11 Oct 2026 19:52:47 +0700 Subject: [PATCH 16/24] ci: include Work export in full check --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d7171e2..e2d80aa 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "productivity" ], "scripts": { - "check": "node scripts/validate-pack.mjs && npm --prefix web run lint && npm --prefix web run build", + "check": "node scripts/validate-pack.mjs && npm run work:test && npm --prefix web run lint && npm --prefix web run build", "dev": "npm --prefix web run dev", "build": "npm --prefix web run build", "lint": "npm --prefix web run lint", From 219a47f50072d16ebe08fc282c8dd2de878178bf Mon Sep 17 00:00:00 2001 From: Hoa Dang <70422390+hoavdc@users.noreply.github.com> Date: Sun, 11 Oct 2026 19:52:57 +0700 Subject: [PATCH 17/24] release: include Work export files in starter pack --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index caa5e94..b2d80ac 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,7 +35,7 @@ jobs: run: | git archive --format=zip --output "codexkit-source-${GITHUB_REF_NAME}.zip" HEAD mkdir -p release/codexkit-starter-pack - cp -R skills templates workspaces playbooks automations mcp scripts release/codexkit-starter-pack/ + cp -R skills templates workspaces playbooks automations mcp scripts config docs tests release/codexkit-starter-pack/ cp README.md LICENSE NOTICE.md package.json CHANGELOG.md skill-finder.md HUONG-DAN-NHANH.md release/codexkit-starter-pack/ cp RELEASE-v*.md release/codexkit-starter-pack/ 2>/dev/null || true cp START-HERE-WINDOWS.cmd START-HERE.sh release/codexkit-starter-pack/ From c65f34c61410d8722d27b2a09cc210e83aedfe3d Mon Sep 17 00:00:00 2001 From: Hoa Dang <70422390+hoavdc@users.noreply.github.com> Date: Sun, 11 Oct 2026 19:53:06 +0700 Subject: [PATCH 18/24] docs: clarify Work and Codex marketplace support --- docs/chatgpt-work.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/chatgpt-work.md b/docs/chatgpt-work.md index 1d4962d..e4bfe63 100644 --- a/docs/chatgpt-work.md +++ b/docs/chatgpt-work.md @@ -18,7 +18,7 @@ The build creates: **For one skill:** use the ChatGPT Skills interface and upload the corresponding ZIP from `dist/work/skill-zips/`. This keeps the installation small and focused. If your account does not show Skills, access may depend on plan, workspace settings, or rollout availability. -**For a domain bundle in Codex:** add the repository as a plugin marketplace using Codex's supported plugin flow, then install only the plugin you need. The generated marketplace references local paths, so it is intended for a checkout on your machine and is not itself a public hosted marketplace. Restart or refresh the client if the newly installed skills do not appear. +**For a domain bundle:** in ChatGPT desktop Work mode or Codex, add the repository as a plugin marketplace using the supported plugin flow, then install only the plugin you need. The generated marketplace references local paths, so it is intended for a checkout on your machine and is not itself a public hosted marketplace. Restart or refresh the client if the newly installed skills do not appear. **For teams:** commit the selected unpacked plugin package to a repository or publish it through the product's supported marketplace process. Do not distribute `.env` files, tokens, or other credentials with a skills package. From d7ef781f1d3fd9858e682e9894b665e86512c20d Mon Sep 17 00:00:00 2001 From: Hoa Dang <70422390+hoavdc@users.noreply.github.com> Date: Sun, 11 Oct 2026 19:53:19 +0700 Subject: [PATCH 19/24] docs: clarify marketplace install surfaces --- web/src/app/docs/installation/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/app/docs/installation/page.tsx b/web/src/app/docs/installation/page.tsx index b591818..23f508c 100644 --- a/web/src/app/docs/installation/page.tsx +++ b/web/src/app/docs/installation/page.tsx @@ -115,7 +115,7 @@ npm run work:test`} From 76de6a435d4ed5f8558db834c24c4305f4c23dc2 Mon Sep 17 00:00:00 2001 From: Hoa Dang <70422390+hoavdc@users.noreply.github.com> Date: Sun, 11 Oct 2026 20:32:28 +0700 Subject: [PATCH 20/24] chore: bump package version to 0.11.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e2d80aa..002960f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codexkit", - "version": "0.10.0", + "version": "0.11.0", "private": true, "repository": { "type": "git", From acd20fce9d2d64e02c777b2ca06341e963d6100b Mon Sep 17 00:00:00 2001 From: Hoa Dang <70422390+hoavdc@users.noreply.github.com> Date: Sun, 11 Oct 2026 20:32:37 +0700 Subject: [PATCH 21/24] chore: bump docs version to 0.11.0 --- web/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/package.json b/web/package.json index 327e751..45f91e4 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "codexkit-docs", - "version": "0.10.0", + "version": "0.11.0", "private": true, "description": "Documentation site for CodexKit, an open-source operating kit for engineering, high-reasoning work, and office automation.", "engines": { From b58a0822d14dfd386cba7e94df348cefa04ba789 Mon Sep 17 00:00:00 2001 From: Hoa Dang <70422390+hoavdc@users.noreply.github.com> Date: Sun, 11 Oct 2026 20:32:46 +0700 Subject: [PATCH 22/24] docs: add v0.11.0 changelog --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ef9dbc..c2fd356 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/). ## [Unreleased] +## [0.11.0] - 2026-10-11 + +### Added + +- **ChatGPT Work export**: Build portable ZIPs for all 90 skills and 14 curated domain plugins, plus a local marketplace manifest, from the existing CodexKit sources. +- **Routing coverage checks**: Add positive and near-miss fixtures for each plugin group, with validation of exported metadata, manifests, marketplace entries, and ZIP structure. +- **ChatGPT Work guide**: Document single-skill uploads, local marketplace setup, explicit and automatic invocation, manual activation checks, and differences between Codex and Work updates. + +### Changed + +- Normalize CodexKit version and category metadata into Agent Skills fields in exported `SKILL.md` files without changing the Codex source layout. +- Extend CI and release starter-pack contents to validate and ship the Work export, documentation, and test fixtures. +- Update skill-authoring guidance for portable Work exports. + ## [0.10.0] - 2026-05-15 ### Added From 3f21c2ec96c87f35599ff5e67cb9c0347922b8c5 Mon Sep 17 00:00:00 2001 From: Hoa Dang <70422390+hoavdc@users.noreply.github.com> Date: Sun, 11 Oct 2026 20:33:00 +0700 Subject: [PATCH 23/24] docs: update release instructions for v0.11.0 --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index ce7e0c1..eceabd0 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ CodexKit is a fresh project rebuilt around the surfaces that matter in Codex tod If you do not want to use Git commands or remember terminal steps, use the GitHub release package: 1. Open the Releases page for this repository. -2. Download `codexkit-starter-pack-v0.10.0.zip`. +2. Download `codexkit-starter-pack-v0.11.0.zip`. 3. Unzip it anywhere on your computer. 4. On Windows, double-click `START-HERE-WINDOWS.cmd`. - If Codex is not installed, the script will show installation instructions. @@ -275,26 +275,26 @@ See [CONTRIBUTING.md](./CONTRIBUTING.md) for the full guide. - Replace placeholder maintainer details where needed. - Add screenshots or a short demo GIF to the docs site. - Commit and push the release candidate branch or `main`. -- Tag `v0.10.0` after validating the pack and docs build. +- Tag `v0.11.0` after validating the pack, Work exports, and docs build. - Push the tag to trigger the GitHub release workflow. ## Release process -1. Update `package.json`, `web/package.json`, and `CHANGELOG.md`. -2. Run `npm run check`. -3. Commit and push the release commit. -4. Create the tag: `git tag v0.10.0` -5. Push the tag: `git push origin v0.10.0` -6. GitHub Actions publishes: - `codexkit-source-v0.10.0.zip` - `codexkit-starter-pack-v0.10.0.zip` +1. Update `package.json`, `web/package.json`, `CHANGELOG.md`, and `RELEASE-v0.11.0.md`. +2. Run `npm run check` (including `npm run work:test`). +3. Merge the validated release candidate into `main`. +4. Create the tag from the release commit: `git tag v0.11.0` +5. Push the tag: `git push origin v0.11.0` +6. GitHub Actions validates the release and publishes: + `codexkit-source-v0.11.0.zip` + `codexkit-starter-pack-v0.11.0.zip` ## Related files - [CONTRIBUTING.md](./CONTRIBUTING.md) - [SECURITY.md](./SECURITY.md) - [NOTICE.md](./NOTICE.md) -- [RELEASE-v0.10.0.md](./RELEASE-v0.10.0.md) +- [RELEASE-v0.11.0.md](./RELEASE-v0.11.0.md) - [templates/project-brief.md](./templates/project-brief.md) - [templates/release-checklist.md](./templates/release-checklist.md) - [workspaces/README.md](./workspaces/README.md) From 54a95758b7623db6953ec33561381914cdf2881f Mon Sep 17 00:00:00 2001 From: Hoa Dang <70422390+hoavdc@users.noreply.github.com> Date: Sun, 11 Oct 2026 20:33:09 +0700 Subject: [PATCH 24/24] docs: add v0.11.0 release notes --- RELEASE-v0.11.0.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 RELEASE-v0.11.0.md diff --git a/RELEASE-v0.11.0.md b/RELEASE-v0.11.0.md new file mode 100644 index 0000000..e075539 --- /dev/null +++ b/RELEASE-v0.11.0.md @@ -0,0 +1,39 @@ +# CodexKit v0.11.0 — ChatGPT Work exports + +> Portable Work plugin packages and skill ZIPs, with routing and packaging checks, built from the existing CodexKit skill library. + +## Summary + +v0.11.0 makes CodexKit skills easier to use across Codex and ChatGPT Work. It adds an export pipeline for individual skill ZIPs, curated domain plugins, and a local marketplace manifest while preserving the existing Codex source layout. + +## Added + +- Portable ZIP exports for all 90 skills. +- 14 curated plugin packages and a local marketplace manifest. +- Routing fixtures for each plugin group, including near-miss cases. +- Documentation for Work installation, invocation, activation checks, and update differences. + +## Improved + +- Exported `SKILL.md` files normalize CodexKit version and category metadata into Agent Skills fields. +- CI validates plugin manifests, metadata, ZIP contents, and routing fixture coverage on Ubuntu and Windows. +- Release starter packs include the Work export, documentation, and tests. + +## Validation + +The release workflow runs: + +```bash +npm run check +``` + +This validates the source pack, builds and tests the Work exports, lints the documentation app, and builds the production docs site. + +## Upgrade notes + +No migration is required. Existing Codex users can update as usual. ChatGPT Work users can use the guide at [docs/chatgpt-work.md](./docs/chatgpt-work.md) to upload a skill or configure the local marketplace. Product access and installation options depend on the account, workspace settings, and client surface. + +## Release assets + +- `codexkit-source-v0.11.0.zip` +- `codexkit-starter-pack-v0.11.0.zip`