更新了部分skill
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
# Updating Existing Local Plugins
|
||||
|
||||
Use this reference when a plugin already exists and the request is about updating the plugin during
|
||||
local development.
|
||||
|
||||
All scripts here are specified relative to the skill root. Update the path for running the scripts
|
||||
depending on your current working directory.
|
||||
|
||||
## When To Use This Flow
|
||||
|
||||
Use this flow when all of the following are true:
|
||||
|
||||
- the plugin already exists locally
|
||||
- the marketplace entry already points at the plugin source you are editing
|
||||
- the user wants Codex to see the updated plugin without manually editing marketplace files
|
||||
|
||||
If the user still needs the initial plugin entry or marketplace structure created, use the scaffold
|
||||
flow first and only then switch to this reinstall flow.
|
||||
|
||||
## Update Loop
|
||||
|
||||
1. Update the plugin manifest to a single Codex cachebuster suffix:
|
||||
|
||||
```bash
|
||||
python3 scripts/update_plugin_cachebuster.py \
|
||||
<plugin-path>
|
||||
```
|
||||
|
||||
Prefer the default helper behavior here. If you omit `--cachebuster`, the helper uses a UTC
|
||||
timestamp down to seconds, which is the recommended path for routine local iteration.
|
||||
|
||||
Only use a manual cachebuster override when the user explicitly asks for one or when a workflow
|
||||
outside Codex depends on a specific token:
|
||||
|
||||
```bash
|
||||
python3 scripts/update_plugin_cachebuster.py \
|
||||
<plugin-path> \
|
||||
--cachebuster local-20260519-184516
|
||||
```
|
||||
|
||||
2. For the default scaffolded flow, read the marketplace name from the personal marketplace file:
|
||||
|
||||
```bash
|
||||
python3 scripts/read_marketplace_name.py
|
||||
```
|
||||
|
||||
Here, "personal marketplace" means the marketplace whose file is at
|
||||
`~/.agents/plugins/marketplace.json`. On Windows, use the equivalent path under the user profile.
|
||||
The helper uses Python's home-directory resolution and prints the marketplace name to use when
|
||||
constructing the install command.
|
||||
|
||||
To read the name from a different marketplace file, pass the path directly:
|
||||
|
||||
```bash
|
||||
python3 scripts/read_marketplace_name.py --marketplace-path <path-to-marketplace.json>
|
||||
```
|
||||
|
||||
3. Reinstall from that marketplace name:
|
||||
|
||||
```bash
|
||||
codex plugin add <plugin-name>@<marketplace-name-from-marketplace-json>
|
||||
```
|
||||
|
||||
The default personal marketplace is discovered implicitly from
|
||||
`~/.agents/plugins/marketplace.json`. You do not need `codex plugin marketplace add` for that
|
||||
path, and `codex plugin marketplace list` is not the right check for whether that default
|
||||
marketplace exists.
|
||||
|
||||
4. If the plugin is not using the personal marketplace file, check which configured local
|
||||
marketplace is actually surfacing that plugin:
|
||||
|
||||
```bash
|
||||
codex plugin list
|
||||
```
|
||||
|
||||
If the plugin is not in the personal marketplace file, confirm which marketplace entry points at
|
||||
the plugin source you are editing and make sure that marketplace is still local. If it is a
|
||||
different local marketplace, reinstall from that marketplace name instead of forcing the personal
|
||||
marketplace flow. If it is not local, stop and help the user resolve the mismatch before
|
||||
continuing.
|
||||
|
||||
5. If the plugin lives in a different confirmed local marketplace, substitute that marketplace
|
||||
name:
|
||||
|
||||
```bash
|
||||
codex plugin add <plugin-name>@<local-marketplace>
|
||||
```
|
||||
|
||||
6. Prompt the user to use a new thread to try the updated plugin, so that Codex picks up new skills
|
||||
and tools.
|
||||
|
||||
## Cachebuster Policy
|
||||
|
||||
- Preserve the existing version prefix and replace only the suffix.
|
||||
- Treat the preserved prefix as everything before `+`.
|
||||
- Use the format:
|
||||
|
||||
```text
|
||||
<base-version>+codex.<cachebuster>
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
- `0.1.0` → `0.1.0+codex.local-20260519-184516`
|
||||
- `0.1.0+codex.old-token` → `0.1.0+codex.local-20260519-184516`
|
||||
- `1.2.3-beta.1+codex.prev` → `1.2.3-beta.1+codex.local-20260519-184516`
|
||||
- `dev-build+other-tag` → `dev-build+codex.local-20260519-184516`
|
||||
|
||||
Replace the existing Codex cachebuster instead of appending another one. Do not keep incrementing
|
||||
numeric version components just to trigger reinstall behavior.
|
||||
|
||||
## Marketplace Rules
|
||||
|
||||
- Marketplace manipulation should happen through commands, not by hand-editing `marketplace.json`
|
||||
or `config.toml` during this update/reinstall flow.
|
||||
- Prefer the personal marketplace file for the default scaffolded flow.
|
||||
- Read the personal marketplace name with
|
||||
`python3 scripts/read_marketplace_name.py` and use the printed value when constructing
|
||||
`codex plugin add <plugin-name>@<marketplace-name>`.
|
||||
- For non-default marketplace files, use
|
||||
`python3 scripts/read_marketplace_name.py --marketplace-path <path-to-marketplace.json>` to read
|
||||
the name before constructing reinstall commands.
|
||||
- Do not tell the user to run `codex plugin marketplace add` for the default personal-marketplace
|
||||
flow. That marketplace is discovered implicitly by Codex.
|
||||
- If the user specified a different marketplace path, make sure that marketplace is installed
|
||||
before giving install or reinstall instructions. Non-default marketplace paths are not
|
||||
discovered implicitly.
|
||||
- Use `codex plugin list` when the plugin lives in a different configured marketplace and you need
|
||||
to confirm which marketplace is surfacing that plugin.
|
||||
- If a non-default local marketplace has not been configured yet, install it with
|
||||
`codex plugin marketplace add <path-to-marketplace-root>` before telling the user to run
|
||||
`codex plugin add <plugin-name>@<marketplace-name>`.
|
||||
- If the plugin is not in the personal marketplace file, confirm that the selected marketplace is
|
||||
local before telling the user to reinstall from it.
|
||||
- If the selected marketplace is not local, stop and help the user resolve that mismatch rather
|
||||
than pretending the normal local reinstall flow applies.
|
||||
- If the plugin source is not already the source referenced by the chosen marketplace entry, stop
|
||||
and fix that first. This update flow does not rewrite marketplace entries.
|
||||
|
||||
## After Reinstall
|
||||
|
||||
After reinstalling, prompt the user to start a new thread for testing. That is the safe boundary for
|
||||
picking up the updated plugin and its MCP tools.
|
||||
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Print the top-level marketplace name from any marketplace.json file."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def default_marketplace_path() -> Path:
|
||||
return Path.home() / ".agents" / "plugins" / "marketplace.json"
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description=(
|
||||
"Print the top-level marketplace name from marketplace.json. Defaults to the personal "
|
||||
"marketplace path under the current home directory."
|
||||
)
|
||||
)
|
||||
parser.add_argument(
|
||||
"--marketplace-path",
|
||||
default=str(default_marketplace_path()),
|
||||
help="Path to marketplace.json",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
marketplace_path = Path(args.marketplace_path).expanduser().resolve()
|
||||
payload = json.loads(marketplace_path.read_text(encoding="utf-8"))
|
||||
if not isinstance(payload, dict):
|
||||
raise ValueError(f"{marketplace_path} must contain a JSON object.")
|
||||
name = payload.get("name")
|
||||
if not isinstance(name, str) or not name.strip():
|
||||
raise ValueError(f"{marketplace_path} must contain a non-empty string 'name'.")
|
||||
print(name.strip())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except Exception as err: # noqa: BLE001 - CLI should surface a single clear message.
|
||||
print(str(err), file=sys.stderr)
|
||||
raise SystemExit(1) from err
|
||||
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Rewrite a local plugin version to a single Codex cachebuster suffix."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
CACHEBUSTER_PREFIX = "codex"
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description=(
|
||||
"Rewrite a local plugin's version so it preserves everything before '+' and uses "
|
||||
"a single +codex.<cachebuster> suffix."
|
||||
)
|
||||
)
|
||||
parser.add_argument("plugin_path", help="Path to the plugin root directory")
|
||||
parser.add_argument(
|
||||
"--cachebuster",
|
||||
help="Optional cachebuster token to embed in the plugin version",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
plugin_root = Path(args.plugin_path).expanduser().resolve()
|
||||
manifest_path = plugin_root / ".codex-plugin" / "plugin.json"
|
||||
manifest = load_manifest(manifest_path)
|
||||
|
||||
version = manifest.get("version")
|
||||
if not isinstance(version, str) or not version.strip():
|
||||
raise ValueError(f"{manifest_path} must contain a non-empty string 'version'.")
|
||||
cachebuster = sanitize_cachebuster(args.cachebuster or default_cachebuster())
|
||||
next_version = with_cachebuster(version, cachebuster)
|
||||
manifest["version"] = next_version
|
||||
manifest_path.write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8")
|
||||
|
||||
print(f"Updated plugin version: {version} -> {next_version}")
|
||||
|
||||
|
||||
def load_manifest(manifest_path: Path) -> dict[str, object]:
|
||||
if not manifest_path.is_file():
|
||||
raise FileNotFoundError(f"missing manifest: {manifest_path}")
|
||||
payload = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||
if not isinstance(payload, dict):
|
||||
raise ValueError(f"{manifest_path} must contain a JSON object.")
|
||||
return payload
|
||||
def sanitize_cachebuster(value: str) -> str:
|
||||
sanitized = re.sub(r"[^a-z0-9-]+", "-", value.strip().lower())
|
||||
sanitized = re.sub(r"-{2,}", "-", sanitized).strip("-")
|
||||
if not sanitized:
|
||||
raise ValueError("Cachebuster must contain at least one letter or digit.")
|
||||
return sanitized
|
||||
|
||||
|
||||
def default_cachebuster() -> str:
|
||||
return datetime.now(timezone.utc).strftime("%Y%m%d%H%M%S")
|
||||
|
||||
|
||||
def with_cachebuster(version: str, cachebuster: str) -> str:
|
||||
version_prefix = version.split("+", 1)[0]
|
||||
return f"{version_prefix}+{CACHEBUSTER_PREFIX}.{cachebuster}"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except Exception as err: # noqa: BLE001 - CLI should surface a single clear message.
|
||||
print(str(err), file=sys.stderr)
|
||||
raise SystemExit(1) from err
|
||||
Reference in New Issue
Block a user