Files
zzet--gortex/cmd/gortex/plugin.go
T
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

116 lines
3.8 KiB
Go

package main
import (
"fmt"
"path/filepath"
"strings"
"github.com/spf13/cobra"
"github.com/zzet/gortex/internal/agents/claudecode"
semver "github.com/zzet/gortex/internal/version"
)
var (
pluginEmitTarget string
pluginEmitVariant string
pluginEmitVersion string
)
var pluginCmd = &cobra.Command{
Use: "plugin",
Short: "Manage Gortex marketplace plugin bundles",
Long: `Generate plugin bundles for agent-host marketplaces.
Today this command emits the Anthropic Plugin Marketplace layout —
a directory containing plugin.json, README, LICENSE, .mcp.json,
slash commands, skills, and hooks. The Cursor variant lands in a
follow-up.
The marketplace bundle is generated, not hand-maintained. The single
source of truth lives in internal/agents/claudecode/content.go;
this command serialises that content into the directory layout the
marketplace expects.`,
}
var pluginEmitCmd = &cobra.Command{
Use: "emit",
Short: "Emit a plugin bundle into a target directory",
Long: `Emit the marketplace plugin bundle.
Example:
gortex plugin emit --target ./claude-plugin --variant anthropic --version 0.18.2
The target directory is created if missing; existing files are
overwritten so re-runs converge on a deterministic output.`,
RunE: runPluginEmit,
}
func init() {
pluginEmitCmd.Flags().StringVar(&pluginEmitTarget, "target", "", "target directory to emit the bundle into (required)")
pluginEmitCmd.Flags().StringVar(&pluginEmitVariant, "variant", string(claudecode.LayoutVariantAnthropic), "marketplace layout variant: anthropic")
pluginEmitCmd.Flags().StringVar(&pluginEmitVersion, "version", "", "gortex SemVer to bake into plugin.json (defaults to the running binary's version)")
pluginCmd.AddCommand(pluginEmitCmd)
rootCmd.AddCommand(pluginCmd)
}
func runPluginEmit(_ *cobra.Command, _ []string) error {
if pluginEmitTarget == "" {
return fmt.Errorf("--target is required")
}
v := pluginEmitVersion
if v == "" {
v = cleanSemver(version)
}
abs, err := filepath.Abs(pluginEmitTarget)
if err != nil {
return fmt.Errorf("resolve target dir: %w", err)
}
written, err := claudecode.EmitPluginBundle(claudecode.PluginBundleSpec{
TargetDir: abs,
Version: v,
LayoutVariant: claudecode.LayoutVariant(pluginEmitVariant),
})
if err != nil {
return err
}
fmt.Fprintf(rootCmd.OutOrStdout(), "Wrote %d files to %s (variant=%s, version=%s)\n", len(written), abs, pluginEmitVariant, v)
return nil
}
// cleanSemver normalises a build-time-injected version string into the
// `MAJOR.MINOR.PATCH` form the marketplace plugin.json expects. The
// running binary's `main.version` may be either a clean semver from
// main.go (e.g. "0.18.2") or git-describe output with build slots
// (e.g. "v0.18.2-1-ga4997e7-dirty") depending on whether ldflags were
// injected. We strip the leading 'v', drop any prerelease/build slot,
// and drop a trailing `-N-g<sha>[-dirty]` git-describe suffix.
//
// On parse failure we return the raw input; the marketplace publish
// step rejects bad versions in its own schema validation, so the
// upstream PR review catches anything we miss here.
func cleanSemver(s string) string {
if s == "" {
return s
}
// First try a structural parse so canonical "v0.18.2" or
// "0.18.2-rc.1" round-trip cleanly.
if v, err := semver.Parse(s); err == nil {
return fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch)
}
// git-describe output looks like "v0.18.2-1-ga4997e7" or
// "v0.18.2-1-ga4997e7-dirty". Strip everything from the first
// "-N-g<sha>" boundary onward and try again.
stripped := strings.TrimPrefix(s, "v")
if idx := strings.Index(stripped, "-"); idx > 0 {
// Anything after the first dash that doesn't parse as a
// SemVer prerelease is probably a git-describe trailer.
head := stripped[:idx]
if _, err := semver.Parse(head); err == nil {
return head
}
}
return s
}