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
25 changes: 19 additions & 6 deletions .claude/skills/quartr/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,29 @@ companies, events, transcripts, reports, slides, audio, and live events.

## Binary

This skill ships inside the `quartr-cli` repo. Build the binary from the repo
root:
Check for an installed binary first — `command -v quartr` — and use it if it is
on `PATH`. `quartr --version` prints the release it came from.

If it is not installed, install a prebuilt one:

```bash
go install github.com/TJC-LP/quartr-cli/cmd/quartr@latest
```

Or download an archive for the platform from
<https://github.com/TJC-LP/quartr-cli/releases> (macOS, Linux, and Windows;
amd64 and arm64), each published with a `SHA256SUMS` file.

This skill also ships inside the `quartr-cli` repo, so when working from a
clone, build from the repo root instead:

```bash
go build -o bin/quartr ./cmd/quartr
make build # ./bin/quartr, with the version baked in
```

Then invoke it as `./bin/quartr` from the repo root, or `$(git rev-parse --show-toplevel)/bin/quartr`
from anywhere inside the worktree. Use `go install ./cmd/quartr` instead to
install it on `$GOBIN/quartr` for PATH-style invocation.
Then invoke it as `./bin/quartr` from the repo root, or
`$(git rev-parse --show-toplevel)/bin/quartr` from anywhere inside the
worktree. `make install` puts it on `$GOBIN/quartr` for PATH-style invocation.

## Auth

Expand Down
16 changes: 11 additions & 5 deletions .claude/skills/quartr/references/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ specific flag or endpoint at hand.
| `audio` | `list`, `get`, `chapters`, `download` | `/audio` | `list` may be tier-restricted; `fileUrl` |
| `live` | `list`, `get` | `/live` | Honors `transcriptVersion` |
| `live audio` | `list`, `get`, `download` | `/live/audio` | Download field is `audio` |
| `live transcripts` | `list`, `get`, `stream` | `/live/transcripts` | `list` may be tier-restricted; stream field is `transcript` |
| `live transcripts` | `list`, `get`, `download`, `stream` | `/live/transcripts` | `list` may be tier-restricted; download and stream both read the `transcript` field |
| `event-types` | `list`, `get` | `/event-types` | Lookup table; `list` returns the whole catalog |
| `document-types` | `list`, `get` | `/document-types` | Lookup table; `list` returns the whole catalog |
| `request` | `get` | (any path) | Escape hatch; `--query k=v --paginate` |
Expand Down Expand Up @@ -62,12 +62,18 @@ Auth precedence: flags > env > config file > defaults.
--end-date 2024-12-31 ISO 8601
--updated-after 2024-01-01 incremental sync lower bound
--updated-before 2024-12-31
--expand event,company event is expanded by the API; company is joined client-side
--type-ids 1,2,3 events / documents* / transcripts* / reports* / slides* / audio*
--event-ids 128301 documents* / transcripts* / reports* / slides* / audio* / live*
--expand event API-side; documents / reports / slides / transcripts / audio.
Dropped on events (a self-expansion) and companies.
--expand company client-side join; works on any resource whose rows carry a
companyId, including events. Only `companies` rejects it,
as redundant.
--type-ids 1,2,3 events / documents / transcripts / reports / slides.
NOT audio — /audio has no typeIds param and drops it.
--event-ids 128301 documents / transcripts / reports / slides / audio / live*
--document-group-ids foo documents* / transcripts* / reports* / slides*
--states live,willBeLive live, live-transcripts, live-audio
--transcript-version 1.7 live, live-transcripts, transcripts (get only), audio (get only)
--transcript-version 1.7 live, live-transcripts (list and get) only; silently
dropped on transcripts/audio, which have no such param
--sort-by id|date events list ONLY; rejected with exit 2 everywhere else
--levels 1,2 chapters subcommand on reports/slides/transcripts/audio
```
Expand Down
25 changes: 25 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,28 @@ jobs:
- uses: golangci/golangci-lint-action@v7
with:
version: v2.12.0

# Exercises the release recipe on every PR. Without this, a broken cross
# compile only shows up after a tag is pushed, which is the worst time.
dist:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true

- name: Build release artifacts
run: make dist VERSION=ci

- name: Smoke test the linux binary
run: |
test "$(./dist/bin/quartr-linux-amd64 --version)" = "quartr ci"

- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/*
retention-days: 7
54 changes: 54 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: Release

on:
push:
tags: ["v*"]

permissions:
contents: write

jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true

- name: Test
run: go test ./...

# Same recipe a maintainer runs locally, so the published archives are
# never a CI-only code path.
- name: Build release artifacts
run: make dist VERSION="${GITHUB_REF_NAME#v}"

# A tag that ships a binary reporting a different version is worse than
# a failed release, so fail here instead.
- name: Verify the tag version was baked in
run: |
expected="quartr ${GITHUB_REF_NAME#v}"
actual="$(./dist/bin/quartr-linux-amd64 --version)"
if [ "$actual" != "$expected" ]; then
echo "version mismatch: binary says '$actual', tag implies '$expected'" >&2
exit 1
fi
echo "$actual"

- name: Publish release
env:
GH_TOKEN: ${{ github.token }}
run: |
# A tag like v0.2.0-rc1 publishes as a prerelease.
prerelease=""
case "$GITHUB_REF_NAME" in
*-*) prerelease="--prerelease" ;;
esac
gh release create "$GITHUB_REF_NAME" \
--title "$GITHUB_REF_NAME" \
--generate-notes \
$prerelease \
dist/*.tar.gz dist/*.zip dist/*SHA256SUMS
40 changes: 36 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Commands

```bash
make build # build ./bin/quartr
make build # build ./bin/quartr, version baked in via -ldflags
make test # go test ./...
make install # go install ./cmd/quartr; if QUARTR_API_KEY is in env,
# also runs `quartr auth login` to persist the key
# to ~/.config/quartr/config.json (mode 0600)
make clean # remove ./bin
make dist # cross-compile release archives + checksums into ./dist
make clean # remove ./bin and ./dist

go test ./internal/cli -run TestBuildConfigPrecedence # run a single test

Expand Down Expand Up @@ -55,9 +56,40 @@ Three internal packages, no external deps (Go stdlib only):

## Repo / branch hygiene

- Remote: `git@github.com:TJC-LP/quartr-cli.git` (private).
- Remote: `git@github.com:TJC-LP/quartr-cli.git` (public).
- Module path is `github.com/TJC-LP/quartr-cli`, which must keep matching the
repo URL — that is what makes `go install github.com/TJC-LP/quartr-cli/cmd/quartr@latest`
resolve. Renaming the repo means renaming the module.
- `main` is protected — push to a feature branch and open a PR with `gh pr create`. Direct pushes to `main` are rejected.
- `.env` and `bin/` are gitignored; the `.env.example` exception is preserved.
- `.env`, `bin/`, and `dist/` are gitignored; the `.env.example` exception is preserved.

## Releases

Cutting a release is one push:

```bash
git tag -a v0.2.0 -m "v0.2.0"
git push origin v0.2.0
```

`.github/workflows/release.yml` fires on `v*` tags, runs the tests, calls
`make dist VERSION=${tag#v}`, and publishes the archives with `gh release
create --generate-notes`. A tag containing a hyphen (`v0.2.0-rc1`) publishes as
a prerelease.

- **Tags carry the `v`; the reported version does not.** `v0.1.0` produces
`quartr 0.1.0`. The workflow asserts this before publishing, so a mismatch
fails the release rather than shipping a mislabeled binary.
- **`make dist` is the single build recipe** — CI calls it rather than
reimplementing the matrix, and the `dist` job in `ci.yml` runs it on every PR
so a broken cross-compile surfaces before a tag exists.
- **Version resolution lives in `internal/quartr/version.go`**, not in `cli`,
so `--version` and the `User-Agent` header cannot drift. Order: `-ldflags
-X ...quartr.buildVersion`, then `debug.ReadBuildInfo` (module version for
`go install pkg@version`, VCS revision for a checkout build), then `dev`.
If you move or rename `buildVersion`, update `VERSION_LDFLAGS` in the Makefile.
- Release archives bundle `README.md` and `LICENSE` alongside the binary and
ship with a `SHA256SUMS` file.

## Skill

Expand Down
52 changes: 48 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,19 +1,63 @@
.PHONY: build install test clean
.PHONY: build install test clean dist

GOBIN := $(shell go env GOBIN)
ifeq ($(GOBIN),)
GOBIN := $(shell go env GOPATH)/bin
endif

# Release string baked into the binary. Defaults to the current git
# description with any leading "v" stripped, so tag v0.1.0 reports 0.1.0.
VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null | sed 's/^v//')
ifeq ($(VERSION),)
VERSION := dev
endif

VERSION_LDFLAGS := -X github.com/TJC-LP/quartr-cli/internal/quartr.buildVersion=$(VERSION)
RELEASE_LDFLAGS := -s -w $(VERSION_LDFLAGS)

# os/arch pairs published on every release.
PLATFORMS := darwin/amd64 darwin/arm64 linux/amd64 linux/arm64 windows/amd64

build:
go build -o bin/quartr ./cmd/quartr
go build -ldflags "$(VERSION_LDFLAGS)" -o bin/quartr ./cmd/quartr

install:
go install ./cmd/quartr
go install -ldflags "$(VERSION_LDFLAGS)" ./cmd/quartr
@if [ -n "$$QUARTR_API_KEY" ]; then printf '%s' "$$QUARTR_API_KEY" | $(GOBIN)/quartr auth login --api-key-stdin; fi

test:
go test ./...

# dist is what the release workflow runs, so a local `make dist` and the
# published artifacts come from the same recipe. Raw binaries land in
# dist/bin for smoke tests; the archives and checksum file get uploaded.
dist:
rm -rf dist
mkdir -p dist/bin
@set -e; for platform in $(PLATFORMS); do \
os=$${platform%/*}; arch=$${platform#*/}; ext=""; \
if [ "$$os" = "windows" ]; then ext=".exe"; fi; \
echo "==> quartr $(VERSION) $$os/$$arch"; \
CGO_ENABLED=0 GOOS=$$os GOARCH=$$arch \
go build -trimpath -ldflags "$(RELEASE_LDFLAGS)" \
-o dist/bin/quartr-$$os-$$arch$$ext ./cmd/quartr; \
stage=dist/stage/quartr_$(VERSION)_$${os}_$${arch}; \
mkdir -p $$stage; \
cp dist/bin/quartr-$$os-$$arch$$ext $$stage/quartr$$ext; \
cp README.md LICENSE $$stage/; \
if [ "$$os" = "windows" ]; then \
(cd dist/stage && zip -qr ../quartr_$(VERSION)_$${os}_$${arch}.zip quartr_$(VERSION)_$${os}_$${arch}); \
else \
tar -czf dist/quartr_$(VERSION)_$${os}_$${arch}.tar.gz -C dist/stage quartr_$(VERSION)_$${os}_$${arch}; \
fi; \
done
@rm -rf dist/stage
@cd dist && if command -v sha256sum >/dev/null 2>&1; then \
sha256sum quartr_$(VERSION)_*.tar.gz quartr_$(VERSION)_*.zip > quartr_$(VERSION)_SHA256SUMS; \
else \
shasum -a 256 quartr_$(VERSION)_*.tar.gz quartr_$(VERSION)_*.zip > quartr_$(VERSION)_SHA256SUMS; \
fi
@echo "artifacts:"; ls -1 dist/*.tar.gz dist/*.zip dist/*SHA256SUMS

clean:
rm -rf bin
rm -rf bin dist
42 changes: 38 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,52 @@ It is designed for API subscribers who want a terminal-friendly interface for co
- Retries transient `429` and `5xx` responses with short backoff and honors `Retry-After` when present.
- Uses only the Go standard library.

## Build
## Install

### From a release

Prebuilt binaries for macOS, Linux, and Windows (amd64 and arm64) are attached
to every [release](https://github.com/TJC-LP/quartr-cli/releases). Download the
archive for your platform, verify it, and put `quartr` on your `PATH`:

```bash
go build -o bin/quartr ./cmd/quartr
VERSION=0.1.0
OS=$(uname -s | tr '[:upper:]' '[:lower:]') # darwin | linux
ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')
BASE="https://github.com/TJC-LP/quartr-cli/releases/download/v$VERSION"

curl -fsSLO "$BASE/quartr_${VERSION}_${OS}_${ARCH}.tar.gz"
curl -fsSLO "$BASE/quartr_${VERSION}_SHA256SUMS"
shasum -a 256 -c quartr_${VERSION}_SHA256SUMS --ignore-missing

tar -xzf "quartr_${VERSION}_${OS}_${ARCH}.tar.gz"
sudo install "quartr_${VERSION}_${OS}_${ARCH}/quartr" /usr/local/bin/quartr
quartr --version
```

Or install from the project directory:
macOS Gatekeeper quarantines binaries downloaded with a browser. If you get
"cannot be opened because the developer cannot be verified", clear the
attribute: `xattr -d com.apple.quarantine /usr/local/bin/quartr`.

### From source

```bash
go install ./cmd/quartr
go install github.com/TJC-LP/quartr-cli/cmd/quartr@latest
```

Or from a clone of this repo:

```bash
make build # ./bin/quartr
make install # $GOBIN/quartr
```

`make` bakes the version in via `-ldflags`, so `quartr --version` reports the
tag. A plain `go build ./cmd/quartr` works too and falls back to whatever the
go tool stamped: the module version for `go install ...@v0.1.0`, a
`0.0.0-<timestamp>-<revision>` pseudo-version when building from a checkout, or
`dev` when no build information is available at all.

## Configure

```bash
Expand Down
2 changes: 1 addition & 1 deletion cmd/quartr/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ package main
import (
"os"

"quartr-cli/internal/cli"
"github.com/TJC-LP/quartr-cli/internal/cli"
)

func main() {
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module quartr-cli
module github.com/TJC-LP/quartr-cli

go 1.26.2
7 changes: 2 additions & 5 deletions internal/cli/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,9 @@ import (
"fmt"
"io"

"quartr-cli/internal/quartr"
"github.com/TJC-LP/quartr-cli/internal/quartr"
)

// Version is the CLI release string, surfaced via `quartr --version`.
const Version = "0.1.0"

type app struct {
out io.Writer
errOut io.Writer
Expand Down Expand Up @@ -44,7 +41,7 @@ func Run(args []string, out, errOut io.Writer) int {
return 2
}
if globals.Version {
fmt.Fprintf(out, "quartr %s\n", Version)
fmt.Fprintf(out, "quartr %s\n", quartr.Version)
return 0
}

Expand Down
2 changes: 1 addition & 1 deletion internal/cli/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"os"
"strings"

"quartr-cli/internal/quartr"
"github.com/TJC-LP/quartr-cli/internal/quartr"
)

type globalOverrides struct {
Expand Down
2 changes: 1 addition & 1 deletion internal/cli/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"path/filepath"
"testing"

"quartr-cli/internal/quartr"
"github.com/TJC-LP/quartr-cli/internal/quartr"
)

func TestBuildConfigPrecedence(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion internal/cli/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"fmt"
"net/http"

"quartr-cli/internal/quartr"
"github.com/TJC-LP/quartr-cli/internal/quartr"
)

// usageError marks a failure caused by the command line the user typed
Expand Down
Loading
Loading