From 237e5d8925b3b3cbcec697c10aaa4d611fa6eed7 Mon Sep 17 00:00:00 2001 From: Myfro9 <36755290+Myfro9@users.noreply.github.com> Date: Mon, 22 Jun 2026 16:40:24 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=86=E9=83=A8=E5=88=86sk?= =?UTF-8?q?ill?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 + .../references/installing-and-updating.md | 143 ++++++++++++++++++ .../scripts/read_marketplace_name.py | 48 ++++++ .../scripts/update_plugin_cachebuster.py | 78 ++++++++++ langgraph-plan-review/SKILL.md | 8 +- langgraph-plan-review/scripts/review_task.py | 15 +- vivado-prj-create/SKILL.md | 2 +- 7 files changed, 290 insertions(+), 7 deletions(-) create mode 100644 .system/plugin-creator/references/installing-and-updating.md create mode 100644 .system/plugin-creator/scripts/read_marketplace_name.py create mode 100644 .system/plugin-creator/scripts/update_plugin_cachebuster.py diff --git a/.gitignore b/.gitignore index 8e32840..0a1dc58 100644 --- a/.gitignore +++ b/.gitignore @@ -345,3 +345,6 @@ vercel-react-view-transitions/references/nextjs.md vercel-react-view-transitions/references/patterns.md vercel-react-view-transitions/SKILL.md web-design-guidelines/SKILL.md +*.mjs +*.pckl +*.pyc diff --git a/.system/plugin-creator/references/installing-and-updating.md b/.system/plugin-creator/references/installing-and-updating.md new file mode 100644 index 0000000..28b3b88 --- /dev/null +++ b/.system/plugin-creator/references/installing-and-updating.md @@ -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 \ + +``` + +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 \ + \ + --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 +``` + +3. Reinstall from that marketplace name: + +```bash +codex plugin add @ +``` + +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 @ +``` + +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 ++codex. +``` + +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 @`. +- For non-default marketplace files, use + `python3 scripts/read_marketplace_name.py --marketplace-path ` 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 ` before telling the user to run + `codex plugin add @`. +- 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. diff --git a/.system/plugin-creator/scripts/read_marketplace_name.py b/.system/plugin-creator/scripts/read_marketplace_name.py new file mode 100644 index 0000000..597e9f7 --- /dev/null +++ b/.system/plugin-creator/scripts/read_marketplace_name.py @@ -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 diff --git a/.system/plugin-creator/scripts/update_plugin_cachebuster.py b/.system/plugin-creator/scripts/update_plugin_cachebuster.py new file mode 100644 index 0000000..82d19e5 --- /dev/null +++ b/.system/plugin-creator/scripts/update_plugin_cachebuster.py @@ -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. 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 diff --git a/langgraph-plan-review/SKILL.md b/langgraph-plan-review/SKILL.md index faf0814..e841ee3 100644 --- a/langgraph-plan-review/SKILL.md +++ b/langgraph-plan-review/SKILL.md @@ -77,7 +77,13 @@ C:\Users\bowei\.claude\skills\langgraph-plan-review\scripts\manage_dev_server.ps ```powershell C:\Users\bowei\.claude\skills\langgraph-plan-review\scripts\manage_dev_server.ps1 start ``` - Wait for the "Ready" confirmation before proceeding. Open the Studio URL in a browser for real-time tracking. + Wait for the "[dev-server] Ready" line in the output. Then **immediately tell the user** in your text response: + - that the dev server is running, and + - the Studio URL: `https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024` + + Example message to user: + > LangGraph dev server 已就绪。可在浏览器中打开 Studio 实时监控工作流: + > https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024 3. Start or resume the LangGraph app: ```powershell # Use double backslashes (\\) in the JSON string so Windows paths are valid JSON. diff --git a/langgraph-plan-review/scripts/review_task.py b/langgraph-plan-review/scripts/review_task.py index c1c498b..eed1a1b 100644 --- a/langgraph-plan-review/scripts/review_task.py +++ b/langgraph-plan-review/scripts/review_task.py @@ -76,14 +76,17 @@ def resolve_codex() -> str: exe = os.environ.get("CODEX_EXE", "") if exe and Path(exe).exists(): return exe - # 2. PATH lookup + # 2. Known install paths (checked before PATH to avoid picking up wrong codex variant) + for candidate in [ + r"C:\Users\bowei\miniconda3\envs\langgraph\codex.cmd", + r"C:\Users\bowei\.conda\envs\langgraph\codex.cmd", + ]: + if Path(candidate).exists(): + return candidate + # 3. PATH lookup fallback found = shutil.which("codex") or shutil.which("codex.cmd") if found: return found - # 3. Hardcoded fallback for the original developer machine - fallback = r"C:\Users\bowei\.conda\envs\langgraph\codex.cmd" - if Path(fallback).exists(): - return fallback raise FileNotFoundError( "Cannot find codex executable. Set CODEX_EXE environment variable or add codex to PATH." ) @@ -128,6 +131,8 @@ def main() -> int: command_args, capture_output=True, text=True, + encoding="utf-8", + errors="replace", timeout=timeout, input=prompt, cwd=work_dir, diff --git a/vivado-prj-create/SKILL.md b/vivado-prj-create/SKILL.md index adb22e6..3e04a4a 100644 --- a/vivado-prj-create/SKILL.md +++ b/vivado-prj-create/SKILL.md @@ -1,6 +1,6 @@ --- name: vivado-prj-create -description: DIRECT EXECUTION - Auto-create Vivado FPGA projects. Triggers: "create Vivado project", "Vivado工程", "FPGA project", "Xilinx project", "setup Vivado". When triggered, IMMEDIATELY execute Step 0 without listing skills or asking for confirmation. +description: 'DIRECT EXECUTION - Auto-create Vivado FPGA projects. Triggers: "create Vivado project", "Vivado工程", "FPGA project", "Xilinx project", "setup Vivado". When triggered, IMMEDIATELY execute Step 0 without listing skills or asking for confirmation.' --- # Vivado Project TCL Generator