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
10 changes: 10 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Normalize line endings. Git stores LF; checkout is native unless pinned below.
* text=auto

# Shell scripts run on Linux/macOS — they must be LF, even when checked out on Windows.
*.sh text eol=lf

# Windows scripts are conventionally CRLF.
*.ps1 text eol=crlf
*.cmd text eol=crlf
*.bat text eol=crlf
67 changes: 67 additions & 0 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: release-please

# Release automation. On every push to main, release-please maintains a "Release PR" that bumps the
# version (from Conventional Commits) and updates the changelog. Merging that Release PR creates the
# vX.Y.Z tag and a GitHub Release; the build job below then attaches the self-contained binaries.
# MinVer reads the tag release-please creates and stamps it into the binaries — the two compose.
on:
push:
branches: [main]

permissions:
contents: write
issues: write
pull-requests: write

jobs:
release-please:
runs-on: ubuntu-latest
outputs:
release_created: ${{ steps.rp.outputs.release_created }}
tag_name: ${{ steps.rp.outputs.tag_name }}
steps:
# Manifest-driven config (release-please-config.json + .release-please-manifest.json).
# Uses the default GITHUB_TOKEN: the build job runs in this same workflow run (gated on
# release_created), so the "GITHUB_TOKEN does not trigger downstream workflows" limitation
# does not apply and no PAT is needed.
- uses: googleapis/release-please-action@v4
id: rp

build:
needs: release-please
if: ${{ needs.release-please.outputs.release_created }}
strategy:
matrix:
include:
- { rid: win-x64, os: windows-latest, ext: .exe }
- { rid: win-arm64, os: windows-latest, ext: .exe }
- { rid: linux-x64, os: ubuntu-latest, ext: '' }
- { rid: osx-arm64, os: macos-latest, ext: '' }
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # MinVer needs full history + the tag release-please just created.

- uses: actions/setup-dotnet@v5
with:
dotnet-version: '10.0.x'

- name: Publish
run: >
dotnet publish src/Protostar.Cli/Protostar.Cli.csproj
-c Release -r ${{ matrix.rid }} --self-contained true
-p:PublishSingleFile=true -p:EnableCompressionInSingleFile=true
-o publish

# Raw, uncompressed binary named exactly as scripts/install.{ps1,sh} expect to fetch from
# releases/latest/download/ : protostar-<rid>[.exe].
- name: Stage asset
shell: bash
run: mv "publish/protostar${{ matrix.ext }}" "protostar-${{ matrix.rid }}${{ matrix.ext }}"

- name: Upload asset to release
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh release upload "${{ needs.release-please.outputs.tag_name }}" "protostar-${{ matrix.rid }}${{ matrix.ext }}" --clobber
3 changes: 3 additions & 0 deletions .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
".": "0.0.0"
}
17 changes: 17 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project>

<!--
Versioning is driven by git tags via MinVer. The latest reachable tag of the form vMAJOR.MINOR.PATCH
becomes the build version; it is stamped into AssemblyVersion / FileVersion / AssemblyInformationalVersion.
Untagged commits get an auto pre-release suffix (e.g. 0.2.0-alpha.0.3). Cut a release by tagging main:
`git tag -a v0.2.0 -m v0.2.0 && git push origin v0.2.0`. No source edits per release.
-->
<PropertyGroup>
<MinVerTagPrefix>v</MinVerTagPrefix>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="MinVer" Version="7.0.0" PrivateAssets="all" />
</ItemGroup>

</Project>
104 changes: 101 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,111 @@ The loop: **use → sync → refine → suggest → adopt → use.**
## Status

Built incrementally, one ticket at a time (Jira project `PROT`). The first component is the
**CLI** — see [`src/Protostar.Cli`](src/Protostar.Cli) and the install instructions below as it
lands.
**CLI** (`protostar`) — a self-contained binary you can run without installing the .NET runtime.

## Install the CLI

The CLI is a self-contained binary — no .NET runtime needed to run it. Install the latest release
with a one-liner:

**Windows (PowerShell)**

```powershell
irm https://raw.githubusercontent.com/voidprojectssoftware/protostar/main/scripts/install.ps1 | iex
```

**Linux / macOS**

```bash
curl -fsSL https://raw.githubusercontent.com/voidprojectssoftware/protostar/main/scripts/install.sh | sh
```

These download the right binary from the latest [GitHub release](https://github.com/voidprojectssoftware/protostar/releases)
and run `protostar install`, which places `protostar` in a per-user directory and adds it to PATH.
Restart your shell, then:

```console
$ protostar
protostar v0.1.0
Live, continuous refinement of agent skills.

Run protostar --help to see available commands.

$ protostar --version
0.1.0
```

> The version is derived from git tags at build time (via MinVer), not hardcoded. A binary built
> from a tagged commit reports that tag (e.g. `0.1.0`); a local build from an untagged commit reports
> a pre-release like `0.1.0-alpha.0.4`. See [Releasing](#releasing).

### Already have the binary?

If you downloaded `protostar` directly, it installs itself:

```console
$ protostar install # copy into a per-user dir + add to PATH
$ protostar install --dir <DIR> # custom location
$ protostar install --no-modify-path
$ protostar uninstall # remove it
```

> Startup is currently JIT (self-contained, untrimmed); making the binary lean and fast is tracked
> as a separate performance-tuning unit of work.

## Build from source

Requires the .NET 10 SDK.

```bash
dotnet build # build the solution
dotnet run --project src/Protostar.Cli # run the CLI in place

# produce a self-contained binary and self-install it:
dotnet publish src/Protostar.Cli -c Release -r win-x64 --self-contained true \
-p:PublishSingleFile=true -o out
./out/protostar install
```

## Releasing

Releases are automated with [release-please](https://github.com/googleapis/release-please). You never
tag by hand — you just write [Conventional Commits](https://www.conventionalcommits.org) and merge a
Release PR.

**The version flows like this:** Conventional Commit messages (`feat:`, `fix:`, `feat!:` for
breaking) tell release-please how to bump the version. release-please keeps an open "Release PR" that
bumps `version.txt` + `CHANGELOG.md`. Merging that Release PR creates the `vMAJOR.MINOR.PATCH` tag and
a GitHub Release; [MinVer](https://github.com/adamralph/minver) reads that tag at build time and
stamps it into the binaries, which the workflow attaches to the release.

**Day-to-day:**

1. Open a PR for your change. Give it a [Conventional Commit](https://www.conventionalcommits.org)
title (e.g. `feat: add sync command`, `fix: handle missing config`).
2. **Squash-merge** it into `main`. The squash commit takes the PR title, so the title is what
release-please reads — keep it conventional.
3. release-please opens or updates a **Release PR** ("chore: release X.Y.Z"). Review it.
4. **Merge the Release PR** when you want to ship. That tags `main`, creates the GitHub Release, and
the `release-please` workflow builds and attaches the `win-x64`, `win-arm64`, `linux-x64`, and
`osx-arm64` binaries. The released binaries self-report the version via `protostar --version`.

> Tags are created by release-please on `main`, so they are always reachable through history — this
> is what makes MinVer reliable regardless of squash/rebase merges. Do not tag manually.

## Repository layout

```text
protostar/
├─ src/ # source projects (Protostar.*)
├─ src/
│ └─ Protostar.Cli/ # the `protostar` CLI (Spectre.Console.Cli); `install`/`uninstall` commands
├─ scripts/
│ ├─ install.ps1 # curl-able release installer (Windows)
│ └─ install.sh # curl-able release installer (Linux/macOS)
├─ .github/workflows/
│ └─ release-please.yml # release-please: Release PR -> tag -> build + attach binaries
├─ release-please-config.json # release-please configuration
├─ .release-please-manifest.json # release-please version tracker
├─ Directory.Build.props # MinVer git-tag versioning
└─ protostar.sln
```
25 changes: 25 additions & 0 deletions protostar.sln
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,37 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Protostar.Cli", "src\Protostar.Cli\Protostar.Cli.csproj", "{B94673CE-1472-46C5-8538-82062FDD51E6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B94673CE-1472-46C5-8538-82062FDD51E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B94673CE-1472-46C5-8538-82062FDD51E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B94673CE-1472-46C5-8538-82062FDD51E6}.Debug|x64.ActiveCfg = Debug|Any CPU
{B94673CE-1472-46C5-8538-82062FDD51E6}.Debug|x64.Build.0 = Debug|Any CPU
{B94673CE-1472-46C5-8538-82062FDD51E6}.Debug|x86.ActiveCfg = Debug|Any CPU
{B94673CE-1472-46C5-8538-82062FDD51E6}.Debug|x86.Build.0 = Debug|Any CPU
{B94673CE-1472-46C5-8538-82062FDD51E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B94673CE-1472-46C5-8538-82062FDD51E6}.Release|Any CPU.Build.0 = Release|Any CPU
{B94673CE-1472-46C5-8538-82062FDD51E6}.Release|x64.ActiveCfg = Release|Any CPU
{B94673CE-1472-46C5-8538-82062FDD51E6}.Release|x64.Build.0 = Release|Any CPU
{B94673CE-1472-46C5-8538-82062FDD51E6}.Release|x86.ActiveCfg = Release|Any CPU
{B94673CE-1472-46C5-8538-82062FDD51E6}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{B94673CE-1472-46C5-8538-82062FDD51E6} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
EndGlobalSection
EndGlobal
10 changes: 10 additions & 0 deletions release-please-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
"include-component-in-tag": false,
"packages": {
".": {
"release-type": "simple",
"package-name": "protostar"
}
}
}
41 changes: 41 additions & 0 deletions scripts/install.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#Requires -Version 5.1
<#
.SYNOPSIS
protostar installer (Windows). Downloads the latest release binary and self-installs it.

.DESCRIPTION
Resolves the right release asset for this machine, downloads it from the latest GitHub release,
and runs `protostar install`. Run via the one-liner:

irm https://raw.githubusercontent.com/voidprojectssoftware/protostar/main/scripts/install.ps1 | iex

Or download and run directly to pass options (e.g. -Dir), which are forwarded to `protostar install`.
#>
[CmdletBinding()]
param([Parameter(ValueFromRemainingArguments = $true)] [string[]]$InstallArgs)

$ErrorActionPreference = 'Stop'
$repo = 'voidprojectssoftware/protostar'

$arch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString().ToLowerInvariant()
switch ($arch) {
'x64' { $rid = 'win-x64' }
'arm64' { $rid = 'win-arm64' }
default { throw "Unsupported architecture: $arch" }
}

$asset = "protostar-$rid.exe"
$url = "https://github.com/$repo/releases/latest/download/$asset"

$tmp = Join-Path ([System.IO.Path]::GetTempPath()) ("protostar-download-" + [System.IO.Path]::GetRandomFileName())
New-Item -ItemType Directory -Force -Path $tmp | Out-Null
$exe = Join-Path $tmp 'protostar.exe'

try {
Write-Host "Downloading $asset ..." -ForegroundColor Cyan
Invoke-WebRequest -Uri $url -OutFile $exe -UseBasicParsing
& $exe install @InstallArgs
}
finally {
Remove-Item $tmp -Recurse -Force -ErrorAction SilentlyContinue
}
37 changes: 37 additions & 0 deletions scripts/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/bin/sh
# protostar installer (Linux/macOS).
# Downloads the latest release binary and self-installs it via `protostar install`.
#
# curl -fsSL https://raw.githubusercontent.com/voidprojectssoftware/protostar/main/scripts/install.sh | sh
#
# Any extra args are forwarded to `protostar install` (e.g. --dir, --no-modify-path) when the
# script is run directly (not via the piped one-liner).
set -e

repo="voidprojectssoftware/protostar"

os=$(uname -s)
arch=$(uname -m)

case "$os" in
Linux) rid_os="linux" ;;
Darwin) rid_os="osx" ;;
*) echo "Unsupported OS: $os" >&2; exit 1 ;;
esac

case "$arch" in
x86_64 | amd64) rid_arch="x64" ;;
aarch64 | arm64) rid_arch="arm64" ;;
*) echo "Unsupported architecture: $arch" >&2; exit 1 ;;
esac

asset="protostar-${rid_os}-${rid_arch}"
url="https://github.com/${repo}/releases/latest/download/${asset}"

tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT

echo "Downloading ${asset} ..."
curl -fsSL "$url" -o "$tmp/protostar"
chmod +x "$tmp/protostar"
"$tmp/protostar" install "$@"
22 changes: 22 additions & 0 deletions src/Protostar.Cli/CliInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Reflection;

namespace Protostar.Cli;

/// <summary>Static CLI metadata. Version is stamped at build time by MinVer from the latest
/// reachable git tag and surfaced via <c>protostar --version</c>.</summary>
internal static class CliInfo
{
public static string Version { get; } = ResolveVersion();

private static string ResolveVersion()
{
var info = typeof(CliInfo).Assembly
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?
.InformationalVersion;
if (string.IsNullOrEmpty(info))
return "0.0.0";
// MinVer/SourceLink append "+<git-sha>" build metadata; trim it for display.
var plus = info.IndexOf('+');
return plus >= 0 ? info[..plus] : info;
}
}
23 changes: 23 additions & 0 deletions src/Protostar.Cli/Commands/DefaultCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Spectre.Console;
using Spectre.Console.Cli;

namespace Protostar.Cli.Commands;

/// <summary>
/// Runs when <c>protostar</c> is invoked with no command. Prints a short, styled status so an
/// operator can confirm the CLI is installed and working. Real commands (auth, sync, hooks) land
/// in later tickets.
/// </summary>
internal sealed class DefaultCommand : Command<DefaultCommand.Settings>
{
public sealed class Settings : CommandSettings;

protected override int Execute(CommandContext context, Settings settings, CancellationToken cancellation)
{
AnsiConsole.MarkupLine($"[aqua]protostar[/] [grey]v{CliInfo.Version}[/]");
AnsiConsole.MarkupLine("[grey]Live, continuous refinement of agent skills.[/]");
AnsiConsole.WriteLine();
AnsiConsole.MarkupLine("Run [yellow]protostar --help[/] to see available commands.");
return 0;
}
}
Loading