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
101 changes: 101 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
name: Release on VERSION bump

# Auto-tag and auto-release whenever the top-level VERSION file changes on main.
# The workflow imports a GPG signing key (so the tag is signed, matching the
# CONTRIBUTING.md signed-commit policy) and attaches per-skill zip artifacts to
# the GitHub release for claude.ai users who can't install skills from a git URL.

on:
push:
branches: [main]
paths:
- 'VERSION'
workflow_dispatch:

permissions:
contents: write

jobs:
release:
name: Tag + release if VERSION bumped
runs-on: ubuntu-latest
steps:
- name: Checkout (full history for tag lookup)
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Read VERSION
id: version
run: |
VERSION=$(cat VERSION | tr -d '[:space:]')
if [ -z "$VERSION" ]; then
echo "::error::VERSION file is empty"
exit 1
fi
if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.]+)?$'; then
echo "::error::VERSION '$VERSION' is not valid SemVer"
exit 1
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "tag=v$VERSION" >> "$GITHUB_OUTPUT"
echo "VERSION: $VERSION"

- name: Skip if tag already exists
id: tag_check
run: |
if git rev-parse "${{ steps.version.outputs.tag }}" >/dev/null 2>&1; then
echo "Tag ${{ steps.version.outputs.tag }} already exists. Nothing to do."
echo "skip=true" >> "$GITHUB_OUTPUT"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi

- name: Import signing GPG key
if: steps.tag_check.outputs.skip == 'false'
id: import_gpg
uses: crazy-max/ghaction-import-gpg@v6
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.GPG_PASSPHRASE }}
git_config_global: true
git_user_signingkey: true
git_tag_gpgsign: true

- name: Create and push signed tag
if: steps.tag_check.outputs.skip == 'false'
env:
GIT_AUTHOR_NAME: ${{ steps.import_gpg.outputs.name }}
GIT_AUTHOR_EMAIL: ${{ steps.import_gpg.outputs.email }}
GIT_COMMITTER_NAME: ${{ steps.import_gpg.outputs.name }}
GIT_COMMITTER_EMAIL: ${{ steps.import_gpg.outputs.email }}
run: |
TAG="${{ steps.version.outputs.tag }}"
git tag -s "$TAG" -m "Release $TAG"
git push origin "$TAG"
echo "✓ Pushed signed tag $TAG"

- name: Build per-skill zip artifacts
if: steps.tag_check.outputs.skip == 'false'
id: zips
run: |
mkdir -p /tmp/skill-zips
VERSION="${{ steps.version.outputs.version }}"
for dir in skills/*/; do
name=$(basename "$dir")
( cd skills && zip -r "/tmp/skill-zips/${name}-v${VERSION}.zip" "$name" -x '*.DS_Store' )
echo "✓ Built ${name}-v${VERSION}.zip"
done
ls -la /tmp/skill-zips

- name: Create GitHub release with skill zips
if: steps.tag_check.outputs.skip == 'false'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="${{ steps.version.outputs.tag }}"
gh release create "$TAG" \
--title "$TAG" \
--generate-notes \
/tmp/skill-zips/*.zip
echo "✓ Created release $TAG"
16 changes: 16 additions & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Code owners for ShiftControl-io/skills
#
# Every PR must be approved by a code owner listed here.
# This file is enforced by GitHub branch protection on `main`.

# Default — anything in the repo
* @shiftcontrol-dan

# Skill content — same as default for now; expand as the team grows
skills/ @shiftcontrol-dan

# Repo tooling and governance
.github/ @shiftcontrol-dan
CONTRIBUTING.md @shiftcontrol-dan
CODEOWNERS @shiftcontrol-dan
LICENSE @shiftcontrol-dan
14 changes: 10 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ Thanks for your interest in contributing. This repo holds customer-facing AI ski

1. **Skills propose, humans approve.** Every skill that performs a write MUST present a concrete diff (before → after) and require explicit user approval before calling the MCP server. Never auto-confirm.
2. **No invented identifiers.** Skills MUST source every UUID from a prior `list_*` or `get_*` tool call. Guessing or constructing UUIDs is rejected at review.
3. **Audit trail in notes.** Any write that doesn't already produce an audit-log entry on the backend MUST append a `notes` value identifying the skill, version, and source data (e.g. `"Updated via refresh-subscription-info v0.1.0 from invoice dated 2026-03-15"`).
4. **Stay within the documented MCP tool surface.** If you need a new tool, file an MCP server change first; don't work around the API.
3. **Stay within the documented MCP tool surface.** If you need a new tool, file an MCP server change first; don't work around the API.

## Commit signing — required

Expand Down Expand Up @@ -49,8 +48,15 @@ Same flow. Bump the version when behavior changes meaningfully (new MCP tool dep
## Release process

- Merged PRs land on `main`.
- We tag releases as `vMAJOR.MINOR.PATCH` (`v0.1.0`, `v0.2.0`, etc.).
- Each tag triggers a release with zipped skill folders for claude.ai users who can't install from a Git URL directly.
- The top-level `VERSION` file is the source of truth (single SemVer line, e.g. `0.1.0`).
- To cut a release: open a PR that bumps `VERSION`. On merge, `.github/workflows/release.yaml` automatically:
1. Imports a CI signing GPG key
2. Creates a signed tag `v<VERSION>`
3. Pushes the tag
4. Builds per-skill zip artifacts (one zip per `skills/<name>/` folder)
5. Creates a GitHub release with auto-generated notes from the commit history and attaches the zips
- Tags are `vMAJOR.MINOR.PATCH` (`v0.1.0`, `v0.2.0`, etc.) per [SemVer](https://semver.org). Per-skill versions live in the SKILL.md frontmatter for the moment; repo-level VERSION is the release coordinator.
- The release workflow only fires when `VERSION` itself changes — skill edits without a version bump land on `main` without producing a release. Bump `VERSION` deliberately.

## Code of conduct

Expand Down
16 changes: 14 additions & 2 deletions INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

ShiftControl uses the [Model Context Protocol (MCP)](https://modelcontextprotocol.io) — an open standard for connecting AI assistants to applications. To use any of the skills in this repo, you first install the **ShiftControl MCP server**, then install the skill(s) in whichever way your AI tool supports.

You'll authenticate to ShiftControl once via PropelAuth (single sign-on, the same login you use for ShiftControl itself). The MCP server uses your account's permissions — anything you can do in ShiftControl, the AI can do on your behalf via this skill.
You'll sign in to ShiftControl once in your browser — the same login you use for ShiftControl itself. After that, your AI assistant works with your actual ShiftControl data using your existing permissions; it can't do anything you can't already do yourself.

---

Expand All @@ -29,7 +29,19 @@ Add the `shiftcontrol` server inside `mcpServers`:
}
```

Restart Claude Desktop. The first time you use a ShiftControl tool, a browser tab opens for PropelAuth login. Approve once.
Restart Claude Desktop. The first time you use a ShiftControl tool, a browser tab opens for ShiftControl sign-in. Approve once.

### Claude.ai (web + mobile)

In Claude on the web ([claude.ai](https://claude.ai)) or in the Claude mobile app:

1. Open **Customize → Connectors**
2. Click the **+ Add** button and select **Add custom connector**
3. Name: `shiftcontrol`
4. Remote MCP Server URL: `https://mcp.shiftcontrol.io/mcp`
5. Click **Add**

A browser tab opens for ShiftControl sign-in on first use. Approve once.

### Claude Code

Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

## What you can do today

- **Stop chasing invoices through your inbox.** Ask your AI to *"refresh my subscription info from my recent invoices"* and it'll search your email, match each invoice to an app in ShiftControl, show you exactly what would change (e.g. Slack $8/user/mo → $7/user/mo, contract through 2027-03-15), and update only what you approve. Every change is audit-logged.
- **Stop chasing invoices through your inbox.** Ask your AI to *"refresh my subscription info from my recent invoices"* and it'll search your email, match each invoice to an app in ShiftControl, show you exactly what would change (e.g. Slack $8/user/mo → $7/user/mo, contract through 2027-03-15), and update only what you approve. Each change records a short note in the app explaining where the new values came from.
- **Use the AI tool you already use.** Claude Desktop, Claude Code, Cursor, Windsurf, Cline, Continue.dev, ChatGPT, Gemini — see [INSTALL.md](INSTALL.md) for your tool.
- **Sign in once.** Connecting takes a single OAuth click through your normal ShiftControl login. Your AI gets exactly the permissions you already have — nothing more.

Expand All @@ -18,7 +18,7 @@

| Skill | What it does | Status |
|---|---|---|
| [`refresh-subscription-info`](skills/refresh-subscription-info/) | Finds recent SaaS invoices in the user's email and proposes ShiftControl subscription updates (cost, billing frequency, contract terms, audit notes). | v0.1.0 |
| [`refresh-subscription-info`](skills/refresh-subscription-info/) | Finds recent SaaS invoices in the user's email and proposes ShiftControl subscription updates (cost, billing frequency, contract terms, notes). | v0.1.0 |

More coming. See [open skill proposals](https://github.com/ShiftControl-io/skills/issues?q=label%3Askill-request).

Expand All @@ -30,7 +30,7 @@ Every skill in this repo follows three rules:

- **Assumes the ShiftControl MCP server is installed and authenticated** — see INSTALL.md
- **Proposes changes for human approval before any write** — no silent mutations
- **Logs an audit-friendly `notes` value on every change** — the change is traceable back to the skill, version, and source data
- **Records a short note on each change** — so the next person to look at the app's record can see where the updated values came from

## Contributing

Expand Down
1 change: 1 addition & 0 deletions VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.1.0
Loading
Loading