From 35dd0e4aaaea8e01ac915f87342394c7471da25d Mon Sep 17 00:00:00 2001 From: rob163 <111229443+rob163@users.noreply.github.com> Date: Mon, 8 Jun 2026 18:34:20 +0800 Subject: [PATCH] Support plugin and clone install paths --- .agents/plugins/marketplace.json | 20 + .claude-plugin/marketplace.json | 19 + .claude-plugin/plugin.json | 12 + README.md | 89 +- codex/.codex-plugin/plugin.json | 18 + codex/skills | 1 + scripts/validate-skill.mjs | 61 +- skills/cli-panel/SKILL.md | 201 ++++ skills/cli-panel/assets/cli-panel.html | 1159 ++++++++++++++++++++++++ 9 files changed, 1563 insertions(+), 17 deletions(-) create mode 100644 .agents/plugins/marketplace.json create mode 100644 .claude-plugin/marketplace.json create mode 100644 .claude-plugin/plugin.json create mode 100644 codex/.codex-plugin/plugin.json create mode 120000 codex/skills create mode 100644 skills/cli-panel/SKILL.md create mode 100644 skills/cli-panel/assets/cli-panel.html diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json new file mode 100644 index 0000000..8cff488 --- /dev/null +++ b/.agents/plugins/marketplace.json @@ -0,0 +1,20 @@ +{ + "name": "html-as-cli-panel-skill", + "interface": { + "displayName": "HTML as CLI Panel Skill" + }, + "plugins": [ + { + "name": "html-as-cli-panel-skill", + "source": { + "source": "local", + "path": "./codex" + }, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_INSTALL" + }, + "category": "Productivity" + } + ] +} diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json new file mode 100644 index 0000000..e466b76 --- /dev/null +++ b/.claude-plugin/marketplace.json @@ -0,0 +1,19 @@ +{ + "name": "html-as-cli-panel-skill", + "owner": { + "name": "rob163" + }, + "metadata": { + "description": "Agent skill for creating static, copy-only HTML command panels from repeat-use CLI workflows." + }, + "plugins": [ + { + "name": "html-as-cli-panel-skill", + "source": { + "source": "github", + "repo": "rob163/html-as-cli-panel-skill" + }, + "description": "Create localStorage-backed HTML command panels for project CLI workflows." + } + ] +} diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..78595ed --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,12 @@ +{ + "name": "html-as-cli-panel-skill", + "description": "Agent skill for creating static, copy-only HTML command panels from repeat-use CLI workflows.", + "version": "0.1.0", + "author": { + "name": "rob163", + "url": "https://github.com/rob163" + }, + "homepage": "https://github.com/rob163/html-as-cli-panel-skill", + "repository": "https://github.com/rob163/html-as-cli-panel-skill", + "license": "MIT" +} diff --git a/README.md b/README.md index 0d99cf1..fe784d0 100644 --- a/README.md +++ b/README.md @@ -32,39 +32,96 @@ do not need to install it separately — `npx skills` runs it on demand. Use the installer so the target agent receives the correct layout. This avoids fragile file-placement mistakes. -Install into the current project (shared with the repo via git): +### skills CLI + +Install into the current project: ```bash -npx skills add rob163/html-as-cli-panel-skill --agent cursor +npx skills add rob163/html-as-cli-panel-skill ``` -Install globally for one agent (available in every project on your machine): +Install for a specific agent: ```bash -npx skills add rob163/html-as-cli-panel-skill --global --agent cursor +npx skills add rob163/html-as-cli-panel-skill --agent codex +npx skills add rob163/html-as-cli-panel-skill --agent claude-code ``` -Replace `cursor` with your agent: `codex`, `claude-code`, and others are -supported. Omit `--agent` only if you want the CLI to auto-detect installed -agents or prompt you to choose. - -Other examples: +Install globally instead of project-local: ```bash -# Codex, project-local -npx skills add rob163/html-as-cli-panel-skill --agent codex - -# Claude Code, global +npx skills add rob163/html-as-cli-panel-skill --global --agent codex npx skills add rob163/html-as-cli-panel-skill --global --agent claude-code +``` + +Skip confirmation prompts when installing in scripts: -# Skip confirmation prompts (useful in scripts) -npx skills add rob163/html-as-cli-panel-skill --global --agent cursor --yes +```bash +npx skills add rob163/html-as-cli-panel-skill --global --agent codex --yes ``` Verify installation: ```bash -npx skills ls -g -a cursor +npx skills ls -g -a codex +``` + +### Codex plugin + +Codex can install this repository as a plugin marketplace. This path uses the +checked-in `.agents/plugins/marketplace.json` and `codex/.codex-plugin/plugin.json` +files, which expose the same `cli-panel` skill through Codex's plugin system. + +```bash +codex plugin marketplace add rob163/html-as-cli-panel-skill +codex plugin add html-as-cli-panel-skill@html-as-cli-panel-skill +``` + +For a local clone: + +```bash +git clone https://github.com/rob163/html-as-cli-panel-skill +codex plugin marketplace add ./html-as-cli-panel-skill +codex plugin add html-as-cli-panel-skill@html-as-cli-panel-skill +``` + +Restart Codex or start a new thread if the installed skill does not appear +immediately. + +### Claude Code plugin + +Install as a versioned Claude Code plugin from inside Claude Code: + +```text +/plugin marketplace add rob163/html-as-cli-panel-skill +/plugin install html-as-cli-panel-skill@html-as-cli-panel-skill +``` + +The plugin uses the `skills/cli-panel` package in this repository, so the +Claude Code plugin and direct skill installs stay aligned. + +### git clone + +Clone directly into the agent's skills directory when you prefer a simple +manual install. + +Codex: + +```bash +git clone https://github.com/rob163/html-as-cli-panel-skill "${CODEX_HOME:-$HOME/.codex}/skills/cli-panel" +``` + +Claude Code: + +```bash +git clone https://github.com/rob163/html-as-cli-panel-skill ~/.claude/skills/cli-panel +``` + +Restart the agent after cloning. To update later: + +```bash +git -C "${CODEX_HOME:-$HOME/.codex}/skills/cli-panel" pull +git -C ~/.claude/skills/cli-panel pull ``` ## Development diff --git a/codex/.codex-plugin/plugin.json b/codex/.codex-plugin/plugin.json new file mode 100644 index 0000000..a0466d9 --- /dev/null +++ b/codex/.codex-plugin/plugin.json @@ -0,0 +1,18 @@ +{ + "name": "html-as-cli-panel-skill", + "version": "0.1.0", + "description": "Agent skill for creating static, copy-only HTML command panels from repeat-use CLI workflows.", + "author": { + "name": "rob163", + "url": "https://github.com/rob163" + }, + "homepage": "https://github.com/rob163/html-as-cli-panel-skill", + "repository": "https://github.com/rob163/html-as-cli-panel-skill", + "license": "MIT", + "skills": "./skills/", + "interface": { + "displayName": "HTML as CLI Panel Skill", + "shortDescription": "Create static HTML command panels for project CLI workflows.", + "category": "Productivity" + } +} diff --git a/codex/skills b/codex/skills new file mode 120000 index 0000000..42c5394 --- /dev/null +++ b/codex/skills @@ -0,0 +1 @@ +../skills \ No newline at end of file diff --git a/scripts/validate-skill.mjs b/scripts/validate-skill.mjs index a3c1725..89bb054 100644 --- a/scripts/validate-skill.mjs +++ b/scripts/validate-skill.mjs @@ -3,7 +3,14 @@ import { readFile, access } from "node:fs/promises"; const requiredFiles = [ "SKILL.md", "assets/cli-panel.html", - "LICENSE" + "LICENSE", + ".agents/plugins/marketplace.json", + ".claude-plugin/marketplace.json", + ".claude-plugin/plugin.json", + "codex/.codex-plugin/plugin.json", + "codex/skills", + "skills/cli-panel/SKILL.md", + "skills/cli-panel/assets/cli-panel.html" ]; async function assertFile(path) { @@ -66,6 +73,56 @@ async function assertTemplate() { } } +async function assertJson(path) { + const raw = await readFile(path, "utf8"); + try { + return JSON.parse(raw); + } catch (error) { + throw new Error(`${path} must contain valid JSON: ${error.message}`); + } +} + +async function assertMirroredSkillPackage() { + const rootSkill = await readFile("SKILL.md", "utf8"); + const pluginSkill = await readFile("skills/cli-panel/SKILL.md", "utf8"); + if (rootSkill !== pluginSkill) { + throw new Error("skills/cli-panel/SKILL.md must match root SKILL.md"); + } + + const rootTemplate = await readFile("assets/cli-panel.html", "utf8"); + const pluginTemplate = await readFile("skills/cli-panel/assets/cli-panel.html", "utf8"); + if (rootTemplate !== pluginTemplate) { + throw new Error("skills/cli-panel/assets/cli-panel.html must match assets/cli-panel.html"); + } +} + +async function assertPluginMetadata() { + const codexPlugin = await assertJson("codex/.codex-plugin/plugin.json"); + if (codexPlugin.name !== "html-as-cli-panel-skill") { + throw new Error("Codex plugin name must be html-as-cli-panel-skill"); + } + if (codexPlugin.skills !== "./skills/") { + throw new Error("Codex plugin skills path must point to ./skills/"); + } + + const codexMarketplace = await assertJson(".agents/plugins/marketplace.json"); + const codexEntry = codexMarketplace.plugins?.[0]; + if (codexEntry?.source?.path !== "./codex") { + throw new Error("Codex marketplace source.path must be ./codex"); + } + + const claudePlugin = await assertJson(".claude-plugin/plugin.json"); + if (claudePlugin.name !== "html-as-cli-panel-skill") { + throw new Error("Claude plugin name must be html-as-cli-panel-skill"); + } + + const claudeMarketplace = await assertJson(".claude-plugin/marketplace.json"); + const claudeEntry = claudeMarketplace.plugins?.[0]; + if (claudeEntry?.source?.repo !== "rob163/html-as-cli-panel-skill") { + throw new Error("Claude plugin marketplace repo must be rob163/html-as-cli-panel-skill"); + } +} + for (const file of requiredFiles) { await assertFile(file); } @@ -73,5 +130,7 @@ for (const file of requiredFiles) { const skill = await readFile("SKILL.md", "utf8"); assertFrontmatter(parseFrontmatter(skill)); await assertTemplate(); +await assertMirroredSkillPackage(); +await assertPluginMetadata(); console.log("Skill package validation passed."); diff --git a/skills/cli-panel/SKILL.md b/skills/cli-panel/SKILL.md new file mode 100644 index 0000000..0eab68a --- /dev/null +++ b/skills/cli-panel/SKILL.md @@ -0,0 +1,201 @@ +--- +name: cli-panel +description: > + Create or update a static localStorage-backed HTML page that turns a project's + repeat-use CLI commands into editable, copyable command panels. +version: 0.1.0 +author: rob163 +license: MIT +homepage: https://github.com/rob163/html-as-cli-panel-skill +when_to_use: > + Use when a project would benefit from a static HTML command panel for + repeat-use CLI workflows, editable parameters, or copyable command recipes. +tags: + - cli + - developer-tools + - html + - workflow +--- + +# CLI Panel + +## Overview + +Create a static `cli-panel.html` in a target project root from +`assets/cli-panel.html`. + +The page is a copy-only command builder. It stores command definitions in browser +`localStorage`, lets users edit command names, groups, effects, fixed prefixes, +and parameters, then builds copyable command strings. + +## Workflow + +1. Copy `assets/cli-panel.html` to the target project root unless the user + requests another path. +2. Inspect the target project's code and files for repeat-use commands before + filling the panel. Do not mirror every README command. +3. Replace `defaultState.commands` with a curated set. Omit one-time setup and + trivial fixed one-liners with no meaningful parameters. Merge related setup + steps into one card when needed. +4. Keep the eyebrow text `HTML as CLI Panel` unchanged. Replace the visible page + title `CLI Panel` with a title that matches the target project. +5. Replace `storageKey` and `defaultsKey` with names derived from the target + project, such as a lowercase slug, so different projects do not collide in + browser `localStorage`. +6. Keep the template UI English unless the user asks for another language. +7. Validate the panel using the checks below. + +## Command Model + +Each default command must use this shape inside `defaultState.commands`: + +```js +{ + type: "Workflow", + label: "Run scenario", + description: "What this command does and when to use it.", + fixedText: "mytool run", + segments: [ + { name: "--mode", value: "dry-run", widget: "text" }, + { name: "--repeat", value: "3", widget: "number" }, + { name: "", value: "./input.json", widget: "text" } + ] +} +``` + +Fields: + +- `type`: tab or group label, such as a workflow category. +- `label`: short command name shown as the card title. +- `description`: what the command does and when to use it. +- `fixedText`: the non-editable command prefix or complete base command. +- `segments`: editable parameters. +- `segments[].name`: option or argument label. Leave empty only for positional + values. +- `segments[].value`: option value. Leave empty for flags that do not take a + value. +- `segments[].widget`: use `"text"` by default, or `"number"` for numeric + fields. + +## Splitting `fixedText` and `segments` + +Think in three layers: + +| Layer | Goes in | Meaning | +| ----- | ------- | ------- | +| Tool family | start of `fixedText` | The main executable or entrypoint | +| Card identity | rest of `fixedText` | The shortest stable prefix that tells sibling cards apart | +| Runtime config | `segments` | Per-run values such as paths, ports, credentials, counts, and filters | + +Rules: + +- `fixedText` stops at the last token that is the same for every sibling card + sharing that prefix and is not an option flag. +- Everything after the card identity belongs in `segments`, unless the whole + command is a fixed recipe with no useful editable surface. +- Option flags are never part of card identity unless the entire flag, including + its value when present, is non-editable. +- Keep each option together as paired segment `name` and `value`; do not split + one flag across `fixedText` and `segments`. +- Positional arguments use an empty `name` and put the value in `value`. +- Fixed recipes can put the full command in `fixedText` and leave `segments` + empty, but do not add a card solely because it is fixed. + +Examples: + +```text +# Good: card identity in fixedText; options in segments. +fixedText: python -m myapp.cli deploy +segments: [--env, staging] [--region, us-east-1] + +# Bad: split flag. +fixedText: python -m myapp.cli deploy --env +segments: [staging] + +# Good: fixed recipe. +fixedText: npm ci +segments: [] + +# Good: positional argument. +fixedText: mytool import +segments: [, ./data/input.csv] +``` + +When filling a real project, derive names and parameters from that project's +command definitions. Do not copy these examples into a target panel. + +## Segment Order + +Preview output follows `fixedText`, then `segments` in array order. + +1. Put semantic segments first, such as `--mode`, `--target`, marker names, or + sub-operation flags. +2. Put runtime configuration after semantic segments, such as paths, ports, + credentials, and repeat counts. +3. Preserve CLI-required positional order. +4. Treat README order as reference, not law, when the target CLI accepts flexible + flag order. + +## Validation + +Validation has two layers. Both matter. + +### Structural Compliance + +Every card must pass all checks: + +- Each option is either fully in `fixedText` or fully in `segments`. +- No flag name in `fixedText` has its value in `segments`. +- Positional values use empty `name` and non-empty `value`, unless intentionally + omitted. +- `fixedText` contains only tool family plus card identity tokens, not partial + options. + +### Semantic Equivalence + +Each preview must represent the same operation as the project's documented +command, using default segment values. + +Semantic equivalence does not require byte-for-byte string equality, identical +flag order when the CLI accepts reordering, or identical quoting style when both +forms are valid. + +Semantic equivalence does require the same executable, module path, subcommand +entrypoint, flags, positional arguments, and default values. + +### Recommended Validation Pass + +1. Pick representative cards, including reordered segments and empty + `segments`. +2. Open the HTML or run the panel preview builder; confirm each preview is + non-empty and shell-valid. +3. Apply structural compliance to every default command. +4. Apply semantic equivalence against project docs, not literal string equality. +5. Confirm high-impact editable fields appear early in `segments`. + +## Updating Existing Panels + +When modifying an existing `cli-panel.html`: + +- Preserve user-added command groups, labels, descriptions, and parameter values + when they still map to the current CLI. +- Update `fixedText` and parameter names to match the latest project command + format. +- Remove stale parameters only when they no longer exist or are actively + misleading. +- Add new required parameters with safe placeholder values. +- Keep existing project-specific `localStorage` keys stable unless the user asks + for a clean reset. +- If the panel still uses template keys, replace them with project-specific + keys. + +## Defaults + +The page has two persistent states: + +- Current state: saved automatically under the page's `storageKey`. +- Saved default: set by the page's `Set as default` button under `defaultsKey`. + +When preparing a project-specific panel, edit `defaultState` in the HTML so a +fresh browser starts with useful commands. Users can later make browser-local +edits and set a new default from the UI. diff --git a/skills/cli-panel/assets/cli-panel.html b/skills/cli-panel/assets/cli-panel.html new file mode 100644 index 0000000..bb826c4 --- /dev/null +++ b/skills/cli-panel/assets/cli-panel.html @@ -0,0 +1,1159 @@ + + + + + + HTML as CLI Panel + + + +
+
+
+

HTML as CLI Panel

+

CLI Panel

+

A static browser panel for project commands. It stores commands in this browser's localStorage, builds copyable CLI strings.

+
+
+ +
+ + +
+
+
+
+ + +
+
+ +
+
+
+
+ +
+ + + +