Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
3c88a83
feat(cli): scaffold go module with cobra command tree
QMalcolm Jun 9, 2026
fcefdc8
chore(cli): add makefile and goreleaser build pipeline
QMalcolm Jun 9, 2026
ac660a0
ci(cli): add github actions workflow for build and test
QMalcolm Jun 9, 2026
5f269d2
test(cli): add unit tests for internal/osidir
QMalcolm Jun 9, 2026
780dcdb
chore(cli): rename osi to ossie throughout cli scaffold
QMalcolm Jun 10, 2026
22e986e
chore(cli): rename internal osidir package to ossiedir
QMalcolm Jun 12, 2026
32955eb
fix(ossiedir): rename defaultOSIDir constant to defaultOssieDir
QMalcolm Jun 23, 2026
c707545
chore(cli): fix .gitignore to ignore cli/ossie build artifact
QMalcolm Jun 23, 2026
7f9fdb7
chore(ossiedir): rename osidir.go and osidir_test.go to ossiedir
QMalcolm Jun 24, 2026
d11a375
feat(cli): add plugin discovery infrastructure
QMalcolm Jun 15, 2026
ca72dc9
test(cli): replace equalStringSlice helper with slices.Equal
QMalcolm Jun 15, 2026
afa1813
fix(cli): use errors.Is for ErrNotExist checks in discover
QMalcolm Jun 15, 2026
7b8bb11
test(cli): use t.TempDir for nonexistent dir test fixture
QMalcolm Jun 15, 2026
8fdac36
test(cli): add test coverage for plugin setup field
QMalcolm Jun 15, 2026
8747de3
docs(cli): document intentional lenient YAML unmarshaling in loadPlugin
QMalcolm Jun 15, 2026
fb5069d
refactor(plugin): rename osi → ossie in ConvertConfig, yaml fields, a…
QMalcolm Jun 23, 2026
bc5abe9
refactor(plugin): drop osi_* backward compat from rawPlugin
QMalcolm Jun 23, 2026
aa56370
feat(plugin): implement basic ossie plugin list command
QMalcolm Jun 23, 2026
e6b0d74
chore(cli): add make install target for local development
QMalcolm Jun 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .github/workflows/cli-ci.yml
Original file line number Diff line number Diff line change
@@ -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 ./...
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
**/__pycache__/
**/.venv/

# Go CLI
cli/dist/
cli/ossie
48 changes: 48 additions & 0 deletions cli/.goreleaser.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
version: 2

project_name: ossie

before:
hooks:
- go mod tidy

builds:
- id: ossie
main: .
binary: ossie
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: ossie
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:"
1 change: 1 addition & 0 deletions cli/.tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
golang 1.26.2
22 changes: 22 additions & 0 deletions cli/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
BINARY_NAME := ossie
BUILD_DIR := dist

.PHONY: build install test lint release-dry-run clean

build:
go build -o $(BUILD_DIR)/$(BINARY_NAME) .

install:
go build -o $(shell go env GOPATH)/bin/$(BINARY_NAME) .

test:
go test ./...

lint:
go vet ./...

release-dry-run:
goreleaser release --snapshot --clean --config .goreleaser.yaml

clean:
rm -rf $(BUILD_DIR)
39 changes: 39 additions & 0 deletions cli/cmd/convert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package cmd

import (
"fmt"

"github.com/spf13/cobra"
)

var convertCmd = &cobra.Command{
Use: "convert --from <platform> --input <path> | --to <platform> --input <path>",
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().StringP("input", "i", "", "Input file or directory path (required)")
convertCmd.Flags().StringP("output", "o", "", "Output directory path (default: ./ossie-output/<plugin>/<direction>)")
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
}
22 changes: 22 additions & 0 deletions cli/cmd/plugin/install.go
Original file line number Diff line number Diff line change
@@ -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
}
47 changes: 47 additions & 0 deletions cli/cmd/plugin/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package plugin

import (
"fmt"
"os"
"path/filepath"
"text/tabwriter"

"github.com/spf13/cobra"

"github.com/open-semantic-interchange/ossie/cli/internal/ossiedir"
"github.com/open-semantic-interchange/ossie/cli/internal/plugin"
)

var listCmd = &cobra.Command{
Use: "list",
Short: "List installed plugins",
// TODO(P1): cross-reference against the embedded plugin registry to show
// latest available versions and update indicators once registry embedding
// (F4) is implemented.
RunE: runPluginList,
}

func runPluginList(cmd *cobra.Command, args []string) error {
pluginsDir, err := ossiedir.PluginDir()
if err != nil {
return err
}

plugins, err := plugin.Discover(pluginsDir, os.Stderr)
if err != nil {
return err
}

if len(plugins) == 0 {
fmt.Fprintln(cmd.OutOrStdout(), "no plugins installed")
return nil
}

w := tabwriter.NewWriter(cmd.OutOrStdout(), 0, 0, 2, ' ', 0)
fmt.Fprintln(w, "NAME\tPLATFORM\tSPEC")
for _, p := range plugins {
name := filepath.Base(p.Path)
fmt.Fprintf(w, "%s\t%s\t%s\n", name, p.Platform.Name, p.OSSIEPluginSpec)
}
return w.Flush()
}
16 changes: 16 additions & 0 deletions cli/cmd/plugin/plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package plugin

import "github.com/spf13/cobra"

// 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 OSSIE plugins",
}

func init() {
Cmd.AddCommand(listCmd)
Cmd.AddCommand(installCmd)
Cmd.AddCommand(removeCmd)
}
19 changes: 19 additions & 0 deletions cli/cmd/plugin/remove.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package plugin

import (
"fmt"

"github.com/spf13/cobra"
)

var removeCmd = &cobra.Command{
Use: "remove <name>",
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
}
38 changes: 38 additions & 0 deletions cli/cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package cmd

import (
"github.com/open-semantic-interchange/ossie/cli/cmd/plugin"
"github.com/open-semantic-interchange/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.`,
// 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 ossiedir.EnsurePluginDir() explicitly.
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
return ossiedir.EnsurePluginDir()
},
}

// Execute runs the root command. Called by main.
func Execute() error {
return rootCmd.Execute()
}

// SetVersion sets the version string reported by `ossie --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)
}
24 changes: 24 additions & 0 deletions cli/cmd/validate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package cmd

import (
"fmt"

"github.com/spf13/cobra"
)

var validateCmd = &cobra.Command{
Use: "validate [flags] <path> [<path>...]",
Short: "Validate one or more OSSIE 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
}
13 changes: 13 additions & 0 deletions cli/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module github.com/open-semantic-interchange/ossie/cli

go 1.22

require (
github.com/spf13/cobra v1.10.2
gopkg.in/yaml.v3 v3.0.1
)

require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/pflag v1.0.9 // indirect
)
13 changes: 13 additions & 0 deletions cli/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Loading
Loading