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
37 changes: 37 additions & 0 deletions .config/binstaller.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/binary-install/binstaller/main/schema/InstallSpec.json
schema: v1
name: cert-prep
repo: waveFrontSet/cert-prep
default_bin_dir: ${HOME}/.local/bin
asset:
# Release assets: cert-prep-linux-x86_64.tar.gz, cert-prep-macos-aarch64.tar.gz,
# cert-prep-windows-x86_64.zip (bare binary at archive root, no version in name)
template: cert-prep-${OS}-${ARCH}${EXT}
default_extension: .tar.gz
binaries:
- name: cert-prep
path: cert-prep
rules:
- when:
arch: amd64
arch: x86_64
- when:
os: darwin
os: macos
- when:
os: darwin
arch: arm64
arch: aarch64
- when:
os: windows
ext: .zip
binaries:
- name: cert-prep
path: cert-prep.exe
supported_platforms:
- os: linux
arch: amd64
- os: darwin
arch: arm64
- os: windows
arch: amd64
13 changes: 13 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,20 @@ permissions:
contents: write

jobs:
check-version:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Verify tag matches package.yaml version
run: |
PKG_VERSION="$(sed -n 's/^version:[[:space:]]*//p' package.yaml)"
if [ "${GITHUB_REF_NAME}" != "v${PKG_VERSION}" ]; then
echo "::error::Tag ${GITHUB_REF_NAME} does not match package.yaml version ${PKG_VERSION} (expected v${PKG_VERSION})"
exit 1
fi

build:
needs: check-version
strategy:
matrix:
include:
Expand Down
81 changes: 81 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,40 @@ A terminal UI application for practicing certification exam questions. Load a
question bank from a JSON file, answer multi-select questions with immediate
color-coded feedback, and see your final score.

## Installation

### Linux / macOS

```bash
curl -fsSL https://raw.githubusercontent.com/waveFrontSet/cert-prep/main/install.sh | sh
```

This installs the latest release to `~/.local/bin`. To choose a different
directory or pin a version:

```bash
curl -fsSL https://raw.githubusercontent.com/waveFrontSet/cert-prep/main/install.sh | sh -s -- -b /usr/local/bin v0.5.0
```

Note: the prebuilt macOS binary is Apple Silicon (aarch64) only. On Intel
Macs, [build from source](#building-from-source).

### Windows

```powershell
powershell -ExecutionPolicy Bypass -c "irm https://raw.githubusercontent.com/waveFrontSet/cert-prep/main/install.ps1 | iex"
```

This installs `cert-prep.exe` to `%LOCALAPPDATA%\Programs\cert-prep` (override
with the `CERT_PREP_INSTALL_DIR` environment variable) and prints a hint if
that directory is not on your `PATH`.

### Manual download

Grab the archive for your platform from the
[releases page](https://github.com/waveFrontSet/cert-prep/releases), extract
it, and put the `cert-prep` binary somewhere on your `PATH`.

## Usage

```bash
Expand Down Expand Up @@ -32,6 +66,7 @@ The selector lists all previously used question banks sorted by most recently us
| `<config.json>` | Path to a question bank JSON file (required on first use) |
| `-n N`, `--sample-amount N` | Override the number of questions to sample |
| `-w CATEGORY:WEIGHT`, `--weight CATEGORY:WEIGHT` | Set category weight for stratified sampling (repeatable) |
| `--version` | Print the version and exit |

### Examples

Expand All @@ -57,6 +92,8 @@ cert-prep ./aws-sa-questions.json -w "AWS Storage:3" -w "AWS Security:2"
| `Enter` | Submit answer / Next question |
| `Arrow keys` / `j` / `k` | Navigate answer choices |
| `h` / `l` | When reviewing, travel to previous / next question |
| `a` | When reviewing, request an AI explanation (see below) |
| `Ctrl-f` / `Ctrl-b` | Scroll the explanation page-wise down / up |
| `q` / `Esc` | Quit |

Mouse clicks on checkboxes and buttons are also supported (not in all terminals).
Expand All @@ -67,6 +104,50 @@ After submitting an answer, selections are color-coded:
- **Yellow `[O]`** -- missed correct answer
- **Red `[X]`** -- incorrect selection

## AI explanations (optional)

With an API key, pressing `a` while reviewing an answer streams an AI-generated
explanation of the correct answer (and why the incorrect choices are wrong)
into the TUI:

```bash
export GEMINI_API_KEY=your-key
cert-prep
```

Without the environment variable, the feature is simply disabled.

By default, explanations use the `gemini-2.5-flash` model via Gemini's
OpenAI-compatible endpoint. Model, endpoint, and system prompt can be
overridden in `settings.json` (see [Configuration files](#configuration-files)):

```json
{
"aiModel": "gemini-2.5-flash",
"aiBaseUrl": "https://generativelanguage.googleapis.com/v1beta/openai",
"aiSystemPrompt": "You are a friendly, concise mentor ..."
}
```

All keys are optional; any OpenAI-compatible endpoint should work.

## Trophies

You can earn trophies for feats such as answer streaks, fast answers, or a
flawless session. Trophies are tracked per question bank and shown in the TUI
when earned.

## Configuration files

State lives in the XDG config directory (`~/.config/cert-prep/` on
Linux/macOS, `%APPDATA%\cert-prep\` on Windows):

| File | Purpose |
| ------ | --------- |
| `registry.json` | Previously used question banks (feeds the config selector) |
| `settings.json` | Optional AI explanation settings |
| `trophies/` | Earned trophies, one file per question bank |

## Question bank format

Question banks are JSON files with the following structure:
Expand Down
4 changes: 3 additions & 1 deletion app/CertPrep/CLI.hs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ module CertPrep.CLI (CLIOptions (..), parseCLIOpts) where

import Data.Text qualified as T
import Data.Text.Read (decimal)
import Data.Version (showVersion)
import Options.Applicative
import Paths_cert_prep (version)

data CLIOptions = CLIOptions
{ cliSampleAmount :: Maybe Int
Expand Down Expand Up @@ -44,7 +46,7 @@ parseWeight = eitherReader $ \s ->
cliInfo :: ParserInfo CLIOptions
cliInfo =
info
(cliParser <**> helper)
(cliParser <**> helper <**> simpleVersioner (showVersion version))
( fullDesc
<> progDesc "Certification exam prep TUI"
<> header "cert-prep - practice certification questions"
Expand Down
2 changes: 1 addition & 1 deletion cert-prep.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ cabal-version: 2.0
-- see: https://github.com/sol/hpack

name: cert-prep
version: 0.1.0.0
version: 0.5.0
license: MIT
license-file: LICENSE
author: Paul Grillenberger
Expand Down
25 changes: 25 additions & 0 deletions install.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Installs the latest cert-prep release for Windows x86_64.
# Usage: powershell -ExecutionPolicy Bypass -c "irm https://raw.githubusercontent.com/waveFrontSet/cert-prep/main/install.ps1 | iex"
$ErrorActionPreference = "Stop"

$Repo = "waveFrontSet/cert-prep"
$Asset = "cert-prep-windows-x86_64.zip"
$InstallDir = if ($env:CERT_PREP_INSTALL_DIR) { $env:CERT_PREP_INSTALL_DIR }
else { Join-Path $env:LOCALAPPDATA "Programs\cert-prep" }

$Url = "https://github.com/$Repo/releases/latest/download/$Asset"
$Tmp = Join-Path $env:TEMP $Asset

Write-Host "Downloading $Url"
Invoke-WebRequest -Uri $Url -OutFile $Tmp

New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null
Expand-Archive -Path $Tmp -DestinationPath $InstallDir -Force
Remove-Item $Tmp

Write-Host "Installed cert-prep.exe to $InstallDir"
if (($env:Path -split ";") -notcontains $InstallDir) {
Write-Host "Note: $InstallDir is not on your PATH. Add it with:"
Write-Host " [Environment]::SetEnvironmentVariable('Path', ([Environment]::GetEnvironmentVariable('Path', 'User') + ';$InstallDir'), 'User')"
Write-Host "Then restart your terminal."
}
Loading
Loading