Skip to content
Open
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
89 changes: 89 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
name: Build and Release Custom Plugins

on:
push:
branches:
- main
paths:
- 'plugins/restic/**'
- 'plugins/llm/**'
- '.github/workflows/release.yml'

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
plugin: [restic, llm]
goos: [linux, darwin]
goarch: [amd64, arm64]

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.21'

- name: Generate registry
run: go run cmd/contrib/main.go registry

- name: Build plugin
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
run: |
mkdir -p dist
go build -o dist/${{ matrix.plugin }}-${{ matrix.goos }}-${{ matrix.goarch }} \
-ldflags="-X 'main.PluginName=${{ matrix.plugin }}'" \
./cmd/contrib/build/

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.plugin }}-${{ matrix.goos }}-${{ matrix.goarch }}
path: dist/${{ matrix.plugin }}-${{ matrix.goos }}-${{ matrix.goarch }}

release:
needs: build
runs-on: ubuntu-latest
permissions:
contents: write

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true

- name: Generate release tag
id: tag
run: echo "tag=v$(date +'%Y%m%d.%H%M%S')" >> $GITHUB_OUTPUT

- name: Create Release
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
gh release create ${{ steps.tag.outputs.tag }} \
--title "Custom Plugins ${{ steps.tag.outputs.tag }}" \
--notes "Custom 1Password shell plugins build.

## Included Plugins
- **restic** - Backup tool with support for S3, Azure, B2, GCS, and REST backends
- **llm** - Simon Willison's LLM CLI with OpenAI and OpenRouter support

## Installation
Download the appropriate binary for your platform and place it in \`~/.config/op/plugins/local/\`

\`\`\`bash
mkdir -p ~/.config/op/plugins/local
chmod 700 ~/.config/op/plugins/local
# Download and rename to just the plugin name (e.g., 'restic' or 'llm')
\`\`\`" \
dist/*
62 changes: 62 additions & 0 deletions plugins/llm/credentials.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package llm

import (
"github.com/1Password/shell-plugins/sdk"
"github.com/1Password/shell-plugins/sdk/importer"
"github.com/1Password/shell-plugins/sdk/provision"
"github.com/1Password/shell-plugins/sdk/schema"
"github.com/1Password/shell-plugins/sdk/schema/credname"
)

// Custom field names for llm-specific fields
const (
OpenAIAPIKey sdk.FieldName = "OpenAI API Key"
OpenRouterAPIKey sdk.FieldName = "OpenRouter API Key"
)

func Credentials() schema.CredentialType {
return schema.CredentialType{
Name: credname.Credentials,
DocsURL: sdk.URL("https://llm.datasette.io/en/stable/setup.html"),
ManagementURL: nil,
Fields: []schema.CredentialField{
{
Name: OpenAIAPIKey,
MarkdownDescription: "The OpenAI API key used to authenticate to OpenAI models.",
Secret: true,
Optional: true,
Composition: &schema.ValueComposition{
Length: 51,
Prefix: "sk-",
Charset: schema.Charset{
Uppercase: true,
Lowercase: true,
Digits: true,
},
},
},
{
Name: OpenRouterAPIKey,
MarkdownDescription: "The OpenRouter API key used to authenticate to OpenRouter models.",
Secret: true,
Optional: true,
Composition: &schema.ValueComposition{
Length: 51,
Prefix: "sk-or-",
Charset: schema.Charset{
Uppercase: true,
Lowercase: true,
Digits: true,
},
},
},
},
DefaultProvisioner: provision.EnvVars(defaultEnvVarMapping),
Importer: importer.TryEnvVarPair(defaultEnvVarMapping),
}
}

var defaultEnvVarMapping = map[string]sdk.FieldName{
"OPENAI_API_KEY": OpenAIAPIKey,
"OPENROUTER_API_KEY": OpenRouterAPIKey,
}
64 changes: 64 additions & 0 deletions plugins/llm/credentials_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package llm

import (
"testing"

"github.com/1Password/shell-plugins/sdk"
"github.com/1Password/shell-plugins/sdk/plugintest"
)

func TestCredentialsProvisioner(t *testing.T) {
plugintest.TestProvisioner(t, Credentials().DefaultProvisioner, map[string]plugintest.ProvisionCase{
"with OpenAI": {
ItemFields: map[sdk.FieldName]string{
OpenAIAPIKey: "sk-1234567890abcdefghijklmnopqrstuvwxyzEXAMPLEKEY",
},
ExpectedOutput: sdk.ProvisionOutput{
Environment: map[string]string{
"OPENAI_API_KEY": "sk-1234567890abcdefghijklmnopqrstuvwxyzEXAMPLEKEY",
},
},
},
"with OpenRouter": {
ItemFields: map[sdk.FieldName]string{
OpenRouterAPIKey: "sk-or-1234567890abcdefghijklmnopqrstuvwxEXAMPLE",
},
ExpectedOutput: sdk.ProvisionOutput{
Environment: map[string]string{
"OPENROUTER_API_KEY": "sk-or-1234567890abcdefghijklmnopqrstuvwxEXAMPLE",
},
},
},
"with both": {
ItemFields: map[sdk.FieldName]string{
OpenAIAPIKey: "sk-1234567890abcdefghijklmnopqrstuvwxyzEXAMPLEKEY",
OpenRouterAPIKey: "sk-or-1234567890abcdefghijklmnopqrstuvwxEXAMPLE",
},
ExpectedOutput: sdk.ProvisionOutput{
Environment: map[string]string{
"OPENAI_API_KEY": "sk-1234567890abcdefghijklmnopqrstuvwxyzEXAMPLEKEY",
"OPENROUTER_API_KEY": "sk-or-1234567890abcdefghijklmnopqrstuvwxEXAMPLE",
},
},
},
})
}

func TestCredentialsImporter(t *testing.T) {
plugintest.TestImporter(t, Credentials().Importer, map[string]plugintest.ImportCase{
"environment": {
Environment: map[string]string{
"OPENAI_API_KEY": "sk-1234567890abcdefghijklmnopqrstuvwxyzEXAMPLEKEY",
"OPENROUTER_API_KEY": "sk-or-1234567890abcdefghijklmnopqrstuvwxEXAMPLE",
},
ExpectedCandidates: []sdk.ImportCandidate{
{
Fields: map[sdk.FieldName]string{
OpenAIAPIKey: "sk-1234567890abcdefghijklmnopqrstuvwxyzEXAMPLEKEY",
OpenRouterAPIKey: "sk-or-1234567890abcdefghijklmnopqrstuvwxEXAMPLE",
},
},
},
},
})
}
22 changes: 22 additions & 0 deletions plugins/llm/llm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package llm

import (
"github.com/1Password/shell-plugins/sdk"
"github.com/1Password/shell-plugins/sdk/needsauth"
"github.com/1Password/shell-plugins/sdk/schema"
"github.com/1Password/shell-plugins/sdk/schema/credname"
)

func LLMCLI() schema.Executable {
return schema.Executable{
Name: "LLM CLI",
Runs: []string{"llm"},
DocsURL: sdk.URL("https://llm.datasette.io/en/stable/"),
NeedsAuth: needsauth.NotForHelpOrVersion(),
Uses: []schema.CredentialUsage{
{
Name: credname.Credentials,
},
},
}
}
22 changes: 22 additions & 0 deletions plugins/llm/plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package llm

import (
"github.com/1Password/shell-plugins/sdk"
"github.com/1Password/shell-plugins/sdk/schema"
)

func New() schema.Plugin {
return schema.Plugin{
Name: "llm",
Platform: schema.PlatformInfo{
Name: "LLM",
Homepage: sdk.URL("https://llm.datasette.io"),
},
Credentials: []schema.CredentialType{
Credentials(),
},
Executables: []schema.Executable{
LLMCLI(),
},
}
}
Loading