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
3 changes: 2 additions & 1 deletion .github/workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ Review and auto-fix workflows are the same `deslicer-code-harness` `@v1` reuseab
| `secret-scan.yml` | TruffleHog `--only-verified` (same posture as DAP) |
| `workflow-syntax-check.yml` | actionlint on changed workflow YAML |
| `build-main.yml` | Five release-target edge builds after merge to `main` |
| `release.yml` / `homebrew.yml` / `crates-publish.yml` | Tag / publish (unchanged) |
| `cut-release.yml` | After Quality Gate on `main` (or manual dispatch): plan semver, tag, call `release.yml` |
| `release.yml` / `homebrew.yml` / `crates-publish.yml` | Build, sign, GitHub Release, Homebrew tap PR, crates.io |

Add **Quality Gate**, **Secret Scanning (Lightweight)**, and **Cursor Code Review** as required status checks on `main` once they have run once.

Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,13 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: shellcheck
run: shellcheck --severity=warning scripts/install.sh scripts/test_install.sh
run: shellcheck --severity=warning scripts/install.sh scripts/test_install.sh scripts/install-from-source.sh scripts/test_install_from_source.sh scripts/cut-release.sh scripts/test_cut_release.sh

- name: Installer shell tests
run: bash scripts/test_install.sh
run: |
bash scripts/test_install.sh
bash scripts/test_cut_release.sh
bash scripts/test_install_from_source.sh

deny:
name: Dependency audit (non-blocking)
Expand Down
127 changes: 127 additions & 0 deletions .github/workflows/cut-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# After Quality Gate passes on main, publish a release when there is something
# to ship: either Cargo.toml is already ahead of the last tag, or conventional
# commits since that tag imply a bump.
#
# A tag push from GITHUB_TOKEN does not retrigger release.yml, so this workflow
# tags first, then calls it. Manual `git push origin vX.Y.Z` still uses the
# tag trigger.
#
# workflow_dispatch cuts a release immediately with an explicit bump.

name: Cut release

on:
workflow_run:
workflows: [Quality Gate]
types: [completed]
workflow_dispatch:
inputs:
bump:
description: Semver bump (auto reads conventional commits since the last tag)
type: choice
options: [auto, patch, minor, major]
default: auto

permissions:
contents: write
pull-requests: write

jobs:
plan:
name: Plan version
if: >
github.event_name == 'workflow_dispatch' ||
(github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.head_branch == 'main')
runs-on: ubuntu-latest
outputs:
should_release: ${{ steps.plan.outputs.should_release }}
needs_bump: ${{ steps.plan.outputs.needs_bump }}
version: ${{ steps.plan.outputs.version }}
tag: ${{ steps.plan.outputs.tag }}
reason: ${{ steps.plan.outputs.reason }}
steps:
- name: Checkout main
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
fetch-tags: true
ref: main

- name: Plan
id: plan
env:
BUMP: ${{ github.event_name == 'workflow_dispatch' && inputs.bump || 'auto' }}
run: |
set -euo pipefail
scripts/cut-release.sh plan --bump "${BUMP}"

tag:
name: Bump and tag
needs: plan
if: needs.plan.outputs.should_release == 'true'
runs-on: ubuntu-latest
outputs:
tagged: ${{ steps.tag.outputs.tagged }}
steps:
- name: Checkout main
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
fetch-tags: true
ref: main

- name: Apply version, commit, and tag
id: tag
env:
VERSION: ${{ needs.plan.outputs.version }}
TAG: ${{ needs.plan.outputs.tag }}
NEEDS_BUMP: ${{ needs.plan.outputs.needs_bump }}
REASON: ${{ needs.plan.outputs.reason }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
echo "${REASON}"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"

if gh release view "${TAG}" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then
echo "release ${TAG} already published"
echo "tagged=false" >> "${GITHUB_OUTPUT}"
exit 0
fi

if [ "${NEEDS_BUMP}" = "true" ]; then
scripts/cut-release.sh apply "${VERSION}"
git add Cargo.toml Cargo.lock
git commit -m "chore(release): bump version to ${VERSION}"
if ! git push origin HEAD:main; then
branch="release/${TAG}"
git push -u origin "HEAD:refs/heads/${branch}"
gh pr create --title "chore(release): bump version to ${VERSION}" \
--body "Automated version bump. Merging this PR lets the next Cut release job tag ${TAG}." \
--base main --head "${branch}"
echo "tagged=false" >> "${GITHUB_OUTPUT}"
exit 0
fi
fi

if ! git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then
git tag "${TAG}"
git push origin "refs/tags/${TAG}"
fi
echo "tagged=true" >> "${GITHUB_OUTPUT}"

publish:
name: Build and publish
needs: [plan, tag]
if: needs.tag.outputs.tagged == 'true'
uses: ./.github/workflows/release.yml
with:
tag: ${{ needs.plan.outputs.tag }}
secrets: inherit
permissions:
contents: write
id-token: write
actions: read
37 changes: 27 additions & 10 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,34 @@
# Release pipeline for deslicer-cli.
# Triggered by semver tags (v*.*.*). Builds five platform targets, signs
# artifacts with cosign keyless (Sigstore OIDC), attaches SLSA provenance,
# publishes a GitHub Release, and moves the floating v1 tag.
# Triggered by semver tags (v*.*.*), workflow_dispatch, or Cut release
# (workflow_call). Builds five platform targets, signs artifacts with
# cosign keyless (Sigstore OIDC), attaches SLSA provenance, publishes a
# GitHub Release, and moves the floating v1 tag.

name: Release

on:
push:
tags: ['v*.*.*']
workflow_call:
inputs:
tag:
description: Tag to publish (e.g. v1.4.0)
required: true
type: string
workflow_dispatch:
inputs:
tag:
description: Existing tag to build and publish
required: true
type: string

permissions:
contents: read

env:
CARGO_TERM_COLOR: always
BINARY_NAME: deslicer
RELEASE_TAG: ${{ github.event_name == 'push' && github.ref_name || inputs.tag }}
# Opt JS actions into the Node 24 runtime (silences Node 20 deprecation annotations
# for third-party actions we cannot pin ourselves, e.g. slsa-github-generator).
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
Expand All @@ -23,8 +37,6 @@ jobs:
build:
name: Build (${{ matrix.target }})
runs-on: ${{ matrix.os }}
env:
DESLICER_GIT_SHA: ${{ github.sha }}
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -52,6 +64,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ env.RELEASE_TAG }}

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
Expand All @@ -78,6 +92,8 @@ jobs:
shell: bash
run: |
set -euo pipefail
DESLICER_GIT_SHA="$(git rev-parse HEAD)"
export DESLICER_GIT_SHA
if [ "${{ matrix.cross }}" = "true" ]; then
cross build --release --target "${{ matrix.target }}"
else
Expand Down Expand Up @@ -157,6 +173,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
ref: ${{ env.RELEASE_TAG }}

- name: Download all build artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
Expand Down Expand Up @@ -185,12 +202,12 @@ jobs:
- name: Create GitHub Release
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
with:
tag_name: ${{ github.ref_name }}
name: ${{ github.ref_name }}
tag_name: ${{ env.RELEASE_TAG }}
name: ${{ env.RELEASE_TAG }}
generate_release_notes: true
# Hyphenated semver tags (e.g. v0.1.0-rc.1) are prereleases: this
# keeps them out of "latest" and lets crates/homebrew skip them.
prerelease: ${{ contains(github.ref_name, '-') }}
prerelease: ${{ contains(env.RELEASE_TAG, '-') }}
fail_on_unmatched_files: false
files: |
release-assets/deslicer-*.tar.gz
Expand All @@ -203,13 +220,13 @@ jobs:

- name: Move floating v1 tag
# Never point the floating v1 tag at a prerelease (rc/beta) build.
if: ${{ !contains(github.ref_name, '-') }}
if: ${{ !contains(env.RELEASE_TAG, '-') }}
shell: bash
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag -f v1 "${GITHUB_SHA}"
git tag -f v1 "$(git rev-parse HEAD)"
git push origin refs/tags/v1 --force

provenance:
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "deslicer-cli"
version = "1.3.2"
version = "1.4.0"
edition = "2021"
rust-version = "1.88"
authors = ["Deslicer <engineering@deslicer.ai>"]
Expand Down
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,27 @@ brew install deslicer/tap/deslicer
cargo install deslicer-cli
```

**curl**
**curl** (latest GitHub Release)

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

**From source** (current `main`, or your local checkout — does not wait for a release)

```bash
./scripts/install-from-source.sh
```

Or without a clone:

```bash
curl -fsSL https://raw.githubusercontent.com/deslicer/cli/main/scripts/install-from-source.sh | bash
```

**CI runners** — install the binary in your pipeline (GitHub Actions, GitLab CI, Azure DevOps, Bitbucket Pipelines). See [docs/installation.md](docs/installation.md) for per-platform OIDC setup.

**Updating** — `deslicer update` self-updates from GitHub Releases (Linux/macOS); Homebrew users run `brew upgrade deslicer`. See [docs/installation.md](docs/installation.md#updating).
**Updating** — `deslicer update` self-updates from GitHub Releases (Linux/macOS); Homebrew users run `brew upgrade deslicer`. Source installs: re-run `scripts/install-from-source.sh`. See [docs/installation.md](docs/installation.md#updating).

## Quick start

Expand Down
12 changes: 7 additions & 5 deletions docs/agent-runs.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ accepts either the id or the name.
## Conversation (REPL)

On a terminal, `deslicer agent` starts a line-oriented conversation. Each
prompt is one server run. The first prompt creates a conversation; later
prompts reuse it. Status and tools go to stderr; the answer goes to stdout.
prompt is one server run. The first prompt creates a conversation and prints its id once; later
prompts reuse it. Tool progress goes to stderr; the answer goes to stdout.

```bash
deslicer agent
Expand Down Expand Up @@ -90,9 +90,11 @@ To pick a different agent, pass a name or id:
deslicer agent run --agent slicer "Which indexers are missing the latest bundle?"
```

The answer streams to **stdout** as it arrives; the conversation id, each tool
the agent reaches for, and any diagnostics go to **stderr**, so `> answer.txt`
captures the answer and nothing else.
The answer streams to **stdout** as it arrives; tool progress and diagnostics
go to **stderr**, so `> answer.txt` captures the answer and nothing else.
Orchestrator bookkeeping (`declare_intent`, task-list updates) stays hidden
unless `--verbose`. A tool result larger than 8 MiB is skipped in the
terminal — the agent still has it — so the conversation can continue.

Omit the prompt to read it from stdin, which avoids the shell quoting and
argument-length limits of a long prompt:
Expand Down
7 changes: 5 additions & 2 deletions docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ To exercise the full plan flow against a local or remote Observer API, see [loca

## Commits

Use [Conventional Commits](https://www.conventionalcommits.org/):
Use [Conventional Commits](https://www.conventionalcommits.org/). Merges to `main` with `feat` / `fix` / `perf` subjects drive the automated release bump (`feat` → minor, `fix`/`perf` → patch, `BREAKING CHANGE` / `type!:` → major). `docs` / `chore` / `ci` / `test` alone do not cut a release.

```
feat(change): add verify polling timeout
Expand Down Expand Up @@ -74,7 +74,10 @@ cli/

## Release

Maintainers only — see [release-process.md](release-process.md).
Merges to `main` cut a GitHub Release automatically when conventional commits
call for it. Maintainers can also dispatch **Cut release** or push a `v*.*.*`
tag. See [release-process.md](release-process.md). To run unreleased `main`
locally, use `./scripts/install-from-source.sh`.

## License

Expand Down
34 changes: 30 additions & 4 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,41 @@ The script detects your OS/arch, downloads the matching release archive from [Gi

Re-running the script updates an existing installation in place. It will be mirrored at `https://get.deslicer.ai/cli/install.sh` once that host is live.

## From source

Use this when you need a commit that is on `main` but not in a GitHub Release yet (or to install a local checkout, including uncommitted fixes):

```bash
git clone https://github.com/deslicer/cli.git
cd cli
./scripts/install-from-source.sh
```

Without a clone, the same script clones `main` into a temp directory, builds, and installs:

```bash
curl -fsSL https://raw.githubusercontent.com/deslicer/cli/main/scripts/install-from-source.sh | bash
```

Requires Rust (rustup honors `rust-toolchain.toml`). Overrides:

| Variable | Effect |
|----------|--------|
| `DESLICER_INSTALL_DIR` | Install destination (default: directory of an existing `deslicer`, otherwise `~/.local/bin`) |
| `DESLICER_REF` | Git ref to clone when not already in the repo (default `main`) |

This overwrites an existing `deslicer` on `PATH` so you are not stuck on the last tagged release.

## Updating

Pick the channel you installed with:

```bash
deslicer update # self-update from GitHub Releases (Linux/macOS)
deslicer update --check # report whether a newer release exists
brew upgrade deslicer # Homebrew installs
cargo install deslicer-cli # crates.io installs (add --force to reinstall)
deslicer update # self-update from GitHub Releases (Linux/macOS)
deslicer update --check # report whether a newer release exists
brew upgrade deslicer # Homebrew installs
cargo install deslicer-cli --force
./scripts/install-from-source.sh # rebuild current main or this checkout
```

`deslicer update` downloads the release archive for your platform, verifies the SHA-256 sidecar, and atomically replaces the running binary. It never installs prereleases unless you pass `--version vX.Y.Z-rc.N` explicitly. On Windows, download the new `.zip` from the releases page instead — in-place replacement of a running `.exe` is blocked by the OS.
Expand Down
Loading
Loading