first commit
This commit is contained in:
14
.dockerignore
Normal file
14
.dockerignore
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
.git
|
||||||
|
.github
|
||||||
|
.gitignore
|
||||||
|
.idea
|
||||||
|
*.md
|
||||||
|
!README.md
|
||||||
|
!LICENSE
|
||||||
|
tests
|
||||||
|
run_tests.sh
|
||||||
|
debian
|
||||||
|
homebrew
|
||||||
|
docker-compose.yml
|
||||||
|
Dockerfile.test
|
||||||
|
.dockerignore
|
||||||
66
.github/workflows/docker.yml
vendored
Normal file
66
.github/workflows/docker.yml
vendored
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
name: Docker Build and Push
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
IMAGE_NAME: ${{ github.repository }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-push:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Docker Build
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Log in to Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Extract metadata
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
|
tags: |
|
||||||
|
type=ref,event=branch
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
|
type=semver,pattern={{major}}
|
||||||
|
type=sha
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
|
||||||
|
- name: Build and push test image
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: Dockerfile.test
|
||||||
|
push: true
|
||||||
|
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:test
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
80
.github/workflows/release.yml
vendored
Normal file
80
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Get version from tag
|
||||||
|
id: get_version
|
||||||
|
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Create release tarball
|
||||||
|
run: |
|
||||||
|
mkdir -p dist
|
||||||
|
tar -czf dist/finish-v${{ steps.get_version.outputs.VERSION }}.tar.gz \
|
||||||
|
--exclude='.git' \
|
||||||
|
--exclude='.github' \
|
||||||
|
--exclude='dist' \
|
||||||
|
--exclude='.idea' \
|
||||||
|
--transform "s,^,finish-${{ steps.get_version.outputs.VERSION }}/," \
|
||||||
|
.
|
||||||
|
|
||||||
|
- name: Calculate SHA256
|
||||||
|
id: sha256
|
||||||
|
run: |
|
||||||
|
SHA256=$(sha256sum dist/finish-v${{ steps.get_version.outputs.VERSION }}.tar.gz | awk '{print $1}')
|
||||||
|
echo "SHA256=$SHA256" >> $GITHUB_OUTPUT
|
||||||
|
echo "SHA256: $SHA256"
|
||||||
|
|
||||||
|
- name: Build Debian package
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y debhelper devscripts
|
||||||
|
dpkg-buildpackage -us -uc -b
|
||||||
|
mv ../finish_*.deb dist/ || true
|
||||||
|
|
||||||
|
- name: Create Release
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
dist/*.tar.gz
|
||||||
|
dist/*.deb
|
||||||
|
body: |
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Quick Install (Linux/macOS)
|
||||||
|
```bash
|
||||||
|
curl -sSL https://git.appmodel.nl/Tour/finish/main/docs/install.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
### Homebrew (macOS)
|
||||||
|
Update the formula with SHA256: `${{ steps.sha256.outputs.SHA256 }}`
|
||||||
|
|
||||||
|
### Debian/Ubuntu
|
||||||
|
Download the `.deb` file and install:
|
||||||
|
```bash
|
||||||
|
sudo dpkg -i finish_*.deb
|
||||||
|
sudo apt-get install -f # Install dependencies
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
```bash
|
||||||
|
docker pull ghcr.io/${{ github.repository }}:${{ steps.get_version.outputs.VERSION }}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
See [CHANGELOG.md](CHANGELOG.md) for details.
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
69
.github/workflows/test.yml
vendored
Normal file
69
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
name: Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main, develop ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main, develop ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
shell: [bash, zsh]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y bash zsh curl jq bc bash-completion bats
|
||||||
|
|
||||||
|
- name: Make script executable
|
||||||
|
run: chmod +x finishte.sh
|
||||||
|
|
||||||
|
- name: Run BATS tests
|
||||||
|
run: |
|
||||||
|
if [ -d tests ]; then
|
||||||
|
bats tests/
|
||||||
|
else
|
||||||
|
echo "No tests directory found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Test installation
|
||||||
|
run: |
|
||||||
|
export HOME=$PWD/test-home
|
||||||
|
mkdir -p $HOME/.local/bin
|
||||||
|
cp finishte.sh $HOME/.local/bin/finishte
|
||||||
|
chmod +x $HOME/.local/bin/finishte
|
||||||
|
$HOME/.local/bin/finishte --help
|
||||||
|
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install shellcheck
|
||||||
|
run: sudo apt-get update && sudo apt-get install -y shellcheck
|
||||||
|
|
||||||
|
- name: Run shellcheck
|
||||||
|
run: shellcheck finishte.sh docs/install.sh || true
|
||||||
|
|
||||||
|
docker:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Build Docker image
|
||||||
|
run: docker build -t finish:test .
|
||||||
|
|
||||||
|
- name: Test Docker image
|
||||||
|
run: |
|
||||||
|
docker run --rm finish:test -c "finishte --help"
|
||||||
35
.gitignore
vendored
Normal file
35
.gitignore
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Build artifacts
|
||||||
|
debian/finish/
|
||||||
|
debian/.debhelper/
|
||||||
|
debian/files
|
||||||
|
debian/*.log
|
||||||
|
debian/*.substvars
|
||||||
|
*.deb
|
||||||
|
*.tar.gz
|
||||||
|
*.tar.xz
|
||||||
|
|
||||||
|
# IDEs
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# Logs and cache
|
||||||
|
finish.log
|
||||||
|
~/.finish/
|
||||||
|
*.cache
|
||||||
|
__pycache__/
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
*.tmp
|
||||||
|
temp/
|
||||||
|
dist/
|
||||||
7
.pre-commit-config.yaml
Normal file
7
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
repos:
|
||||||
|
- repo: https://github.com/koalaman/shellcheck-precommit
|
||||||
|
rev: v0.10.0
|
||||||
|
hooks:
|
||||||
|
- id: shellcheck
|
||||||
|
files: ^.+\.sh$
|
||||||
|
|
||||||
52
Dockerfile
Normal file
52
Dockerfile
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# finishte.sh Docker Image
|
||||||
|
FROM ubuntu:22.04
|
||||||
|
|
||||||
|
LABEL maintainer="finish contributors"
|
||||||
|
LABEL description="LLM-powered command-line autocompletion with LM-Studio integration"
|
||||||
|
LABEL version="0.5.0"
|
||||||
|
|
||||||
|
# Prevent interactive prompts during installation
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y \
|
||||||
|
bash \
|
||||||
|
bash-completion \
|
||||||
|
curl \
|
||||||
|
wget \
|
||||||
|
jq \
|
||||||
|
bc \
|
||||||
|
vim \
|
||||||
|
git \
|
||||||
|
&& apt-get clean && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Create application directory
|
||||||
|
WORKDIR /opt/finish
|
||||||
|
|
||||||
|
# Copy application files
|
||||||
|
COPY finish.sh /usr/bin/finish
|
||||||
|
COPY README.md LICENSE ./
|
||||||
|
|
||||||
|
# Make script executable
|
||||||
|
RUN chmod +x /usr/bin/finish
|
||||||
|
|
||||||
|
# Create finish directory
|
||||||
|
RUN mkdir -p /root/.finish
|
||||||
|
|
||||||
|
# Source bash-completion in .bashrc
|
||||||
|
RUN echo "" >> /root/.bashrc && \
|
||||||
|
echo "# Bash completion" >> /root/.bashrc && \
|
||||||
|
echo "if [ -f /etc/bash_completion ] && ! shopt -oq posix; then" >> /root/.bashrc && \
|
||||||
|
echo " . /etc/bash_completion" >> /root/.bashrc && \
|
||||||
|
echo "fi" >> /root/.bashrc
|
||||||
|
|
||||||
|
# Set working directory to home
|
||||||
|
WORKDIR /root
|
||||||
|
|
||||||
|
# Set entrypoint to bash
|
||||||
|
ENTRYPOINT ["/bin/bash"]
|
||||||
|
|
||||||
|
# Default command shows help
|
||||||
|
CMD ["-c", "echo 'finish.sh Docker Container' && echo '' && finish --help && exec /bin/bash"]
|
||||||
34
Dockerfile.test
Normal file
34
Dockerfile.test
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# finish.sh Test Container
|
||||||
|
FROM ubuntu:22.04
|
||||||
|
|
||||||
|
LABEL maintainer="finish contributors"
|
||||||
|
LABEL description="Test environment for finish.sh"
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
# Install dependencies including BATS for testing
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y \
|
||||||
|
bash \
|
||||||
|
bash-completion \
|
||||||
|
curl \
|
||||||
|
wget \
|
||||||
|
jq \
|
||||||
|
bc \
|
||||||
|
vim \
|
||||||
|
git \
|
||||||
|
bats \
|
||||||
|
&& apt-get clean && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /opt/finish
|
||||||
|
|
||||||
|
# Copy all files
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Make scripts executable
|
||||||
|
RUN chmod +x finish.sh
|
||||||
|
|
||||||
|
# Run tests by default
|
||||||
|
ENTRYPOINT ["bats"]
|
||||||
|
CMD ["tests"]
|
||||||
24
LICENSE
Normal file
24
LICENSE
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
BSD 2-Clause License
|
||||||
|
|
||||||
|
Copyright (c) 2025, finish contributors
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
365
PUBLISHING.md
Normal file
365
PUBLISHING.md
Normal file
@@ -0,0 +1,365 @@
|
|||||||
|
# Publishing Guide for finishte.sh
|
||||||
|
|
||||||
|
This guide explains how to publish and distribute finishte.sh through various channels.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
finishte.sh can be distributed through multiple channels:
|
||||||
|
1. **Direct installation script** - One-line curl install
|
||||||
|
2. **Debian/Ubuntu packages** - APT repository
|
||||||
|
3. **Homebrew** - macOS package manager
|
||||||
|
4. **Docker** - Container images
|
||||||
|
5. **GitHub Releases** - Direct downloads
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Before publishing, ensure:
|
||||||
|
- [ ] All tests pass (`./run_tests.sh`)
|
||||||
|
- [ ] Version numbers are updated in all files
|
||||||
|
- [ ] CHANGELOG.md is updated
|
||||||
|
- [ ] Documentation is complete and accurate
|
||||||
|
|
||||||
|
## Version Management
|
||||||
|
|
||||||
|
Update version numbers in these files:
|
||||||
|
1. `finishte.sh` - Line 34: `export ACSH_VERSION=0.5.0`
|
||||||
|
2. `debian/changelog` - Add new entry
|
||||||
|
3. `homebrew/finish.rb` - Line 6: `version "0.5.0"`
|
||||||
|
4. `docs/install.sh` - Line 7: `ACSH_VERSION="v0.5.0"`
|
||||||
|
5. `Dockerfile` - Line 6: `LABEL version="0.5.0"`
|
||||||
|
|
||||||
|
## 1. Direct Installation Script
|
||||||
|
|
||||||
|
The install script at `docs/install.sh` enables one-line installation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sSL https://git.appmodel.nl/Tour/finish/raw/branch/main/docs/install.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
### Setup:
|
||||||
|
- Host the script on a reliable server or GitHub/GitLab
|
||||||
|
- Ensure the URL is accessible and supports HTTPS
|
||||||
|
- Test the installation on clean systems
|
||||||
|
|
||||||
|
### Testing:
|
||||||
|
```bash
|
||||||
|
# Test in Docker
|
||||||
|
docker run --rm -it ubuntu:22.04 bash
|
||||||
|
curl -sSL YOUR_URL/install.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Debian/Ubuntu Packages
|
||||||
|
|
||||||
|
### Building the Package
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install build tools
|
||||||
|
sudo apt-get install debhelper devscripts
|
||||||
|
|
||||||
|
# Build the package
|
||||||
|
dpkg-buildpackage -us -uc -b
|
||||||
|
|
||||||
|
# This creates:
|
||||||
|
# ../finish_0.5.0-1_all.deb
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing the Package
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install locally
|
||||||
|
sudo dpkg -i ../finish_*.deb
|
||||||
|
sudo apt-get install -f # Fix dependencies
|
||||||
|
|
||||||
|
# Test installation
|
||||||
|
finishte --help
|
||||||
|
finishte install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Creating an APT Repository
|
||||||
|
|
||||||
|
#### Option A: Using Packagecloud (Recommended for beginners)
|
||||||
|
|
||||||
|
1. Sign up at https://packagecloud.io
|
||||||
|
2. Create a repository
|
||||||
|
3. Upload your .deb file:
|
||||||
|
```bash
|
||||||
|
gem install package_cloud
|
||||||
|
package_cloud push yourname/finish/ubuntu/jammy ../finish_*.deb
|
||||||
|
```
|
||||||
|
|
||||||
|
Users install with:
|
||||||
|
```bash
|
||||||
|
curl -s https://packagecloud.io/install/repositories/yourname/finish/script.deb.sh | sudo bash
|
||||||
|
sudo apt-get install finish
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Option B: Using GitHub Pages + reprepro
|
||||||
|
|
||||||
|
1. Create a repository structure:
|
||||||
|
```bash
|
||||||
|
mkdir -p apt-repo/{conf,pool,dists}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Create `apt-repo/conf/distributions`:
|
||||||
|
```
|
||||||
|
Origin: finishte.sh
|
||||||
|
Label: finish
|
||||||
|
Codename: stable
|
||||||
|
Architectures: all
|
||||||
|
Components: main
|
||||||
|
Description: APT repository for finishte.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Add packages:
|
||||||
|
```bash
|
||||||
|
reprepro -b apt-repo includedeb stable ../finish_*.deb
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Host on GitHub Pages or your server
|
||||||
|
|
||||||
|
Users install with:
|
||||||
|
```bash
|
||||||
|
echo "deb https://your-domain.com/apt-repo stable main" | sudo tee /etc/apt/sources.list.d/finish.list
|
||||||
|
curl -fsSL https://your-domain.com/apt-repo/key.gpg | sudo apt-key add -
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install finish
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Homebrew Formula
|
||||||
|
|
||||||
|
### Creating a Homebrew Tap
|
||||||
|
|
||||||
|
1. Create a GitHub repository: `homebrew-finish`
|
||||||
|
|
||||||
|
2. Add the formula to `Formula/finish.rb`
|
||||||
|
|
||||||
|
3. Create a release tarball and calculate SHA256:
|
||||||
|
```bash
|
||||||
|
git archive --format=tar.gz --prefix=finish-0.5.0/ v0.5.0 > finish-0.5.0.tar.gz
|
||||||
|
sha256sum finish-0.5.0.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Update the formula with the correct URL and SHA256
|
||||||
|
|
||||||
|
5. Test the formula:
|
||||||
|
```bash
|
||||||
|
brew install --build-from-source ./homebrew/finish.rb
|
||||||
|
brew test finish
|
||||||
|
brew audit --strict finish
|
||||||
|
```
|
||||||
|
|
||||||
|
### Publishing the Tap
|
||||||
|
|
||||||
|
Users can install with:
|
||||||
|
```bash
|
||||||
|
brew tap yourname/finish
|
||||||
|
brew install finish
|
||||||
|
```
|
||||||
|
|
||||||
|
Or directly:
|
||||||
|
```bash
|
||||||
|
brew install yourname/finish/finish
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Docker Images
|
||||||
|
|
||||||
|
### Building Images
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Production image
|
||||||
|
docker build -t finish:0.5.0 -t finish:latest .
|
||||||
|
|
||||||
|
# Test image
|
||||||
|
docker build -f Dockerfile.test -t finish:test .
|
||||||
|
```
|
||||||
|
|
||||||
|
### Publishing to GitHub Container Registry
|
||||||
|
|
||||||
|
The GitHub Actions workflow automatically publishes Docker images when you push a tag:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git tag -a v0.5.0 -m "Release v0.5.0"
|
||||||
|
git push origin v0.5.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Images will be available at:
|
||||||
|
```
|
||||||
|
ghcr.io/appmodel/finish:0.5.0
|
||||||
|
ghcr.io/appmodel/finish:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### Publishing to Docker Hub
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Login
|
||||||
|
docker login
|
||||||
|
|
||||||
|
# Tag and push
|
||||||
|
docker tag finish:latest yourname/finish:0.5.0
|
||||||
|
docker tag finish:latest yourname/finish:latest
|
||||||
|
docker push yourname/finish:0.5.0
|
||||||
|
docker push yourname/finish:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. GitHub Releases
|
||||||
|
|
||||||
|
### Automated Release Process
|
||||||
|
|
||||||
|
The `.github/workflows/release.yml` workflow automatically:
|
||||||
|
1. Creates release artifacts when you push a tag
|
||||||
|
2. Builds Debian packages
|
||||||
|
3. Publishes Docker images
|
||||||
|
4. Creates a GitHub release with downloads
|
||||||
|
|
||||||
|
### Manual Release Process
|
||||||
|
|
||||||
|
1. Create a release tarball:
|
||||||
|
```bash
|
||||||
|
git archive --format=tar.gz --prefix=finish-0.5.0/ v0.5.0 > finish-0.5.0.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Create release on GitHub:
|
||||||
|
- Go to Releases → Draft a new release
|
||||||
|
- Tag: `v0.5.0`
|
||||||
|
- Title: `finishte.sh v0.5.0`
|
||||||
|
- Upload: tarball and .deb file
|
||||||
|
- Write release notes
|
||||||
|
|
||||||
|
## Release Checklist
|
||||||
|
|
||||||
|
Before releasing a new version:
|
||||||
|
|
||||||
|
- [ ] Update all version numbers
|
||||||
|
- [ ] Update CHANGELOG.md
|
||||||
|
- [ ] Run all tests
|
||||||
|
- [ ] Test installation script
|
||||||
|
- [ ] Build and test Debian package
|
||||||
|
- [ ] Test Docker images
|
||||||
|
- [ ] Update documentation
|
||||||
|
- [ ] Create git tag
|
||||||
|
- [ ] Push tag to trigger CI/CD
|
||||||
|
- [ ] Verify GitHub release created
|
||||||
|
- [ ] Verify Docker images published
|
||||||
|
- [ ] Update Homebrew formula with new SHA256
|
||||||
|
- [ ] Test installation from all sources
|
||||||
|
- [ ] Announce release (if applicable)
|
||||||
|
|
||||||
|
## Distribution URLs
|
||||||
|
|
||||||
|
After publishing, users can install from:
|
||||||
|
|
||||||
|
**Quick Install:**
|
||||||
|
```bash
|
||||||
|
curl -sSL https://git.appmodel.nl/Tour/finish/raw/branch/main/docs/install.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
**Homebrew:**
|
||||||
|
```bash
|
||||||
|
brew tap appmodel/finish
|
||||||
|
brew install finish
|
||||||
|
```
|
||||||
|
|
||||||
|
**APT (if configured):**
|
||||||
|
```bash
|
||||||
|
sudo apt-get install finish
|
||||||
|
```
|
||||||
|
|
||||||
|
**Docker:**
|
||||||
|
```bash
|
||||||
|
docker pull ghcr.io/appmodel/finish:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
**Direct Download:**
|
||||||
|
```
|
||||||
|
https://git.appmodel.nl/Tour/finish/releases/latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Debian Package Issues
|
||||||
|
|
||||||
|
**Problem:** Package won't install
|
||||||
|
```bash
|
||||||
|
# Check dependencies
|
||||||
|
dpkg-deb -I finish_*.deb
|
||||||
|
# Test installation
|
||||||
|
sudo dpkg -i finish_*.deb
|
||||||
|
sudo apt-get install -f
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problem:** Lintian warnings
|
||||||
|
```bash
|
||||||
|
lintian finish_*.deb
|
||||||
|
# Fix issues in debian/ files
|
||||||
|
```
|
||||||
|
|
||||||
|
### Homebrew Issues
|
||||||
|
|
||||||
|
**Problem:** Formula audit fails
|
||||||
|
```bash
|
||||||
|
brew audit --strict ./homebrew/finish.rb
|
||||||
|
# Fix issues reported
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problem:** Installation fails
|
||||||
|
```bash
|
||||||
|
brew install --verbose --debug ./homebrew/finish.rb
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker Issues
|
||||||
|
|
||||||
|
**Problem:** Build fails
|
||||||
|
```bash
|
||||||
|
docker build --no-cache -t finish:test .
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problem:** Image too large
|
||||||
|
```bash
|
||||||
|
# Use multi-stage builds
|
||||||
|
# Remove unnecessary files
|
||||||
|
# Combine RUN commands
|
||||||
|
```
|
||||||
|
|
||||||
|
## Continuous Integration
|
||||||
|
|
||||||
|
The project includes three GitHub Actions workflows:
|
||||||
|
|
||||||
|
1. **test.yml** - Runs on every push/PR
|
||||||
|
- Runs BATS tests
|
||||||
|
- Tests installation
|
||||||
|
- Runs shellcheck
|
||||||
|
- Tests Docker builds
|
||||||
|
|
||||||
|
2. **docker.yml** - Builds and publishes Docker images
|
||||||
|
- Triggered by tags and main branch
|
||||||
|
- Publishes to GitHub Container Registry
|
||||||
|
|
||||||
|
3. **release.yml** - Creates releases
|
||||||
|
- Triggered by version tags
|
||||||
|
- Builds all artifacts
|
||||||
|
- Creates GitHub release
|
||||||
|
- Publishes packages
|
||||||
|
|
||||||
|
## Support and Documentation
|
||||||
|
|
||||||
|
- Update the README.md with installation instructions
|
||||||
|
- Maintain CHANGELOG.md with version history
|
||||||
|
- Create issue templates for bug reports and feature requests
|
||||||
|
- Set up discussions for community support
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
- Always use HTTPS for installation scripts
|
||||||
|
- Sign Debian packages with GPG
|
||||||
|
- Use checksums for release artifacts
|
||||||
|
- Regularly update dependencies
|
||||||
|
- Monitor for security vulnerabilities
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
After initial publication:
|
||||||
|
1. Monitor GitHub issues for user feedback
|
||||||
|
2. Set up analytics if desired
|
||||||
|
3. Create documentation site
|
||||||
|
4. Announce on relevant forums/communities
|
||||||
|
5. Consider submitting to package manager directories
|
||||||
300
README.md
Normal file
300
README.md
Normal file
@@ -0,0 +1,300 @@
|
|||||||
|
# finishte.sh
|
||||||
|
|
||||||
|
Command-line completion powered by local LLMs. Press `Tab` twice and get intelligent suggestions based on your terminal context.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### One-Line Install (Recommended)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sSL https://git.appmodel.nl/Tour/finish/raw/branch/main/docs/install.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://git.appmodel.nl/Tour/finish.git finish
|
||||||
|
cd finish
|
||||||
|
./finish.sh install
|
||||||
|
source ~/.bashrc
|
||||||
|
```
|
||||||
|
|
||||||
|
### Other Installation Methods
|
||||||
|
|
||||||
|
See [Installation](#installation) below for package managers, Docker, and more options.
|
||||||
|
|
||||||
|
## What It Does
|
||||||
|
|
||||||
|
finishte.sh enhances your terminal with context-aware command suggestions. It analyzes:
|
||||||
|
|
||||||
|
- Current directory and recent files
|
||||||
|
- Command history
|
||||||
|
- Environment variables
|
||||||
|
- Command help output
|
||||||
|
|
||||||
|
Then generates relevant completions using a local LLM.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
**Local by Default**
|
||||||
|
Runs with LM-Studio on localhost. No external API calls, no data leaving your machine.
|
||||||
|
|
||||||
|
**Context-Aware**
|
||||||
|
Understands what you're doing based on your environment and history.
|
||||||
|
|
||||||
|
**Cached**
|
||||||
|
Stores recent queries locally for instant responses.
|
||||||
|
|
||||||
|
**Flexible**
|
||||||
|
Switch between different models and providers easily.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
View current settings:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
finishte config
|
||||||
|
```
|
||||||
|
|
||||||
|
Change the endpoint or model:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
finishte config set endpoint http://192.168.1.100:1234/v1/chat/completions
|
||||||
|
finishte config set model your-model-name
|
||||||
|
```
|
||||||
|
|
||||||
|
Select a model interactively:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
finishte model
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Type a command and press `Tab` twice:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker <TAB><TAB>
|
||||||
|
git commit <TAB><TAB>
|
||||||
|
ffmpeg <TAB><TAB>
|
||||||
|
```
|
||||||
|
|
||||||
|
Natural language works too:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# find large files <TAB><TAB>
|
||||||
|
# compress to zip <TAB><TAB>
|
||||||
|
```
|
||||||
|
|
||||||
|
Test it without executing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
finishte command "your command"
|
||||||
|
finishte command --dry-run "your command"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Quick Install (Linux/macOS)
|
||||||
|
|
||||||
|
The fastest way to get started:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sSL https://git.appmodel.nl/Tour/finish/raw/branch/main/docs/install.sh | bash
|
||||||
|
source ~/.bashrc # or ~/.zshrc for zsh
|
||||||
|
finishte model
|
||||||
|
```
|
||||||
|
|
||||||
|
### Package Managers
|
||||||
|
|
||||||
|
#### Homebrew (macOS)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew tap appmodel/finish
|
||||||
|
brew install finish
|
||||||
|
finishte install
|
||||||
|
source ~/.bashrc
|
||||||
|
```
|
||||||
|
|
||||||
|
#### APT (Debian/Ubuntu)
|
||||||
|
|
||||||
|
Download the `.deb` package from [releases](https://git.appmodel.nl/Tour/finish/releases):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo dpkg -i finish_*.deb
|
||||||
|
sudo apt-get install -f # Install dependencies
|
||||||
|
finishte install
|
||||||
|
source ~/.bashrc
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
|
||||||
|
Run finishte.sh in a container:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build the image
|
||||||
|
docker build -t finish .
|
||||||
|
|
||||||
|
# Run interactively
|
||||||
|
docker run -it finish
|
||||||
|
|
||||||
|
# Or use docker-compose
|
||||||
|
docker-compose up -d finish
|
||||||
|
docker-compose exec finish bash
|
||||||
|
```
|
||||||
|
|
||||||
|
Inside the container:
|
||||||
|
```bash
|
||||||
|
finishte install
|
||||||
|
source ~/.bashrc
|
||||||
|
finishte model # Configure your LLM endpoint
|
||||||
|
```
|
||||||
|
|
||||||
|
### From Source
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://git.appmodel.nl/Tour/finish.git finish
|
||||||
|
cd finish
|
||||||
|
chmod +x finish.sh
|
||||||
|
sudo ln -s $PWD/finish.sh /usr/local/bin/finish
|
||||||
|
finishte install
|
||||||
|
source ~/.bashrc
|
||||||
|
```
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Bash 4.0+ or Zsh 5.0+
|
||||||
|
- curl
|
||||||
|
- jq
|
||||||
|
- bc
|
||||||
|
- bash-completion (recommended)
|
||||||
|
- LM-Studio or Ollama (for LLM inference)
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
~/.finishte/
|
||||||
|
├── config # Configuration file
|
||||||
|
├── finishte.log # Usage log
|
||||||
|
└── cache/ # Cached completions
|
||||||
|
```
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
finishte install # Set up finishte
|
||||||
|
finishte remove # Uninstall
|
||||||
|
finishte config # Show/edit configuration
|
||||||
|
finishte model # Select model
|
||||||
|
finishte enable # Enable completions
|
||||||
|
finishte disable # Disable completions
|
||||||
|
finishte clear # Clear cache and logs
|
||||||
|
finishte usage # Show usage statistics
|
||||||
|
finishte system # Show system information
|
||||||
|
finishte --help # Show help
|
||||||
|
```
|
||||||
|
|
||||||
|
## Providers
|
||||||
|
|
||||||
|
Currently supports:
|
||||||
|
|
||||||
|
- **LM-Studio** (default) - Local models via OpenAI-compatible API
|
||||||
|
- **Ollama** - Local models via Ollama API
|
||||||
|
|
||||||
|
Add custom providers by editing `finishte.sh` and adding entries to `_finishte_modellist`.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
### Local Development
|
||||||
|
|
||||||
|
Clone and link for development:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://git.appmodel.nl/Tour/finish.git finish
|
||||||
|
cd finish
|
||||||
|
ln -s $PWD/finishte.sh $HOME/.local/bin/finish
|
||||||
|
finishte install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install BATS testing framework
|
||||||
|
sudo apt-get install bats # Ubuntu/Debian
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
./run_tests.sh
|
||||||
|
|
||||||
|
# Or use Docker
|
||||||
|
docker build -f Dockerfile.test -t finish:test .
|
||||||
|
docker run --rm finish:test
|
||||||
|
```
|
||||||
|
|
||||||
|
### Building Packages
|
||||||
|
|
||||||
|
#### Debian Package
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install build dependencies
|
||||||
|
sudo apt-get install debhelper devscripts
|
||||||
|
|
||||||
|
# Build the package
|
||||||
|
dpkg-buildpackage -us -uc -b
|
||||||
|
|
||||||
|
# Package will be created in parent directory
|
||||||
|
ls ../*.deb
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Docker Images
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Production image
|
||||||
|
docker build -t finish:latest .
|
||||||
|
|
||||||
|
# Test image
|
||||||
|
docker build -f Dockerfile.test -t finish:test .
|
||||||
|
|
||||||
|
# Using docker-compose
|
||||||
|
docker-compose build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Distribution & Publishing
|
||||||
|
|
||||||
|
### Creating a Release
|
||||||
|
|
||||||
|
1. Update version in `finishte.sh` (ACSH_VERSION)
|
||||||
|
2. Update version in `debian/changelog`
|
||||||
|
3. Update version in `homebrew/finish.rb`
|
||||||
|
4. Create and push a git tag:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git tag -a v0.5.0 -m "Release version 0.5.0"
|
||||||
|
git push origin v0.5.0
|
||||||
|
```
|
||||||
|
|
||||||
|
This triggers the GitHub Actions workflow which:
|
||||||
|
- Builds release artifacts
|
||||||
|
- Creates Debian packages
|
||||||
|
- Publishes Docker images to GitHub Container Registry
|
||||||
|
- Creates a GitHub release with downloadable assets
|
||||||
|
|
||||||
|
### Homebrew Formula
|
||||||
|
|
||||||
|
After creating a release, update the Homebrew formula:
|
||||||
|
|
||||||
|
1. Calculate SHA256 of the release tarball:
|
||||||
|
```bash
|
||||||
|
curl -sL https://git.appmodel.nl/Tour/finish/archive/v0.5.0.tar.gz | sha256sum
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Update `homebrew/finish.rb` with the new SHA256 and version
|
||||||
|
|
||||||
|
3. Submit to your Homebrew tap or the main Homebrew repository
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Contributions are welcome! Please feel free to submit a Pull Request.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
BSD 2-Clause License. See LICENSE file.
|
||||||
201
SUMMARY.md
Normal file
201
SUMMARY.md
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
# Publication Setup Summary
|
||||||
|
|
||||||
|
This document summarizes the publication setup completed for finishte.sh.
|
||||||
|
|
||||||
|
## Files Created
|
||||||
|
|
||||||
|
### 1. Installation Script
|
||||||
|
- **`docs/install.sh`** - Enhanced one-line installer with:
|
||||||
|
- Dependency checking
|
||||||
|
- Shell detection (bash/zsh)
|
||||||
|
- Color-coded output
|
||||||
|
- Error handling
|
||||||
|
- PATH management
|
||||||
|
- Usage: `curl -sSL <URL>/install.sh | bash`
|
||||||
|
|
||||||
|
### 2. Debian Package Structure
|
||||||
|
Complete Debian packaging in `debian/` directory:
|
||||||
|
- **`control`** - Package metadata and dependencies
|
||||||
|
- **`rules`** - Build instructions
|
||||||
|
- **`changelog`** - Version history
|
||||||
|
- **`copyright`** - License information
|
||||||
|
- **`compat`** - Debhelper compatibility level
|
||||||
|
- **`postinst`** - Post-installation script
|
||||||
|
- **`source/format`** - Package format specification
|
||||||
|
|
||||||
|
Build command: `dpkg-buildpackage -us -uc -b`
|
||||||
|
|
||||||
|
### 3. Homebrew Formula
|
||||||
|
- **`homebrew/finish.rb`** - Homebrew formula for macOS
|
||||||
|
- Includes dependencies, installation steps, and caveats
|
||||||
|
- Update SHA256 after creating releases
|
||||||
|
|
||||||
|
### 4. Docker Configuration
|
||||||
|
- **`Dockerfile`** - Production container image
|
||||||
|
- **`Dockerfile.test`** - Testing container with BATS
|
||||||
|
- **`docker-compose.yml`** - Multi-service Docker setup
|
||||||
|
- **`.dockerignore`** - Excludes unnecessary files from builds
|
||||||
|
|
||||||
|
### 5. GitHub Actions Workflows
|
||||||
|
CI/CD automation in `.github/workflows/`:
|
||||||
|
- **`test.yml`** - Runs tests on every push/PR
|
||||||
|
- **`release.yml`** - Creates releases and builds packages
|
||||||
|
- **`docker.yml`** - Builds and publishes Docker images
|
||||||
|
|
||||||
|
### 6. Documentation
|
||||||
|
- **`PUBLISHING.md`** - Comprehensive publishing guide
|
||||||
|
- **`README.md`** - Updated with installation methods
|
||||||
|
- **`.gitignore`** - Updated to exclude build artifacts
|
||||||
|
|
||||||
|
## Distribution Channels
|
||||||
|
|
||||||
|
### 1. Quick Install (Recommended)
|
||||||
|
```bash
|
||||||
|
curl -sSL https://git.appmodel.nl/Tour/finish/raw/branch/main/docs/install.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Debian/Ubuntu Package
|
||||||
|
Users can install the `.deb` file:
|
||||||
|
```bash
|
||||||
|
sudo dpkg -i finish_*.deb
|
||||||
|
sudo apt-get install -f
|
||||||
|
```
|
||||||
|
|
||||||
|
Or from an APT repository (when configured):
|
||||||
|
```bash
|
||||||
|
sudo apt-get install finish
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Homebrew (macOS)
|
||||||
|
```bash
|
||||||
|
brew tap closedloop-technologies/finish
|
||||||
|
brew install finish
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Docker
|
||||||
|
```bash
|
||||||
|
docker pull ghcr.io/closedloop-technologies/finish:latest
|
||||||
|
docker run -it ghcr.io/closedloop-technologies/finish:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. From Source
|
||||||
|
```bash
|
||||||
|
git clone https://git.appmodel.nl/Tour/finish.git
|
||||||
|
cd finish
|
||||||
|
./finishte.sh install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
### Immediate Actions
|
||||||
|
1. **Test the installation script** on clean systems
|
||||||
|
2. **Build and test the Debian package**:
|
||||||
|
```bash
|
||||||
|
dpkg-buildpackage -us -uc -b
|
||||||
|
sudo dpkg -i ../finish_*.deb
|
||||||
|
```
|
||||||
|
3. **Test Docker builds**:
|
||||||
|
```bash
|
||||||
|
docker build -t finish:test .
|
||||||
|
docker run -it finish:test
|
||||||
|
```
|
||||||
|
|
||||||
|
### For First Release
|
||||||
|
1. **Update version numbers** in:
|
||||||
|
- `finishte.sh` (line 34)
|
||||||
|
- `debian/changelog`
|
||||||
|
- `homebrew/finish.rb`
|
||||||
|
- `docs/install.sh`
|
||||||
|
- `Dockerfile`
|
||||||
|
|
||||||
|
2. **Create release tarball**:
|
||||||
|
```bash
|
||||||
|
git archive --format=tar.gz --prefix=finish-0.5.0/ v0.5.0 > finish-0.5.0.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Calculate SHA256** for Homebrew:
|
||||||
|
```bash
|
||||||
|
sha256sum finish-0.5.0.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Create and push tag**:
|
||||||
|
```bash
|
||||||
|
git tag -a v0.5.0 -m "Release v0.5.0"
|
||||||
|
git push origin v0.5.0
|
||||||
|
```
|
||||||
|
This triggers GitHub Actions to build everything automatically.
|
||||||
|
|
||||||
|
### Setting Up Package Repositories
|
||||||
|
|
||||||
|
#### APT Repository Options:
|
||||||
|
1. **Packagecloud** (easiest):
|
||||||
|
- Sign up at https://packagecloud.io
|
||||||
|
- Upload .deb file
|
||||||
|
- Users get one-line install
|
||||||
|
|
||||||
|
2. **GitHub Pages** (free):
|
||||||
|
- Use `reprepro` to create repository
|
||||||
|
- Host on GitHub Pages
|
||||||
|
- Users add your repository to sources
|
||||||
|
|
||||||
|
See `PUBLISHING.md` for detailed instructions.
|
||||||
|
|
||||||
|
#### Homebrew Tap:
|
||||||
|
1. Create GitHub repo: `homebrew-finish`
|
||||||
|
2. Add formula to `Formula/finish.rb`
|
||||||
|
3. Users install with: `brew tap yourname/finish && brew install finish`
|
||||||
|
|
||||||
|
## Testing Checklist
|
||||||
|
|
||||||
|
Before releasing:
|
||||||
|
- [ ] Test install script on Ubuntu 22.04
|
||||||
|
- [ ] Test install script on Debian 12
|
||||||
|
- [ ] Test install script on macOS
|
||||||
|
- [ ] Build Debian package successfully
|
||||||
|
- [ ] Install and test Debian package
|
||||||
|
- [ ] Build Docker image successfully
|
||||||
|
- [ ] Run BATS tests in Docker
|
||||||
|
- [ ] Test Homebrew formula (if applicable)
|
||||||
|
- [ ] Verify all version numbers match
|
||||||
|
- [ ] Test GitHub Actions workflows
|
||||||
|
|
||||||
|
## Automation
|
||||||
|
|
||||||
|
The GitHub Actions workflows handle:
|
||||||
|
- **On every push/PR**: Run tests, lint code, build Docker images
|
||||||
|
- **On version tag push**: Build packages, create release, publish Docker images
|
||||||
|
|
||||||
|
This means once set up, releasing a new version is as simple as:
|
||||||
|
```bash
|
||||||
|
git tag -a v0.5.1 -m "Release v0.5.1"
|
||||||
|
git push origin v0.5.1
|
||||||
|
```
|
||||||
|
|
||||||
|
Everything else happens automatically!
|
||||||
|
|
||||||
|
## Support Channels
|
||||||
|
|
||||||
|
Consider setting up:
|
||||||
|
- GitHub Issues for bug reports
|
||||||
|
- GitHub Discussions for Q&A
|
||||||
|
- Documentation website (GitHub Pages)
|
||||||
|
- Discord/Slack community (optional)
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
- Debian Packaging Guide: https://www.debian.org/doc/manuals/maint-guide/
|
||||||
|
- Homebrew Formula Cookbook: https://docs.brew.sh/Formula-Cookbook
|
||||||
|
- GitHub Actions Documentation: https://docs.github.com/en/actions
|
||||||
|
- Docker Best Practices: https://docs.docker.com/develop/dev-best-practices/
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
Your project is now fully set up for publication with:
|
||||||
|
- ✅ One-line installation script
|
||||||
|
- ✅ Debian/Ubuntu package support
|
||||||
|
- ✅ Homebrew formula for macOS
|
||||||
|
- ✅ Docker containers
|
||||||
|
- ✅ Automated CI/CD with GitHub Actions
|
||||||
|
- ✅ Comprehensive documentation
|
||||||
|
|
||||||
|
All distribution channels are ready. Follow the "Next Steps" section above to test and publish your first release!
|
||||||
140
USAGE.md
Normal file
140
USAGE.md
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
# Usage Guide
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Install and set up finishte:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
finishte install
|
||||||
|
source ~/.bashrc
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
View current settings:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
finishte config
|
||||||
|
```
|
||||||
|
|
||||||
|
Change settings:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
finishte config set temperature 0.5
|
||||||
|
finishte config set endpoint http://localhost:1234/v1/chat/completions
|
||||||
|
finishte config set model your-model-name
|
||||||
|
```
|
||||||
|
|
||||||
|
Reset to defaults:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
finishte config reset
|
||||||
|
```
|
||||||
|
|
||||||
|
## Model Selection
|
||||||
|
|
||||||
|
Select a model interactively:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
finishte model
|
||||||
|
```
|
||||||
|
|
||||||
|
Use arrow keys to navigate, Enter to select, or 'q' to quit.
|
||||||
|
|
||||||
|
## Basic Commands
|
||||||
|
|
||||||
|
Show help:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
finishte --help
|
||||||
|
```
|
||||||
|
|
||||||
|
Test completions without caching:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
finishte command "your command here"
|
||||||
|
```
|
||||||
|
|
||||||
|
Preview the prompt sent to the model:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
finishte command --dry-run "your command here"
|
||||||
|
```
|
||||||
|
|
||||||
|
View system information:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
finishte system
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage Statistics
|
||||||
|
|
||||||
|
Check your usage stats and costs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
finishte usage
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cache Management
|
||||||
|
|
||||||
|
Clear cached completions and logs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
finishte clear
|
||||||
|
```
|
||||||
|
|
||||||
|
## Enable/Disable
|
||||||
|
|
||||||
|
Temporarily disable:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
finishte disable
|
||||||
|
```
|
||||||
|
|
||||||
|
Re-enable:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
finishte enable
|
||||||
|
```
|
||||||
|
|
||||||
|
## Uninstallation
|
||||||
|
|
||||||
|
Remove finishte completely:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
finishte remove
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using finishte
|
||||||
|
|
||||||
|
Once enabled, just type a command and press `Tab` twice to get suggestions:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git <TAB><TAB>
|
||||||
|
docker <TAB><TAB>
|
||||||
|
find <TAB><TAB>
|
||||||
|
```
|
||||||
|
|
||||||
|
Natural language also works:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# convert video to mp4 <TAB><TAB>
|
||||||
|
# list all processes using port 8080 <TAB><TAB>
|
||||||
|
# compress this folder to tar.gz <TAB><TAB>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration File
|
||||||
|
|
||||||
|
The config file is located at `~/.finishte/config` and contains:
|
||||||
|
|
||||||
|
- `provider`: Model provider (lmstudio, ollama)
|
||||||
|
- `model`: Model name
|
||||||
|
- `temperature`: Response randomness (0.0 - 1.0)
|
||||||
|
- `endpoint`: API endpoint URL
|
||||||
|
- `api_prompt_cost`: Cost per input token
|
||||||
|
- `api_completion_cost`: Cost per output token
|
||||||
|
- `max_history_commands`: Number of recent commands to include in context
|
||||||
|
- `max_recent_files`: Number of recent files to include in context
|
||||||
|
- `cache_dir`: Directory for cached completions
|
||||||
|
- `cache_size`: Maximum number of cached items
|
||||||
|
- `log_file`: Path to usage log file
|
||||||
10
debian/changelog
vendored
Normal file
10
debian/changelog
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
finish (0.5.0-1) stable; urgency=medium
|
||||||
|
|
||||||
|
* Initial Debian package release
|
||||||
|
* LLM-powered bash and zsh finishtion
|
||||||
|
* Support for LM-Studio and Ollama providers
|
||||||
|
* Context-aware command suggestions
|
||||||
|
* Intelligent caching system
|
||||||
|
* Privacy-focused local operation
|
||||||
|
|
||||||
|
-- finishte.sh Contributors <noreply@finishte.sh> Mon, 02 Dec 2024 12:00:00 +0000
|
||||||
1
debian/compat
vendored
Normal file
1
debian/compat
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
10
|
||||||
24
debian/control
vendored
Normal file
24
debian/control
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
Source: finish
|
||||||
|
Section: utils
|
||||||
|
Priority: optional
|
||||||
|
Maintainer: finishte.sh Contributors <noreply@finishte.sh>
|
||||||
|
Build-Depends: debhelper (>= 10)
|
||||||
|
Standards-Version: 4.5.0
|
||||||
|
Homepage: https://git.appmodel.nl/Tour/finish/finish
|
||||||
|
Vcs-Git: https://git.appmodel.nl/Tour/finish.git
|
||||||
|
|
||||||
|
Package: finish
|
||||||
|
Architecture: all
|
||||||
|
Depends: bash (>= 4.0) | zsh (>= 5.0), curl, jq, bc, bash-completion
|
||||||
|
Recommends: lm-studio | ollama
|
||||||
|
Description: LLM-powered command-line finishtion
|
||||||
|
finishte.sh enhances your terminal with context-aware command suggestions
|
||||||
|
using local large language models. Features include:
|
||||||
|
.
|
||||||
|
* Local-first operation with LM-Studio or Ollama
|
||||||
|
* Context-aware suggestions based on command history and environment
|
||||||
|
* Intelligent caching for instant responses
|
||||||
|
* Support for multiple LLM providers
|
||||||
|
* Privacy-focused - no data leaves your machine by default
|
||||||
|
.
|
||||||
|
Simply press Tab twice to get intelligent command suggestions powered by AI.
|
||||||
30
debian/copyright
vendored
Normal file
30
debian/copyright
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||||
|
Upstream-Name: finish
|
||||||
|
Upstream-Contact: https://git.appmodel.nl/Tour/finish/finish
|
||||||
|
Source: https://git.appmodel.nl/Tour/finish
|
||||||
|
|
||||||
|
Files: *
|
||||||
|
Copyright: 2024-2025 finishte.sh Contributors
|
||||||
|
License: BSD-2-Clause
|
||||||
|
|
||||||
|
License: BSD-2-Clause
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
.
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
.
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
27
debian/postinst
vendored
Normal file
27
debian/postinst
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
configure)
|
||||||
|
echo "finishte.sh has been installed successfully!"
|
||||||
|
echo ""
|
||||||
|
echo "To complete setup, run:"
|
||||||
|
echo " finishte install"
|
||||||
|
echo ""
|
||||||
|
echo "Then reload your shell:"
|
||||||
|
echo " source ~/.bashrc # for bash"
|
||||||
|
echo " source ~/.zshrc # for zsh"
|
||||||
|
;;
|
||||||
|
|
||||||
|
abort-upgrade|abort-remove|abort-deconfigure)
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo "postinst called with unknown argument \`$1'" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
#DEBHELPER#
|
||||||
|
|
||||||
|
exit 0
|
||||||
19
debian/rules
vendored
Normal file
19
debian/rules
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#!/usr/bin/make -f
|
||||||
|
# -*- makefile -*-
|
||||||
|
|
||||||
|
# Uncomment this to turn on verbose mode.
|
||||||
|
#export DH_VERBOSE=1
|
||||||
|
|
||||||
|
%:
|
||||||
|
dh $@
|
||||||
|
|
||||||
|
override_dh_auto_install:
|
||||||
|
install -D -m 0755 finishte.sh $(CURDIR)/debian/finish/usr/bin/finishte
|
||||||
|
install -D -m 0644 LICENSE $(CURDIR)/debian/finish/usr/share/doc/finish/copyright
|
||||||
|
install -D -m 0644 README.md $(CURDIR)/debian/finish/usr/share/doc/finish/README.md
|
||||||
|
|
||||||
|
override_dh_auto_build:
|
||||||
|
# Nothing to build
|
||||||
|
|
||||||
|
override_dh_auto_test:
|
||||||
|
# Tests run in CI
|
||||||
1
debian/source/format
vendored
Normal file
1
debian/source/format
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
3.0 (native)
|
||||||
30
docker-compose.yml
Normal file
30
docker-compose.yml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
finish:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
image: finish:latest
|
||||||
|
container_name: finish
|
||||||
|
stdin_open: true
|
||||||
|
tty: true
|
||||||
|
volumes:
|
||||||
|
- finish-data:/root/.finish
|
||||||
|
environment:
|
||||||
|
- ACSH_ENDPOINT=http://host.docker.internal:1234/v1/chat/completions
|
||||||
|
extra_hosts:
|
||||||
|
- "host.docker.internal:host-gateway"
|
||||||
|
command: /bin/bash
|
||||||
|
|
||||||
|
finish-test:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.test
|
||||||
|
image: finish:test
|
||||||
|
container_name: finish-test
|
||||||
|
volumes:
|
||||||
|
- .:/opt/finish
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
finish-data:
|
||||||
1
docs/CNAME
Normal file
1
docs/CNAME
Normal file
@@ -0,0 +1 @@
|
|||||||
|
finishte
|
||||||
198
docs/install.sh
Normal file
198
docs/install.sh
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# finishte.sh installer
|
||||||
|
# Usage: curl -sSL https://git.appmodel.nl/Tour/finish/raw/branch/main/docs/install.sh | bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
ACSH_VERSION="v0.5.0"
|
||||||
|
BRANCH_OR_VERSION=${1:-main}
|
||||||
|
REPO_URL="https://git.appmodel.nl/Tour/finish/raw/branch"
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
echo_green() {
|
||||||
|
printf "${GREEN}%s${NC}\n" "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo_error() {
|
||||||
|
printf "${RED}ERROR: %s${NC}\n" "$1" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
echo_warning() {
|
||||||
|
printf "${YELLOW}WARNING: %s${NC}\n" "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Detect the current shell
|
||||||
|
detect_shell() {
|
||||||
|
if [ -n "$ZSH_VERSION" ]; then
|
||||||
|
echo "zsh"
|
||||||
|
elif [ -n "$BASH_VERSION" ]; then
|
||||||
|
echo "bash"
|
||||||
|
else
|
||||||
|
parent_shell=$(ps -p $PPID -o comm= 2>/dev/null || echo "")
|
||||||
|
case "$parent_shell" in
|
||||||
|
*zsh*) echo "zsh" ;;
|
||||||
|
*bash*) echo "bash" ;;
|
||||||
|
*) echo "unknown" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check dependencies
|
||||||
|
check_dependencies() {
|
||||||
|
local missing=""
|
||||||
|
|
||||||
|
if ! command -v curl > /dev/null 2>&1 && ! command -v wget > /dev/null 2>&1; then
|
||||||
|
missing="${missing}curl or wget, "
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v jq > /dev/null 2>&1; then
|
||||||
|
missing="${missing}jq, "
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v bc > /dev/null 2>&1; then
|
||||||
|
missing="${missing}bc, "
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$missing" ]; then
|
||||||
|
echo_error "Missing dependencies: ${missing%, }"
|
||||||
|
echo ""
|
||||||
|
echo "Install them with:"
|
||||||
|
echo " Ubuntu/Debian: sudo apt-get install curl jq bc bash-completion"
|
||||||
|
echo " CentOS/RHEL: sudo yum install curl jq bc bash-completion"
|
||||||
|
echo " macOS: brew install jq bc bash-completion"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Download file
|
||||||
|
download_file() {
|
||||||
|
local url="$1"
|
||||||
|
local output="$2"
|
||||||
|
|
||||||
|
if command -v curl > /dev/null 2>&1; then
|
||||||
|
curl -fsSL "$url" -o "$output"
|
||||||
|
elif command -v wget > /dev/null 2>&1; then
|
||||||
|
wget -q -O "$output" "$url"
|
||||||
|
else
|
||||||
|
echo_error "Neither curl nor wget is available"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main installation
|
||||||
|
main() {
|
||||||
|
echo_green "=========================================="
|
||||||
|
echo_green " finishte.sh Installation"
|
||||||
|
echo_green " Version: $ACSH_VERSION"
|
||||||
|
echo_green "=========================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Detect shell
|
||||||
|
SHELL_TYPE=$(detect_shell)
|
||||||
|
case "$SHELL_TYPE" in
|
||||||
|
zsh)
|
||||||
|
SCRIPT_NAME="finishte.zsh"
|
||||||
|
RC_FILE="$HOME/.zshrc"
|
||||||
|
;;
|
||||||
|
bash)
|
||||||
|
SCRIPT_NAME="finishte.sh"
|
||||||
|
RC_FILE="$HOME/.bashrc"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo_error "Unsupported shell. Currently only bash and zsh are supported."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "Detected shell: $SHELL_TYPE"
|
||||||
|
|
||||||
|
# Check dependencies
|
||||||
|
echo "Checking dependencies..."
|
||||||
|
if ! check_dependencies; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo_green "✓ All dependencies found"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Determine installation location
|
||||||
|
if [ -d "$HOME/.local/bin" ]; then
|
||||||
|
INSTALL_LOCATION="$HOME/.local/bin/finishte"
|
||||||
|
# Add to PATH if not already there
|
||||||
|
if ! echo "$PATH" | grep -q "$HOME/.local/bin"; then
|
||||||
|
echo_warning "$HOME/.local/bin is not in PATH"
|
||||||
|
if [ ! -f "$RC_FILE" ] || ! grep -q ".local/bin" "$RC_FILE"; then
|
||||||
|
echo "Adding $HOME/.local/bin to PATH in $RC_FILE"
|
||||||
|
echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$RC_FILE"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
elif [ -w "/usr/local/bin" ]; then
|
||||||
|
INSTALL_LOCATION="/usr/local/bin/finishte"
|
||||||
|
else
|
||||||
|
echo_error "Cannot write to /usr/local/bin and $HOME/.local/bin doesn't exist"
|
||||||
|
echo "Please create $HOME/.local/bin or run with sudo"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create directory if needed
|
||||||
|
mkdir -p "$(dirname "$INSTALL_LOCATION")"
|
||||||
|
|
||||||
|
# Download script
|
||||||
|
echo "Downloading finishte.sh..."
|
||||||
|
URL="$REPO_URL/$BRANCH_OR_VERSION/$SCRIPT_NAME"
|
||||||
|
if ! download_file "$URL" "$INSTALL_LOCATION"; then
|
||||||
|
echo_error "Failed to download from $URL"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
chmod +x "$INSTALL_LOCATION"
|
||||||
|
echo_green "✓ Installed to $INSTALL_LOCATION"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check bash-completion
|
||||||
|
if [ "$SHELL_TYPE" = "bash" ]; then
|
||||||
|
if ! command -v _init_completion > /dev/null 2>&1; then
|
||||||
|
if [ -f /usr/share/bash-completion/bash_completion ]; then
|
||||||
|
echo_warning "bash-completion not loaded. Add to $RC_FILE:"
|
||||||
|
echo " source /usr/share/bash-completion/bash_completion"
|
||||||
|
elif [ -f /etc/bash_completion ]; then
|
||||||
|
echo_warning "bash-completion not loaded. Add to $RC_FILE:"
|
||||||
|
echo " source /etc/bash_completion"
|
||||||
|
else
|
||||||
|
echo_warning "bash-completion not found. Install it for best experience:"
|
||||||
|
echo " sudo apt-get install bash-completion # Ubuntu/Debian"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Run finishte install
|
||||||
|
echo "Running finishte installation..."
|
||||||
|
if "$INSTALL_LOCATION" install; then
|
||||||
|
echo ""
|
||||||
|
echo_green "=========================================="
|
||||||
|
echo_green " Installation Complete!"
|
||||||
|
echo_green "=========================================="
|
||||||
|
echo ""
|
||||||
|
echo "Next steps:"
|
||||||
|
echo " 1. Reload your shell configuration:"
|
||||||
|
echo " source $RC_FILE"
|
||||||
|
echo ""
|
||||||
|
echo " 2. Select a language model:"
|
||||||
|
echo " finishte model"
|
||||||
|
echo ""
|
||||||
|
echo " 3. Start using by pressing Tab twice after any command"
|
||||||
|
echo ""
|
||||||
|
echo "Documentation: https://git.appmodel.nl/Tour/finish/finish"
|
||||||
|
else
|
||||||
|
echo_error "Installation failed. Please check the output above."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
960
finish.sh
Normal file
960
finish.sh
Normal file
@@ -0,0 +1,960 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
###############################################################################
|
||||||
|
# Enhanced Error Handling #
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
error_exit() {
|
||||||
|
echo -e "\e[finish.sh - $1\e[0m" >&2
|
||||||
|
# In a completion context, exit is too severe. Use return instead.
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
echo_error() {
|
||||||
|
echo -e "\e[finish.sh - $1\e[0m" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
echo_green() {
|
||||||
|
echo -e "\e[32m$1\e[0m"
|
||||||
|
}
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Global Variables & Model Definitions #
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
export ACSH_VERSION=0.5.0
|
||||||
|
|
||||||
|
unset _finishte_modellist
|
||||||
|
declare -A _finishte_modellist
|
||||||
|
# LM-Studio models
|
||||||
|
_finishte_modellist['lmstudio: darkidol-llama-3.1-8b-instruct-1.3-uncensored_gguf:2']='{ "completion_cost":0.0000000, "prompt_cost":0.0000000, "endpoint": "http://localhost:1234/v1/chat/completions", "model": "darkidol-llama-3.1-8b-instruct-1.3-uncensored_gguf:2", "provider": "lmstudio" }'
|
||||||
|
# Ollama model
|
||||||
|
_finishte_modellist['ollama: codellama']='{ "completion_cost":0.0000000, "prompt_cost":0.0000000, "endpoint": "http://localhost:11434/api/chat", "model": "codellama", "provider": "ollama" }'
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# System Information Functions #
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
_get_terminal_info() {
|
||||||
|
local terminal_info=" * User name: \$USER=$USER
|
||||||
|
* Current directory: \$PWD=$PWD
|
||||||
|
* Previous directory: \$OLDPWD=$OLDPWD
|
||||||
|
* Home directory: \$HOME=$HOME
|
||||||
|
* Operating system: \$OSTYPE=$OSTYPE
|
||||||
|
* Shell: \$BASH=$BASH
|
||||||
|
* Terminal type: \$TERM=$TERM
|
||||||
|
* Hostname: \$HOSTNAME"
|
||||||
|
echo "$terminal_info"
|
||||||
|
}
|
||||||
|
|
||||||
|
machine_signature() {
|
||||||
|
local signature
|
||||||
|
signature=$(echo "$(uname -a)|$$USER" | md5sum | cut -d ' ' -f 1)
|
||||||
|
echo "$signature"
|
||||||
|
}
|
||||||
|
|
||||||
|
_system_info() {
|
||||||
|
echo "# System Information"
|
||||||
|
echo
|
||||||
|
uname -a
|
||||||
|
echo "SIGNATURE: $(machine_signature)"
|
||||||
|
echo
|
||||||
|
echo "BASH_VERSION: $BASH_VERSION"
|
||||||
|
echo "BASH_COMPLETION_VERSINFO: ${BASH_COMPLETION_VERSINFO}"
|
||||||
|
echo
|
||||||
|
echo "## Terminal Information"
|
||||||
|
_get_terminal_info
|
||||||
|
}
|
||||||
|
|
||||||
|
_completion_vars() {
|
||||||
|
echo "BASH_COMPLETION_VERSINFO: ${BASH_COMPLETION_VERSINFO}"
|
||||||
|
echo "COMP_CWORD: ${COMP_CWORD}"
|
||||||
|
echo "COMP_KEY: ${COMP_KEY}"
|
||||||
|
echo "COMP_LINE: ${COMP_LINE}"
|
||||||
|
echo "COMP_POINT: ${COMP_POINT}"
|
||||||
|
echo "COMP_TYPE: ${COMP_TYPE}"
|
||||||
|
echo "COMP_WORDBREAKS: ${COMP_WORDBREAKS}"
|
||||||
|
echo "COMP_WORDS: ${COMP_WORDS[*]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# LLM Completion Functions #
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
_get_system_message_prompt() {
|
||||||
|
echo "You are a helpful bash_completion script. Generate relevant and concise auto-complete suggestions for the given user command in the context of the current directory, operating system, command history, and environment variables. The output must be a list of two to five possible completions or rewritten commands, each on a new line, without spanning multiple lines. Each must be a valid command or chain of commands. Do not include backticks or quotes."
|
||||||
|
}
|
||||||
|
|
||||||
|
_get_output_instructions() {
|
||||||
|
echo "Provide a list of suggested completions or commands that could be run in the terminal. YOU MUST provide a list of two to five possible completions or rewritten commands. DO NOT wrap the commands in backticks or quotes. Each must be a valid command or chain of commands. Focus on the user's intent, recent commands, and the current environment. RETURN A JSON OBJECT WITH THE COMPLETIONS."
|
||||||
|
}
|
||||||
|
|
||||||
|
_get_command_history() {
|
||||||
|
local HISTORY_LIMIT=${ACSH_MAX_HISTORY_COMMANDS:-20}
|
||||||
|
history | tail -n "$HISTORY_LIMIT"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Refined sanitization: only replace long hex sequences, UUIDs, and API-key–like tokens.
|
||||||
|
_get_clean_command_history() {
|
||||||
|
local recent_history
|
||||||
|
recent_history=$(_get_command_history)
|
||||||
|
recent_history=$(echo "$recent_history" | sed -E 's/\b[[:xdigit:]]{32,40}\b/REDACTED_HASH/g')
|
||||||
|
recent_history=$(echo "$recent_history" | sed -E 's/\b[0-9a-fA-F-]{36}\b/REDACTED_UUID/g')
|
||||||
|
recent_history=$(echo "$recent_history" | sed -E 's/\b[A-Za-z0-9]{16,40}\b/REDACTED_APIKEY/g')
|
||||||
|
echo "$recent_history"
|
||||||
|
}
|
||||||
|
|
||||||
|
_get_recent_files() {
|
||||||
|
local FILE_LIMIT=${ACSH_MAX_RECENT_FILES:-20}
|
||||||
|
find . -maxdepth 1 -type f -exec ls -ld {} + | sort -r | head -n "$FILE_LIMIT"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Rewritten _get_help_message using a heredoc to preserve formatting.
|
||||||
|
_get_help_message() {
|
||||||
|
local COMMAND HELP_INFO
|
||||||
|
COMMAND=$(echo "$1" | awk '{print $1}')
|
||||||
|
HELP_INFO=""
|
||||||
|
{
|
||||||
|
set +e
|
||||||
|
HELP_INFO=$(cat <<EOF
|
||||||
|
$($COMMAND --help 2>&1 || true)
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
set -e
|
||||||
|
} || HELP_INFO="'$COMMAND --help' not available"
|
||||||
|
echo "$HELP_INFO"
|
||||||
|
}
|
||||||
|
|
||||||
|
_build_prompt() {
|
||||||
|
local user_input command_history terminal_context help_message recent_files output_instructions other_environment_variables prompt
|
||||||
|
user_input="$*"
|
||||||
|
command_history=$(_get_clean_command_history)
|
||||||
|
terminal_context=$(_get_terminal_info)
|
||||||
|
help_message=$(_get_help_message "$user_input")
|
||||||
|
recent_files=$(_get_recent_files)
|
||||||
|
output_instructions=$(_get_output_instructions)
|
||||||
|
other_environment_variables=$(env | grep '=' | grep -v 'ACSH_' | awk -F= '{print $1}' | grep -v 'PWD\|OSTYPE\|BASH\|USER\|HOME\|TERM\|OLDPWD\|HOSTNAME')
|
||||||
|
|
||||||
|
prompt="User command: \`$user_input\`
|
||||||
|
|
||||||
|
# Terminal Context
|
||||||
|
## Environment variables
|
||||||
|
$terminal_context
|
||||||
|
|
||||||
|
Other defined environment variables
|
||||||
|
\`\`\`
|
||||||
|
$other_environment_variables
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
## History
|
||||||
|
Recently run commands (some information redacted):
|
||||||
|
\`\`\`
|
||||||
|
$command_history
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
## File system
|
||||||
|
Most recently modified files:
|
||||||
|
\`\`\`
|
||||||
|
$recent_files
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
## Help Information
|
||||||
|
$help_message
|
||||||
|
|
||||||
|
# Instructions
|
||||||
|
$output_instructions
|
||||||
|
"
|
||||||
|
echo "$prompt"
|
||||||
|
}
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Payload Building Functions #
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
build_common_payload() {
|
||||||
|
jq -n --arg model "$model" \
|
||||||
|
--arg temperature "$temperature" \
|
||||||
|
--arg system_prompt "$system_prompt" \
|
||||||
|
--arg prompt_content "$prompt_content" \
|
||||||
|
'{
|
||||||
|
model: $model,
|
||||||
|
messages: [
|
||||||
|
{role: "system", content: $system_prompt},
|
||||||
|
{role: "user", content: $prompt_content}
|
||||||
|
],
|
||||||
|
temperature: ($temperature | tonumber)
|
||||||
|
}'
|
||||||
|
}
|
||||||
|
|
||||||
|
_build_payload() {
|
||||||
|
local user_input prompt system_message_prompt payload acsh_prompt
|
||||||
|
local model temperature
|
||||||
|
model="${ACSH_MODEL:-gpt-4o}"
|
||||||
|
temperature="${ACSH_TEMPERATURE:-0.0}"
|
||||||
|
|
||||||
|
user_input="$1"
|
||||||
|
prompt=$(_build_prompt "$@")
|
||||||
|
system_message_prompt=$(_get_system_message_prompt)
|
||||||
|
|
||||||
|
acsh_prompt="# SYSTEM PROMPT
|
||||||
|
$system_message_prompt
|
||||||
|
# USER MESSAGE
|
||||||
|
$prompt"
|
||||||
|
export ACSH_PROMPT="$acsh_prompt"
|
||||||
|
|
||||||
|
prompt_content="$prompt"
|
||||||
|
system_prompt="$system_message_prompt"
|
||||||
|
|
||||||
|
local base_payload
|
||||||
|
base_payload=$(build_common_payload)
|
||||||
|
|
||||||
|
case "${ACSH_PROVIDER^^}" in
|
||||||
|
"OLLAMA")
|
||||||
|
payload=$(echo "$base_payload" | jq '. + {
|
||||||
|
format: "json",
|
||||||
|
stream: false,
|
||||||
|
options: {temperature: (.temperature | tonumber)}
|
||||||
|
}')
|
||||||
|
;;
|
||||||
|
"LMSTUDIO")
|
||||||
|
payload=$(echo "$base_payload" | jq '. + {response_format: {type: "json_object"}}')
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
payload=$(echo "$base_payload" | jq '. + {response_format: {type: "json_object"}}')
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
echo "$payload"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_request() {
|
||||||
|
local user_input response_body user_input_hash log_file prompt_tokens completion_tokens created api_cost
|
||||||
|
local prompt_tokens_int completion_tokens_int
|
||||||
|
user_input="$1"
|
||||||
|
response_body="$2"
|
||||||
|
user_input_hash=$(echo -n "$user_input" | md5sum | cut -d ' ' -f 1)
|
||||||
|
|
||||||
|
prompt_tokens=$(echo "$response_body" | jq -r '.usage.prompt_tokens')
|
||||||
|
prompt_tokens_int=$((prompt_tokens))
|
||||||
|
completion_tokens=$(echo "$response_body" | jq -r '.usage.completion_tokens')
|
||||||
|
completion_tokens_int=$((completion_tokens))
|
||||||
|
|
||||||
|
created=$(date +%s)
|
||||||
|
created=$(echo "$response_body" | jq -r ".created // $created")
|
||||||
|
api_cost=$(echo "$prompt_tokens_int * $ACSH_API_PROMPT_COST + $completion_tokens_int * $ACSH_API_COMPLETION_COST" | bc)
|
||||||
|
log_file=${ACSH_LOG_FILE:-"$HOME/.finish/finish.log"}
|
||||||
|
echo "$created,$user_input_hash,$prompt_tokens_int,$completion_tokens_int,$api_cost" >> "$log_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
openai_completion() {
|
||||||
|
local content status_code response_body default_user_input user_input api_key payload endpoint timeout attempt max_attempts
|
||||||
|
endpoint=${ACSH_ENDPOINT:-"http://localhost:1234/v1/chat/completions"}
|
||||||
|
timeout=${ACSH_TIMEOUT:-30}
|
||||||
|
default_user_input="Write two to six most likely commands given the provided information"
|
||||||
|
user_input=${*:-$default_user_input}
|
||||||
|
|
||||||
|
if [[ -z "$ACSH_ACTIVE_API_KEY" && ${ACSH_PROVIDER^^} != "OLLAMA" && ${ACSH_PROVIDER^^} != "LMSTUDIO" ]]; then
|
||||||
|
echo_error "ACSH_ACTIVE_API_KEY not set. Please set it with: export ${ACSH_PROVIDER^^}_API_KEY=<your-api-key>"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
api_key="${ACSH_ACTIVE_API_KEY}"
|
||||||
|
payload=$(_build_payload "$user_input")
|
||||||
|
|
||||||
|
max_attempts=2
|
||||||
|
attempt=1
|
||||||
|
while [ $attempt -le $max_attempts ]; do
|
||||||
|
if [[ "${ACSH_PROVIDER^^}" == "OLLAMA" ]]; then
|
||||||
|
response=$(\curl -s -m "$timeout" -w "\n%{http_code}" "$endpoint" --data "$payload")
|
||||||
|
else
|
||||||
|
response=$(\curl -s -m "$timeout" -w "\n%{http_code}" "$endpoint" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$payload")
|
||||||
|
fi
|
||||||
|
status_code=$(echo "$response" | tail -n1)
|
||||||
|
response_body=$(echo "$response" | sed '$d')
|
||||||
|
if [[ $status_code -eq 200 ]]; then
|
||||||
|
break
|
||||||
|
else
|
||||||
|
echo_error "API call failed with status $status_code. Retrying... (Attempt $attempt of $max_attempts)"
|
||||||
|
sleep 1
|
||||||
|
attempt=$((attempt+1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ $status_code -ne 200 ]]; then
|
||||||
|
case $status_code in
|
||||||
|
400) echo_error "Bad Request: The API request was invalid or malformed." ;;
|
||||||
|
401) echo_error "Unauthorized: The provided API key is invalid or missing." ;;
|
||||||
|
429) echo_error "Too Many Requests: The API rate limit has been exceeded." ;;
|
||||||
|
500) echo_error "Internal Server Error: An unexpected error occurred on the API server." ;;
|
||||||
|
*) echo_error "Unknown Error: Unexpected status code $status_code received. Response: $response_body" ;;
|
||||||
|
esac
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${ACSH_PROVIDER^^}" == "OLLAMA" ]]; then
|
||||||
|
content=$(echo "$response_body" | jq -r '.message.content')
|
||||||
|
content=$(echo "$content" | jq -r '.completions')
|
||||||
|
else
|
||||||
|
content=$(echo "$response_body" | jq -r '.choices[0].message.content')
|
||||||
|
content=$(echo "$content" | jq -r '.completions')
|
||||||
|
fi
|
||||||
|
|
||||||
|
local completions
|
||||||
|
completions=$(echo "$content" | jq -r '.[]' | grep -v '^$')
|
||||||
|
echo -n "$completions"
|
||||||
|
log_request "$user_input" "$response_body"
|
||||||
|
}
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Completion Functions #
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
_get_default_completion_function() {
|
||||||
|
local cmd="$1"
|
||||||
|
complete -p "$cmd" 2>/dev/null | awk -F' ' '{ for(i=1;i<=NF;i++) { if ($i ~ /^-F$/) { print $(i+1); exit; } } }'
|
||||||
|
}
|
||||||
|
|
||||||
|
_default_completion() {
|
||||||
|
local current_word="" first_word="" default_func
|
||||||
|
if [[ -n "${COMP_WORDS[*]}" ]]; then
|
||||||
|
first_word="${COMP_WORDS[0]}"
|
||||||
|
if [[ -n "$COMP_CWORD" && "$COMP_CWORD" -lt "${#COMP_WORDS[@]}" ]]; then
|
||||||
|
current_word="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
default_func=$(_get_default_completion_function "$first_word")
|
||||||
|
if [[ -n "$default_func" ]]; then
|
||||||
|
"$default_func"
|
||||||
|
else
|
||||||
|
local file_completions
|
||||||
|
if [[ -z "$current_word" ]]; then
|
||||||
|
file_completions=$(compgen -f -- || true)
|
||||||
|
else
|
||||||
|
file_completions=$(compgen -f -- "$current_word" || true)
|
||||||
|
fi
|
||||||
|
if [[ -n "$file_completions" ]]; then
|
||||||
|
readarray -t COMPREPLY <<<"$file_completions"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
list_cache() {
|
||||||
|
local cache_dir=${ACSH_CACHE_DIR:-"$HOME/.finish/cache"}
|
||||||
|
find "$cache_dir" -maxdepth 1 -type f -name "acsh-*" -printf '%T+ %p\n' | sort
|
||||||
|
}
|
||||||
|
|
||||||
|
_finishtesh() {
|
||||||
|
_init_completion || return
|
||||||
|
_default_completion
|
||||||
|
if [[ ${#COMPREPLY[@]} -eq 0 && $COMP_TYPE -eq 63 ]]; then
|
||||||
|
local completions user_input user_input_hash
|
||||||
|
acsh_load_config
|
||||||
|
if [[ -z "$ACSH_ACTIVE_API_KEY" && ${ACSH_PROVIDER^^} != "OLLAMA" && ${ACSH_PROVIDER^^} != "LMSTUDIO" ]]; then
|
||||||
|
local provider_key="${ACSH_PROVIDER}_API_KEY"
|
||||||
|
provider_key=$(echo "$provider_key" | tr '[:lower:]' '[:upper:]')
|
||||||
|
echo_error "${provider_key} is not set. Please set it using: export ${provider_key}=<your-api-key> or disable finish via: finish disable"
|
||||||
|
echo
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if [[ -n "${COMP_WORDS[*]}" ]]; then
|
||||||
|
command="${COMP_WORDS[0]}"
|
||||||
|
if [[ -n "$COMP_CWORD" && "$COMP_CWORD" -lt "${#COMP_WORDS[@]}" ]]; then
|
||||||
|
current="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
user_input="${COMP_LINE:-"$command $current"}"
|
||||||
|
user_input_hash=$(echo -n "$user_input" | md5sum | cut -d ' ' -f 1)
|
||||||
|
export ACSH_INPUT="$user_input"
|
||||||
|
export ACSH_PROMPT=
|
||||||
|
export ACSH_RESPONSE=
|
||||||
|
local cache_dir=${ACSH_CACHE_DIR:-"$HOME/.finish/cache"}
|
||||||
|
local cache_size=${ACSH_CACHE_SIZE:-100}
|
||||||
|
local cache_file="$cache_dir/acsh-$user_input_hash.txt"
|
||||||
|
if [[ -d "$cache_dir" && "$cache_size" -gt 0 && -f "$cache_file" ]]; then
|
||||||
|
completions=$(cat "$cache_file" || true)
|
||||||
|
touch "$cache_file"
|
||||||
|
else
|
||||||
|
echo -en "\e]12;green\a"
|
||||||
|
completions=$(openai_completion "$user_input" || true)
|
||||||
|
if [[ -z "$completions" ]]; then
|
||||||
|
echo -en "\e]12;red\a"
|
||||||
|
sleep 1
|
||||||
|
completions=$(openai_completion "$user_input" || true)
|
||||||
|
fi
|
||||||
|
echo -en "\e]12;white\a"
|
||||||
|
if [[ -d "$cache_dir" && "$cache_size" -gt 0 ]]; then
|
||||||
|
echo "$completions" > "$cache_file"
|
||||||
|
while [[ $(list_cache | wc -l) -gt "$cache_size" ]]; do
|
||||||
|
oldest=$(list_cache | head -n 1 | cut -d ' ' -f 2-)
|
||||||
|
rm "$oldest" || true
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
export ACSH_RESPONSE=$completions
|
||||||
|
if [[ -n "$completions" ]]; then
|
||||||
|
local num_rows
|
||||||
|
num_rows=$(echo "$completions" | wc -l)
|
||||||
|
COMPREPLY=()
|
||||||
|
if [[ $num_rows -eq 1 ]]; then
|
||||||
|
readarray -t COMPREPLY <<<"$(echo -n "${completions}" | sed "s/${command}[[:space:]]*//" | sed 's/:/\\:/g')"
|
||||||
|
else
|
||||||
|
completions=$(echo "$completions" | awk '{print NR". "$0}')
|
||||||
|
readarray -t COMPREPLY <<< "$completions"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [[ ${#COMPREPLY[@]} -eq 0 ]]; then
|
||||||
|
COMPREPLY=("$current")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# CLI Commands & Configuration Management #
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
show_help() {
|
||||||
|
echo_green "finish.sh - LLM Powered Bash Completion"
|
||||||
|
echo "Usage: finish [options] command"
|
||||||
|
echo " finish [options] install|remove|config|model|enable|disable|clear|usage|system|command|--help"
|
||||||
|
echo
|
||||||
|
echo "finish.sh enhances bash completion with LLM capabilities."
|
||||||
|
echo "Press Tab twice for suggestions."
|
||||||
|
echo "Commands:"
|
||||||
|
echo " command Run finish (simulate double Tab)"
|
||||||
|
echo " command --dry-run Show prompt without executing"
|
||||||
|
echo " model Change language model"
|
||||||
|
echo " usage Display usage stats"
|
||||||
|
echo " system Display system information"
|
||||||
|
echo " config Show or set configuration values"
|
||||||
|
echo " config set <key> <value> Set a config value"
|
||||||
|
echo " config reset Reset config to defaults"
|
||||||
|
echo " install Install finish to .bashrc"
|
||||||
|
echo " remove Remove installation from .bashrc"
|
||||||
|
echo " enable Enable finish"
|
||||||
|
echo " disable Disable finish"
|
||||||
|
echo " clear Clear cache and log files"
|
||||||
|
echo " --help Show this help message"
|
||||||
|
echo
|
||||||
|
echo "Submit issues at: https://git.appmodel.nl/Tour/finish/issues"
|
||||||
|
}
|
||||||
|
|
||||||
|
is_subshell() {
|
||||||
|
if [[ "$$" != "$BASHPID" ]]; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
show_config() {
|
||||||
|
local config_file="$HOME/.finish/config" term_width small_table
|
||||||
|
echo_green "finish.sh - Configuration and Settings - Version $ACSH_VERSION"
|
||||||
|
if is_subshell; then
|
||||||
|
echo " STATUS: Unknown. Run 'source finish config' to check status."
|
||||||
|
return
|
||||||
|
elif check_if_enabled; then
|
||||||
|
echo -e " STATUS: \033[32;5mEnabled\033[0m"
|
||||||
|
else
|
||||||
|
echo -e " STATUS: \033[31;5mDisabled\033[0m - Run 'source finish config' to verify."
|
||||||
|
fi
|
||||||
|
if [ ! -f "$config_file" ]; then
|
||||||
|
echo_error "Configuration file not found: $config_file. Run finish install."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
acsh_load_config
|
||||||
|
term_width=$(tput cols)
|
||||||
|
if [[ $term_width -gt 70 ]]; then
|
||||||
|
term_width=70; small_table=0
|
||||||
|
fi
|
||||||
|
if [[ $term_width -lt 40 ]]; then
|
||||||
|
term_width=70; small_table=1
|
||||||
|
fi
|
||||||
|
for config_var in $(compgen -v | grep ACSH_); do
|
||||||
|
if [[ $config_var == "ACSH_INPUT" || $config_var == "ACSH_PROMPT" || $config_var == "ACSH_RESPONSE" ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
config_value="${!config_var}"
|
||||||
|
if [[ ${config_var: -8} == "_API_KEY" ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
echo -en " $config_var:\e[90m"
|
||||||
|
if [[ $small_table -eq 1 ]]; then
|
||||||
|
echo -e "\n $config_value\e[0m"
|
||||||
|
else
|
||||||
|
printf '%s%*s' "" $((term_width - ${#config_var} - ${#config_value} - 3)) ''
|
||||||
|
echo -e "$config_value\e[0m"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo -e " ===================================================================="
|
||||||
|
for config_var in $(compgen -v | grep ACSH_); do
|
||||||
|
if [[ $config_var == "ACSH_INPUT" || $config_var == "ACSH_PROMPT" || $config_var == "ACSH_RESPONSE" ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [[ ${config_var: -8} != "_API_KEY" ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
echo -en " $config_var:\e[90m"
|
||||||
|
if [[ -z ${!config_var} ]]; then
|
||||||
|
config_value="UNSET"
|
||||||
|
echo -en "\e[31m"
|
||||||
|
else
|
||||||
|
rest=${!config_var:4}
|
||||||
|
config_value="${!config_var:0:4}...${rest: -4}"
|
||||||
|
echo -en "\e[32m"
|
||||||
|
fi
|
||||||
|
if [[ $small_table -eq 1 ]]; then
|
||||||
|
echo -e "\n $config_value\e[0m"
|
||||||
|
else
|
||||||
|
printf '%s%*s' "" $((term_width - ${#config_var} - ${#config_value} - 3)) ''
|
||||||
|
echo -e "$config_value\e[0m"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
set_config() {
|
||||||
|
local key="$1" value="$2" config_file="$HOME/.finish/config"
|
||||||
|
key=$(echo "$key" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
||||||
|
key=$(echo "$key" | tr '[:lower:]' '[:upper:]' | sed 's/[^A-Z0-9]/_/g')
|
||||||
|
if [ -z "$key" ]; then
|
||||||
|
echo_error "SyntaxError: expected 'finish config set <key> <value>'"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if [ ! -f "$config_file" ]; then
|
||||||
|
echo_error "Configuration file not found: $config_file. Run finishte install."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
sed -i "s|^\($key:\).*|\1 $value|" "$config_file"
|
||||||
|
acsh_load_config
|
||||||
|
}
|
||||||
|
|
||||||
|
config_command() {
|
||||||
|
local command config_file="$HOME/.finishte/config"
|
||||||
|
command="${*:2}"
|
||||||
|
if [ -z "$command" ]; then
|
||||||
|
show_config
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if [ "$2" == "set" ]; then
|
||||||
|
local key="$3" value="$4"
|
||||||
|
echo "Setting configuration key '$key' to '$value'"
|
||||||
|
set_config "$key" "$value"
|
||||||
|
echo_green "Configuration updated. Run 'finishte config' to view changes."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if [[ "$command" == "reset" ]]; then
|
||||||
|
echo "Resetting configuration to default values."
|
||||||
|
rm "$config_file" || true
|
||||||
|
build_config
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
echo_error "SyntaxError: expected 'finishte config set <key> <value>' or 'finishte config reset'"
|
||||||
|
}
|
||||||
|
|
||||||
|
build_config() {
|
||||||
|
local config_file="$HOME/.finishte/config" default_config
|
||||||
|
if [ ! -f "$config_file" ]; then
|
||||||
|
echo "Creating default configuration file at ~/.finishte/config"
|
||||||
|
default_config="# ~/.finishte/config
|
||||||
|
|
||||||
|
# Model configuration
|
||||||
|
provider: lmstudio
|
||||||
|
model: darkidol-llama-3.1-8b-instruct-1.3-uncensored_gguf:2
|
||||||
|
temperature: 0.0
|
||||||
|
endpoint: http://localhost:1234/v1/chat/completions
|
||||||
|
api_prompt_cost: 0.000000
|
||||||
|
api_completion_cost: 0.000000
|
||||||
|
|
||||||
|
# Max history and recent files
|
||||||
|
max_history_commands: 20
|
||||||
|
max_recent_files: 20
|
||||||
|
|
||||||
|
# Cache settings
|
||||||
|
cache_dir: $HOME/.finishte/cache
|
||||||
|
cache_size: 10
|
||||||
|
|
||||||
|
# Logging settings
|
||||||
|
log_file: $HOME/.finishte/finishte.log"
|
||||||
|
echo "$default_config" > "$config_file"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
acsh_load_config() {
|
||||||
|
local config_file="$HOME/.finishte/config" key value
|
||||||
|
if [ -f "$config_file" ]; then
|
||||||
|
while IFS=':' read -r key value; do
|
||||||
|
if [[ $key =~ ^# ]] || [[ -z $key ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
key=$(echo "$key" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
||||||
|
value=$(echo "$value" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
||||||
|
key=$(echo "$key" | tr '[:lower:]' '[:upper:]' | sed 's/[^A-Z0-9]/_/g')
|
||||||
|
if [[ -n $value ]]; then
|
||||||
|
export "ACSH_$key"="$value"
|
||||||
|
fi
|
||||||
|
done < "$config_file"
|
||||||
|
if [[ -z "$ACSH_OLLAMA_API_KEY" && -n "$LLM_API_KEY" ]]; then
|
||||||
|
export ACSH_OLLAMA_API_KEY="$LLM_API_KEY"
|
||||||
|
fi
|
||||||
|
# If the custom API key was set, map it to OLLAMA if needed.
|
||||||
|
if [[ -z "$ACSH_OLLAMA_API_KEY" && -n "$ACSH_CUSTOM_API_KEY" ]]; then
|
||||||
|
export ACSH_OLLAMA_API_KEY="$ACSH_CUSTOM_API_KEY"
|
||||||
|
fi
|
||||||
|
case "${ACSH_PROVIDER:-lmstudio}" in
|
||||||
|
"ollama") export ACSH_ACTIVE_API_KEY="$ACSH_OLLAMA_API_KEY" ;;
|
||||||
|
"lmstudio") export ACSH_ACTIVE_API_KEY="" ;;
|
||||||
|
*) echo_error "Unknown provider: $ACSH_PROVIDER" ;;
|
||||||
|
esac
|
||||||
|
else
|
||||||
|
echo "Configuration file not found: $config_file"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
install_command() {
|
||||||
|
local bashrc_file="$HOME/.bashrc" finishte_setup="source finishte enable" finishte_cli_setup="complete -F _finishtesh_cli finishte"
|
||||||
|
if ! command -v finishte &>/dev/null; then
|
||||||
|
echo_error "finishte.sh not in PATH. Follow install instructions at https://git.appmodel.nl/Tour/finish"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if [[ ! -d "$HOME/.finishte" ]]; then
|
||||||
|
echo "Creating ~/.finishte directory"
|
||||||
|
mkdir -p "$HOME/.finishte"
|
||||||
|
fi
|
||||||
|
local cache_dir=${ACSH_CACHE_DIR:-"$HOME/.finishte/cache"}
|
||||||
|
if [[ ! -d "$cache_dir" ]]; then
|
||||||
|
mkdir -p "$cache_dir"
|
||||||
|
fi
|
||||||
|
build_config
|
||||||
|
acsh_load_config
|
||||||
|
if ! grep -qF "$finishte_setup" "$bashrc_file"; then
|
||||||
|
echo -e "# finishte.sh" >> "$bashrc_file"
|
||||||
|
echo -e "$finishte_setup\n" >> "$bashrc_file"
|
||||||
|
echo "Added finishte.sh setup to $bashrc_file"
|
||||||
|
else
|
||||||
|
echo "finishte.sh setup already exists in $bashrc_file"
|
||||||
|
fi
|
||||||
|
if ! grep -qF "$finishte_cli_setup" "$bashrc_file"; then
|
||||||
|
echo -e "# finishte.sh CLI" >> "$bashrc_file"
|
||||||
|
echo -e "$finishte_cli_setup\n" >> "$bashrc_file"
|
||||||
|
echo "Added finishte CLI completion to $bashrc_file"
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
echo_green "finishte.sh - Version $ACSH_VERSION installation complete."
|
||||||
|
echo -e "Run: source $bashrc_file to enable finishte."
|
||||||
|
echo -e "Then run: finishte model to select a language model."
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_command() {
|
||||||
|
local config_file="$HOME/.finishte/config" cache_dir=${ACSH_CACHE_DIR:-"$HOME/.finishte/cache"} log_file=${ACSH_LOG_FILE:-"$HOME/.finishte/finishte.log"} bashrc_file="$HOME/.bashrc"
|
||||||
|
echo_green "Removing finishte.sh installation..."
|
||||||
|
[ -f "$config_file" ] && { rm "$config_file"; echo "Removed: $config_file"; }
|
||||||
|
[ -d "$cache_dir" ] && { rm -rf "$cache_dir"; echo "Removed: $cache_dir"; }
|
||||||
|
[ -f "$log_file" ] && { rm "$log_file"; echo "Removed: $log_file"; }
|
||||||
|
if [ -d "$HOME/.finishte" ]; then
|
||||||
|
if [ -z "$(ls -A "$HOME/.finishte")" ]; then
|
||||||
|
rmdir "$HOME/.finishte"
|
||||||
|
echo "Removed: $HOME/.finishte"
|
||||||
|
else
|
||||||
|
echo "Skipped removing $HOME/.finishte (not empty)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [ -f "$bashrc_file" ]; then
|
||||||
|
if grep -qF "source finishte enable" "$bashrc_file"; then
|
||||||
|
sed -i '/# finishte.sh/d' "$bashrc_file"
|
||||||
|
sed -i '/finishte/d' "$bashrc_file"
|
||||||
|
echo "Removed finishte.sh setup from $bashrc_file"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
local finishte_script
|
||||||
|
finishte_script=$(command -v finishte)
|
||||||
|
if [ -n "$finishte_script" ]; then
|
||||||
|
echo "finishte script is at: $finishte_script"
|
||||||
|
if [ "$1" == "-y" ]; then
|
||||||
|
rm "$finishte_script"
|
||||||
|
echo "Removed: $finishte_script"
|
||||||
|
else
|
||||||
|
read -r -p "Remove the finishte script? (y/n): " confirm
|
||||||
|
if [[ $confirm == "y" ]]; then
|
||||||
|
rm "$finishte_script"
|
||||||
|
echo "Removed: $finishte_script"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo "Uninstallation complete."
|
||||||
|
}
|
||||||
|
|
||||||
|
check_if_enabled() {
|
||||||
|
local is_enabled
|
||||||
|
is_enabled=$(complete -p | grep _finishtesh | grep -cv _finishtesh_cli)
|
||||||
|
(( is_enabled > 0 )) && return 0 || return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
_finishtesh_cli() {
|
||||||
|
if [[ -n "${COMP_WORDS[*]}" ]]; then
|
||||||
|
command="${COMP_WORDS[0]}"
|
||||||
|
if [[ -n "$COMP_CWORD" && "$COMP_CWORD" -lt "${#COMP_WORDS[@]}" ]]; then
|
||||||
|
current="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [[ $current == "config" ]]; then
|
||||||
|
readarray -t COMPREPLY <<< "set
|
||||||
|
reset"
|
||||||
|
return
|
||||||
|
elif [[ $current == "command" ]]; then
|
||||||
|
readarray -t COMPREPLY <<< "command --dry-run"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if [[ -z "$current" ]]; then
|
||||||
|
readarray -t COMPREPLY <<< "install
|
||||||
|
remove
|
||||||
|
config
|
||||||
|
enable
|
||||||
|
disable
|
||||||
|
clear
|
||||||
|
usage
|
||||||
|
system
|
||||||
|
command
|
||||||
|
model
|
||||||
|
--help"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
enable_command() {
|
||||||
|
if check_if_enabled; then
|
||||||
|
echo_green "Reloading finishte.sh..."
|
||||||
|
disable_command
|
||||||
|
fi
|
||||||
|
acsh_load_config
|
||||||
|
complete -D -E -F _finishtesh -o nospace
|
||||||
|
}
|
||||||
|
|
||||||
|
disable_command() {
|
||||||
|
if check_if_enabled; then
|
||||||
|
complete -F _completion_loader -D
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
command_command() {
|
||||||
|
local args=("$@")
|
||||||
|
for ((i = 0; i < ${#args[@]}; i++)); do
|
||||||
|
if [ "${args[i]}" == "--dry-run" ]; then
|
||||||
|
args[i]=""
|
||||||
|
_build_prompt "${args[@]}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
openai_completion "$@" || true
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
clear_command() {
|
||||||
|
local cache_dir=${ACSH_CACHE_DIR:-"$HOME/.finishte/cache"} log_file=${ACSH_LOG_FILE:-"$HOME/.finishte/finishte.log"}
|
||||||
|
echo "This will clear the cache and log file."
|
||||||
|
echo -e "Cache directory: \e[31m$cache_dir\e[0m"
|
||||||
|
echo -e "Log file: \e[31m$log_file\e[0m"
|
||||||
|
read -r -p "Are you sure? (y/n): " confirm
|
||||||
|
if [[ $confirm != "y" ]]; then
|
||||||
|
echo "Aborted."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if [ -d "$cache_dir" ]; then
|
||||||
|
local cache_files
|
||||||
|
cache_files=$(list_cache)
|
||||||
|
if [ -n "$cache_files" ]; then
|
||||||
|
while read -r line; do
|
||||||
|
file=$(echo "$line" | cut -d ' ' -f 2-)
|
||||||
|
rm "$file"
|
||||||
|
echo "Removed: $file"
|
||||||
|
done <<< "$cache_files"
|
||||||
|
echo "Cleared cache in: $cache_dir"
|
||||||
|
else
|
||||||
|
echo "Cache is empty."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
[ -f "$log_file" ] && { rm "$log_file"; echo "Removed: $log_file"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
usage_command() {
|
||||||
|
local log_file=${ACSH_LOG_FILE:-"$HOME/.finishte/finishte.log"} cache_dir=${ACSH_CACHE_DIR:-"$HOME/.finishte/cache"}
|
||||||
|
local cache_size number_of_lines api_cost avg_api_cost
|
||||||
|
cache_size=$(list_cache | wc -l)
|
||||||
|
echo_green "finishte.sh - Usage Information"
|
||||||
|
echo
|
||||||
|
echo -n "Log file: "; echo -e "\e[90m$log_file\e[0m"
|
||||||
|
if [ ! -f "$log_file" ]; then
|
||||||
|
number_of_lines=0
|
||||||
|
api_cost=0
|
||||||
|
avg_api_cost=0
|
||||||
|
else
|
||||||
|
number_of_lines=$(wc -l < "$log_file")
|
||||||
|
api_cost=$(awk -F, '{sum += $5} END {print sum}' "$log_file")
|
||||||
|
avg_api_cost=$(echo "$api_cost / $number_of_lines" | bc -l)
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
echo -e "\tUsage count:\t\e[32m$number_of_lines\e[0m"
|
||||||
|
echo -e "\tAvg Cost:\t\$$(printf "%.4f" "$avg_api_cost")"
|
||||||
|
echo -e "\tTotal Cost:\t\e[31m\$$(printf "%.4f" "$api_cost")\e[0m"
|
||||||
|
echo
|
||||||
|
echo -n "Cache Size: $cache_size of ${ACSH_CACHE_SIZE:-10} in "; echo -e "\e[90m$cache_dir\e[0m"
|
||||||
|
echo "To clear log and cache, run: finishte clear"
|
||||||
|
}
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Enhanced Interactive Menu UX #
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
get_key() {
|
||||||
|
IFS= read -rsn1 key 2>/dev/null >&2
|
||||||
|
if [[ $key == $'\x1b' ]]; then
|
||||||
|
read -rsn2 key
|
||||||
|
if [[ $key == [A ]]; then echo up; fi
|
||||||
|
if [[ $key == [B ]]; then echo down; fi
|
||||||
|
if [[ $key == q ]]; then echo q; fi
|
||||||
|
elif [[ $key == "q" ]]; then
|
||||||
|
echo q
|
||||||
|
else
|
||||||
|
echo "$key"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
menu_selector() {
|
||||||
|
options=("$@")
|
||||||
|
selected=0
|
||||||
|
show_menu() {
|
||||||
|
echo
|
||||||
|
echo "Select a Language Model (Up/Down arrows, Enter to select, 'q' to quit):"
|
||||||
|
for i in "${!options[@]}"; do
|
||||||
|
if [[ $i -eq $selected ]]; then
|
||||||
|
echo -e "\e[1;32m> ${options[i]}\e[0m"
|
||||||
|
else
|
||||||
|
echo " ${options[i]}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
tput sc
|
||||||
|
while true; do
|
||||||
|
tput rc; tput ed
|
||||||
|
show_menu
|
||||||
|
key=$(get_key)
|
||||||
|
case $key in
|
||||||
|
up)
|
||||||
|
((selected--))
|
||||||
|
if ((selected < 0)); then
|
||||||
|
selected=$((${#options[@]} - 1))
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
down)
|
||||||
|
((selected++))
|
||||||
|
if ((selected >= ${#options[@]})); then
|
||||||
|
selected=0
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
q)
|
||||||
|
echo "Selection canceled."
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
"")
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
clear
|
||||||
|
return $selected
|
||||||
|
}
|
||||||
|
|
||||||
|
model_command() {
|
||||||
|
clear
|
||||||
|
local selected_model options=()
|
||||||
|
if [[ $# -ne 3 ]]; then
|
||||||
|
mapfile -t sorted_keys < <(for key in "${!_finishte_modellist[@]}"; do echo "$key"; done | sort)
|
||||||
|
for key in "${sorted_keys[@]}"; do
|
||||||
|
options+=("$key")
|
||||||
|
done
|
||||||
|
echo -e "\e[1;32mfinishte.sh - Model Configuration\e[0m"
|
||||||
|
menu_selector "${options[@]}"
|
||||||
|
selected_option=$?
|
||||||
|
if [[ $selected_option -eq 1 ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
selected_model="${options[selected_option]}"
|
||||||
|
selected_value="${_finishte_modellist[$selected_model]}"
|
||||||
|
else
|
||||||
|
provider="$2"
|
||||||
|
model_name="$3"
|
||||||
|
selected_value="${_finishte_modellist["$provider: $model_name"]}"
|
||||||
|
if [[ -z "$selected_value" ]]; then
|
||||||
|
echo "ERROR: Invalid provider or model name."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
set_config "model" "$(echo "$selected_value" | jq -r '.model')"
|
||||||
|
set_config "endpoint" "$(echo "$selected_value" | jq -r '.endpoint')"
|
||||||
|
set_config "provider" "$(echo "$selected_value" | jq -r '.provider')"
|
||||||
|
prompt_cost=$(echo "$selected_value" | jq -r '.prompt_cost' | awk '{printf "%.8f", $1}')
|
||||||
|
completion_cost=$(echo "$selected_value" | jq -r '.completion_cost' | awk '{printf "%.8f", $1}')
|
||||||
|
set_config "api_prompt_cost" "$prompt_cost"
|
||||||
|
set_config "api_completion_cost" "$completion_cost"
|
||||||
|
model="${ACSH_MODEL:-ERROR}"
|
||||||
|
temperature=$(echo "${ACSH_TEMPERATURE:-0.0}" | awk '{printf "%.3f", $1}')
|
||||||
|
echo -e "Provider:\t\e[90m$ACSH_PROVIDER\e[0m"
|
||||||
|
echo -e "Model:\t\t\e[90m$model\e[0m"
|
||||||
|
echo -e "Temperature:\t\e[90m$temperature\e[0m"
|
||||||
|
echo
|
||||||
|
echo -e "Cost/token:\t\e[90mprompt: \$$ACSH_API_PROMPT_COST, completion: \$$ACSH_API_COMPLETION_COST\e[0m"
|
||||||
|
echo -e "Endpoint:\t\e[90m$ACSH_ENDPOINT\e[0m"
|
||||||
|
if [[ ${ACSH_PROVIDER^^} == "OLLAMA" || ${ACSH_PROVIDER^^} == "LMSTUDIO" ]]; then
|
||||||
|
echo "To set a custom endpoint:"
|
||||||
|
echo -e "\t\e[34mfinishte config set endpoint <your-url>\e[0m"
|
||||||
|
echo "Other models can be set with:"
|
||||||
|
echo -e "\t\e[34mfinishte config set model <model-name>\e[0m"
|
||||||
|
fi
|
||||||
|
echo "To change temperature:"
|
||||||
|
echo -e "\t\e[90mfinishte config set temperature <temperature>\e[0m"
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# CLI ENTRY POINT #
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
"--help")
|
||||||
|
show_help
|
||||||
|
;;
|
||||||
|
system)
|
||||||
|
_system_info
|
||||||
|
;;
|
||||||
|
install)
|
||||||
|
install_command
|
||||||
|
;;
|
||||||
|
remove)
|
||||||
|
remove_command "$@"
|
||||||
|
;;
|
||||||
|
clear)
|
||||||
|
clear_command
|
||||||
|
;;
|
||||||
|
usage)
|
||||||
|
usage_command
|
||||||
|
;;
|
||||||
|
model)
|
||||||
|
model_command "$@"
|
||||||
|
;;
|
||||||
|
config)
|
||||||
|
config_command "$@"
|
||||||
|
;;
|
||||||
|
enable)
|
||||||
|
enable_command
|
||||||
|
;;
|
||||||
|
disable)
|
||||||
|
disable_command
|
||||||
|
;;
|
||||||
|
command)
|
||||||
|
command_command "$@"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [[ -n "$1" ]]; then
|
||||||
|
echo_error "Unknown command $1 - run 'finishte --help' for usage or visit https://finishte.sh"
|
||||||
|
else
|
||||||
|
echo_green "finishte.sh - LLM Powered Bash Completion - Version $ACSH_VERSION - https://finishte.sh"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
47
homebrew/finish.rb
Normal file
47
homebrew/finish.rb
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
class finishteSh < Formula
|
||||||
|
desc "LLM-powered command-line finishtion"
|
||||||
|
homepage "https://git.appmodel.nl/Tour/finishte"
|
||||||
|
url "https://git.appmodel.nl/Tour/finish/archive/v0.5.0.tar.gz"
|
||||||
|
sha256 "" # Update with actual SHA256 of the release tarball
|
||||||
|
license "BSD-2-Clause"
|
||||||
|
version "0.5.0"
|
||||||
|
|
||||||
|
depends_on "jq"
|
||||||
|
depends_on "bash" => :optional
|
||||||
|
depends_on "bash-completion@2" => :optional
|
||||||
|
|
||||||
|
def install
|
||||||
|
bin.install "finishte.sh" => "finishte"
|
||||||
|
doc.install "README.md"
|
||||||
|
doc.install "LICENSE"
|
||||||
|
end
|
||||||
|
|
||||||
|
def caveats
|
||||||
|
<<~EOS
|
||||||
|
To complete the installation:
|
||||||
|
|
||||||
|
1. Run the installer:
|
||||||
|
finishte install
|
||||||
|
|
||||||
|
2. Reload your shell:
|
||||||
|
source ~/.bashrc # for bash
|
||||||
|
source ~/.zshrc # for zsh
|
||||||
|
|
||||||
|
3. Select a language model:
|
||||||
|
finishte model
|
||||||
|
|
||||||
|
You'll need LM-Studio or Ollama running locally to use finishte.sh.
|
||||||
|
|
||||||
|
LM-Studio: https://lmstudio.ai
|
||||||
|
Ollama: https://ollama.ai
|
||||||
|
|
||||||
|
For more information, visit:
|
||||||
|
https:/git.appmodel.nl/Tour/finishte
|
||||||
|
EOS
|
||||||
|
end
|
||||||
|
|
||||||
|
test do
|
||||||
|
system "#{bin}/finishte", "--help"
|
||||||
|
assert_match "finishte.sh", shell_output("#{bin}/finishte --help")
|
||||||
|
end
|
||||||
|
end
|
||||||
3
run_tests.sh
Normal file
3
run_tests.sh
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
bats tests/
|
||||||
43
tests/test_finish.bats
Normal file
43
tests/test_finish.bats
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#!/usr/bin/env bats
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
# Install finishte.sh and run testing against the main branch
|
||||||
|
wget -qO- https://git.appmodel.nl/Tour/finish/raw/branch/main/docs/install.sh | bash -s -- main
|
||||||
|
|
||||||
|
# Source bashrc to make sure finishte is available in the current session
|
||||||
|
source ~/.bashrc
|
||||||
|
|
||||||
|
# Configure for local LM-Studio
|
||||||
|
finishte config set provider lmstudio
|
||||||
|
finishte config set endpoint http://localhost:1234/v1/chat/completions
|
||||||
|
finishte config set model darkidol-llama-3.1-8b-instruct-1.3-uncensored_gguf:2
|
||||||
|
}
|
||||||
|
|
||||||
|
teardown() {
|
||||||
|
# Remove finishte.sh installation
|
||||||
|
finishte remove -y
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "which finishte returns something" {
|
||||||
|
run which finishte
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[ -n "$output" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "finishte returns a string containing finishte.sh (case insensitive)" {
|
||||||
|
run finishte
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" =~ [Aa]utocomplete\.sh ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "finishte config should have lmstudio provider" {
|
||||||
|
run finishte config
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" =~ lmstudio ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "finishte command 'ls # show largest files' should return something" {
|
||||||
|
run finishte command "ls # show largest files"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[ -n "$output" ]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user