Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 7 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@ This file provides guidance to various AI agents when working with code in this

### Building
```bash
# Build for current platform
# Build with version info (recommended)
make build

# Or install to $GOPATH/bin
make install

# Build without make
go build -o pinner ./cmd/pinner

# Cross-compile for different platforms
GOOS=linux GOARCH=amd64 go build -o pinner-linux-amd64 ./cmd/pinner
GOOS=darwin GOARCH=arm64 go build -o pinner-darwin-arm64 ./cmd/pinner
GOOS=windows GOARCH=amd64 go build -o pinner-windows-amd64.exe ./cmd/pinner

# Build with version info
go build -ldflags="-X 'build.Version=1.0.0' -X 'build.GitCommit=abc123'" -o pinner ./cmd/pinner
```

### Testing
Expand Down
28 changes: 28 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
.PHONY: build install clean

VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo dev)
GIT_COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null || echo unknown)
BUILD_TIME := $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
GO_VERSION := $(shell go version | sed 's/go version //')
PLATFORM := $(shell go env GOOS)
ARCH := $(shell go env GOARCH)

PKG := go.lumeweb.com/pinner-cli/build

LDFLAGS := -X '$(PKG).Version=$(VERSION)' \
-X '$(PKG).GitCommit=$(GIT_COMMIT)' \
-X '$(PKG).GitBranch=$(GIT_BRANCH)' \
-X '$(PKG).BuildTime=$(BUILD_TIME)' \
-X '$(PKG).GoVersion=$(GO_VERSION)' \
-X '$(PKG).Platform=$(PLATFORM)' \
-X '$(PKG).Architecture=$(ARCH)'

build:
go build -ldflags="$(LDFLAGS)" -o pinner ./cmd/pinner

install:
go install -ldflags="$(LDFLAGS)" ./cmd/pinner

clean:
rm -f pinner
35 changes: 23 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,35 @@ A developer-focused CLI for pinning content to IPFS, managing websites, DNS, and

## Installation

### From Releases

Download the latest binary from the [releases page](https://github.com/lumeweb/pinner-cli/releases).

### From go install

```bash
go install github.com/lumeweb/pinner-cli/cmd/pinner@latest
```

### From Source

```bash
# Build for current platform
# Build with version info from git (recommended)
make build

# Or install to $GOPATH/bin
make install

# Build without make
go build -o pinner ./cmd/pinner

# Cross-compile for different platforms
GOOS=linux GOARCH=amd64 go build -o pinner-linux-amd64 ./cmd/pinner
GOOS=darwin GOARCH=arm64 go build -o pinner-darwin-arm64 ./cmd/pinner
GOOS=windows GOARCH=amd64 go build -o pinner-windows-amd64.exe ./cmd/pinner

# Build with version info
go build -ldflags="-X 'build.Version=1.0.0' -X 'build.GitCommit=abc123'" -o pinner ./cmd/pinner
```

### From Pre-built Binaries

Download the latest release from the [releases page](https://github.com/lumeweb/pinner-cli/releases).
The Makefile injects git commit, branch, version, build time, and platform info via ldflags.

## Quick Start

Expand Down Expand Up @@ -277,7 +288,7 @@ pinner pins rm --all --status failed --force
pinner pins add bafybeig... --dry-run
```

**Note**: `pin`, `list`, `status`, and `unpin` are aliases for `pins add`, `pins ls`, `pins status`, and `pins rm` respectively. They work identically and are not deprecated.
**Note**: `pin`, `list`, `status`, and `unpin` are first-class shortcuts for `pins add`, `pins ls`, `pins status`, and `pins rm` respectively — use whichever you prefer.

### Pin

Expand Down Expand Up @@ -837,16 +848,16 @@ mockery --name=AuthService
### Building

```bash
# Build for current platform
# Build with version info (recommended)
make build

# Build without make
go build -o pinner ./cmd/pinner

# Cross-compile for different platforms
GOOS=linux GOARCH=amd64 go build -o pinner-linux-amd64 ./cmd/pinner
GOOS=darwin GOARCH=arm64 go build -o pinner-darwin-arm64 ./cmd/pinner
GOOS=windows GOARCH=amd64 go build -o pinner-windows-amd64.exe ./cmd/pinner

# Build with version info
go build -ldflags="-X 'build.Version=1.0.0' -X 'build.GitCommit=abc123'" -o pinner ./cmd/pinner
```

### Running the CLI
Expand Down
8 changes: 4 additions & 4 deletions pkg/cli/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ After successful verification, 2FA will be required for all future logins.`,
},
Action: func(ctx context.Context, cmd *cli.Command) error {
output := setupOutput(cmd)
return accountOTPEnable(ctx, cmd, output, defaultConfigManagerFactory, defaultAuthServiceFactory)
return accountOTPEnable(ctx, newCLICommandWrapper(cmd), output, defaultConfigManagerFactory, defaultAuthServiceFactory)
},
},
{
Expand All @@ -78,14 +78,14 @@ WARNING: This reduces your account security. Consider re-enabling 2FA.`,
},
Action: func(ctx context.Context, cmd *cli.Command) error {
output := setupOutput(cmd)
return accountOTPDisable(ctx, cmd, output, defaultConfigManagerFactory, defaultAuthServiceFactory)
return accountOTPDisable(ctx, newCLICommandWrapper(cmd), output, defaultConfigManagerFactory, defaultAuthServiceFactory)
},
},
},
}
}

func accountOTPEnable(ctx context.Context, cmd *cli.Command, output Output, cfgMgrFactory ConfigManagerFactory, authServiceFactory AuthServiceFactory) error {
func accountOTPEnable(ctx context.Context, cmd flagGetter, output Output, cfgMgrFactory ConfigManagerFactory, authServiceFactory AuthServiceFactory) error {
cfgMgr, err := cfgMgrFactory()
if err != nil {
return fmt.Errorf("failed to initialize config manager: %w", err)
Expand All @@ -99,7 +99,7 @@ func accountOTPEnable(ctx context.Context, cmd *cli.Command, output Output, cfgM
return authService.EnableOTP(ctx, otpCode)
}

func accountOTPDisable(ctx context.Context, cmd *cli.Command, output Output, cfgMgrFactory ConfigManagerFactory, authServiceFactory AuthServiceFactory) error {
func accountOTPDisable(ctx context.Context, cmd flagGetter, output Output, cfgMgrFactory ConfigManagerFactory, authServiceFactory AuthServiceFactory) error {
cfgMgr, err := cfgMgrFactory()
if err != nil {
return fmt.Errorf("failed to initialize config manager: %w", err)
Expand Down
46 changes: 22 additions & 24 deletions pkg/cli/account_api_keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/urfave/cli/v3"
portalsdk "go.lumeweb.com/portal-sdk"
"go.lumeweb.com/pinner-cli/pkg/config"
)

func newAccountAPIKeysCommand() *cli.Command {
Expand Down Expand Up @@ -41,7 +42,12 @@ Use --search to filter keys by name.`,
},
Action: func(ctx context.Context, cmd *cli.Command) error {
output := setupOutput(cmd)
return accountAPIKeysList(ctx, cmd, output, defaultConfigManagerFactory, defaultAuthServiceFactory, defaultAPIKeyServiceFactory)
cfgMgr, err := defaultConfigManagerFactory()
if err != nil {
return err
}
authToken := GetAuthToken(cmd, cfgMgr)
return accountAPIKeysList(ctx, newCLICommandWrapper(cmd), output, cfgMgr, authToken, defaultAuthServiceFactory, defaultAPIKeyServiceFactory)
},
},
{
Expand All @@ -58,7 +64,12 @@ This key can be used with:
PINNER_AUTH_TOKEN=<token> pinner <command>`,
Action: func(ctx context.Context, cmd *cli.Command) error {
output := setupOutput(cmd)
return accountAPIKeysCreate(ctx, cmd, output, defaultConfigManagerFactory, defaultAuthServiceFactory, defaultAPIKeyServiceFactory)
cfgMgr, err := defaultConfigManagerFactory()
if err != nil {
return err
}
authToken := GetAuthToken(cmd, cfgMgr)
return accountAPIKeysCreate(ctx, newCLICommandWrapper(cmd), output, cfgMgr, authToken, defaultAuthServiceFactory, defaultAPIKeyServiceFactory)
},
},
{
Expand All @@ -79,21 +90,20 @@ current key, you must re-authenticate with 'pinner auth'.`,
},
Action: func(ctx context.Context, cmd *cli.Command) error {
output := setupOutput(cmd)
return accountAPIKeysDelete(ctx, cmd, output, defaultConfigManagerFactory, defaultAuthServiceFactory, defaultAPIKeyServiceFactory)
cfgMgr, err := defaultConfigManagerFactory()
if err != nil {
return err
}
authToken := GetAuthToken(cmd, cfgMgr)
return accountAPIKeysDelete(ctx, newCLICommandWrapper(cmd), output, cfgMgr, authToken, defaultAuthServiceFactory, defaultAPIKeyServiceFactory)
},
},
},
}
}

func accountAPIKeysList(ctx context.Context, cmd *cli.Command, output Output, cfgMgrFactory ConfigManagerFactory, authServiceFactory AuthServiceFactory, svcFactory APIKeyServiceFactory) error {
cfgMgr, err := cfgMgrFactory()
if err != nil {
return fmt.Errorf("failed to initialize config manager: %w", err)
}

func accountAPIKeysList(ctx context.Context, cmd flagGetter, output Output, cfgMgr config.Manager, authToken string, authServiceFactory AuthServiceFactory, svcFactory APIKeyServiceFactory) error {
apiEndpoint := cfgMgr.Config().GetAPIEndpoint()
authToken := GetAuthToken(cmd, cfgMgr)
authService := authServiceFactory(cfgMgr, output, apiEndpoint)
svc := svcFactory(authService, authToken)

Expand Down Expand Up @@ -136,14 +146,8 @@ func accountAPIKeysList(ctx context.Context, cmd *cli.Command, output Output, cf
return nil
}

func accountAPIKeysCreate(ctx context.Context, cmd *cli.Command, output Output, cfgMgrFactory ConfigManagerFactory, authServiceFactory AuthServiceFactory, svcFactory APIKeyServiceFactory) error {
cfgMgr, err := cfgMgrFactory()
if err != nil {
return fmt.Errorf("failed to initialize config manager: %w", err)
}

func accountAPIKeysCreate(ctx context.Context, cmd argsFlagGetter, output Output, cfgMgr config.Manager, authToken string, authServiceFactory AuthServiceFactory, svcFactory APIKeyServiceFactory) error {
apiEndpoint := cfgMgr.Config().GetAPIEndpoint()
authToken := GetAuthToken(cmd, cfgMgr)
authService := authServiceFactory(cfgMgr, output, apiEndpoint)
svc := svcFactory(authService, authToken)

Expand Down Expand Up @@ -177,14 +181,8 @@ func accountAPIKeysCreate(ctx context.Context, cmd *cli.Command, output Output,
return nil
}

func accountAPIKeysDelete(ctx context.Context, cmd *cli.Command, output Output, cfgMgrFactory ConfigManagerFactory, authServiceFactory AuthServiceFactory, svcFactory APIKeyServiceFactory) error {
cfgMgr, err := cfgMgrFactory()
if err != nil {
return fmt.Errorf("failed to initialize config manager: %w", err)
}

func accountAPIKeysDelete(ctx context.Context, cmd argsFlagGetterWithBool, output Output, cfgMgr config.Manager, authToken string, authServiceFactory AuthServiceFactory, svcFactory APIKeyServiceFactory) error {
apiEndpoint := cfgMgr.Config().GetAPIEndpoint()
authToken := GetAuthToken(cmd, cfgMgr)
authService := authServiceFactory(cfgMgr, output, apiEndpoint)
svc := svcFactory(authService, authToken)

Expand Down
37 changes: 37 additions & 0 deletions pkg/cli/account_api_keys_service_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package cli

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestNewAPIKeyService(t *testing.T) {
mockAuth := NewMockAuthService(t)
svc := NewAPIKeyService(mockAuth, "test-token")
require.NotNil(t, svc)
}

func TestDefaultAPIKeyServiceFactory(t *testing.T) {
mockAuth := NewMockAuthService(t)
svc := defaultAPIKeyServiceFactory(mockAuth, "test-token")
require.NotNil(t, svc)
}

func TestAPIKeyServiceRequireAuthenticated(t *testing.T) {
t.Run("authenticated with token", func(t *testing.T) {
mockAuth := NewMockAuthService(t)
svc := NewAPIKeyService(mockAuth, "test-token")
err := svc.RequireAuthenticated()
assert.NoError(t, err)
})

t.Run("not authenticated without token", func(t *testing.T) {
mockAuth := NewMockAuthService(t)
svc := NewAPIKeyService(mockAuth, "")
err := svc.RequireAuthenticated()
assert.Error(t, err)
assert.Equal(t, ErrNotAuthenticated, err)
})
}
Loading
Loading