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
88 changes: 88 additions & 0 deletions cmd/nightshift/commands/commit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package commands

import (
"fmt"
"io"
"os"

"github.com/marcus/nightshift/internal/commits"
"github.com/spf13/cobra"
)

var commitCmd = &cobra.Command{
Use: "commit",
Short: "Conventional Commits helpers",
Long: `Tools for working with Conventional Commits messages.

Use "commit normalize" to validate and reformat a commit message so it
follows the project's rules (type prefix, lowercase type, subject length,
and wrapped body).`,
}

var commitNormalizeCmd = &cobra.Command{
Use: "normalize [MESSAGE]",
Short: "Normalize a commit message to Conventional Commits format",
Long: `Validate and rewrite a commit message into canonical Conventional
Commits form.

The message is read from a positional argument, from a file passed via
--file (typically .git/COMMIT_EDITMSG by a commit-msg hook), or from stdin
when no argument and no --file are given.

nightshift commit normalize "feat: add login"
nightshift commit normalize --file .git/COMMIT_EDITMSG
git log -1 --pretty=%B | nightshift commit normalize

Use --check to only validate without rewriting; the exit code is non-zero
when the message does not conform.`,
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
check, _ := cmd.Flags().GetBool("check")
file, _ := cmd.Flags().GetString("file")

raw, err := readCommitMessage(args, file)
if err != nil {
return err
}

normalized, err := commits.Normalize(raw)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
return err
}

if check {
fmt.Fprintln(os.Stdout, normalized)
return nil
}
fmt.Fprintln(os.Stdout, normalized)
return nil
},
}

func init() {
commitNormalizeCmd.Flags().BoolP("check", "c", false, "Only validate; do not rewrite")
commitNormalizeCmd.Flags().StringP("file", "f", "", "Read the message from this file (use by the commit-msg hook)")
commitCmd.AddCommand(commitNormalizeCmd)
rootCmd.AddCommand(commitCmd)
}

// readCommitMessage resolves the message source in order: positional arg,
// --file, then stdin.
func readCommitMessage(args []string, file string) (string, error) {
if len(args) == 1 {
return args[0], nil
}
if file != "" {
b, err := os.ReadFile(file)
if err != nil {
return "", fmt.Errorf("read %s: %w", file, err)
}
return string(b), nil
}
b, err := io.ReadAll(os.Stdin)
if err != nil {
return "", fmt.Errorf("read stdin: %w", err)
}
return string(b), nil
}
4 changes: 4 additions & 0 deletions cmd/provider-calibration/main.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// Command provider-calibration is a standalone diagnostic tool that reads
// local Claude and Codex session artifacts and reports per-session token
// metrics, so provider token behavior can be compared on the same machine.
// See docs/guides/provider-calibration.md for usage.
package main

import (
Expand Down
47 changes: 47 additions & 0 deletions docs/commit-messages.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Commit Messages

Nightshift uses [Conventional Commits](https://www.conventionalcommits.org/)
for all commit messages. This keeps the history readable and lets tooling
derive changelogs automatically.

## Format

```
<type>(<scope>): <subject>

<body>
```

- **type** — one of `feat`, `fix`, `docs`, `style`, `refactor`, `test`,
`chore`, `perf`, `build`, `ci`.
- **scope** — optional, e.g. `fix(api): ...`.
- **subject** — lowercase, imperative mood, no trailing period, max 72 chars.
- **body** — optional, wrapped at 72 columns, separated from the subject by a
blank line.

## The `commit normalize` command

Validate and reformat a message:

```sh
nightshift commit normalize "feat: add login screen"
nightshift commit normalize --file .git/COMMIT_EDITMSG
git log -1 --pretty=%B | nightshift commit normalize
```

Add `--check` to validate only. The command exits non-zero when a message
cannot be normalized (missing/unknown type, capitalized or overlong subject).

## commit-msg hook

To enforce the rules locally, install the hook:

```sh
make install-hooks
# or manually:
ln -sf ../../scripts/commit-msg.sh .git/hooks/commit-msg
```

The hook normalizes your message file in place before the commit is created and
rejects messages that cannot be fixed automatically. Bypass it with
`git commit --no-verify`.
164 changes: 164 additions & 0 deletions docs/guides/commands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# Command Reference

This is a quick reference for every CLI command registered by nightshift, generated from the Cobra command definitions in `cmd/nightshift/commands/`. Run `nightshift <command> --help` for the full flag list.

## Global flags

| Flag | Description |
|------|-------------|
| `--verbose` | Enable verbose output (persistent, available on all commands) |

## Core execution

### `nightshift run`

Execute tasks. See [Run Lifecycle](run-lifecycle.md) for what happens from trigger to finished run.

| Flag | Description |
|------|-------------|
| `--dry-run` | Simulate execution without making changes |
| `-p, --project <path>` | Path to project directory |
| `-t, --task <name>` | Run a specific task by name |
| `--max-projects <n>` | Max projects to process per run (default 1; ignored with `--project`) |
| `--max-tasks <n>` | Max tasks to run per project (default 1; ignored with `--task`) |
| `--ignore-budget` | Bypass budget checks (use with caution) |
| `-y, --yes` | Skip confirmation prompt |
| `--random-task` | Pick a random task from eligible tasks |
| `-b, --branch <name>` | Base branch for new feature branches (defaults to current branch) |
| `--timeout <duration>` | Per-agent execution timeout |
| `--no-color` | Disable colored output |

### `nightshift preview`

Preview the next scheduled runs without executing anything.

| Flag | Description |
|------|-------------|
| `-n, --runs <n>` | Number of upcoming runs to preview (default 3) |
| `-p, --project <path>` | Preview only a specific project path |
| `-t, --task <type>` | Preview only a specific task type |
| `--long` | Show full prompts (default truncates) |
| `--write <dir>` | Write full prompts to a directory |
| `--explain` | Show budget and task-filter explanations |
| `--plain` | Disable gum pager output |
| `--json` | Output JSON (includes full prompts) |

## Tasks

### `nightshift task`

Manage and run tasks. See [Adding Tasks](adding-tasks.md) for the built-in task registry and how to add task types.

| Subcommand | Description |
|------------|-------------|
| `task list` | List available tasks with budget info (`--category`, `--cost`, `--json`) |
| `task show <task-type>` | Show task details and prompt (`--prompt-only`, `--json`, `-p, --project`) |
| `task run <task-type>` | Run a task immediately (`--provider`, `-p, --project`, `--dry-run`, `--timeout`, `-b, --branch`) |

`task run` requires `--provider <claude|codex|copilot>`.

## Budget and calibration

### `nightshift budget`

Show budget status. `--provider, -p` filters to one provider (claude, codex, copilot).

| Subcommand | Description |
|------------|-------------|
| `budget snapshot` | Capture a usage snapshot (`-p, --provider`, `--local-only` to skip tmux scraping) |
| `budget history` | Show recent budget snapshots (`-p, --provider`, `-n <count>`, default 20) |
| `budget calibrate` | Show inferred budget calibration status (`-p, --provider`) |

Budget snapshots combine local token counts with tmux-scraped usage percentages; see [Agent tmux Integration](agent-tmux-integration.md) and [Codex Budget Tracking](codex-budget-tracking.md). The standalone `cmd/provider-calibration` tool is covered in [Provider Calibration](provider-calibration.md).

### `nightshift busfactor`

Analyze code ownership concentration (bus factor) for a repository.

| Flag | Description |
|------|-------------|
| `-p, --path <dir>` | Repository or directory path |
| `--json` | Output as JSON |
| `--since` / `--until` | Date range (RFC3339 or YYYY-MM-DD) |
| `-f, --file <pattern>` | Analyze a specific file or pattern |
| `--save` | Save results to database |
| `--db <path>` | Database path (uses config if not set) |

## Reporting and status

### `nightshift report`

Show what nightshift did.

| Flag | Description |
|------|-------------|
| `-r, --report <type>` | `overview` (default), `tasks`, `projects`, `budget`, or `raw` |
| `-p, --period <period>` | `last-night` (default), `last-run`, `last-24h`, `last-7d`, `today`, `yesterday`, `all` |
| `-n, --runs <n>` | Max runs to include (0 = all, default 3) |
| `--since` / `--until` | Time filters (YYYY-MM-DD, YYYY-MM-DD HH:MM, or RFC3339) |
| `--format <fmt>` | `fancy` (default), `plain`, `markdown`, `json` |
| `--no-color` | Disable ANSI colors |
| `--paths` | Include report/log file paths |
| `--max-items <n>` | Max highlights per run (default 5) |

### `nightshift stats`

Show aggregate statistics (`--json`, `-p, --period` for `all`, `last-7d`, `last-30d`, `last-night`).

### `nightshift status`

Show run history (`-n, --last <count>`, default 5; `--today` for today's activity summary).

### `nightshift logs`

View logs.

| Flag | Description |
|------|-------------|
| `-n, --tail <n>` | Number of log lines to show (default 50) |
| `-f, --follow` | Follow log output |
| `-e, --export <file>` | Export logs to file |
| `--since` / `--until` | Time filters |
| `--level <lvl>` | Minimum log level (debug\|info\|warn\|error) |
| `--component <str>` | Filter by component substring |
| `--match <str>` | Filter by message substring |
| `--summary` | Show summary only |
| `--raw` | Raw log lines without formatting |
| `--no-color` | Disable ANSI colors |
| `--path <dir>` | Override log directory |

## Setup and configuration

### `nightshift setup`

Interactive onboarding wizard (task presets and project configuration).

### `nightshift init`

Create a configuration file (`--global` for global config, `-f, --force` to overwrite without prompting).

### `nightshift config`

| Subcommand | Description |
|------------|-------------|
| `config get KEY` | Get a configuration value |
| `config set KEY VALUE` | Set a configuration value (`-g, --global` writes global config) |
| `config validate` | Validate the configuration file |

### `nightshift install [launchd|systemd|cron]` / `nightshift uninstall`

Install or remove the system service that triggers scheduled runs.

## Operations

### `nightshift daemon`

Manage the background daemon: `daemon start` (`-f, --foreground`, `--timeout`), `daemon stop`, `daemon status`.

### `nightshift doctor`

Check nightshift configuration and environment (credentials, providers, tmux availability).

### `nightshift commit`

Conventional Commits helpers. `commit normalize [MESSAGE]` rewrites a message to Conventional Commits format (`-c, --check` validates only; `-f, --file` reads the message from a file, as used by the commit-msg hook). See [Commit Messages](../commit-messages.md).
Loading