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