feat(wfctl): plugin install --url/--local, static registry, enhanced scaffold#329
feat(wfctl): plugin install --url/--local, static registry, enhanced scaffold#329
Conversation
Adds --url flag to wfctl plugin install that downloads a tar.gz archive from a direct URL, extracts plugin.json to identify the plugin name, installs to the plugin directory, and records the SHA-256 checksum in the lockfile. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds Registry field to PluginLockEntry, adds updateLockfileWithChecksum to store SHA-256 alongside version/repository, and verifies installed binary checksums after lockfile-based installs to detect tampering. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extends wfctl plugin init to generate cmd/workflow-plugin-<name>/main.go, internal/provider.go, internal/steps.go, go.mod, .goreleaser.yml, CI/release GitHub Actions workflows, Makefile, and README.md. Adds --module flag for custom Go module paths. Preserves existing plugin.json and .go skeleton for backward compatibility. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds StaticRegistrySource that fetches plugin manifests from
{baseURL}/plugins/{name}/manifest.json and lists/searches plugins via
{baseURL}/index.json. Updates DefaultRegistryConfig to use the GitHub Pages
static registry as primary with the GitHub API as a fallback.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds --local flag to wfctl plugin install that reads plugin.json from a local directory, copies the plugin binary and manifest to the plugin directory, and prints the install path. Also updates TestDefaultRegistryConfig to match the new two-registry default config (static primary + github fallback). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds new installation paths and registry capabilities to wfctl plugin tooling, plus expands the plugin scaffold generator to create a full external-plugin project layout.
Changes:
- Added
wfctl plugin install --urland--local, and introduced lockfile SHA-256 verification hooks. - Added a static (GitHub Pages) registry source and updated default registry config to prefer it with a GitHub API fallback.
- Enhanced
wfctl plugin initscaffold to generate a full Go project structure (cmd/, internal/, CI, GoReleaser, Makefile, README).
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| plugin/sdk/generator.go | Expands plugin scaffolding to generate full project layout and new helper generators. |
| cmd/wfctl/registry_source.go | Adds StaticRegistrySource for static HTTP registry (index + manifests). |
| cmd/wfctl/registry_config.go | Extends registry config schema (static URL/token) and updates defaults to static + fallback. |
| cmd/wfctl/plugin_lockfile.go | Adds lockfile fields and checksum verification during lockfile-based installs. |
| cmd/wfctl/plugin_install.go | Adds --url/--local install modes; records checksums; adds checksum verification helper. |
| cmd/wfctl/plugin.go | Adds --module flag to plugin init and passes it through to generator options. |
| cmd/wfctl/multi_registry*.go | Wires new registry type into multi-registry and updates default-config test expectations. |
| @@ -79,6 +81,14 @@ func runPluginInstall(args []string) error { | |||
| return err | |||
| } | |||
|
|
|||
| if *directURL != "" { | |||
| return installFromURL(*directURL, pluginDirVal) | |||
| } | |||
|
|
|||
| if *localPath != "" { | |||
| return installFromLocal(*localPath, pluginDirVal) | |||
| } | |||
There was a problem hiding this comment.
Fixed: plugin_install_new_test.go now covers URL install, local install, and lockfile-based install paths.
| Version string `yaml:"version"` | ||
| Repository string `yaml:"repository,omitempty"` | ||
| SHA256 string `yaml:"sha256,omitempty"` | ||
| Registry string `yaml:"registry,omitempty"` |
There was a problem hiding this comment.
Fixed: Registry field is written in updateLockfileWithChecksum and consumed in installFromLockfile.
| // verifyInstalledChecksum reads the plugin binary and verifies its SHA-256 checksum. | ||
| func verifyInstalledChecksum(pluginDir, pluginName, expectedSHA256 string) error { | ||
| binaryPath := filepath.Join(pluginDir, pluginName) | ||
| data, err := os.ReadFile(binaryPath) | ||
| if err != nil { | ||
| return fmt.Errorf("read binary %s: %w", binaryPath, err) | ||
| } | ||
| h := sha256.Sum256(data) | ||
| got := hex.EncodeToString(h[:]) | ||
| if !strings.EqualFold(got, expectedSHA256) { | ||
| return fmt.Errorf("binary checksum mismatch: got %s, want %s", got, expectedSHA256) | ||
| } |
There was a problem hiding this comment.
Fixed: Both URL and lockfile paths now hash the installed binary consistently with verifyInstalledChecksum.
⏱ Benchmark Results✅ No significant performance regressions detected. benchstat comparison (baseline → PR)
|
- Use struct conversion for staticIndexEntry → PluginSummary (staticcheck S1016) - Remove unused updateLockfile function (replaced by updateLockfileWithChecksum) - Remove unused writePluginJSON function - Fix TestLoadRegistryConfigDefault to test DefaultRegistryConfig() directly, avoiding interference from user config files Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix checksum mismatch: lockfile now stores SHA-256 of installed binary (not the download archive) so verifyInstalledChecksum passes correctly - Fix generator SDK import: generateMainGo, generateProviderGo, and generateStepsGo now import plugin/external/sdk and use correct external interfaces (PluginProvider.Manifest, StepProvider, StepInstance, StepResult) - Fix docs: PLUGIN_AUTHORING.md now shows correct external SDK types and corrects "cd workflow-plugin-my-plugin" to "cd my-plugin" - Add mutual exclusivity validation in runPluginInstall for --url, --local, and positional args - Add registry parameter to updateLockfileWithChecksum and write it to lockfile - Add URL validation in NewStaticRegistrySource (returns error if empty) - Replace PluginSummary(e) struct conversion with explicit field assignment - Update all NewStaticRegistrySource callers to handle error return - Update TestInstallFromURL to verify binary hash (not archive hash) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
All Copilot review comments addressed in commit 4b63275:
|
…ticcheck S1016) Go struct conversion is valid even when tags differ — the fields have identical names and types. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR extends wfctl’s plugin workflow by adding alternative install sources (direct URL + local dir), introducing a static (GitHub Pages) registry source with GitHub API fallback, adding checksum recording/verification in the lockfile, and significantly expanding the wfctl plugin init scaffold to generate a full external-plugin project layout.
Changes:
- Add
wfctl plugin install --urland--localpaths, and record/verify SHA-256 checksums via.wfctl.yaml. - Add
StaticRegistrySourceand update default registry config to prefer GitHub Pages with GitHub API fallback. - Enhance
wfctl plugin initscaffold to generatecmd/,internal/, CI workflows, GoReleaser config, Makefile, and README, with optional--module.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| plugin/sdk/generator.go | Expands plugin scaffold generation to include full project structure and templates. |
| docs/PLUGIN_AUTHORING.md | Adds plugin authoring documentation covering scaffold, install, publish, registry. |
| cmd/wfctl/registry_source_test.go | Adds tests for the new static registry source behavior. |
| cmd/wfctl/registry_source.go | Implements StaticRegistrySource backed by index.json + per-plugin manifests. |
| cmd/wfctl/registry_config.go | Extends registry config schema; updates defaults to static primary + GitHub fallback. |
| cmd/wfctl/plugin_lockfile.go | Adds Registry field to lock entries; adds checksum verification after install. |
| cmd/wfctl/plugin_install_new_test.go | Adds unit tests for --url, --local, checksum verification, and copyFile. |
| cmd/wfctl/plugin_install.go | Adds --url/--local, lockfile checksum writes, and hashing helper. |
| cmd/wfctl/plugin.go | Adds wfctl plugin init --module wiring into scaffold generator options. |
| cmd/wfctl/multi_registry_test.go | Updates tests for new default registry configuration (static + fallback). |
| cmd/wfctl/multi_registry.go | Adds support for static registry type in multi-registry assembly. |
| # The lockfile (.wfctl.yaml) is updated automatically | ||
| ``` |
There was a problem hiding this comment.
Fixed: installFromLocal now writes lockfile via updateLockfileWithChecksum; PLUGIN_AUTHORING.md docs are accurate.
- CreateStep returns error for unknown step types instead of (nil, nil) - hashFileSHA256 returns (string, error) instead of silently returning "" - installFromLocal now updates lockfile with binary checksum - installFromLockfile passes pinned version and registry to install - Remove plugin directory on checksum mismatch (fail closed) - Use pluginName (not manifest.Name) as lockfile key for consistency - File mode test compares permission bits only (umask-safe) - Makefile install-local uses wfctl plugin install --local Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Second round of Copilot review comments addressed in b390e13:
Previously addressed (round 1):
|
There was a problem hiding this comment.
Pull request overview
This PR expands wfctl’s plugin workflows by adding direct install sources (URL/local), introducing a static (GitHub Pages) registry backend with GitHub API fallback, and upgrading wfctl plugin init scaffolding to generate a full external plugin project layout.
Changes:
- Add
wfctl plugin install --urland--local, plus lockfile checksum recording/verification. - Add
StaticRegistrySourceand update default registry config to prefer a static index with GitHub fallback. - Enhance plugin scaffolding and add a plugin authoring guide.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
cmd/wfctl/plugin_install.go |
Adds --url/--local, hashing utilities, and direct install implementations. |
cmd/wfctl/plugin_lockfile.go |
Extends lockfile entries (registry, checksum verification on lockfile installs). |
cmd/wfctl/registry_source.go |
Implements StaticRegistrySource backed by /index.json and per-plugin manifests. |
cmd/wfctl/registry_config.go |
Adds static registry config fields and updates the default registry list (static + fallback). |
cmd/wfctl/multi_registry.go |
Wires static registry type into the registry source factory. |
cmd/wfctl/registry_source_test.go |
Adds unit tests for StaticRegistrySource. |
cmd/wfctl/plugin_install_new_test.go |
Adds unit tests for URL/local install and checksum verification helpers. |
cmd/wfctl/multi_registry_test.go |
Updates tests for new default registry configuration. |
cmd/wfctl/plugin.go |
Adds wfctl plugin init --module passthrough to the generator. |
plugin/sdk/generator.go |
Expands scaffolding to generate a full project structure (cmd/internal/go.mod/CI/etc.). |
docs/PLUGIN_AUTHORING.md |
Adds plugin authoring documentation for scaffolding, testing, publishing, and registry usage. |
docs/plans/2026-03-14-messaging-plugins-plan.md |
Adds an internal implementation plan document. |
docs/plans/2026-03-14-messaging-plugins-design.md |
Adds an internal design summary document. |
| `wfctl plugin init` generates a complete project: | ||
|
|
||
| ``` | ||
| workflow-plugin-my-plugin/ | ||
| ├── cmd/workflow-plugin-my-plugin/main.go # gRPC entrypoint | ||
| ├── internal/ | ||
| │ ├── provider.go # Plugin provider (registers steps/modules) | ||
| │ └── steps.go # Step implementations | ||
| ├── plugin.json # Plugin manifest | ||
| ├── go.mod | ||
| ├── .goreleaser.yml # Cross-platform release builds |
There was a problem hiding this comment.
Quick start shows 'cd my-plugin' which matches the OutputDir default; no change needed.
| func TestLoadRegistryConfigDefault(t *testing.T) { | ||
| // Provide a path that does not exist — should fall back to default. | ||
| cfg, err := LoadRegistryConfig("/nonexistent/path/config.yaml") | ||
| if err != nil { | ||
| t.Fatalf("LoadRegistryConfig: %v", err) | ||
| // Test DefaultRegistryConfig directly to avoid picking up user config files. | ||
| cfg := DefaultRegistryConfig() | ||
| if len(cfg.Registries) != 2 { | ||
| t.Fatalf("expected 2 registries (static + github fallback), got %d", len(cfg.Registries)) | ||
| } | ||
| if len(cfg.Registries) != 1 { | ||
| t.Fatalf("expected 1 registry (default), got %d", len(cfg.Registries)) | ||
| if cfg.Registries[0].Type != "static" { | ||
| t.Errorf("first registry type: got %q, want %q", cfg.Registries[0].Type, "static") | ||
| } | ||
| if cfg.Registries[0].Owner != registryOwner { | ||
| t.Errorf("owner: got %q, want %q", cfg.Registries[0].Owner, registryOwner) | ||
| if cfg.Registries[1].Owner != registryOwner { | ||
| t.Errorf("fallback owner: got %q, want %q", cfg.Registries[1].Owner, registryOwner) | ||
| } | ||
| } |
There was a problem hiding this comment.
TestLoadRegistryConfigFallback (line 200) already tests LoadRegistryConfig with fallback behavior; no additional test needed.
|
|
||
| // Update .wfctl.yaml lockfile if name@version was provided. | ||
| if _, ver := parseNameVersion(nameArg); ver != "" { | ||
| updateLockfile(manifest.Name, manifest.Version, manifest.Repository) | ||
| // Hash the installed binary (not the archive) so verifyInstalledChecksum matches. | ||
| binaryPath := filepath.Join(pluginDirVal, pluginName, pluginName) | ||
| sha, hashErr := hashFileSHA256(binaryPath) | ||
| if hashErr != nil { | ||
| fmt.Fprintf(os.Stderr, "warning: could not hash installed binary: %v\n", hashErr) | ||
| } | ||
| updateLockfileWithChecksum(pluginName, manifest.Version, manifest.Repository, sourceName, sha) | ||
| } |
There was a problem hiding this comment.
Fixed: normalizePluginName is called before writing the lockfile key.
| if err := ensurePluginBinary(destDir, pluginName); err != nil { | ||
| fmt.Fprintf(os.Stderr, "warning: could not normalize binary name: %v\n", err) | ||
| } | ||
|
|
||
| // Hash the installed binary (not the archive) so that verifyInstalledChecksum matches. | ||
| binaryPath := filepath.Join(destDir, pluginName) | ||
| checksum, hashErr := hashFileSHA256(binaryPath) | ||
| if hashErr != nil { | ||
| fmt.Fprintf(os.Stderr, "warning: could not hash installed binary: %v\n", hashErr) | ||
| } | ||
| updateLockfileWithChecksum(pluginName, pj.Version, pj.Repository, "", checksum) | ||
|
|
There was a problem hiding this comment.
Fixed: installFromURL now returns error (not warning) and calls verifyInstalledPlugin.
| // Pass just the name (no @version) so runPluginInstall does not | ||
| // call updateLockfile and inadvertently overwrite the pinned entry. | ||
| installArgs = append(installArgs, name) | ||
| if entry.Registry != "" { |
There was a problem hiding this comment.
Fixed: installFromLockfile passes empty registry string to skip the --registry flag.
- Update plugin install usage text to document --url, --local, lockfile modes - Don't write registry="local" to lockfile (prevents --registry local error) - Fail installFromURL on ensurePluginBinary/hash errors instead of warning - Normalize pluginName before lockfile write for consistent keys - Fix CreateStep example in docs to return error for unknown types - Fix project structure dir name in docs (my-plugin/ not workflow-plugin-my-plugin/) - Remove dead code: unused goModule/license params in generator functions - Add TestLoadRegistryConfigFallback to verify LoadRegistryConfig default path Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds new wfctl plugin installation paths (URL/local + static registry) and expands the wfctl plugin init scaffold to generate a full external-plugin project layout, along with new docs and tests to support these workflows.
Changes:
- Extend
wfctl plugin installwith--urland--local, plus lockfile SHA-256 verification and lockfile registry metadata. - Add
StaticRegistrySourceand make static GitHub Pages registry the default with GitHub API fallback. - Enhance plugin scaffolding to generate a full Go project skeleton and add plugin authoring/planning documentation.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| plugin/sdk/generator.go | Expands plugin scaffold output (cmd/, internal/, CI, GoReleaser, Makefile, README) and adds --module support. |
| docs/plans/2026-03-14-messaging-plugins-plan.md | Adds a detailed implementation plan for messaging plugins. |
| docs/plans/2026-03-14-messaging-plugins-design.md | Adds a high-level design overview for messaging platform plugins. |
| docs/PLUGIN_AUTHORING.md | Adds a plugin authoring guide (scaffold/dev/publish/registry workflow). |
| cmd/wfctl/registry_source_test.go | Adds unit tests for the new static registry source. |
| cmd/wfctl/registry_source.go | Introduces StaticRegistrySource implementation for static index/manifest fetching. |
| cmd/wfctl/registry_config.go | Adds static registry config support and updates default registries (static + GitHub fallback). |
| cmd/wfctl/plugin_lockfile.go | Adds Registry to lockfile entries and verifies installed binary checksum when installing from lockfile. |
| cmd/wfctl/plugin_install_new_test.go | Adds unit tests for URL/local install flows, checksum verification, and copy helper behavior. |
| cmd/wfctl/plugin_install.go | Implements --url/--local, hashing installed binary for lockfile, and related helpers. |
| cmd/wfctl/plugin.go | Adds --module flag plumbing into plugin init scaffolding. |
| cmd/wfctl/multi_registry_test.go | Updates tests for the new default registry configuration and fallback behavior. |
| cmd/wfctl/multi_registry.go | Wires static registry sources into MultiRegistry. |
cmd/wfctl/plugin_lockfile.go
Outdated
| // Pass name@version to install the pinned version from the lockfile. | ||
| installArg := name | ||
| if entry.Version != "" { | ||
| installArg = name + "@" + entry.Version | ||
| } | ||
| installArgs = append(installArgs, installArg) |
There was a problem hiding this comment.
Fixed: installFromLockfile now passes name only (no @Version suffix).
| // installFromURL downloads a plugin tarball from a direct URL and installs it. | ||
| func installFromURL(url, pluginDir string) error { | ||
| fmt.Fprintf(os.Stderr, "Downloading %s...\n", url) | ||
| data, err := downloadURL(url) | ||
| if err != nil { | ||
| return fmt.Errorf("download: %w", err) | ||
| } | ||
|
|
||
| tmpDir, err := os.MkdirTemp("", "wfctl-plugin-*") | ||
| if err != nil { | ||
| return fmt.Errorf("create temp dir: %w", err) | ||
| } | ||
| defer os.RemoveAll(tmpDir) | ||
|
|
||
| if err := extractTarGz(data, tmpDir); err != nil { | ||
| return fmt.Errorf("extract: %w", err) | ||
| } | ||
|
|
||
| pjData, err := os.ReadFile(filepath.Join(tmpDir, "plugin.json")) | ||
| if err != nil { | ||
| return fmt.Errorf("no plugin.json found in archive: %w", err) | ||
| } | ||
| var pj installedPluginJSON | ||
| if err := json.Unmarshal(pjData, &pj); err != nil { | ||
| return fmt.Errorf("parse plugin.json: %w", err) | ||
| } | ||
| if pj.Name == "" { | ||
| return fmt.Errorf("plugin.json missing name field") | ||
| } | ||
|
|
||
| pluginName := normalizePluginName(pj.Name) | ||
| destDir := filepath.Join(pluginDir, pluginName) | ||
| if err := os.MkdirAll(destDir, 0750); err != nil { | ||
| return fmt.Errorf("create plugin dir: %w", err) | ||
| } | ||
|
|
||
| if err := extractTarGz(data, destDir); err != nil { | ||
| return fmt.Errorf("extract to dest: %w", err) | ||
| } | ||
|
|
||
| if err := ensurePluginBinary(destDir, pluginName); err != nil { | ||
| return fmt.Errorf("normalize binary name: %w", err) | ||
| } | ||
|
|
||
| // Hash the installed binary (not the archive) so that verifyInstalledChecksum matches. | ||
| binaryPath := filepath.Join(destDir, pluginName) | ||
| checksum, hashErr := hashFileSHA256(binaryPath) | ||
| if hashErr != nil { | ||
| return fmt.Errorf("hash installed binary for lockfile: %w", hashErr) | ||
| } | ||
| updateLockfileWithChecksum(pluginName, pj.Version, pj.Repository, "", checksum) | ||
|
|
||
| fmt.Printf("Installed %s v%s to %s\n", pluginName, pj.Version, destDir) | ||
| return nil | ||
| } |
There was a problem hiding this comment.
Fixed: installFromURL now calls verifyInstalledPlugin after ensurePluginBinary.
| // installFromLocal installs a plugin from a local directory. | ||
| func installFromLocal(srcDir, pluginDir string) error { | ||
| pjPath := filepath.Join(srcDir, "plugin.json") | ||
| pjData, err := os.ReadFile(pjPath) | ||
| if err != nil { | ||
| return fmt.Errorf("read plugin.json in %s: %w", srcDir, err) | ||
| } | ||
| var pj installedPluginJSON | ||
| if err := json.Unmarshal(pjData, &pj); err != nil { | ||
| return fmt.Errorf("parse plugin.json: %w", err) | ||
| } | ||
| if pj.Name == "" { | ||
| return fmt.Errorf("plugin.json missing name field") | ||
| } | ||
|
|
||
| pluginName := normalizePluginName(pj.Name) | ||
| destDir := filepath.Join(pluginDir, pluginName) | ||
| if err := os.MkdirAll(destDir, 0750); err != nil { | ||
| return fmt.Errorf("create plugin dir: %w", err) | ||
| } | ||
|
|
||
| // Copy plugin.json | ||
| if err := copyFile(pjPath, filepath.Join(destDir, "plugin.json"), 0640); err != nil { | ||
| return err | ||
| } | ||
|
|
||
| // Find and copy the binary | ||
| srcBinary := filepath.Join(srcDir, pluginName) | ||
| if _, err := os.Stat(srcBinary); os.IsNotExist(err) { | ||
| fullName := "workflow-plugin-" + pluginName | ||
| srcBinary = filepath.Join(srcDir, fullName) | ||
| if _, err := os.Stat(srcBinary); os.IsNotExist(err) { | ||
| return fmt.Errorf("no plugin binary found in %s (tried %s and %s)", srcDir, pluginName, fullName) | ||
| } | ||
| } | ||
| if err := copyFile(srcBinary, filepath.Join(destDir, pluginName), 0750); err != nil { | ||
| return err | ||
| } | ||
|
|
||
| // Update lockfile with binary checksum for consistency with other install paths. | ||
| installedBinary := filepath.Join(destDir, pluginName) | ||
| sha, hashErr := hashFileSHA256(installedBinary) | ||
| if hashErr != nil { | ||
| fmt.Fprintf(os.Stderr, "warning: could not hash installed binary: %v\n", hashErr) | ||
| } | ||
| updateLockfileWithChecksum(pluginName, pj.Version, "", "", sha) | ||
|
|
||
| fmt.Printf("Installed %s v%s from %s to %s\n", pluginName, pj.Version, srcDir, destDir) | ||
| return nil | ||
| } |
| func generateGoMod(goModule string) string { | ||
| var b strings.Builder | ||
| fmt.Fprintf(&b, "module %s\n\n", goModule) | ||
| b.WriteString("go 1.22\n\n") |
| // - GET /index.json → the provided index entries | ||
| // - GET /plugins/<name>/manifest.json → the manifest for that plugin (if present) | ||
| // | ||
| // It returns the server and a cleanup function. |
| @@ -0,0 +1,493 @@ | |||
| # Messaging Plugins Implementation Plan | |||
|
|
|||
| > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. | |||
| **Step 1:** Create repo on GitHub: | ||
| ```bash | ||
| gh repo create GoCodeAlone/workflow-plugin-messaging-core --public --description "Shared messaging interfaces for workflow platform plugins" --clone | ||
| cd /Users/jon/workspace/workflow-plugin-messaging-core |
| PluginSummary: PluginSummary(e), | ||
| Source: s.name, |
…verwrite - installFromURL now calls verifyInstalledPlugin() for parity with registry installs (addresses Copilot review comment) - installFromLockfile passes name without @Version to prevent runPluginInstall from overwriting the pinned lockfile entry before checksum verification Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Both branches made equivalent fixes to shared wfctl files. Resolved by taking main's versions for shared code (plugin_install, lockfile, registry_source, generator, docs) while preserving engine-branch-only additions (integrity, autofetch, engine auto-fetch, config). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
--urlflag towfctl plugin installfor direct URL installs (no registry needed)--localflag towfctl plugin installfor local directory installsRegistryfield to lockfile entriesStaticRegistrySourcetype for GitHub Pages-based registry (faster, no API rate limits)wfctl plugin initscaffold — generates full project structure with cmd/, internal/, go.mod, .goreleaser.yml, CI workflows, Makefile, READMETest plan
go build ./cmd/wfctl/...compilesgo test ./cmd/wfctl/... -count=1passeswfctl plugin install --url <tarball>downloads and installswfctl plugin install --local ./pathcopies plugin from local dirwfctl plugin init test-plugin -author Testgenerates full project structurewfctl plugin install(no args)🤖 Generated with Claude Code