From dd67918e24723653fd7a7938404c0367ad49ed46 Mon Sep 17 00:00:00 2001 From: Quigley Malcolm Date: Tue, 9 Jun 2026 16:12:29 -0500 Subject: [PATCH 01/14] feat(cli): scaffold go module with cobra command tree MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The OSI converters currently require manual environment setup and per-converter invocation. This lays the foundation for a unified CLI (osi) that will discover and invoke converters as plugins via a stdin/stdout JSON protocol. Creates cli/ at the repo root as a self-contained Go module (github.com/open-semantic-interchange/osi/cli). All commands are stubbed — no logic is wired in this commit. Design decisions: - Go chosen for static binary distribution; no runtime dependency for end users (brew/apt installable) - internal/osidir owns ~/.osi/plugins/ initialization and respects $OSI_PLUGIN_DIR for override; uses os.UserHomeDir() rather than $HOME for Windows portability - PersistentPreRunE on the root command ensures dir init runs before every subcommand; commented caveat that Cobra does not chain this automatically if a subcommand defines its own - MarkFlagsMutuallyExclusive("from", "to") handles the both-set case on osi convert; the neither-set case is validated manually in RunE since Cobra only guards against both being provided Build pipeline (Makefile, .goreleaser.yaml) and CI follow in separate commits. --- .gitignore | 4 ++++ cli/.tool-versions | 1 + cli/cmd/convert.go | 39 ++++++++++++++++++++++++++++++++++ cli/cmd/plugin/install.go | 22 +++++++++++++++++++ cli/cmd/plugin/list.go | 18 ++++++++++++++++ cli/cmd/plugin/plugin.go | 16 ++++++++++++++ cli/cmd/plugin/remove.go | 19 +++++++++++++++++ cli/cmd/root.go | 38 +++++++++++++++++++++++++++++++++ cli/cmd/validate.go | 24 +++++++++++++++++++++ cli/go.mod | 10 +++++++++ cli/go.sum | 10 +++++++++ cli/internal/osidir/osidir.go | 40 +++++++++++++++++++++++++++++++++++ cli/main.go | 21 ++++++++++++++++++ 13 files changed, 262 insertions(+) create mode 100644 cli/.tool-versions create mode 100644 cli/cmd/convert.go create mode 100644 cli/cmd/plugin/install.go create mode 100644 cli/cmd/plugin/list.go create mode 100644 cli/cmd/plugin/plugin.go create mode 100644 cli/cmd/plugin/remove.go create mode 100644 cli/cmd/root.go create mode 100644 cli/cmd/validate.go create mode 100644 cli/go.mod create mode 100644 cli/go.sum create mode 100644 cli/internal/osidir/osidir.go create mode 100644 cli/main.go diff --git a/.gitignore b/.gitignore index 63e36ff7..8cf6aa83 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ **/__pycache__/ **/.venv/ + +# Go CLI +cli/dist/ +cli/osi diff --git a/cli/.tool-versions b/cli/.tool-versions new file mode 100644 index 00000000..05a23a62 --- /dev/null +++ b/cli/.tool-versions @@ -0,0 +1 @@ +golang 1.26.2 diff --git a/cli/cmd/convert.go b/cli/cmd/convert.go new file mode 100644 index 00000000..aa8797d7 --- /dev/null +++ b/cli/cmd/convert.go @@ -0,0 +1,39 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +var convertCmd = &cobra.Command{ + Use: "convert --from --input | --to --input ", + Short: "Convert a semantic model between OSI and a platform format", + RunE: runConvert, +} + +func init() { + convertCmd.Flags().String("from", "", "Source platform — converts platform → OSI") + convertCmd.Flags().String("to", "", "Target platform — converts OSI → platform") + convertCmd.Flags().StringP("input", "i", "", "Input file or directory path (required)") + convertCmd.Flags().StringP("output", "o", "", "Output directory path (default: ./osi-output//)") + convertCmd.Flags().String("plugin", "", "Path to plugin directory (bypasses name-based discovery)") + convertCmd.Flags().Int("timeout", 60, "Plugin invocation timeout in seconds") + convertCmd.Flags().String("max-input-size", "100MB", "Maximum total input size (e.g. 500MB)") + + _ = convertCmd.MarkFlagRequired("input") + convertCmd.MarkFlagsMutuallyExclusive("from", "to") +} + +func runConvert(cmd *cobra.Command, args []string) error { + from, _ := cmd.Flags().GetString("from") + to, _ := cmd.Flags().GetString("to") + + // MarkFlagsMutuallyExclusive handles the both-set case; handle neither here. + if from == "" && to == "" { + return fmt.Errorf("exactly one of --from or --to must be specified") + } + + fmt.Fprintln(cmd.OutOrStdout(), "not yet implemented") + return nil +} diff --git a/cli/cmd/plugin/install.go b/cli/cmd/plugin/install.go new file mode 100644 index 00000000..aabde2d1 --- /dev/null +++ b/cli/cmd/plugin/install.go @@ -0,0 +1,22 @@ +package plugin + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +var installCmd = &cobra.Command{ + Use: "install [name[@version] | url]", + Short: "Install a plugin from the registry or a URL", + RunE: runPluginInstall, +} + +func init() { + installCmd.Flags().Bool("all", false, "Install the latest version of all registry plugins") +} + +func runPluginInstall(cmd *cobra.Command, args []string) error { + fmt.Fprintln(cmd.OutOrStdout(), "not yet implemented") + return nil +} diff --git a/cli/cmd/plugin/list.go b/cli/cmd/plugin/list.go new file mode 100644 index 00000000..f69e8bbe --- /dev/null +++ b/cli/cmd/plugin/list.go @@ -0,0 +1,18 @@ +package plugin + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +var listCmd = &cobra.Command{ + Use: "list", + Short: "List installed and available plugins", + RunE: runPluginList, +} + +func runPluginList(cmd *cobra.Command, args []string) error { + fmt.Fprintln(cmd.OutOrStdout(), "not yet implemented") + return nil +} diff --git a/cli/cmd/plugin/plugin.go b/cli/cmd/plugin/plugin.go new file mode 100644 index 00000000..a57e103c --- /dev/null +++ b/cli/cmd/plugin/plugin.go @@ -0,0 +1,16 @@ +package plugin + +import "github.com/spf13/cobra" + +// Cmd is the parent "osi plugin" command. It is exported so cmd/root.go can +// register it. Invoking it bare prints help. +var Cmd = &cobra.Command{ + Use: "plugin", + Short: "Manage OSI plugins", +} + +func init() { + Cmd.AddCommand(listCmd) + Cmd.AddCommand(installCmd) + Cmd.AddCommand(removeCmd) +} diff --git a/cli/cmd/plugin/remove.go b/cli/cmd/plugin/remove.go new file mode 100644 index 00000000..1b886696 --- /dev/null +++ b/cli/cmd/plugin/remove.go @@ -0,0 +1,19 @@ +package plugin + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +var removeCmd = &cobra.Command{ + Use: "remove ", + Short: "Remove an installed plugin", + Args: cobra.ExactArgs(1), + RunE: runPluginRemove, +} + +func runPluginRemove(cmd *cobra.Command, args []string) error { + fmt.Fprintln(cmd.OutOrStdout(), "not yet implemented") + return nil +} diff --git a/cli/cmd/root.go b/cli/cmd/root.go new file mode 100644 index 00000000..40d1fbc7 --- /dev/null +++ b/cli/cmd/root.go @@ -0,0 +1,38 @@ +package cmd + +import ( + "github.com/open-semantic-interchange/osi/cli/cmd/plugin" + "github.com/open-semantic-interchange/osi/cli/internal/osidir" + "github.com/spf13/cobra" +) + +var rootCmd = &cobra.Command{ + Use: "osi", + Short: "Open Semantic Interchange CLI", + Long: `osi is the command-line tool for the Open Semantic Interchange (OSI) project.`, + // NOTE: Cobra does NOT automatically chain PersistentPreRunE from parent to + // child. If any subcommand defines its own PersistentPreRunE or PreRunE, this + // function will not run for that subcommand. Future subcommands that define + // their own must call osidir.EnsurePluginDir() explicitly. + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + return osidir.EnsurePluginDir() + }, +} + +// Execute runs the root command. Called by main. +func Execute() error { + return rootCmd.Execute() +} + +// SetVersion sets the version string reported by `osi --version`. +func SetVersion(v string) { + rootCmd.Version = v +} + +func init() { + rootCmd.PersistentFlags().BoolP("verbose", "v", false, "Enable verbose output (shows plugin stderr)") + + rootCmd.AddCommand(convertCmd) + rootCmd.AddCommand(validateCmd) + rootCmd.AddCommand(plugin.Cmd) +} diff --git a/cli/cmd/validate.go b/cli/cmd/validate.go new file mode 100644 index 00000000..5ef9edc4 --- /dev/null +++ b/cli/cmd/validate.go @@ -0,0 +1,24 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +var validateCmd = &cobra.Command{ + Use: "validate [flags] [...]", + Short: "Validate one or more OSI YAML or JSON files", + Args: cobra.MinimumNArgs(1), + RunE: runValidate, +} + +func init() { + validateCmd.Flags().Bool("strict", false, "Promote warnings to errors") + validateCmd.Flags().String("output", "text", "Output format: text or json") +} + +func runValidate(cmd *cobra.Command, args []string) error { + fmt.Fprintln(cmd.OutOrStdout(), "not yet implemented") + return nil +} diff --git a/cli/go.mod b/cli/go.mod new file mode 100644 index 00000000..eabd4e0f --- /dev/null +++ b/cli/go.mod @@ -0,0 +1,10 @@ +module github.com/open-semantic-interchange/osi/cli + +go 1.22 + +require github.com/spf13/cobra v1.10.2 + +require ( + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/spf13/pflag v1.0.9 // indirect +) diff --git a/cli/go.sum b/cli/go.sum new file mode 100644 index 00000000..a6ee3e0f --- /dev/null +++ b/cli/go.sum @@ -0,0 +1,10 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/cli/internal/osidir/osidir.go b/cli/internal/osidir/osidir.go new file mode 100644 index 00000000..cdb3f6bb --- /dev/null +++ b/cli/internal/osidir/osidir.go @@ -0,0 +1,40 @@ +package osidir + +import ( + "fmt" + "os" + "path/filepath" +) + +const ( + defaultOSIDir = ".osi" + pluginsSubdir = "plugins" + envVar = "OSI_PLUGIN_DIR" +) + +// PluginDir returns the resolved plugin directory path. +// It respects $OSI_PLUGIN_DIR if set, otherwise defaults to ~/.osi/plugins/. +func PluginDir() (string, error) { + if override := os.Getenv(envVar); override != "" { + return override, nil + } + // Use os.UserHomeDir rather than $HOME for Windows portability. + home, err := os.UserHomeDir() + if err != nil { + return "", fmt.Errorf("could not determine home directory: %w", err) + } + return filepath.Join(home, defaultOSIDir, pluginsSubdir), nil +} + +// EnsurePluginDir ensures the plugin directory exists, creating it if needed. +// It is safe to call multiple times — os.MkdirAll is idempotent. +func EnsurePluginDir() error { + dir, err := PluginDir() + if err != nil { + return err + } + if err := os.MkdirAll(dir, 0755); err != nil { + return fmt.Errorf("could not create plugin directory %s: %w", dir, err) + } + return nil +} diff --git a/cli/main.go b/cli/main.go new file mode 100644 index 00000000..9262cb51 --- /dev/null +++ b/cli/main.go @@ -0,0 +1,21 @@ +package main + +import ( + "os" + + "github.com/open-semantic-interchange/osi/cli/cmd" +) + +// version, commit, and date are set at build time by GoReleaser via ldflags. +var ( + version = "dev" + commit = "none" + date = "unknown" +) + +func main() { + cmd.SetVersion(version) + if err := cmd.Execute(); err != nil { + os.Exit(1) + } +} From 260f721a0d33f540424a62e13d41bd41b4c4d577 Mon Sep 17 00:00:00 2001 From: Quigley Malcolm Date: Tue, 9 Jun 2026 16:33:56 -0500 Subject: [PATCH 02/14] chore(cli): add makefile and goreleaser build pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enables local builds and cross-platform release artifacts for the OSI CLI. Makefile provides standard targets for day-to-day development: build, test, lint, release-dry-run, and clean. All targets are designed to run from within cli/. GoReleaser config targets linux/amd64, linux/arm64, darwin/amd64, darwin/arm64, and windows/amd64. windows/arm64 is excluded — no widely available CI runner and negligible current demand. CGO_ENABLED=0 is set for fully static binaries, enabling cross-compilation from any host without a C toolchain. Version, commit, and date are injected at build time via ldflags from the vars declared in main.go. --- cli/.goreleaser.yaml | 48 ++++++++++++++++++++++++++++++++++++++++++++ cli/Makefile | 19 ++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 cli/.goreleaser.yaml create mode 100644 cli/Makefile diff --git a/cli/.goreleaser.yaml b/cli/.goreleaser.yaml new file mode 100644 index 00000000..2351e42b --- /dev/null +++ b/cli/.goreleaser.yaml @@ -0,0 +1,48 @@ +version: 2 + +project_name: osi + +before: + hooks: + - go mod tidy + +builds: + - id: osi + main: . + binary: osi + goos: + - linux + - darwin + - windows + goarch: + - amd64 + - arm64 + ignore: + - goos: windows + goarch: arm64 + env: + - CGO_ENABLED=0 + ldflags: + - -s -w + - -X main.version={{.Version}} + - -X main.commit={{.Commit}} + - -X main.date={{.Date}} + +archives: + - id: osi + format: tar.gz + format_overrides: + - goos: windows + format: zip + name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" + +checksum: + name_template: "checksums.txt" + +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" + - "^chore:" diff --git a/cli/Makefile b/cli/Makefile new file mode 100644 index 00000000..ed8de036 --- /dev/null +++ b/cli/Makefile @@ -0,0 +1,19 @@ +BINARY_NAME := osi +BUILD_DIR := dist + +.PHONY: build test lint release-dry-run clean + +build: + go build -o $(BUILD_DIR)/$(BINARY_NAME) . + +test: + go test ./... + +lint: + go vet ./... + +release-dry-run: + goreleaser release --snapshot --clean --config .goreleaser.yaml + +clean: + rm -rf $(BUILD_DIR) From e4a1fe62f96f3943a17c20cdce0ab7444b1fc392 Mon Sep 17 00:00:00 2001 From: Quigley Malcolm Date: Tue, 9 Jun 2026 16:40:00 -0500 Subject: [PATCH 03/14] ci(cli): add github actions workflow for build and test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Runs go build, go vet, and go test on every push and pull request that touches cli/ or the workflow file itself. The paths filter prevents CLI changes from triggering unrelated workflows in this polyglot repo and vice versa. Go version is derived from go-version-file: cli/go.mod so the workflow automatically picks up any future toolchain bumps without a separate workflow edit. defaults.run.working-directory avoids repeating cd cli/ on every step. Cross-platform build testing is not included — CGO_ENABLED=0 means the linux/amd64 build is representative of all targets. GoReleaser snapshot builds are deferred to a future release workflow. --- .github/workflows/cli-ci.yml | 38 ++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .github/workflows/cli-ci.yml diff --git a/.github/workflows/cli-ci.yml b/.github/workflows/cli-ci.yml new file mode 100644 index 00000000..e7290892 --- /dev/null +++ b/.github/workflows/cli-ci.yml @@ -0,0 +1,38 @@ +name: CLI CI + +on: + push: + paths: + - 'cli/**' + - '.github/workflows/cli-ci.yml' + pull_request: + paths: + - 'cli/**' + - '.github/workflows/cli-ci.yml' + +jobs: + build-and-test: + name: Build and test + runs-on: ubuntu-latest + defaults: + run: + working-directory: cli + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: cli/go.mod + cache-dependency-path: cli/go.sum + + - name: go build + run: go build ./... + + - name: go vet + run: go vet ./... + + - name: go test + run: go test ./... From 38d52c1b4c57735437f592cd5049f15caffb2ae3 Mon Sep 17 00:00:00 2001 From: Quigley Malcolm Date: Tue, 9 Jun 2026 17:47:45 -0500 Subject: [PATCH 04/14] test(cli): add unit tests for internal/osidir Covers the only logic in F1 that warrants testing: $OSI_PLUGIN_DIR env var override, default path construction via os.UserHomeDir(), directory creation, and idempotent re-invocation of EnsurePluginDir. t.Setenv is used throughout so env var mutations are automatically restored after each test. t.TempDir is used for filesystem tests so no cleanup is needed and tests are safe to run in parallel. --- cli/internal/osidir/osidir_test.go | 65 ++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 cli/internal/osidir/osidir_test.go diff --git a/cli/internal/osidir/osidir_test.go b/cli/internal/osidir/osidir_test.go new file mode 100644 index 00000000..1d39193d --- /dev/null +++ b/cli/internal/osidir/osidir_test.go @@ -0,0 +1,65 @@ +package osidir + +import ( + "os" + "path/filepath" + "testing" +) + +func TestPluginDir_envOverride(t *testing.T) { + want := "/custom/plugin/dir" + t.Setenv(envVar, want) + + got, err := PluginDir() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got != want { + t.Errorf("got %q, want %q", got, want) + } +} + +func TestPluginDir_default(t *testing.T) { + t.Setenv(envVar, "") + + home, err := os.UserHomeDir() + if err != nil { + t.Fatalf("could not determine home dir: %v", err) + } + want := filepath.Join(home, defaultOSIDir, pluginsSubdir) + + got, err := PluginDir() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got != want { + t.Errorf("got %q, want %q", got, want) + } +} + +func TestEnsurePluginDir_createsDirectory(t *testing.T) { + tmp := t.TempDir() + target := filepath.Join(tmp, "plugins") + t.Setenv(envVar, target) + + if err := EnsurePluginDir(); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if _, err := os.Stat(target); os.IsNotExist(err) { + t.Errorf("expected directory %q to exist, but it does not", target) + } +} + +func TestEnsurePluginDir_idempotent(t *testing.T) { + tmp := t.TempDir() + target := filepath.Join(tmp, "plugins") + t.Setenv(envVar, target) + + if err := EnsurePluginDir(); err != nil { + t.Fatalf("first call failed: %v", err) + } + if err := EnsurePluginDir(); err != nil { + t.Fatalf("second call failed: %v", err) + } +} From ef807ad9d5c2e45b56c7cf76d0e94bdd0fb6a5a4 Mon Sep 17 00:00:00 2001 From: Quigley Malcolm Date: Wed, 10 Jun 2026 15:45:12 -0500 Subject: [PATCH 05/14] chore(cli): rename osi to ossie throughout cli scaffold MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Project branding has changed from OSI to OSSIE. Updates all user-facing and internal references in the CLI: - Binary name: osi → ossie - Go module path: .../osi/cli → .../ossie/cli - Default plugin directory: ~/.osi → ~/.ossie - Environment variable: OSI_PLUGIN_DIR → OSSIE_PLUGIN_DIR - GoReleaser project name and archive ids - All command descriptions and flag help text - Output directory default: ./osi-output → ./ossie-output --- cli/.goreleaser.yaml | 8 ++++---- cli/Makefile | 2 +- cli/cmd/convert.go | 8 ++++---- cli/cmd/plugin/plugin.go | 4 ++-- cli/cmd/root.go | 10 +++++----- cli/cmd/validate.go | 2 +- cli/go.mod | 2 +- cli/internal/osidir/osidir.go | 6 +++--- cli/main.go | 2 +- 9 files changed, 22 insertions(+), 22 deletions(-) diff --git a/cli/.goreleaser.yaml b/cli/.goreleaser.yaml index 2351e42b..3a0a17ba 100644 --- a/cli/.goreleaser.yaml +++ b/cli/.goreleaser.yaml @@ -1,15 +1,15 @@ version: 2 -project_name: osi +project_name: ossie before: hooks: - go mod tidy builds: - - id: osi + - id: ossie main: . - binary: osi + binary: ossie goos: - linux - darwin @@ -29,7 +29,7 @@ builds: - -X main.date={{.Date}} archives: - - id: osi + - id: ossie format: tar.gz format_overrides: - goos: windows diff --git a/cli/Makefile b/cli/Makefile index ed8de036..9ecbcb77 100644 --- a/cli/Makefile +++ b/cli/Makefile @@ -1,4 +1,4 @@ -BINARY_NAME := osi +BINARY_NAME := ossie BUILD_DIR := dist .PHONY: build test lint release-dry-run clean diff --git a/cli/cmd/convert.go b/cli/cmd/convert.go index aa8797d7..92722eea 100644 --- a/cli/cmd/convert.go +++ b/cli/cmd/convert.go @@ -8,15 +8,15 @@ import ( var convertCmd = &cobra.Command{ Use: "convert --from --input | --to --input ", - Short: "Convert a semantic model between OSI and a platform format", + Short: "Convert a semantic model between OSSIE and a platform format", RunE: runConvert, } func init() { - convertCmd.Flags().String("from", "", "Source platform — converts platform → OSI") - convertCmd.Flags().String("to", "", "Target platform — converts OSI → platform") + convertCmd.Flags().String("from", "", "Source platform — converts platform → OSSIE") + convertCmd.Flags().String("to", "", "Target platform — converts OSSIE → platform") convertCmd.Flags().StringP("input", "i", "", "Input file or directory path (required)") - convertCmd.Flags().StringP("output", "o", "", "Output directory path (default: ./osi-output//)") + convertCmd.Flags().StringP("output", "o", "", "Output directory path (default: ./ossie-output//)") convertCmd.Flags().String("plugin", "", "Path to plugin directory (bypasses name-based discovery)") convertCmd.Flags().Int("timeout", 60, "Plugin invocation timeout in seconds") convertCmd.Flags().String("max-input-size", "100MB", "Maximum total input size (e.g. 500MB)") diff --git a/cli/cmd/plugin/plugin.go b/cli/cmd/plugin/plugin.go index a57e103c..d996f133 100644 --- a/cli/cmd/plugin/plugin.go +++ b/cli/cmd/plugin/plugin.go @@ -2,11 +2,11 @@ package plugin import "github.com/spf13/cobra" -// Cmd is the parent "osi plugin" command. It is exported so cmd/root.go can +// Cmd is the parent "ossie plugin" command. It is exported so cmd/root.go can // register it. Invoking it bare prints help. var Cmd = &cobra.Command{ Use: "plugin", - Short: "Manage OSI plugins", + Short: "Manage OSSIE plugins", } func init() { diff --git a/cli/cmd/root.go b/cli/cmd/root.go index 40d1fbc7..45347bc8 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -1,15 +1,15 @@ package cmd import ( - "github.com/open-semantic-interchange/osi/cli/cmd/plugin" - "github.com/open-semantic-interchange/osi/cli/internal/osidir" + "github.com/open-semantic-interchange/ossie/cli/cmd/plugin" + "github.com/open-semantic-interchange/ossie/cli/internal/osidir" "github.com/spf13/cobra" ) var rootCmd = &cobra.Command{ - Use: "osi", + Use: "ossie", Short: "Open Semantic Interchange CLI", - Long: `osi is the command-line tool for the Open Semantic Interchange (OSI) project.`, + Long: `ossie is the command-line tool for the Open Semantic Interchange (OSSIE) project.`, // NOTE: Cobra does NOT automatically chain PersistentPreRunE from parent to // child. If any subcommand defines its own PersistentPreRunE or PreRunE, this // function will not run for that subcommand. Future subcommands that define @@ -24,7 +24,7 @@ func Execute() error { return rootCmd.Execute() } -// SetVersion sets the version string reported by `osi --version`. +// SetVersion sets the version string reported by `ossie --version`. func SetVersion(v string) { rootCmd.Version = v } diff --git a/cli/cmd/validate.go b/cli/cmd/validate.go index 5ef9edc4..b11d00ad 100644 --- a/cli/cmd/validate.go +++ b/cli/cmd/validate.go @@ -8,7 +8,7 @@ import ( var validateCmd = &cobra.Command{ Use: "validate [flags] [...]", - Short: "Validate one or more OSI YAML or JSON files", + Short: "Validate one or more OSSIE YAML or JSON files", Args: cobra.MinimumNArgs(1), RunE: runValidate, } diff --git a/cli/go.mod b/cli/go.mod index eabd4e0f..a8284253 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -1,4 +1,4 @@ -module github.com/open-semantic-interchange/osi/cli +module github.com/open-semantic-interchange/ossie/cli go 1.22 diff --git a/cli/internal/osidir/osidir.go b/cli/internal/osidir/osidir.go index cdb3f6bb..27fb7429 100644 --- a/cli/internal/osidir/osidir.go +++ b/cli/internal/osidir/osidir.go @@ -7,13 +7,13 @@ import ( ) const ( - defaultOSIDir = ".osi" + defaultOSIDir = ".ossie" pluginsSubdir = "plugins" - envVar = "OSI_PLUGIN_DIR" + envVar = "OSSIE_PLUGIN_DIR" ) // PluginDir returns the resolved plugin directory path. -// It respects $OSI_PLUGIN_DIR if set, otherwise defaults to ~/.osi/plugins/. +// It respects $OSSIE_PLUGIN_DIR if set, otherwise defaults to ~/.ossie/plugins/. func PluginDir() (string, error) { if override := os.Getenv(envVar); override != "" { return override, nil diff --git a/cli/main.go b/cli/main.go index 9262cb51..9d2a2028 100644 --- a/cli/main.go +++ b/cli/main.go @@ -3,7 +3,7 @@ package main import ( "os" - "github.com/open-semantic-interchange/osi/cli/cmd" + "github.com/open-semantic-interchange/ossie/cli/cmd" ) // version, commit, and date are set at build time by GoReleaser via ldflags. From 44bcd4492a2f2ffb1b2d7d7865e521fb4deaa8a5 Mon Sep 17 00:00:00 2001 From: Quigley Malcolm Date: Fri, 12 Jun 2026 10:04:42 -0500 Subject: [PATCH 06/14] chore(cli): rename internal osidir package to ossiedir MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Completes the OSI → OSSIE rename by updating the internal package directory, package declaration, and import reference in cmd/root.go. --- cli/cmd/root.go | 6 +++--- cli/internal/{osidir => ossiedir}/osidir.go | 2 +- cli/internal/{osidir => ossiedir}/osidir_test.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) rename cli/internal/{osidir => ossiedir}/osidir.go (98%) rename cli/internal/{osidir => ossiedir}/osidir_test.go (98%) diff --git a/cli/cmd/root.go b/cli/cmd/root.go index 45347bc8..772d87b1 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -2,7 +2,7 @@ package cmd import ( "github.com/open-semantic-interchange/ossie/cli/cmd/plugin" - "github.com/open-semantic-interchange/ossie/cli/internal/osidir" + "github.com/open-semantic-interchange/ossie/cli/internal/ossiedir" "github.com/spf13/cobra" ) @@ -13,9 +13,9 @@ var rootCmd = &cobra.Command{ // NOTE: Cobra does NOT automatically chain PersistentPreRunE from parent to // child. If any subcommand defines its own PersistentPreRunE or PreRunE, this // function will not run for that subcommand. Future subcommands that define - // their own must call osidir.EnsurePluginDir() explicitly. + // their own must call ossiedir.EnsurePluginDir() explicitly. PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - return osidir.EnsurePluginDir() + return ossiedir.EnsurePluginDir() }, } diff --git a/cli/internal/osidir/osidir.go b/cli/internal/ossiedir/osidir.go similarity index 98% rename from cli/internal/osidir/osidir.go rename to cli/internal/ossiedir/osidir.go index 27fb7429..93960412 100644 --- a/cli/internal/osidir/osidir.go +++ b/cli/internal/ossiedir/osidir.go @@ -1,4 +1,4 @@ -package osidir +package ossiedir import ( "fmt" diff --git a/cli/internal/osidir/osidir_test.go b/cli/internal/ossiedir/osidir_test.go similarity index 98% rename from cli/internal/osidir/osidir_test.go rename to cli/internal/ossiedir/osidir_test.go index 1d39193d..0fe341c7 100644 --- a/cli/internal/osidir/osidir_test.go +++ b/cli/internal/ossiedir/osidir_test.go @@ -1,4 +1,4 @@ -package osidir +package ossiedir import ( "os" From aa4f8d99302856860279b109f154a2ad74f7fa02 Mon Sep 17 00:00:00 2001 From: Quigley Malcolm Date: Tue, 23 Jun 2026 15:32:53 -0500 Subject: [PATCH 07/14] fix(ossiedir): rename defaultOSIDir constant to defaultOssieDir MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The osi → ossie rename missed this unexported constant. The value (.ossie) was already correct; only the identifier name was stale. --- cli/internal/ossiedir/osidir.go | 4 ++-- cli/internal/ossiedir/osidir_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/internal/ossiedir/osidir.go b/cli/internal/ossiedir/osidir.go index 93960412..051a8bd8 100644 --- a/cli/internal/ossiedir/osidir.go +++ b/cli/internal/ossiedir/osidir.go @@ -7,7 +7,7 @@ import ( ) const ( - defaultOSIDir = ".ossie" + defaultOssieDir = ".ossie" pluginsSubdir = "plugins" envVar = "OSSIE_PLUGIN_DIR" ) @@ -23,7 +23,7 @@ func PluginDir() (string, error) { if err != nil { return "", fmt.Errorf("could not determine home directory: %w", err) } - return filepath.Join(home, defaultOSIDir, pluginsSubdir), nil + return filepath.Join(home, defaultOssieDir, pluginsSubdir), nil } // EnsurePluginDir ensures the plugin directory exists, creating it if needed. diff --git a/cli/internal/ossiedir/osidir_test.go b/cli/internal/ossiedir/osidir_test.go index 0fe341c7..76a89813 100644 --- a/cli/internal/ossiedir/osidir_test.go +++ b/cli/internal/ossiedir/osidir_test.go @@ -26,7 +26,7 @@ func TestPluginDir_default(t *testing.T) { if err != nil { t.Fatalf("could not determine home dir: %v", err) } - want := filepath.Join(home, defaultOSIDir, pluginsSubdir) + want := filepath.Join(home, defaultOssieDir, pluginsSubdir) got, err := PluginDir() if err != nil { From 310618999c786535c10e1bbda1de91c751e942a1 Mon Sep 17 00:00:00 2001 From: Quigley Malcolm Date: Tue, 23 Jun 2026 15:33:28 -0500 Subject: [PATCH 08/14] chore(cli): fix .gitignore to ignore cli/ossie build artifact MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The osi → ossie rename missed the gitignore entry, so the local build artifact cli/ossie would no longer be ignored by git. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8cf6aa83..5f39ab37 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ # Go CLI cli/dist/ -cli/osi +cli/ossie From d86a25059bf42ffead823cc60a827a9a0a1b09a3 Mon Sep 17 00:00:00 2001 From: Quigley Malcolm Date: Wed, 24 Jun 2026 16:11:52 -0500 Subject: [PATCH 09/14] chore(ossiedir): rename osidir.go and osidir_test.go to ossiedir MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit File names were inconsistent with the package directory name (ossiedir/). Pure rename — no code changes. --- cli/internal/ossiedir/{osidir.go => ossiedir.go} | 0 cli/internal/ossiedir/{osidir_test.go => ossiedir_test.go} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename cli/internal/ossiedir/{osidir.go => ossiedir.go} (100%) rename cli/internal/ossiedir/{osidir_test.go => ossiedir_test.go} (100%) diff --git a/cli/internal/ossiedir/osidir.go b/cli/internal/ossiedir/ossiedir.go similarity index 100% rename from cli/internal/ossiedir/osidir.go rename to cli/internal/ossiedir/ossiedir.go diff --git a/cli/internal/ossiedir/osidir_test.go b/cli/internal/ossiedir/ossiedir_test.go similarity index 100% rename from cli/internal/ossiedir/osidir_test.go rename to cli/internal/ossiedir/ossiedir_test.go From 782fb0c9362731a114c01009c077d0a3ae438036 Mon Sep 17 00:00:00 2001 From: Quigley Malcolm Date: Wed, 15 Jul 2026 15:42:31 -0500 Subject: [PATCH 10/14] Swap references to Open Semantic Interchange to Apache Ossie has moved from the Open Semantic Interchange to incubation with the Apache Software Foundation. As such, the references in this PR needed to be updated accordingly. --- cli/cmd/root.go | 8 ++++---- cli/go.mod | 2 +- cli/main.go | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/cmd/root.go b/cli/cmd/root.go index 772d87b1..fe213a52 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -1,15 +1,15 @@ package cmd import ( - "github.com/open-semantic-interchange/ossie/cli/cmd/plugin" - "github.com/open-semantic-interchange/ossie/cli/internal/ossiedir" + "github.com/apache/ossie/cli/cmd/plugin" + "github.com/apache/ossie/cli/internal/ossiedir" "github.com/spf13/cobra" ) var rootCmd = &cobra.Command{ Use: "ossie", - Short: "Open Semantic Interchange CLI", - Long: `ossie is the command-line tool for the Open Semantic Interchange (OSSIE) project.`, + Short: "Apache Ossie (incubating) CLI", + Long: `ossie is the command-line tool for the Apache Ossie (incubating) project.`, // NOTE: Cobra does NOT automatically chain PersistentPreRunE from parent to // child. If any subcommand defines its own PersistentPreRunE or PreRunE, this // function will not run for that subcommand. Future subcommands that define diff --git a/cli/go.mod b/cli/go.mod index a8284253..49f2219f 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -1,4 +1,4 @@ -module github.com/open-semantic-interchange/ossie/cli +module github.com/apache/ossie/cli go 1.22 diff --git a/cli/main.go b/cli/main.go index 9d2a2028..305d92d8 100644 --- a/cli/main.go +++ b/cli/main.go @@ -3,7 +3,7 @@ package main import ( "os" - "github.com/open-semantic-interchange/ossie/cli/cmd" + "github.com/apache/ossie/cli/cmd" ) // version, commit, and date are set at build time by GoReleaser via ldflags. From b834d75979802162e5eda52a4e6aaf7d55e0b0ab Mon Sep 17 00:00:00 2001 From: Quigley Malcolm Date: Wed, 15 Jul 2026 16:05:10 -0500 Subject: [PATCH 11/14] chore(cli): align go.mod version with .tool-versions pin cli/go.mod declared `go 1.22` while cli/.tool-versions pins golang 1.26.2, giving contributors two conflicting sources of truth for which Go version this module targets. Flagged in review by khush-bhatia on PR #151. There's no existing compatibility requirement forcing a lower minimum, so align go.mod to the same version .tool-versions already pins rather than introduce a toolchain directive or lower the asdf pin. --- cli/go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/go.mod b/cli/go.mod index 49f2219f..c57048e1 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -1,6 +1,6 @@ module github.com/apache/ossie/cli -go 1.22 +go 1.26.2 require github.com/spf13/cobra v1.10.2 From ccd7c55588d463ccb50b3a636dd12a5b60a14dc9 Mon Sep 17 00:00:00 2001 From: Quigley Malcolm Date: Wed, 15 Jul 2026 16:05:20 -0500 Subject: [PATCH 12/14] refactor(cli): let cobra enforce --from/--to via MarkFlagsOneRequired convert.go hand-rolled a check for the "neither --from nor --to set" case alongside MarkFlagsMutuallyExclusive, which already covers the "both set" case. Suggested by khush-bhatia on PR #151 that cobra's MarkFlagsOneRequired covers this directly. Combining MarkFlagsMutuallyExclusive with MarkFlagsOneRequired on the same flag set gives "exactly one of" semantics entirely through cobra's flag annotations, so the manual check and its now-unneeded fmt.Errorf branch in runConvert are removed. --- cli/cmd/convert.go | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/cli/cmd/convert.go b/cli/cmd/convert.go index 92722eea..87f31e0d 100644 --- a/cli/cmd/convert.go +++ b/cli/cmd/convert.go @@ -23,17 +23,10 @@ func init() { _ = convertCmd.MarkFlagRequired("input") convertCmd.MarkFlagsMutuallyExclusive("from", "to") + convertCmd.MarkFlagsOneRequired("from", "to") } func runConvert(cmd *cobra.Command, args []string) error { - from, _ := cmd.Flags().GetString("from") - to, _ := cmd.Flags().GetString("to") - - // MarkFlagsMutuallyExclusive handles the both-set case; handle neither here. - if from == "" && to == "" { - return fmt.Errorf("exactly one of --from or --to must be specified") - } - fmt.Fprintln(cmd.OutOrStdout(), "not yet implemented") return nil } From b46aa7f5953b745d1d9880db39f21af01b5f20dc Mon Sep 17 00:00:00 2001 From: Khushboo Bhatia Date: Wed, 15 Jul 2026 15:46:59 -0700 Subject: [PATCH 13/14] Apply suggestions from code review Co-authored-by: Khushboo Bhatia --- cli/cmd/convert.go | 6 +++--- cli/cmd/plugin/plugin.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cli/cmd/convert.go b/cli/cmd/convert.go index 87f31e0d..dd7d3600 100644 --- a/cli/cmd/convert.go +++ b/cli/cmd/convert.go @@ -8,13 +8,13 @@ import ( var convertCmd = &cobra.Command{ Use: "convert --from --input | --to --input ", - Short: "Convert a semantic model between OSSIE and a platform format", + Short: "Convert a semantic model between Ossie and a platform format", RunE: runConvert, } func init() { - convertCmd.Flags().String("from", "", "Source platform — converts platform → OSSIE") - convertCmd.Flags().String("to", "", "Target platform — converts OSSIE → platform") + convertCmd.Flags().String("from", "", "Source platform — converts platform → Ossie") + convertCmd.Flags().String("to", "", "Target platform — converts Ossie → platform") convertCmd.Flags().StringP("input", "i", "", "Input file or directory path (required)") convertCmd.Flags().StringP("output", "o", "", "Output directory path (default: ./ossie-output//)") convertCmd.Flags().String("plugin", "", "Path to plugin directory (bypasses name-based discovery)") diff --git a/cli/cmd/plugin/plugin.go b/cli/cmd/plugin/plugin.go index d996f133..c19e37d7 100644 --- a/cli/cmd/plugin/plugin.go +++ b/cli/cmd/plugin/plugin.go @@ -6,7 +6,7 @@ import "github.com/spf13/cobra" // register it. Invoking it bare prints help. var Cmd = &cobra.Command{ Use: "plugin", - Short: "Manage OSSIE plugins", + Short: "Manage Ossie plugins", } func init() { From ed0a0ad219a7b9aa8522aaf6be7d4ea680507aef Mon Sep 17 00:00:00 2001 From: Quigley Malcolm Date: Thu, 16 Jul 2026 11:11:44 -0500 Subject: [PATCH 14/14] chore(cli): stop tracking .tool-versions, gitignore for local use MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cli/go.mod and cli/.tool-versions each pinned the Go version independently — even after b834d75 aligned their values, nothing forced the two to move together, so they could drift apart again on the next bump. Removed cli/.tool-versions from the repo so go.mod is the only in-repo source of truth. Verified this doesn't break tooling: CI already resolves its Go version from go.mod via actions/setup-go's `go-version-file: cli/go.mod` (.github/workflows/cli-ci.yml), and asdf's golang plugin (~/.asdf/plugins/golang/bin/parse-legacy-file) reads the version straight out of go.mod's `go` directive when no .tool-versions is present. Rather than deleting the file with no path back, gitignored cli/.tool-versions so contributors whose version manager still wants its own pin file can keep one locally without risk of it getting committed and re-introducing a second tracked source of truth. Treats the toolchain-manager pin as personal environment config, not project config — the same rationale as gitignoring editor-specific config instead of committing it. Caveats: - Relies on asdf's legacy-version-file lookup, which is off by default and must be enabled per-user via `legacy_version_file = yes` in ~/.asdfrc (or ASDF_LEGACY_VERSION_FILE=yes) for `asdf install`/`asdf current` to auto-detect the version from go.mod. Not adding a repo-level workaround since asdf has no per-project way to set this. - No automated enforcement that a contributor's local pin matches go.mod; a stale local .tool-versions can still silently drift, as demonstrated on this machine when the global asdf fallback (golang 1.20.1) was too old to parse a 3-component go directive. Accepted as the tradeoff for not owning tooling-manager config in the repo. --- .gitignore | 3 ++- cli/.tool-versions | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 cli/.tool-versions diff --git a/.gitignore b/.gitignore index db0a5339..77ca2ead 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ # Go CLI cli/dist/ -cli/ossie \ No newline at end of file +cli/ossie +cli/.tool-versions diff --git a/cli/.tool-versions b/cli/.tool-versions deleted file mode 100644 index 05a23a62..00000000 --- a/cli/.tool-versions +++ /dev/null @@ -1 +0,0 @@ -golang 1.26.2