This commit is contained in:
commit
07eb6dc053
318 changed files with 339857 additions and 0 deletions
229
.codex-home/skills/.system/skill-creator/SKILL.md
Normal file
229
.codex-home/skills/.system/skill-creator/SKILL.md
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
---
|
||||
name: skill-creator
|
||||
description: Create or update a Codex skill with appropriately scoped instructions and any needed supporting resources.
|
||||
metadata:
|
||||
short-description: Create or update a skill
|
||||
---
|
||||
|
||||
# Skill Creator
|
||||
|
||||
Create skills that give Codex useful, non-obvious guidance without constraining unrelated work.
|
||||
|
||||
## Core Principles
|
||||
|
||||
**Assume Codex is already capable.** Include only information that changes its decisions or improves its work. Remove generic advice, repeated instructions, speculative edge cases, and examples that do not materially clarify the task.
|
||||
|
||||
**Preserve user intent and scope.** A skill should support the requested task, not replace the user's chosen product, expand the assignment, modify unrelated configuration, or imply permission for additional external actions. Do not turn a particular example, past failure, or personal preference into a universal requirement.
|
||||
|
||||
Approval to complete a task does not expand its scope or execution permissions. For retrying or externally mutating workflows, define a stopping condition proportional to the risk.
|
||||
|
||||
**Match specificity to the risk.** Give the model room to choose an appropriate approach when multiple approaches are reasonable. Use detailed steps, deterministic scripts, or absolute language only when correctness, safety, permissions, or a genuinely fragile workflow requires them.
|
||||
|
||||
For open-ended work, describe the outcome and relevant decision criteria. For workflows with a preferred shape, offer useful examples or configurable scripts. Reserve fixed sequences and narrow parameters for operations where deviation would cause a concrete problem. Preserve non-obvious operational invariants, distinguish actual requirements from optional recommendations or local conventions, and avoid restating policies already enforced elsewhere.
|
||||
|
||||
**Keep discovery cheap and precise.** Skill names and descriptions are available before a skill is loaded. Describe the actual capability and when it applies, adding exclusions only when they prevent likely misrouting. Avoid exhaustive capability lists and catchalls that attract unrelated requests.
|
||||
|
||||
Keep skills self-contained; refer to another skill or tool only when the requested workflow genuinely requires it and it is available in the target environment. Specialized review, hardening, or audit workflows should apply when requested or genuinely needed, not merely because ordinary work touches the same subject.
|
||||
|
||||
**Disclose detail progressively.** Keep shared purpose, essential constraints, and useful routing in `SKILL.md`. Put substantial mode-specific guidance, schemas, examples, or procedures in supporting references and read only the references relevant to the current task. A simple self-contained skill does not need a router or extra files.
|
||||
|
||||
## Anatomy of a Skill
|
||||
|
||||
Every skill is a folder containing a required `SKILL.md` file and any optional resources its actual workflow needs:
|
||||
|
||||
```text
|
||||
skill-name/
|
||||
|-- SKILL.md Required skill instructions
|
||||
| |-- YAML frontmatter Required name and description
|
||||
| `-- Markdown body Instructions loaded when the skill is used
|
||||
|-- agents/ Optional UI metadata and invocation policy
|
||||
| `-- openai.yaml
|
||||
|-- scripts/ Optional executable helpers
|
||||
|-- references/ Optional documentation loaded as needed
|
||||
`-- assets/ Optional files used in generated output
|
||||
```
|
||||
|
||||
Choose the structure that fits the actual task. Some skills are short and self-contained; others route among operating modes or delegate complex mechanics to scripts. Avoid creating directories, placeholders, examples, or ancillary documentation without a clear use.
|
||||
|
||||
### SKILL.md
|
||||
|
||||
The YAML frontmatter identifies the skill and determines when it should be considered. Include the required `name` and `description`, and preserve supported optional fields such as existing `metadata` when appropriate.
|
||||
|
||||
The Markdown body is loaded only when the skill is used. Put the purpose, essential workflow, real constraints, and useful links there. Keep detailed procedures and examples in supporting references when they are relevant only to particular modes.
|
||||
|
||||
Skill information is disclosed in three stages:
|
||||
|
||||
1. **Name and description:** Available during skill selection, so keep them concise and discriminating.
|
||||
2. **SKILL.md body:** Loaded when the skill applies, so keep its instructions relevant to that task.
|
||||
3. **Supporting resources:** Read or execute only when the current task actually needs them.
|
||||
|
||||
The entrypoint should be as short as the task permits while retaining important constraints. A large upper bound is not a target: move conditional detail into references when doing so improves clarity or context use, rather than waiting for the file to become unwieldy.
|
||||
|
||||
### Scripts
|
||||
|
||||
Use `scripts/` for executable code when the same logic would otherwise be rewritten repeatedly or deterministic execution materially improves reliability.
|
||||
|
||||
- **Example:** `scripts/rotate_pdf.py` for a PDF operation that would otherwise require recreating the same code.
|
||||
- **Useful for:** Repeated transformations, reliable API operations, data processing, and other concrete automation.
|
||||
- **Validation:** Run new or changed scripts to verify their behavior. Scripts can usually be executed without loading their full implementation into context, although an agent may need to inspect them when patching or adapting them.
|
||||
|
||||
### References
|
||||
|
||||
Use `references/` for documentation that is needed only in particular contexts.
|
||||
|
||||
- **Examples:** `references/schema.md` for database tables, `references/policies.md` for domain rules, `references/api_docs.md` for an API, or separate writing guides for different deliverables.
|
||||
- **Useful for:** Schemas, API documentation, company policies, format-specific procedures, detailed workflows, and substantial examples.
|
||||
- **Routing:** Link each reference from `SKILL.md` or another relevant resource and explain when it should be read. Keep information in one place instead of duplicating it across the entrypoint and references.
|
||||
|
||||
Keep references focused on maintained, task-specific information that changes the agent's decisions. Avoid copied manuals, exhaustive catalogs, and generic tutorials already available from authoritative sources. Before removing existing resources, inspect their callers and purpose.
|
||||
|
||||
For large references, include useful search terms or a short contents section when that makes the needed material easier to find.
|
||||
|
||||
### Assets
|
||||
|
||||
Use `assets/` for files that belong in generated output rather than in the model's instructions.
|
||||
|
||||
- **Examples:** `assets/logo.png`, `assets/slides.pptx`, `assets/font.ttf`, or `assets/frontend-template/`.
|
||||
- **Useful for:** Templates, images, fonts, icons, boilerplate projects, and other files copied or adapted into the result.
|
||||
- **Context:** Do not load assets as instructions unless the task requires inspecting them.
|
||||
|
||||
### UI Metadata and Invocation Policy
|
||||
|
||||
`agents/openai.yaml` can provide UI-facing metadata such as `display_name`, `short_description`, and `default_prompt`, along with invocation policy. When creating or updating those settings, read [references/openai_yaml.md](references/openai_yaml.md) and keep the values consistent with the skill.
|
||||
|
||||
Automatic skill selection is allowed by default. Change that default only when the user explicitly requests an explicit-only skill:
|
||||
|
||||
```yaml
|
||||
policy:
|
||||
allow_implicit_invocation: false
|
||||
```
|
||||
|
||||
This keeps the skill available when explicitly invoked as `$skill-name` without adding it to the model context automatically. Preserve unrelated existing UI, policy, and dependency fields when updating `agents/openai.yaml`.
|
||||
|
||||
The initializer creates this file automatically. For new or interface-only metadata, generate it with:
|
||||
|
||||
```bash
|
||||
scripts/generate_openai_yaml.py <path/to/skill-folder> --interface key=value
|
||||
```
|
||||
|
||||
The generator replaces the entire file. If an existing file contains `policy` or `dependencies`, update only the intended fields in place instead of regenerating it.
|
||||
|
||||
Include optional interface fields only when the user provides or requests them.
|
||||
|
||||
### What Not to Include
|
||||
|
||||
Include files that directly support the skill's work. Avoid adding a `README.md`, installation guide, changelog, duplicated quick reference, or other auxiliary documentation unless a specific task or packaging requirement calls for it.
|
||||
|
||||
## Progressive Disclosure in Practice
|
||||
|
||||
For a skill with multiple substantial modes, keep the shared guidance and mode-selection criteria in `SKILL.md`. Link each supporting reference where its use becomes relevant. Do not load every reference by default, duplicate reference content in the entrypoint, or add a routing layer when there is nothing meaningful to route.
|
||||
|
||||
For example, a deployment skill can keep provider selection in `SKILL.md` and separate provider details:
|
||||
|
||||
```text
|
||||
cloud-deploy/
|
||||
|-- SKILL.md
|
||||
`-- references/
|
||||
|-- aws.md
|
||||
|-- gcp.md
|
||||
`-- azure.md
|
||||
```
|
||||
|
||||
When the user chooses AWS, read `references/aws.md`; do not also load the GCP and Azure guides. The same pattern can separate business domains, deliverable types, or other genuinely distinct operating modes.
|
||||
|
||||
A short skill can instead route to details only when an advanced operation needs them:
|
||||
|
||||
```markdown
|
||||
## Documents
|
||||
|
||||
Handle ordinary edits directly.
|
||||
|
||||
- For tracked changes, read [references/redlining.md](references/redlining.md).
|
||||
- For document internals, read [references/ooxml.md](references/ooxml.md).
|
||||
```
|
||||
|
||||
These examples illustrate options, not a required structure. Choose the organization that makes the skill easier to use without loading irrelevant material.
|
||||
|
||||
## Create or Update a Skill
|
||||
|
||||
Adapt the work to the request. Creating a complex new skill may involve understanding realistic use cases, choosing supporting resources, initializing files, writing instructions, and validating the result. A narrow update to an existing skill may require only a focused edit and validation.
|
||||
|
||||
Ask clarifying questions only when the missing information matters and cannot be reasonably inferred. Respect a user-specified location; otherwise create discoverable skills in `$CODEX_HOME/skills`, or `~/.codex/skills` when `CODEX_HOME` is unset.
|
||||
|
||||
Keep automatic skill selection enabled unless the user explicitly requests an explicit-only skill. When the intended invocation mode is genuinely unclear and matters to the requested workflow, ask whether the user wants normal automatic discovery or explicit-only invocation; otherwise preserve the default. Do not infer explicit-only invocation from sensitive operations or required approvals: keep the skill discoverable and require authorization immediately before the actual mutation. Preserve an existing skill's invocation policy unless the user asks to change it.
|
||||
|
||||
For a new or substantially revised skill, consider the actual requests it should handle and which reusable resources would improve those tasks:
|
||||
|
||||
- A repeated PDF transformation may justify a `scripts/rotate_pdf.py` helper.
|
||||
- An application-building workflow may benefit from an `assets/frontend-template/` starter.
|
||||
- A data-analysis skill may need a `references/schema.md` guide to avoid rediscovering table relationships.
|
||||
|
||||
Create those resources only when their concrete benefit justifies them. If the user has already explained the task clearly, proceed without requesting additional examples.
|
||||
|
||||
### Naming
|
||||
|
||||
- Use lowercase letters, digits, and hyphens.
|
||||
- Keep names under 64 characters and prefer short action-oriented names.
|
||||
- Namespace by tool or domain when doing so improves discovery.
|
||||
- Name the skill folder after the skill.
|
||||
|
||||
### Initialize a New Skill
|
||||
|
||||
For a new skill, use the bundled initializer when it helps create the required files consistently:
|
||||
|
||||
```bash
|
||||
scripts/init_skill.py <skill-name> --path <output-directory> [--resources scripts,references,assets] [--examples]
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```bash
|
||||
scripts/init_skill.py my-skill --path "${CODEX_HOME:-$HOME/.codex}/skills"
|
||||
scripts/init_skill.py my-skill --path "${CODEX_HOME:-$HOME/.codex}/skills" --resources references
|
||||
```
|
||||
|
||||
Request only the resource directories the skill needs. Use `--examples` only when concrete placeholders would help, and replace or remove them before finishing. Do not initialize an existing skill again.
|
||||
|
||||
The initializer creates the skill directory, a concise `SKILL.md` starter, and `agents/openai.yaml`. It creates resource directories and example files only when requested. Pass generated UI values as `--interface key=value` when needed.
|
||||
|
||||
### Write the Instructions
|
||||
|
||||
The frontmatter `description` should briefly explain what the skill does and when it applies. Include a meaningful boundary when similar requests should not activate the skill.
|
||||
|
||||
For example:
|
||||
|
||||
```yaml
|
||||
description: Create or edit Word documents when formatting, tracked changes, or comments require document-specific handling.
|
||||
```
|
||||
|
||||
Put detailed workflows, tool choices, examples, and operating modes in the body or relevant references rather than listing them all in the description. Preserve supported optional frontmatter, such as existing `metadata`, when appropriate.
|
||||
|
||||
Write only the instructions needed for another Codex instance to perform the task well. State the desired outcome, non-obvious context, real constraints, and relevant references or tools. Preserve the user's explicit choices and existing authorization boundaries. Avoid prescribing a fixed structure, process, or number of steps when the task does not require one.
|
||||
|
||||
### Validate and Iterate
|
||||
|
||||
Validate the completed skill with:
|
||||
|
||||
```bash
|
||||
scripts/quick_validate.py <path/to/skill-folder>
|
||||
```
|
||||
|
||||
The validator checks frontmatter, naming, and unfinished scaffold placeholders; it does not prove that the skill makes good decisions. Also check that descriptions remain discriminating, instructions preserve user intent, references are discoverable, and any added scripts actually work.
|
||||
|
||||
When testing is warranted, verify observable behavior or meaningful invariants. Avoid tests that merely match generated wording, headings, or regex patterns.
|
||||
|
||||
Improve the skill based on real usage or demonstrated failures. Prefer a narrow correction to accumulating universal rules for every observed example.
|
||||
|
||||
## Independent Forward-Testing
|
||||
|
||||
Use an independent subagent pass when a skill is sufficiently complex or risky that realistic behavioral validation would add meaningful confidence, and when delegation is available and authorized. Ordinary creation or small edits do not automatically require subagents.
|
||||
|
||||
Give the evaluating agent a realistic user request, the skill, and the minimum raw artifacts needed to perform the task. Do not provide the intended answer, suspected bug, proposed fix, or prior conclusions unless the evaluation genuinely requires them.
|
||||
|
||||
For example:
|
||||
|
||||
```text
|
||||
Use $skill-name at /path/to/skill-name to complete this realistic request.
|
||||
```
|
||||
|
||||
Keep the evaluation scoped to permitted resources and side effects. Use an isolated temporary workspace for generated artifacts so they do not enter the working tree or contaminate later evaluations. Ask for approval when the proposed evaluation would require additional authorization, affect a live production system, or impose substantial time or cost. Review the actual outcome and artifacts, then make only changes supported by the observed behavior.
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
interface:
|
||||
display_name: "Skill Creator"
|
||||
short_description: "Create or update a skill"
|
||||
icon_small: "./assets/skill-creator-small.svg"
|
||||
icon_large: "./assets/skill-creator.png"
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill="#0D0D0D" d="M12.03 4.113a3.612 3.612 0 0 1 5.108 5.108l-6.292 6.29c-.324.324-.56.561-.791.752l-.235.176c-.205.14-.422.261-.65.36l-.229.093a4.136 4.136 0 0 1-.586.16l-.764.134-2.394.4c-.142.024-.294.05-.423.06-.098.007-.232.01-.378-.026l-.149-.05a1.081 1.081 0 0 1-.521-.474l-.046-.093a1.104 1.104 0 0 1-.075-.527c.01-.129.035-.28.06-.422l.398-2.394c.1-.602.162-.987.295-1.35l.093-.23c.1-.228.22-.445.36-.65l.176-.235c.19-.232.428-.467.751-.79l6.292-6.292Zm-5.35 7.232c-.35.35-.534.535-.66.688l-.11.147a2.67 2.67 0 0 0-.24.433l-.062.154c-.08.22-.124.462-.232 1.112l-.398 2.394-.001.001h.003l2.393-.399.717-.126a2.63 2.63 0 0 0 .394-.105l.154-.063a2.65 2.65 0 0 0 .433-.24l.147-.11c.153-.126.339-.31.688-.66l4.988-4.988-3.227-3.226-4.987 4.988Zm9.517-6.291a2.281 2.281 0 0 0-3.225 0l-.364.362 3.226 3.227.363-.364c.89-.89.89-2.334 0-3.225ZM4.583 1.783a.3.3 0 0 1 .294.241c.117.585.347 1.092.707 1.48.357.385.859.668 1.549.783a.3.3 0 0 1 0 .592c-.69.115-1.192.398-1.549.783-.315.34-.53.77-.657 1.265l-.05.215a.3.3 0 0 1-.588 0c-.117-.585-.347-1.092-.707-1.48-.357-.384-.859-.668-1.549-.783a.3.3 0 0 1 0-.592c.69-.115 1.192-.398 1.549-.783.36-.388.59-.895.707-1.48l.015-.05a.3.3 0 0 1 .279-.19Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
202
.codex-home/skills/.system/skill-creator/license.txt
Normal file
202
.codex-home/skills/.system/skill-creator/license.txt
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
# openai.yaml fields (full example + descriptions)
|
||||
|
||||
`agents/openai.yaml` is an extended, product-specific config intended for the machine/harness to read, not the agent. Other product-specific config can also live in the `agents/` folder.
|
||||
|
||||
## Full example
|
||||
|
||||
```yaml
|
||||
interface:
|
||||
display_name: "Optional user-facing name"
|
||||
short_description: "Optional user-facing description"
|
||||
icon_small: "./assets/small-400px.png"
|
||||
icon_large: "./assets/large-logo.svg"
|
||||
brand_color: "#3B82F6"
|
||||
default_prompt: "Optional surrounding prompt to use the skill with"
|
||||
|
||||
dependencies:
|
||||
tools:
|
||||
- type: "mcp"
|
||||
value: "github"
|
||||
description: "GitHub MCP server"
|
||||
transport: "streamable_http"
|
||||
url: "https://api.githubcopilot.com/mcp/"
|
||||
|
||||
policy:
|
||||
allow_implicit_invocation: true
|
||||
```
|
||||
|
||||
## Field descriptions and constraints
|
||||
|
||||
Top-level constraints:
|
||||
|
||||
- Quote all string values.
|
||||
- Keep keys unquoted.
|
||||
- For `interface.default_prompt`: generate a helpful, short (typically 1 sentence) example starting prompt based on the skill. It must explicitly mention the skill as `$skill-name` (e.g., "Use $skill-name-here to draft a concise weekly status update.").
|
||||
|
||||
- `interface.display_name`: Human-facing title shown in UI skill lists and chips.
|
||||
- `interface.short_description`: Human-facing short UI blurb (25–64 chars) for quick scanning.
|
||||
- `interface.icon_small`: Path to a small icon asset (relative to skill dir). Default to `./assets/` and place icons in the skill's `assets/` folder.
|
||||
- `interface.icon_large`: Path to a larger logo asset (relative to skill dir). Default to `./assets/` and place icons in the skill's `assets/` folder.
|
||||
- `interface.brand_color`: Hex color used for UI accents (e.g., badges).
|
||||
- `interface.default_prompt`: Default prompt snippet inserted when invoking the skill.
|
||||
- `dependencies.tools[].type`: Dependency category. Only `mcp` is supported for now.
|
||||
- `dependencies.tools[].value`: Identifier of the tool or dependency.
|
||||
- `dependencies.tools[].description`: Human-readable explanation of the dependency.
|
||||
- `dependencies.tools[].transport`: Connection type when `type` is `mcp`.
|
||||
- `dependencies.tools[].url`: MCP server URL when `type` is `mcp`.
|
||||
- `policy.allow_implicit_invocation`: When false, the skill is not injected into
|
||||
the model context by default, but can still be invoked explicitly via `$skill`.
|
||||
Defaults to true.
|
||||
|
|
@ -0,0 +1,231 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
OpenAI YAML Generator - Creates agents/openai.yaml for a skill folder.
|
||||
|
||||
Usage:
|
||||
generate_openai_yaml.py <skill_dir> [--name <skill_name>] [--interface key=value]
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ACRONYMS = {
|
||||
"GH",
|
||||
"MCP",
|
||||
"API",
|
||||
"CI",
|
||||
"CLI",
|
||||
"LLM",
|
||||
"PDF",
|
||||
"PR",
|
||||
"UI",
|
||||
"URL",
|
||||
"SQL",
|
||||
}
|
||||
|
||||
BRANDS = {
|
||||
"openai": "OpenAI",
|
||||
"openapi": "OpenAPI",
|
||||
"github": "GitHub",
|
||||
"pagerduty": "PagerDuty",
|
||||
"datadog": "DataDog",
|
||||
"sqlite": "SQLite",
|
||||
"fastapi": "FastAPI",
|
||||
}
|
||||
|
||||
SMALL_WORDS = {"and", "or", "to", "up", "with"}
|
||||
|
||||
ALLOWED_INTERFACE_KEYS = {
|
||||
"display_name",
|
||||
"short_description",
|
||||
"icon_small",
|
||||
"icon_large",
|
||||
"brand_color",
|
||||
"default_prompt",
|
||||
}
|
||||
|
||||
|
||||
def yaml_quote(value):
|
||||
escaped = value.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n")
|
||||
return f'"{escaped}"'
|
||||
|
||||
|
||||
def format_display_name(skill_name):
|
||||
words = [word for word in skill_name.split("-") if word]
|
||||
formatted = []
|
||||
for index, word in enumerate(words):
|
||||
lower = word.lower()
|
||||
upper = word.upper()
|
||||
if upper in ACRONYMS:
|
||||
formatted.append(upper)
|
||||
continue
|
||||
if lower in BRANDS:
|
||||
formatted.append(BRANDS[lower])
|
||||
continue
|
||||
if index > 0 and lower in SMALL_WORDS:
|
||||
formatted.append(lower)
|
||||
continue
|
||||
formatted.append(word.capitalize())
|
||||
return " ".join(formatted)
|
||||
|
||||
|
||||
def generate_short_description(display_name):
|
||||
description = f"Help with {display_name} tasks"
|
||||
|
||||
if len(description) < 25:
|
||||
description = f"Help with {display_name} tasks and workflows"
|
||||
if len(description) < 25:
|
||||
description = f"Help with {display_name} tasks with guidance"
|
||||
|
||||
if len(description) > 64:
|
||||
description = f"Help with {display_name}"
|
||||
if len(description) > 64:
|
||||
description = f"{display_name} helper"
|
||||
if len(description) > 64:
|
||||
description = f"{display_name} tools"
|
||||
if len(description) > 64:
|
||||
suffix = " helper"
|
||||
max_name_length = 64 - len(suffix)
|
||||
trimmed = display_name[:max_name_length].rstrip()
|
||||
description = f"{trimmed}{suffix}"
|
||||
if len(description) > 64:
|
||||
description = description[:64].rstrip()
|
||||
|
||||
if len(description) < 25:
|
||||
description = f"{description} workflows"
|
||||
if len(description) > 64:
|
||||
description = description[:64].rstrip()
|
||||
|
||||
return description
|
||||
|
||||
|
||||
def read_frontmatter_name(skill_dir):
|
||||
skill_md = Path(skill_dir) / "SKILL.md"
|
||||
if not skill_md.exists():
|
||||
print(f"[ERROR] SKILL.md not found in {skill_dir}")
|
||||
return None
|
||||
content = skill_md.read_text()
|
||||
match = re.match(r"^---\n(.*?)\n---", content, re.DOTALL)
|
||||
if not match:
|
||||
print("[ERROR] Invalid SKILL.md frontmatter format.")
|
||||
return None
|
||||
frontmatter_text = match.group(1)
|
||||
|
||||
import yaml
|
||||
|
||||
try:
|
||||
frontmatter = yaml.safe_load(frontmatter_text)
|
||||
except yaml.YAMLError as exc:
|
||||
print(f"[ERROR] Invalid YAML frontmatter: {exc}")
|
||||
return None
|
||||
if not isinstance(frontmatter, dict):
|
||||
print("[ERROR] Frontmatter must be a YAML dictionary.")
|
||||
return None
|
||||
name = frontmatter.get("name", "")
|
||||
if not isinstance(name, str) or not name.strip():
|
||||
print("[ERROR] Frontmatter 'name' is missing or invalid.")
|
||||
return None
|
||||
return name.strip()
|
||||
|
||||
|
||||
def parse_interface_overrides(raw_overrides):
|
||||
overrides = {}
|
||||
optional_order = []
|
||||
for item in raw_overrides:
|
||||
if "=" not in item:
|
||||
print(f"[ERROR] Invalid interface override '{item}'. Use key=value.")
|
||||
return None, None
|
||||
key, value = item.split("=", 1)
|
||||
key = key.strip()
|
||||
value = value.strip()
|
||||
if not key:
|
||||
print(f"[ERROR] Invalid interface override '{item}'. Key is empty.")
|
||||
return None, None
|
||||
if key not in ALLOWED_INTERFACE_KEYS:
|
||||
allowed = ", ".join(sorted(ALLOWED_INTERFACE_KEYS))
|
||||
print(f"[ERROR] Unknown interface field '{key}'. Allowed: {allowed}")
|
||||
return None, None
|
||||
overrides[key] = value
|
||||
if (
|
||||
key not in ("display_name", "short_description")
|
||||
and key not in optional_order
|
||||
):
|
||||
optional_order.append(key)
|
||||
return overrides, optional_order
|
||||
|
||||
|
||||
def write_openai_yaml(skill_dir, skill_name, raw_overrides):
|
||||
overrides, optional_order = parse_interface_overrides(raw_overrides)
|
||||
if overrides is None:
|
||||
return None
|
||||
|
||||
display_name = overrides.get("display_name") or format_display_name(skill_name)
|
||||
short_description = overrides.get(
|
||||
"short_description"
|
||||
) or generate_short_description(display_name)
|
||||
|
||||
if not (25 <= len(short_description) <= 64):
|
||||
print(
|
||||
"[ERROR] short_description must be 25-64 characters "
|
||||
f"(got {len(short_description)})."
|
||||
)
|
||||
return None
|
||||
|
||||
interface_lines = [
|
||||
"interface:",
|
||||
f" display_name: {yaml_quote(display_name)}",
|
||||
f" short_description: {yaml_quote(short_description)}",
|
||||
]
|
||||
|
||||
for key in optional_order:
|
||||
value = overrides.get(key)
|
||||
if value is not None:
|
||||
interface_lines.append(f" {key}: {yaml_quote(value)}")
|
||||
|
||||
agents_dir = Path(skill_dir) / "agents"
|
||||
agents_dir.mkdir(parents=True, exist_ok=True)
|
||||
output_path = agents_dir / "openai.yaml"
|
||||
output_path.write_text("\n".join(interface_lines) + "\n")
|
||||
print(f"[OK] Created agents/openai.yaml")
|
||||
return output_path
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Create agents/openai.yaml for a skill directory.",
|
||||
)
|
||||
parser.add_argument("skill_dir", help="Path to the skill directory")
|
||||
parser.add_argument(
|
||||
"--name",
|
||||
help="Skill name override (defaults to SKILL.md frontmatter)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--interface",
|
||||
action="append",
|
||||
default=[],
|
||||
help="Interface override in key=value format (repeatable)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
skill_dir = Path(args.skill_dir).resolve()
|
||||
if not skill_dir.exists():
|
||||
print(f"[ERROR] Skill directory not found: {skill_dir}")
|
||||
sys.exit(1)
|
||||
if not skill_dir.is_dir():
|
||||
print(f"[ERROR] Path is not a directory: {skill_dir}")
|
||||
sys.exit(1)
|
||||
|
||||
skill_name = args.name or read_frontmatter_name(skill_dir)
|
||||
if not skill_name:
|
||||
sys.exit(1)
|
||||
|
||||
result = write_openai_yaml(skill_dir, skill_name, args.interface)
|
||||
if result:
|
||||
sys.exit(0)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
308
.codex-home/skills/.system/skill-creator/scripts/init_skill.py
Normal file
308
.codex-home/skills/.system/skill-creator/scripts/init_skill.py
Normal file
|
|
@ -0,0 +1,308 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Skill Initializer - Creates a new skill from template
|
||||
|
||||
Usage:
|
||||
init_skill.py <skill-name> --path <path> [--resources scripts,references,assets] [--examples] [--interface key=value]
|
||||
|
||||
Examples:
|
||||
init_skill.py my-new-skill --path skills/public
|
||||
init_skill.py my-new-skill --path skills/public --resources scripts,references
|
||||
init_skill.py my-api-helper --path skills/private --resources scripts --examples
|
||||
init_skill.py custom-skill --path /custom/location
|
||||
init_skill.py my-skill --path skills/public --interface short_description="Short UI label"
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from generate_openai_yaml import write_openai_yaml
|
||||
|
||||
MAX_SKILL_NAME_LENGTH = 64
|
||||
ALLOWED_RESOURCES = {"scripts", "references", "assets"}
|
||||
|
||||
SKILL_TEMPLATE = """---
|
||||
name: {skill_name}
|
||||
description: "[TODO: Briefly describe what this skill does and when it applies.]"
|
||||
---
|
||||
|
||||
# {skill_title}
|
||||
|
||||
[TODO: Add the task-specific guidance Codex needs. Reference supporting files only when they are relevant.]
|
||||
"""
|
||||
|
||||
EXAMPLE_SCRIPT = '''#!/usr/bin/env python3
|
||||
"""
|
||||
Example helper script for {skill_name}
|
||||
|
||||
This is a placeholder script that can be executed directly.
|
||||
Replace with actual implementation or delete if not needed.
|
||||
|
||||
Example real scripts from other skills:
|
||||
- pdf/scripts/fill_fillable_fields.py - Fills PDF form fields
|
||||
- pdf/scripts/convert_pdf_to_images.py - Converts PDF pages to images
|
||||
"""
|
||||
|
||||
def main():
|
||||
print("This is an example script for {skill_name}")
|
||||
# TODO: Add actual script logic here
|
||||
# This could be data processing, file conversion, API calls, etc.
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
'''
|
||||
|
||||
EXAMPLE_REFERENCE = """# Reference for {skill_title}
|
||||
|
||||
Replace this placeholder with maintained, task-specific details that Codex
|
||||
would not reliably know, such as operational constraints, local schemas, or
|
||||
fragile integration behavior.
|
||||
|
||||
Delete this file if no supported workflow needs it.
|
||||
"""
|
||||
|
||||
EXAMPLE_ASSET = """# Example Asset File
|
||||
|
||||
This placeholder represents where asset files would be stored.
|
||||
Replace with actual asset files (templates, images, fonts, etc.) or delete if not needed.
|
||||
|
||||
Asset files are NOT intended to be loaded into context, but rather used within
|
||||
the output Codex produces.
|
||||
|
||||
Example asset files from other skills:
|
||||
- Brand guidelines: logo.png, slides_template.pptx
|
||||
- Frontend builder: hello-world/ directory with HTML/React boilerplate
|
||||
- Typography: custom-font.ttf, font-family.woff2
|
||||
- Data: sample_data.csv, test_dataset.json
|
||||
|
||||
## Common Asset Types
|
||||
|
||||
- Templates: .pptx, .docx, boilerplate directories
|
||||
- Images: .png, .jpg, .svg, .gif
|
||||
- Fonts: .ttf, .otf, .woff, .woff2
|
||||
- Boilerplate code: Project directories, starter files
|
||||
- Icons: .ico, .svg
|
||||
- Data files: .csv, .json, .xml, .yaml
|
||||
|
||||
Note: This is a text placeholder. Actual assets can be any file type.
|
||||
"""
|
||||
|
||||
|
||||
def normalize_skill_name(skill_name):
|
||||
"""Normalize a skill name to lowercase hyphen-case."""
|
||||
normalized = skill_name.strip().lower()
|
||||
normalized = re.sub(r"[^a-z0-9]+", "-", normalized)
|
||||
normalized = normalized.strip("-")
|
||||
normalized = re.sub(r"-{2,}", "-", normalized)
|
||||
return normalized
|
||||
|
||||
|
||||
def title_case_skill_name(skill_name):
|
||||
"""Convert hyphenated skill name to Title Case for display."""
|
||||
return " ".join(word.capitalize() for word in skill_name.split("-"))
|
||||
|
||||
|
||||
def parse_resources(raw_resources):
|
||||
if not raw_resources:
|
||||
return []
|
||||
resources = [item.strip() for item in raw_resources.split(",") if item.strip()]
|
||||
invalid = sorted({item for item in resources if item not in ALLOWED_RESOURCES})
|
||||
if invalid:
|
||||
allowed = ", ".join(sorted(ALLOWED_RESOURCES))
|
||||
print(f"[ERROR] Unknown resource type(s): {', '.join(invalid)}")
|
||||
print(f" Allowed: {allowed}")
|
||||
sys.exit(1)
|
||||
deduped = []
|
||||
seen = set()
|
||||
for resource in resources:
|
||||
if resource not in seen:
|
||||
deduped.append(resource)
|
||||
seen.add(resource)
|
||||
return deduped
|
||||
|
||||
|
||||
def create_resource_dirs(
|
||||
skill_dir, skill_name, skill_title, resources, include_examples
|
||||
):
|
||||
for resource in resources:
|
||||
resource_dir = skill_dir / resource
|
||||
resource_dir.mkdir(exist_ok=True)
|
||||
if resource == "scripts":
|
||||
if include_examples:
|
||||
example_script = resource_dir / "example.py"
|
||||
example_script.write_text(EXAMPLE_SCRIPT.format(skill_name=skill_name))
|
||||
example_script.chmod(0o755)
|
||||
print("[OK] Created scripts/example.py")
|
||||
else:
|
||||
print("[OK] Created scripts/")
|
||||
elif resource == "references":
|
||||
if include_examples:
|
||||
example_reference = resource_dir / "api_reference.md"
|
||||
example_reference.write_text(
|
||||
EXAMPLE_REFERENCE.format(skill_title=skill_title)
|
||||
)
|
||||
print("[OK] Created references/api_reference.md")
|
||||
else:
|
||||
print("[OK] Created references/")
|
||||
elif resource == "assets":
|
||||
if include_examples:
|
||||
example_asset = resource_dir / "example_asset.txt"
|
||||
example_asset.write_text(EXAMPLE_ASSET)
|
||||
print("[OK] Created assets/example_asset.txt")
|
||||
else:
|
||||
print("[OK] Created assets/")
|
||||
|
||||
|
||||
def init_skill(skill_name, path, resources, include_examples, interface_overrides):
|
||||
"""
|
||||
Initialize a new skill directory with template SKILL.md.
|
||||
|
||||
Args:
|
||||
skill_name: Name of the skill
|
||||
path: Path where the skill directory should be created
|
||||
resources: Resource directories to create
|
||||
include_examples: Whether to create example files in resource directories
|
||||
|
||||
Returns:
|
||||
Path to created skill directory, or None if error
|
||||
"""
|
||||
# Determine skill directory path
|
||||
skill_dir = Path(path).resolve() / skill_name
|
||||
|
||||
# Check if directory already exists
|
||||
if skill_dir.exists():
|
||||
print(f"[ERROR] Skill directory already exists: {skill_dir}")
|
||||
return None
|
||||
|
||||
# Create skill directory
|
||||
try:
|
||||
skill_dir.mkdir(parents=True, exist_ok=False)
|
||||
print(f"[OK] Created skill directory: {skill_dir}")
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Error creating directory: {e}")
|
||||
return None
|
||||
|
||||
# Create SKILL.md from template
|
||||
skill_title = title_case_skill_name(skill_name)
|
||||
skill_content = SKILL_TEMPLATE.format(
|
||||
skill_name=skill_name, skill_title=skill_title
|
||||
)
|
||||
|
||||
skill_md_path = skill_dir / "SKILL.md"
|
||||
try:
|
||||
skill_md_path.write_text(skill_content)
|
||||
print("[OK] Created SKILL.md")
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Error creating SKILL.md: {e}")
|
||||
return None
|
||||
|
||||
# Create agents/openai.yaml
|
||||
try:
|
||||
result = write_openai_yaml(skill_dir, skill_name, interface_overrides)
|
||||
if not result:
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Error creating agents/openai.yaml: {e}")
|
||||
return None
|
||||
|
||||
# Create resource directories if requested
|
||||
if resources:
|
||||
try:
|
||||
create_resource_dirs(
|
||||
skill_dir, skill_name, skill_title, resources, include_examples
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Error creating resource directories: {e}")
|
||||
return None
|
||||
|
||||
# Print next steps
|
||||
print(f"\n[OK] Skill '{skill_name}' initialized successfully at {skill_dir}")
|
||||
print("\nNext steps:")
|
||||
print("1. Edit SKILL.md to complete the TODO items and update the description")
|
||||
if resources:
|
||||
if include_examples:
|
||||
print(
|
||||
"2. Customize or delete the example files in scripts/, references/, and assets/"
|
||||
)
|
||||
else:
|
||||
print("2. Add resources to scripts/, references/, and assets/ as needed")
|
||||
else:
|
||||
print(
|
||||
"2. Create resource directories only if needed (scripts/, references/, assets/)"
|
||||
)
|
||||
print("3. Update agents/openai.yaml if the UI metadata should differ")
|
||||
print("4. Run the validator when ready to check the skill structure")
|
||||
print(
|
||||
"5. Consider independent forward-testing only when complexity or risk warrants it"
|
||||
)
|
||||
|
||||
return skill_dir
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Create a new skill directory with a SKILL.md template.",
|
||||
)
|
||||
parser.add_argument("skill_name", help="Skill name (normalized to hyphen-case)")
|
||||
parser.add_argument("--path", required=True, help="Output directory for the skill")
|
||||
parser.add_argument(
|
||||
"--resources",
|
||||
default="",
|
||||
help="Comma-separated list: scripts,references,assets",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--examples",
|
||||
action="store_true",
|
||||
help="Create example files inside the selected resource directories",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--interface",
|
||||
action="append",
|
||||
default=[],
|
||||
help="Interface override in key=value format (repeatable)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
raw_skill_name = args.skill_name
|
||||
skill_name = normalize_skill_name(raw_skill_name)
|
||||
if not skill_name:
|
||||
print("[ERROR] Skill name must include at least one letter or digit.")
|
||||
sys.exit(1)
|
||||
if len(skill_name) > MAX_SKILL_NAME_LENGTH:
|
||||
print(
|
||||
f"[ERROR] Skill name '{skill_name}' is too long ({len(skill_name)} characters). "
|
||||
f"Maximum is {MAX_SKILL_NAME_LENGTH} characters."
|
||||
)
|
||||
sys.exit(1)
|
||||
if skill_name != raw_skill_name:
|
||||
print(f"Note: Normalized skill name from '{raw_skill_name}' to '{skill_name}'.")
|
||||
|
||||
resources = parse_resources(args.resources)
|
||||
if args.examples and not resources:
|
||||
print("[ERROR] --examples requires --resources to be set.")
|
||||
sys.exit(1)
|
||||
|
||||
path = args.path
|
||||
|
||||
print(f"Initializing skill: {skill_name}")
|
||||
print(f" Location: {path}")
|
||||
if resources:
|
||||
print(f" Resources: {', '.join(resources)}")
|
||||
if args.examples:
|
||||
print(" Examples: enabled")
|
||||
else:
|
||||
print(" Resources: none (create as needed)")
|
||||
print()
|
||||
|
||||
result = init_skill(skill_name, path, resources, args.examples, args.interface)
|
||||
|
||||
if result:
|
||||
sys.exit(0)
|
||||
else:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Quick validation script for skills - minimal version
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
MAX_SKILL_NAME_LENGTH = 64
|
||||
|
||||
|
||||
def validate_skill(skill_path):
|
||||
"""Basic validation of a skill"""
|
||||
skill_path = Path(skill_path)
|
||||
|
||||
skill_md = skill_path / "SKILL.md"
|
||||
if not skill_md.exists():
|
||||
return False, "SKILL.md not found"
|
||||
|
||||
content = skill_md.read_text()
|
||||
if not content.startswith("---"):
|
||||
return False, "No YAML frontmatter found"
|
||||
|
||||
match = re.match(r"^---\n(.*?)\n---", content, re.DOTALL)
|
||||
if not match:
|
||||
return False, "Invalid frontmatter format"
|
||||
|
||||
frontmatter_text = match.group(1)
|
||||
|
||||
try:
|
||||
frontmatter = yaml.safe_load(frontmatter_text)
|
||||
if not isinstance(frontmatter, dict):
|
||||
return False, "Frontmatter must be a YAML dictionary"
|
||||
except yaml.YAMLError as e:
|
||||
return False, f"Invalid YAML in frontmatter: {e}"
|
||||
|
||||
allowed_properties = {"name", "description", "license", "allowed-tools", "metadata"}
|
||||
|
||||
unexpected_keys = set(frontmatter.keys()) - allowed_properties
|
||||
if unexpected_keys:
|
||||
allowed = ", ".join(sorted(allowed_properties))
|
||||
unexpected = ", ".join(sorted(unexpected_keys))
|
||||
return (
|
||||
False,
|
||||
f"Unexpected key(s) in SKILL.md frontmatter: {unexpected}. Allowed properties are: {allowed}",
|
||||
)
|
||||
|
||||
if "name" not in frontmatter:
|
||||
return False, "Missing 'name' in frontmatter"
|
||||
if "description" not in frontmatter:
|
||||
return False, "Missing 'description' in frontmatter"
|
||||
|
||||
name = frontmatter.get("name", "")
|
||||
if not isinstance(name, str):
|
||||
return False, f"Name must be a string, got {type(name).__name__}"
|
||||
name = name.strip()
|
||||
if name:
|
||||
if not re.match(r"^[a-z0-9-]+$", name):
|
||||
return (
|
||||
False,
|
||||
f"Name '{name}' should be hyphen-case (lowercase letters, digits, and hyphens only)",
|
||||
)
|
||||
if name.startswith("-") or name.endswith("-") or "--" in name:
|
||||
return (
|
||||
False,
|
||||
f"Name '{name}' cannot start/end with hyphen or contain consecutive hyphens",
|
||||
)
|
||||
if len(name) > MAX_SKILL_NAME_LENGTH:
|
||||
return (
|
||||
False,
|
||||
f"Name is too long ({len(name)} characters). "
|
||||
f"Maximum is {MAX_SKILL_NAME_LENGTH} characters.",
|
||||
)
|
||||
|
||||
description = frontmatter.get("description", "")
|
||||
if not isinstance(description, str):
|
||||
return False, f"Description must be a string, got {type(description).__name__}"
|
||||
description = description.strip()
|
||||
if description.startswith("[TODO:"):
|
||||
return False, "Description contains an unfinished TODO placeholder"
|
||||
if description:
|
||||
if "<" in description or ">" in description:
|
||||
return False, "Description cannot contain angle brackets (< or >)"
|
||||
if len(description) > 1024:
|
||||
return (
|
||||
False,
|
||||
f"Description is too long ({len(description)} characters). Maximum is 1024 characters.",
|
||||
)
|
||||
|
||||
body = content[match.end() :]
|
||||
fence_marker = None
|
||||
fence_length = 0
|
||||
for line in body.splitlines():
|
||||
fence = re.match(r"^[ \t]*(?:(?:[-+*]|\d+[.)])[ \t]+)?(`{3,}|~{3,})(.*)$", line)
|
||||
if fence:
|
||||
marker = fence.group(1)
|
||||
if fence_marker is None:
|
||||
fence_marker = marker[0]
|
||||
fence_length = len(marker)
|
||||
elif (
|
||||
marker[0] == fence_marker
|
||||
and len(marker) >= fence_length
|
||||
and not fence.group(2).strip()
|
||||
):
|
||||
fence_marker = None
|
||||
fence_length = 0
|
||||
continue
|
||||
|
||||
if fence_marker is None and re.fullmatch(
|
||||
r"[ ]{0,3}\[TODO:[^\n]*\][ \t]*", line
|
||||
):
|
||||
return False, "Skill instructions contain an unfinished TODO placeholder"
|
||||
|
||||
return True, "Skill is valid!"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 2:
|
||||
print("Usage: python quick_validate.py <skill_directory>")
|
||||
sys.exit(1)
|
||||
|
||||
valid, message = validate_skill(sys.argv[1])
|
||||
print(message)
|
||||
sys.exit(0 if valid else 1)
|
||||
Loading…
Add table
Add a link
Reference in a new issue